Как задать текущую рабочую директорию как директорию скрипта в Bash?
Я пишу Bash-скрипт. Мне нужно, чтобы текущей рабочей директорией всегда была директорий, в которой расположен сам скрипт.
По умолчанию текущая рабочая директория в скрипте соответствует той оболочке, из которой я его запускаю, но я не хочу такого поведения. Как я могу сделать так, чтобы скрипт всегда работал в своей собственной директории?
5 ответ(ов)
Краткий вывод
#!/usr/bin/env bash
cd "$(dirname "$0")"
Объяснение
Как это работает и как это справляется с крайними случаями?
- Вы вводите команду вызова скрипта в своем интерактивном терминале, который может не быть bash.
- Этот интерактивный терминал сообщает ядру выполнить скрипт по указанной команде, и ядро затем вызывает
bash
с некоторым именем, которое указываетbash
, какой скрипт запустить. - Этот
bash
вызываетdirname
с аргументомbash
, который указывает на скрипт. - Наконец, этот
bash
вызываетcd
с выводомdirname
как своим аргументом.
команда вызова скрипта | аргумент bash |
аргумент dirname |
аргумент cd |
---|---|---|---|
foo (найден в $PATH по пути /path/to/foo ) |
/path/to/foo |
/path/to/foo |
/path/to |
bash foo |
foo |
foo |
. |
/foo |
/foo |
/foo |
/ |
./foo |
./foo |
./foo |
. |
"./pa th/to/foo" |
./pa th/to/foo |
./pa th/to/foo |
./pa th/to |
"../pa th/to/foo" |
../pa th/to/foo |
../pa th/to/foo |
../pa th/to |
"../../pa th/to/foo" |
../../pa th/to/foo |
../../pa th/to/foo |
../../pa th/to |
"pa th/to/foo" |
pa th/to/foo |
pa th/to/foo |
pa th/to |
--help/foo |
--help/foo * |
N/A | N/A |
--help |
N/A ** | N/A | N/A |
О символических ссылках
Команда cd
будет следовать за символическими ссылками, если они задействованы. Обычно символическая ссылка существует, чтобы ее могли использовать, поэтому в большинстве ситуаций следование за символической ссылкой — это правильный подход. Почему возникла бы проблема, если код в скрипте последует за символической ссылкой, если она была корректной несколько миллисекунд назад при загрузке скрипта?
Об аргументах команд, начинающихся с дефиса
Развивая две ситуации с аргументами, начинающимися с дефиса в приведенной выше таблице (отмечены * и **, соответственно):
* Существует только один случай, когда аргумент для dirname
может начинаться с -
, и это случай относительного пути --help/foo
. Если скрипт находится в подкаталоге с именем --help
, выполнение скрипта вызовет bash --help/foo
, и bash не знает никакой опции --help/foo
, поэтому завершится с сообщением об ошибке. Скрипт никогда не будет выполнен, поэтому не имеет значения, что бы выполнял cd "$(dirname "$0")"
.
** Обратите внимание, что если название скрипта --help
, оболочка не найдет команду, когда вы вводите --help
в той же директории. В противном случае, если в $PATH
содержится текущая директория, скрипт будет выполнен как /path/to/--help
или ./--help
, всегда с чем-то перед символом -
.
Если bash не введет аргументы командной строки с параметрами, разделенными =
, то вряд ли или невозможно передать аргумент, начинающийся с -
, который содержал бы /
позже, и который будет принят bash.
Если вы можете полагаться на то, что dirname
принимает аргумент --
(встроенная команда cd
в bash точно примет --
), вы можете изменить фрагмент скрипта на
cd -- "$(dirname -- "$0")"
Пожалуйста, напишите комментарий, если сможете придумать способ построить аргумент, начинающийся с -
, который можно было бы незаметно пропустить в bash.
Замечание
Снippet TL;DR также работает с не-bash /bin/sh
.
Команда cd "$(dirname "${BASH_SOURCE[0]}")"
используется в Bash для изменения текущего рабочего каталога на тот, в котором находится выполняемый скрипт.
Вот что она делает:
${BASH_SOURCE[0]}
— это специальная переменная, которая содержит путь к текущему исполняемому скрипту. Если скрипт вызывается из другого скрипта или из командной строки, она вернет путь к текущему файлу.dirname
— это команда, которая получает путь к директории из полного пути файла.$(...)
— это конструкция, позволяющая выполнить команду (в данном случаеdirname
) и вернуть ее результат.В итоге, команда
cd
переходит в директорию, в которой находится текущий скрипт.
Таким образом, эта команда удобна для обеспечения того, чтобы любой последующий код в вашем скрипте выполнялся из его директории, независимо от того, откуда был вызван скрипт. Это особенно полезно для работы с относительными путями к файлам и ресурсам.
Принятый ответ хорошо работает для скриптов, которые не были созданы с использованием символических ссылок, например, в $PATH
.
#!/bin/bash
cd "$(dirname "$0")"
Однако, если скрипт запускается через символическую ссылку, например:
ln -sv ~/project/script.sh ~/bin/;
~/bin/script.sh
то команда cd
выполнится в директории ~/bin/
, а не в директории ~/project/
. Это, вероятно, приведет к сбою вашего скрипта, если цель использования cd
— это подключение зависимостей, относительных к ~/project/
.
Безопасный вариант для работы с символическими ссылками приведен ниже:
#!/bin/bash
cd "$(dirname "$(readlink -f "${BASH_SOURCE[0]}")")" # переход в текущую директорию
Использование readlink -f
необходимо для определения абсолютного пути к потенциально символической ссылке.
Кавычки обязательны для поддержки путей к файлам, которые могут содержать пробелы (хорошей практикой это не является, но нельзя предполагать, что такой вариант исключен).
В данном обсуждении присутствует множество правильных ответов, однако один из подходов, который чаще всего оказывается для меня полезным (так как помогает поддерживать предсказуемость относительных путей в скрипте), — это использование команд pushd
и popd
:
pushd "$(dirname ${BASH_SOURCE[0]})"
trap popd EXIT
# ./xyz и т.д.
Команда pushd
добавляет директорию, в которой находится исходный файл скрипта, в стек навигации, тем самым изменяя текущую рабочую директорию. А когда скрипт завершается (по любой причине, включая ошибку), сработает trap
, который выполнит команду popd
, восстанавливая рабочую директорию до того, как был выполнен скрипт. Если бы скрипт использовал cd
и затем завершился с ошибкой, ваша терминальная сессия могла бы оказаться в непредсказуемом состоянии после выполнения. Использование trap
предотвращает это.
Этот скрипт, похоже, работает для меня:
#!/bin/bash
mypath=`realpath $0`
cd `dirname $mypath`
pwd
Команда pwd
выводит местоположение скрипта как текущий рабочий каталог, независимо от того, откуда я его запускаю.
Как запросить ввод Yes/No/Cancel в скрипте оболочки Linux?
Как объявить и использовать логические переменные в shell-скрипте?
Как работает "cat << EOF" в bash?
Как в Bash-скрипте выйти из всего скрипта при выполнении определенного условия?
sudo echo "что-то" >> /etc/привилегированныйФайл не работает