Przenośny sposób na uzyskanie absolutnej ścieżki skryptu?

29

Jaki jest przenośny sposób dla skryptu (zsh) na określenie jego bezwzględnej ścieżki?

W systemie Linux używam czegoś takiego

mypath=$(readlink -f $0)

... ale to nie jest przenośne. (Np. readlinkNa Darwinie nie rozpoznaje -fflagi, ani nie ma żadnego odpowiednika.) (Ponadto, użycie readlinkdo tego jest, co prawda, dość niejasno wyglądającym włamaniem.)

Jaki jest bardziej przenośny sposób?

kjo
źródło

Odpowiedzi:

28

Dzięki zsh, to po prostu:

mypath=$0:A

Teraz jednak dla innych powłok są realpath()i readlink()są funkcjami standardowymi (te ostatnie są wywołaniem systemowym) realpathi readlinknie są standardowymi poleceniami, chociaż niektóre systemy mają jedno lub drugie lub oba z różnymi zachowaniami i zestawem funkcji.

Jak często, w celu przenoszenia, możesz skorzystać z perl:

abs_path() {
  perl -MCwd -le '
    for (@ARGV) {
      if ($p = Cwd::abs_path $_) {
        print $p;
      } else {
        warn "abs_path: $_: $!\n";
        $ret = 1;
      }
    }
    exit $ret' "$@"
}

To działałoby bardziej jak GNU readlink -fniż realpath()(GNU readlink -e), ponieważ nie będzie narzekać, jeśli plik nie istnieje tak długo, jak jego nazwa katalogu.

Stéphane Chazelas
źródło
Uwaga: to nie działa na .zshrc: Zobacz zamiast tego post .
Bryce Guinta
24

W Zsh możesz wykonać następujące czynności:

mypath=${0:a}

Lub, aby uzyskać katalog, w którym znajduje się skrypt:

mydir=${0:a:h}

Źródło: strona podręcznika zshexpn (1), sekcja ROZSZERZENIE HISTORII, podsekcja Modyfikatory (lub po prostu info -f zsh -n Modifiers ).

mrmenken
źródło
Słodkie! Szukałem czegoś takiego od wieków i czytałem całą grupę stron zsh szukających tego, ale nigdy nie przyszło mi do głowy, aby zajrzeć do „Ekspansji historii”.
Vucar Timnärakrul
1
readlink -fRaczej odpowiada to GNU $0:A.
Stéphane Chazelas,
11

Używam tego od kilku lat:

# The absolute, canonical ( no ".." ) path to this script
canonical=$(cd -P -- "$(dirname -- "$0")" && printf '%s\n' "$(pwd -P)/$(basename -- "$0")")
użytkownik17591
źródło
1
lubię to! jak dotąd jest to dość przenośne. działa w systemach Solaris, OmniOS, Linux, Mac, a nawet Cygwin w systemie Windows 2008.
Tim Kennedy
4
Nie jest to równoważne z GNU, readlink -fgdy sam skrypt jest dowiązaniem symbolicznym.
Stéphane Chazelas,
Na Ubuntu 16.04 z zsh, jeśli wykonam to bezpośrednio lub w podpowłoce (jak sugerowano) podczas pracy w moim domowym katalogu ( /home/ville), zostanie wydrukowane /home/ville/zsh.
Ville
8

Ta składnia powinna być przenośny do dowolnego stylu Bourne shell tłumacza (testowane z bash, ksh88, ksh93, zsh, mksh, dashi busybox sh):

mypath=$(exec 2>/dev/null;cd -- $(dirname "$0"); unset PWD; /usr/bin/pwd || /bin/pwd || pwd)
echo mypath=$mypath

Ta wersja dodaje zgodność ze starszą powłoką AT&T Bourne (bez POSIX):

mypath=`dirname "$0"`
mypath=`exec 2>/dev/null;(cd -- "$mypath") && cd -- "$mypath"|| cd "$mypath"; unset PWD; /usr/bin/pwd || /bin/pwd || pwd`
echo mypath=$mypath
jlliagre
źródło
dzięki. myślę jednak, że niepokojenie $PWDmoże być przesadą - możesz po prostu ustawić na prąd absolutny jak cd -P .. Wątpię, czy zadziałałoby to w stylu bursztynu - ale powinno działać we wszystkich testowanych dla pierwszego. i tak robi dla mnie.
mikeserv
@moose Z jakiego systemu operacyjnego korzystasz?
jlliagre
kim jest łoś co?
mikeserv
@mikeserv łoś jest przechodzień, który opublikował komentarz na temat jakiegoś problemu z zsha dirnamejednak szybko wycofać hir / jej komentarz ...
jlliagre
Twój skrypt Bourne Shell nie będzie działał z Bourne Shell, ponieważ Bourne Shell nie używa getopt () dla cd (1).
schily
4

