Как получить каталог script в POSIX sh?

У меня есть следующий код в моем bash script. Теперь я хочу использовать его в POSIX sh. Итак, как его преобразовать? Благодарю.

DIR="$( cd "$( dirname "${BASH_SOURCE[0]}" )" > /dev/null && pwd )"

Ответ 1

Оболочка POSIX-оболочки (sh) $BASH_SOURCE равна $0. см. снизу для справочной информации

Предостережение. Важным отличием является то, что , если ваш script находится в исходной оболочке с .), снизу ниже не будет работать должным образом. ниже ниже

Обратите внимание, что в приведенных ниже фрагментах я изменил DIR на DIR, потому что он лучше не использовать имена переменных с прописными буквами, чтобы избежать столкновений с переменными среды и специальными переменными оболочки.
Префикс CDPATH= заменяет > /dev/null в исходной команде: $CDPATH устанавливается в пустую строку, чтобы гарантировать, что cd никогда не будет эхом ничего.

В простейшем случае это будет (эквивалент команды OP):

dir=$(CDPATH= cd -- "$(dirname -- "$0")" && pwd)

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

dir=$(CDPATH= cd -- "$(dirname -- "$0")" && pwd -P)

Предостережение. Это НЕ так же, как поиск script собственного исходного каталога происхождения:
Скажем, ваш script foo символически связан с /usr/local/bin/foo в $PATH, но его истинный путь /foodir/bin/foo.
Вышеописанное будет сообщать /usr/local/bin, поскольку разрешение символьной ссылки (-P) применяется к каталогу /usr/local/bin, а не к самому script.

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

GNU readlink -f (лучше: readlink -e) может сделать это для вас, но readlink не является утилитой POSIX.
Хотя на платформах BSD, включая OSX, есть утилита readlink, а на OSX она не поддерживает функциональность -f. Тем не менее, чтобы показать, насколько проста задача, если readlink -f доступен: dir=$(dirname "$(readlink -f -- "$0")").

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

следующая, совместимая с POSIX функция оболочки реализует то, что GNU readlink -e делает, и является <сильным > достаточно надежным решением, которое работает только в двух случаях с редким краем

  • пути со встроенными символами новой строки (очень редко)
  • имена файлов, содержащие литеральную строку -> (также редко)

С помощью этой функции, названной rreadlink, определенной, , определяется путь script истинного каталога происхождения:

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 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 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 /[email protected] -> /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.


Как обрабатывать вызовы с исходными кодами:

tl; dr:

Использование только функций POSIX:

  • Вы не можете определить путь script в исходном вызове (кроме zsh, который, однако, обычно не действует как sh).
  • может определить, будет ли ваш script использоваться только в том случае, если ваш script будет получен непосредственно с помощью оболочки (например, в файле оболочки/файле инициализации, возможно, через цепочка источников), сравнивая $0 с исполняемым именем/контуром оболочки (кроме zsh, где, как отмечено $0, действительно является текущим script). Напротив (кроме zsh), script был получен из другого script, который сам был вызван непосредственно, содержит этот путь script в $0.
  • Чтобы решить эти проблемы, bash, ksh и zsh имеют нестандартные функции, которые позволяют определять фактический путь script даже в сценариях с исходными кодами, а также определять, w370 > является источником или нет; например, в bash, $BASH_SOURCE всегда содержит пробег script, независимо от того, является ли он источником или нет, а [[ $0 != "$BASH_SOURCE" ]] может использоваться для проверки того, будет ли источник script отправлен.

Чтобы показать, почему это невозможно сделать, проанализируйте команду ответ Уолтера:

    # 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" предназначен для покрытия одного дополнительного сценария: если script получается из интерактивной оболочки, $0 обычно содержит простое имя исполняемого файла оболочки (sh), и в этом случае dirname просто вернет . (потому что то, что dirname неизменно делает, когда задан аргумент без компонента пути). command -v -- "$0" затем возвращает этот абсолютный путь оболочки через поиск $PATH (/bin/sh). Обратите внимание, однако, что в командах для входа на некоторых платформах (например, OSX) есть имя файла с префиксом - в $0 (-sh), и в этом случае command -v -- "$0" не работает должным образом (возвращает пустую строку).
    • И наоборот, command -v -- "$0" может ошибочно работать в двух сценариях, не относящихся к источникам, в которые непосредственно запускается исполняемый файл оболочки sh, с аргументом script в качестве аргумента:
      • если сам script не является исполняемым: command -v -- "$0" может возвращать пустую строку, в зависимости от того, какая конкретная оболочка действует как sh для данной системы: bash, ksh и zsh return пустая строка; только dash эхо-сигналы $0
        спецификация POSIX. для command явно не указано, должен ли command -v при применении к пути файловой системы возвращать только исполняемые файлы - это то, что bash, ksh и zsh do - но вы может утверждать, что это подразумевается самой целью command; Любопытно, что dash, который обычно является наиболее совместимым гражданином POSIX, отклоняется от стандарта здесь. Напротив, ksh является единственным гражданином модели здесь, поскольку он является единственным, который сообщает только исполняемые файлы и сообщает им абсолютный (хотя и не нормированный) путь, как того требует спецификация.
      • если script является исполняемым, но не в $PATH, а вызов использует его простое имя файла (например, sh myScript), command -v -- "$0" также возвращает пустую строку, за исключением dash.
    • Учитывая, что каталог script не может быть определен при получении script - поскольку $0 затем не содержит эту информацию (кроме zsh, которая обычно не действует как sh) - нет хорошего решения этой проблемы.
      • Возврат пути к каталогу исполняемого файла оболочки в этой ситуации ограничен ограниченным использованием - это, в конце концов, не каталог script - за исключением, возможно, более позднего использования этого пути в тесте, чтобы определить, есть ли script.
        • Более надежным подходом было бы просто непосредственно протестировать $0: [ "$0" = "sh" ] || [ "$0" = "-sh" ] || [ "$0" = "/bin/sh" ]
      • Однако даже это не работает, если script получен из другого script (который сам был вызван непосредственно), потому что $0 затем просто содержит путь sourcing script.
    • Учитывая ограниченную полезность command -v -- "$0" в сценариях источников и тот факт, что он разбивает два сценария, не относящихся к источникам, мой голос за НЕиспользует его, что оставляет нам:
      • Все сценарии, не связанные с источником.
      • В запросах с источниками вы не можете определить путь script, и в лучшем случае в ограниченных обстоятельствах вы можете определить, происходит ли поиск источника:
        • При непосредственном использовании оболочки (например, из профиля оболочки/файла инициализации) $dir заканчивается либо содержащим ., если исполняемый файл оболочки был вызван как простое имя файла (применение dirname к простому filename всегда возвращает .) или путь к исполняемому каталогу оболочки в противном случае. . не может быть надежно отличен от невостребованного вызова из текущего каталога.
        • Когда источник из другого script (который сам по себе также не был найден), $0 содержит этот путь script, а источник script не имеет возможности сообщить, является ли этот случай.

Фоновая информация:

POSIX определяет поведение $0 по сценариям оболочки здесь.

По существу, $0 должен отражать путь к файлу script, как указано, что подразумевает:

  • НЕ полагайтесь на $0, содержащий абсолютный путь.
  • $0 содержит абсолютный путь, только если:

    • вы явно указываете абсолютный путь; например.:
      • ~/bin/myScript (при условии, что сам script является исполняемым)
      • sh ~/bin/myScript
    • вы вызываете исполняемый файл script по простому имени файла, который требует, чтобы он исполнялся и в $PATH; за кулисами система преобразует myScript в абсолютный путь и затем выполняет ее; например.:
      • myScript # executes /home/jdoe/bin/myScript, for instance
  • Во всех остальных случаях $0 будет отображать путь script , как указано:

    • При явном вызове sh с script это может быть просто имя файла (например, sh myScript) или относительный путь (например, sh ./myScript)
    • При непосредственном вызове исполняемого файла script это может быть относительный путь (например, ./myScript) - обратите внимание, что простое имя файла найдет только скрипты в $PATH).

