Как получить путь к каталогу, в котором находится Bash script, внутри которого script?
Например, скажем, я хочу использовать Bash script в качестве запуска для другого приложения. Я хочу изменить рабочий каталог на тот, где находится Bash script, поэтому я могу работать с файлами в этом каталоге, например:
$ ./application
#!/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
ошибки $CDPATH
и побочные эффекты вывода 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'
bash
, как вам нужно было бы сделать, если вы хотите вызвать с параметром оболочки, как в sh -x myscript.sh
Используйте dirname "$ 0"
:
использование pwd
не будет работать, если вы не используете script из каталога, в котором он содержится.
[matt @server1 ~] $pwd
/Главная/матовый
[matt @server1 ~] $./test2.sh
script, который вы используете, имеет basename test2.sh, dirname.
Настоящий рабочий каталог /home/matt
[matt @server1 ~] $cd/tmp
[matt @server1 tmp] $~/test2.sh
script, который вы используете, имеет basename test2.sh, dirname/home/matt
Настоящий рабочий каталог /tmp
Код>
type -p
если скрипт исполняемый. Это также может открыть тонкую дыру, если скрипт выполняется с использованием bash test2.sh
и существует другой скрипт с таким же именем, исполняемый где-то еще.
Команда dirname является самой простой, просто анализируя путь до имени файла с переменной $0 (script name):
dirname "$0"
Но, как указал матовый b, возвращаемый путь отличается в зависимости от того, как вызывается script. pwd не выполняет эту работу, потому что это только говорит вам, что представляет собой текущий каталог, а не каталог, в котором находится script. Кроме того, если выполняется символическая ссылка на script, вы получите вероятно, относительный) путь туда, где находится ссылка, а не фактический script.
Некоторые другие упомянули команду readlink, но по своему простейшему вы можете использовать:
dirname "$(readlink -f "$0")"
readlink будет разрешать путь script к абсолютному пути из корня файловой системы. Таким образом, любые пути, содержащие одиночные или двойные точки, тильды и/или символические ссылки, будут разрешены для полного пути.
Здесь script, демонстрирующий каждый из них, 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))"
Запуск этого script в моем домашнем каталоге, используя относительный путь:
>>>$ ./whatdir.sh
pwd: /Users/phatblat
$0: ./whatdir.sh
basename: whatdir.sh
dirname: .
dirname/readlink: /Users/phatblat
Опять же, но используя полный путь к script:
>>>$ /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
И, наконец, используя символическую ссылку для выполнения script:
>>>$ ln -s ~/whatdir.sh whatdirlink.sh
>>>$ ./whatdirlink.sh
pwd: /tmp
$0: ./whatdirlink.sh
basename: whatdirlink.sh
dirname: .
dirname/readlink: /Users/phatblat
readlink
не будет доступен на некоторых платформах при установке по умолчанию. Старайтесь избегать его использования, если можете
export SCRIPT_DIR="$(dirname "$(readlink -f "$0")")"
: export SCRIPT_DIR="$(dirname "$(readlink -f "$0")")"
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
readlink -f $(dirname "${VIRTUAL_ENV}")
;
Вы можете использовать $BASH_SOURCE
#!/bin/bash
scriptdir=`dirname "$BASH_SOURCE"`
Обратите внимание, что вам нужно использовать #!/bin/ bash, а не #!/bin/sh, так как его расширение bash
./foo/script
, тогда $(dirname $BASH_SOURCE)
будет ./foo
.
Это должно сделать это:
DIR=$(dirname "$(readlink -f "$0")")
Работает с символическими ссылками и пробелами в пути. См. Man-страницы для dirname и readlink.
Изменить:
С комментария, похоже, не работает с Mac OS. Я понятия не имею, почему это так. Любые предложения?
./script.sh
показывает .
вместо полного пути к каталогу
stat
. Но все же, это показывает .
если вы находитесь в «этом», реж.
pwd
можно использовать для поиска текущего рабочего каталога, а dirname
для поиска каталога определенного файла (команда, которая была запущена, - $0
, поэтому dirname $0
должна предоставить вам каталог текущий script).
Однако dirname
дает точно часть каталога имени файла, которая скорее всего будет относиться к текущему рабочему каталогу. Если по какой-то причине ваш script должен изменить каталог, то вывод из dirname
становится бессмысленным.
Я предлагаю следующее:
#!/bin/bash
reldir=`dirname $0`
cd $reldir
directory=`pwd`
echo "Directory is $directory"
Таким образом, вы получаете абсолютный, а не относительный каталог.
Так как script будет запущен в отдельном экземпляре bash, нет необходимости восстанавливать рабочий каталог впоследствии, но если вы по какой-либо причине хотите вернуться в свой script, вы можете легко присвойте значение pwd
переменной перед изменением каталога для будущего использования.
Хотя просто
cd `dirname $0`
решает конкретный сценарий в вопросе, я считаю, что абсолютный путь к более полезному обычно.
dirname $0
&& pwd)
Я не думаю, что это так же просто, как другие сделали это. pwd не работает, так как текущий каталог не обязательно является каталогом с script. $0 также не всегда содержит информацию. Рассмотрим следующие три способа вызова script.
./script
/usr/bin/script
script
В первом и третьем способах $0 не имеет полной информации о пути. Во втором и третьем случае pwd не работает. Единственный способ получить dir в третьем способе - пропустить путь и найти файл с правильным совпадением. В принципе, код должен будет повторить то, что делает ОС.
Один из способов сделать то, что вы просите, - просто скопировать данные в каталог /usr/share и указать его полным путем. В любом случае данные не будут находиться в каталоге /usr/bin, так что, вероятно, это нужно сделать.
SCRIPT_DIR=$( cd ${0%/*} && pwd -P )
$0
ни pwd
не гарантируют правильную информацию в зависимости от того, как вызывается скрипт.
Получает текущий рабочий каталог в Mac OS X 10.6.6:
DIR=$(cd "$(dirname "$0")"; pwd)
$(dirname "$(readlink -f "$BASH_SOURCE")")
Это конкретный Linux, но вы можете использовать:
SELF=$(readlink /proc/$$/fd/255)
/proc/fd/$$/255
указывает на tty, а не на каталог. Например, в моей текущей оболочке входа в систему дескрипторы файлов 0, 1, 2 и 255 ссылаются на /dev/pts/4
. В любом случае, в руководстве по bash не упоминается fd 255, поэтому, вероятно, неразумно зависеть от этого поведения. \
realpath ${BASH_SOURCE[0]};
Казалось бы, лучший путь.
Вот однострочный совместимый с POSIX:
SCRIPT_PATH=`dirname "$0"`; SCRIPT_PATH=`eval "cd \"$SCRIPT_PATH\" && pwd"`
# test
echo $SCRIPT_PATH
cd
настроен для печати нового пути.
Я пробовал каждый из них, и никто из них не работал. Один был очень близок, но имел крошечную ошибку, которая сильно нарушала его; они забыли обернуть путь в кавычки.
Также многие полагают, что вы используете script из оболочки, поэтому забудьте, когда вы откроете новый script по умолчанию ваш дом.
Попробуйте этот каталог для размера:
/var/No one/Мысль/О пространствах Бытие/В каталоге/Имя/И здесь ваш файл .text
Это становится правильным, независимо от того, как и где вы его запускаете.
#!/bin/bash
echo "pwd: `pwd`"
echo "\$0: $0"
echo "basename: `basename "$0"`"
echo "dirname: `dirname "$0"`"
Итак, чтобы сделать это действительно полезным здесь, как перейти в каталог запуска script:
cd "`dirname "$0"`"
Надеюсь, что поможет
Вот простой, правильный способ:
actual_path=$(readlink -f "${BASH_SOURCE[0]}")
script_dir=$(dirname "$actual_path")
Пояснение:
${BASH_SOURCE[0]}
- полный путь к script. Значение этого параметра будет правильным, даже если источник script будет получен, например. source <(echo 'echo $0')
печатает bash, а при замене его ${BASH_SOURCE[0]}
будет напечатан полный путь к script. (Конечно, это предполагает, что вы в порядке, принимая зависимость от Bash.)
readlink -f
- Рекурсивно разрешает любые символические ссылки по указанному пути. Это расширение GNU и недоступно для (например) BSD-систем. Если вы используете Mac, вы можете использовать Homebrew для установки GNU coreutils
и вытеснить это с помощью greadlink -f
.
И, конечно, dirname
получает родительский каталог пути.
SCRIPT_DIR=$(dirname $(readlink -f "${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
sh
! Проблема с простыми решениями на основе dirname "$0"
: если скрипт находится в $PATH
и вызывается без пути, он даст неверный результат.
PATH
, $0
будет содержать абсолютное имя файла. Если скрипт вызывается с относительным или абсолютным именем файла, содержащим /
, $0
будет содержать это.
Небольшой пересмотр решения e-satis и 3bcdnlklvc04a указал в их ответ
SCRIPT_DIR=''
pushd "$(dirname "$(readlink -f "$BASH_SOURCE")")" > /dev/null && {
SCRIPT_DIR="$PWD"
popd > /dev/null
}
Это должно работать во всех случаях, которые они перечисляли.
РЕДАКТИРОВАТЬ: предотвратить появление popd после неудачного pushd, благодаря konsolebox
SCRIPT_DIR=''; pushd "$(dirname "$(readlink -f "$BASH_SOURCE")")" > /dev/null && { SCRIPT_DIR=$PWD; popd > /dev/null; }
#!/bin/sh
PRG="$0"
# need this for relative symlinks
while [ -h "$PRG" ] ; do
PRG=`readlink "$PRG"`
done
scriptdir=`dirname "$PRG"`
Я сравнил многие из приведенных ответов и придумал несколько более компактных решений. Кажется, что они обрабатывают все сумасшедшие грани, которые возникают из вашей любимой комбинации:
script
, bash script
, bash -c script
, source script
или . script
Если вы работаете в Linux, кажется, что использование дескриптора proc
- лучшее решение для поиска полностью разрешенного источника текущего script (в интерактивном сеансе ссылка указывает на соответствующий /dev/pts/X
):
resolved="$(readlink /proc/$$/fd/255 && echo X)" && resolved="${resolved%$'\nX'}"
У этого есть немного уродства, но исправление компактно и легко понять. Мы не используем только примитивы bash, но я в порядке, потому что readlink
значительно упрощает задачу. echo X
добавляет X
в конец строки переменной, чтобы никакие конечные пробелы в имени файла не ели, а замена параметра ${VAR%X}
в конце строки избавляется от X
, Поскольку readlink
добавляет новую строку (которая обычно будет использоваться в подстановке команд, если не для нашего предыдущего обмана), мы тоже должны избавиться от нее. Это проще всего выполнить с помощью схемы цитирования $''
, которая позволяет использовать escape-последовательности, такие как \n
для представления новых строк (это также то, как вы можете легко сделать коварно названные каталоги и файлы).
Вышеупомянутое должно охватывать ваши потребности в поиске текущего script в 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"
/dev/pts/30
с bash на рабочем столе Ubuntu 14.10.
$_ стоит упомянуть в качестве альтернативы $0. Если вы используете script из bash, принятый ответ можно сократить до:
DIR="$( dirname "$_" )"
Обратите внимание, что это должно быть первое выражение в вашем script.
source
или .
сценарий. В этих ситуациях $_
будет содержать последний параметр последней команды, которую вы выполнили перед .
, $BASH_SOURCE
работает каждый раз.
Для систем, имеющих GNU coreutils readlink (например, linux):
$(readlink -f "$(dirname "$0")")
Нет необходимости использовать BASH_SOURCE
когда $0
содержит имя файла сценария.
dirname
. Необходим, если путь к каталогу содержит пробелы.
Я устал приходить на эту страницу снова и снова, чтобы скопировать однострочный вкладыш в принятый ответ. Проблема в том, что это непросто понять и запомнить.
Вот простой сценарий:
DIR=$(dirname "${BASH_SOURCE[0]}") # get the directory name
DIR=$(realpath "${DIR}") # resolve its full path if need be
realpath
исходит из GNU coreutils , но кто-нибудь знает, насколько он распространен среди других дисков?
Итак... Я считаю, что у меня есть этот. Поздно к вечеринке, но я думаю, что некоторые из них оценят, что именно здесь они сталкиваются с этой нитью. Комментарии должны быть объяснены.
#!/bin/sh # dash bash ksh # !zsh (issues). G. Nixon, 12/2013. Public domain.
## 'linkread' or 'fullpath' or (you choose) is a little tool to recursively
## dereference symbolic links (ala 'readlink') until the originating file
## is found. This is effectively the same function provided in stdlib.h as
## 'realpath' and on the command line in GNU 'readlink -f'.
## Neither of these tools, however, are particularly accessible on the many
## systems that do not have the GNU implementation of readlink, nor ship
## with a system compiler (not to mention the requisite knowledge of C).
## This script is written with portability and (to the extent possible, speed)
## in mind, hence the use of printf for echo and case statements where they
## can be substituded for test, though I've had to scale back a bit on that.
## It is (to the best of my knowledge) written in standard POSIX shell, and
## has been tested with bash-as-bin-sh, dash, and ksh93. zsh seems to have
## issues with it, though I'm not sure why; so probably best to avoid for now.
## Particularly useful (in fact, the reason I wrote this) is the fact that
## it can be used within a shell script to find the path of the script itself.
## (I am sure the shell knows this already; but most likely for the sake of
## security it is not made readily available. The implementation of "$0"
## specificies that the $0 must be the location of **last** symbolic link in
## a chain, or wherever it resides in the path.) This can be used for some
## ...interesting things, like self-duplicating and self-modifiying scripts.
## Currently supported are three errors: whether the file specified exists
## (ala ENOENT), whether its target exists/is accessible; and the special
## case of when a sybolic link references itself "foo -> foo": a common error
## for beginners, since 'ln' does not produce an error if the order of link
## and target are reversed on the command line. (See POSIX signal ELOOP.)
## It would probably be rather simple to write to use this as a basis for
## a pure shell implementation of the 'symlinks' util included with Linux.
## As an aside, the amount of code below **completely** belies the amount
## effort it took to get this right -- but I guess that coding for you.
##===-------------------------------------------------------------------===##
for argv; do :; done # Last parameter on command line, for options parsing.
## Error messages. Use functions so that we can sub in when the error occurs.
recurses(){ printf "Self-referential:\n\t$argv ->\n\t$argv\n" ;}
dangling(){ printf "Broken symlink:\n\t$argv ->\n\t"$(readlink "$argv")"\n" ;}
errnoent(){ printf "No such file: "$@"\n" ;} # Borrow a horrible signal name.
# Probably best not to install as 'pathfull', if you can avoid it.
pathfull(){ cd "$(dirname "$@")"; link="$(readlink "$(basename "$@")")"
## 'test and 'ls' report different status for bad symlinks, so we use this.
if [ ! -e "$@" ]; then if $(ls -d "$@" 2>/dev/null) 2>/dev/null; then
errnoent 1>&2; exit 1; elif [ ! -e "$@" -a "$link" = "$@" ]; then
recurses 1>&2; exit 1; elif [ ! -e "$@" ] && [ ! -z "$link" ]; then
dangling 1>&2; exit 1; fi
fi
## Not a link, but there might be one in the path, so 'cd' and 'pwd'.
if [ -z "$link" ]; then if [ "$(dirname "$@" | cut -c1)" = '/' ]; then
printf "$@\n"; exit 0; else printf "$(pwd)/$(basename "$@")\n"; fi; exit 0
fi
## Walk the symlinks back to the origin. Calls itself recursivly as needed.
while [ "$link" ]; do
cd "$(dirname "$link")"; newlink="$(readlink "$(basename "$link")")"
case "$newlink" in
"$link") dangling 1>&2 && exit 1 ;;
'') printf "$(pwd)/$(basename "$link")\n"; exit 0 ;;
*) link="$newlink" && pathfull "$link" ;;
esac
done
printf "$(pwd)/$(basename "$newlink")\n"
}
## Demo. Install somewhere deep in the filesystem, then symlink somewhere
## else, symlink again (maybe with a different name) elsewhere, and link
## back into the directory you started in (or something.) The absolute path
## of the script will always be reported in the usage, along with "$0".
if [ -z "$argv" ]; then scriptname="$(pathfull "$0")"
# Yay ANSI l33t codes! Fancy.
printf "\n\033[3mfrom/as: \033[4m$0\033[0m\n\n\033[1mUSAGE:\033[0m "
printf "\033[4m$scriptname\033[24m [ link | file | dir ]\n\n "
printf "Recursive readlink for the authoritative file, symlink after "
printf "symlink.\n\n\n \033[4m$scriptname\033[24m\n\n "
printf " From within an invocation of a script, locate the script "
printf "own file\n (no matter where it has been linked or "
printf "from where it is being called).\n\n"
else pathfull "$@"
fi
Попробуйте использовать:
real=$(realpath $(dirname $0))
Попробуйте следующее кросс-совместимое решение:
CWD="$(cd -P -- "$(dirname -- "$0")" && pwd -P)"
как realpath
или readlink
не всегда доступны (в зависимости от операционной системы), а ${BASH_SOURCE[0]}
доступен только в оболочке bash.
В качестве альтернативы вы можете попробовать следующую функцию в bash:
realpath () {
[[ $1 = /* ]] && echo "$1" || echo "$PWD/${1#./}"
}
Эта функция принимает 1 аргумент. Если аргумент уже имеет абсолютный путь, напечатайте его как есть, в противном случае напечатайте $PWD
variable + имя файла (без ./
префикса).
по теме:
realpath
принимает 1 аргумент. Если аргумент уже имеет абсолютный путь, выведите его как есть, в противном случае выведите $PWD
+ filename (без префикса ./
).
Хмм, если в basename и dirname пути просто не будет сокращаться и ходить по пути тяжело (что, если родитель не экспортировал PATH!). Однако оболочка должна иметь открытую ручку для своего script, а в bash дескриптор # 255.
SELF=`readlink /proc/$$/fd/255`
работает для меня.
/dev/pts/30
с bash на Ubuntu 14.10 Desktop, вместо того, чтобы каталог, из которого я запускаю скрипт.
Это работает в bash -3.2:
path="$( dirname "$( which "$0" )" )"
Вот пример его использования:
Скажем, у вас есть каталог ~/bin, который находится в $PATH. У вас есть script A внутри этого каталога. Это источник s script ~/bin/lib/B. Вы знаете, где включен script относительно исходного (подкаталог lib), но не там, где он относится к текущему каталогу пользователя.
Это решается следующим (внутри A):
source "$( dirname "$( which "$0" )" )/lib/B"
Не имеет значения, где пользователь или как он называет script, это всегда будет работать.
which
очень спорно. type
, hash
и другие встроенные функции делают то же самое лучше в bash. which
вроде более переносимо, хотя на самом деле это не то же самое, which
используется в других оболочках, таких как tcsh, в которых он встроен.
which
является внешним инструментом, у вас нет оснований полагать, что он ведет себя идентично родительской оболочке.
Лучшим компактным решением, на мой взгляд, будет:
"$( cd "$( echo "${BASH_SOURCE[0]%/*}" )"; pwd )"
Нет никакой зависимости ни от чего, кроме Bash. Использование dirname
, readlink
и basename
в конечном итоге приведет к проблемам совместимости, поэтому их лучше всего избегать, если это вообще возможно.
"$( cd "$( echo "${BASH_SOURCE[0]%/*}/" )"; pwd )"
. У вас будут проблемы с корневым каталогом, если вы этого не сделаете. Кроме того, почему вы даже должны использовать эхо?
Ни один из них не работал для bash script, запущенного Finder в OS X - я закончил использование:
SCRIPT_LOC="`ps -p $$ | sed /PID/d | sed s:.*/Network/:/Network/: |
sed s:.*/Volumes/:/Volumes/:`"
Не очень, но он выполняет свою работу.
Это единственный способ, которым я убедился в достоверности:
SCRIPT_DIR=$(dirname $(cd "$(dirname "$BASH_SOURCE")"; pwd))
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
Это сработало для меня, когда другие ответы здесь не были:
thisScriptPath=`realpath $0`
thisDirPath=`dirname $thisScriptPath`
echo $thisDirPath
dirname
выведет имя каталога. И, в этом случае, вызов echo без правильного цитирования переменной потенциально приведет к другому выводу, поскольку он будет заполнять пробелы. В целом, чище просто написать dirname "$(realpath "$0")"
$0
не является надежным способом получения текущего пути script. Например, это мой .xprofile
:
#!/bin/bash
echo "$0 $1 $2"
echo "${BASH_SOURCE[0]}"
# $dir/my_script.sh &
cd/tmp && ~/.xprofile && & source ~/.xprofile
/home/puchuu/.xprofile
/home/puchuu/.xprofile
-bash
/home/puchuu/.xprofile
Поэтому используйте BASH_SOURCE
вместо этого.
Используйте комбинацию readlink для канонизации имени (с бонусом после его возврата к исходному, если это символическая ссылка) и dirname для извлечения имени каталога:
script="`readlink -f "${BASH_SOURCE[0]}"`"
dir="`dirname "$script"`"
ЕСЛИ ВАШ ОБЩИЙ СЦЕНАРИЙ - СИМЛИНК, тогда это способ сделать это
#!/usr/bin/env bash
dirn="$(dirname "$0")"
rl="$(readlink "$0")";
exec_dir="$(dirname $(dirname "$rl"))";
my_path="$dirn/$exec_dir";
X="$(cd $(dirname ${my_path}) && pwd)/$(basename ${my_path})"
X - это каталог, содержащий ваш скрипт bash (исходный файл, а не символическую ссылку). Я клянусь Богом, что это работает, и это единственный способ, которым я знаю это правильно.
Еще один вариант похвалить все другие отличные ответы
$ (cd "$ (dirname" $ {BASH_SOURCE [0]} ")"; pwd) "
Вот как я работаю над своими скриптами:
pathvar = "$ (cd" $(dirname $0) "& & pwd)"
Это скажет вам, из какого каталога запускается Launcher (текущий script).
Это решение применяется только к bash. Обратите внимание, что ответ, который обычно предоставляется ${BASH_SOURCE[0]}
, не будет работать, если вы попытаетесь найти путь изнутри функции.
Я нашел, что эта строка всегда работает, независимо от того, был ли файл получен или запущен как script.
dirname ${BASH_SOURCE[${#BASH_SOURCE[@]} - 1]}
Если вы хотите следовать символическим ссылкам, используйте readlink
в пути, который вы видите выше, рекурсивно или не рекурсивно.
Здесь a script, чтобы попробовать и сравнить его с другими предлагаемыми решениями. Вызовите его как 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 [$ я + 1]}.
имя_функции
Переменная массива, содержащая имена всех функций оболочки в настоящее время в стеке выполнения вызовов. Элемент с индексом 0 - это имя любой текущей исполняемой функции оболочки. Самый нижний элемент (тот, который имеет самый высокий индекс) является "основным". Эта переменная существует только при выполнении функции оболочки. Присвоения функции FUNCNAME не влияют и возвращают статус ошибки. Если функция FUNCNAME не установлена, она теряет свои специальные свойства, даже если она впоследствии reset.
Эта переменная может использоваться с BASH_LINENO и BASH_SOURCE. Каждый элемент FUNCNAME имеет соответствующие элементы в BASH_LINENO и BASH_SOURCE для описания стека вызовов. Например, ${FUNCNAME [$ i]} вызывается из файла ${BASH_SOURCE [$ я + 1]} при номере строки ${BASH_LINENO [$ i]}. С помощью этой информации встроенный caller отображает текущий стек вызовов.
[Источник: Bash manual]
Вот отрывок из моего ответа на shell script: проверить имя каталога и преобразовать в нижний регистр, в котором я демонстрирую не только то, как решить эту проблему с очень простой Утилиты, указанные в POSIX, я также адресую , как очень просто сохранить результаты функции в возвращаемой переменной...
... Ну, как вы можете видеть, с некоторой помощью, я натолкнулся на довольно простое и очень мощное решение:
Я могу передать функцию своего рода переменной мессенджера и разыменовать любое явное использование результирующего аргумента функции $1
name с eval
по мере необходимости, а после завершения процедуры функции я использую eval
и обратную косую черту цитируя трюк, чтобы присвоить переменной моего мессенджера значение, которое я хочу, даже не зная его имени.
В полном раскрытии,... [Я нашел переменную часть сообщения этого] и Rich sh tricks, и я также выложил соответствующую часть его страницы ниже моего собственного отрывка ответа.
... ВЫПИСКА: ...
Хотя не строго POSIX, realpath является основным приложением GNU с 2012 года. Полное раскрытие: никогда не слышал об этом, прежде чем я заметил его в TOC info coreutils
и сразу подумал о [связанном] вопросе, но используя следующую функцию, как показано, должен быть надежно (скоро POSIXLY?) И, надеюсь, эффективно
предоставить своему вызывающему абоненту абсолютно исходный $0
:
% _abs_0() {
> o1="${1%%/*}"; ${o1:="${1}"}; ${o1:=`realpath -s "${1}"`}; eval "$1=\${o1}";
> }
% _abs_0 ${abs0:="${0}"} ; printf %s\\n "${abs0}"
/no/more/dots/in/your/path2.sh
EDIT: Возможно, стоит отметить, что в этом решении используется расширение параметра POSIX, чтобы сначала проверить, на самом деле требуется расширение и разрешение вообще, прежде чем пытаться это сделать. Это должно вернуть абсолютно sourced $0
через переменную messenger (с заметным исключением, что она сохранит symlinks
) как эффективно, как я мог себе представить, что это можно сделать , является ли путь уже абсолютным.
...
( незначительное редактирование: перед тем, как найти realpath
в документах, я по крайней мере уменьшил мою версию [версии ниже], чтобы не зависеть от поля времени [как это было в первая команда ps
], но, честное предупреждение, после тестирования некоторые из них менее убедительны. ps
полностью надежен в своем расширении пропускной способности)
С другой стороны, вы можете сделать это:
ps ww -fp $$ | grep -Eo '/[^:]*'"${0#*/}"
eval "abs0=${`ps ww -fp $$ | grep -Eo ' /'`#?}"
... И от Rich sh tricks: ...
Возвращаемые строки из функции оболочки
Как видно из приведенной выше ошибки подстановки команд, stdout не является хорошим средством для функций оболочки, чтобы возвращать строки их вызывающему абоненту, если выход не находится в формате, где конечные символы новой строки несущественны. Конечно, такая практика неприемлема для функций, предназначенных для обработки произвольных строк. Итак, что можно сделать?
Попробуйте следующее:
func () {
body here
eval "$1=\${foo}"
}
Конечно, ${foo}
можно заменить любой подстановкой. Ключевым трюком здесь является линия eval и использование экранирования. "$1"
расширяется, когда аргумент eval создается главным парсером команды. Но "${foo}"
не расширяется на этом этапе, поскольку цитируется "$"
. Вместо этого его расширение, когда eval оценивает его аргумент. Если неясно, почему это важно, подумайте о том, как это будет плохо:
foo='hello ; rm -rf /'
dest=bar
eval "$dest=$foo"
Но, конечно, следующая версия совершенно безопасна:
foo='hello ; rm -rf /'
dest=bar
eval "$dest=\$foo"
Обратите внимание, что в исходном примере "$1"
использовался, чтобы позволить вызывающему лицу передать имя целевой переменной в качестве аргумента функции. Если вашей функции необходимо использовать команду shift, например, чтобы обрабатывать оставшиеся аргументы как "$@"
, тогда может быть полезно сохранить значение "$1"
во временной переменной в начале функции.
Я обычно делаю:
LIBDIR=$(dirname "$(readlink -f "$(type -P $0 || echo $0)")")
source $LIBDIR/lib.sh
Основываясь на этом ответе, я предлагаю уточненную версию, которая получает SCRIPT_HOME
как содержащую папку любого текущего запуска bash script
s=${BASH_SOURCE[0]} ; s=`dirname $s` ; SCRIPT_HOME=`cd $s ; pwd`
echo $SCRIPT_HOME
Посмотрите на тест внизу со странными именами каталогов.
Чтобы изменить рабочий каталог на тот, где находится Bash script, вы должны попробовать этот простой, проверенный и проверить с помощью shellcheck:
#!/bin/bash --
cd "$(dirname "${0}")"/. || exit 2
Тест:
$ ls
application
$ mkdir "$(printf "\1\2\3\4\5\6\7\10\11\12\13\14\15\16\17\20\21\22\23\24\25\26\27\30\31\32\33\34\35\36\37\40\41\42\43\44\45\46\47testdir" "")"
$ mv application *testdir
$ ln -s *testdir "$(printf "\1\2\3\4\5\6\7\10\11\12\13\14\15\16\17\20\21\22\23\24\25\26\27\30\31\32\33\34\35\36\37\40\41\42\43\44\45\46\47symlink" "")"
$ ls -lb
total 4
lrwxrwxrwx 1 jay stacko 46 Mar 30 20:44 \001\002\003\004\005\006\a\b\t\n\v\f\r\016\017\020\021\022\023\024\025\026\027\030\031\032\033\034\035\036\037\ !"#$%&'symlink -> \001\002\003\004\005\006\a\b\t\n\v\f\r\016\017\020\021\022\023\024\025\026\027\030\031\032\033\034\035\036\037\ !"#$%&'testdir
drwxr-xr-x 2 jay stacko 4096 Mar 30 20:44 \001\002\003\004\005\006\a\b\t\n\v\f\r\016\017\020\021\022\023\024\025\026\027\030\031\032\033\034\035\036\037\ !"#$%&'testdir
$ *testdir/application && printf "SUCCESS\n" ""
SUCCESS
$ *symlink/application && printf "SUCCESS\n" ""
SUCCESS
Очень поздно обсуждать, но попробуйте что-то вроде этого:
function get_realpath() {
if [[ -f "$1" ]]
then
# file *must* exist
if cd "$(echo "${1%/*}")" &>/dev/null
then
# file *may* not be local
# exception is ./file.ext
# try 'cd .; cd -;' *works!*
local tmppwd="$PWD"
cd - &>/dev/null
else
# file *must* be local
local tmppwd="$PWD"
fi
else
# file *cannot* exist
return 1 # failure
fi
# reassemble realpath
echo "$tmppwd"/"${1##*/}"
return 0 # success
}
function get_dirname(){
local realpath="$(get_realpath "$1")"
if (( $? )) # true when non-zero.
then
return $? # failure
fi
echo "${realpath%/*}"
return 0 # success
}
# Then from the top level:
get_dirname './script.sh'
# Or Within a script:
get_dirname "$0"
# Can even test the outcome!
if (( $? )) # true when non-zero.
then
exit 1 # failure
fi
Эти функции и связанные с ними инструменты являются частью нашего продукта, который был бесплатно предоставлен сообществу и может быть найден в GitHub как realpath-lib. Он прост, чист и хорошо документирован (отлично подходит для обучения), чистый Bash и не имеет зависимостей. Хорошо подходит и для кросс-платформенного использования. Итак, для приведенного выше примера, в script вы могли бы просто:
source '/path/to/realpath-lib'
get_dirname "$0"
if (( $? )) # true when non-zero.
then
exit 1 # failure
fi
Что все!
Я обычно включаю следующее в начало моих скриптов, которое работает в большинстве случаев:
[ "$(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 до местоположения самой ссылки.
Есть, вероятно, лучшие решения, но это самый чистый, который мне удалось придумать.
Я хочу убедиться, что script запущен в своем каталоге. Так
cd $(dirname $(which $0) )
После этого, если вы действительно хотите знать, где вы работаете, выполните следующую команду.
DIR=$(/usr/bin/pwd)
Выбранный ответ работает очень хорошо. Я публикую свое решение для тех, кто ищет более короткие альтернативы, которые все еще касаются поиска, выполнения, полных путей, относительных путей и символических ссылок. Наконец, это будет работать на MacOS, учитывая, что нельзя предполагать, что доступна версия readlink для GNU coreutils.
Суть в том, что он не использует Bash, но его легко использовать в сценарии Bash. Хотя OP не накладывает никаких ограничений на язык решения, вероятно, лучше всего, чтобы большинство оставалось в мире Bash. Это просто альтернатива, и, возможно, непопулярная.
PHP по умолчанию доступен на MacOS и установлен на ряде других платформ, хотя не обязательно по умолчанию. Я понимаю, что это недостаток, но я все равно оставлю это здесь для всех, кто работает в поисковых системах.
export SOURCE_DIRECTORY="$(php -r 'echo dirname(realpath($argv[1]));' -- "${BASH_SOURCE[0]}")"
(Примечание: этот ответ прошел через множество изменений, поскольку я улучшил оригинал. По состоянию на последнюю ревизию никто еще не прокомментировал или не проголосовал.)
Я добавляю этот ответ как можно больше для моей собственной выгоды - помнить об этом и собирать комментарии - как для кого-то другого. Ключевая часть ответа заключается в том, что я уменьшаю объем проблемы: я запрещаю косвенное выполнение script по пути (как в пути /bin/sh [script относительно компонента пути]). Это можно обнаружить, потому что $0 будет относительным путем, который не разрешает какой-либо файл относительно текущей папки. Я считаю, что прямое выполнение с использованием "#!" механизм всегда приводит к абсолютному $0, в том числе, когда script находится на пути. Я также требую, чтобы имя пути и любые пути по цепочке символических ссылок содержали только разумное подмножество символов, в частности не '\n', ' > ', '*' или '?'. Это необходимо для логики синтаксического анализа. Есть еще несколько неявных ожиданий, которые я не буду вдаваться (см. Предыдущий ответ < 1 > ), и я не пытаюсь обрабатывать преднамеренные саботажа в размере 0 долларов США (так что учитывайте любые последствия для безопасности). Я ожидаю, что это будет работать практически на любой Unix-подобной системе с Bourne-like/bin/sh.
Комментарии и предложения приветствуются!
#!/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
)
Нет вил (кроме подсели) и может обрабатывать формы "чужие" пути, такие как символы с символами новой строки, как некоторые утверждают:
IFS= read -rd '' DIR < <([[ $BASH_SOURCE != */* ]] || cd "${BASH_SOURCE%/*}/" >&- && echo -n "$PWD")
cur_dir=`old=\`pwd\`; cd \`dirname $0\`; echo \`pwd\`; cd $old;`
function getScriptAbsoluteDir { # fold>>
# @description used to get the script path
# @param $1 the script $0 parameter
local script_invoke_path="$1"
local cwd=`pwd`
# absolute path ? if so, the first character is a /
if test "x${script_invoke_path:0:1}" = 'x/'
then
RESULT=`dirname "$script_invoke_path"`
else
RESULT=`dirname "$cwd/$script_invoke_path"`
fi
} # <<fold
getScriptAbsoluteDir
с bash-скриптом, могу ли я вызвать эту функцию, просто набрав getScriptAbsoluteDir
или local currdir='getScriptAbsoluteDir'
?
function
недоступно в оболочках POSIX и является излишне несовместимым bash / ksh / & c. расширение. Кроме того, если у вас достаточно новая оболочка, чтобы иметь это расширение, вам не нужен тестовый хак "x [...]".
ME=`type -p $0`
MDIR="${ME%/*}"
WORK_DIR=$(cd $MDIR && pwd)
bash script.sh
.
Этот однострочный файл работает на CYGWIN, даже если script был вызван из Windows с помощью bash -c <script>
:
set mydir="$(cygpath "$(dirname "$0")")"
FOLDERNAME=${PWD##*/}
это самый быстрый способ, который я знаю
DIR="$( cd "$( dirname "${BASH_SOURCE[0]}" )" && pwd && echo x)"
- и удалить его без подстановка команды -DIR="${DIR%x}"
.mkdir $'\n'
.