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

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

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

$ ./application

Ответы [ 58 ]

11 голосов
/ 21 апреля 2013

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

  • Абсолютные пути или относительные пути
  • Программные ссылки для файлов и каталогов
  • Вызывается как 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 X добавляет 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"
11 голосов
/ 16 сентября 2011

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

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

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

10 голосов
/ 06 февраля 2012

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

real=$(realpath $(dirname $0))
10 голосов
/ 28 мая 2014

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

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

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

8 голосов
/ 21 декабря 2013

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

#!/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 голосов
/ 28 ноября 2013

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

CWD="$(cd -P -- "$(dirname -- "$0")" && pwd -P)"
Команды

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

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

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

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

Связанный:

7 голосов
/ 24 января 2009

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

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

у меня работает.

7 голосов
/ 09 марта 2018

Подводя итог многим ответам:

    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

6 голосов
/ 14 октября 2008

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

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

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

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

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

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

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

5 голосов
/ 08 октября 2013

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

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

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

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...