Zakładając, że naprawdę masz na myśli ścieżkę bezwzględną, tj. Ścieżkę z katalogu głównego:

case $0 in
  /*) mypath=$0;;
  *) mypath=$PWD/$0;;
esac

Nawiasem mówiąc, działa to w dowolnej powłoce w stylu Bourne'a.

Jeśli miałeś na myśli ścieżkę z rozwiązanymi wszystkimi dowiązaniami symbolicznymi, to inna sprawa. readlink -fdziała na Linuksie (z wyłączeniem niektórych zredukowanych systemów BusyBox), FreeBSD, NetBSD, OpenBSD i Cygwin, ale nie na OS / X, AIX, HP / UX lub Solaris. Jeśli tak readlink, możesz wywołać go w pętli:

realpath () {
  [ -e "$1" ] || return
  case $1 in
    /*) :;;
    *) set "$PWD/$1";;
  esac
  while [ -L "$1" ]; do
    set "${1%/*}" "$(readlink "$1")"
    case $2 in
      /*) set "$2";;
      *) if [ -z "$1" ]; then set "/$2"; else set "$(cd "$1" && pwd -P)/$2"; fi;;
    esac
  done
  case $1 in
    */.|*/..) set "$(cd "$1" && pwd -P)";;
    */./*|*/../*) set "$(cd "${1%/*}" && pwd -P)/${1##*/}"
  esac
  realpath=$1
}

Jeśli nie masz readlink, możesz to przybliżyć ls -n, ale działa to tylko wtedy, gdy lsnie zmienia w nazwie pliku żadnego niedrukowalnego znaku.

poor_mans_readlink () {
  if [ -L "$1" ]; then
    set -- "$1" "$(LC_ALL=C command ls -n -- "$2"; echo z)"
    set -- "${2%??}"
    set -- "${2#*"$1 -> "}"
  fi
  printf '%s\n' "$1"
}

(Dodatkowa zjest w przypadku, gdy cel łącza kończy się na nowej linii, która w przeciwnym razie pochłonęłaby komendę. Nawiasem realpathmówiąc, funkcja nie obsługuje tej nazwy nazw katalogów).

Gilles „SO- przestań być zły”
źródło
1
Czy znasz jakieś lsimplementacje, które zniekształcają znaki niedrukowalne, gdy dane wyjściowe nie trafiają do terminala.
Stéphane Chazelas,
1
@ StéphaneChazelas touch Stéphane; LC_ALL=C busybox ls Stéphane | catSt??phane(jeśli nazwa ma UTF-8, latin1 daje ci singla ?). Myślę, że widziałem to również w starszych komercyjnych Unices.
Gilles „SO- przestań być zły”
@ StéphaneChazelas Naprawiłem kilka błędów, ale nie poddałem się szczegółowym testom. Daj mi znać, jeśli nadal zawiedzie w niektórych przypadkach (poza brakiem uprawnień do wykonywania w niektórych katalogach, nie będę zajmować się tą sprawą).
Gilles 'SO - przestań być zły'
@Gilles - co to busyboxjest? według git busybox ls nie zmienił kodu od 2011 roku. Mój busybox ls- około 2013 roku - tego nie robi. To jeden - około 2012 - robi . To może wyjaśniać dlaczego. Czy masz już wbudowaną busyboxobsługę Unicode - aby włączyć obsługę wchar? Możesz spróbować, w przeciwnym razie sprawdź opcje kompilacji w mkinitcpio busyboxpakiecie.
mikeserv
Gilles - myślę, że początkowo źle oceniłem tę odpowiedź - a przynajmniej jej część. Chociaż mocno wierzę, że twoja mangrująca nazwa pliku mantra jest całkowicie błędna, zdecydowanie twoje poor_mans_readlink jest bardzo dobrze zrobione. Jeśli zrobisz mi uprzejmość dokonania edycji - jakakolwiek edycja zrobi - i pingowania mnie później, chciałbym cofnąć mój głos w tej sprawie.
mikeserv
1

