Получить исходный каталог скрипта Bash из самого скрипта - PullRequest
4414 голосов
/ 13 сентября 2008

Как получить путь к каталогу, в котором находится сценарий Bash , внутри этого сценария?

Например, допустим, я хочу использовать скрипт Bash в качестве средства запуска для другого приложения. Я хочу изменить рабочий каталог на тот, где находится скрипт Bash, чтобы я мог работать с файлами в этом каталоге, например так:

$ ./application

Ответы [ 58 ]

0 голосов
/ 09 октября 2015

(Примечание: этот ответ прошел через множество ревизий, так как я улучшил оригинал. На момент последней ревизии еще никто не комментировал и не голосовал.)

Я добавляю этот ответ столько же, сколько для себя - чтобы запомнить его и собирать комментарии - как и для кого-либо еще. Ключевая часть ответа заключается в том, что я уменьшаю масштаб проблемы: я запрещаю косвенное выполнение скрипта через путь (как в / bin / sh [путь скрипта относительно компонента пути]). Это может быть обнаружено, потому что $ 0 будет относительным путем, который не разрешается ни в один файл относительно текущей папки. Я считаю, что прямое исполнение с использованием "#!" Механизм всегда приводит к абсолютному $ 0, в том числе, когда сценарий находится на пути. Я также требую, чтобы имя пути и любые имена путей в цепочке символических ссылок содержали только разумное подмножество символов, в частности, не '\ n', '>', '*' или '?'. Это требуется для логики разбора. Есть еще несколько неявных ожиданий, в которые я не буду вдаваться (см. Предыдущий ответ <1>), и я не пытаюсь справиться с преднамеренным саботажем в размере 0 долларов США (поэтому рассмотрим любые последствия для безопасности). Я ожидаю, что это будет работать практически на любой Unix-подобной системе с Bourne-подобным /bin/sh.

Комментарии и предложения приветствуются!

<1> https://stackoverflow.com/a/4794711/213180

#!/bin/sh
(
    path="${0}"
    while test -n "${path}"; do
        # Make sure we have at least one slash and no leading dash.
        expr "${path}" : / > /dev/null || path="./${path}"
        # Filter out bad characters in the path name.
        expr "${path}" : ".*[*?<>\\]" > /dev/null && exit 1
        # Catch embedded new-lines and non-existing (or path-relative) files.
        # $0 should always be absolute when scripts are invoked through "#!".
        test "`ls -l -d "${path}" 2> /dev/null | wc -l`" -eq 1 || exit 1
        # Change to the folder containing the file to resolve relative links.
        folder=`expr "${path}" : "\(.*/\)[^/][^/]*/*$"` || exit 1
        path=`expr "x\`ls -l -d "${path}"\`" : "[^>]* -> \(.*\)"`
        cd "${folder}"
        # If the last path was not a link then we are in the target folder.
        test -n "${path}" || pwd
    done
)
0 голосов
/ 12 февраля 2009

Я хочу убедиться, что скрипт работает в своем каталоге. Так

cd $(dirname $(which $0) )

После этого, если вы действительно хотите знать, где вы работаете, выполните команду ниже.

DIR=$(/usr/bin/pwd)
0 голосов
/ 06 февраля 2019

Еще одна возможность дополнить все остальные отличные ответы

$ (cd "$ (dirname" $ ​​{BASH_SOURCE [0]} ")"; pwd) "

0 голосов
/ 03 июля 2014

Никаких разветвлений (кроме subshell) и может обрабатывать «чужие» формы путей, такие как с символами новой строки, как утверждают некоторые:

