Niezawodny sposób na skrypt bash, aby uzyskać pełną ścieżkę do siebie? [duplikat]

To pytanie ma już odpowiedź tutaj:

Mam skrypt Basha, który musi znać pełną ścieżkę. Staram się znaleźć ogólnie zgodny sposób, aby to zrobić, nie kończąc na względnych lub funky wyglądających ścieżek. Muszę tylko wspierać Basha, a nie sh, csh, itd.

Co znalazłem do tej pory:

  1. Przyjęta odpowiedź na " uzyskanie katalogu źródłowego skryptu Bash z wewnątrz " dotyczy uzyskania ścieżki skryptu przez dirname $0, co jest w porządku, ale może to zwrócić względną ścieżkę (jak .), co jest problemem, jeśli chcesz zmienić katalogi w skrypcie, a ścieżka nadal wskazuje na katalog skryptu. / Align = "left" /

  2. The accepted odpowiedź na "bash script absolute path with OSX" (OS X specyficzne, ale odpowiedź działa niezależnie) daje funkcję, która będzie testować, aby sprawdzić, czy $0 wygląda względnie i jeśli tak, będzie pre-pend $PWD do niego. Ale wynik może nadal zawierać bity względne (chociaż ogólnie jest absolutny) - na przykład, jeśli skrypt znajduje się t w katalogu /usr/bin, a Ty jesteś w /usr i wpisujesz bin/../bin/t, aby go uruchomić (tak, to jest zawiłe), kończysz z /usr/bin/../bin jako ścieżką do katalogu skryptu. Które Działa , ale...

  3. Rozwiązanie readlink na tej stronie , które wygląda tak:

    # Absolute path to this script. /home/user/bin/foo.sh
    SCRIPT=$(readlink -f $0)
    # Absolute path this script is in. /home/user/bin
    SCRIPTPATH=`dirname $SCRIPT`
    

    Ale readlink to nie POSIX i najwyraźniej rozwiązanie opiera się na readlink GNU, gdzie BSD nie będzie działać z jakiegoś powodu (nie mam dostępu do systemu podobnego do BSD do sprawdzenia).

Więc, różne sposoby na to, ale wszystkie mają swoje zastrzeżenia.

