Как получить исходный каталог скрипта Bash из самого скрипта

4957

Как мне получить путь к каталогу, в котором находится скрипт Bash , внутри этого скрипта?

Я хочу использовать скрипт Bash в качестве средства запуска для другого приложения. Я хочу изменить рабочий каталог на тот, где находится скрипт Bash, чтобы я мог работать с файлами в этом каталоге, например так:

$ ./application
Jiaaro
источник
69
Ни одно из текущих решений не работает, если в конце имени каталога есть какие-либо символы новой строки - они будут удалены при подстановке команды. Чтобы обойти это, вы можете добавить не-символ новой строки внутри подстановки команды DIR="$( cd "$( dirname "${BASH_SOURCE[0]}" )" && pwd && echo x)"- и удалить ее без подстановки команды - DIR="${DIR%x}".
10
80
@ jpmc26 Есть две очень распространенные ситуации: аварии и саботаж. Сценарий не должен выходить из строя непредсказуемым образом только потому, что кто-то где-то это сделал mkdir $'\n'.
10
24
Любой, кто позволяет людям саботировать свою систему таким способом, не должен бросать это на bash, чтобы обнаружить такие проблемы ... тем более нанимать людей, способных совершить такую ​​ошибку. Я никогда не имел, в 25 лет использования Баша, видел такого рода вещи случаются в любом месте .... поэтому у нас есть такие вещи , как Perl и методы , такие как проверка меченой (я, вероятно , буду волнистым утверждать , что :)
Osirisgothra
3
@ l0b0 Учтите, что вам нужна такая же защита dirname, и что каталог может начинаться с -(например --help). DIR=$(reldir=$(dirname -- "$0"; echo x); reldir=${reldir%?x}; cd -- "$reldir" && pwd && echo x); DIR=${DIR%?x}, Возможно, это излишне?
Score_Under
56
Я настоятельно рекомендую прочитать этот Bash FAQ по этому вопросу.
Рани Альбег Вайн

Ответы:

6575
#!/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запустили другой каталог перед запуском этого фрагмента, результат может быть неверным!

Кроме того, следите за побочными эффектами $CDPATHgetchas и 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'
Dave Dopson
источник
27
Вы можете соединить этот подход с ответом на user25866 , чтобы прийти к решению , которое работает с source <script>и bash <script>: DIR="$(cd -P "$(dirname "${BASH_SOURCE[0]}")" && pwd)".
Дэн Молдинг
21
Иногда cdпечатает что-то для STDOUT! Например, если у вас $CDPATHесть .. Чтобы покрыть этот случай, используйтеDIR="$( cd "$( dirname "${BASH_SOURCE[0]}" )" > /dev/null && pwd )"
user716468
182
Этот принятый ответ не в порядке, он не работает с символическими ссылками и является слишком сложным. dirname $(readlink -f $0)это правильная команда. См. Gist.github.com/tvlooy/cbfbdb111a4ebad8b93e для теста
tvlooy
167
@tvlooy IMO ваш ответ не совсем в порядке, как есть, потому что он терпит неудачу, когда в пути есть пробел. В отличие от символа новой строки, это не маловероятно или даже необычно. dirname "$(readlink -f "$0")"не добавляет сложности и справедливо более надежно для минимального количества проблем.
Адриан Гюнтер
11
@tvlooy Ваш комментарий не совместим с macOS (или, вероятно, BSD), в то время как принятый ответ. readlink -f $0дает readlink: illegal option -- f.
Александр Юнгберг
876

Используйте 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
матовый б
источник
25
Для переносимости за пределы bash, $ 0 не всегда может быть достаточно. Возможно, вам придется заменить «type -p $ 0», чтобы эта работа работала, если команда была найдена в пути.
Даррон
10
@Darron: вы можете использовать только type -pесли скрипт исполняемый. Это также может открыть тонкую дыру, если скрипт выполняется с использованием bash test2.shи есть другой скрипт с тем же именем, исполняемый где-то еще.
Д.Шоули
90
@Darron: но так как вопрос помечен, bashа в строке хэш-бенга явно упоминается, /bin/bashя бы сказал, что довольно безопасно зависеть от ошибок.
Иоахим Зауэр
34
+1, но проблема с использованием dirname $0заключается в том, что если каталог является текущим каталогом, вы получите .. Это нормально, если вы не собираетесь изменять каталоги в сценарии и ожидать, что вы будете использовать путь, который вы получили, dirname $0как если бы он был абсолютным. Для того, чтобы получить абсолютный путь: pushd `dirname $0` > /dev/null, SCRIPTPATH=`pwd`, popd > /dev/null: pastie.org/1489386 (Но , конечно , есть лучший способ расширить этот путь?)
TJ Crowder
9
@TJ Crowder Я не уверен, что dirname $0это проблема, если вы назначаете ее переменной, а затем используете ее для запуска скрипта, подобного $dir/script.sh; Я предполагаю, что это вариант использования для такого типа вещей в 90% случаев. ./script.shбудет работать нормально.
Matt B
516

Эта 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
phatblat
источник
13
readlinkне будет доступен на некоторых платформах при установке по умолчанию. Старайтесь избегать его использования, если можете
TL
43
будьте осторожны, чтобы процитировать все, чтобы избежать проблем с export SCRIPT_DIR="$(dirname "$(readlink -f "$0")")"
пробелами
13
В OSX Yosemite 10.10.1 -fне распознается как опция readlink. Использование stat -fвместо этого делает работу. Спасибо
cucu8
11
В OSX есть greadlink, который в основном readlinkнам всем знаком. Вот независимая от платформы версия:dir=`greadlink -f ${BASH_SOURCE[0]} || readlink -f ${BASH_SOURCE[0]}`
Роберт
6
Хороший звонок, @ Роберт. К вашему сведению, greadlinkможно легко установить с помощью доморощенного:brew install coreutils
phatblat
184
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

user25866
источник
4
Ницца! Можно сделать короче, заменив «pushd [...] popd / dev / null» на SCRIPT_PATH = readlink -f $(dirname "${VIRTUAL_ENV}");
e-удовлетворительно
5
И вместо того, чтобы использовать pushd ...; не лучше ли использовать $ (cd dirname "${SCRIPT_PATH}"&& pwd)? Но в любом случае отличный сценарий!
ovanes
6
Сценарию опасно cdвыходить из своего текущего каталога в надежде cdвернуться снова позже: у сценария может не быть разрешения изменить каталог обратно в каталог, который был текущим на момент его вызова. (То же самое касается pushd / popd)
Адриан Пронк
7
readlink -fявляется специфичным для GNU. У BSD readlinkтакой опции нет.
Кара Брайтвелл
3
Что со всеми ненужными подоболочками? ([ ... ])менее эффективен [ ... ], и нет никакой выгоды от изоляции, предлагаемой взамен этой производительности.
Чарльз Даффи
110

Короткий ответ:

`dirname $0`

или ( предпочтительно ):

$(dirname "$0")
Fabien
источник
17
Это не сработает, если вы поставите скрипт. "source my / script.sh"
Арунпрасад Раджкумар
Я использую это все время в своих скриптах bash, которые автоматизируют вещи и часто вызывают другие скрипты в том же каталоге. Я никогда не буду использовать sourceих, и их cd $(dirname $0)легко запомнить.
17
16
@vidstige: ${BASH_SOURCE[0]}вместо того, $0чтобы работать сsource my/script.sh
Тимоти Джонс
@TimothyJones, который не будет работать 100% времени, если получен из любой другой оболочки, кроме bash. ${BASH_SOURCE[0]}не является удовлетворительным вообще. ${BASH_SOURCE:-0}намного лучше.
Матье КАРОФФ
106

Вы можете использовать $BASH_SOURCE:

#!/bin/bash

scriptdir=`dirname "$BASH_SOURCE"`

Обратите внимание, что вам нужно использовать, #!/bin/bashа не #!/bin/shпотому, что это расширение Bash.

Mr Shark
источник
14
Когда я делаю ./foo/script, то $(dirname $BASH_SOURCE)есть ./foo.
до
1
@Till, в этом случае мы можем использовать realpathкоманду, чтобы получить полный путь ./foo/script. Так dirname $(realpath ./foo/script) что даст путь сценария.
Пурушотаман Пувай
73

Это должно сделать это:

DIR="$(dirname "$(readlink -f "$0")")"

Это работает с символическими ссылками и пробелами в пути.

Смотрите справочные страницы для dirnameи readlink.

Судя по комментариям, он не работает с Mac OS. Я понятия не имею, почему это так. Какие-либо предложения?

Simon Rigét
источник
6
с вашим решением, вызывая скрипт, как ./script.shпоказано, .вместо полного пути к каталогу
Bruno Negrão Zica
5
Нет опции -f для readlink в MacOS. Используйте statвместо этого. Но тем не менее, это показывает, .если вы находитесь в «этом» реж.
Денис Угроза
2
Вам нужно установить coreutilsс Homebrew и использовать greadlinkдля получения -fопции на MacOS, потому что это * BSD под прикрытием, а не Linux.
dragon788
Вы должны добавить двойные кавычки вокруг правой стороны:DIR="$(dirname "$(readlink -f "$0")")"
Гагелло
60

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`

решает конкретный сценарий в вопросе, я нахожу имеющий абсолютный путь к более полезному в целом.

SpoonMeiser
источник
9
Вы можете сделать все это в одной строке, например: DIRECTORY = $ (cd dirname $0&& pwd)
dogbane
Это не работает, если скрипт использует другой скрипт, и вы хотите знать имя последнего.
reinierpost
52

Я устал приходить на эту страницу снова и снова, чтобы скопировать вставить одну строку в принятый ответ. Проблема в том, что это нелегко понять и запомнить.

Вот простой в запоминании скрипт:

DIR="$(dirname "${BASH_SOURCE[0]}")"  # get the directory name
DIR="$(realpath "${DIR}")"    # resolve its full path if need be
Thamme Gowda
источник
2
Или, более туманно, на одной линии: DIR=$(realpath "$(dirname "${BASH_SOURCE[0]}")")
АРУ
Почему это не принятый ответ? Есть ли какая-либо разница с использованием realpath«вручную» с циклом readlink? Даже на readlinkстранице Note realpath(1) is the preferred command to use for canonicalization functionality.
руководства
1
И, кстати, разве мы не должны подавать заявку realpathраньше dirname, а не после? Если сам файл сценария является символической ссылкой ... Это даст что-то вроде DIR="$(dirname "$(realpath "${BASH_SOURCE[0]}")")". На самом деле очень близко к ответу, предложенному Саймоном.
User9123
@ User9123 Я думаю, что принять, это попытаться быть совместимым со всеми популярными оболочками / дистрибутивами. Более того, в зависимости от того, что вы пытаетесь сделать, в большинстве случаев люди хотят получить каталог, в котором находится символическая ссылка, а не каталог фактического источника.
Ван
37

Я не думаю, что это так просто, как это сделали другие. pwdне работает, так как текущий каталог не обязательно является каталогом со скриптом. $0тоже не всегда есть информация. Рассмотрим следующие три способа вызова скрипта:

./script

/usr/bin/script

script

В первом и третьем способах $0нет полной информации о пути. Во втором и третьем pwdне работает. Единственный способ получить каталог третьим способом - запустить путь и найти файл с правильным соответствием. В основном код должен был бы переделывать то, что делает ОС.

Один из способов сделать то, что вы просите, - просто жестко закодировать данные в /usr/shareкаталоге и сослаться на них по полному пути. В /usr/binлюбом случае данные не должны находиться в каталоге, так что это, вероятно, то, что нужно сделать.

Джим
источник
9
Если вы намерены опровергнуть его комментарий, ДОКАЗАТЬ, что скрипт МОЖЕТ получить доступ к тому месту, где он хранится, с примером кода.
Ричард Дуэрр
34
SCRIPT_DIR=$( cd ${0%/*} && pwd -P )
вечера
источник
Это намного короче выбранного ответа. И, кажется, работает так же хорошо. Это заслуживает 1000 голосов, чтобы люди не упустили это из виду.
Патрик
2
Как многие из предыдущих ответов подробно объяснить, ни , $0ни pwdгарантированно иметь нужную информацию, в зависимости от того, как будет вызван скрипт.
IMSoP
34
$(dirname "$(readlink -f "$BASH_SOURCE")")
тест11
источник
Я предпочитаю, $BASH_SOURCEболее $0, потому что это явно даже для читателей не очень хорошо разбирающихся в Баш. $(dirname -- "$(readlink -f -- "$BASH_SOURCE")")
blobmaster
32

Это получает текущий рабочий каталог в Mac OS X 10.6.6:

DIR=$(cd "$(dirname "$0")"; pwd)
Pubguy
источник
27

Это специфично для Linux, но вы можете использовать:

SELF=$(readlink /proc/$$/fd/255)
Стив Бейкер
источник
1
Это также специфично для bash, но, возможно, поведение bash изменилось? /proc/fd/$$/255кажется, указывает на tty, а не на каталог. Например, в моей текущей оболочке входа в систему все файловые дескрипторы 0, 1, 2 и 255 относятся к /dev/pts/4. В любом случае, в руководстве по bash не упоминается fd 255. Поэтому, вероятно, неразумно зависеть от этого поведения. \
Кит Томпсон,
2
Интерактивная оболочка! = Скрипт. В любом случае realpath ${BASH_SOURCE[0]};, казалось бы, лучший путь.
Стив Бейкер,
23

Вот POSIX-совместимая строка:

SCRIPT_PATH=`dirname "$0"`; SCRIPT_PATH=`eval "cd \"$SCRIPT_PATH\" && pwd"`

# test
echo $SCRIPT_PATH
lamawithonel
источник
4
Я имел успех с этим, когда запускал скрипт сам или с помощью sudo, но не при вызове source ./script.sh
Michael R
И он не работает, когда cdнастроен для печати нового пути.
Аарон Дигулла
18

Я попробовал все это, и никто не работал. Один был очень близко, но имел крошечную ошибку, которая сильно его сломала; они забыли завернуть путь в кавычки.

Также многие люди предполагают, что вы запускаете скрипт из оболочки, поэтому они забывают, когда вы открываете новый скрипт, он по умолчанию у вас дома.

Попробуйте этот каталог для размера:

/var/No one/Thought/About Spaces Being/In a Directory/Name/And Here's your file.text

Это правильно, независимо от того, как и где вы запускаете:

#!/bin/bash
echo "pwd: `pwd`"
echo "\$0: $0"
echo "basename: `basename "$0"`"
echo "dirname: `dirname "$0"`"

Итак, чтобы сделать его действительно полезным, вот как перейти в каталог запущенного скрипта:

cd "`dirname "$0"`"
Майк Бетани
источник
4
Не работает, если скрипт получен из другого скрипта.
reinierpost
Это не работает, если последняя часть $ 0 является символической ссылкой, указывающей на запись другого каталога ( ln -s ../bin64/foo /usr/bin/foo).
Гагелло
17

Вот простой, правильный способ:

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 для установки GNU coreutilsи заменить его на greadlink -f.

  • И, конечно, dirnameполучает родительский каталог пути.

Джеймс Ко
источник
1
greadlink -fк сожалению, не работает эффективно при sourceнаписании сценария на Mac :(
Гейб Копли
17

Самый короткий и самый элегантный способ сделать это:

#!/bin/bash
DIRECTORY=$(cd `dirname $0` && pwd)
echo $DIRECTORY

Это будет работать на всех платформах и очень чисто.

Более подробную информацию можно найти в разделе «В каком каталоге находится этот bash-скрипт? ».

Atul
источник
отличное чистое решение, но это не сработает, если файл имеет символическую ссылку.
Ruuter
16

Я бы использовал что-то вроде этого:

# 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и вызывается без пути, он даст неверный результат.
Notinlist
@ Notinlist Не так. Если скрипт найден через PATH, $0будет содержать абсолютное имя файла. Если скрипт вызывается с относительным или абсолютным именем файла, содержащим /, $0будет содержать это.
Нил Мэйхью
Не будет работать для исходного сценария.
Амит Найду
16

Это небольшой пересмотр решения e-sat, и 3bcdnlklvc04a указал в своем ответе :

SCRIPT_DIR=''
pushd "$(dirname "$(readlink -f "$BASH_SOURCE")")" > /dev/null && {
    SCRIPT_DIR="$PWD"
    popd > /dev/null
}    

Это должно работать во всех перечисленных случаях.

Это предотвратит popdнеудачу pushd, благодаря konsolebox.

Fuwjax
источник
Это отлично работает, чтобы получить «настоящее» имя, а не просто символическую ссылку. Спасибо!
Джей Тейлор
1
ЛучшеSCRIPT_DIR=''; pushd "$(dirname "$(readlink -f "$BASH_SOURCE")")" > /dev/null && { SCRIPT_DIR=$PWD; popd > /dev/null; }
konsolebox
@konsolebox, от чего ты пытаешься защищаться? Я вообще фанат встраивания логических условных выражений, но какую конкретно ошибку вы увидели в pushd? Я бы предпочел найти способ обработать его напрямую, а не возвращать пустой SCRIPT_DIR.
Fuwjax
@Fuwjax Естественная практика, которую следует избегать popdв случаях (даже в редких случаях), где происходит pushdсбой. А в случае pushdнеудачи, как вы думаете, какой должна быть ценность SCRIPT_DIR? Действие может варьироваться в зависимости от того, что может показаться логичным или того, что один пользователь может предпочесть, но, безусловно, делать popdэто неправильно.
konsolebox
Всех этих pushd popdопасностей можно избежать, просто отбросив их и используя вместо них знак cd+, pwdзаключенный в подстановку команд. SCRIPT_DIR=$(...)
Амит Найду
16

Для систем, имеющих GNU coreutils readlink(например, linux):

$(readlink -f "$(dirname "$0")")

Там нет необходимости использовать, BASH_SOURCEкогда $0содержит имя файла сценария.

user1338062
источник
3
если сценарий не был получен. или 'source', в этом случае это все равно будет тот сценарий, из которого он получен, или, если из командной строки, '-bash' (tty login) или 'bash' (вызывается через 'bash -l') или '/ bin / bash '(вызывается как интерактивная оболочка без входа в систему)
osirisgothra
Я добавил вторую пару цитат вокруг dirnameколла. Необходим, если путь к каталогу содержит пробелы.
user1338062
14
#!/bin/sh
PRG="$0"

# need this for relative symlinks
while [ -h "$PRG" ] ; do
   PRG=`readlink "$PRG"`
done

scriptdir=`dirname "$PRG"`
Monkeyboy
источник
Я не проверял это в разных системах. Но это решение, которое работает сразу, по крайней мере, для Ubuntu, для меня!
Натус Дрю
$0не будет работать для исходного сценария
Амит Найду
13

$_Стоит упомянуть в качестве альтернативы $0. Если вы запускаете скрипт из Bash, принятый ответ может быть сокращен до:

DIR="$( dirname "$_" )"

Обратите внимание, что это должно быть первым утверждением в вашем скрипте.

спеша
источник
4
Это ломается, если вы sourceили .сценарий. В этих ситуациях $_будет содержать последний параметр последней команды, которую вы выполнили перед .. $BASH_SOURCEработает каждый раз.
клик
11

Я сравнил многие ответы и дал несколько более компактных решений. Похоже, что они справляются со всеми безумными крайними случаями, возникающими из вашей любимой комбинации:

  • Абсолютные пути или относительные пути
  • Мягкие ссылки на файлы и каталоги
  • Призывание , как 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 Xadd добавляет 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"
billyjmc
источник
Я получаю /dev/pts/30с bash на Ubuntu 14.10 Desktop.
Дан Даскалеску
@DanDascalescu Использование однострочника? Или полный фрагмент кода внизу? И вы кормили его хитрыми путями?
Billyjmc
Одна линия плюс еще одна линия , чтобы echo $resolvedя сохранил его как d, chmod +x d, ./d.
Дан Даскалеску
@DanDascalescu Первая строка в вашем скрипте должна быть#!/bin/bash
billyjmc
10

Попробуйте использовать:

real=$(realpath $(dirname $0))
eeerahul
источник
1
Все, что я хочу знать, - почему этот путь не годится? Это казалось не плохим и правильным для меня. Может ли кто-нибудь объяснить, почему за него проголосовали?
Шоу Я
7
realpath не является стандартной утилитой.
Стив Беннетт
2
В Linux realpath - это стандартная утилита (часть пакета GNU coreutils), но она не является встроенной в bash (то есть функцией, предоставляемой самой bash). Если вы используете Linux, этот метод, вероятно , работать, хотя я бы подставить $0для ${BASH_SOURCE[0]}так что этот способ будет работать в любом месте, в том числе и в функции.
Даг Ричардсон,
3
Порядок действий в этом ответе неправильный. Сначала нужно разрешить символическую ссылку, а затем сделать, dirnameпотому что последняя часть $0может быть символической ссылкой, указывающей на файл, который не находится в том же каталоге, что и сама символическая ссылка. Решение, описанное в этом ответе, просто получает путь к каталогу, в котором хранится символическая ссылка, а не каталог цели. Кроме того, в этом решении отсутствует цитирование. Это не будет работать, если путь содержит специальные символы.
Гагелло
3
dir="$(realpath "$(dirname "${BASH_SOURCE[0]}")")"
Константин Пономаренко,
9

Попробуйте следующее кросс-совместимое решение:

CWD="$(cd -P -- "$(dirname -- "${BASH_SOURCE[0]}")" && pwd -P)"

поскольку такие команды, как realpathили readlinkмогут быть недоступны (в зависимости от операционной системы).

Примечание: в Bash рекомендуется использовать ${BASH_SOURCE[0]}вместо $0, в противном случае путь может сломаться при поиске файла ( source/ .).

В качестве альтернативы вы можете попробовать следующую функцию в bash:

realpath () {
  [[ $1 = /* ]] && echo "$1" || echo "$PWD/${1#./}"
}

Эта функция принимает 1 аргумент. Если аргумент уже имеет абсолютный путь, выведите его как есть, в противном случае выведите $PWDпеременную + аргумент имени файла (без ./префикса).

Связанные с:

кенорб
источник
Пожалуйста, объясните больше о функции realpath.
Крис
1
realpathФункция @Chris принимает 1 аргумент. Если аргумент уже имеет абсолютный путь, выведите его как есть, в противном случае напечатайте $PWD+ имя файла (без ./префикса).
kenorb
Ваше кросс-совместимое решение не работает, когда скрипт имеет символическую ссылку.
Якуб Джирутка
9

Я верю, что у меня есть это. Я опаздываю на вечеринку, но я думаю, что некоторые оценят, что они здесь, если встретят эту тему. Комментарии должны объяснить:

#!/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's 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's "
 printf "own file\n         (no matter where it has been linked or "
 printf "from where it is being called).\n\n"

else pathfull "$@"
fi
Джефф Никсон
источник
8

Вот краткие способы получения информации о скрипте:

Папки и файлы:

    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

User8461
источник
Я думаю, что мой ответ в порядке, потому что трудно найти простое рабочее издание. Здесь вы можете взять код, который вам нравится, например, cd + pwd, dirname + realpath или dirname + readlink. Я не уверен, что все части существуют раньше, и большинство ответов являются сложными и перегруженными. Здесь вы можете узнать код, который вам нравится использовать. По крайней мере, пожалуйста, не удаляйте его, как мне нужно в будущем: D
User8461
8

Это работает в Bash-3.2:

path="$( dirname "$( which "$0" )" )"

Если у вас есть ~/binкаталог в вашем $PATH, у вас есть Aвнутри этого каталога. Это источники сценария ~/bin/lib/B. Вы знаете, где включенный сценарий относительно исходного в libподкаталоге, а не где он находится относительно текущего каталога пользователя.

Это решается следующим (внутри A):

source "$( dirname "$( which "$0" )" )/lib/B"

Неважно, где пользователь или как он вызывает скрипт, это всегда будет работать.

Мэтт Тардифф
источник
3
Точка на whichочень спорно. type, hashИ другие встроенные команды делают то же самое лучше в Баш. whichявляется более переносимым, хотя на самом деле это не то же самое, что whichиспользуется в других оболочках, таких как tcsh, в которых он встроен.
Восстановите Монику, пожалуйста,
"Всегда"? Не за что. whichбудучи внешним инструментом, у вас нет оснований полагать, что он ведет себя идентично родительской оболочке.
Чарльз Даффи
7

Лучшее компактное решение, на мой взгляд, будет:

"$( cd "$( echo "${BASH_SOURCE[0]%/*}" )"; pwd )"

Там нет опоры ни на что, кроме Bash. Использование dirname, readlinkи basenameв конечном итоге привести к проблемам с совместимостью, поэтому они лучше избегать , если это вообще возможно.

AsymLabs
источник
2
Вы , вероятно , следует добавить слэш к этому: "$( cd "$( echo "${BASH_SOURCE[0]%/*}/" )"; pwd )". У вас будут проблемы с корневым каталогом, если вы этого не сделаете. Кроме того, почему вы даже должны использовать эхо?
konsolebox
dirnameи basenamePOSIX стандартизированы, так почему бы не использовать их? Ссылки: dirname,basename
myrdd
Одной из причин может быть предотвращение двух дополнительных форков процессов и прикрепление к встроенным компонентам оболочки.
Амит Найду