Portabler Weg, um den absoluten Pfad eines Skripts zu ermitteln?

29

Was ist ein portabler Weg für ein (zsh) Skript, um seinen absoluten Pfad zu bestimmen?

Unter Linux verwende ich sowas

mypath=$(readlink -f $0)

... aber das ist nicht portabel. (ZB readlinkerkennt darwin das nicht-f Flagge nicht und hat auch keine Entsprechung readlink.)

Was ist portabler?

kjo
quelle
1
Siehe auch Wird $ 0 immer den Pfad zum Skript enthalten?
Stéphane Chazelas

Antworten:

28

Mit zshist es nur:

mypath=$0:A

Nun aber zu anderen Shells realpath()und zu readlink()Standardfunktionen (wobei letztere ein Systemaufruf ist) realpathund readlinknicht zum Standardbefehl gehören, obwohl einige Systeme über die eine oder andere oder beide mit unterschiedlichem Verhalten und Funktionsumfang verfügen.

Aus Gründen der Portabilität möchten Sie möglicherweise wie oft auf Folgendes zurückgreifen perl:

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

Das würde sich eher wie bei GNU verhalten readlink -falsrealpath() (GNU readlink -e) , da es sich nicht beschwert, wenn die Datei nicht existiert, solange ihr Dirname existiert.

Stéphane Chazelas
quelle
Hinweis: Dies funktioniert nicht für Ihre .zshrc: Siehe stattdessen diesen Beitrag .
Bryce Guinta
24

In zsh können Sie Folgendes tun:

mypath=${0:a}

Oder um das Verzeichnis abzurufen, in dem sich das Skript befindet:

mydir=${0:a:h}

Quelle: Manpage zshexpn (1), Abschnitt HISTORY EXPANSION, Unterabschnitt Modifiers (oder einfach info -f zsh -n Modifiers).

mrmenken
quelle
Süss! Ich habe so etwas schon seit Ewigkeiten gesucht und die ganze Reihe der zsh-Manpages gelesen, die danach gesucht haben, aber es wäre mir nie in den Sinn gekommen, unter "Verlaufserweiterung" nachzuschauen.
Vucar Timnärakrul
1
Das Äquivalent von GNU readlink -fwäre eher $0:A.
Stéphane Chazelas
11

Ich benutze das jetzt seit einigen Jahren:

# The absolute, canonical ( no ".." ) path to this script
canonical=$(cd -P -- "$(dirname -- "$0")" && printf '%s\n' "$(pwd -P)/$(basename -- "$0")")
user17591
quelle
1
ich mag das! Bisher ist das ziemlich portabel. Funktioniert unter Solaris, OmniOS, Linux, Mac und sogar Cygwin unter Windows 2008.
Tim Kennedy
4
Wo es nicht zu GNUs äquivalent ist, readlink -fist, wenn das Skript selbst ein Symlink ist.
Stéphane Chazelas
Unter Ubuntu 16.04 mit zsh, wenn ich dies entweder direkt oder in der Subshell (wie vorgeschlagen) ausführe, während ich mich in meinem Home-Verzeichnis () befinde /home/ville, wird es gedruckt /home/ville/zsh.
Ville
8

Diese Syntax sollte in jedem Stil Bourne - Shell - Interpreter tragbar sein (getestet mit bash, ksh88, ksh93, zsh, mksh, dashund busybox sh):

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

Diese Version fügt der älteren AT & T Bourne-Shell (nicht POSIX) Kompatibilität hinzu:

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
quelle
Vielen Dank. Ich denke jedoch, dass das Deaktivieren $PWDein Overkill sein könnte - Sie können es einfach auf den absoluten Strom wie einstellen cd -P .. Ich bezweifle, dass das in Bourneshell funktionieren würde - aber es sollte in allen funktionieren, die Sie für das erste Mal getestet haben. tut für mich sowieso
mikeserv
@ Elch Welches Betriebssystem verwenden Sie?
Juli
Wer ist Elch? Was?
mikeserv
@ mikeserv Elch ist ein Passant, der einen Kommentar zu einem Problem mit gepostet zshund dirnameaber schnell seinen / ihren Kommentar zurückgezogen hat ...
jlliagre
Ihr Bourne-Shell-Skript funktioniert nicht mit einer Bourne-Shell, da die Bourne-Shell für cd (1) nicht getopt () verwendet.
Schily
4

Angenommen, Sie meinen wirklich den absoluten Pfad, dh einen Pfad aus dem Stammverzeichnis:

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

Dies funktioniert übrigens in jeder Bourne-artigen Shell.

Wenn Sie einen Pfad gemeint haben, bei dem alle symbolischen Links aufgelöst sind, ist das eine andere Sache. readlink -fFunktioniert unter Linux (mit Ausnahme einiger reduzierter BusyBox-Systeme), FreeBSD, NetBSD, OpenBSD und Cygwin, jedoch nicht unter OS / X, AIX, HP / UX oder Solaris. Wenn Sie haben readlink, können Sie es in einer Schleife aufrufen:

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
}

Wenn nicht readlink, können Sie es mit approximieren. ls -nDies funktioniert jedoch nur, wenn lsder Dateiname keine nicht druckbaren Zeichen enthält.

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"
}

(Das Besondere zfür den Fall, dass das Link-Ziel in einer neuen Zeile endet, die sonst durch eine Befehlsersetzung aufgebraucht würde. Die realpathFunktion behandelt diesen Fall übrigens nicht für Verzeichnisnamen.)

Gilles 'SO - hör auf böse zu sein'
quelle
1
Kennen Sie eine lsImplementierung, die nicht druckbare Zeichen entstellt, wenn die Ausgabe nicht an ein Terminal gesendet wird ?
Stéphane Chazelas
1
@ StéphaneChazelas touch Stéphane; LC_ALL=C busybox ls Stéphane | catSt??phane(Wenn der Name in UTF-8 steht, gibt latin1 eine Single aus ?). Ich glaube, das habe ich auch bei älteren kommerziellen Unices gesehen.
Gilles 'SO- hör auf böse zu sein'
@ StéphaneChazelas Ich habe einige Fehler behoben, aber nicht ausgiebig getestet. Lassen Sie mich wissen, ob es in einigen Fällen immer noch fehlschlägt (abgesehen von fehlenden Ausführungsberechtigungen in einigen Verzeichnissen werde ich mich nicht mit diesem Edge-Fall befassen).
Gilles 'SO- hör auf böse zu sein'
@ Gilles - was busyboxist das? laut git busybox ls hat sich der code seit 2011 nicht mehr geändert. my busybox ls- circa 2013 - macht das nicht. Das eine - circa 2012 - tut . Dies könnte erklären, warum. Haben Sie Ihre busyboxmit Unicode-Unterstützung erstellt - um wchar-Unterstützung einzuschließen? Vielleicht möchten Sie es ausprobieren, um die Build-Optionen im mkinitcpio busyboxPaket zu überprüfen .
mikeserv
Gilles - ich glaube, ich habe diese Antwort anfangs falsch eingeschätzt - oder zumindest einen Teil davon. Während ich glaube fest daran , Ihre Mangeln Dateinamen Mantra völliger Irrtum zu sein, auf jeden Fall Ihre poor_mans_readlink sind sehr gut gemacht. Wenn Sie mir die Freundlichkeit erweisen, eine Bearbeitung vorzunehmen - jede Bearbeitung wird es tun - und mich anschließend anpingen, möchte ich meine Abstimmung darüber umkehren.
mikeserv
1

Vorausgesetzt, Sie haben Ausführungsberechtigungen für das aktuelle Verzeichnis - oder für das Verzeichnis, von dem aus Sie Ihr Shell-Skript ausgeführt haben -, wenn Sie nur einen absoluten Pfad zu einem Verzeichnis benötigen cd.

Schritt 10 von cd's spec

Wenn die -POption aktiviert ist, muss die $PWDUmgebungsvariable auf die Zeichenfolge gesetzt werden, die von ausgegeben werden würde pwd -P. Wenn für das neue Verzeichnis oder für ein übergeordnetes Verzeichnis dieses Verzeichnisses keine ausreichende Berechtigung zum Ermitteln des aktuellen Arbeitsverzeichnisses vorhanden ist, ist der Wert der $PWDUmgebungsvariablen nicht angegeben.

Und weiter pwd -P

Der in die Standardausgabe geschriebene Pfadname darf keine Komponenten enthalten, die auf Dateien des Typs symbolic link verweisen. Wenn es mehrere Pfadnamen gibt, die das pwdDienstprogramm in die Standardausgabe schreiben kann, wobei einer mit einem einzelnen / Schrägstrich und einer oder mehrere mit zwei / Schrägstrich beginnen, schreibt es den Pfadnamen, der mit einem einzelnen / Schrägstrich beginnt. Der Pfadname darf keine unnötigen / Schrägstriche nach den führenden ein oder zwei / Schrägstrichen enthalten.

Es ist , weil cd -Phat das aktuelle Arbeitsverzeichnis zu setzen , was pwd -Psollte sonst drucken und das cd -hat zu drucken , $OLDPWDdass folgende Arbeiten:

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

AUSGABE

/home/mikeserv/test/ln

warte darauf...

cd -P . ; cd . ; cd -

AUSGABE

/home/mikeserv/test/dir

