Надежный способ для скрипта Bash получить полный путь к себе
У меня есть сценарий Bash, который должен знать полный путь. Я пытаюсь найти широко совместимый способ сделать это, не заканчивая относительными или выглядящими в стиле фанк путями. Мне нужно только поддерживать Bash, а не sh, csh и т. Д.
Что я нашел до сих пор:
Принятый ответ на Получение исходного каталога скрипта Bash из адресов, получающих путь к скрипту через
dirname $0
, что хорошо, но это может вернуть относительный путь (например,.
), что является проблемой, если вы хотите изменить каталоги в сценарии и указать путь к каталогу сценария. Еще,dirname
будет частью головоломки.Принятый ответ на абсолютный путь скрипта Bash с OS X (специфично для OS X, но ответ работает независимо) дает функцию, которая проверит,
$0
выглядит относительным и, если так, будет в ожидании$PWD
к этому. Но результат все равно может содержать относительные биты (хотя в целом он абсолютный) - например, если скриптt
в каталоге/usr/bin
и ты в/usr
и вы печатаетеbin/../bin/t
чтобы запустить его (да, это запутанно), вы в конечном итоге/usr/bin/../bin
как путь к каталогу скрипта. Который работает, но...readlink
Решение на этой странице, которое выглядит так:# Absolute path to this script. /home/user/bin/foo.sh SCRIPT=$(readlink -f $0) # 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).
- (Обновлено, спасибо за wich) Не нужно разрешать символические ссылки (на самом деле, я бы предпочел, чтобы он оставил их в покое, но это не является обязательным требованием).
23 ответа
Вот то, что я придумал (правка: плюс некоторые sfstewman, предоставленные sfstewman, levigroker, Kyle Strand и Rob Kennedy), которые в основном соответствуют моим "лучшим" критериям:
SCRIPTPATH="$( cd "$(dirname "$0")" ; pwd -P )"
Тот SCRIPTPATH
линия кажется особенно карусельной, но нам это нужно, а не SCRIPTPATH=`pwd`
для того, чтобы правильно обрабатывать пробелы и символические ссылки.
Обратите также внимание на то, что эзотерические ситуации, такие как выполнение сценария, который вообще не идет из файла в доступной файловой системе (что вполне возможно), не учитываются там (или в любом другом ответе, который я видел).).
Я удивлен, что realpath
Команда не была упомянута здесь. Насколько я понимаю, это широко переносимый / портированный.
Ваше первоначальное решение становится:
SCRIPT=`realpath $0`
SCRIPTPATH=`dirname $SCRIPT`
И оставить символические ссылки неразрешенными по вашему предпочтению:
SCRIPT=`realpath -s $0`
SCRIPTPATH=`dirname $SCRIPT`
Самый простой способ найти полный канонический путь в Bash - это использовать cd
а также pwd
:
ABSOLUTE_PATH="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)/$(basename "${BASH_SOURCE[0]}")"
С помощью ${BASH_SOURCE[0]}
вместо $0
производит то же поведение независимо от того, вызывается ли скрипт как <name>
или же source <name>
,
Я просто должен был вернуться к этой проблеме сегодня и обнаружил получение исходного каталога скрипта Bash изнутри. Это развивает решение, которое я использовал в прошлом.
DIR="$( cd "$( dirname "${BASH_SOURCE[0]}" )" && pwd )"
В связанном ответе больше вариантов, например, для случая, когда сам скрипт является символической ссылкой.
Получить абсолютный путь сценария оболочки
Он не использует -f
опция в readlink, и поэтому она должна работать на BSD/Mac OS X.
опоры
- источник./script (при вызове
.
оператор точки) - Абсолютный путь / путь / к / скрипту
- Относительный путь вроде./script
- /path/dir1/../dir2/dir3/../script
- Когда вызывается из символической ссылки
- Например, если символическая ссылка вложена
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`]"
Известный Иссус
Сценарий должен быть где-то на диске. Пусть это будет по сети. Если вы попытаетесь запустить этот скрипт из ТРУБЫ, он не будет работать
wget -o /dev/null -O - http://host.domain/dir/script.sh |bash
Технически говоря, это не определено. Фактически, нет никакого здравого способа обнаружить это. (Совместный процесс не может получить доступ к среде родителя.)
Использование:
SCRIPT_PATH=$(dirname `which $0`)
which
выводит на стандартный вывод полный путь к исполняемому файлу, который был бы выполнен, если переданный аргумент был введен в приглашении оболочки (что содержит $0)
dirname
удаляет суффикс без каталога из имени файла.
Следовательно, вы получите полный путь к скрипту, независимо от того, был ли указан путь или нет.
Поскольку realpath не установлен по умолчанию в моей системе Linux, у меня работает следующее:
SCRIPT="$(readlink --canonicalize-existing "$0")"
SCRIPTPATH="$(dirname "$SCRIPT")"
$SCRIPT
будет содержать реальный путь к файлу скрипта и $SCRIPTPATH
реальный путь к каталогу, содержащему скрипт.
Перед использованием этого прочитайте комментарии к этому ответу.
Легко читать? Ниже приводится альтернатива. Игнорирует символические ссылки
#!/bin/bash
currentDir=$(
cd $(dirname "$0")
pwd
)
echo -n "current "
pwd
echo script $currentDir
Просто:
BASEDIR=$(readlink -f $0 | xargs dirname)
Необычные операторы не нужны.
Вы можете попытаться определить следующую переменную:
CWD="$(cd -P -- "$(dirname -- "$0")" && pwd -P)"
Или вы можете попробовать следующую функцию в Bash:
realpath () {
[[ $1 = /* ]] && echo "$1" || echo "$PWD/${1#./}"
}
Эта функция принимает один аргумент. Если аргумент уже имеет абсолютный путь, выведите его как есть, иначе выведите $PWD
переменная + аргумент имени файла (без ./
префикс).
Связанные с:
Отвечая на этот вопрос очень поздно, но я использую:
SCRIPT=$( readlink -m $( type -p $0 )) # 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 "$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
}
Не требует никаких внешних зависимостей, только Bash 4+. Также содержит функции для get_dirname
, get_filename
, get_stemname
а также validate_pathvalidate_realpath
, Он бесплатный, чистый, простой и хорошо документированный, поэтому его можно использовать и в учебных целях, и, без сомнения, его можно улучшить. Попробуйте на разных платформах.
Обновление: после некоторого обзора и тестирования мы заменили вышеуказанную функцию на что-то, что дает тот же результат (без использования dirname, только чистый Bash), но с большей эффективностью:
function get_realpath() {
[[ ! -f "$1" ]] && 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
это дает возможность разрешать символические ссылки на физическую систему. По умолчанию он сохраняет символические ссылки без изменений.
Bourne shell (sh
) совместимый способ:
SCRIPT_HOME=`dirname $0 | while read a; do cd $a && pwd && break; done`
Рассмотрим эту проблему еще раз: есть очень популярное решение, на которое ссылается этот поток, и его происхождение здесь:
DIR="$( cd "$( dirname "${BASH_SOURCE[0]}" )" && pwd )"
Я остался в стороне от этого решения из-за использования dirname - оно может создавать кроссплатформенные трудности, особенно если по соображениям безопасности необходимо заблокировать скрипт. Но как чистая альтернатива Bash, как насчет использования:
DIR="$( cd "$( echo "${BASH_SOURCE[0]%/*}" )" && pwd )"
Будет ли это вариант?
Если мы используем Bash, я считаю, что это наиболее удобный способ, так как он не требует вызовов каких-либо внешних команд:
THIS_PATH="${BASH_SOURCE[0]}";
THIS_DIR=$(dirname $THIS_PATH)
Принятое решение имеет неудобство (для меня), чтобы не быть "исходным":
если вы называете это изsource ../../yourScript
", $0
было бы "bash
"!
Следующая функция (для bash >= 3.0) дает мне правильный путь, однако скрипт может быть вызван (напрямую или через source
(с абсолютным или относительным путем):
(под "правильным путем" я подразумеваю полный абсолютный путь вызываемого скрипта, даже если он вызывается из другого пути, напрямую или с помощью "source
")
#!/bin/bash
echo $0 executed
function bashscriptpath() {
local _sp=$1
local ascript="$0"
local asp="$(dirname $0)"
#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]}
чтобы вернуть реальный сценарий.
Возможно, принятый ответ на следующий вопрос может быть полезным.
Как я могу получить поведение readlink -f GNU на Mac?
Учитывая, что вы просто хотите канонизировать имя, которое вы получаете от объединения $PWD и $0 (при условии, что $ 0 не является абсолютным с самого начала), просто используйте серию регулярных замен по линии abs_dir=${abs_dir//\/.\//\/}
и тому подобное.
Да, я знаю, это выглядит ужасно, но это сработает и будет чистым Bash.
Попробуй это:
cd $(dirname $([ -L $0 ] && readlink -f $0 || echo $0))
Некоторое время я успешно использовал следующий подход (но не в OS X), и он использует только встроенную оболочку и обрабатывает случай 'source foobar.sh', насколько я видел.
Одна проблема с (наскоро сложенным) примером кода ниже состоит в том, что функция использует $PWD, которая может быть или не быть правильной во время вызова функции. Так что это должно быть обработано.
#!/bin/bash
function canonical_path() {
# Handle relative vs absolute path
[ ${1:0:1} == '/' ] && x=$1 || x=$PWD/$1
# 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
Просто, черт возьми, я немного взломал скрипт, который делает вещи чисто текстовыми, чисто в Bash. Я надеюсь, что я поймал все крайние случаи.
Обратите внимание, что ${var//pat/repl}
что я упоминал в другом ответе, не работает, так как вы не можете заставить его заменить только самое короткое возможное совпадение, что является проблемой для замены /foo/../
как например /*/../
возьму все до него, а не только одну запись. И поскольку эти шаблоны на самом деле не являются регулярными выражениями, я не понимаю, как это можно заставить работать. Итак, вот чудесное замысловатое решение, которое я придумала, наслаждайтесь.;)
Кстати, дайте мне знать, если вы найдете какие-либо необработанные крайние случаи.
#!/bin/bash
canonicalize_path() {
local path="$1"
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/"
Еще один способ сделать это:
shopt -s extglob
selfpath=$0
selfdir=${selfpath%%+([!/])}
while [[ -L "$selfpath" ]];do
selfpath=$(readlink "$selfpath")
if [[ ! "$selfpath" =~ ^/ ]];then
selfpath=${selfdir}${selfpath}
fi
selfdir=${selfpath%%+([!/])}
done
echo $selfpath $selfdir
Проще говоря, это то, что работает для меня:
MY_DIR=`dirname $0`
source $MY_DIR/_inc_db.sh