Вопрос или проблема
Некоторые мои коллеги предпочитают писать следующее в начале своих BASH-скриптов, чтобы определить каталог, содержащий скрипт:
SCRIPT_DIR="$( cd "$( dirname "${BASH_SOURCE[0]}" )" && pwd )"
В то время как я предпочитаю следующее:
SCRIPT_DIR="$(dirname "${BASH_SOURCE[0]}")"
(Обратите внимание, что в большинстве случаев нам не нужно разрешать символические ссылки, поэтому мы склонны использовать это, а не, например, readlink
)
Есть ли какие-либо преимущества у подхода cd ... && pwd
по сравнению с подходом, использующим только dirname
? Похоже, что это выполняет дополнительные шаги для достижения того же результата, но я хочу убедиться, что я не упускаю какое-то желание.
Суть:
SCRIPT_DIR="$( cd "$( dirname "${BASH_SOURCE[0]}" )" && pwd )"
На самом деле должно быть:
SCRIPT_DIR=$(
CDPATH= cd -P -- "$(dirname -- "$BASH_SOURCE")" && pwd
)
В том, что это дает вам абсолютный путь (и с -P
, канонический абсолютный путь, realpath()
), в то время как $BASH_SOURCE
($var
является сокращением для ${var[0]}
в bash, как в ksh) может быть относительным путем, таким как ../file.bash
.
Без -P
, в случае ../file.bash
, cd ..
может привести вас в другой каталог, отличный от того, который устанавливает скрипт по умолчанию (снова как в ksh, который ввел эту (неудачную) особенность) cd
выполняет логическое перемещение, где cd ..
не выполняет chdir("..")
, а скорее chdir(dirname($PWD))
(и $PWD
будет содержать компоненты символических ссылок, если вы ранее использовали cd
для путей с компонентами символических ссылок).
Обратите внимание, что это все равно не работает должным образом, если реальный путь этого каталога заканчивается символами новой строки (поскольку подстановка команд удаляет все завершающие символы новой строки, а не только тот, который добавляется dirname
/pwd
для вывода), или если $BASH_SOURCE
является, например, -/file
(так как cd -
является сокращением для cd -- "$OLDPWD"
).
В zsh вы можете сделать просто:
SCRIPT_DIR=$0:h:P
Или:
SCRIPT_DIR=$0:P:h
Где :P
дает вам realpath()
, а :h
возвращает голову (dirname, как в csh).
Оба не обязательно дадут вам один и тот же результат, если $0
является самой символической ссылкой.
У них нет никаких из тех проблем, которые есть у подхода bash выше.
Честно говоря, я бы сказал, что оба эти подхода ненужны и сложны (и я был бы неправ, см. ответ Стефана, который объясняет, как это сделать, если вам нужно разрешить абсолютные пути). Даже ваша более простая версия все еще вызывает внешнюю команду, где это не нужно. Почему бы просто не сделать:
script_dir="${BASH_SOURCE%/*}"
Что касается этих двух, я не вижу никаких преимуществ в еще более запутанном подходе с использованием двух внешних команд. Зачем запускать pwd
, если вы можете использовать $PWD
, но зачем запускать столько команд в первую очередь? Если вам нужно было бы разрешать символические ссылки, тогда я мог бы понять, почему нужны внешние команды, но для чего-то столь простого, как это?
Теперь есть крайние случаи, когда это не сработает. Как объяснил Стефан Шазелас в (теперь удаленном) комментарии:
Это дает вам
file.bash
, если$BASH_SOURCE
простоfile.bash
(в то время какdirname
даст вам.
), и пустую строку, если$BASH_SOURCE
равно/file.bash
(гдеdirname
даст вам/
).
Релевантно это в вашем случае или нет, можете знать только вы.
К слову, вам действительно не стоит использовать ЗАГЛАВНЫЕ БУКВЫ для имен переменных оболочки. Поскольку, по соглашению, глобальные переменные окружения пишутся с заглавной буквы, плохо использовать заглавные буквы для своих собственных локальных переменных, так как это может привести к конфликту имен и трудноуловимым ошибкам.
Ответ или решение
В вопросе о методах получения директории, содержащей исполняемый скрипт в Bash, стоит рассмотреть два популярных подхода: cd && pwd
и dirname
. Давайте проанализируем каждый из них, чтобы выделить их преимущества и недостатки.
Метод с использованием cd && pwd
Первый метод выглядит следующим образом:
SCRIPT_DIR="$( cd "$( dirname "${BASH_SOURCE[0]}" )" && pwd )"
Преимущества:
- Получение абсолютного пути: Данный подход возвращает абсолютный путь к директории, что может быть полезно в большинстве случаев. При использовании
pwd
послеcd
вы уверены, что всегда получите каноническое (абсолютное) представление пути даже в случае работы с относительными путями. - Разрешение символических ссылок: С флагом
-P
(cd -P
) метод позволяет избежать проблем с символическими ссылками, направляя вас в фактическую директорию, что может быть критично, если в вашем проекте используются ссылки.
Недостатки:
- Производительность: Этот метод требует выполнения дополнительных команд (движение по директориям и вызов
pwd
), что может быть избыточным для простого случая, когда разрешение символических ссылок не важно. - Сложность: Чтение и понимание кода может быть затруднительным из-за дополнительного уровня сложности.
Метод с использованием dirname
Второй метод выглядит следующим образом:
SCRIPT_DIR="$(dirname "${BASH_SOURCE[0]}")"
Преимущества:
- Простота: Этот код проще для понимания и требует меньше ресурсов. Он выполняется быстрее, так как не требуется вызов дополнительных команд.
- Использование встроенных конструкций: Использование
dirname
вместо вызоваcd && pwd
делает скрипт более читабельным и эффективным в контексте bash-скриптов.
Недостатки:
- Относительный путь: Данный метод может вернуть относительный путь, что может привести к проблемам в случае, если скрипт вызывается из другой директории.
- Проблемы при работе с символическими ссылками: Если
BASH_SOURCE
содержит символические ссылки, данный метод может вернуть не тот путь, который вы ожидаете.
Заключение
Выбор между этими методами зависит от требований вашего проекта:
- Если вам необходима уверенность в получении абсолютного пути, особенно в средах, где используются символические ссылки, то предпочтительный метод —
cd && pwd
. - Если сценарий достаточно прост и не требует разрешения ссылок, то лучше использовать
dirname
, что сэкономит время выполнения и упростит код.
В конечном счете, в разработке скриптов всегда важно находить баланс между производительностью и ясностью кода, что и следует учитывать при выборе метода.