Und wenn ich mit cd -drucke, drucke ich $OLDPWD. cdsetzt $PWDsobald ich cd -P . $PWDjetzt einen absoluten pfad dazu habe /- also brauche ich keine weiteren variablen. Und eigentlich sollte ich nicht einmal die Hinter brauchen , .aber es gibt eine spezifizierte Verhalten Zurücksetzen $PWDauf $HOMEin einer interaktiven Shell , wenn cdschmucklos ist. Es ist also nur eine gute Angewohnheit, sich zu entwickeln.

Das oben aufgeführte Vorgehen auf dem Pfad in ${0%/*}sollte also mehr als genug sein, um $0den Pfad zu überprüfen , aber in dem Fall, dass $0es sich selbst um einen Softlink handelt, können Sie das Verzeichnis leider nicht ändern.

Hier ist eine Funktion, die das erledigt:

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                                    
}

Es wird versucht, so viel wie möglich in der aktuellen Shell zu tun - ohne eine Subshell aufzurufen - obwohl Subshells für Fehler und Softlinks aufgerufen werden, die nicht auf Verzeichnisse verweisen. Es hängt von einer POSIX-kompatiblen Shell und einem POSIX-kompatiblen lssowie einem sauberen _function()Namespace ab. Ohne Letzteres funktioniert es immer noch einwandfrei, obwohl es dann möglicherweise überschrieben wirdunset in diesem Fall einige aktuelle Shell-Funktionen . Im Allgemeinen sollten all diese Abhängigkeiten auf einer Unix-Maschine ziemlich zuverlässig verfügbar sein.

Wird es mit oder ohne Argumente aufgerufen, wird es zunächst $PWDauf seinen kanonischen Wert zurückgesetzt. Dabei werden alle darin enthaltenen Verknüpfungen zu ihren Zielen nach Bedarf aufgelöst. Ohne Argumente angerufen und das war's auch schon; aber mit ihnen angerufen und es wird aufgelöst und kanonisiert den Pfad für jeden oder sonst eine Nachricht zu druckenstderr warum nicht.

Da es hauptsächlich in der aktuellen Shell ausgeführt wird, sollte es in der Lage sein, eine Argumentliste beliebiger Länge zu verarbeiten. Es sucht auch nach der $_zdlmVariablen (die es auch ist, unsetwenn sie durch ist) und gibt ihren C-escaped-Wert unmittelbar rechts von jedem seiner Argumente aus, auf die immer auch ein einzelnes \newline-Zeichen folgt.

Es macht eine Menge Verzeichnis zu ändern, aber, anders als es nach dem kanonischen Wert, ist es nicht beeinflussen $PWD, obwohl$OLDPWD auf keinen Fall damit gerechnet werden kann, wann es fertig ist.

Es versucht, jedes seiner Argumente so schnell wie möglich zu beenden. Es versucht zuerst cdhinein $1. Wenn dies möglich ist, wird der kanonische Pfad des Arguments zu ausgegeben stdout. Wenn dies nicht möglich ist, wird überprüft, ob $1ein Softlink vorhanden ist. Wenn dies zutrifft, wird gedruckt.

Auf diese Weise werden alle Dateitypargumente verarbeitet, zu deren Adressierung die Shell berechtigt ist, es $1sei denn, es handelt sich um einen symbolischen Link, der nicht auf ein Verzeichnis verweist. In diesem Fall wird die whileSchleife in einer Subshell aufgerufen.

Es ruft lsauf, um den Link zu lesen. Das aktuelle Verzeichnis muss zuerst auf den Anfangswert geändert werden, damit referenzierte Pfade zuverlässig verarbeitet werden können. In der Subshell für die Befehlsersetzung führt die Funktion daher Folgendes aus:

cd -...ls...echo /

Es wird lsso wenig von der linken Seite der Ausgabe entfernt, wie es erforderlich ist, um den Namen des Links und die Zeichenfolge vollständig zu enthalten ->. Ich habe zunächst versucht, dies zu vermeiden, shiftund $IFSes hat sich herausgestellt, dass dies die zuverlässigste Methode ist, soweit ich das beurteilen kann. Dies ist das gleiche, was Gilles ' poor_mans_readlink tut - und es ist gut gemacht.

Dieser Vorgang wird in einer Schleife wiederholt, bis der zurückgegebene Dateiname lsdefinitiv kein Softlink mehr ist. An diesem Punkt kanonisiert es diesen Pfad wie zuvor cdund druckt dann.

Anwendungsbeispiel:

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 \
    . ..      

AUSGABE

/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/

Oder vielleicht ...

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

zdlm=\\0 zpath * | cat -A

AUSGABE

/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
quelle
0

Was ist mit einem sympathischen Einzeiler, wenn Python verfügbar ist, um zu verhindern, dass ein Algorithmus neu definiert wird?

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

Entspricht https://stackoverflow.com/a/7305217

Antonio Daniel Rodriguez
quelle