8

Как задать текущую рабочую директорию как директорию скрипта в Bash?

2

Я пишу Bash-скрипт. Мне нужно, чтобы текущей рабочей директорией всегда была директорий, в которой расположен сам скрипт.

По умолчанию текущая рабочая директория в скрипте соответствует той оболочке, из которой я его запускаю, но я не хочу такого поведения. Как я могу сделать так, чтобы скрипт всегда работал в своей собственной директории?

5 ответ(ов)

10

Краткий вывод

#!/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.

1

Команда cd "$(dirname "${BASH_SOURCE[0]}")" используется в Bash для изменения текущего рабочего каталога на тот, в котором находится выполняемый скрипт.

Вот что она делает:

  1. ${BASH_SOURCE[0]} — это специальная переменная, которая содержит путь к текущему исполняемому скрипту. Если скрипт вызывается из другого скрипта или из командной строки, она вернет путь к текущему файлу.

  2. dirname — это команда, которая получает путь к директории из полного пути файла.

  3. $(...) — это конструкция, позволяющая выполнить команду (в данном случае dirname) и вернуть ее результат.

  4. В итоге, команда cd переходит в директорию, в которой находится текущий скрипт.

Таким образом, эта команда удобна для обеспечения того, чтобы любой последующий код в вашем скрипте выполнялся из его директории, независимо от того, откуда был вызван скрипт. Это особенно полезно для работы с относительными путями к файлам и ресурсам.

0

Принятый ответ хорошо работает для скриптов, которые не были созданы с использованием символических ссылок, например, в $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 необходимо для определения абсолютного пути к потенциально символической ссылке.

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

0

В данном обсуждении присутствует множество правильных ответов, однако один из подходов, который чаще всего оказывается для меня полезным (так как помогает поддерживать предсказуемость относительных путей в скрипте), — это использование команд pushd и popd:

pushd "$(dirname ${BASH_SOURCE[0]})"
trap popd EXIT

# ./xyz и т.д.

Команда pushd добавляет директорию, в которой находится исходный файл скрипта, в стек навигации, тем самым изменяя текущую рабочую директорию. А когда скрипт завершается (по любой причине, включая ошибку), сработает trap, который выполнит команду popd, восстанавливая рабочую директорию до того, как был выполнен скрипт. Если бы скрипт использовал cd и затем завершился с ошибкой, ваша терминальная сессия могла бы оказаться в непредсказуемом состоянии после выполнения. Использование trap предотвращает это.

0

Этот скрипт, похоже, работает для меня:

#!/bin/bash
mypath=`realpath $0`
cd `dirname $mypath`
pwd

Команда pwd выводит местоположение скрипта как текущий рабочий каталог, независимо от того, откуда я его запускаю.

Чтобы ответить на вопрос, пожалуйста, войдите или зарегистрируйтесь