Jaki byłby lepszy sposób? Gdzie "lepsze" oznacza:

  • daje mi absolutna ścieżka.
  • usuwa funky bity, nawet gdy są wywoływane w zawiły sposób(patrz komentarz na #2 powyżej). (Np. przynajmniej umiarkowanie kanoniczna ścieżka.)
  • opiera się tylko na bash-isms lub rzeczach, które są prawie pewne, że będą na najpopularniejszych systemach * nix (GNU / Linux, BSD i systemach podobnych do BSD, takich jak OS X, itp.).
  • unika wywoływania zewnętrznych programów, jeśli to możliwe(np. preferuje wbudowane bash).
  • (Updated , thanks for the heads up, wich ) nie musi rozwiązywać dowiązań symbolicznych (w rzeczywistości wolałbym zostawić je w spokoju, ale to nie jest wymóg).
 526
Author: Community, 2011-01-23

23 answers

Oto co wymyśliłem (edit: plus kilka poprawek dostarczonych przez sfstewman, levigroker, Kyle Strand i Rob Kennedy ), to wydaje się w większości pasować do moich "lepszych" kryteriów:

SCRIPTPATH="$( cd "$(dirname "$0")" ; pwd -P )"

Ta linia SCRIPTPATH wydaje się szczególnie okrężna, ale potrzebujemy jej raczej niż SCRIPTPATH=`pwd`, aby prawidłowo obsługiwać spacje i dowiązania symboliczne.

Zauważ również, że sytuacje Ezoteryczne, takie jak wykonywanie skryptu, który nie pochodzi z pliku w dostępnym systemie plików w ogóle (co jest całkowicie możliwe), nie jest tam zaspokajane (lub w żadnej z innych odpowiedzi widziałem).

 417
Author: T.J. Crowder,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2017-11-07 06:40:00

Dziwię się, że nie wspomniano tu o komendzie realpath. Rozumiem, że jest szeroko przenośny / portowany.

Twoje początkowe rozwiązanie staje się:

SCRIPT=`realpath $0`
SCRIPTPATH=`dirname $SCRIPT`

I pozostawić dowiązania symboliczne nierozwiązane zgodnie z Twoimi preferencjami:

SCRIPT=`realpath -s $0`
SCRIPTPATH=`dirname $SCRIPT`
 160
Author: Darshan Rivka Whittle,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2012-06-20 07:11:49

Najprostszym sposobem, jaki znalazłem, aby uzyskać pełną ścieżkę kanoniczną w bash jest użycie cd i pwd:

ABSOLUTE_PATH="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)/$(basename "${BASH_SOURCE[0]}")"

Użycie ${BASH_SOURCE[0]} zamiast $0 powoduje takie samo zachowanie niezależnie od tego, czy skrypt jest wywoływany jako <name> czy source <name>

 138
Author: Andrew Norrie,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2016-02-18 19:41:47

Musiałem dzisiaj wrócić do tego problemu i znalazłem https://stackoverflow.com/a/246128/1034080 . opracowuje rozwiązanie, które używałem również w przeszłości .

DIR="$( cd "$( dirname "${BASH_SOURCE[0]}" )" && pwd )"

W linked answer jest więcej wariantów, np. w przypadku, gdy sam skrypt jest dowiązaniem symbolicznym.

 39
Author: Felix Rabe,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2017-05-23 12:10:48

Get absolute path of shell script

Nie używa opcji -f w readlink, dlatego powinna działać w bsd / mac-osx

Podpory

  • źródło ./ script (wywołany przez . operator kropki)
  • absolutna Ścieżka / Ścieżka / do / skryptu
  • ścieżka względna jak ./ script
  • / path / dir1/../ dir2 / dir3/./ script
  • wywołane z dowiązania symbolicznego
  • gdy dowiązanie symboliczne jest zagnieżdżone np.) foo->dir1/dir2/bar bar->./../doe doe->script
  • gdy wywołujący zmienia Skrypty nazwa

Szukam przypadków narożnych, w których ten kod nie działa . Proszę dać mi znać.

Kod

pushd . > /dev/null
SCRIPT_PATH="${BASH_SOURCE[0]}";
while([ -h "${SCRIPT_PATH}" ]); do
    cd "`dirname "${SCRIPT_PATH}"`"
    SCRIPT_PATH="$(readlink "`basename "${SCRIPT_PATH}"`")";
done
cd "`dirname "${SCRIPT_PATH}"`" > /dev/null
SCRIPT_PATH="`pwd`";
popd  > /dev/null
echo "srcipt=[${SCRIPT_PATH}]"
echo "pwd   =[`pwd`]"

Znane issuse

Skrypt musi być gdzieś na dysku , niech będzie przez sieć. jeśli spróbujesz uruchomić ten skrypt z rury to nie zadziała

wget -o /dev/null -O - http://host.domain/dir/script.sh |bash
Technicznie rzecz biorąc, jest niezdefiniowany.
Praktycznie rzecz biorąc, nie ma rozsądnego sposobu, aby to wykryć. (co-process nie może uzyskać dostępu do ENV rodzica)
 35
Author: GreenFox,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2013-11-29 18:10:34

A co z używaniem:

SCRIPT_PATH=$(dirname `which $0`)

which wypisuje na stdout pełną ścieżkę pliku wykonywalnego, który byłby wykonywany, gdy przekazany argument został wprowadzony w wierszu polecenia powłoki (co zawiera $0)

dirname usuwa przyrostek nie-katalogowy z nazwy pliku

Stąd to, co kończy się z pełną ścieżką do skryptu, bez względu na to, czy ścieżka została określona, czy nie.

 30
Author: Matt,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2014-06-04 18:21:05

Ponieważ realpath nie jest domyślnie zainstalowany na moim systemie Linux, działa dla mnie:

