Unix shell script выяснит, в каком каталоге находится файл скрипта?
511
По сути, мне нужно запустить сценарий с путями, связанными с расположением файла сценария оболочки, как я могу изменить текущий каталог на тот же каталог, где находится файл сценария?
Это действительно дубликат? Этот вопрос о "сценарии оболочки Unix", другой - о Bash.
Майкл
2
@BoltClock: Этот вопрос был неправильно закрыт. Связанный вопрос о Bash. Этот вопрос касается программирования оболочки Unix. Обратите внимание, что принятые ответы совершенно разные!
Дитрих Эпп
@Dietrich Epp: Ты прав. Похоже, что ответчик выбрал принятый ответ и добавление тега [bash] (вероятно, в ответ на это) привело меня к тому, что я пометил вопрос как дубликат в ответ на флаг.
Это не сработает, если вы вызвали скрипт по символической ссылке в другом каталоге. Чтобы сделать эту работу, вы должны использовать readlinkтакже (см. Ответ
Ала
44
В Баше безопаснее использовать $BASH_SOURCEвместо $0, потому что $0не всегда содержит путь к скрипту вызываются, например, когда «поиск» сценария.
mklement0
2
$BASH_SOURCEспецифичен для Bash, вопрос в целом о скрипте оболочки.
Ха-Дуонг Нгуен
7
@auraham: CUR_PATH=$(pwd)или pwdверните текущий каталог (который не обязательно должен быть родительским каталогом сценариев)!
Андреас Дитрих
5
Я попробовал метод @ mklement0, рекомендованный, используя $BASH_SOURCE, и он возвращает то, что мне нужно. Мой скрипт вызывается из другого скрипта и $0возвращает, .пока $BASH_SOURCEвозвращает правильный подкаталог (в моем случае scripts).
Дэвид Риссато Круз
402
Оригинальный пост содержит решение (игнорируйте ответы, они не добавляют ничего полезного). Интересную работу выполняет упомянутая команда unix readlinkс опцией -f. Работает, когда скрипт вызывается как по абсолютному, так и по относительному пути.
Для bash, sh, ksh:
#!/bin/bash # Absolute path to this script, e.g. /home/user/bin/foo.sh
SCRIPT=$(readlink -f "$0")# Absolute path this script is in, thus /home/user/bin
SCRIPTPATH=$(dirname "$SCRIPT")
echo $SCRIPTPATH
Для tcsh, csh:
#!/bin/tcsh# Absolute path to this script, e.g. /home/user/bin/foo.cshset SCRIPT=`readlink -f "$0"`# Absolute path this script is in, thus /home/user/binset SCRIPTPATH=`dirname "$SCRIPT"`
echo $SCRIPTPATH
Чтобы уточнить комментарий @ Ergwun: -fвообще не поддерживается в OS X (с Lion); там вы можете либо отказаться от -fразрешения не более одного уровня косвенного обращения, например pushd "$(dirname "$(readlink "$BASH_SOURCE" || echo "$BASH_SOURCE")")", либо вы можете запустить свой собственный рекурсивный скрипт, следующий за символической ссылкой, как показано в связанном посте.
mklement0
2
Я до сих пор не понимаю, зачем ОП нужен абсолютный путь. Составление отчетов "." должно работать нормально, если вы хотите получить доступ к файлам, относящимся к пути сценариев, и вы назвали сценарий как ./myscript.sh
Стефан Хаберл
10
@ StefanHaberl Я думаю, что это будет проблемой, если вы запустите скрипт, когда ваш текущий рабочий каталог будет отличаться от местоположения скрипта (например sh /some/other/directory/script.sh), в данном случае .это будет ваш pwd, а не/some/other/directory
Jon z
51
Более ранний комментарий к ответу сказал это, но это легко пропустить среди всех других ответов.
При использовании bash:
echo this file:"$BASH_SOURCE"
echo this dir:"$(dirname "$BASH_SOURCE")"
Только этот работает со сценарием в пути к среде, большинство проголосовавших не работают. благодарю вас!
июля
Вы должны использовать dirname "$BASH_SOURCE"вместо этого для обработки пробелов в $ BASH_SOURCE.
Mygod
1
Более точный способ печати каталога, согласно инструменту ShellCheck, будет выглядеть следующим образом: "$ (dirname" $ {BASH_SOURCE {0}} ")", потому что BASH_SOURCE является массивом, и без нижнего индекса первый элемент берется по умолчанию ,
Адриан М.
1
@AdrianM. Вы хотите скобки, а не фигурные скобки, для индекса:"$(dirname "${BASH_SOURCE[0]}")"
Hashbrown
Плохо для меня .. печатает только точку (т.е. текущий каталог, предположительно)
Этот скрипт должен напечатать каталог, в котором вы находитесь, а затем каталог, в котором находится скрипт. Например, при вызове его /с помощью скрипта /home/mez/он выводит
//home/mez
Помните, что при назначении переменных из выходных данных команды оберните команду $(и )- или вы не получите желаемого результата.
Это не будет работать, когда я вызываю скрипт из текущего каталога.
Эрик Ван
1
@EricWang вы всегда в текущем каталоге.
Ctrl-Alt-Delor
Для меня $ current_dir - это путь, с которого я вызываю скрипт. Однако $ script_dir - это не директория скрипта, а просто точка.
Майкл
31
Если вы используете Bash ....
#!/bin/bash
pushd $(dirname "${0}")>/dev/null
basedir=$(pwd -L)# Use "pwd -P" for the path without links. man bash for more info.
popd >/dev/null
echo "${basedir}"
Вы можете заменить pushd/ popdна cd $(dirname "${0}")и cd -заставить его работать на других оболочках, если они есть pwd -L.
docwhat
почему вы используете здесь pushd и popd?
qodeninja
1
Поэтому мне не нужно хранить исходный каталог в переменной. Это шаблон, который я часто использую в функциях и тому подобное. Он очень хорошо гнездится, и это хорошо.
docwhat
Он все еще хранится в памяти - в переменной - независимо от того, на какую переменную ссылается ваш скрипт или нет. Кроме того, я считаю, что затраты на выполнение pushd и popd намного перевешивают экономию от отсутствия локальной переменной Bash в вашем скрипте, как в циклах ЦП, так и в удобочитаемости.
здесь
20
Как Марко предлагает:
BASEDIR=$(dirname $0)
echo $BASEDIR
Это работает, если вы не выполните скрипт из той же директории, где находится скрипт, и в этом случае вы получите значение '.'
Теперь вы можете использовать переменную current_dir в вашем скрипте для ссылки на каталог скриптов. Однако это может все еще иметь проблему символической ссылки.
DIR="$( cd "$( dirname "${BASH_SOURCE[0]}")" && pwd )"
Одна строка, которая даст вам полное имя каталога скрипта независимо от того, откуда он вызывается.
Чтобы понять, как это работает, вы можете выполнить следующий скрипт:
#!/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 locatedfidone
echo "SOURCE is '$SOURCE'"
RDIR="$( dirname "$SOURCE" )"
DIR="$( cd -P "$( dirname "$SOURCE")" && pwd )"if["$DIR"!="$RDIR"];then
echo "DIR '$RDIR' resolves to '$DIR'"fi
echo "DIR is '$DIR'"
как написано, cd: too many argumentsесли пробелы в пути, и возвращает $PWD. (очевидное исправление, но показывает, сколько на самом деле существует крайних случаев)
Майкл,
1
Был бы upvote. За исключением комментария от @michael, что он терпит неудачу с пробелами в пути ... Есть ли решение для этого?
@Der_Meister - пожалуйста, будьте более конкретны. Или напишите (и, возможно, зашифруйте) электронное письмо Джасану по адресу jasan.tk
Ян Шареник
2
ln -s / home / der / 1 / test / home / der / 2 / test && / home / der / 2 / test => / home / der / 2 (вместо этого следует указать путь к исходному сценарию)
самый надежный способ, не связанный с bash, который я знаю.
Eddygeek
10
Если вы хотите получить фактический каталог скриптов (независимо от того, вызываете ли вы скрипт с использованием символической ссылки или напрямую), попробуйте:
Это работает как на Linux, так и на MacOS. Я не мог видеть никого здесь упоминание оrealpath . Не уверен, есть ли какие-либо недостатки в этом подходе.
на MacOS, вам нужно установить, coreutilsчтобы использовать realpath. Например: brew install coreutils.
ПОЧЕМУ ИСПОЛЬЗУЕТСЯ dirname "$ 0" НА ЭТОМ СОБСТВЕННОМ НЕ РАБОТАЕТ?
имя $ 0 будет работать только в том случае, если пользователь запускает скрипт очень специфическим образом. Мне удалось найти несколько ситуаций, когда этот ответ не удается и происходит сбой сценария.
Прежде всего, давайте разберемся, как работает этот ответ. Он получает каталог скриптов, выполняя
dirname "$0"
$ 0 представляет первую часть команды, вызывающей скрипт (в основном это введенная команда без аргументов:
/some/path/./script argument1 argument2
$ 0 = "/ некоторые / путь /./ сценарий"
dirname в основном находит последний / в строке и обрезает его там. Так что если вы делаете:
dirname /usr/bin/sha256sum
вы получите: / usr / bin
Этот пример хорошо работает, потому что / usr / bin / sha256sum - это правильно отформатированный путь, но
dirname "/some/path/./script"
не будет работать хорошо и даст вам:
BASENAME="/some/path/."#which would crash your script if you try to use it as a path
Допустим, вы находитесь в том же каталоге, что и ваш скрипт, и запускаете его с помощью этой команды
./script
$ 0 в этой ситуации будет ./script и dirname $ 0 даст:
.#or BASEDIR=".", again this will crash your script
С помощью:
sh script
Без ввода полного пути также получим BASEDIR = "."
Использование относительных каталогов:
../some/path/./script
Дает имя $ 0 из:
../some/path/.
Если вы находитесь в каталоге / some и вызываете скрипт таким образом (обратите внимание на отсутствие / в начале, снова относительный путь):
path/./script.sh
Вы получите это значение для dirname $ 0:
path/.
и ./path/./script (другая форма относительного пути) дает:
./path/.
Единственные две ситуации, когда basedir $ 0 будет работать, - это если пользователь использует sh или touch для запуска скрипта, потому что оба приведут к $ 0:
$0=/some/path/script
который даст вам путь, который вы можете использовать с dirname.
РЕШЕНИЕ
Вы должны были бы учесть и обнаружить каждую из вышеупомянутых ситуаций и применить исправление, если оно возникнет:
#!/bin/bash#this script will only work in bash, make sure it's installed on your system.#set to false to not see all the echos
debug=true
if["$debug"= true ];then echo "\$0=$0";fi#The line below detect script's parent directory. $0 is the part of the launch command that doesn't contain the arguments
BASEDIR=$(dirname "$0")#3 situations will cause dirname $0 to fail: #situation1: user launches script while in script dir ( $0=./script)#situation2: different dir but ./ is used to launch script (ex. $0=/path_to/./script)#situation3: different dir but relative path used to launch scriptif["$debug"= true ];then echo 'BASEDIR=$(dirname "$0") gives: '"$BASEDIR";fiif["$BASEDIR"="."];then BASEDIR="$(pwd)";fi# fix for situation1
_B2=${BASEDIR:$((${#BASEDIR}-2))}; B_=${BASEDIR::1}; B_2=${BASEDIR::2}; B_3=${BASEDIR::3}# <- bash onlyif["$_B2"="/."];then BASEDIR=${BASEDIR::$((${#BASEDIR}-1))};fi#fix for situation2 # <- bash onlyif["$B_"!="/"];then#fix for situation3 #<- bash onlyif["$B_2"="./"];then#covers ./relative_path/(./)scriptif["$(pwd)"!="/"];then BASEDIR="$(pwd)/${BASEDIR:2}";else BASEDIR="/${BASEDIR:2}";fielse#covers relative_path/(./)script and ../relative_path/(./)script, using ../relative_path fails if current path is a symbolic linkif["$(pwd)"!="/"];then BASEDIR="$(pwd)/$BASEDIR";else BASEDIR="/$BASEDIR";fififiif["$debug"= true ];then echo "fixed BASEDIR=$BASEDIR";fi
Эта строка содержит информацию о том, где находится сценарий оболочки, не имеет значения, запускали ли вы его или использовали его . Кроме того, он разрешает любые задействованные символические ссылки, если это так:
Так много ответов, все правдоподобно, каждый с за и против и слегка отличающимися целями (которые, вероятно, должны быть указаны для каждого). Вот еще одно решение, которое отвечает основной задаче: быть понятным и работать во всех системах, на всех bash (без предположений о версиях bash readlinkилиpwd опциях ) и разумно делать то, что вы ожидаете (например, разрешение символических ссылок - это интересная проблема, но обычно это не то, что вам действительно нужно), обрабатывать крайние случаи, такие как пробелы в путях и т. д., игнорирует любые ошибки и использует вменяемое значение по умолчанию, если есть какие-либо проблемы.
Каждый компонент хранится в отдельной переменной, которую вы можете использовать индивидуально:
# script path, filename, directory
PROG_PATH=${BASH_SOURCE[0]}# this script's name
PROG_NAME=${PROG_PATH##*/} # basename of script (strip path)
PROG_DIR="$(cd "$(dirname "${PROG_PATH:-$PWD}")" 2>/dev/null 1>&2 && pwd)"
Это может выглядеть уродливо в зависимости от того, как он был вызван и cwd, но должно привести вас туда, куда вам нужно (или вы можете настроить строку, если вам все равно, как она выглядит).
Здесь проблема с выходом из стека overoverflow: она, безусловно, должна выглядеть следующим образом: `pwd`/`dirname $0`но она все равно может потерпеть неудачу в символических ссылках
Ответы:
В Bash вы должны получить то, что вам нужно, вот так:
источник
readlink
также (см. Ответ$BASH_SOURCE
вместо$0
, потому что$0
не всегда содержит путь к скрипту вызываются, например, когда «поиск» сценария.$BASH_SOURCE
специфичен для Bash, вопрос в целом о скрипте оболочки.CUR_PATH=$(pwd)
илиpwd
верните текущий каталог (который не обязательно должен быть родительским каталогом сценариев)!$BASH_SOURCE
, и он возвращает то, что мне нужно. Мой скрипт вызывается из другого скрипта и$0
возвращает,.
пока$BASH_SOURCE
возвращает правильный подкаталог (в моем случаеscripts
).Оригинальный пост содержит решение (игнорируйте ответы, они не добавляют ничего полезного). Интересную работу выполняет упомянутая команда unix
readlink
с опцией-f
. Работает, когда скрипт вызывается как по абсолютному, так и по относительному пути.Для bash, sh, ksh:
Для tcsh, csh:
Смотрите также: https://stackoverflow.com/a/246128/59087
источник
readlink
. Вот почему я рекомендовал использовать pushd / popd (встроенные в bash).-f
Вариантreadlink
делает что - то другое на OS X (Lion) и , возможно , BSD. stackoverflow.com/questions/1055671/…-f
вообще не поддерживается в OS X (с Lion); там вы можете либо отказаться от-f
разрешения не более одного уровня косвенного обращения, напримерpushd "$(dirname "$(readlink "$BASH_SOURCE" || echo "$BASH_SOURCE")")"
, либо вы можете запустить свой собственный рекурсивный скрипт, следующий за символической ссылкой, как показано в связанном посте.sh /some/other/directory/script.sh)
, в данном случае.
это будет ваш pwd, а не/some/other/directory
Более ранний комментарий к ответу сказал это, но это легко пропустить среди всех других ответов.
При использовании bash:
Справочное руководство Bash, 5.2 Переменные Bash
источник
dirname "$BASH_SOURCE"
вместо этого для обработки пробелов в $ BASH_SOURCE."$(dirname "${BASH_SOURCE[0]}")"
Предполагая, что вы используете Bash
Этот скрипт должен напечатать каталог, в котором вы находитесь, а затем каталог, в котором находится скрипт. Например, при вызове его
/
с помощью скрипта/home/mez/
он выводитПомните, что при назначении переменных из выходных данных команды оберните команду
$(
и)
- или вы не получите желаемого результата.источник
Если вы используете Bash ....
источник
pushd
/popd
наcd $(dirname "${0}")
иcd -
заставить его работать на других оболочках, если они естьpwd -L
.Как Марко предлагает:
Это работает, если вы не выполните скрипт из той же директории, где находится скрипт, и в этом случае вы получите значение '.'
Чтобы обойти эту проблему, используйте:
Теперь вы можете использовать переменную current_dir в вашем скрипте для ссылки на каталог скриптов. Однако это может все еще иметь проблему символической ссылки.
источник
Лучший ответ на этот вопрос был дан здесь:
Получение исходного каталога скрипта Bash изнутри
И это:
Одна строка, которая даст вам полное имя каталога скрипта независимо от того, откуда он вызывается.
Чтобы понять, как это работает, вы можете выполнить следующий скрипт:
источник
источник
Давайте сделаем это POSIX oneliner:
Протестировано на многих Bourne-совместимых оболочках, включая BSD.
Насколько я знаю, я являюсь автором и выложил его в общественное достояние. Для получения дополнительной информации см .: https://www.jasan.tk/posts/2017-05-11-posix_shell_dirname_replacement/
источник
cd: too many arguments
если пробелы в пути, и возвращает$PWD
. (очевидное исправление, но показывает, сколько на самом деле существует крайних случаев)источник
Если вы хотите получить фактический каталог скриптов (независимо от того, вызываете ли вы скрипт с использованием символической ссылки или напрямую), попробуйте:
Это работает как на Linux, так и на MacOS. Я не мог видеть никого здесь упоминание о
realpath
. Не уверен, есть ли какие-либо недостатки в этом подходе.на MacOS, вам нужно установить,
coreutils
чтобы использоватьrealpath
. Например:brew install coreutils
.источник
ВВЕДЕНИЕ
Этот ответ исправляет очень испорченный, но шокирующе лучший голос этой ветки (написанный TheMarko):
ПОЧЕМУ ИСПОЛЬЗУЕТСЯ dirname "$ 0" НА ЭТОМ СОБСТВЕННОМ НЕ РАБОТАЕТ?
имя $ 0 будет работать только в том случае, если пользователь запускает скрипт очень специфическим образом. Мне удалось найти несколько ситуаций, когда этот ответ не удается и происходит сбой сценария.
Прежде всего, давайте разберемся, как работает этот ответ. Он получает каталог скриптов, выполняя
$ 0 представляет первую часть команды, вызывающей скрипт (в основном это введенная команда без аргументов:
$ 0 = "/ некоторые / путь /./ сценарий"
dirname в основном находит последний / в строке и обрезает его там. Так что если вы делаете:
вы получите: / usr / bin
Этот пример хорошо работает, потому что / usr / bin / sha256sum - это правильно отформатированный путь, но
не будет работать хорошо и даст вам:
Допустим, вы находитесь в том же каталоге, что и ваш скрипт, и запускаете его с помощью этой команды
$ 0 в этой ситуации будет ./script и dirname $ 0 даст:
С помощью:
Без ввода полного пути также получим BASEDIR = "."
Использование относительных каталогов:
Дает имя $ 0 из:
Если вы находитесь в каталоге / some и вызываете скрипт таким образом (обратите внимание на отсутствие / в начале, снова относительный путь):
Вы получите это значение для dirname $ 0:
и ./path/./script (другая форма относительного пути) дает:
Единственные две ситуации, когда basedir $ 0 будет работать, - это если пользователь использует sh или touch для запуска скрипта, потому что оба приведут к $ 0:
который даст вам путь, который вы можете использовать с dirname.
РЕШЕНИЕ
Вы должны были бы учесть и обнаружить каждую из вышеупомянутых ситуаций и применить исправление, если оно возникнет:
источник
Эта строка содержит информацию о том, где находится сценарий оболочки, не имеет значения, запускали ли вы его или использовали его . Кроме того, он разрешает любые задействованные символические ссылки, если это так:
Кстати, я полагаю, вы используете / bin / bash .
источник
Так много ответов, все правдоподобно, каждый с за и против и слегка отличающимися целями (которые, вероятно, должны быть указаны для каждого). Вот еще одно решение, которое отвечает основной задаче: быть понятным и работать во всех системах, на всех bash (без предположений о версиях bash
readlink
илиpwd
опциях ) и разумно делать то, что вы ожидаете (например, разрешение символических ссылок - это интересная проблема, но обычно это не то, что вам действительно нужно), обрабатывать крайние случаи, такие как пробелы в путях и т. д., игнорирует любые ошибки и использует вменяемое значение по умолчанию, если есть какие-либо проблемы.Каждый компонент хранится в отдельной переменной, которую вы можете использовать индивидуально:
источник
Вдохновленный ответом голубоглазого
источник
Это должно делать свое дело:
Это может выглядеть уродливо в зависимости от того, как он был вызван и cwd, но должно привести вас туда, куда вам нужно (или вы можете настроить строку, если вам все равно, как она выглядит).
источник
`pwd`/`dirname $0`
но она все равно может потерпеть неудачу в символических ссылках