Wie zu beheben symbolischen links in einem shell-Skript
Frage
Angesichts einer absoluten oder relativen Pfad (in einem Unix-ähnlichen system), ich würde wie, um zu bestimmen, den vollständigen Pfad der Ziel-nach dem auflösen einer zwischengeschalteten symlinks.Bonus-Punkte für die Lösung ~username notation zur gleichen Zeit.
Wenn das Ziel ein Verzeichnis ist, wird es möglich sein könnte, chdir() in das Verzeichnis und rufen Sie dann getcwd(), aber ich möchte wirklich, dies zu tun von einem shell-Skript eher als das schreiben einer C-Hilfsprogramm.Leider Schalen haben eine Tendenz, zu versuchen zu verbergen die Existenz Verknüpfungen des Benutzers (dies ist die bash unter OS X):
$ ls -ld foo bar
drwxr-xr-x 2 greg greg 68 Aug 11 22:36 bar
lrwxr-xr-x 1 greg greg 3 Aug 11 22:36 foo -> bar
$ cd foo
$ pwd
/Users/greg/tmp/foo
$
Was ich möchte, ist eine Funktion resolve (), so dass, wenn ausgeführt, aus dem tmp-Verzeichnis, in dem oben genannten Beispiel, Entschlossenheit("foo") == "/Users/greg/tmp/bar".
Andere Tipps
readlink -f "$path"
Editor ' s note:Die oben genannten arbeiten mit GNU readlink
und FreeBSD/PC-BSD/OpenBSD readlink
, aber nicht auf OS X ab 10.11.
GNU readlink
bietet zusätzliche, relevante Optionen, wie -m
für die Lösung eines symlink, ob oder nicht das ultimative Ziel vorhanden ist.
Hinweis: da die GNU-coreutils 8.15 (2012-01-06), es ist ein realpath Programm zur Verfügung, das ist weniger stumpf und flexibler als die oben genannten.Es ist auch kompatibel mit dem FreeBSD-util mit dem gleichen Namen.Es enthält auch Funktionen zum generieren einer relativen Pfad zwischen zwei Dateien.
realpath $path
[Admin neben unten aus Kommentar von halloleo —danorton]
Für Mac OS X (mindestens durch 10.11.x) verwenden readlink
ohne -f
option:
readlink $path
Editor ' s note:Diese wird nicht lösen symlinks rekursiv und damit nicht Bericht die ultimate Ziel;z.B. angesichts symlink a
, die Punkte zu b
, die wiederum auf c
, nur Bericht b
(und nicht gewährleisten, dass es ist Ausgang als absoluter Pfad).
Verwenden Sie die folgenden perl
Befehl in OS X, um die Lücke der fehlenden readlink -f
Funktionalität:
perl -MCwd -le 'print Cwd::abs_path(shift)' "$path"
"pwd -P" zu funktionieren scheint, wenn Sie wollen einfach nur das Verzeichnis, aber wenn für einige Grund, Sie wollen die Namen der tatsächlichen ausführbaren ich glaube nicht, dass das hilft.Hier ist meine Lösung:
#!/bin/bash
# get the absolute path of the executable
SELF_PATH=$(cd -P -- "$(dirname -- "$0")" && pwd -P) && SELF_PATH=$SELF_PATH/$(basename -- "$0")
# resolve symlinks
while [[ -h $SELF_PATH ]]; do
# 1) cd to directory of the symlink
# 2) cd to the directory of where the symlink points
# 3) get the pwd
# 4) append the basename
DIR=$(dirname -- "$SELF_PATH")
SYM=$(readlink "$SELF_PATH")
SELF_PATH=$(cd "$DIR" && cd "$(dirname -- "$SYM")" && pwd)/$(basename -- "$SYM")
done
Einer meiner Favoriten ist realpath foo
realpath - return the canonicalized absolute pathname realpath expands all symbolic links and resolves references to '/./', '/../' and extra '/' characters in the null terminated string named by path and stores the canonicalized absolute pathname in the buffer of size PATH_MAX named by resolved_path. The resulting path will have no symbolic link, '/./' or '/../' components.
readlink -e [filepath]
zu sein scheint, genau das, was Sie für Fragen - es nimmt eine arbirary Weg, löst alle symlinks, und gibt den "echten" Pfad - und es ist "standard *nix", dass wahrscheinlich alle Systeme, die bereits haben
Ein anderer Weg:
# Gets the real path of a link, following all links
myreadlink() { [ ! -h "$1" ] && echo "$1" || (local link="$(expr "$(command ls -ld -- "$1")" : '.*-> \(.*\)$')"; cd $(dirname $1); myreadlink "$link" | sed "s|^\([^/].*\)\$|$(dirname $1)/\1|"); }
# Returns the absolute path to a command, maybe in $PATH (which) or not. If not found, returns the same
whereis() { echo $1 | sed "s|^\([^/].*/.*\)|$(pwd)/\1|;s|^\([^/]*\)$|$(which -- $1)|;s|^$|$1|"; }
# Returns the realpath of a called command.
whereis_realpath() { local SCRIPT_PATH=$(whereis $1); myreadlink ${SCRIPT_PATH} | sed "s|^\([^/].*\)\$|$(dirname ${SCRIPT_PATH})/\1|"; }
Putting einige der angegebenen Lösungen zusammen, wohl wissend, dass readlink ist auf den meisten Systemen verfügbar, aber braucht verschiedene Argumente, das funktioniert gut für mich unter OS x und Debian.Ich bin mir nicht sicher, etwa BSD-Systeme.Vielleicht ist die Bedingung muss sein [[ $OSTYPE != darwin* ]]
ausschließen -f
von OSX nur.
#!/bin/bash
MY_DIR=$( cd $(dirname $(readlink `[[ $OSTYPE == linux* ]] && echo "-f"` $0)) ; pwd -P)
echo "$MY_DIR"
Hinweis:Ich glaube, dass dies eine solide, portable, ready-made-Lösung, die von immer langen aus diesem Grunde.
Unten ist ein vollständig POSIX-konform script / Funktion das ist deshalb cross-Plattform (funktioniert auf macOS zu, deren readlink
noch nicht unterstützt -f
ab 10.12 (Sierra)) - es wird verwendet, nur POSIX-shell-Funktionen der Sprache und nur POSIX-kompatibles Dienstprogramm fordert.
Es ist ein portable Implementierung des GNU readlink -e
(die strengere version readlink -f
).
Sie können führen Sie die Skript mit sh
oder Quelle Funktion in bash
, ksh
, und zsh
:
Zum Beispiel, innerhalb eines Skripts Sie können es verwenden, wie folgt vor, um die Ausführung Schrift wahr-Verzeichnis der Ursprung, die mit symlinks behoben:
trueScriptDir=$(dirname -- "$(rreadlink "$0")")
rreadlink
script / function definition:
Der code wurde so angepasst, mit Dankbarkeit aus diese Antwort.
Ich habe auch ein bash
-basierte stand-alone-Dienstprogramm-version hier, die Sie installieren können, mit
npm install rreadlink -g
, wenn Sie haben Node.js installiert.
#!/bin/sh
# SYNOPSIS
# rreadlink <fileOrDirPath>
# DESCRIPTION
# Resolves <fileOrDirPath> to its ultimate target, if it is a symlink, and
# prints its canonical path. If it is not a symlink, its own canonical path
# is printed.
# A broken symlink causes an error that reports the non-existent target.
# LIMITATIONS
# - Won't work with filenames with embedded newlines or filenames containing
# the string ' -> '.
# COMPATIBILITY
# This is a fully POSIX-compliant implementation of what GNU readlink's
# -e option does.
# EXAMPLE
# In a shell script, use the following to get that script's true directory of origin:
# trueScriptDir=$(dirname -- "$(rreadlink "$0")")
rreadlink() ( # Execute the function in a *subshell* to localize variables and the effect of `cd`.
target=$1 fname= targetDir= CDPATH=
# Try to make the execution environment as predictable as possible:
# All commands below are invoked via `command`, so we must make sure that
# `command` itself is not redefined as an alias or shell function.
# (Note that command is too inconsistent across shells, so we don't use it.)
# `command` is a *builtin* in bash, dash, ksh, zsh, and some platforms do not
# even have an external utility version of it (e.g, Ubuntu).
# `command` bypasses aliases and shell functions and also finds builtins
# in bash, dash, and ksh. In zsh, option POSIX_BUILTINS must be turned on for
# that to happen.
{ \unalias command; \unset -f command; } >/dev/null 2>&1
[ -n "$ZSH_VERSION" ] && options[POSIX_BUILTINS]=on # make zsh find *builtins* with `command` too.
while :; do # Resolve potential symlinks until the ultimate target is found.
[ -L "$target" ] || [ -e "$target" ] || { command printf '%s\n' "ERROR: '$target' does not exist." >&2; return 1; }
command cd "$(command dirname -- "$target")" # Change to target dir; necessary for correct resolution of target path.
fname=$(command basename -- "$target") # Extract filename.
[ "$fname" = '/' ] && fname='' # !! curiously, `basename /` returns '/'
if [ -L "$fname" ]; then
# Extract [next] target path, which may be defined
# *relative* to the symlink's own directory.
# Note: We parse `ls -l` output to find the symlink target
# which is the only POSIX-compliant, albeit somewhat fragile, way.
target=$(command ls -l "$fname")
target=${target#* -> }
continue # Resolve [next] symlink target.
fi
break # Ultimate target reached.
done
targetDir=$(command pwd -P) # Get canonical dir. path
# Output the ultimate target's canonical path.
# Note that we manually resolve paths ending in /. and /.. to make sure we have a normalized path.
if [ "$fname" = '.' ]; then
command printf '%s\n' "${targetDir%/}"
elif [ "$fname" = '..' ]; then
# Caveat: something like /var/.. will resolve to /private (assuming /var@ -> /private/var), i.e. the '..' is applied
# AFTER canonicalization.
command printf '%s\n' "$(command dirname -- "${targetDir}")"
else
command printf '%s\n' "${targetDir%/}/$fname"
fi
)
rreadlink "$@"
Eine Tangente an Sicherheit:
jarno, in Bezug auf die Funktion, sicherzustellen, dass builtin command
ist nicht beschattet durch ein alias oder ein shell-Funktion mit dem gleichen Namen, fragt in einem Kommentar:
Was ist, wenn
unalias
oderunset
und[
als Aliase oder shell-Funktionen?
Die motivation hinter rreadlink
sicherstellen, dass command
hat seine ursprüngliche Bedeutung ist zu verwenden Sie es auf bypass (gutartigen) Komfort Aliase und Funktionen Häufig verwendet, um Schatten, standard-Befehle im interaktiven shells, wie neu definieren ls
enthalten bevorzugten Optionen.
Ich denke, es ist sicher zu sagen, dass, wenn Sie ' re Umgang mit einem nicht vertrauenswürdigen, schädliche Umwelt, sich Gedanken über unalias
oder unset
- oder, für diese Angelegenheit, while
, do
, ...- neu definiert, ist nicht von Belang.
Es ist etwas die Funktion muß sich auf seine ursprüngliche Bedeutung und Verhalten - es gibt keinen Weg darum herum.
Das POSIX-shells erlauben Neudefinition der gelieferten und auch die Sprache keywords inhärent ein Sicherheits-Risiko (und schreiben paranoid-code ist schwer im Allgemeinen).
Zu Adresse Ihre Bedenken insbesondere:
Die Funktion beruht auf unalias
und unset
mit Ihrer ursprünglichen Bedeutung.Mit Ihnen neu definiert als shell-Funktionen in einer Art und Weise verändert, Ihr Verhalten wäre ein problem;Neudefinition als eine alias ist
nicht unbedingt ein Anliegen, weil zitieren (Teil -) Namen des Befehls (z.B., \unalias
) umgeht die Aliasnamen.
Jedoch, zu zitieren ist nicht eine option für die shell keywords (while
, for
, if
, do
, ...) und während die shell-keywords zu tun haben Vorrang vor shell Funktionen, in bash
und zsh
Aliase haben die höchste Priorität, also zum Schutz gegen shell-Schlüsselwort neudefinitionen, die Sie ausführen müssen unalias
mit Ihrem Namen (obwohl in non-interactive bash
Muscheln (wie z.B. Skripte) Aliase werden nicht standardmäßig erweitert - nur, wenn shopt -s expand_aliases
explizit aufgerufen wird zuerst).
Um sicherzustellen, dass unalias
- als builtin - hat seine ursprüngliche Bedeutung, die Sie verwenden müssen \unset
auf ihn zuerst, die erfordert, dass unset
seine ursprüngliche Bedeutung:
unset
ist ein shell - builtin, also, um sicherzustellen, dass es aufgerufen wird, als solche, Sie würde haben zu machen sicher, dass es sich nicht neu definiert als Funktion.Während Sie umgehen kann eine alias-form mit zitieren, die Sie nicht umgehen können, eine shell-Funktion - catch-22.
So, es sei denn, Sie können sich darauf verlassen, unset
seine ursprüngliche Bedeutung, von dem, was ich sagen kann, es gibt keine garantierte Möglichkeit, zu verteidigen gegen alle bösartigen neudefinitionen.
Common shell-Skripts, oft zu finden, dass Ihre "home" - Verzeichnis, auch wenn Sie angerufen werden, als einen symlink.Das script so zu finden, Ihr "real" - position von nur $0.
cat `mvn`
auf meinem system druckt ein Skript mit den folgenden, was einen guten Hinweis auf das, was Sie brauchen.
if [ -z "$M2_HOME" ] ; then
## resolve links - $0 may be a link to maven's home
PRG="$0"
# need this for relative symlinks
while [ -h "$PRG" ] ; do
ls=`ls -ld "$PRG"`
link=`expr "$ls" : '.*-> \(.*\)$'`
if expr "$link" : '/.*' > /dev/null; then
PRG="$link"
else
PRG="`dirname "$PRG"`/$link"
fi
done
saveddir=`pwd`
M2_HOME=`dirname "$PRG"`/..
# make it fully qualified
M2_HOME=`cd "$M2_HOME" && pwd`
function realpath {
local r=$1; local t=$(readlink $r)
while [ $t ]; do
r=$(cd $(dirname $r) && cd $(dirname $t) && pwd -P)/$(basename $t)
t=$(readlink $r)
done
echo $r
}
#example usage
SCRIPT_PARENT_DIR=$(dirname $(realpath "$0"))/..
Versuchen Sie dies:
cd $(dirname $([ -L $0 ] && readlink -f $0 || echo $0))
Da habe ich dieses viele Male im Laufe der Jahre, und dieses mal brauchte ich ein reines bash-portable-version, die ich verwenden könnte, für OSX und linux, ich ging voran und schrieb ein:
Die Wohn-version lebt hier:
https://github.com/keen99/shell-functions/tree/master/resolve_path
aber für den Willen ALSO, hier ist die aktuelle version (ich glaube, es ist gut getestet..aber ich bin offen für feedback!)
Vielleicht nicht schwer zu machen es Arbeit für plain bourne-shell (sh), aber ich habe nicht versucht...ich mag $FUNCNAME zu viel.:)
#!/bin/bash
resolve_path() {
#I'm bash only, please!
# usage: resolve_path <a file or directory>
# follows symlinks and relative paths, returns a full real path
#
local owd="$PWD"
#echo "$FUNCNAME for $1" >&2
local opath="$1"
local npath=""
local obase=$(basename "$opath")
local odir=$(dirname "$opath")
if [[ -L "$opath" ]]
then
#it's a link.
#file or directory, we want to cd into it's dir
cd $odir
#then extract where the link points.
npath=$(readlink "$obase")
#have to -L BEFORE we -f, because -f includes -L :(
if [[ -L $npath ]]
then
#the link points to another symlink, so go follow that.
resolve_path "$npath"
#and finish out early, we're done.
return $?
#done
elif [[ -f $npath ]]
#the link points to a file.
then
#get the dir for the new file
nbase=$(basename $npath)
npath=$(dirname $npath)
cd "$npath"
ndir=$(pwd -P)
retval=0
#done
elif [[ -d $npath ]]
then
#the link points to a directory.
cd "$npath"
ndir=$(pwd -P)
retval=0
#done
else
echo "$FUNCNAME: ERROR: unknown condition inside link!!" >&2
echo "opath [[ $opath ]]" >&2
echo "npath [[ $npath ]]" >&2
return 1
fi
else
if ! [[ -e "$opath" ]]
then
echo "$FUNCNAME: $opath: No such file or directory" >&2
return 1
#and break early
elif [[ -d "$opath" ]]
then
cd "$opath"
ndir=$(pwd -P)
retval=0
#done
elif [[ -f "$opath" ]]
then
cd $odir
ndir=$(pwd -P)
nbase=$(basename "$opath")
retval=0
#done
else
echo "$FUNCNAME: ERROR: unknown condition outside link!!" >&2
echo "opath [[ $opath ]]" >&2
return 1
fi
fi
#now assemble our output
echo -n "$ndir"
if [[ "x${nbase:=}" != "x" ]]
then
echo "/$nbase"
else
echo
fi
#now return to where we were
cd "$owd"
return $retval
}
hier ist ein klassisches Beispiel, Dank zu brauen:
%% ls -l `which mvn`
lrwxr-xr-x 1 draistrick 502 29 Dec 17 10:50 /usr/local/bin/mvn@ -> ../Cellar/maven/3.2.3/bin/mvn
verwenden Sie diese Funktion, und es erfolgt die Rückkehr der echten Pfad:
%% cat test.sh
#!/bin/bash
. resolve_path.inc
echo
echo "relative symlinked path:"
which mvn
echo
echo "and the real path:"
resolve_path `which mvn`
%% test.sh
relative symlinked path:
/usr/local/bin/mvn
and the real path:
/usr/local/Cellar/maven/3.2.3/libexec/bin/mvn
Arbeiten, um die Mac-Inkompatibilität, ich kam mit
echo `php -r "echo realpath('foo');"`
Nicht groß, aber cross-OS
Hier ist, wie man mit dem Pfad zu der Datei im MacOS - /Unix mithilfe von inline-Perl-Skript:
FILE=$(perl -e "use Cwd qw(abs_path); print abs_path('$0')")
Ebenso, um das Verzeichnis einer symlinked Datei:
DIR=$(perl -e "use Cwd qw(abs_path); use File::Basename; print dirname(abs_path('$0'))")
Ist Ihr Pfad ein Verzeichnis ist, oder könnte es eine Datei?Wenn es ein Verzeichnis ist, ist es einfach:
(cd "$DIR"; pwd -P)
Jedoch, wenn es vielleicht eine Datei, dann funktioniert es nicht:
DIR=$(cd $(dirname "$FILE"); pwd -P); echo "${DIR}/$(readlink "$FILE")"
weil der symlink könnten lösen, in einen relativen oder absoluten Pfad.
Auf scripts, die ich brauchen, um zu finden die tatsächlichen Pfad, so dass ich vielleicht Referenz-Konfiguration oder sonstigen Skripts verwenden, installiert zusammen mit ihm, ich benutze dieses:
SOURCE="${BASH_SOURCE[0]}"
while [ -h "$SOURCE" ]; do # resolve $SOURCE until the file is no longer a symlink
DIR="$( cd -P "$( dirname "$SOURCE" )" && pwd )"
SOURCE="$(readlink "$SOURCE")"
[[ $SOURCE != /* ]] && SOURCE="$DIR/$SOURCE" # if $SOURCE was a relative symlink, we need to resolve it relative to the path where the symlink file was located
done
Sie könnte set SOURCE
zu jeder Datei-Pfad.Im Grunde, so lange, wie Sie den Pfad ein symbolischer Link, behebt es, dass der symlink.Der trick ist, in der letzten Zeile der Schleife.Wenn die gelöst symlink ist absolut, es verwenden, als SOURCE
.Allerdings, wenn es relativ ist, werden voranstellen DIR
für ihn, das war aufgelöst in einer realen Lage der einfache trick, den ich zuerst beschrieben.
Ich glaube, das ist der wahre und endgültige "Lösung für ein symlink" ob es ein Verzeichnis oder eine nicht-Verzeichnis, mithilfe der Bash-Shell:
function readlinks {(
set -o errexit -o nounset
declare n=0 limit=1024 link="$1"
# If it's a directory, just skip all this.
if cd "$link" 2>/dev/null
then
pwd -P "$link"
return 0
fi
# Resolve until we are out of links (or recurse too deep).
while [[ -L $link ]] && [[ $n -lt $limit ]]
do
cd "$(dirname -- "$link")"
n=$((n + 1))
link="$(readlink -- "${link##*/}")"
done
cd "$(dirname -- "$link")"
if [[ $n -ge $limit ]]
then
echo "Recursion limit ($limit) exceeded." >&2
return 2
fi
printf '%s/%s\n' "$(pwd -P)" "${link##*/}"
)}
Beachten Sie, dass alle cd
und set
Sachen erfolgt in einer subshell.
Hier präsentiere ich, was ich glaube zu sein, ein cross-Plattform (Linux und macOS mindestens) eine Lösung auf die Antwort, die gut funktioniert für mich derzeit.
crosspath()
{
local ref="$1"
if [ -x "$(which realpath)" ]; then
path="$(realpath "$ref")"
else
path="$(readlink -f "$ref" 2> /dev/null)"
if [ $? -gt 0 ]; then
if [ -x "$(which readlink)" ]; then
if [ ! -z "$(readlink "$ref")" ]; then
ref="$(readlink "$ref")"
fi
else
echo "realpath and readlink not available. The following may not be the final path." 1>&2
fi
if [ -d "$ref" ]; then
path="$(cd "$ref"; pwd -P)"
else
path="$(cd $(dirname "$ref"); pwd -P)/$(basename "$ref")"
fi
fi
fi
echo "$path"
}
Hier ist ein macOS (nur?) Lösung.Möglicherweise besser geeignet, um die ursprüngliche Frage.
mac_realpath()
{
local ref="$1"
if [[ ! -z "$(readlink "$ref")" ]]; then
ref="$(readlink "$1")"
fi
if [[ -d "$ref" ]]; then
echo "$(cd "$ref"; pwd -P)"
else
echo "$(cd $(dirname "$ref"); pwd -P)/$(basename "$ref")"
fi
}