Получение исходного каталога скрипта Bash изнутри

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

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

$ ./application

30 ответов


DIR="$( cd "$( dirname "${BASH_SOURCE[0]}" )" >/dev/null && pwd )"

является полезным однострочным, который даст вам полное имя каталога скрипта независимо от того, откуда он вызывается.

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

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 && 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 && pwd )"

этот последний будет работать с любой комбинацией псевдонимы, source, bash -c, симлинки, так далее.

остерегайтесь: если вы cd в другой каталог перед запуском этого фрагмента, результат может быть неправильным! Кроме того, следите за $CDPATH gotchas.

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

#!/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" )" && 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'

использовать dirname "":

#!/bin/bash
echo "The script you are running has basename `basename ""`, dirname `dirname ""`"
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

команда dirname является самой простой, просто разбирая путь до имени файла с переменной $0 (имя скрипта):

dirname ""

, а как Мэтт б указал, возвращаемый путь отличается в зависимости от того, как вызывается скрипт. pwd не выполняет эту работу, потому что это говорит вам только о текущем каталоге, а не о том, в каком каталоге находится скрипт. Кроме того, если выполняется символическая ссылка на скрипт ,вы получите (возможно, относительную) путь к тому, где находится ссылка, а не фактический скрипт.

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

dirname "$(readlink -f "")"

readlink разрешит путь скрипта к абсолютному пути от корня файловой системы. Таким образом, любые пути, содержащие одиночные или двойные точки, Тильды и/или символические ссылки, будут разрешены до полного пути.

вот сценарий, демонстрирующий каждый из них, whatdir.sh:

#!/bin/bash
echo "pwd: `pwd`"
echo "$0: "
echo "basename: `basename `"
echo "dirname: `dirname `"
echo "dirname/readlink: $(dirname $(readlink -f ))"

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

>>>$ ./whatdir.sh 
pwd: /Users/phatblat
: ./whatdir.sh
basename: whatdir.sh
dirname: .
dirname/readlink: /Users/phatblat

снова, но используя полный путь к скрипту:

>>>$ /Users/phatblat/whatdir.sh 
pwd: /Users/phatblat
: /Users/phatblat/whatdir.sh
basename: whatdir.sh
dirname: /Users/phatblat
dirname/readlink: /Users/phatblat

теперь изменение каталогов:

>>>$ cd /tmp
>>>$ ~/whatdir.sh 
pwd: /tmp
: /Users/phatblat/whatdir.sh
basename: whatdir.sh
dirname: /Users/phatblat
dirname/readlink: /Users/phatblat

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

>>>$ ln -s ~/whatdir.sh whatdirlink.sh
>>>$ ./whatdirlink.sh 
pwd: /tmp
: ./whatdirlink.sh
basename: whatdirlink.sh
dirname: .
dirname/readlink: /Users/phatblat

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

работает для всех версий,в том числе

  • при вызове через мягкую ссылку multple depth,
  • когда файл
  • когда скрипт вызывается командой "source" ака . (точка) оператор.
  • когда arg изменяется от абонента.
  • "./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 год. Все права защищены. Никаких гарантий. Вы были warned.
http://www.gnu.org/licenses/gpl-2.0.txt
http://creativecommons.org/licenses/by-sa/3.0/
18eedfe1c99df68dc94d4a94712a71aaa8e1e9e36cacf421b9463dd2bbaa02906d0d6656


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

`dirname `

или (желательно):

$(dirname "")

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

#!/bin/bash

scriptdir=`dirname "$BASH_SOURCE"`

обратите внимание, что вы должны использовать #!/ bin / bash, а не #!/bin / sh с его расширением bash


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

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

работает с символическими ссылками и пробелами в path. Видеть страницах dirname и более ранних версий.

изменить:

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


pwd можно использовать для поиска текущего рабочего каталога и dirname найти каталог конкретного файла (команда, которая была запущена, составляет , так что dirname должен дать вам каталог текущего скрипта).

, dirname дает точно часть каталога имени файла, которая, скорее всего, будет относительно текущего рабочего каталога. Если вашему скрипту по какой-то причине нужно изменить каталог, то вывод из dirname становиться бессмысленным.

Я предлагаю следующее:

#!/bin/bash

reldir=`dirname `
cd $reldir
directory=`pwd`

echo "Directory is $directory"

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

поскольку скрипт будет запущен в отдельном экземпляре bash, нет необходимости восстанавливать рабочий каталог после этого, но если вы по какой-то причине хотите вернуться в свой скрипт, вы можете легко назначить значение pwd в переменную перед изменением каталога для дальнейшего использования.

хотя просто

cd `dirname `

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


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

./script

/usr/bin/script

script

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

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


