Портативный способ получить абсолютный путь скрипта?

Что такое переносимый способ для (zsh) скрипта для определения его абсолютного пути?

В Linux я использую что-то вроде

mypath=$(readlink -f $0) 

… но это не переносимо. (Например, readlink на darwin не распознает флаг -f и не имеет эквивалента.) (Кроме того, использование readlink для этого, по общему признанию, довольно неясно выглядящее взлома.)

Что более переносимо?

7 Solutions collect form web for “Портативный способ получить абсолютный путь скрипта?”

С zsh это просто:

 mypath=$0:A 

Теперь для других оболочек, хотя realpath() и readlink() являются стандартными функциями (последний является системным вызовом), realpath и readlink не являются стандартной командой, хотя некоторые системы имеют один или другой или оба с различным поведением и набором функций.

Как часто, для переносимости, вы можете прибегнуть к perl :

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

Это будет больше напоминать realpath() GNU readlink -f чем realpath() (GNU readlink -e ), поскольку она не будет жаловаться, если файл не существует до тех пор, пока его dirname делает.

В zsh вы можете сделать следующее:

 mypath=${0:a} 

Или, чтобы получить каталог, в котором находится скрипт:

 mydir=${0:a:h} 

Источник: zshexpn (1) справочная страница, раздел ИСТОРИЯ РАСШИРЕНИЯ, подразделы Модификаторы (или просто info -f zsh -n Modifiers ).

Я использовал это уже несколько лет.

 # The absolute, canonical ( no ".." ) path to this script canonical=$(cd -P -- "$(dirname -- "$0")" && printf '%s\n' "$(pwd -P)/$(basename -- "$0")") 

Этот синтаксис должен быть переносимым для любого интерпретатора стиля оболочки Bourne (проверен с помощью bash , ksh88 , ksh93 , zsh , mksh , dash и busybox sh ):

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

Эта версия добавляет совместимость с устаревшей оболочкой AT & T Bourne (не 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 

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

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

Кстати, это работает в любой оболочке в стиле Борна.

Если вы имели в виду путь со всеми разрешенными символическими ссылками, это другое дело. readlink -f работает на Linux (за исключением некоторых урезанных систем BusyBox), FreeBSD, NetBSD, OpenBSD и Cygwin, но не на OS / X, AIX, HP / UX или Solaris. Если у вас есть readlink , вы можете вызвать его в цикле:

 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 } 

Если у вас нет readlink , вы можете аппроксимировать его с помощью ls -n , но это работает только в том случае, если ls не искажает какой-либо непечатаемый символ в имени файла.

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

(Дополнительный z в том случае, если цель ссылки заканчивается в новой строке, которая в противном случае заменила бы команду. Функция realpath не обрабатывает этот случай для имен каталогов, кстати.)

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

Шаг 10 спецификации cd

Если параметр -P действует, переменная среды $PWD должна быть установлена ​​в строку, которая будет выводиться pwd -P . Если для нового каталога или любого родителя этого каталога недостаточно разрешения для определения текущего рабочего каталога, значение переменной среды $PWD не указано.

И на pwd -P

Путь, записанный на стандартный вывод, не должен содержать компонентов, относящихся к файлам символической ссылки типа. Если есть несколько путей, которые утилита pwd могла бы записать на стандартный вывод, один начинается с символа одиночного / косой черты и одного или нескольких, начинающихся с двух символов / косой черты, тогда он должен написать путь, начинающийся с символа одиночного / косой черты. Имя пути не должно содержать лишних символов / косой черты после следующих символов одного или двух / косой черты.

Это связано с тем, что cd -P должен установить текущий рабочий каталог на то, что pwd -P должен в противном случае распечатать и что cd - должен напечатать $OLDPWD что работает следующее:

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

ВЫВОД

 /home/mikeserv/test/ln 

ждать его…

 cd -P . ; cd . ; cd - 

ВЫВОД

 /home/mikeserv/test/dir 