Pod warunkiem, że masz uprawnienia do wykonywania w bieżącym katalogu - lub w katalogu, z którego wykonałeś skrypt powłoki - jeśli potrzebujesz bezwzględnej ścieżki do katalogu, wszystko czego potrzebujesz cd.

Krok 10 cdspecyfikacji

Jeśli -Popcja jest aktywna, $PWDzmienna środowiskowa powinna być ustawiona na ciąg znaków, który byłby wyprowadzany pwd -P. Jeśli nie ma wystarczających uprawnień do nowego katalogu lub nadrzędnego tego katalogu, aby określić bieżący katalog roboczy, wartość $PWDzmiennej środowiskowej jest nieokreślona.

I dalej pwd -P

Nazwa ścieżki zapisana na standardowe wyjście nie może zawierać żadnych składników, które odnoszą się do plików typu dowiązanie symboliczne. Jeśli istnieje wiele ścieżek, które pwdnarzędzie może zapisać na standardowym wyjściu, jeden zaczyna się od znaku pojedynczego / ukośnika, a jeden lub więcej zaczyna się od dwóch znaków / ukośnika, wówczas powinien zapisać nazwę ścieżki rozpoczynającą się od znaku pojedynczego / ukośnika. Nazwa ścieżki nie może zawierać żadnych niepotrzebnych / ukośników po wiodącym jednym lub dwóch / ukośniku.

Jest tak, ponieważ cd -Pmusi ustawić bieżący katalog roboczy na to, co w pwd -Pprzeciwnym razie powinno zostać wydrukowane, i to cd -musi wydrukować to $OLDPWD, co działa:

mkdir ./dir
ln -s ./dir ./ln
cd ./ln ; cd . ; cd -

WYDAJNOŚĆ

/home/mikeserv/test/ln

czekaj na to...

cd -P . ; cd . ; cd -

WYDAJNOŚĆ

/home/mikeserv/test/dir

A kiedy cd -drukuję, drukuję $OLDPWD. cdustawia się, $PWDjak tylko jestem cd -P . $PWDteraz absolutną ścieżką do /- więc nie potrzebuję żadnych innych zmiennych. I właściwie nie powinienem nawet potrzebować końcowego, .ale jest określone zachowanie resetowania $PWDdo $HOMEw interaktywnej powłoce, gdy nie cdjest ozdobione. Więc to dobry nawyk, aby się rozwijać.

Tak więc wykonanie powyższej czynności na ścieżce ${0%/*}powinno być więcej niż wystarczające do zweryfikowania $0ścieżki, ale w przypadku, gdy $0jest to miękkie łącze, prawdopodobnie nie możesz zmienić katalogu na to, niestety.

Oto funkcja, która sobie z tym poradzi:

zpath() { cd -P . || return
    _out() { printf "%s$_zdlm\n" "$PWD/${1##*/}"; }
    _cd()  { cd -P "$1" ; } >/dev/null 2>&1
    while [ $# -gt 0 ] && _cd .
    do  if     _cd "$1" 
        then   _out
        elif ! [ -L "$1" ] && [ -e "$1" ] 
        then   _cd "${1%/*}"; _out "$1"
        elif   [ -L "$1" ]
        then   ( while set -- "${1%?/}"; _cd "${1%/*}"; [ -L "${1##*/}" ]
                 do    set " $1" "$(_cd -; ls -nd -- "$1"; echo /)"
                       set -- "${2#*"$1" -> }"
                 done; _out "$1" 
    );  else   ( PS4=ERR:\ NO_SUCH_PATH; set -x; : "$1" )
    fi; _cd -; shift; done
    unset -f _out _cd; unset -v _zdlm                                    
}

Stara się zrobić tyle, ile może w bieżącej powłoce - bez wywoływania podpowłoki - chociaż są wywoływane podpowłoki z powodu błędów i miękkich linków, które nie wskazują na katalogi. To zależy od powłoki kompatybilnej z POSIX i kompatybilnej z POSIX, lsa także z czystej _function()przestrzeni nazw. Nadal będzie działał dobrze bez tego drugiego, choć może wtedy nadpisaćunset w takim przypadku niektóre bieżące funkcje powłoki. Zasadniczo wszystkie te zależności powinny być dość niezawodnie dostępne na maszynie uniksowej.