IFS= read -rd '' DIR < <([[ $BASH_SOURCE != */* ]] || cd "${BASH_SOURCE%/*}/" >&- && echo -n "$PWD")
0 голосов
/ 08 мая 2019

Нет 100% портативного и надежного способа запроса пути к текущей директории скрипта. Особенно между различными бэкэндами, такими как cygwin / mingw / msys / Linux и т. Д. Эта проблема не была должным образом и полностью решена в bash целую вечность.

Например, это не может быть решено, если вы хотите запросить путь после команды source, чтобы сделать вложенное включение другого скрипта bash, который, в свою очередь, использует ту же команду source, чтобы включить другой скрипт bash на.

В случае команды source я предлагаю заменить команду source на что-то вроде этого:

function include()
{
  if [[ -n "$CURRENT_SCRIPT_DIR" ]]; then
    local dir_path=... get directory from `CURRENT_SCRIPT_DIR/$1`, depends if $1 is absolute path or relative ...
    local include_file_path=...
  else
    local dir_path=... request the directory from the "$1" argument using one of answered here methods...
    local include_file_path=...
  fi
  ... push $CURRENT_SCRIPT_DIR in to stack ... 
  export CURRENT_SCRIPT_DIR=... export current script directory using $dir_path ...
  source "$include_file_path"
  ... pop $CURRENT_SCRIPT_DIR from stack ...
}

Отныне и после использования include(...) основан на предыдущем CURRENT_SCRIPT_DIR в вашем скрипте.

Это работает только тогда, когда вы можете заменить все команды source на команду include. Если вы не можете, то у вас нет выбора. По крайней мере, до тех пор, пока разработчики интерпретатора bash не сделают явную команду для запроса текущего пути к каталогу запущенного скрипта.

0 голосов
/ 01 сентября 2014

Это решение относится только к bash. Обратите внимание, что обычно предоставляемый ответ ${BASH_SOURCE[0]} не будет работать, если вы попытаетесь найти путь внутри функции.

Я обнаружил, что эта строка всегда работает, независимо от того, был ли файл получен или запущен как скрипт.

dirname ${BASH_SOURCE[${#BASH_SOURCE[@]} - 1]}

Если вы хотите следовать по символическим ссылкам, используйте readlink на пути, который вы указали выше, рекурсивно или не рекурсивно.

Вот скрипт, чтобы попробовать его и сравнить с другими предлагаемыми решениями. Вызовите его как source test1/test2/test_script.sh или bash test1/test2/test_script.sh.

#
# Location: test1/test2/test_script.sh
#
echo $0
echo $_
echo ${BASH_SOURCE}
echo ${BASH_SOURCE[${#BASH_SOURCE[@]} - 1]}

cur_file="${BASH_SOURCE[${#BASH_SOURCE[@]} - 1]}"
cur_dir="$(dirname "${cur_file}")"
source "${cur_dir}/func_def.sh"

function test_within_func_inside {
    echo ${BASH_SOURCE}
    echo ${BASH_SOURCE[${#BASH_SOURCE[@]} - 1]}
}

echo "Testing within function inside"
test_within_func_inside

echo "Testing within function outside"
test_within_func_outside

#
# Location: test1/test2/func_def.sh
#
function test_within_func_outside {
    echo ${BASH_SOURCE}
    echo ${BASH_SOURCE[${#BASH_SOURCE[@]} - 1]}
}

Причина, по которой работает однострочник, объясняется использованием переменной окружения BASH_SOURCE и ее ассоциированного FUNCNAME.

BASH_SOURCE

Переменная массива, членами которой являются имена файлов источника, где определены соответствующие имена функций оболочки в переменной массива FUNCNAME. Функция оболочки $ {FUNCNAME [$ i]} определена в файле $ {BASH_SOURCE [$ i]} и вызвана из $ {BASH_SOURCE [$ i + 1]}.

имя_функция

Переменная массива, содержащая имена всех функций оболочки, которые в данный момент находятся в стеке вызовов выполнения. Элемент с индексом 0 является именем любой выполняемой в данный момент функции оболочки. Самый нижний элемент (с наивысшим индексом) является «основным». Эта переменная существует только при выполнении функции оболочки. Назначения FUNCNAME не имеют никакого эффекта и возвращают статус ошибки. Если FUNCNAME не установлено, оно теряет свои специальные свойства, даже если оно впоследствии сбрасывается.

Эта переменная может использоваться с BASH_LINENO и BASH_SOURCE. Каждый элемент FUNCNAME имеет соответствующие элементы в BASH_LINENO и BASH_SOURCE для описания стека вызовов. Например, $ {FUNCNAME [$ i]} был вызван из файла $ {BASH_SOURCE [$ i + 1]} по номеру строки $ {BASH_LINENO [$ i]}. Встроенная функция вызывающего абонента отображает текущий стек вызовов, используя эту информацию.

[Источник: руководство по Bash]

0 голосов
/ 22 мая 2019

Вот команда, которая работает под bash или zsh и выполняется ли она автономно или из источника:

[ -n "$ZSH_VERSION" ] && this_dir=$(dirname "${(%):-%x}") \
    || this_dir=$(dirname "${BASH_SOURCE[0]:-$0}")

Как это работает

Текущее расширение файла zsh: ${(%):-%x}

${(%):-%x} в zsh раскрывает путь к текущему исполняемому файлу.

Резервный оператор подстановки :-

Вы уже знаете, что ${...} подставляет переменные внутри строк. Вы можете не знать, что некоторые операции возможны (как в bash , так и zsh ) с переменными во время подстановки, например, в качестве оператора аварийного расширения :-:

% x=ok
% echo "${x}"
ok

% echo "${x:-fallback}"
ok

% x=
% echo "${x:-fallback}"
fallback

% y=yvalue
% echo "${x:-$y}"
yvalue

Код подсказки %x

Далее мы введем быстрые коды перехода, функция только для zsh. В zsh %x будет расширяться до пути к файлу, но обычно это происходит только при расширении для строк приглашения . Чтобы включить эти коды в нашу замену, мы можем добавить флаг (%) перед именем переменной:

% cat apath/test.sh
fpath=%x
echo "${(%)fpath}"

% source apath/test.sh
apath/test.sh

% cd apath
% source test.sh
test.sh

маловероятное совпадение: процент побега и запасной вариант

То, что у нас есть, работает, но было бы лучше избегать создания дополнительной переменной fpath. Вместо ввода %x в fpath мы можем использовать :- и поставить %x в резервной строке:

% cat test.sh
echo "${(%):-%x}"

% source test.sh
test.sh

Обратите внимание, что обычно мы помещаем имя переменной между (%) и :-, но мы оставляем это поле пустым. Переменная с пустым именем не может быть объявлена ​​или установлена, поэтому запасной вариант всегда срабатывает.

Завершение: как насчет print -P %x?

Теперь у нас почти есть каталог нашего скрипта. Мы могли бы использовать print -P %x, чтобы получить тот же путь к файлу с меньшим количеством хаков, но в нашем случае, когда нам нужно передать его в качестве аргумента dirname, для этого потребовались бы дополнительные затраты на запуск новой подоболочки:

% cat apath/test.sh
dirname "$(print -P %x)"  # $(...) runs a command in a new process
dirname "${(%):-%x}"

% source apath/test.sh
apath
apath

Оказывается, что хакерский способ более эффективен и лаконичен.

0 голосов
/ 09 июня 2013

Я обычно добавляю в начало своих скриптов следующее, которое работает в большинстве случаев:

[ "$(dirname $0)" = '.' ] && SOURCE_DIR=$(pwd) || SOURCE_DIR=$(dirname $0);
ls -l $0 | grep -q ^l && SOURCE_DIR=$(ls -l $0 | awk '{print $NF}');

Первая строка назначает источник на основе значения pwd, если он запускается из текущего пути или dirname, если вызывается из другого места.

Во второй строке проверяется путь, чтобы определить, является ли он символической ссылкой, и если да, обновляет SOURCE_DIR до местоположения самой ссылки.

Возможно, есть лучшие решения, но это самое чистое, что мне удалось придумать.

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...