Относительные пути, основанные на расположении файла вместо текущего рабочего каталога [дубликат]
этот вопрос уже есть ответ здесь:
- Получение исходного каталога скрипта Bash изнутри 51 ответы
дано:
some.txt
dir
|-cat.sh
С cat.sh имея содержание:
cat ../some.txt
под управлением ./cat.sh
внутри dir
работает во время работы ./dir/cat.sh
на том же уровне, что и dir
нет. Я ожидаю, что это будет связано с различными рабочими каталогами. Есть ли легко было сделать путь ../some.txt
относительно местоположения cat.sh
?3 ответа:
что вы хотите сделать, это получить абсолютный путь к скрипту (доступен через
${BASH_SOURCE[0]}
) и затем использовать это, чтобы получить родительский каталог иcd
в начале скрипта.#!/bin/bash parent_path=$( cd "$(dirname "${BASH_SOURCE[0]}")" ; pwd -P ) cd "$parent_path" cat ../some.text
это сделает ваш сценарий оболочки работать независимо от того, где вы его вызываете. Каждый раз, когда вы запускаете его, это будет, как если бы вы бежали
./cat.sh
внутриdir
.обратите внимание, что этот скрипт работает только если вы вызываете скрипт напрямую (т. е. не через симлинк), в противном случае поиск текущего местоположения скрипта становится немного сложнее)
ответ @ Martin Konecny дает правильный ответ, но - как он упоминает - он работает только в том случае, если фактический скрипт не вызывается через ссылка проживающего в разное каталог.
этот ответ охватывает этот случай: a решение, которое также работает, когда скрипт вызывается через ссылка или даже цепочка символических ссылок:
Linux / GNU
readlink
устранение:если ваш скрипт должен работать на Linux только или вы знаете, что GNU
readlink
находится в$PATH
используйтеreadlink -f
, что удобно разрешает символическую ссылку на его ultimate цель:scriptDir=$(dirname -- "$(readlink -f -- "$BASH_SOURCE")")
обратите внимание, что GNU
readlink
имеет 3 связанные опции для разрешения символической ссылки на полный путь конечной цели:-f
(--canonicalize
),-e
(--canonicalize-existing
), и-m
(--canonicalize-missing
) - см.man readlink
.
Поскольку цель по определению существует в этом сценарии, можно использовать любой из трех вариантов; я выбрал-f
здесь, потому что это самый известный один.
мульти-(Unix-like -) платформенное решение (включая платформы с только POSIX набор утилит):
если ваш скрипт должен работать на любой платформе, что:
есть
readlink
утилиты, но не хватает-f
вариант (в смысле GNU разрешения символической ссылки на ее конечную цель) - например, macOS.
- macOS использует старая версия реализации BSD
readlink
; обратите внимание, что последние версии FreeBSD / PC-BSD do поддержка-f
.даже не имеет
readlink
, но и POSIX-совместимое служебные программы - например, HP-UX (спасибо, @Charles Даффи.)следующее решение, вдохновленноеhttps://stackoverflow.com/a/1116890/45375, определяет вспомогательная функция оболочки,
rreadlink()
, который разрешает данную символическую ссылку на ее конечную цель в цикле-эта функция фактически POSIX-совместимая реализация GNUreadlink
' s-e
опции, которая составляет аналогично , за исключением того, что конечная цель должна .Примечание: функция
bash
функция, и является POSIX-совместимым только в том смысле, что используются только утилиты POSIX с POSIX-совместимыми опциями. Для версии этой функции написано в POSIX-совместимом коде оболочки (для/bin/sh
), см. здесь.
если
readlink
доступно, используется (без опций) - true на большинстве современная платформа.в противном случае, выход из
ls -l
анализируется, что является единственным POSIX-совместимым способом определения цели символьной ссылки.
будьте осторожны: это сломается, если имя файла или Путь содержит литеральную подстроку->
- что, однако, маловероятно.
(Обратите внимание, что платформы, которые не хватаетreadlink
может по-прежнему предоставлять другие, не POSIX методы для разрешения символьной ссылки; например, @Charles Duffy упоминает HP-UX'sfind
утилиты поддержка%l
char формат. с его-printf
первичный; в интересах краткости функция не пытается обнаружить такие случаи.)устанавливаемую утилиты (скрипт) форма функции ниже (с дополнительной функциональностью) можно найти как
rreadlink
в реестре НПМ; на Linux и macOS, установите его с[sudo] npm install -g rreadlink
; на других платформах (предполагая, что у них естьbash
), следуя руководство по установке инструкции.если аргумент является символьной ссылкой, возвращается канонический путь конечной цели; в противном случае возвращается собственный канонический путь аргумента.
#!/usr/bin/env bash # Helper function. rreadlink() ( # execute function in a *subshell* to localize the effect of `cd`, ... local target= fname targetDir readlinkexe=$(command -v readlink) CDPATH= # Since we'll be using `command` below for a predictable execution # environment, we make sure that it has its original meaning. { \unalias command; \unset -f command; } &>/dev/null while :; do # Resolve potential symlinks until the ultimate target is found. [[ -L $target || -e $target ]] || { command printf '%s\n' "$FUNCNAME: 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 is defined # relative to the symlink's own directory. if [[ -n $readlinkexe ]]; then # Use `readlink`. target=$("$readlinkexe" -- "$fname") else # `readlink` utility not available. # Parse `ls -l` output, which, unfortunately, is the only POSIX-compliant # way to determine a symlink's target. Hypothetically, this can break with # filenames containig literal ' -> ' and embedded newlines. target=$(command ls -l -- "$fname") target=${target#* -> } fi 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 ) # Determine ultimate script dir. using the helper function. # Note that the helper function returns a canonical path. scriptDir=$(dirname -- "$(rreadlink "$BASH_SOURCE")")