Как получить каталог скриптов в POSIX sh?
У меня есть следующий код в моем скрипте bash. Теперь я хочу использовать его в POSIX sh. Так как же это конвертировать? Благодарю.
DIR="$( cd "$( dirname "${BASH_SOURCE[0]}" )" > /dev/null && pwd )"
2 ответа
POSIX-оболочка (sh
) аналог $BASH_SOURCE
является $0
, см внизу для справочной информации
Предостережение: принципиальное различие заключается в том, что если ваш скрипт получен (загружен в текущую оболочку с .
), фрагменты ниже не будут работать должным образом. объяснение ниже
Обратите внимание, что я изменился DIR
в dir
в приведенных ниже фрагментах, потому что лучше не использовать имена переменных в верхнем регистре, чтобы избежать конфликтов с переменными среды и специальными переменными оболочки.
CDPATH=
префикс занимает место > /dev/null
в оригинальной команде: $CDPATH
устанавливается в нулевую строку, чтобы гарантировать, что cd
никогда ничего не повторяет.
В простейшем случае это будет сделано (эквивалент команды OP):
dir=$(CDPATH= cd -- "$(dirname -- "$0")" && pwd)
Если вы также хотите разрешить результирующий путь к каталогу до его конечной цели, если каталог и / или его компоненты являются символическими ссылками, добавьте -P
к pwd
команда:
dir=$(CDPATH= cd -- "$(dirname -- "$0")" && pwd -P)
Предостережение: это НЕ то же самое, что найти собственный истинный каталог происхождения скрипта:
Допустим твой сценарий foo
символическая ссылка на /usr/local/bin/foo
в $PATH
, но его истинный путь /foodir/bin/foo
,
Выше будет еще доклад /usr/local/bin
, потому что разрешение символической ссылки (-P
) применяется к каталогу, /usr/local/bin
, а не к самому сценарию.
Чтобы найти собственный истинный исходный каталог сценария, вам необходимо проверить путь сценария, чтобы увидеть, является ли он символической ссылкой, и, если это так, перейти по (цепочке) символических ссылок к конечному целевому файлу, а затем извлечь путь к каталогу из канонический путь к целевому файлу.
ГНУ readlink -f
(лучше: readlink -e
) может сделать это для вас, но readlink
не является утилитой POSIX
В то время как платформы BSD, включая OSX, имеют readlink
утилита тоже на OSX не поддерживает -f
функциональность. Тем не менее, чтобы показать, насколько простой становится задача, если readlink -f
доступен: dir=$(dirname "$(readlink -f -- "$0")")
,
На самом деле, нет утилиты POSIX для разрешения файловых ссылок. Есть способы обойти это, но они громоздки и не совсем надежны:
Следующая POSIX-совместимая функция оболочки реализует то, что GNU readlink -e
делает и является достаточно надежным решением, которое дает сбой только в двух редких случаях:
- пути со встроенными символами новой строки (очень редко)
- имена файлов, содержащие буквенную строку
->
(также редко)
С помощью этой функции, названной rreadlink
, определено, следующее определяет истинный путь к каталогу сценария:
dir=$(dirname -- "$(rreadlink "$0")")
rreadlink()
исходный код - поместите перед обращением к нему в скриптах:
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
)
Чтобы быть надежным и предсказуемым, функция использует command
чтобы были вызваны только встроенные функции оболочки или внешние утилиты (игнорирует перегрузки в виде псевдонимов и функций).
Это было проверено в последних версиях следующих оболочек: bash
, dash
, ksh
, zsh
,
Как обращаться с источниками вызовов:
тл; др:
Использование только функций POSIX:
- Вы не можете определить путь к скрипту в источнике вызова (кроме
zsh
что, однако, обычно не действует какsh
). - Вы можете определить, идет ли ваш сценарий ТОЛЬКО в том случае, если ваш сценарий получен напрямую из оболочки (например, в профиле оболочки / файле инициализации; возможно, через цепочку источников), сравнивая
$0
к имени / пути исполняемого файла оболочки (кромеzsh
где, как отмечено$0
действительно путь текущего скрипта). В отличие от (за исключениемzsh
), сценарий, полученный из другого сценария, который был вызван непосредственно, содержит путь этого сценария в$0
, - Чтобы решить эти проблемы,
bash
,ksh
, а такжеzsh
иметь нестандартные функции, которые позволяют определять фактический путь к сценарию даже в сценариях с источником, а также определять, используется сценарий или нет; например, вbash
,$BASH_SOURCE
всегда содержит путь работающего скрипта, независимо от того, идет он из источника или нет, и[[ $0 != "$BASH_SOURCE" ]]
может использоваться для проверки того, что сценарий получен.
Чтобы показать, почему этого нельзя сделать, давайте проанализируем команду из ответа Уолтера А.
# NOT recommended - see discussion below.
DIR=$( cd -P -- "$(dirname -- "$(command -v -- "$0")")" && pwd -P )
- (Два в стороне:
- С помощью
-P
дважды избыточно - достаточно использовать его сpwd
, - В команде отсутствует глушение
cd
Потенциальный вывод stdout, если$CDPATH
случается быть установленным.)
- С помощью
command -v -- "$0"
command -v -- "$0"
предназначен для охвата еще одного сценария: если сценарий получен из интерактивной оболочки,$0
обычно содержит простое имя файла исполняемого файла оболочки (sh
), в таком случаеdirname
просто вернется.
(потому что это то, чтоdirname
неизменно делает, когда дан аргумент без компонента пути).command -v -- "$0"
затем возвращает абсолютный путь этой оболочки через$PATH
уважать (/bin/sh
). Обратите внимание, однако, что для оболочек входа в систему на некоторых платформах (например, OSX) их имена файлов имеют префикс-
в$0
(-sh
), в таком случаеcommand -v -- "$0"
не работает как задумано (возвращает пустую строку).- Наоборот,
command -v -- "$0"
может вести себя неправильно в двух сценариях без источников, в которых исполняемый файл оболочки,sh
, вызывается напрямую, со скриптом в качестве аргумента:- если сам скрипт не является исполняемым:
command -v -- "$0"
может вернуть пустую строку, в зависимости от того, какая конкретная оболочка действует какsh
в данной системе:bash
,ksh
, а такжеzsh
вернуть пустую строку; толькоdash
отголоски$0
POSIX спец. заcommand
не говорит явноcommand -v
при применении к пути к файловой системе должен возвращать только исполняемые файлы - вот чтоbash
,ksh
, а такжеzsh
делать - но вы можете утверждать, что это подразумевается самой цельюcommand
; как ни странно,dash
, который обычно является самым совместимым гражданином POSIX, здесь отклоняется от стандарта. В отличие отksh
здесь единственный гражданин модели, потому что он единственный, который сообщает только о исполняемых файлах и сообщает о них с абсолютным (хотя и не нормализованным) путем, как того требует спецификация. - если скрипт исполняемый, но не в
$PATH
и вызов использует свое простое имя файла (например,sh myScript
),command -v -- "$0"
также вернет пустую строку, кромеdash
,
- если сам скрипт не является исполняемым:
- Учитывая, что каталог скрипта не может быть определен при его получении - потому что
$0
затем не содержит эту информацию (кромеzsh
который обычно не действует какsh
) - нет хорошего решения этой проблемы.- Возвращение пути к каталогу исполняемого файла оболочки в этой ситуации имеет ограниченное использование - это, в конце концов, не каталог скрипта - за исключением, возможно, дальнейшего использования этого пути в тесте, чтобы определить, является ли сценарий источником.
- Более надежным подходом было бы просто проверить
$0
непосредственно:[ "$0" = "sh" ] || [ "$0" = "-sh" ] || [ "$0" = "/bin/sh" ]
- Более надежным подходом было бы просто проверить
- Однако даже это не работает, если сценарий получен из другого сценария (который был вызван непосредственно), потому что
$0
затем просто содержит путь сценария поиска.
- Возвращение пути к каталогу исполняемого файла оболочки в этой ситуации имеет ограниченное использование - это, в конце концов, не каталог скрипта - за исключением, возможно, дальнейшего использования этого пути в тесте, чтобы определить, является ли сценарий источником.
- Учитывая ограниченную полезность
command -v -- "$0"
в исходных сценариях и в том факте, что он нарушает два исходных сценария, я голосую за НЕ использовать его, что оставляет нас с:- Все сценарии, не связанные с источниками , охватываются.
- В вызовах из источника вы не можете определить путь сценария, и в лучшем случае при ограниченных обстоятельствах вы можете определить, происходит ли получение источника:
- При получении напрямую от оболочки (например, из профиля оболочки / файла инициализации),
$dir
заканчивается либо содержащий.
, если исполняемый файл оболочки был вызван как простое имя файла (применениеdirname
просто имя файла всегда возвращает.
) или путь к каталогу исполняемого файла оболочки в противном случае..
не может быть надежно отличен от вызова из внешнего источника из текущего каталога. - Когда получены из другого сценария (который сам не был также),
$0
содержит путь к этому сценарию, а исходный сценарий не может определить, так ли это на самом деле.
- При получении напрямую от оболочки (например, из профиля оболочки / файла инициализации),
Исходная информация:
POSIX определяет поведение $0
в отношении сценариев оболочки здесь.
По существу, $0
должен отражать путь к файлу скрипта, как указано, что подразумевает:
- НЕ полагайтесь на
$0
содержащий абсолютный путь. $0
содержит абсолютный путь, только если:- вы явно указываете абсолютный путь; например:
~/bin/myScript
(при условии, что сам скрипт является исполняемым)sh ~/bin/myScript
- Вы вызываете исполняемый скрипт по простому имени файла, что требует, чтобы он был и исполняемым, и в
$PATH
; за кадром система трансформируетсяmyScript
в абсолютный путь, а затем выполняет его; например:myScript # executes /home/jdoe/bin/myScript, for instance
- вы явно указываете абсолютный путь; например:
Во всех остальных случаях
$0
будет отражать путь к скрипту, как указано:- При явном обращении
sh
со скриптом это может быть просто имя файла (например,sh myScript
) или относительный путь (например,sh ./myScript
) - При непосредственном вызове исполняемого скрипта это может быть относительный путь (например,
./myScript
- обратите внимание, что простое имя файла может найти только сценарии в$PATH
).
- При явном обращении
На практике, bash
, dash
, ksh
, а также zsh
все демонстрируют это поведение.
В отличие от этого, POSIX НЕ предписывает значение $0
при поиске скрипта (с помощью специальной встроенной утилиты .
("точка")), поэтому вы не можете полагаться на нее, и на практике поведение различается для разных оболочек.
- Таким образом, вы не можете использовать вслепую
$0
когда ваш сценарий получен и ожидается стандартизированное поведение.- На практике,
bash
,dash
, а такжеksh
Покидать$0
нетронутым при поиске сценариев, что означает, что$0
содержит абонента$0
значение, или, точнее,$0
значение самого последнего звонящего в цепочке вызовов, который сам не был получен; Таким образом,$0
может указывать либо на исполняемый файл оболочки, либо на путь другого (непосредственно вызываемого) сценария, который использовал текущий. - В отличие от
zsh
как одинокий инакомыслящий, на самом деле сообщает путь текущего скрипта в$0
, Наоборот,$0
не предоставит никаких указаний относительно того, используется сценарий или нет. - Короче говоря: используя только функции POSIX, вы не можете с уверенностью сказать, происходит ли поиск сценария под рукой, каков путь данного сценария, и каковы отношения
$0
к текущему пути скрипта есть.
- На практике,
- Если вам нужно справиться с этой ситуацией, вы должны определить конкретную оболочку и получить доступ к ее специфическим нестандартным функциям:
bash
,ksh
, а такжеzsh
все они предлагают свои собственные способы получения пути работающего скрипта, даже когда он получен.
Ради полноты: значение $0
в других контекстах:
- В функции оболочки POSIX обязывает
$0
оставить без изменений; следовательно, какое бы значение он ни имел вне функции, он также будет иметь значение и внутри.- На практике,
bash
,dash
, а такжеksh
ведите себя так. - Снова,
zsh
является одиноким диссидентом и сообщает имя функции.
- На практике,
- В оболочке, которая приняла командную строку через
-c
опция при запуске, это первый операнд (не опциональный аргумент), который устанавливает$0
; например:sh -c 'echo \$0: $0 \$1: $1' foo one # -> '$0: foo $1: one'
bash
,dash
,ksh
, а такжеzsh
все ведут себя так.
- В противном случае в оболочке, не выполняющей файл сценария,
$0
это значение первого аргумента, который передал родительский процесс оболочки - обычно это имя или путь оболочки (например,sh
, или же/bin/sh
); Это включает:- интерактивная оболочка
- Предостережение: некоторые платформы, особенно OSX, всегда создают оболочки входа при создании интерактивных оболочек и добавляют
-
на имя оболочки перед размещением в$0
, чтобы сообщить оболочке, что это оболочка _login; таким образом, по умолчанию,$0
отчеты-bash
неbash
в интерактивных оболочках на OSX.
- Предостережение: некоторые платформы, особенно OSX, всегда создают оболочки входа при создании интерактивных оболочек и добавляют
- оболочка, которая читает команды из стандартного ввода
- это также относится к передаче файла сценария в оболочку через стандартный ввод (например,
sh < myScript
)
- это также относится к передаче файла сценария в оболочку через стандартный ввод (например,
bash
,dash
,ksh
, а такжеzsh
все ведут себя так.
- интерактивная оболочка
@City ответил, что
DIR=$( cd -P -- "$(dirname -- "$(command -v -- "$0")")" && pwd -P )
работает. Я тоже это использовал.
Я нашел команду по адресу /questions/22333353/mogu-li-ya-poluchit-absolyutnyij-put-k-tekuschemu-skriptu-v-kornshell.
if OLDPWD=/dev/fd/0 \
cd - && ls -lLidFH ?
then cd . <8
fi </proc/self/fd 8<. 9<$0
там. это должно позволить вам изменить directpry через некоторые магические ссылки в качестве дескрипторов файлов.
установка $OLDPWD
пред- cd
экспортирует значение в течение одного каталога изменений (примечание: cd
может иметь остаточное влияние на hash
таблицы, но единственные sh
о котором я знаю, что на самом деле мужчины, любое хорошее использование их - это Кевин Алмквист - и, поскольку Герберт Сюй - dash
и, может быть, некоторые bsd
вещи, но что я знаю?) но не переносит cd
экспорт в результате изменений.
Таким образом, $OLDPWD
на самом деле не меняется, и если оно вообще имело какое-либо значение, то оно остается таким, как было. $PWD
изменен в результате первого cd
и значение становится /dev/fd/0
что указывает на /proc/self/fd
где список файловых дескрипторов для нашего процесса должен быть в .
, включить что угодно $0
находится на ./2
,
поэтому мы делаем ls ... ?
и посмотрим на всю замечательную информацию, которую мы можем получить, и мы идем, откуда мы пришли.
ура!