Надежный путь к bash-скрипт, чтобы получить полный путь к себе? [дубликат]
этот вопрос уже есть ответ здесь:
у меня есть скрипт bash, который должен знать свой полный путь. Я пытаюсь найти широко совместимый способ сделать это, не заканчивая относительными или фанковыми путями. Мне только нужно поддержка bash, а не sh, csh и т. д.
что я нашел до сих пор:
принятый ответ на "получение исходного каталога скрипта Bash изнутри" адресует получение пути скрипта через
dirname, что нормально, но это может вернуть a относительные путь (например,.), что является проблемой, если вы хотите изменить каталоги в скрипте и путь все еще указывает на каталог скрипта. И все же,dirnameбудет головоломка.принятый ответ на "абсолютный путь скрипта Bash с OSX"(OS X конкретный, но ответ работает независимо) дает функцию, которая будет проверять, чтобы увидеть, если
выглядит относительным и если так будет pre-pend$PWDк нему. Но результат все равно может иметь относительные биты (хотя в целом он абсолютный) - например, если скриптtв каталоге/usr/binи ты/usrа вы типаbin/../bin/tчтобы запустить его (да, это запутанный), вы в конечном итоге с/usr/bin/../binкак путь к каталогу скрипта. Который работает, но...
The
readlinkрешение на этой странице, который выглядит так:
# Absolute path to this script. /home/user/bin/foo.sh
SCRIPT=$(readlink -f )
# Absolute path this script is in. /home/user/bin
SCRIPTPATH=`dirname $SCRIPT`
но
readlinkэто не POSIX и, по-видимому, решение зависит от GNUreadlinkгде BSD не будет работать по какой-то причине (у меня нет доступа к BSD-подобной системе для проверки).
Итак, различные способы сделать это, но у них у всех есть свои предостережения.
что было бы лучше? Где "лучше" означает:
- дает мне абсолютный путь.
- вынимает фанки биты даже при вызове запутанным способом (см. Комментарий к #2 выше). (Например, по крайней мере умеренно канонизирует путь.)
- полагается только на bash-isms или вещи, которые почти наверняка будут на самых популярных вариантах систем *nix (GNU / Linux, BSD и BSD-подобных систем, таких как OS X, так далее.).
- избегает вызова внешних программ, если это возможно (например, предпочитает встроенные bash).
- (Обновлено, спасибо за головы, которые) не нужно разрешать символические ссылки (на самом деле, я бы предпочел оставить их в покое, но это не требование).
23 ответов:
вот что я придумал (edit: плюс некоторые настройки, предоставляемые sfstewman,levigroker,Кайл Сумка и Роб Кеннеди), что, кажется, в основном соответствует моим" лучшим " критериям:
SCRIPTPATH="$( cd "$(dirname "")" ; pwd -P )"Это
SCRIPTPATHлиния кажется особенно карусели, но нам это нужно, а неSCRIPTPATH=`pwd`для правильной обработки пробелов и символических ссылок.обратите внимание также, что эзотерические ситуации, такие как выполнение сценария, который не является исходящий из файла в доступной файловой системе вообще (что вполне возможно), не обслуживается там (или в любом из других ответов, которые я видел).
Я удивлен, что
realpathкоманда здесь не упоминалась. Я понимаю, что он широко переносится / портируется.ваше первоначальное решение становится:
SCRIPT=`realpath ` SCRIPTPATH=`dirname $SCRIPT`и оставить символические ссылки неразрешенными в соответствии с вашими предпочтениями:
SCRIPT=`realpath -s ` SCRIPTPATH=`dirname $SCRIPT`
самый простой способ, который я нашел, чтобы получить полный канонический путь в bash является использование
cdиpwd:ABSOLUTE_PATH="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)/$(basename "${BASH_SOURCE[0]}")"используя
${BASH_SOURCE[0]}вместопроизводит такое же поведение независимо от того, вызывается ли скрипт как<name>илиsource <name>
Я просто должен был вернуться к этому вопросу сегодня и нашел https://stackoverflow.com/a/246128/1034080. он разрабатывает решение, которое я использовал в прошлом, а также.
DIR="$( cd "$( dirname "${BASH_SOURCE[0]}" )" && pwd )"в связанном ответе есть больше вариантов, например, для случая, когда сам скрипт является символической ссылкой.
получить абсолютный путь скрипта
Не использовать
-fопция в readlink, поэтому должна работать в bsd / mac-osxподдерживает
- источник ./ script (при вызове
.точка оператора)- абсолютный путь/path/to / script
- относительный путь ./ script
- / path/dir1/../dir2 / dir3/../ script
- при вызове из symlink
- если ссылка вложена например)
foo->dir1/dir2/bar bar->./../doe doe->script- когда вызывающий изменяет имя скрипта
я ищу угловые случаи, когда этот код не работает. Пожалуйста, дайте мне знать.
код
pushd . > /dev/null SCRIPT_PATH="${BASH_SOURCE[0]}"; while([ -h "${SCRIPT_PATH}" ]); do cd "`dirname "${SCRIPT_PATH}"`" SCRIPT_PATH="$(readlink "`basename "${SCRIPT_PATH}"`")"; done cd "`dirname "${SCRIPT_PATH}"`" > /dev/null SCRIPT_PATH="`pwd`"; popd > /dev/null echo "srcipt=[${SCRIPT_PATH}]" echo "pwd =[`pwd`]"известный issuse
скрипт должно быть где-то на диске пусть будет по сети. если вы попытаетесь запустить этот скрипт из трубы, он не будет работать
wget -o /dev/null -O - http://host.domain/dir/script.sh |bashТехнически говоря, это не определено.
Практически говоря, нет никакого разумного способа обнаружить это. (совместный процесс не может получить доступ к env родителя)
а как насчет:
SCRIPT_PATH=$(dirname `which `)
whichвыводит в stdout полный путь к исполняемому файлу, который был бы выполнен, когда переданный аргумент был введен в командной строке (что и содержит $0)
dirnameудаляет суффикс без каталога из имени файлатаким образом, вы получаете полный путь к скрипту, независимо от того, был ли указан путь или нет.
поскольку realpath не установлен по умолчанию в моей системе Linux, для меня работает следующее:
SCRIPT="$(readlink --canonicalize-existing "")" SCRIPTPATH="$(dirname "$SCRIPT")"
$SCRIPTбудет содержать реальный путь к файлу сценария и$SCRIPTPATHреальный путь к каталогу, содержащему скрипт.перед использованием этого читать комментарии ответ.
отвечая на этот вопрос слишком поздно, но я использую:
SCRIPT=$( readlink -m $( type -p )) # Full path to script BASE_DIR=`dirname ${SCRIPT}` # Directory script is run in NAME=`basename ${SCRIPT}` # Actual name of script even if linked
мы разместили наш собственный продукт realpath-lib на GitHub для свободного и необремененного использования сообщества.
бесстыдный плагин, но с этой библиотекой Bash вы можете:
get_realpath <absolute|relative|symlink|local file>эта функция является ядром библиотеки:
function get_realpath() { if [[ -f "" ]] 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 }он не требует каких-либо внешних зависимостей, просто Bash 4+. Также содержит функции для
get_dirname,get_filename,get_stemnameиvalidate_pathvalidate_realpath. Это бесплатно, чисто, просто и хорошо документировано, поэтому его можно использовать и для учебных целей, и, несомненно, можно улучшить. Попробуйте его на разных платформах.обновление: после некоторого обзора и тестирования мы заменили вышеуказанную функцию чем-то, что достигает того же результата (без использования dirname, только pure Bash), но с лучшей эффективностью:
function get_realpath() { [[ ! -f "" ]] && return 1 # failure : file does not exist. [[ -n "$no_symlinks" ]] && local pwdp='pwd -P' || local pwdp='pwd' # do symlinks. echo "$( cd "$( echo "${1%/*}" )" 2>/dev/null; $pwdp )"/"${1##*/}" # echo result. return 0 # success }это также включает в себя параметр среды
no_symlinksэто обеспечивает возможность разрешения символических ссылок на физическую систему. По умолчанию он сохраняет символические ссылки нетронутый.
вы можете попытаться определить следующую переменную:
CWD="$(cd -P -- "$(dirname -- "")" && pwd -P)"или вы можете попробовать следующую функцию в bash:
realpath () { [[ = /* ]] && echo "" || echo "$PWD/${1#./}" }эта функция принимает 1 аргумент. Если аргумент уже имеет абсолютный путь, выведите его как есть, иначе выведите
$PWDпеременная + аргумент имени файла (без./префикс).по теме:
возможно, принятый ответ на следующий вопрос может помочь.
как я могу получить поведение GNU readlink-f на Mac?
учитывая, что вы просто хотите канонизировать имя, которое вы получаете от объединения $PWD и $0 (предполагая, что $0 не является абсолютным для начала), просто используйте серию регулярных выражений по строке
abs_dir=${abs_dir//\/.\//\/}и так далее.Да, я знаю, что это выглядит ужасно, но будем работать и чисто Баш.
рассматривая эту проблему снова: есть очень популярное решение, на которое ссылаются в этом потоке, который имеет свое происхождение здесь:
DIR="$( cd "$( dirname "${BASH_SOURCE[0]}" )" && pwd )"Я держался подальше от этого решения из - за использования dirname-это может представлять кросс-платформенные трудности, особенно если сценарий должен быть заблокирован по соображениям безопасности. Но как чистая альтернатива Bash, как насчет использования:
DIR="$( cd "$( echo "${BASH_SOURCE[0]%/*}" )" && pwd )"будет ли это вариант?
легко читается? альтернатива. Игнорирует символические ссылки
#!/bin/bash currentDir=$( cd $(dirname "") pwd ) echo -n "current " pwd echo script $currentDir
принятое решение имеет неудобное (для меня), чтобы не быть "source-able":
если вы называете это от "source ../../yourScript",будет "bash"!следующая функция (для bash >= 3.0) дает мне правильный путь, однако скрипт может быть вызван (напрямую или через
source, С абсолютным или относительным путем):
(под "правильным путем" я подразумеваю полный абсолютный путь вызываемого скрипта, даже при вызове с другого пути, напрямую или с "source")#!/bin/bash echo executed function bashscriptpath() { local _sp= local ascript="" local asp="$(dirname )" #echo "b1 asp '$asp', b1 ascript '$ascript'" if [[ "$asp" == "." && "$ascript" != "bash" && "$ascript" != "./.bashrc" ]] ; then asp="${BASH_SOURCE[0]%/*}" elif [[ "$asp" == "." && "$ascript" == "./.bashrc" ]] ; then asp=$(pwd) else if [[ "$ascript" == "bash" ]] ; then ascript=${BASH_SOURCE[0]} asp="$(dirname $ascript)" fi #echo "b2 asp '$asp', b2 ascript '$ascript'" if [[ "${ascript#/}" != "$ascript" ]]; then asp=$asp ; elif [[ "${ascript#../}" != "$ascript" ]]; then asp=$(pwd) while [[ "${ascript#../}" != "$ascript" ]]; do asp=${asp%/*} ascript=${ascript#../} done elif [[ "${ascript#*/}" != "$ascript" ]]; then if [[ "$asp" == "." ]] ; then asp=$(pwd) ; else asp="$(pwd)/${asp}"; fi fi fi eval $_sp="'$asp'" } bashscriptpath H export H=${H}ключ должен обнаружить "
source" дело и использовать${BASH_SOURCE[0]}чтобы вернуть реальный скрипт.
Если мы используем Bash, я считаю, что это самый удобный способ, поскольку он не требует вызовов каких-либо внешних команд:
THIS_PATH="${BASH_SOURCE[0]}"; THIS_DIR=$(dirname $THIS_PATH)
просто для этого я немного взломал скрипт, который делает вещи чисто текстуально, чисто в bash. Надеюсь, я поймал все крайние случаи. Обратите внимание, что
${var//pat/repl}то, что я упомянул в другом ответе, не работает, так как вы не можете заменить его только самым коротким совпадением, что является проблемой для замены/foo/../, например,/*/../примет все, а не только одну запись. И поскольку эти шаблоны на самом деле не являются регулярными выражениями, я не вижу, как это можно сделать работа. Итак, вот красиво запутанное решение, которое я придумал, наслаждайтесь. ;)кстати, дайте мне знать, если вы найдете какие-либо необработанные крайние случаи.
#!/bin/bash canonicalize_path() { local path="" OIFS="$IFS" IFS=$'/' read -a parts < <(echo "$path") IFS="$OIFS" local i=${#parts[@]} local j=0 local back=0 local -a rev_canon while (($i > 0)); do ((i--)) case "${parts[$i]}" in ""|.) ;; ..) ((back++));; *) if (($back > 0)); then ((back--)) else rev_canon[j]="${parts[$i]}" ((j++)) fi;; esac done while (($j > 0)); do ((j--)) echo -n "/${rev_canon[$j]}" done echo } canonicalize_path "/.././..////../foo/./bar//foo/bar/.././bar/../foo/bar/./../..//../foo///bar/"
я использовал следующий подход успешно некоторое время (не на OSX, хотя), и он использует только встроенную оболочку и обрабатывает " источник foobar.sh-дело, насколько я знаю.
одна из проблем с (поспешно собранным) примером кода ниже заключается в том, что функция использует $PWD, который может быть или не быть правильным во время вызова функции. Так что это должно быть обработано.
#!/bin/bash function canonical_path() { # Handle realtive vs absolute path [ ${1:0:1} == '/' ] && x= || x=$PWD/ # Change to dirname of x cd ${x%/*} # Combine new pwd with basename of x echo $(pwd -P)/${x##*/} cd $OLDPWD } echo $(canonical_path "${BASH_SOURCE[0]}") type [ type cd type echo type pwd
еще один способ сделать это:
shopt -s extglob selfpath= selfdir=${selfpath%%+([!/])} while [[ -L "$selfpath" ]];do selfpath=$(readlink "$selfpath") if [[ ! "$selfpath" =~ ^/ ]];then selfpath=${selfdir}${selfpath} fi selfdir=${selfpath%%+([!/])} done echo $selfpath $selfdir
Comments