И когда я печатаю с помощью cd - я $OLDPWD . cd устанавливает $PWD как только я cd -P . $PWD теперь является абсолютным путем к / – поэтому мне не нужны никакие другие переменные. И на самом деле, мне не нужно даже отставать . но существует заданное поведение сброса $PWD в $HOME в интерактивной оболочке, когда cd не укомплектован. Так что это хорошая привычка развиваться.

Так что просто сделать вышеописанное на пути в ${0%/*} должно быть более чем достаточно, чтобы проверить путь $0 , но в случае, когда $0 сам является софт-ссылкой, вы, вероятно, не можете изменить каталог в нем, к сожалению ,

Вот функция, которая будет обрабатывать это:

 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 } 

Он стремится сделать столько, сколько мог бы в текущей оболочке – без вызова подоболочки – хотя для ошибок и мягких ссылок есть не подобраны подсели, которые не указывают на каталоги. Это зависит от POSIX-совместимой оболочки и POSIX-совместимого ls а также от чистого пространства имен _function() . Он по-прежнему будет функционировать нормально, без последнего, хотя он может перезаписать, а затем unset некоторые текущие функции оболочки в этом случае. В общем, все эти зависимости должны быть достаточно надежно доступны на машине Unix.

Вызывается с аргументами или без них, первое, что он делает, сбрасывает $PWD на его каноническое значение – он решает любые ссылки на них по мере необходимости. Вызывается без аргументов, и это все; но вызывается с ними, и он будет разрешать и canonicalize путь для каждого или иначе печатать сообщение для stderr почему бы и нет.

Поскольку он в основном работает в текущей оболочке, он должен иметь возможность обрабатывать список аргументов любой длины. Он также ищет переменную $_zdlm (которую он также unset когда она проходит) и печатает свое значение с C-экранированием сразу справа от каждого из своих аргументов, каждый из которых всегда сопровождается одним символом \n ewline ,

Он меняет много изменений каталога, но, кроме установки его канонического значения, он не влияет на $PWD , хотя $OLDPWD ни в коем случае нельзя рассчитывать, когда он $OLDPWD .

Он пытается закрыть каждый из своих аргументов, как только это возможно. Сначала он пытается cd в $1 . Если он может печатать канонический путь аргумента в stdout . Если он не может проверить, что существует $1 и не является мягкой ссылкой. Если true, он печатает.

Таким образом, он обрабатывает любой аргумент типа файла, который оболочка имеет разрешения для адресации, если только $1 является символической ссылкой, которая не указывает на каталог. В этом случае он вызывает цикл while в подоболочке.

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

 cd -...ls...echo / 

Он выводит слева от вывода ls столько, сколько нужно, чтобы полностью содержать имя ссылки и строку -> . В то время как я вначале старался избегать этого со shift и $IFS оказывается, что это самый надежный метод, как я могу себе представить. Это то же самое, что и у бедного пользователя Gilles's bad_mans_readlink , и это хорошо сделано.

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

Пример использования:

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

ВЫВОД

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

Или возможно …

 ls dir/ file file? folder/ link@ ln@ script* script3@ script4@ zdlm=\\0 zpath * | cat -A 

ВЫВОД

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

как насчет симпатической однострочной линии, когда существует возможность использовать python для предотвращения переопределения алгоритма?

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

так же, как https://stackoverflow.com/a/7305217

  • Завершение zsh не исправимо? (вопросительный знак) в дорожках
  • Расширяет ли расширение zsh по-разному в неинтерактивных скриптах?
  • Почему экспорт vim как EDITOR в zsh отключает быстрые клавиши?
  • autojump: поддержка папок, которые разделяют большинство родителей с текущим каталогом
  • Всплывающее окно Python virtualenvwrapper и ZSH
  • Почему требуется перезагрузка ежедневной .zshrc с использованием псевдонима, содержащего $ (date -u +% Y% m% d)?
  • gvim pipe не читает zshrc при запуске с рабочего стола
  • Как установить bash для запуска * .exe с моно?
  • Запустите .zshrc при передаче команды через -c
  • Записи вкладки Zsh не работают по желанию для частичных путей
  • Как фильтровать glob в zsh
  • Linux и Unix - лучшая ОС в мире.