На практике bash, dash, ksh и zsh проявляют это поведение.

В отличие от POSIX НЕ задает значение $0 при поиске script (используя специальную встроенную утилиту . ( "точка" )), поэтому вы не можете полагаться на него, и на практике поведение отличается от оболочки.

  • Таким образом, вы не можете вслепую использовать $0, когда ваш script является источником и ожидает стандартизованного поведения.
    • На практике bash, dash и ksh оставляют $0 нетронутыми при поиске сценариев, что означает, что $0 содержит значение $0 вызывающего абонента или, более точно, значение $0 самого последнего вызывающего абонента в цепочке вызовов, которая не была получена сама по себе; таким образом, $0 может указывать либо на исполняемый файл оболочки, либо на путь другого (непосредственно вызываемого) script, который был источником текущего.
    • В отличие от этого, zsh, как одиночный диссидент, действительно сообщает текущий путь script в $0. И наоборот, $0 не будет указывать, является ли источник script или нет.
    • Вкратце: используя только функции POSIX, вы не можете точно сказать, находится ли script под рукой, а не то, что находится в пути script, а также то, что отношение $0 к текущий script путь.
  • Если вам необходимо обработать эту ситуацию, , вы должны определить конкретную оболочку под рукой и получить доступ к ее нестандартным функциям:
    • bash, ksh и zsh все предлагают свои собственные способы получения запущенного пути script, даже если он был создан.

Для полноты: значение $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 все ведут себя таким образом.
  • В противном случае, в оболочке, не выполняющей файл script, $0 - это значение первого аргумента, переданного родительским процессом оболочки, как правило, имя или путь оболочки (например, sh или /bin/sh); Это включает:
    • интерактивная оболочка
      • Caveat: некоторые платформы, в частности OSX, всегда создают оболочки входа при создании интерактивных оболочек и добавляют - к имени оболочки перед тем, как поместить его в $0, чтобы сигнализировать shell, что это оболочка _login; таким образом, по умолчанию $0 сообщает -bash, а не bash, в интерактивных оболочках OSX.
    • оболочка, которая читает команды из stdin
      • это также относится к подключению файла script к оболочке через stdin (например, sh < myScript)
    • bash, dash, ksh и zsh все ведут себя таким образом.

Ответ 2

@City ответил, что

DIR=$( cd -P -- "$(dirname -- "$(command -v -- "$0")")" && pwd -P )

работает. Я тоже это использовал. Я нашел команду fooobar.com/questions/37598/....

Ответ 3

if      OLDPWD=/dev/fd/0 \
        cd - && ls -lLidFH ?
then    cd . <8
fi      </proc/self/fd 8<. 9<$0

там. это должно позволить вам изменить directpry через некоторые магические ссылки как дескрипторы файлов.

настройка $OLDPWD pre- cd экспортирует значение для продолжительности один смена каталога (Примечание: cd может иметь остаточные эффекты на hash - таблицах, но только sh, о которых я знаю, что на самом деле мужчин любое хорошее использование из них является Кевин ahlmquists - и так как Герберт Сюй - dash, и, возможно, некоторые вещи bsd, но что я знаю?), но не переносит экспорт cd в результате изменения.

Таким образом, $OLDPWD, на самом деле, не изменяется, и если оно вообще имело какое-либо значение, оно остается таким же, как было. $PWD изменяется в результате первого cd, и значение становится /dev/fd/0 которое указывает на /proc/self/fd, где должен быть список файловых дескрипторов для нашего процесса . , чтобы включить все, что $0 на ./2.

так мы делаем ls...? и посмотрим на всю замечательную информацию, которую мы можем получить, и мы идем, откуда мы пришли.

ура!