SCRIPT="$(readlink --canonicalize-existing "$0")"
SCRIPTPATH="$(dirname "$SCRIPT")"

$SCRIPT będzie zawierać prawdziwą ścieżkę do skryptu i $SCRIPTPATH prawdziwą ścieżkę do katalogu zawierającego skrypt.

Przed użyciem przeczytaj komentarze tej odpowiedzi .

 24
Author: ypid,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2017-05-23 12:02:48

ODPOWIEDŹ na to pytanie bardzo późno, ale używam:

SCRIPT=$( readlink -m $( type -p $0 ))      # Full path to script
BASE_DIR=`dirname ${SCRIPT}`                # Directory script is run in
NAME=`basename ${SCRIPT}`                   # Actual name of script even if linked
 11
Author: Stormcloud,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2014-03-24 16:11:39

Umieściliśmy nasz własny produkt realpath-lib na Githubie do bezpłatnego i nieobciążonego użytku społeczności.

Bezwstydna wtyczka, ale z tą biblioteką Basha możesz:

get_realpath <absolute|relative|symlink|local file>

Ta funkcja jest rdzeniem biblioteki:

function get_realpath() {

if [[ -f "$1" ]]
then 
    # file *must* exist
    if cd "$(echo "${1%/*}")" &>/dev/null
    then 
        # file *may* not be local
        # exception is ./file.ext
        # try 'cd .; cd -;' *works!*
        local tmppwd="$PWD"
        cd - &>/dev/null
    else 
        # file *must* be local
        local tmppwd="$PWD"
    fi
else 
    # file *cannot* exist
    return 1 # failure
fi

# reassemble realpath
echo "$tmppwd"/"${1##*/}"
return 0 # success

}

Nie wymaga żadnych zewnętrznych zależności, tylko Bash 4+. Zawiera również funkcje do get_dirname, get_filename, get_stemname and validate_path validate_realpath. Jest darmowy, czysty, prosty i dobrze udokumentowany, więc może być również używany do celów edukacyjnych i bez wątpienia można to poprawić. Spróbuj na różnych platformach.

Update: po pewnym przejrzeniu i przetestowaniu zastąpiliśmy powyższą funkcję czymś, co osiąga ten sam wynik (bez użycia dirname, tylko czysty Bash), ale z lepszą wydajnością:

function get_realpath() {

    [[ ! -f "$1" ]] && return 1 # failure : file does not exist.
    [[ -n "$no_symlinks" ]] && local pwdp='pwd -P' || local pwdp='pwd' # do symlinks.
    echo "$( cd "$( echo "${1%/*}" )" 2>/dev/null; $pwdp )"/"${1##*/}" # echo result.
    return 0 # success

}

Obejmuje to również ustawienie środowiska no_symlinks, które zapewnia możliwość rozwiązywania dowiązań symbolicznych do fizycznego systemu. Domyślnie zachowuje dowiązania symboliczne nienaruszone.

 7
Author: AsymLabs,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2013-10-05 13:15:16

Możesz spróbować zdefiniować następującą zmienną:

CWD="$(cd -P -- "$(dirname -- "$0")" && pwd -P)"

Lub możesz wypróbować następującą funkcję w bash:

