Как получить каталог скриптов в 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.
    • оболочка, которая читает команды из стандартного ввода
      • это также относится к передаче файла сценария в оболочку через стандартный ввод (например, 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 ... ? и посмотрим на всю замечательную информацию, которую мы можем получить, и мы идем, откуда мы пришли.

ура!

Другие вопросы по тегам