Относительные пути в зависимости от местоположения файла вместо текущего рабочего каталога
Дано:
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 дает правильный ответ, но, как он упоминает, он работает только в том случае, если реальный сценарий не вызывается по символической ссылке, находящейся в другом каталоге.
Этот ответ покрывает этот случай: решение, которое также работает, когда скрипт вызывается через символическую ссылку или даже цепочку символических ссылок:
Linux / GNU readlink
решение:
Если ваш скрипт должен работать только в Linux или вы знаете, что GNU readlink
находится в $PATH
использовать readlink -f
, который удобно разрешает символическую ссылку на конечную цель:
scriptDir=$(dirname -- "$(readlink -f -- "$BASH_SOURCE")")
Обратите внимание, что GNU readlink
имеет 3 связанных варианта разрешения символической ссылки на полный путь конечной цели: -f
( --canonicalize
), -e
( --canonicalize-existing
), а также -m
( --canonicalize-missing
) - увидеть man readlink
,
Поскольку цель по определению существует в этом сценарии, можно использовать любой из 3 вариантов; Я выбрал -f
здесь, потому что это самый известный.
Решение для нескольких (Unix-like-) платформ (включая платформы с набором утилит только для POSIX):
Если ваш скрипт должен работать на любой платформе, которая:
имеет
readlink
утилита, но не хватает-f
опция (в смысле GNU разрешения символической ссылки на конечную цель) - например, macOS.- macOS использует более старую версию реализации BSD
readlink
; обратите внимание, что последние версии FreeBSD/PC-BSD поддерживают-f
,
- macOS использует более старую версию реализации BSD
даже не имеет
readlink
, но имеет POSIX-совместимые утилиты - например, HP-UX (спасибо, @Charles Duffy).
Следующее решение, основанное на /questions/38587695/kak-ya-mogu-poluchit-povedenie-readlink-f-gnu-na-mac/38587720#38587720, определяет функцию вспомогательной оболочки: rreadlink()
, который разрешает данную символьную ссылку в своей конечной цели в цикле - эта функция в действительности является POSIX-совместимой реализацией GNU readlink
"s -e
вариант, который похож на -f
вариант, за исключением того, что конечная цель должна существовать.
Примечание: функция bash
и является POSIX-совместимым только в том смысле, что используются только утилиты POSIX с POSIX-совместимыми параметрами. Для версии этой функции, которая сама написана в POSIX-совместимом коде оболочки (для /bin/sh
), смотрите здесь.
Если
readlink
доступно, используется (без опций) - правда на большинстве современных платформ.В противном случае вывод
ls -l
анализируется, что является единственным POSIX-совместимым способом определения цели символической ссылки.
Предупреждение: это сломается, если имя файла или путь содержит буквенную подстроку->
- что маловероятно, однако.
(Обратите внимание, что на платформах, где нетreadlink
может по-прежнему предоставлять другие, не POSIX методы для разрешения символической ссылки; например, @Charles Duffy упоминает HP-UX'sfind
утилита, поддерживающая%l
формат чар. с этими-printf
первичный; в целях краткости функция НЕ пытается обнаружить такие случаи.)Установленная утилита (сценарий) в форме функции ниже (с дополнительными функциями) может быть найдена как
rreadlink
в реестре npm; на 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=$1 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")")
Всего одна строка будет в порядке.
cat "`dirname $0`"/../some.txt