realpath () {
  [[ $1 = /* ]] && echo "$1" || echo "$PWD/${1#./}"
}

Funkcja ta przyjmuje 1 argument. Jeśli argument ma już ścieżkę bezwzględną, wypisuje ją taką jaka jest, w przeciwnym razie wypisuje zmienną $PWD + argument nazwy pliku (bez prefiksu ./).

Powiązane:

 5
Author: kenorb,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2017-05-23 12:18:23

Sh:

SCRIPT_HOME=`dirname $0 | while read a; do cd $a && pwd && break; done`
 5
Author: Haruo Kinoshita,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2016-11-11 18:48:34

Po prostu: BASEDIR=$(readlink -f $0 | xargs dirname)

Brak fantazyjnych operatorów

HIH.
 4
Author: poussma,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2013-12-03 13:59:03

Być może przyjęta odpowiedź na poniższe pytanie może być pomocna.

Jak mogę sprawdzić zachowanie READLINK-F GNU na komputerze Mac?

Biorąc pod uwagę, że po prostu chcesz kanonikować nazwę otrzymaną z połączenia $ PWD i $0 (zakładając, że $0 nie jest absolutne na początek), po prostu użyj serii zamienników regex wzdłuż linii abs_dir=${abs_dir//\/.\//\/} i takich.

Tak, Wiem, że wygląda okropnie, ale zadziała i jest czystym bashem.

 3
Author: wich,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2017-05-23 10:31:37

Rozważając ten problem ponownie: istnieje bardzo popularne rozwiązanie, które jest przywołane w tym wątku, który ma swoje pochodzenie TUTAJ :

DIR="$( cd "$( dirname "${BASH_SOURCE[0]}" )" && pwd )"

Trzymałem się z dala od tego rozwiązania ze względu na użycie dirname - może to powodować trudności międzyplatformowe, szczególnie jeśli skrypt musi być zablokowany ze względów bezpieczeństwa. Ale jako czystą alternatywę Basha, jak o użyciu:

DIR="$( cd "$( echo "${BASH_SOURCE[0]%/*}" )" && pwd )"

Czy to jest opcja?

 3
Author: AsymLabs,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2017-05-23 12:10:48

Łatwy do odczytania? alternatywa. Ignoruje dowiązania symboliczne

#!/bin/bash
currentDir=$(
  cd $(dirname "$0")
  pwd
) 

echo -n "current "
pwd
echo script $currentDir 
 3
Author: gerardw,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2016-05-12 18:43:39

Przyjęte rozwiązanie ma niewygodne (dla mnie) nie być "zdolne do źródła":
jeśli zadzwonisz z "source ../../yourScript", $0 byłoby "bash"!

Następująca funkcja (dla bash >= 3.0) daje mi właściwą ścieżkę, jednak skrypt może być wywołany (bezpośrednio lub poprzez source, ze ścieżką bezwzględną lub względną):
(przez "właściwą ścieżkę" mam na myśli pełną ścieżkę bezwzględną wywołanego skryptu, nawet gdy wywołany jest z innej ścieżki, bezpośrednio lub z "source")

#!/bin/bash
echo $0 executed

function bashscriptpath() {
  local _sp=$1
  local ascript="$0"
  local asp="$(dirname $0)"
  #echo "b1 asp '$asp', b1 ascript '$ascript'"
  if [[ "$asp" == "." && "$ascript" != "bash" && "$ascript" != "./.bashrc" ]] ; then asp="${BASH_SOURCE[0]%/*}"
  elif [[ "$asp" == "." && "$ascript" == "./.bashrc" ]] ; then asp=$(pwd)
  else
    if [[ "$ascript" == "bash" ]] ; then
      ascript=${BASH_SOURCE[0]}
      asp="$(dirname $ascript)"
    fi  
    #echo "b2 asp '$asp', b2 ascript '$ascript'"
    if [[ "${ascript#/}" != "$ascript" ]]; then asp=$asp ;
    elif [[ "${ascript#../}" != "$ascript" ]]; then
      asp=$(pwd)
      while [[ "${ascript#../}" != "$ascript" ]]; do
        asp=${asp%/*}
        ascript=${ascript#../}
      done
    elif [[ "${ascript#*/}" != "$ascript" ]];  then
      if [[ "$asp" == "." ]] ; then asp=$(pwd) ; else asp="$(pwd)/${asp}"; fi
    fi  
  fi  
  eval $_sp="'$asp'"
}

bashscriptpath H
export H=${H}

Kluczem jest wykrycie przypadku" source" i użycie ${BASH_SOURCE[0]}, aby odzyskać rzeczywisty skrypt.

 2
Author: VonC,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2011-06-24 02:45:06

Jeśli używamy Basha uważam, że jest to najwygodniejszy sposób, ponieważ nie wymaga wywołania żadnych zewnętrznych poleceń:

THIS_PATH="${BASH_SOURCE[0]}";
THIS_DIR=$(dirname $THIS_PATH)
 2
Author: Nordlöw,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2014-04-18 20:05:31

Spróbuj tego:

cd $(dirname $([ -L $0 ] && readlink -f $0 || echo $0))
 1
Author: diyism,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2014-08-29 03:05:05

Do diabła z tym zrobiłem trochę hakowania na skrypcie, który robi rzeczy czysto tekstowo, czysto w bash. Mam nadzieję, że złapałem wszystkie przypadki edge ' a. Zauważ, że ${var//pat/repl}, o którym wspomniałem w drugiej odpowiedzi nie działa, ponieważ nie można go zastąpić tylko najkrótszym możliwym dopasowaniem, co jest problemem dla zastąpienia /foo/../, ponieważ np. /*/../ weźmie wszystko przed nim, a nie tylko pojedynczy wpis. A ponieważ te wzorce nie są tak naprawdę wyrażeniami regularnymi, nie widzę, jak można to zrobić. Więc oto ładnie zawiłe rozwiązanie, które wymyśliłem, baw się dobrze. ;)

Przy okazji, daj mi znać, jeśli znajdziesz jakieś nieobsługiwane sprawy.
#!/bin/bash

canonicalize_path() {
  local path="$1"
  OIFS="$IFS"
  IFS=$'/'
  read -a parts < <(echo "$path")
  IFS="$OIFS"

  local i=${#parts[@]}
  local j=0
  local back=0
  local -a rev_canon
  while (($i > 0)); do
    ((i--))
    case "${parts[$i]}" in
      ""|.) ;;
      ..) ((back++));;
      *) if (($back > 0)); then
           ((back--))
         else
           rev_canon[j]="${parts[$i]}"
           ((j++))
         fi;;
    esac
  done
  while (($j > 0)); do
    ((j--))
    echo -n "/${rev_canon[$j]}"
  done
  echo
}

