Как разрешать символические ссылки в сценарии оболочки

Учитывая абсолютный или относительный путь (в Unix-подобной системе), я хотел бы определить полный путь к цели после разрешения любых промежуточных символических ссылок. Бонусные баллы за одновременное разрешение ~ имени пользователя.

Если целью является каталог, возможно, chdir () может быть в каталоге, а затем вызвать getcwd (), но я действительно хочу сделать это из сценария оболочки, а не писать помощника C. К сожалению, оболочки имеют тенденцию пытаться скрыть существование символических ссылок от пользователя (это bash на 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 $ 

То, что я хочу, является функцией resolve (), так что при выполнении из каталога tmp в приведенном выше примере разрешите (“foo”) == “/ Users / greg / tmp / bar”.

    16 Solutions collect form web for “Как разрешать символические ссылки в сценарии оболочки”

    Согласно стандартам, pwd -P должен возвращать путь с разрешенными символическими ссылками.

    Функция C char *getcwd(char *buf, size_t size) из unistd.h должна иметь такое же поведение.

    getcwd pwd

     readlink -f "$path" 

    Примечание редактора: вышеупомянутое работает с GNU readlink и FreeBSD / PC-BSD / OpenBSD , но не с OS X с 10.11.
    GNU readlink предлагает дополнительные связанные параметры, такие как -m для разрешения символической ссылки, существует ли конечная цель.

    Заметьте, поскольку GNU coreutils 8.15 (2012-01-06), есть доступная программа realpath, которая менее туповата и более гибкая, чем выше. Он также совместим с тем же именем, что и FreeBSD. Он также включает функции для создания относительного пути между двумя файлами.

     realpath $path 

    [Добавление администратора ниже из комментария halloleo – danorton]

    Для Mac OS X (не менее 10.11.x) используйте readlink без опции -f :

     readlink $path 

    Примечание редактора: Это не будет рекурсивно разрешать символические ссылки и, таким образом, не будет сообщать конечную цель; например, с учетом символьной ссылки a которая указывает на b , которая, в свою очередь, указывает на c , это будет только сообщать b (и не гарантирует, что он будет выводиться как абсолютный путь ).
    Используйте следующую команду perl для OS X, чтобы заполнить пробел в отсутствующей функции readlink -f :
    perl -MCwd -le 'print Cwd::abs_path(shift)' "$path"

    «pwd -P», похоже, работает, если вам просто нужен каталог, но если по какой-то причине вы хотите получить имя фактического исполняемого файла, я не думаю, что это помогает. Вот мое решение:

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

    Один из моих фаворитов – realpath foo

     realpath - возвращает каноническое абсолютное имя пути
    
     realpath расширяет все символические ссылки и разрешает ссылки на символы «/ ./», «/../» и дополнительные символы «/» в нулевой завершаемой строке, названной путем и
            хранит каноническое абсолютное имя пути в буфере размера PATH_MAX с именем resol_path.  Полученный путь не будет иметь символической ссылки: «/ ./» или
            '/../' компоненты.
    
     readlink -e [filepath] 

    похоже, именно то, о чем вы просите, – он принимает путь arbirary, разрешает все символические ссылки и возвращает «реальный» путь – и это «стандартный * nix», который, вероятно, все системы уже имеют

    Объединяя некоторые из данных решений, зная, что readlink доступен для большинства систем, но для этого нужны разные аргументы, это хорошо работает для меня на OSX и Debian. Я не уверен в BSD-системах. Возможно, условие должно быть [[ $OSTYPE != darwin* ]] чтобы исключить -f только из OSX.

     #!/bin/bash MY_DIR=$( cd $(dirname $(readlink `[[ $OSTYPE == linux* ]] && echo "-f"` $0)) ; pwd -P) echo "$MY_DIR" 

    Другой путь:

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

    Примечание. Я считаю, что это твердое, портативное, готовое решение, которое по этой причине неизменно длинно .

    Ниже приведен полностью совместимый с POSIX скрипт / функция, которая, следовательно, кросс-платформенная (работает также и с macOS, чья readlink прежнему не поддерживает -f с 10.12 (Sierra)) – она ​​использует только функции языка оболочки POSIX и только POSIX- совместимые служебные вызовы.

    Это переносная реализация readlink -e GNU (более строгая версия readlink -f ).

    Вы можете запустить скрипт с помощью sh или source в bash , ksh и zsh :

    Например, внутри скрипта вы можете использовать его следующим образом, чтобы получить подлинный каталог источника запуска, с разрешенными символическими ссылками:

     trueScriptDir=$(dirname -- "$(rreadlink "$0")") 

    rreadlink скрипта / функции rreadlink :

    Код был адаптирован с благодарностью от этого ответа .
    Здесь я также создал автономную версию утилиты bash , которую вы можете установить с помощью
    npm install rreadlink -g , если у вас установлен Node.js.

     #!/bin/sh # SYNOPSIS # rreadlink  # DESCRIPTION # Resolves  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 (eg, 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), ie the '..' is applied # AFTER canonicalization. command printf '%s\n' "$(command dirname -- "${targetDir}")" else command printf '%s\n' "${targetDir%/}/$fname" fi ) rreadlink "$@" 

    Тангенс по безопасности:

    jarno , ссылаясь на функцию, гарантирующую, что встроенная command не затенена функцией псевдонима или оболочки с тем же именем, запрашивает в комментарии:

    Что делать, если unalias или unset и [ установлены как псевдонимы или функции оболочки?

    Мотивация rreadlink гарантирующая, что command имеет свое первоначальное значение, состоит в том, чтобы использовать ее для обхода (мягких) псевдонимов удобства и функций, часто используемых для теневого использования стандартных команд в интерактивных shellх, таких как переопределение ls для включения избранных опций.

    Я думаю, что можно с уверенностью сказать, что, если вы не имеете дело с ненадежной, злонамеренной средой, беспокоиться о unalias или unset – или, если на то пошло, в while , do , … – пересмотр не является проблемой.

    Есть что-то, что функция должна полагаться, чтобы иметь свое первоначальное значение и поведение – нет никакого способа обойти это.
    То, что POSIX-подобные оболочки позволяют переопределять встроенные и даже языковые ключевые слова, по сути, представляют собой угрозу безопасности (и писать параноидальный код в целом сложно).

    Чтобы конкретно решить свои проблемы:

    Функция полагается на unalias и unset имеет своего первоначального значения. Если они переопределены как функции оболочки таким образом, чтобы изменить их поведение, это будет проблемой; переопределение в качестве псевдонима не обязательно вызывает беспокойство, поскольку цитирование (часть) имени команды (например, \unalias ) обходит псевдонимы.

    Однако цитирование не является вариантом для ключевых слов оболочки ( while , for , if , do , …), а в то время как ключевые слова оболочки имеют приоритет над функциями оболочки, в bash и zsh псевдонимы имеют наивысший приоритет, поэтому для защиты от shell- ключевое слово unalias вы должны запускать unalias с их именами (хотя в неинтерактивных shellх bash (таких как скрипты) псевдонимы не shopt -s expand_aliases по умолчанию – только в том случае, если shopt -s expand_aliases явно вызывается первым).

    Чтобы гарантировать, что unalias – как встроенный – имеет свое первоначальное значение, вы должны сначала использовать \unset на нем, что требует, чтобы unset имел свое первоначальное значение:

    unset – это встроенная shell, поэтому, чтобы гарантировать, что она вызывается как таковая, вам нужно убедиться, что она сама не переопределена как функция . В то время как вы можете обойти форму псевдонима с помощью цитирования, вы не можете обойти форму-функцию-catch 22.

    Таким образом, если вы не можете полагаться на то, чтобы не иметь своего первоначального значения, из того, что я могу сказать, нет гарантированного способа защиты от всех вредоносных переопределений.

    Общие сценарии оболочки часто должны найти свой «домашний» каталог, даже если они вызываются как символическая ссылка. Таким образом, сценарий должен найти свою «реальную» позицию всего за $ 0.

     cat `mvn` 

    в моей системе печатается сценарий, содержащий следующее, что должно быть хорошим намеком на то, что вам нужно.

     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"))/.. 

    Попробуй это:

     cd $(dirname $([ -L $0 ] && readlink -f $0 || echo $0)) 

    С тех пор, как я сталкивался с этим много раз с годами, и на этот раз мне понадобилась чистая портативная версия bash, которую я мог бы использовать на OSX и Linux, я пошел вперед и написал один:

    Живая версия живет здесь:

    https://github.com/keen99/shell-functions/tree/master/resolve_path

    но ради SO, вот текущая версия (я чувствую, что она хорошо протестирована … но я открыт для обратной связи!)

    Не может быть трудно заставить его работать для простой оболочки bourne (sh), но я не пробовал … Мне очень нравится $ FUNCNAME. 🙂

     #!/bin/bash resolve_path() { #I'm bash only, please! # usage: resolve_path  # 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 } 

    вот classический пример, благодаря заварке:

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

    используйте эту функцию, и она вернет путь -real:

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

    Чтобы обойти несовместимость Mac, я придумал

     echo `php -r "echo realpath('foo');"` 

    Не большая, но перекрестная ОС

    Является ли ваш путь каталогом или может быть файлом? Если это каталог, это просто:

     (cd "$DIR"; pwd -P) 

    Однако, если это может быть файл, это не сработает:

     DIR=$(cd $(dirname "$FILE"); pwd -P); echo "${DIR}/$(readlink "$FILE")" 

    потому что символическая ссылка может быть разрешена в относительный или полный путь.

    В сценариях мне нужно найти реальный путь, чтобы я мог ссылаться на конфигурацию или другие скрипты, установленные вместе с ним, я использую это:

     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 

    Вы можете установить SOURCE в любой путь к файлу. В принципе, до тех пор, пока путь является символической ссылкой, он решает эту символическую ссылку. Трюк находится в последней строке цикла. Если разрешенная символическая ссылка является абсолютной, она будет использовать ее как SOURCE . Однако, если он относительный, он добавит DIR для него, который был разрешен в реальное местоположение простым трюком, который я впервые описал.

    Я считаю, что это истинный и окончательный «способ разрешить символическую ссылку», будь то каталог или некаталог, используя Bash:

     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##*/}" )} 

    Обратите внимание, что все cd и set имеют место в подоболочке.

    Вот как можно получить фактический путь к файлу в MacOS / Unix с помощью встроенного скрипта Perl:

     FILE=$(perl -e "use Cwd qw(abs_path); print abs_path('$0')") 

    Аналогично, чтобы получить каталог символического файла:

     DIR=$(perl -e "use Cwd qw(abs_path); use File::Basename; print dirname(abs_path('$0'))") 
    Interesting Posts

    android BluetoothDevice.getName () return null

    Нажмите, чтобы показать изображение в ImageView

    Является ли разгон законным?

    Бесплатное программное обеспечение для преобразования видео, которое поддерживает широкоэкранный формат

    Передача параметров в действие с помощью ModelDriven в Struts 2

    Почему LINQ .Where (предикат) .First () быстрее, чем .First (предикат)?

    mylib.so имеет текстовые перестановки. Это напрасно тратит память и представляет угрозу безопасности. Пожалуйста исправьте

    В streamах Java просматривается действительно только для отладки?

    Может кто-нибудь объяснить, как это произошло?

    Sqlite преобразует строку на сегодняшний день

    Почему getline пропускает первую строку?

    BASH: повторение последнего запуска команды

    Очистка Mac OS X

    Как передать переменные командлетом Invoke-Command?

    Как применить одну и ту же функцию к каждому указанному столбцу в таблице данных.

    Давайте будем гением компьютера.