SCRIPT_DIR=$( cd ${0%/*} && pwd -P )

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

DIR=$(cd "$(dirname "")"; pwd)

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

SELF=$(readlink /proc/$$/fd/255)

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

вот POSIX совместимый однострочный:

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

# test
echo $SCRIPT_PATH

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

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

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

/ var / No one / Thought/About Spaces Being/in a Directory/Name / и вот ваш файл.текст

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

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

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

cd "`dirname ""`"

надеюсь, что это поможет


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

# retrieve the full pathname of the called script
scriptPath=$(which )

# 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

небольшое изменение решения e-satis и 3bcdnlklvc04a указано в ответ

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

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

EDIT: предотвращение popd после неудачного pushd, благодаря konsolebox


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

actual_path=$(readlink -f "${BASH_SOURCE[0]}")
script_dir=$(dirname "$actual_path")

объяснение:

  • ${BASH_SOURCE[0]} - полный путь к скрипту. Значение этого будет правильным даже тогда, когда скрипт находится в исходном состоянии, например source <(echo 'echo ') печать Баш, при замене его на ${BASH_SOURCE[0]} будет напечатан полный путь к скрипту. (Конечно, это предполагает, что вы в порядке, принимая зависимость от Bash.)

  • readlink -f - Рекурсивно разрешает любые символические ссылки в указанном пути. Это расширение GNU и недоступно (например) в системах BSD. Если вы используете Mac, вы можете использовать Homebrew для установки GNU coreutils и замените это на greadlink -f.

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


#!/bin/sh
PRG=""

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

scriptdir=`dirname "$PRG"`

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

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

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


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

  • абсолютные пути или относительные пути
  • файл и каталог ссылок
  • вызов как script, bash script, bash -c script, source script или . script
  • пробелы, вкладки, новые строки, unicode и т. д. в каталогах и/или именем
  • имена файлов, начинающиеся с дефиса

если вы работаете с Linux, кажется, что с помощью proc handle-лучшее решение для поиска полностью разрешенного источника текущего запущенного скрипта (в интерактивном сеансе ссылка указывает на соответствующий /dev/pts/X):

resolved="$(readlink /proc/$$/fd/255 && echo X)" && resolved="${resolved%$'\nX'}"

в этом есть немного уродства, но исправление компактно и легко понять. Мы не используем только примитивы bash, но я в порядке с этим потому что readlink значительно упрощает задачу. The echo X добавляет X до конца строки переменной, так что любые конечные пробелы в имени файла не съедаются, а подстановка параметров ${VAR%X} в конце строки избавляется от X. Потому что readlink добавляет свою собственную новую строку (которая обычно была бы съедена в замене команды, если бы не наш предыдущий обман), мы должны избавиться от этого тоже. Это легче всего сделать с помощью $'' схема цитирования, которая позволяет использовать escape-последовательности, такие как \n для представления новых строк (это также, как вы можете легко сделать хитро именованные каталоги и файлы).

выше должно охватывать ваши потребности в поиске текущего сценария на Linux, но если у вас нет proc файловая система в вашем распоряжении, или если вы пытаетесь найти полностью разрешенный путь к какому-либо другому файлу, возможно, вы найдете приведенный ниже код полезным. Это только небольшая модификация от выше один-лайнер. Если вы играете со странным каталогом / именами файлов, проверьте вывод с помощью обоих ls и readlink информативно, как ls выведет "упрощенные" пути, подставляя ? для таких вещей, как newlines.

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"

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

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

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


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

real=$(realpath $(dirname ))

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

#!/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 ""
## specificies that the  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 "".

if [ -z "$argv" ]; then scriptname="$(pathfull "")"

# Yay ANSI l33t codes! Fancy.
 printf "\n3[3mfrom/as: 3[4m3[0m\n\n3[1mUSAGE:3[0m   "
 printf "3[4m$scriptname3[24m [ link | file | dir ]\n\n         "
 printf "Recursive readlink for the authoritative file, symlink after "
 printf "symlink.\n\n\n         3[4m$scriptname3[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

хм, если в пути basename & dirname просто не собираются его вырезать и ходить по пути трудно (что, если родитель не экспортировал путь!). Однако оболочка должна иметь открытый дескриптор для своего скрипта, а в bash дескриптор #255.

SELF=`readlink /proc/$$/fd/255`

работает для меня.


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

CWD="$(cd -P -- "$(dirname -- "")" && pwd -P)"

as realpath или readlink команды не всегда доступен (в зависимости от операционной системы) и ${BASH_SOURCE[0]} доступно только в оболочке bash.

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

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

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

по теме:


это работает в bash-3.2:

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

вот пример его использования:

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

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

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

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


это единственный способ, который я нашел, чтобы сказать надежно:

SCRIPT_DIR=$(dirname $(cd "$(dirname "$BASH_SOURCE")"; pwd))

ни один из них не работал для сценария bash, запущенного Finder в OS X - I, в конечном итоге использовал:

SCRIPT_LOC="`ps -p $$ | sed /PID/d | sed s:.*/Network/:/Network/: |
sed s:.*/Volumes/:/Volumes/:`"

Не очень, но он получает работу.


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

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

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