canonicalize_path "/.././..////../foo/./bar//foo/bar/.././bar/../foo/bar/./../..//../foo///bar/"
 0
Author: wich,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2011-01-23 14:53:44

Od jakiegoś czasu używam z powodzeniem następującego podejścia (nie na OSX) i używa tylko wbudowanej powłoki i obsługuje 'source foobar.sh / align = "left" /

Jeden problem z poniższym (pospiesznie złożonym) przykładowym kodem jest taki, że funkcja używa zmiennej $ PWD, która może, ale nie musi być poprawna w momencie wywołania funkcji. Więc trzeba się tym zająć.

#!/bin/bash

function canonical_path() {
  # Handle realtive vs absolute path
  [ ${1:0:1} == '/' ] && x=$1 || x=$PWD/$1
  # Change to dirname of x
  cd ${x%/*}
  # Combine new pwd with basename of x
  echo $(pwd -P)/${x##*/}
  cd $OLDPWD
}

echo $(canonical_path "${BASH_SOURCE[0]}")

type [
type cd
type echo
type pwd
 0
Author: andro,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2014-04-18 21:38:57

Yet another way to do this:

shopt -s extglob

selfpath=$0
selfdir=${selfpath%%+([!/])}

while [[ -L "$selfpath" ]];do
  selfpath=$(readlink "$selfpath")
  if [[ ! "$selfpath" =~ ^/ ]];then
    selfpath=${selfdir}${selfpath}
  fi
  selfdir=${selfpath%%+([!/])}
done

echo $selfpath $selfdir
 -1
Author: Meow,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2014-12-19 14:02:50

One liner

`dirname $(realpath $0)`
 -1
Author: Abhijit,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2015-12-18 02:02:32

Prościej, to jest to, co działa dla mnie:

MY_DIR=`dirname $0`
source $MY_DIR/_inc_db.sh
 -4
Author: Elendurwen,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2016-11-15 11:31:48