Get the source directory of a Bash script from within the script itself
Как мне получить путь к каталогу, в котором находится скрипт Bash, внутри этого скрипта?
Например, допустим, я хочу использовать скрипт Bash в качестве средства запуска для другого приложения. Я хочу изменить рабочий каталог на тот, где находится скрипт Bash, чтобы я мог работать с файлами в этом каталоге, например так:
$./application
78 ответов
#!/bin/bash
DIR="$( cd "$( dirname "${BASH_SOURCE[0]}" )" >/dev/null 2>&1 && pwd )"
полезный однострочный, который даст вам полное имя каталога скрипта независимо от того, откуда он вызывается.
Он будет работать до тех пор, пока последний компонент пути, используемый для поиска сценария, не является символической ссылкой (ссылки на каталоги в порядке). Если вы также хотите разрешить любые ссылки на сам скрипт, вам нужно многострочное решение:
#!/bin/bash
SOURCE="${BASH_SOURCE[0]}"
while [ -h "$SOURCE" ]; do # resolve $SOURCE until the file is no longer a symlink
DIR="$( cd -P "$( dirname "$SOURCE" )" >/dev/null 2>&1 && pwd )"
SOURCE="$(readlink "$SOURCE")"
[[ $SOURCE != /* ]] && SOURCE="$DIR/$SOURCE" # if $SOURCE was a relative symlink, we need to resolve it relative to the path where the symlink file was located
done
DIR="$( cd -P "$( dirname "$SOURCE" )" >/dev/null 2>&1 && pwd )"
Этот последний будет работать с любой комбинацией псевдонимов, source
, bash -c
, символические ссылки и т. д.
Осторожно: если вы cd
перед запуском этого фрагмента в другой каталог результат может быть неверным!
Кроме того, следите за $CDPATH
побочные эффекты getchas и stderr, если пользователь умело переопределил cd для перенаправления вывода на stderr (включая escape-последовательности, например, при вызове update_terminal_cwd >&2
на Mac). Добавление >/dev/null 2>&1
в конце вашего cd
Команда позаботится об обеих возможностях.
Чтобы понять, как это работает, попробуйте запустить эту более подробную форму:
#!/bin/bash
SOURCE="${BASH_SOURCE[0]}"
while [ -h "$SOURCE" ]; do # resolve $SOURCE until the file is no longer a symlink
TARGET="$(readlink "$SOURCE")"
if [[ $TARGET == /* ]]; then
echo "SOURCE '$SOURCE' is an absolute symlink to '$TARGET'"
SOURCE="$TARGET"
else
DIR="$( dirname "$SOURCE" )"
echo "SOURCE '$SOURCE' is a relative symlink to '$TARGET' (relative to '$DIR')"
SOURCE="$DIR/$TARGET" # if $SOURCE was a relative symlink, we need to resolve it relative to the path where the symlink file was located
fi
done
echo "SOURCE is '$SOURCE'"
RDIR="$( dirname "$SOURCE" )"
DIR="$( cd -P "$( dirname "$SOURCE" )" >/dev/null 2>&1 && pwd )"
if [ "$DIR" != "$RDIR" ]; then
echo "DIR '$RDIR' resolves to '$DIR'"
fi
echo "DIR is '$DIR'"
И это напечатает что-то вроде:
SOURCE './scriptdir.sh' is a relative symlink to 'sym2/scriptdir.sh' (relative to '.')
SOURCE is './sym2/scriptdir.sh'
DIR './sym2' resolves to '/home/ubuntu/dotfiles/fo fo/real/real1/real2'
DIR is '/home/ubuntu/dotfiles/fo fo/real/real1/real2'
Использование dirname "$0"
:
#!/bin/bash
echo "The script you are running has basename `basename "$0"`, dirname `dirname "$0"`"
echo "The present working directory is `pwd`"
с помощью pwd
один не будет работать, если вы не запускаете скрипт из каталога, в котором он находится.
[matt@server1 ~]$ pwd
/home/matt
[matt@server1 ~]$ ./test2.sh
The script you are running has basename test2.sh, dirname .
The present working directory is /home/matt
[matt@server1 ~]$ cd /tmp
[matt@server1 tmp]$ ~/test2.sh
The script you are running has basename test2.sh, dirname /home/matt
The present working directory is /tmp
Команда dirname является самой простой, просто анализируя путь до имени файла из переменной $0 (имя скрипта):
dirname "$0"
Но, как указал matt b, возвращаемый путь отличается в зависимости от того, как вызывается скрипт. pwd не выполняет эту работу, потому что он говорит только о том, что является текущим каталогом, а не в каком каталоге находится скрипт. Кроме того, если выполняется символическая ссылка на скрипт, вы получите (возможно, относительный) путь где находится ссылка, а не фактический скрипт.
Некоторые другие упоминали команду readlink, но в простейшем случае вы можете использовать:
dirname "$(readlink -f "$0")"
readlink разрешит путь сценария к абсолютному пути от корня файловой системы. Таким образом, любые пути, содержащие одинарные или двойные точки, тильды и / или символические ссылки, будут преобразованы в полный путь.
Вот скрипт, демонстрирующий каждый из них, whatdir.sh:
#!/bin/bash
echo "pwd: `pwd`"
echo "\$0: $0"
echo "basename: `basename $0`"
echo "dirname: `dirname $0`"
echo "dirname/readlink: $(dirname $(readlink -f $0))"
Запуск этого скрипта в моей домашней директории, используя относительный путь:
>>>$ ./whatdir.sh
pwd: /Users/phatblat
$0: ./whatdir.sh
basename: whatdir.sh
dirname: .
dirname/readlink: /Users/phatblat
Опять же, но с использованием полного пути к сценарию:
>>>$ /Users/phatblat/whatdir.sh
pwd: /Users/phatblat
$0: /Users/phatblat/whatdir.sh
basename: whatdir.sh
dirname: /Users/phatblat
dirname/readlink: /Users/phatblat
Теперь меняем каталоги:
>>>$ cd /tmp
>>>$ ~/whatdir.sh
pwd: /tmp
$0: /Users/phatblat/whatdir.sh
basename: whatdir.sh
dirname: /Users/phatblat
dirname/readlink: /Users/phatblat
И наконец, используя символическую ссылку для выполнения скрипта:
>>>$ ln -s ~/whatdir.sh whatdirlink.sh
>>>$ ./whatdirlink.sh
pwd: /tmp
$0: ./whatdirlink.sh
basename: whatdirlink.sh
dirname: .
dirname/readlink: /Users/phatblat
pushd . > /dev/null
SCRIPT_PATH="${BASH_SOURCE[0]}"
if ([ -h "${SCRIPT_PATH}" ]); then
while([ -h "${SCRIPT_PATH}" ]); do cd `dirname "$SCRIPT_PATH"`;
SCRIPT_PATH=`readlink "${SCRIPT_PATH}"`; done
fi
cd `dirname ${SCRIPT_PATH}` > /dev/null
SCRIPT_PATH=`pwd`;
popd > /dev/null
Работает для всех версий, в том числе
- при вызове через мягкую ссылку множественной глубины,
- когда файл это
- когда скрипт вызывается командой "
source
"ака.
(точка) оператор. - когда арг
$0
модифицируется от звонящего. "./script"
"/full/path/to/script"
"/some/path/../../another/path/script"
"./some/folder/script"
В качестве альтернативы, если сам скрипт bash является относительной символической ссылкой, вы хотите перейти по нему и вернуть полный путь связанного сценария:
pushd . > /dev/null
SCRIPT_PATH="${BASH_SOURCE[0]}";
if ([ -h "${SCRIPT_PATH}" ]) then
while([ -h "${SCRIPT_PATH}" ]) do cd `dirname "$SCRIPT_PATH"`; SCRIPT_PATH=`readlink "${SCRIPT_PATH}"`; done
fi
cd `dirname ${SCRIPT_PATH}` > /dev/null
SCRIPT_PATH=`pwd`;
popd > /dev/null
SCRIPT_PATH
дается в полном пути, независимо от того, как это называется.
Просто убедитесь, что вы нашли это в начале скрипта.
Этот комментарий и код Copyleft, выбираемая лицензия под GPL2.0 или выше или CC-SA 3.0 (CreativeCommons Share Alike) или выше. (c) 2008. Все права защищены. Никаких гарантий. Вы были предупреждены.
http://www.gnu.org/licenses/gpl-2.0.txt
http://creativecommons.org/licenses/by-sa/3.0/
18eedfe1c99df68dc94d4a94712a71aaa8e1e9e36cacf421b9463dd2bbaa02906d0d6656
Я устал заходить на эту страницу снова и снова, чтобы скопировать и вставить одну строку в принятый ответ. Проблема в том, что это нелегко понять и запомнить.
Вот простой в запоминании скрипт:
DIR=$(dirname "${BASH_SOURCE[0]}") # get the directory name
DIR=$(realpath "${DIR}") # resolve its full path if need be
Вы можете использовать $BASH_SOURCE
#!/bin/bash
scriptdir=`dirname "$BASH_SOURCE"`
Обратите внимание, что вам нужно использовать #!/ Bin/bash, а не #!/ Bin/sh, так как это расширение bash
Это должно сделать это:
DIR=$(dirname "$(readlink -f "$0")")
Работает с символическими ссылками и пробелами в пути. Смотрите справочные страницы для dirname и readlink.
Редактировать:
Судя по комментариям, он не работает с Mac OS. Я понятия не имею, почему это так. Какие-либо предложения?
pwd
может использоваться для поиска текущего рабочего каталога, и dirname
найти каталог определенного файла (команда, которая была запущена, $0
, так dirname $0
должен дать вам каталог текущего скрипта).
Тем не мение, dirname
дает именно часть каталога имени файла, которая, скорее всего, будет относительно текущего рабочего каталога. Если по какой-либо причине вашему сценарию необходимо изменить каталог, вывод dirname
становится бессмысленным.
Я предлагаю следующее:
#!/bin/bash
reldir=`dirname $0`
cd $reldir
directory=`pwd`
echo "Directory is $directory"
Таким образом, вы получите абсолютный, а не относительный каталог.
Поскольку сценарий будет запускаться в отдельном экземпляре bash, нет необходимости впоследствии восстанавливать рабочий каталог, но если вы по какой-то причине захотите вернуться обратно в свой сценарий, вы можете легко присвоить значение pwd
в переменную, прежде чем изменить каталог, для будущего использования.
Хотя просто
cd `dirname $0`
решает конкретный сценарий в вопросе, я нахожу имеющий абсолютный путь к более полезному в целом.
Я не думаю, что это так просто, как это сделали другие. pwd не работает, так как текущий каталог не обязательно является каталогом со скриптом. $0 тоже не всегда имеет информацию. Рассмотрим следующие три способа вызова сценария.
./script
/usr/bin/script
script
В первом и третьем способах $ 0 не имеет полной информации о пути. Во втором и третьем pwd не работают. Единственный способ получить каталог третьим способом - это запустить путь и найти файл с правильным соответствием. В основном код должен был бы переделывать то, что делает ОС.
One way to do what you are asking would be to just hardcode the data in the /usr/share dir, and reference it by full path. Data shoudn't be in the /usr/bin dir anyway, so this is probably the thing to do.
Это получает текущий рабочий каталог в Mac OS X 10.6.6:
DIR=$(cd "$(dirname "$0")"; pwd)
Это специфично для Linux, но вы можете использовать:
SELF=$(readlink /proc/$$/fd/255)
Самый короткий и элегантный способ сделать это:
#!/bin/bash
DIRECTORY=$(cd `dirname $0` && pwd)
echo $DIRECTORY
Это будет работать на всех платформах и очень чисто.
Более подробную информацию можно найти в разделе "В каком каталоге находится этот сценарий bash?".
Вот POSIX-совместимая строка:
SCRIPT_PATH=`dirname "$0"`; SCRIPT_PATH=`eval "cd \"$SCRIPT_PATH\" && pwd"`
# test
echo $SCRIPT_PATH
Во многих случаях все, что вам нужно получить, - это полный путь к только что вызванному сценарию. Это легко сделать следующим образом. Обратите внимание, чтоrealpath
является частью GNU coreutils. Если он у вас еще не установлен (по умолчанию в Ubuntu), вы можете установить его с помощьюsudo apt update && sudo apt install coreutils
.
realpath_test.sh:
#!/bin/bash
PATH_TO_SCRIPT="$(realpath $0)"
echo "PATH_TO_SCRIPT = \"$PATH_TO_SCRIPT\""
Пример вывода:
$./realpath_test.sh
PATH_TO_SCRIPT = "/home/gabriel/dev/linux_scripts/practice/realpath/realpath_test.sh"
Обратите внимание, что realpath
также успешно переходит по символическим ссылкам, чтобы определять и указывать на их цели, а не указывать на символическую ссылку.
Ссылки:
#!/bin/sh
PRG="$0"
# need this for relative symlinks
while [ -h "$PRG" ] ; do
PRG=`readlink "$PRG"`
done
scriptdir=`dirname "$PRG"`
Я попробовал каждый из них, и ни один из них не сработал. Один был очень близко, но имел крошечную ошибку, которая его сильно сломала; они забыли завернуть путь в кавычки.
Также многие люди предполагают, что вы запускаете скрипт из оболочки, поэтому забудьте, что при открытии нового скрипта он по умолчанию будет установлен у вас дома.
Попробуйте этот каталог для размера:
/ var / Никто / Мысль / О Пространствах / В каталоге / Имя / И вот ваш файл.text
Это правильно, независимо от того, как и где вы запускаете.
#!/bin/bash
echo "pwd: `pwd`"
echo "\$0: $0"
echo "basename: `basename "$0"`"
echo "dirname: `dirname "$0"`"
Итак, чтобы сделать его действительно полезным, вот как перейти в каталог запущенного скрипта:
cd "`dirname "$0"`"
надеюсь, это поможет
Вот простой, правильный способ:
actual_path=$(readlink -f "${BASH_SOURCE[0]}")
script_dir=$(dirname "$actual_path")
Объяснение:
${BASH_SOURCE[0]}
- полный путь к сценарию. Значение этого будет правильным, даже когда сценарий получен, напримерsource <(echo 'echo $0')
печатает bash, заменяя его${BASH_SOURCE[0]}
напечатает полный путь скрипта. (Конечно, это предполагает, что вы в порядке, принимая зависимость от Bash.)readlink -f
- Рекурсивно разрешает любые символические ссылки в указанном пути. Это расширение GNU, которое недоступно (например, в системах BSD). Если вы используете Mac, вы можете использовать Homebrew для установки GNUcoreutils
и вытеснить это сgreadlink -f
,И конечно
dirname
получает родительский каталог пути.
Небольшой пересмотр решения e-удовлетворительно и 3bcdnlklvc04a указал в своем ответе
SCRIPT_DIR=''
pushd "$(dirname "$(readlink -f "$BASH_SOURCE")")" > /dev/null && {
SCRIPT_DIR="$PWD"
popd > /dev/null
}
Это должно работать во всех перечисленных случаях.
РЕДАКТИРОВАТЬ: предотвратить popd после неудачного pushd, благодаря konsolebox
Для систем, имеющих GNU coreutils readlink (например, linux):
$(readlink -f "$(dirname "$0")")
Не нужно использовать BASH_SOURCE
когда $0
содержит имя файла сценария.
Я бы использовал что-то вроде этого:
# retrieve the full pathname of the called script
scriptPath=$(which $0)
# check whether the path is a link or not
if [ -L $scriptPath ]; then
# it is a link then retrieve the target path and get the directory name
sourceDir=$(dirname $(readlink -f $scriptPath))
else
# otherwise just get the directory name of the script path
sourceDir=$(dirname $scriptPath)
fi
$_ стоит упомянуть как альтернативу $0. Если вы запускаете скрипт из bash, принятый ответ может быть сокращен до:
DIR="$( dirname "$_" )"
Обратите внимание, что это должно быть первым утверждением в вашем скрипте.
Невероятно, насколько простым может быть, как бы вы ни называли скрипт:
#!/bin/bash
#
the_source=$(readlink -f ${BASH_SOURCE[0]})
the_dirname=$(dirname ${the_source})
echo "the_source: ${the_source}"
echo "the_dirname: ${the_dirname}"
Бегайте откуда угодно:
user@computer:~/Downloads/temp$ ./test.sh
Выход:
the_source: /home/user/Downloads/temp/test.sh
the_dirname: /home/user/Downloads/temp
Подводя итог многим ответам:
Script: "/tmp/src dir/test.sh"
Calling folder: "/tmp/src dir/other"
Используемые команды
echo Script-Dir : `dirname "$(realpath $0)"`
echo Script-Dir : $( cd ${0%/*} && pwd -P )
echo Script-Dir : $(dirname "$(readlink -f "$0")")
echo
echo Script-Name : `basename "$(realpath $0)"`
echo Script-Name : `basename $0`
echo
echo Script-Dir-Relative : `dirname "$BASH_SOURCE"`
echo Script-Dir-Relative : `dirname $0`
echo
echo Calling-Dir : `pwd`
Выход:
Script-Dir : /tmp/src dir
Script-Dir : /tmp/src dir
Script-Dir : /tmp/src dir
Script-Name : test.sh
Script-Name : test.sh
Script-Dir-Relative : ..
Script-Dir-Relative : ..
Calling-Dir : /tmp/src dir/other
Смотрите https://pastebin.com/J8KjxrPF
Это работает в bash-3.2:
path="$( dirname "$( which "$0" )" )"
Вот пример его использования:
Допустим, у вас есть каталог ~/bin, который находится в вашем $ PATH. У вас есть скрипт А внутри этого каталога. Это исходный текст сценария ~/bin / lib / B. Вы знаете, где включенный скрипт относительно исходного (подкаталог lib), но не где он относительно текущего каталога пользователя.
Это решается следующим (внутри А):
source "$( dirname "$( which "$0" )" )/lib/B"
Неважно, где находится пользователь или как он вызывает скрипт, это всегда будет работать.
Я сравнил многие из приведенных ответов и предложил несколько более компактных решений. Похоже, что они справляются со всеми безумными крайними случаями, возникающими из вашей любимой комбинации:
- Абсолютные пути или относительные пути
- Мягкие ссылки на файлы и каталоги
- Вызов как
script
,bash script
,bash -c script
,source script
, или же. script
- Пробелы, вкладки, переводы строк, юникод и т. Д. В каталогах и / или именах файлов
- Имена файлов, начинающиеся с дефиса
Если вы работаете с Linux, кажется, что с помощью proc
дескриптор - лучшее решение для поиска полностью разрешенного источника запущенного в данный момент сценария (в интерактивном сеансе ссылка указывает на соответствующий /dev/pts/X
):
resolved="$(readlink /proc/$$/fd/255 && echo X)" && resolved="${resolved%$'\nX'}"
Это немного уродливо, но исправление компактно и легко для понимания. Мы не используем только примитивы bash, но я согласен с этим, потому что readlink
значительно упрощает задачу echo X
добавляет X
до конца строки переменной, так что любые пробелы в имени файла не съедаются, а подстановка параметров ${VAR%X}
в конце строки избавляется от X
, Так как readlink
добавив новую строку (которая обычно используется в подстановке команд, если бы не наш предыдущий трюк), мы также должны избавиться от этого. Это легче всего сделать, используя $''
схема цитирования, которая позволяет нам использовать escape-последовательности, такие как \n
представлять новые строки (это также то, как вы можете легко создавать хитро названные каталоги и файлы).
Вышеприведенное должно охватывать ваши потребности в поиске текущего запущенного скрипта в Linux, но если у вас нет proc
файловая система в вашем распоряжении, или если вы пытаетесь найти полностью разрешенный путь какого-либо другого файла, то, возможно, вы найдете приведенный ниже код полезным. Это всего лишь небольшая модификация вышеупомянутой однострочной. Если вы играете со странными каталогами / именами файлов, проверьте вывод с обоими ls
а также readlink
информативно, так как ls
выведет "упрощенные" пути, подставив ?
для таких вещей, как переводы строк.
absolute_path=$(readlink -e -- "${BASH_SOURCE[0]}" && echo x) && absolute_path=${absolute_path%?x}
dir=$(dirname -- "$absolute_path" && echo x) && dir=${dir%?x}
file=$(basename -- "$absolute_path" && echo x) && file=${file%?x}
ls -l -- "$dir/$file"
printf '$absolute_path: "%s"\n' "$absolute_path"
Попробуйте следующее кросс-совместимое решение:
CWD="$(cd -P -- "$(dirname -- "$0")" && pwd -P)"
как realpath
или же readlink
команды не всегда доступны (в зависимости от операционной системы) и ${BASH_SOURCE[0]}
доступно только в оболочке bash.
В качестве альтернативы вы можете попробовать следующую функцию в bash:
realpath () {
[[ $1 = /* ]] && echo "$1" || echo "$PWD/${1#./}"
}
Эта функция принимает 1 аргумент. Если аргумент уже имеет абсолютный путь, выведите его как есть, иначе выведите $PWD
переменная + аргумент имени файла (без ./
префикс).
Связанные с: