Получить исходный каталог скрипта Bash из самого скрипта - PullRequest
4414 голосов
/ 13 сентября 2008

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

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

$ ./application

Ответы [ 58 ]

5865 голосов
/ 29 октября 2008
#!/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 gotchas и побочные эффекты вывода stderr, если пользователь умно переопределил cd для перенаправления вывода на stderr (включая escape-последовательности, например, при вызове update_terminal_cwd >&2 on 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'
767 голосов
/ 13 сентября 2008

Использование 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
420 голосов
/ 27 сентября 2009

Команда 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
175 голосов
/ 07 октября 2008
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" или оператором . (точка).
  • когда arg $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

102 голосов
/ 03 декабря 2008

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

`dirname $0`

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

$(dirname "$0")
97 голосов
/ 13 сентября 2008

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

#!/bin/bash

scriptdir=`dirname "$BASH_SOURCE"`

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

62 голосов
/ 13 февраля 2016

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

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

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

Редактировать:

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

55 голосов
/ 15 сентября 2008

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`

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

33 голосов
/ 13 сентября 2008

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

./script

/usr/bin/script

script

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

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

32 голосов
/ 07 октября 2010
SCRIPT_DIR=$( cd ${0%/*} && pwd -P )
Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...