Wywoływany z argumentami lub bez, pierwszą rzeczą, którą robi, jest resetowany $PWDdo wartości kanonicznej - w razie potrzeby rozwiązuje wszelkie zawarte w nim linki do ich celów. Nazywany bez argumentów i to wszystko; ale wywoływany wraz z nimi, rozwiąże i kanonizuje ścieżkę dla każdego z nich lub wydrukuje wiadomośćstderr dlaczego nie.

Ponieważ działa on głównie w bieżącej powłoce, powinien być w stanie obsłużyć listę argumentów dowolnej długości. Wyszukuje również $_zdlmzmienną (która również unsetprzechodzi, gdy jest już w toku ) i wypisuje swoją wartość C-esc natychmiast po prawej stronie każdego argumentu, po którym zawsze następuje pojedynczy \nznak ewline.

Często zmienia katalog, ale poza ustawieniem jego wartości kanonicznej, nie ma to wpływu $PWD, chociaż $OLDPWDnie można w żaden sposób liczyć na to, kiedy się skończy.

Próbuje jak najszybciej zrezygnować z każdego z argumentów. Najpierw próbuje cdsię $1. Jeśli to możliwe, wypisuje kanoniczną ścieżkę argumentu do stdout. Jeśli nie może, sprawdza, czy $1istnieje i nie jest linkiem miękkim. Jeśli to prawda, drukuje się.

W ten sposób obsługuje dowolny argument typu pliku, do którego powłoka ma uprawnienia do adresowania, chyba że $1jest dowiązaniem symbolicznym, który nie wskazuje na katalog. W takim przypadku wywołuje whilepętlę w podpowłoce.

Wzywa lsdo odczytania linku. Bieżący katalog musi zostać najpierw zmieniony na wartość początkową, aby niezawodnie obsługiwać ścieżki referencyjne, a zatem w podpowłoce podstawiania poleceń funkcja:

cd -...ls...echo /

Usuwa z lewej strony danych lswyjściowych tak mało, jak to konieczne, aby w pełni zawierać nazwę łącza i ciąg znaków ->. Podczas gdy na początku starał się unikać zrobić z shifti $IFSokazuje się, że jest to najbardziej wiarygodna metoda tak blisko, jak mogę dowiedzieć. To jest to samo, co robi biedny link_mans_read Gillesa - i jest dobrze zrobione.

Powtórzy ten proces w pętli, dopóki zwracana nazwa pliku lsnie będzie z pewnością miękkim linkiem. W tym momencie kanonizuje tę ścieżkę jak poprzednio, a cdnastępnie drukuje.

Przykładowe użycie:

zpath \
    /tmp/script \   #symlink to $HOME/test/dir/script.sh
    ln \            #symlink to ./dir/
    ln/nl \         #symlink to ../..
    /dev/fd/0 \     #currently a here-document like : dash <<\HD
    /dev/fd/1 \     #(zlink) | dash
    file \          #regular file                                             
    doesntexist \   #doesnt exist
    /dev/disk/by-path/pci-0000:00:16.2-usb-0:3:1.0-scsi-0:0:0:0 \
    /dev/./././././././null \
    . ..      

WYDAJNOŚĆ

/home/mikeserv/test/dir/script.sh
/home/mikeserv/test/dir/
/home/mikeserv/test/
/tmp/zshtpKRVx (deleted)
/proc/17420/fd/pipe:[1782312]
/home/mikeserv/test/file
ERR: NO_SUCH_PATH: doesntexist
/dev/sdd
/dev/null
/home/mikeserv/test/
/home/mikeserv/

A może…

ls
dir/  file  file?  folder/  link@  ln@  script*  script3@  script4@

zdlm=\\0 zpath * | cat -A

WYDAJNOŚĆ

/home/mikeserv/test/dir/^@$               
/home/mikeserv/test/file^@$
/home/mikeserv/test/file$
^@$
/home/mikeserv/test/folder/^@$
/home/mikeserv/test/file$               #'link' -> 'file\n'
^@$
/home/mikeserv/test/dir/^@$             #'ln' -> './dir'
/home/mikeserv/test/script^@$
/home/mikeserv/test/dir/script.sh^@$    #'script3' -> './dir/script.sh'
/home/mikeserv/test/dir/script.sh^@$    #'script4' -> '/tmp/script' -> ...
mikeserv
źródło
0

co z sympatycznym jednowierszowym, gdy dostępny jest python, aby zapobiec przedefiniowaniu algorytmu?

function readlink { python -c "import os.path; print os.path.realpath('$1')"; }

taki sam jak https://stackoverflow.com/a/7305217

Antonio Daniel Rodriguez
źródło