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

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

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

$ ./application

person Community    schedule 12.09.2008    source источник
comment
Ни одно из текущих решений не работает, если в конце имени каталога есть символы новой строки - они будут удалены при подстановке команды. Чтобы обойти это, вы можете добавить символ, не являющийся новой строкой, внутри подстановки команды - DIR="$( cd "$( dirname "${BASH_SOURCE[0]}" )" && pwd && echo x)" - и удалить его без подстановки команды - DIR="${DIR%x}".   -  person l0b0    schedule 24.09.2012
comment
@ jpmc26 Есть две очень распространенные ситуации: аварии и саботаж. Сценарий не должен непредсказуемо давать сбой только потому, что кто-то где-то сделал mkdir $'\n'.   -  person l0b0    schedule 28.03.2013
comment
Обычно вызывающий сценарий может легко предоставить ввод для этого сценария, который ему нужен, например, / home / userx / bin / my_script / home / userx / bin, поскольку вызывающий знает путь, который он использует для доступа к сценарию. Поскольку Linux использует модель inode из Unix (отличная функция), часто бывает сложно найти ответ автоматически. И даже при использовании описанных ниже приемов это может быть непросто. Например, сценарий может располагаться более чем по одному пути (ln без -s). Если хрупкость, которую добавляют эти соображения, не вызывает беспокойства, ответы с использованием BASH_SOURCE и т.п. делают свою работу.   -  person ash    schedule 22.08.2013
comment
ash: Используя жесткие ссылки, вы получите один из правильных ответов - путь, который использовался для вызова. Если жесткие ссылки представляют собой проблему, не используйте их (они, вероятно, в любом случае плохая идея для скриптов в большинстве случаев).   -  person Blaisorblade    schedule 27.09.2013
comment
@ l0b0 Я использовал ваше решение на RHEL 6.5x64, но моя переменная $ DIR все равно заканчивается новой строкой после нее. Есть идеи, почему это может быть?   -  person Ryan    schedule 01.07.2014
comment
@ l0b0 Я разместил вопрос: stackoverflow.com/questions/24537068/   -  person Ryan    schedule 02.07.2014
comment
stackoverflow.com/questions/19313056/   -  person gavenkoa    schedule 07.01.2015
comment
любой, кто позволяет людям саботировать свою систему таким образом, не должен оставлять ее на произвол судьбы для обнаружения таких проблем ... не говоря уже о том, чтобы нанимать людей, способных совершить такую ​​ошибку. За 25 лет использования bash я ни разу не видел, чтобы подобное происходило где-нибудь ... вот почему у нас есть такие вещи, как perl, и такие практики, как проверка taint (я, вероятно, буду возмущен, если скажу это :)   -  person osirisgothra    schedule 05.02.2015
comment
@ l0b0 Учтите, что вам потребуется такая же защита на dirname, и что каталог может начинаться с - (например, --help). DIR=$(reldir=$(dirname -- "$0"; echo x); reldir=${reldir%?x}; cd -- "$reldir" && pwd && echo x); DIR=${DIR%?x}. Может, это перебор?   -  person Score_Under    schedule 28.04.2015
comment
Ребята, посмотрите мой простой скрипт здесь: github.com/lingtalfi/printScriptDir / blob / master /, обрабатывает символические ссылки, относительные пути, абсолютные пути, протестировано на linux и macOsX.   -  person ling    schedule 03.10.2015
comment
Я настоятельно рекомендую прочитать этот Bash FAQ по этой теме.   -  person Rany Albeg Wein    schedule 30.01.2016
comment
@Blaisorblade пишет: Если жесткие ссылки - проблема, не используйте их. Это невозможно. Каждая запись в каталоге - это жесткая ссылка. Каждый путь к файлу - это жесткая ссылка. Без жестких ссылок вы не можете получить доступ к файлу. Проблема с этим вопросом в формулировке: файлы не хранятся в каталогах. Каталоги - это не что иное, как список жестких ссылок.   -  person William Pursell    schedule 27.08.2016
comment
@WilliamPursell Затем прочтите это, чтобы не добавлять в файл вторую жесткую ссылку, что, на мой взгляд, понятно в контексте и является стандартным злоупотреблением языком. В FAQ по Bash перечислены другие и более важные проблемы, хотя он, кажется, больше склонен не одобрять это, чем описывать, когда это безопасно.   -  person Blaisorblade    schedule 28.08.2016
comment
Есть ли причина, по которой никто не сказал realpath "$0"?   -  person Nonny Moose    schedule 10.06.2017
comment
Имя файла см. На странице Как узнать имя файла сценария в сценарии Bash?   -  person kenorb    schedule 19.07.2017
comment
@Score_Under Просто не используйте dirname: просто используйте reldir=${0%/*} напрямую   -  person mtraceur    schedule 19.10.2018
comment
@ l0b0 почему это не ответ?   -  person Rainb    schedule 15.04.2021
comment
@Rainb Я понятия не имел, что мои комментарии были популярны :) Я разместил его как ответ. Редакторы, если вы это видите, мне следует удалить исходные комментарии?   -  person l0b0    schedule 15.04.2021
comment
Надежное и удобное для Mac / Linux решение: HERE = $ (cd $ (dirname $ BASH_SOURCE); cd -P $ (dirname $ (readlink $ BASH_SOURCE || echo.)); Pwd) - объяснение (с более простыми вариантами) здесь : binaryphile .com / bash / 2020/01/12 /   -  person Binary Phile    schedule 18.04.2021


Ответы (71)


#!/bin/bash

SCRIPT_DIR="$( cd "$( dirname "${BASH_SOURCE[0]}" )" &> /dev/null && 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 ошибок и побочные эффекты вывода 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'
person Community    schedule 29.10.2008
comment
Это также не работает, если оболочка вызывается с явной командой bash, как вам нужно будет сделать, если вы хотите вызвать с параметром оболочки, как в sh -x myscript.sh - person Jim Garrison; 09.08.2011
comment
Для последнего обновления попробуйте следующее: DIR = $ (cd -P $ (dirname $ 0) && pwd) ---, что даст вам абсолютный разыменованный путь, то есть разрешит все символические ссылки. - person Dave Dopson; 16.08.2011
comment
Также не работает, если вы вызываете скрипты через символическую ссылку; он возвращает каталог, в котором находится символическая ссылка. Но это может быть даже желательно. - person eold; 19.08.2011
comment
Вы можете объединить этот подход с ответом user25866, чтобы прийти к решению, которое работает с source <script> и bash <script>: DIR="$(cd -P "$(dirname "${BASH_SOURCE[0]}")" && pwd)". - person Dan Moulding; 19.10.2011
comment
позвольте мне попробовать это еще раз ... Цикл while прерывается, если какая-либо символическая ссылка в цепочке является относительной символической ссылкой. Рассмотрим следующую настройку: / bin / bar - ›/ etc / alternatives / bar -› / usr / lib / bar-bsd - ›bsdbar. Вам нужно изменить цикл while на while [-h $ SOURCE]; сделать LAST_SOURCE = $ SOURCE; ИСТОЧНИК = $ (ссылка для чтения $ ИСТОЧНИК); если [$ {SOURCE: 0: 1}! = /]; тогда ИСТОЧНИК = $ (имя каталога $ LAST_SOURCE) / $ ИСТОЧНИК; fi; сделано - person beltorak; 15.12.2011
comment
При присвоении переменной выходу подстановки команды или выходу ссылки на переменную нет никакой причины использовать кавычки. Доказательство: запустите x="my name is ryran", а затем a=$x или a=${x/ryran/bob jones} или a=$(echo $x) - person rsaw; 08.03.2012
comment
Это здорово, но я столкнулся с очень странной проблемой. У меня определена забавная командная строка, и при использовании этого точного метода я получал непечатаемые символы в моем DIR. Я исправил это, используя следующее: DIR = $ (cd $ (dirname $ {BASH_SOURCE [0]}) ›/ dev / null 2› & 1 && pwd) - person fileoffset; 26.03.2012
comment
Чтобы справиться с относительной символической ссылкой, я обнаружил, что мне нужен DIR="$( cd "$( dirname "${BASH_SOURCE[0]}" )" && cd -P "$( dirname "$SOURCE" )" && pwd )" - person Joseph Wright; 15.04.2012
comment
@ryran: да, но имейте в виду, что нельзя ожидать, что local a=$x или export a=$x будут работать одинаково. Я не уверен насчет a=$x foo, где foo - это некоторая команда или встроенная функция, но я подозреваю, что вы также не можете переносимо полагаться на поведение присваивания, которое вы там описываете. - person dubiousjim; 31.05.2012
comment
@dubiousjim: Хм. Что вы имеете в виду портативно ожидать? Я не понимаю, как у вас могут возникнуть проблемы с любым из них - локальным / экспортным, что угодно. Все это работает во всех версиях BASH (и здесь мы говорим только о BASH). Что касается a=$x foo, где foo - это какая-то команда или встроенная функция ... Я не уверен, что вы пытаетесь там сказать. - person rsaw; 01.06.2012
comment
@ryran: Под переносимостью я имел в виду в других оболочках. Конечно, это обсуждение специфично для bash, но стоит знать, какие идиомы используются, которые могут неожиданно прерваться в dash, ash и т. Д. Легко вспомнить, что [[ ]] не переносится; более удивительно, что local a=$x без кавычек вокруг $x тоже. Я только что проверил FreeBSD sh (разновидность ясеня): здесь local a=$x присваивает a только первое слово расширения $x; аналогично с export. Но a=$x ./foo (временно) назначит все содержимое расширения $x файлу. - person dubiousjim; 01.06.2012
comment
@dubiousjim: Я слышу вас, и вы правы, что хорошо знать. Мне все равно, потому что я никогда не кодирую сценарии оболочки ни на чем, кроме bash, поскольку практически каждый дистрибутив Linux поставляется с установленным gnu bash. (Я не имею в виду оболочку по умолчанию; я просто имею в виду установленную и доступную для сценариев bash.) - person rsaw; 02.06.2012
comment
Краткая версия не работает в cygwin, если путь похож на C: \ .... Вывод cd - : No such file or directoryabc, где abc - конец имени каталога, содержащего скрипт. Хотя вторая версия работает. - person Qwertie; 21.06.2012
comment
Не переходите cd в другой каталог перед этим кодом, иначе это будет неправильно. - person weynhamz; 25.12.2012
comment
Обратите внимание, что BASH_SOURCE был добавлен для целей отладки в версии bash-3.0-alpha. Так что, если вы работаете с устаревшей системой, это не сработает. - person JeffCharter; 12.01.2013
comment
Иногда cd что-то выводит в STDOUT! Например, если ваш $CDPATH имеет .. Чтобы охватить этот случай, используйте DIR="$( cd "$( dirname "${BASH_SOURCE[0]}" )" > /dev/null && pwd )" - person user716468; 03.02.2013
comment
Обратите внимание, что dirname $0 appriach не будет работать с только что запущенным сеансом оболочки bash. (Конечно, это не случай OP) Попробуйте на только что запущенном терминале. Напротив, подход BASH_SOURCE работает хорошо. - person eonil; 03.04.2013
comment
@fileoffset: Вероятно, у вас что-то установлено для CDPATH. Если эта переменная установлена ​​в вашей среде, команда cd отобразит новый каталог в stdout. Любой переносимый сценарий ksh или bash, использующий cd, должен либо перенаправить свой вывод, либо должен префикс команды cd с помощью CDPATH =. - person Chris Quenelle; 17.04.2013
comment
Всегда ли BASH_SOURCE [0] то же самое, что и $ 0? Если это правда, то почему бы не использовать 0 долларов? Это проще, портативнее и идиоматичнее. BASH_SOURCE полезен для обработки как список для поиска стека вызовов. В этом случае достаточно 0 долларов. - person Chris Quenelle; 17.04.2013
comment
@DaveDopson: Я предлагаю добавить >/dev/null после cd, потому что приведенное выше ломается по-разному, когда cd что-то выводит на стандартный вывод. - person Aaron Digulla; 04.11.2013
comment
По моему опыту, нет необходимости добавлять кавычки вокруг $() выражений. Таким образом: FILES=$(ls -1) и FILES="$(ls -1)" абсолютно одинаковы. - person kevinarpe; 24.11.2013
comment
Глупая ошибка, но сначала я не понял, почему $ {BASH_SOURCE [0]} не работает, пока не понял, что использовал #! / Bin / sh вместо #! / Bin / bash. - person Zitrax; 17.02.2014
comment
Почему бы не использовать следующую команду? DIR=$(dirname -- "$(readlink -f -- "${BASH_SOURCE[0]}")") - person x-yuri; 29.04.2014
comment
Согласно документам ss64, pwd принимает аргумент -P. В описании указано, что аргумент -P гарантирует, что путь не содержит символических ссылок. Можно ли использовать этот аргумент для перехода по символическим ссылкам вместо цикла? - person jpmc26; 14.08.2014
comment
@ x-yuri - FYI, readlink недоступен в текущей форме в OSX, поэтому этот метод будет лучшим, если вы пытаетесь написать полукросс-платформенный скрипт в Linux и OSX с помощью Bash. - person slm; 02.09.2014
comment
К сожалению, это не будет работать с ~ / .bashrc, потому что переменная $ DIR оценивается только один раз во время входа в систему, а не постоянно динамически. :-( - person lpapp; 20.10.2014
comment
@lpapp Если вы пытаетесь сослаться на каталог ~/.bashrc, вы это уже знаете! Это ~. ;) Обычно я не люблю предполагать, но в данном случае это довольно безопасное предположение, поскольку это происходит при входе в систему, а bash ищет только в этом конкретном месте. - person jpmc26; 03.12.2014
comment
Это решение не работает для сценариев autoenv .env (или аналогичных сценариев, которые выполняются, когда вы cd, потому что они будут вызывать себя рекурсивно), почему бы не просто DIR = $ (dirname $ {BASH_SOURCE [0]}) - person Pete; 06.05.2015
comment
Этот принятый ответ неверен, он не работает с символическими ссылками и слишком сложен. dirname $(readlink -f $0) - правильная команда. См. Тестовый пример gist.github.com/tvlooy/cbfbdb111a4ebad8b93e - person tvlooy; 09.06.2015
comment
@tvlooy IMO ваш ответ не совсем в порядке, потому что он не работает, когда на пути есть пробел. В отличие от символа новой строки, это не маловероятно и даже не редко. dirname "$(readlink -f "$0")" не добавляет сложности и более надежен для минимального количества проблем. - person Adrian Günter; 29.10.2015
comment
Это решение не работает в сценарии, вызываемом при запуске приложения путем нажатия значка в Ubuntu, сценарий упоминается в атрибуте Exec. Этот же скрипт безупречно работает в терминале. В настоящее время я занимаюсь расследованием. Я не хочу устанавливать атрибут Path в файле рабочего стола, поскольку он не зависит от дистрибутива. - person gouessej; 29.10.2015
comment
В качестве примечания: по соглашению переменные среды (PATH, EDITOR, SHELL, ...) и внутренние переменные оболочки (BASH_VERSION, RANDOM, ...) полностью пишутся с заглавной буквы. Все остальные имена переменных должны быть в нижнем регистре. Поскольку имена переменных чувствительны к регистру, это соглашение позволяет избежать случайного переопределения переменных среды и внутренних переменных. - person Rany Albeg Wein; 16.01.2016
comment
По соглашению, переменные среды (PATH, EDITOR, SHELL, ...) и внутренние переменные оболочки (BASH_VERSION, RANDOM, ...) полностью пишутся с заглавной буквы. Все остальные имена переменных должны быть в нижнем регистре. Поскольку имена переменных чувствительны к регистру, это соглашение позволяет избежать случайного переопределения переменных среды и внутренних переменных. - person Rany Albeg Wein; 30.01.2016
comment
Чтобы работать с символическими ссылками, используйте pwd -P - person Jesin; 06.09.2016
comment
@ switch87 ваше решение работает не во всех случаях. Пожалуйста, не редактируйте ответ, если вы не уверены на 100%. - person smancill; 20.03.2017
comment
Одиночный лайнер предполагает, что «cd» молчит. В противном случае вы получите мусор. Лучше: DIR = $ (cd $ (dirname $ {BASH_SOURCE [0]}) ›› / dev / null && pwd) - person Andreas; 27.04.2017
comment
@tvlooy ваш комментарий несовместим с macOS (или, возможно, с BSD в целом), хотя принятый ответ совместим. readlink -f $0 дает readlink: illegal option -- f. - person Alexander Ljungberg; 09.05.2017
comment
Это есть во всех BSD. OSX этого не делает. Установите coreutils и используйте greadline - person tvlooy; 09.05.2017
comment
Симпатичный формат: DIR=$(cd $(dirname ${BASH_SOURCE[0]}) && pwd) - person zored; 17.07.2017
comment
Похоже, ни у кого нет проблем с изменением рабочего каталога, не вернув его на место. :( РЕДАКТИРОВАТЬ: Мой плохой $() создает новую оболочку, не так ли? - person tishma; 09.09.2017
comment
Хотя оба -h и -L выбрали test широкую двойную поддержку, предпочтение отдается -L. См. Эти вопросы о stackoverflow: ref 1 и ref 2. Однако ваш пробег может варьироваться в зависимости от того, кто отказывается следовать стандартам. - person Novice C; 27.09.2017
comment
Итак, @tvlooy, если я работаю на macOS, ваше решение - установить пакет программного обеспечения, чтобы я мог использовать ваш ответ? Это намного хуже, чем небольшая сложность принятого ответа (при условии отсутствия символических ссылок). - person Ethan Reesor; 08.11.2017
comment
Почему бы не использовать realpath (как в других ответах) вместо цикла while? - person Vladimir Panteleev; 13.12.2017
comment
Вероятно, это не надежно, для ссылки и отсутствия ссылки я делаю следующее: LINK = readlink "${BASH_SOURCE[0]}" if [$? -экв 0]; затем echo Link обнаружен экспорт SCRIPT_DIR = $ (cd $ (dirname $ LINK) && pwd) else echo Нет экспорта ссылки SCRIPT_DIR = $ (cd $ (dirname $ {BASH_SOURCE [0]}) && pwd) fi - person Anton Krug; 21.08.2018
comment
Примечание: не забывайте #!/bin/bash вверху. #!/bin/sh не сработает. Вы получите Bad substitution ошибку. - person Gabriel Staples; 02.12.2018
comment
Однострочный код не работает в OSX El Capitan из-за сеансов Bash, которые загрязняют вывод Saving session... ...copying shared history... ...saving history...truncating history files... ...completed. Это работает: read DIR < <(cd "$( dirname "${BASH_SOURCE[0]}" )" >/dev/null 2>&1 && pwd) - person Rich Kuzsma; 09.05.2019
comment
Я всегда меняю форматирование этой строки на: DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" >/dev/null 2>&1 && pwd)" - person wieczorek1990; 28.06.2019
comment
Можно ли обернуть это функцией или его нужно копировать / вставлять повсюду? - person Alexander; 13.07.2019
comment
Я только что обнаружил, что приведенное выше не работает, когда скрипт выполняется напрямую, а не из источника: stackoverflow.com/questions/27880087/ - person Andrei LED; 27.08.2019
comment
@Andrei LED, похоже, это работает для случая ошибки bash: $ (cd $ (dirname $ {BASH_SOURCE [@]}) & ›/ dev / null && pwd) - person Neil McGill; 24.09.2019
comment
почему $ {BASH_SOURCE [0]} вместо более простого $ BASH_SOURCE - person Nam G VU; 22.11.2019
comment
Во многих случаях вместо всего этого будет работать realpath; пример: PATH_TO_SCRIPT=$(realpath $0). Я нашел примерно realpath отсюда: stackoverflow.com/a/14892459/4561887. Я также разместил это здесь как новый ответ: stackoverflow.com/questions/59895/. - person Gabriel Staples; 10.02.2020
comment
dirname выполняет дополнительный процесс. рассмотрите вместо этого $ {BASH_SOURCE [0]% / *}. - person Serg; 06.04.2020
comment
Компактное решение tvlooy работало для всех тестовых случаев пыток, которые я пробовал. Более сложное решение Дэйва Допсона также работает для всех тестовых случаев пыток, которые я пробовал. - person MAXdB; 03.07.2020
comment
Случаи пыток включали запуск в качестве службы и использование нескольких вариантов относительных символических ссылок с разными именами, вызывающих относительные символические ссылки с разными именами, в конце концов, вызывающих сценарий относительно. Мне также нужно фактическое имя сценария, а не имя первой символической ссылки. TRUESCRIPT="$(dirname $(readlink -f $0))/$(basename $(readlink -f $0))" - person MAXdB; 03.07.2020
comment
@MAXdB, вы тестировали на osx? Проблема была в osx. - person fuzzyTew; 05.05.2021
comment
Просто пришел сюда, чтобы сказать, что это мой самый часто используемый ответ о переполнении стека прямо здесь. - person Mark Pauley; 19.05.2021
comment
Как указано в пункте «Осторожно», использование компакт-диска таким образом может в некоторых случаях привести к проблемам и является излишним для проблемы. Решение $ (dirname ...)) o даже лучше $ (dirname $ (readlink -f ...)) мне кажется более прямым, простым и лучшим. - person Luis Vazquez; 28.06.2021

Используйте 1_:

#!/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
person Community    schedule 12.09.2008
comment
Для переносимости за пределами bash не всегда может быть достаточно 0 долларов. Вам может потребоваться заменить тип -p $ 0, чтобы это сработало, если команда была найдена на пути. - person Darron; 24.10.2008
comment
@ Даррон: вы можете использовать type -p только в том случае, если скрипт исполняемый. Это также может открыть небольшую дыру, если сценарий выполняется с использованием bash test2.sh, и где-то в другом месте есть другой сценарий с таким же именем, исполняемый. - person D.Shawley; 05.02.2010
comment
@Darron: но поскольку вопрос помечен как bash, а в строке hash-bang явно упоминается /bin/bash, я бы сказал, что зависеть от башизмов довольно безопасно. - person Joachim Sauer; 11.06.2010
comment
+1, но проблема с использованием dirname $0 заключается в том, что если каталог является текущим каталогом, вы получите .. Это нормально, если вы не собираетесь менять каталоги в скрипте и рассчитываете использовать путь, полученный от dirname $0, как если бы он был абсолютным. Чтобы получить абсолютный путь: pushd `dirname $0` > /dev/null, SCRIPTPATH=`pwd`, popd > /dev/null: pastie.org/1489386 (но конечно < / i> есть способ лучше расширить этот путь?) - person T.J. Crowder; 23.01.2011
comment
@ T.J. Crowder Я не уверен, что dirname $0 представляет собой проблему, если вы назначите его переменной, а затем используете ее для запуска сценария типа $dir/script.sh; Я предполагаю, что это вариант использования такого рода вещей в 90% случаев. ./script.sh будет работать нормально. - person matt b; 24.01.2011
comment
@matt b: Как я уже сказал, это нормально, если ваш сценарий, использующий его, не изменяет текущий каталог. Если ваш сценарий работает, то вы пытаетесь запустить ./script.sh, а script.sh должен находиться в том же каталоге, что и оригинал, он потерпит неудачу, потому что вас там больше нет. См. Также: stackoverflow.com/questions/4774054/ - person T.J. Crowder; 24.01.2011
comment
$0 дает неверный результат в исходных скриптах (включая bashrc) - person ivan_pozdeev; 11.01.2012
comment
Круто, это не разрешает символические ссылки, а это именно то, что я искал. - person Ehtesh Choudhury; 14.03.2013
comment
@ T.J. Crowder ›Но есть ли лучший способ расширить этот путь? Да, вы можете использовать $ (readlink -f $ 0), чтобы получить полный путь в канонической форме со всеми разрешенными символическими ссылками. Решает массу проблем. - person Volodymyr M. Lisivka; 09.07.2013
comment
ПРИМЕЧАНИЕ $ {BASH_SOURCE [0]} отличается от $ 0, когда вы включаете один файл bash в другой, используя. или источник. - person Du Song; 21.07.2013
comment
DIR = $ (cd $ (имя каталога $ {BASH_SOURCE [0]}) && pwd); [-h $ DIR / $ 0] && DIR = readlink -n "$DIR/$0" - person Heavy Gray; 19.11.2013
comment
Даже с bash $0 не всегда достаточно. Скрипт может быть источником другого скрипта! - person reinierpost; 28.03.2014
comment
@ T.J.Crowder @ VolodymyrM.Lisivka вы также можете использовать $(realpath "$0"), но обратите внимание, что ни ссылка для чтения, ни путь чтения не будут работать в Mac OSX. stackoverflow.com/questions/3572030/ - person joeytwiddle; 27.01.2015
comment
@mattb В этой версии произошел сбой с именами каталогов, которые содержат специальные символы и двойные кавычки. например mkdir $ (printf \ 1 \ 2 \ 3 \ 4 \ 5 \ 6 \ 7 \ 10 \ 11 \ 12 \ 13 \ 14 \ 15 \ 16 \ 17 \ 20 \ 21 \ 22 \ 23 \ 24 \ 25 \ 26 \ 27 \ 30 \ 31 \ 32 \ 33 \ 34 \ 35 \ 36 \ 37 \ 40 \ 41 \ 42 \ 43 \ 44 \ 45 \ 46 \ 47testdir) - person Jay jargot; 31.03.2016
comment
Чтобы это работало в Mac OS, вероятно, из-за пробелов в пути, мне пришлось заключить аргумент в кавычки, например: `dirname "$0"`. - person Demis; 01.06.2016
comment
@NecipAllef, который работает только в том случае, если скрипт находится в том же каталоге, что и вы / вызывающий, если вы вызовете /directory/test.sh в /, он выведет я на //test.sh - person Muskovets; 10.07.2020
comment
Должен сказать, раздражает, насколько это полно ловушек ... проблема в $0 - в Windows это всегда имя сценария, так что это очень просто: %~dp0 - person Paul; 26.08.2020

Команда 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
person Community    schedule 26.09.2009
comment
readlink не будет доступен на некоторых платформах при установке по умолчанию. Старайтесь избегать его использования, если можете - person T.L; 11.01.2012
comment
будьте осторожны, заключайте все в кавычки, чтобы избежать проблем с пробелами: export SCRIPT_DIR="$(dirname "$(readlink -f "$0")")" - person Catskul; 17.09.2013
comment
В OSX Yosemite 10.10.1 -f не распознается как опция для readlink. Использование stat -f вместо этого делает свою работу. Спасибо - person cucu8; 26.11.2014
comment
В OSX есть greadlink, который в основном знаком readlink. Вот версия, независимая от платформы: dir=`greadlink -f ${BASH_SOURCE[0]} || readlink -f ${BASH_SOURCE[0]}` - person robert; 14.01.2016
comment
Хороший звонок, @robert. К вашему сведению, greadlink можно легко установить через homebrew: brew install coreutils - person phatblat; 16.01.2016
comment
Обратите внимание, что $0 не работает, если файл взят из источника. Вместо имени скрипта вы получите -bash. - person svvac; 30.06.2016
comment
В OSX вы можете использовать realpath или grealpath, что даст вам разрешенный путь от корня файловой системы. Пример: cd /usr/bin; realpath ls /usr/bin/ls - person Dave Stern; 25.01.2017
comment
@ T.L readlink является частью пакета coreutils, который содержит базовые утилиты GNU. Понятия не имею, почему он не будет доступен в системе на основе GNU. Я думаю, мы можем смело предположить, что OP использует GNU, поскольку вопрос касается GNU Bash? - person Adam Burley; 24.01.2018
comment
ИМХО, это наиболее точный ответ. Если у вас нет readlink, можно также использовать realpath. При необходимости используйте только канонический вывод = ›readlink -f - person LeoRado; 26.04.2021
comment
Команда readlink находится в пакетах coreutils, которые являются частью любого стандартного дистрибутива Linux, поэтому я думаю, что dirname $(readlink -f "$0") - более общее и простое решение, которое не зависит от того, как был выполнен скрипт (абсолютные и относительные пути) - person Luis Vazquez; 28.06.2021

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 задается полным путем, как бы он ни назывался.

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

person Community    schedule 07.10.2008
comment
Отлично! Можно сделать короче, заменив pushd [...] popd / dev / null на SCRIPT_PATH = readlink -f $(dirname "${VIRTUAL_ENV}"); - person e-satis; 29.11.2009
comment
Это, безусловно, самая стабильная версия, которую я когда-либо видел. Спасибо! - person Tomer Gabel; 26.01.2010
comment
Не следует ли переместить двоеточие в строке 1 во вторую строку между операторами if и then? - person ovanes; 18.08.2010
comment
И вместо использования pushd ...; не лучше ли использовать $ (cd dirname "${SCRIPT_PATH}" && pwd)? Но все равно отличный сценарий! - person ovanes; 18.08.2010
comment
Просто обратите внимание на мой вариант использования: я сохранил сценарий как ~/getpath; а затем сделал псевдоним bash: echo "alias getpath='"'GP="getpath" ; cp ~/$GP ./$GP ; source ./$GP ; rm ./$GP'"'" >> ~/.bashrc ... и теперь я могу делать это в оболочке или в скрипте: getpath ; echo $SCRIPT_PATH. Спасибо за сценарий - ура! - person sdaau; 06.08.2011
comment
Жаль, что я пропустил свое временное окно редактирования - на самом деле, мой комментарий псевдонима выше не работает в скрипте, даже с shopt -s expand_aliases (см. Псевдонимы) - person sdaau; 06.08.2011
comment
Вы можете объединить этот подход с ответом ddopson, чтобы прийти к простому однострочному решению: DIR="$(cd -P "$(dirname "${BASH_SOURCE[0]}")" && pwd)". - person Dan Moulding; 19.10.2011
comment
Для сценария опасно cd выйти из текущего каталога в надежде на cd снова вернуться к нему позже: сценарий может не иметь разрешения на изменение каталога обратно в каталог, который был текущим, когда он был вызван. (То же самое и с pushd / popd) - person Adrian Pronk; 06.11.2012
comment
readlink -f зависит от GNU. BSD readlink не имеет такой возможности. - person Kara Brightwell; 03.06.2014
comment
Что со всеми ненужными подоболочками? ([ ... ]) менее эффективен, чем [ ... ], и здесь не используется изоляция, предлагаемая взамен этого снижения производительности. - person Charles Duffy; 09.06.2014
comment
В приведенном выше списке рассмотренных дел (Works for all versions) есть один элемент == when the file it. Может ли кто-нибудь надлежащим образом завершить этот фрагмент предложения? - person TomRoche; 01.11.2017
comment
безусловно, лучший - person Vivick; 09.11.2017
comment
Что с длинным хешем внизу? Это какая-то подпись? Что это нам говорит? - person mwfearnley; 29.03.2019
comment
На самом деле, почему вообще такое копилефт? Требует ли автор (user25866) указания авторства, несмотря на то, что ответом является вики сообщества? Я не думаю, что SO - хорошее место, чтобы сбросить небольшой фрагмент, а затем прикрепить к нему лицензию. Для защиты прав собственности и аутентификации изменений было бы намного лучше Github Gist. - person mwfearnley; 29.03.2019
comment
Нужен ли блок if? Условие while такое же, и while будет пропущено, если условие ложно в первый раз. - person Jacktose; 26.01.2021

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

#!/bin/bash

scriptdir=`dirname "$BASH_SOURCE"`

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

person Community    schedule 12.09.2008
comment
Когда я делаю ./foo/script, тогда $(dirname $BASH_SOURCE) становится ./foo. - person Till; 25.10.2010
comment
@Till, в этом случае мы можем использовать команду realpath, чтобы получить полный путь к ./foo/script. Таким образом, dirname $(realpath ./foo/script) предоставит путь к скрипту. - person purushothaman poovai; 17.12.2019

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

`dirname $0`

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

$(dirname "$0")
person Community    schedule 03.12.2008
comment
Это не сработает, если вы создадите сценарий. источник my / script.sh - person Arunprasad Rajkumar; 05.02.2014
comment
Я все время использую это в своих сценариях bash, которые автоматизируют работу и часто вызывают другие сценарии в том же каталоге. Я бы никогда не использовал source на них, и cd $(dirname $0) легко запомнить. - person kqw; 03.01.2017
comment
@vidstige: ${BASH_SOURCE[0]} вместо $0 будет работать с source my/script.sh - person Timothy Jones; 27.09.2017
comment
@TimothyJones, который выйдет из строя в 100% случаев, если получен из любой другой оболочки, кроме bash. ${BASH_SOURCE[0]} совершенно не удовлетворительна. ${BASH_SOURCE:-0} намного лучше. - person Mathieu CAROFF; 13.11.2018

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

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

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

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

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

person Community    schedule 12.02.2016
comment
с вашим решением, вызывая сценарий, например ./script.sh, показывает . вместо полного пути к каталогу - person Bruno Negrão Zica; 14.06.2016
comment
В MacOS нет опции -f для ссылки для чтения. Вместо этого используйте stat. Но все же он показывает ., если вы находитесь в каталоге 'this'. - person Denis The Menace; 21.11.2016
comment
Вам необходимо установить coreutils из Homebrew и использовать greadlink, чтобы получить параметр -f в MacOS, потому что это * BSD, а не Linux. - person dragon788; 22.04.2019
comment
Вы должны заключить в двойные кавычки всю правую часть: DIR="$(dirname "$(readlink -f "$0")")" - person hagello; 17.03.2020

Вот простой для запоминания сценарий:

DIR="$(dirname "${BASH_SOURCE[0]}")"  # Get the directory name
DIR="$(realpath "${DIR}")"    # Resolve its full path if need be
person Community    schedule 07.11.2018
comment
Или, что более неясно, в одной строке: DIR=$(realpath "$(dirname "${BASH_SOURCE[0]}")") - person agc; 04.04.2019
comment
Почему это не принятый ответ? Есть ли разница между использованием realpath и ручным разрешением с использованием цикла readlink? Даже readlink страница руководства говорит Note realpath(1) is the preferred command to use for canonicalization functionality. - person User9123; 25.03.2020
comment
И, кстати, не следует ли применять realpath до dirname, а не после? Если сам файл сценария представляет собой символическую ссылку ... Это даст что-то вроде DIR="$(dirname "$(realpath "${BASH_SOURCE[0]}")")". На самом деле очень близко к ответу, предложенному Саймоном. - person User9123; 25.03.2020
comment
@ User9123 Я думаю, что вариант accept - попытаться быть совместимым со всеми популярными оболочками / дистрибутивами. Более того, в зависимости от того, что вы пытаетесь сделать, в большинстве случаев люди хотят получить каталог, в котором расположена символическая ссылка, а не каталог фактического источника. - person Wang; 24.05.2020
comment
Единственная причина - отсутствие coreutils на Mac. Я использую SCRIPT=$(realpath "${BASH_SOURCE[0]}") + DIR=$(dirname "$SCRIPT"). - person puchu; 29.08.2020

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`

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

person Community    schedule 15.09.2008
comment
Вы можете сделать все это в одной строке, например: DIRECTORY = $ (cd dirname $0 && pwd) - person dogbane; 29.10.2008
comment
Это не сработает, если скрипт является источником другого скрипта, и вы хотите узнать его имя. - person reinierpost; 28.03.2014

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

./script

/usr/bin/script

script

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

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

person Community    schedule 13.09.2008
comment
Если вы намерены опровергнуть его комментарий, ДОКАЖИТЕ, что сценарий МОЖЕТ получить доступ к тому месту, где он хранится, с помощью примера кода. - person Richard Duerr; 18.11.2015

Это получает текущий рабочий каталог в Mac OS X v10.6.6 (Snow Leopard):

DIR=$(cd "$(dirname "$0")"; pwd)
person Community    schedule 30.12.2010

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

SELF=$(readlink /proc/$$/fd/255)
person Community    schedule 16.09.2008
comment
Это также специфично для bash, но, возможно, поведение bash изменилось? /proc/fd/$$/255, похоже, указывает на tty, а не на каталог. Например, в моей текущей оболочке входа в систему все файловые дескрипторы 0, 1, 2 и 255 относятся к /dev/pts/4. В любом случае в руководстве по bash не упоминается fd 255, поэтому, вероятно, неразумно полагаться на такое поведение. \ - person Keith Thompson; 29.03.2015
comment
Интерактивная оболочка! = Скрипт. В любом случае realpath ${BASH_SOURCE[0]}; кажется лучшим выходом. - person Steve Baker; 06.04.2015

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

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

# test
echo $SCRIPT_PATH
person Community    schedule 06.03.2013
comment
Я добился успеха при запуске скрипта отдельно или с помощью sudo, но не при вызове source ./script.sh - person Michael R; 18.04.2013
comment
И это не удается, когда cd настроен на печать нового имени пути. - person Aaron Digulla; 04.11.2013

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

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

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

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

person Community    schedule 20.06.2019
comment
отличное чистое решение, но это не сработает, если файл имеет символическую ссылку. - person ruuter; 26.09.2019

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

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

Примерьте размер этого каталога:

/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"`"
person Community    schedule 09.09.2010
comment
Не работает, если сценарий получен из другого сценария. - person reinierpost; 28.03.2014
comment
Это не работает, если последняя часть $ 0 является символической ссылкой, указывающей на запись другого каталога (ln -s ../bin64/foo /usr/bin/foo). - person hagello; 17.03.2020

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

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 получает родительский каталог пути.

person Community    schedule 19.02.2016
comment
greadlink -f к сожалению, не работает эффективно при source запуске сценария на Mac :( - person Gabe Kopley; 01.05.2019

Это небольшая переработка решения e-satis и 3bcdnlklvc04a, указанного в их ответ:

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

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

Это предотвратит popd после неудачного pushd. Спасибо консольбоксу.

person Community    schedule 13.04.2010
comment
Это отлично работает, чтобы получить настоящее имя каталога, а не просто имя символической ссылки. Спасибо! - person Jay Taylor; 24.06.2010
comment
Лучше SCRIPT_DIR=''; pushd "$(dirname "$(readlink -f "$BASH_SOURCE")")" > /dev/null && { SCRIPT_DIR=$PWD; popd > /dev/null; } - person konsolebox; 03.07.2014
comment
@konsolebox, от чего вы пытаетесь защититься? Я вообще поклонник встраивания логических условных выражений, но какую конкретную ошибку вы видели в pushd? Я бы предпочел найти способ обработать его напрямую, а не возвращать пустой SCRIPT_DIR. - person Fuwjax; 19.01.2015
comment
@Fuwjax Естественная практика, позволяющая избегать выполнения popd в случаях (даже в редких случаях), когда pushd терпит неудачу. И в случае отказа pushd, каким, по вашему мнению, должно быть значение SCRIPT_DIR? Действие может варьироваться в зависимости от того, что может показаться логичным или что может предпочесть один пользователь, но, безусловно, делать popd неправильно. - person konsolebox; 20.01.2015
comment
Всех этих pushd popd опасностей можно было избежать, просто отбросив их и вместо этого используя cd + pwd, заключенные в подстановку команд. SCRIPT_DIR=$(...) - person Amit Naidu; 27.05.2020

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

# 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
person Community    schedule 21.11.2012
comment
Это настоящий! Работает и с простым sh! Проблема с простыми решениями на основе dirname "$0": если сценарий находится в $PATH и вызывается без пути, они дадут неправильный результат. - person Notinlist; 18.11.2014
comment
@Notinlist Не совсем так. Если сценарий найден через PATH, $0 будет содержать абсолютное имя файла. Если сценарий вызывается с относительным или абсолютным именем файла, содержащим /, $0 будет содержать это. - person Neil Mayhew; 04.02.2016

Для систем с GNU coreutils readlink (например, Linux):

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

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

person Community    schedule 28.05.2014
comment
если сценарий не был получен с помощью. или 'source', и в этом случае это будет тот сценарий, из которого он был получен, или, если из командной строки, '-bash' (tty login) или 'bash' (вызывается через 'bash -l') или '/ bin / bash '(вызывается как интерактивная оболочка без входа в систему) - person osirisgothra; 05.02.2015
comment
Я добавил вторую пару кавычек вокруг dirname звонка. Требуется, если путь к каталогу содержит пробелы. - person user1338062; 13.05.2018

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

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

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

person Community    schedule 16.09.2011
comment
Он сломается, если вы source или . скрипт. В таких ситуациях $_ будет содержать последний параметр последней выполненной вами команды перед .. $BASH_SOURCE работает каждый раз. - person clacke; 31.01.2014
comment
Это похоже на Perl! Совпадение? - person Peter Mortensen; 20.01.2021

Это короткие способы получить информацию о скрипте:

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

    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

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

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

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

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

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

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

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

person Community    schedule 14.10.2008
comment
Пункт по which очень спорный. type, hash и другие встроенные команды лучше выполняют то же самое в bash. which в некотором роде более портативен, хотя на самом деле это не тот which, который используется в других оболочках, таких как tcsh, в которых он встроен. - person Reinstate Monica Please; 14.01.2014
comment
Всегда? Нисколько. which поскольку это внешний инструмент, у вас нет оснований полагать, что он ведет себя идентично родительской оболочке. - person Charles Duffy; 09.06.2014

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

real=$(realpath $(dirname $0))
person Community    schedule 06.02.2012
comment
Все, что я хочу знать, это почему этот способ не хорош? Мне это показалось неплохим и правильным. Может ли кто-нибудь объяснить, почему он отвергнут? - person Shou Ya; 28.08.2012
comment
realpath не является стандартной утилитой. - person Steve Bennett; 13.05.2013
comment
В Linux realpath - это стандартная утилита (часть пакета GNU coreutils), но не встроенная в bash (т.е. функция, предоставляемая самим bash). Если вы используете Linux, этот метод, вероятно, будет работать, хотя я бы заменил $0 на ${BASH_SOURCE[0]}, чтобы этот метод работал где угодно, в том числе в функции. - person Doug Richardson; 18.07.2014
comment
Порядок действий в этом ответе неправильный. Вам нужно сначала разрешить символическую ссылку, затем сделать dirname, потому что последняя часть $0 может быть символической ссылкой, которая указывает на файл, который находится не в том же каталоге, что и символическая ссылка сам. Решение, описанное в этом ответе, просто получает путь к каталогу, в котором хранится символическая ссылка, а не каталог цели. Кроме того, в этом решении отсутствует цитирование. Это не сработает, если путь содержит специальные символы. - person hagello; 14.04.2015
comment
Это решение просто получает путь к каталогу, в котором хранится символическая ссылка, и это то, что я действительно хочу. PS: цитаты еще надо добавить. - person Kostiantyn Ponomarenko; 11.03.2019

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

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

Если вы работаете из 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"
person Community    schedule 21.04.2013
comment
Я получаю /dev/pts/30 с bash на рабочем столе Ubuntu 14.10. - person Dan Dascalescu; 15.08.2015
comment
@DanDascalescu Используете однострочник? Или полный фрагмент кода внизу? И вы кормили его какими-нибудь хитрыми путями? - person billyjmc; 19.08.2015
comment
Одна строка плюс другая строка до echo $resolved, я сохранил ее как d, chmod +x d, ./d. - person Dan Dascalescu; 20.08.2015
comment
@DanDascalescu Первая строка в вашем скрипте должна быть #!/bin/bash - person billyjmc; 23.08.2015

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

#!/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
person Community    schedule 21.12.2013

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

Во многих случаях все, что вам нужно получить, - это полный путь к только что вызванному сценарию. Это легко сделать с помощью realpath. Обратите внимание, что realpath является частью GNU coreutils. Если он у вас еще не установлен (по умолчанию в Ubuntu), вы можете установить его с помощью sudo apt update && sudo apt install coreutils.

get_script_path.sh:

#!/bin/bash

FULL_PATH_TO_SCRIPT="$(realpath "$0")"

# You can then also get the full path to the directory, and the base
# filename, like this:
SCRIPT_DIRECTORY="$(dirname "$FULL_PATH_TO_SCRIPT")"
SCRIPT_FILENAME="$(basename "$FULL_PATH_TO_SCRIPT")"

# Now print it all out
echo "FULL_PATH_TO_SCRIPT = \"$FULL_PATH_TO_SCRIPT\""
echo "SCRIPT_DIRECTORY    = \"$SCRIPT_DIRECTORY\""
echo "SCRIPT_FILENAME     = \"$SCRIPT_FILENAME\""

Пример вывода:

~/GS/dev/eRCaGuy_hello_world/bash$ ./get_script_path.sh 
FULL_PATH_TO_SCRIPT = "/home/gabriel/GS/dev/eRCaGuy_hello_world/bash/get_script_path.sh"
SCRIPT_DIRECTORY    = "/home/gabriel/GS/dev/eRCaGuy_hello_world/bash"
SCRIPT_FILENAME     = "get_script_path.sh"

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

Приведенный выше код теперь является частью моего репозитория eRCaGuy_hello_world в этом файле здесь: bash / get_script_path.sh.

Использованная литература:

  1. Как получить абсолютный путь, заданный относительным
person Gabriel Staples    schedule 10.02.2020

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

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

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

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

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

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

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

Связанный:

person Community    schedule 28.11.2013
comment
Пожалуйста, объясните больше о функции realpath. - person Chris; 27.03.2015
comment
Функция @Chris realpath принимает 1 аргумент. Если аргумент уже имеет абсолютный путь, выведите его как есть, иначе выведите $PWD + filename (без префикса ./). - person kenorb; 27.03.2015
comment
Ваше кросс-совместимое решение не работает, если на скрипт есть символическая ссылка. - person Jakub Jirutka; 08.09.2015

Хм, если в пути basename и _ 2_ просто не собираются его сокращать, и идти по пути сложно (что, если родительский элемент не экспортировал PATH ?!) .

Однако оболочка должна иметь открытый дескриптор своего скрипта, а в Bash дескриптор # 255.

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

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

person Community    schedule 24.01.2009
comment
Я получаю /dev/pts/30 с bash на рабочем столе Ubuntu 14.10 вместо фактического каталога, из которого я запускаю скрипт. - person Dan Dascalescu; 15.08.2015

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

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

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

person Community    schedule 08.10.2013
comment
Вам, вероятно, следует добавить к этому косую черту: "$( cd "$( echo "${BASH_SOURCE[0]%/*}/" )"; pwd )". Если вы этого не сделаете, у вас возникнут проблемы с корневым каталогом. И почему вам вообще нужно использовать эхо? - person konsolebox; 02.07.2014
comment
dirname и basename стандартизированы POSIX, так почему бы их не использовать? Ссылки: dirname, _ 4_ - person myrdd; 29.10.2018
comment
Одной из причин может быть предотвращение двух дополнительных веток процессов и использование встроенных команд оболочки. - person Amit Naidu; 27.05.2020
comment
Можете называть меня пуристом - у меня есть несколько похожая версия, в которой не используются подоболочки или разветвление: BIN=${0/#[!\/]/"$PWD/${0:0:1}"}; DIR=${BIN%/*}. Сначала замените все, что не начинается с / (т.е. относительный путь), на $PWD/ (мы также должны добавить первый символ, который мы сопоставили при подстановке). Тогда получите каталог. Наконец, вы можете cd $DIR, если хотите, или использовать его в относительных путях. - person Thomas Guyot-Sionnest; 23.12.2020

Вы можете сделать это, просто объединив имя сценария ($0) с realpath и / или _ 3_. Это работает для Bash и Shell.

#!/usr/bin/env bash

RELATIVE_PATH="${0}"
RELATIVE_DIR_PATH="$(dirname "${0}")"
FULL_DIR_PATH="$(realpath "${0}" | xargs dirname)"
FULL_PATH="$(realpath "${0}")"

echo "RELATIVE_PATH->${RELATIVE_PATH}<-"
echo "RELATIVE_DIR_PATH->${RELATIVE_DIR_PATH}<-"
echo "FULL_DIR_PATH->${FULL_DIR_PATH}<-"
echo "FULL_PATH->${FULL_PATH}<-"

Результат будет примерно таким:

# RELATIVE_PATH->./bin/startup.sh<-
# RELATIVE_DIR_PATH->./bin<-
# FULL_DIR_PATH->/opt/my_app/bin<-
# FULL_PATH->/opt/my_app/bin/startup.sh<-

$ 0 - это имя самого скрипта

4.4. Специальные типы переменных

Пример: LozanoMatheus / get_script_paths.sh

person Community    schedule 26.08.2019

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

thisScriptPath=`realpath $0`
thisDirPath=`dirname $thisScriptPath`
echo $thisDirPath
person Community    schedule 10.04.2017
comment
Обратите внимание, что нет необходимости прибегать к эхо. При простом вызове dirname будет напечатано имя каталога. И в этом случае вызов echo без правильного заключения переменной в кавычки потенциально приведет к другому выводу, так как это приведет к сжатию пробелов. В общем, чище просто написать dirname "$(realpath "$0")" - person William Pursell; 08.08.2017
comment
Вы правы, что это можно сжать в одну строку. Разбивка на отдельные переменные предназначена для иллюстративных целей / самодокументирования. Линия эха присутствует как p.o.c. - person BuvinJ; 08.08.2017
comment
Я почти уверен, что да (если я правильно помню). Обратите внимание, что разные дистрибутивы nix и контексты, такие как вы (и скажем, Linux, Mac или Cygwin ... bash vs dash ...), обрабатывают все это несколько по-разному. Следовательно, в этой теме ТАК много ответов! Вам нужно будет протестировать свой вариант использования, чтобы подтвердить. Хотелось бы, чтобы на этот простой вопрос был надежный кроссплатформенный / контекстный ответ! - person BuvinJ; 19.04.2019

Ни один из этих других ответов не работал для сценария Bash, запущенного Finder в OS X. В итоге я использовал:

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

Это некрасиво, но выполняет свою работу.

person Community    schedule 22.09.2010

Используйте комбинацию ссылки для чтения, чтобы канонизировать имя (с бонусом в виде повторного перехода к его источник, если это символическая ссылка) и dirname для извлечения имени каталога:

script="`readlink -f "${BASH_SOURCE[0]}"`"
dir="`dirname "$script"`"
person Community    schedule 13.10.2010

Я нашел единственный способ достоверно сказать:

SCRIPT_DIR=$(dirname $(cd "$(dirname "$BASH_SOURCE")"; pwd))
person Community    schedule 14.04.2009
comment
это дает мне каталог с удаленной последней записью, то есть путь к контейнеру контейнера сценария. - person tim; 28.03.2015

Обычно я использую:

dirname $(which $BASH_SOURCE)
person Community    schedule 17.05.2019

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

dir="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd && echo x)"
dir="${dir%x}"

Это защищает от двух очень распространенных ситуаций: несчастных случаев и саботажа. Сценарий не должен непредсказуемо давать сбой только потому, что кто-то где-то сделал mkdir $'\n'.

person Community    schedule 15.04.2021

Верхний ответ работает не во всех случаях ...

Поскольку у меня были проблемы с BASH_SOURCE с включенным подходом cd на некоторых очень свежих, а также на менее свежих версиях Ubuntu 16.04 (Xenial Xerus) при вызове сценария оболочки с помощью sh my_script.sh я попробовал кое-что другое, что на данный момент, кажется, работает довольно гладко для моих целей. Подход немного более компактный в сценарии и, кроме того, вызывает гораздо меньшее загадочное ощущение.

В этом альтернативном подходе используются внешние приложения 'realpath' и 'dirname 'из пакета coreutils. (Хорошо, никому не нравятся накладные расходы, связанные с вызовом вторичных процессов, но при просмотре многострочного сценария для разрешения истинного объекта это будет не так уж плохо, если он будет решен за одно двоичное использование.)

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

PATH_TO_SCRIPT=`realpath -s $0`
PATH_TO_SCRIPT_DIR=`dirname $PATH_TO_SCRIPT`

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

PATH_TO_SCRIPT=`realpath -s "$0"`
PATH_TO_SCRIPT_DIR=`dirname "$PATH_TO_SCRIPT"`

В самом деле, если вам не нужно значение переменной SCRIPT, вы можете объединить этот двухстрочный файл даже в одну строку. Но зачем вам тратить на это усилия?

person Community    schedule 25.10.2019
comment
Этот вопрос bash специфический. Если вы вызываете сценарий с sh, оболочкой может быть что-то еще, например zsh или dash. - person neu242; 04.12.2019
comment
Я не буду сейчас проверять его код, но вы можете вызвать его с помощью bash, если хотите. см. sh просто как псевдоним для двоичного выбора совместимого исполнителя оболочки. - person Alexander Stohr; 15.06.2021

$0 - ненадежный способ получить текущий путь к скрипту. Например, это мой .xprofile:

#!/bin/bash
echo "$0 $1 $2"
echo "${BASH_SOURCE[0]}"
# $dir/my_script.sh &

cd / tmp && ~ / .xprofile && source ~ / .xprofile

/home/puchuu/.xprofile
/home/puchuu/.xprofile
-bash
/home/puchuu/.xprofile

Поэтому используйте вместо этого BASH_SOURCE.

person Community    schedule 04.02.2017

Следующее вернет текущий каталог скрипта

  • работает, если он получен или не получен
  • работает, если запускается в текущем или другом каталоге.
  • работает, если используются относительные каталоги.
  • работает с bash, не уверен в других оболочках.
/tmp/a/b/c $ . ./test.sh
/tmp/a/b/c

/tmp/a/b/c $ . /tmp/a/b/c/test.sh
/tmp/a/b/c

/tmp/a/b/c $ ./test.sh
/tmp/a/b/c

/tmp/a/b/c $ /tmp/a/b/c/test.sh
/tmp/a/b/c

/tmp/a/b/c $ cd

~ $ . /tmp/a/b/c/test.sh
/tmp/a/b/c

~ $ . ../../tmp/a/b/c/test.sh
/tmp/a/b/c

~ $ /tmp/a/b/c/test.sh
/tmp/a/b/c

~ $ ../../tmp/a/b/c/test.sh
/tmp/a/b/c

test.sh

#!/usr/bin/env bash

# snagged from: https://stackoverflow.com/a/51264222/26510
function toAbsPath {
    local target
    target="$1"

    if [ "$target" == "." ]; then
        echo "$(pwd)"
    elif [ "$target" == ".." ]; then
        echo "$(dirname "$(pwd)")"
    else
        echo "$(cd "$(dirname "$1")"; pwd)/$(basename "$1")"
    fi
}

function getScriptDir(){
  local SOURCED
  local RESULT
  (return 0 2>/dev/null) && SOURCED=1 || SOURCED=0

  if [ "$SOURCED" == "1" ]
  then
    RESULT=$(dirname "$1")
  else
    RESULT="$( cd "$( dirname "${BASH_SOURCE[0]}" )" >/dev/null 2>&1 && pwd )"
  fi
  toAbsPath "$RESULT"
}

SCRIPT_DIR=$(getScriptDir "$0")
echo "$SCRIPT_DIR"
person Community    schedule 08.10.2019

Это решение применимо только к Bash. Обратите внимание, что обычно предоставляемый ответ ${BASH_SOURCE[0]} не будет работать, если вы попытаетесь найти путь из функции.

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

dirname ${BASH_SOURCE[${#BASH_SOURCE[@]} - 1]}

Если вы хотите следовать символическим ссылкам, используйте readlink по указанному выше пути, рекурсивно или без -рекурсивно.

Вот сценарий, чтобы опробовать его и сравнить с другими предлагаемыми решениями. Вызовите его как source test1/test2/test_script.sh или bash test1/test2/test_script.sh.

#
# Location: test1/test2/test_script.sh
#
echo $0
echo $_
echo ${BASH_SOURCE}
echo ${BASH_SOURCE[${#BASH_SOURCE[@]} - 1]}

cur_file="${BASH_SOURCE[${#BASH_SOURCE[@]} - 1]}"
cur_dir="$(dirname "${cur_file}")"
source "${cur_dir}/func_def.sh"

function test_within_func_inside {
    echo ${BASH_SOURCE}
    echo ${BASH_SOURCE[${#BASH_SOURCE[@]} - 1]}
}

echo "Testing within function inside"
test_within_func_inside

echo "Testing within function outside"
test_within_func_outside

#
# Location: test1/test2/func_def.sh
#
function test_within_func_outside {
    echo ${BASH_SOURCE}
    echo ${BASH_SOURCE[${#BASH_SOURCE[@]} - 1]}
}

Причина, по которой работает однострочник, объясняется использованием переменной среды BASH_SOURCE и связанной с ней FUNCNAME.

BASH_SOURCE

Переменная массива, членами которой являются имена исходных файлов, в которых определены соответствующие имена функций оболочки в переменной массива FUNCNAME. Функция оболочки $ {FUNCNAME [$ i]} определена в файле $ {BASH_SOURCE [$ i]} и вызывается из $ {BASH_SOURCE [$ i + 1]}.

FUNCNAME

Переменная массива, содержащая имена всех функций оболочки, находящихся в настоящее время в стеке вызовов выполнения. Элемент с индексом 0 - это имя любой выполняющейся в данный момент функции оболочки. Самый нижний элемент (с наивысшим индексом) является основным. Эта переменная существует только при выполнении функции оболочки. Назначение FUNCNAME не имеет никакого эффекта и возвращает статус ошибки. Если FUNCNAME не задано, он теряет свои особые свойства, даже если впоследствии будет сброшен.

Эту переменную можно использовать с BASH_LINENO и BASH_SOURCE. Каждый элемент FUNCNAME имеет соответствующие элементы в BASH_LINENO и BASH_SOURCE для описания стека вызовов. Например, $ {FUNCNAME [$ i]} был вызван из файла $ {BASH_SOURCE [$ i + 1]} в строке номер $ {BASH_LINENO [$ i]}. Встроенная функция вызывающего абонента отображает текущий стек вызовов, используя эту информацию.

[Источник: руководство Bash]

person Community    schedule 31.08.2014

Если ваш сценарий Bash является символической ссылкой, то это можно сделать следующим образом:

#!/usr/bin/env bash

dirn="$(dirname "$0")"
rl="$(readlink "$0")";
exec_dir="$(dirname $(dirname "$rl"))";
my_path="$dirn/$exec_dir";
X="$(cd $(dirname ${my_path}) && pwd)/$(basename ${my_path})"

X - это каталог, содержащий ваш сценарий Bash (исходный файл, а не символическую ссылку). Клянусь Богом, это работает, и это единственный способ сделать это правильно.

person Community    schedule 01.06.2018

Вот команда, которая работает как в Bash, так и в zsh, независимо от того, выполняется она автономно или из источника:

[ -n "$ZSH_VERSION" ] && this_dir=$(dirname "${(%):-%x}") \
    || this_dir=$(dirname "${BASH_SOURCE[0]:-$0}")

Как это работает

Текущее расширение файла zsh: ${(%):-%x}

${(%):-%x} в zsh заменяется на путь к исполняемому в данный момент файлу.

Оператор резервной замены :-

Вы уже знаете, что ${...} заменяет переменные внутри строк. Вы могли не знать, что некоторые операции возможны (как в Bash, так и zsh) для переменных во время подстановки, например резервное раскрытие. оператор :-:

% x=ok
% echo "${x}"
ok

% echo "${x:-fallback}"
ok

% x=
% echo "${x:-fallback}"
fallback

% y=yvalue
% echo "${x:-$y}"
yvalue

Escape-код приглашения %x

Далее мы познакомим вас с кодами быстрого перехода, которые доступны только для zsh. В zsh %x будет расширяться до пути к файлу, но обычно это происходит только при расширении для строки приглашения. Чтобы включить эти коды в нашу замену, мы можем добавить флаг (%) перед именем переменной:

% cat apath/test.sh
fpath=%x
echo "${(%)fpath}"

% source apath/test.sh
apath/test.sh

% cd apath
% source test.sh
test.sh

Маловероятное совпадение: процент выхода и откат

То, что у нас есть, работает, но было бы проще избежать создания дополнительной переменной fpath. Вместо того, чтобы помещать %x в fpath, мы можем использовать :- и поместить %x в резервную строку:

% cat test.sh
echo "${(%):-%x}"

% source test.sh
test.sh

Обратите внимание, что обычно мы помещаем имя переменной между (%) и :-, но мы оставили его пустым. Переменная с пустым именем не может быть объявлена ​​или установлена, поэтому откат всегда срабатывает.

Завершение: как насчет print -P %x?

Теперь у нас почти есть каталог нашего скрипта. Мы могли бы использовать print -P %x, чтобы получить тот же путь к файлу с меньшим количеством взломов, но в нашем случае, когда нам нужно передать его в качестве аргумента для dirname, это потребовало бы накладных расходов на запуск новой подоболочки:

% cat apath/test.sh
dirname "$(print -P %x)"  # $(...) runs a command in a new process
dirname "${(%):-%x}"

% source apath/test.sh
apath
apath

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

person Community    schedule 22.05.2019

Python упоминался несколько раз. Вот альтернатива JavaScript (т.е. Node.js):

baseDirRelative=$(dirname "$0")
baseDir=$(node -e "console.log(require('path').resolve('$baseDirRelative'))") # Get absolute path using Node.js

echo $baseDir
person Community    schedule 26.12.2019

Я попробовал следующее с 3 разными исполнениями.

echo $(realpath $_)

. application         # /correct/path/to/dir or /path/to/temporary_dir
bash application      # /path/to/bash
/PATH/TO/application  # /correct/path/to/dir

echo $(realpath $(dirname $0))

. application         # failed with `realpath: missing operand`
bash application      # /correct/path/to/dir
/PATH/TO/application  # /correct/path/to/dir

echo $(realpath $BASH_SOURCE)

$BASH_SOURCE в основном то же самое с ${BASH_SOURCE[0]}.

. application         # /correct/path/to/dir
bash application      # /correct/path/to/dir
/PATH/TO/application  # /correct/path/to/dir

Только $(realpath $BASH_SOURCE) кажется надежным.

person Community    schedule 20.01.2021

Одно из преимуществ этого метода заключается в том, что он не задействует ничего, кроме самого Bash, и не создает никакой подоболочки.

Во-первых, используйте подстановку шаблона, чтобы заменить все, что не начинается с / (т. Е. Относительный путь), на $PWD/. Поскольку мы используем подстановку, чтобы соответствовать первому символу $0, мы также должны добавить его обратно (${0:0:1} в подстановке).

Теперь у нас есть полный путь к скрипту; мы можем получить каталог, удалив последний / и все, что следует за ним (т.е. имя скрипта). Затем этот каталог можно использовать в cd или в качестве префикса для других путей относительно вашего скрипта.

#!/bin/bash

BIN=${0/#[!\/]/"$PWD/${0:0:1}"}
DIR=${BIN%/*}

cd "$DIR"

Если ваш сценарий может быть получен, а не выполнен, вы, конечно, можете заменить $0 на ${BASH_SOURCE[0]}, например:

BIN=${BASH_SOURCE[0]/#[!\/]/"$PWD/${BASH_SOURCE[0]:0:1}"}

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

person Community    schedule 23.12.2020

Большинство ответов либо не обрабатывают файлы, которые связаны символическими ссылками через относительный путь, не являются однострочными, либо не обрабатывают BSD (Mac). Решение, которое делает все три:

HERE=$(cd "$(dirname "$BASH_SOURCE")"; cd -P "$(dirname "$(readlink "$BASH_SOURCE" || echo .)")"; pwd)

Во-первых, cd к концепции каталога сценария в bash. Затем прочтите ссылку на файл, чтобы увидеть, является ли это символической ссылкой (относительной или иной), и если да, перейдите в этот каталог. Если нет, перейдите в текущий каталог (необходимо, чтобы все было однострочным). Затем отобразите текущий каталог через pwd.

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

Вы можете увидеть полное объяснение с иллюстрациями здесь:

https://www.binaryphile.com/bash/2020/01/12/determining-the-location-of-your-script-in-bash.html

person Community    schedule 18.04.2021

Это, к сожалению, единственный однострочник, который я обнаружил, который работает как в Linux, так и в macOS, когда исполняемый скрипт представляет собой символическую ссылку:

SCRIPT_DIR=$(python -c "import os, sys; print(os.path.dirname(os.path.realpath('${BASH_SOURCE[0]}')))")

Протестировано на Linux и macOS и сравнивается с другими решениями в этом разделе: https://gist.github.com/ptc-mrucci/61772387878ed53a6c717d51a21d9371

person Community    schedule 20.06.2021

Вот как я работаю над своими скриптами:

pathvar="$( cd "$( dirname $0 )" && pwd )"

Это сообщит вам, из какого каталога запускается программа запуска (текущий скрипт).

person Community    schedule 17.04.2018

Я обычно делаю:

LIBDIR=$(dirname "$(readlink -f "$(type -P $0 || echo $0)")")
source $LIBDIR/lib.sh
person Community    schedule 24.01.2009

Вот чистое решение Bash

$ cat a.sh
BASENAME=${BASH_SOURCE/*\/}
DIRNAME=${BASH_SOURCE%$BASENAME}.
echo $DIRNAME

$ a.sh
/usr/local/bin/.

$ ./a.sh
./.

$ . a.sh
/usr/local/bin/.

$ /usr/local/bin/a.sh
/usr/local/bin/.
person Community    schedule 14.02.2013

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

... Как видите, с некоторой помощью я нашел довольно простое и очень мощное решение:

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

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

... ВЫДЕРЖКА: ...

Хотя это еще не строго POSIX, realpath является основным приложением GNU с 2012 года. Полное раскрытие: никогда не слышал об этом, прежде чем заметил это в info coreutils TOC и сразу подумал о [связанном] вопросе, но использование следующей функции, как показано, должно надежно (скоро POSIXLY?) И, я надеюсь, эффективно предоставить ее вызывающему с абсолютно исходным $0:

% _abs_0() {
> o1="${1%%/*}"; ${o1:="${1}"}; ${o1:=`realpath -s "${1}"`}; eval "$1=\${o1}";
> }
% _abs_0 ${abs0:="${0}"} ; printf %s\\n "${abs0}"
/no/more/dots/in/your/path2.sh

Возможно, стоит отметить, что это решение использует расширение параметра POSIX. чтобы сначала проверить, действительно ли путь нуждается в расширении и разрешении, прежде чем пытаться это сделать. Это должно вернуть $0 с абсолютным источником $0 через переменную мессенджера (с заметным исключением, что он сохранит symlinks) так эффективно, насколько я мог себе представить. выполняется независимо от того, является ли путь уже абсолютным. ...

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

С другой стороны, вы можете сделать это:

ps ww -fp $$ | grep -Eo '/[^:]*'"${0#*/}"

eval "abs0=${`ps ww -fp $$ | grep -Eo ' /'`#?}"

... И из трюков Рича: ...

Возврат строк из функции оболочки

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

Попробуй это:

func () {
body here
eval "$1=\${foo}"
}

Конечно, ${foo} можно заменить любой заменой. Ключевой уловкой здесь является строка eval и использование экранирования. “$1” раскрывается, когда аргумент eval создается основным синтаксическим анализатором команд. Но “${foo}” на данном этапе не раскрывается, потому что “$” был процитирован. Вместо этого он расширяется, когда eval оценивает свой аргумент. Если непонятно, почему это важно, подумайте, чем может быть плохое следующее:

foo='hello ; rm -rf /'
dest=bar
eval "$dest=$foo"

Но, конечно, совершенно безопасна следующая версия:

foo='hello ; rm -rf /'
dest=bar
eval "$dest=\$foo"

Обратите внимание, что в исходном примере “$1” использовался, чтобы позволить вызывающей стороне передать имя целевой переменной в качестве аргумента функции. Если вашей функции необходимо использовать команду shift, например, для обработки оставшихся аргументов как “$@”, тогда может быть полезно сохранить значение “$1” во временной переменной в начале функции.

person Community    schedule 26.11.2013

Я думаю, что самый простой ответ - это расширение параметров исходной переменной:

#!/usr/bin/env bash

DIR="$( cd "$( dirname "${BASH_SOURCE[0]}" )" >/dev/null 2>&1 && pwd )"
echo "opt1; original answer: $DIR"
echo ''

echo "opt2; simple answer  : ${BASH_SOURCE[0]%/*}"

Результат должен быть примерно таким:

$ /var/tmp/test.sh
opt1; original answer: /var/tmp

opt2; simple answer  : /var/tmp

Расширение переменных / параметров ${BASH_SOURCE[0]%/*}" кажется намного проще в обслуживании.

person Community    schedule 15.06.2020

Ниже приведен путь к каталогу скрипта в переменной dir.

(Он также пытается поддерживать выполнение под Cygwin в Windows.)

И, наконец, он запускает исполняемый файл my-sample-app со всеми аргументами, переданными этому сценарию, используя "$@":

#!/usr/bin/env sh

dir=$(cd "${0%[/\\]*}" > /dev/null && pwd)

if [ -d /proc/cygdrive ]; then
    case "$(uname -s)" in
        CYGWIN*|MINGW32*|MSYS*|MINGW*)
            # We are under Windows, so translate path to Windows format.
            dir=$(cygpath -m "$dir");
            ;;
    esac
fi

# Runs the executable which is beside this script
"${dir}/my-sample-app" "$@"
person Community    schedule 26.03.2019
comment
Что такое Windows PHP? PHP для Windows? Или WAMP? Или что-то другое? - person Peter Mortensen; 20.01.2021
comment
С помощью другого сообщения отпала необходимость в PHP (и теперь проверяется, работает ли он под Windows). - person Top-Master; 21.01.2021

Нет вилок (кроме подоболочки) и может обрабатывать "чужие" формы пути, подобные тем, которые содержат символы новой строки, как утверждают некоторые:

IFS= read -rd '' DIR < <([[ $BASH_SOURCE != */* ]] || cd "${BASH_SOURCE%/*}/" >&- && echo -n "$PWD")
person Community    schedule 03.07.2014

Посмотрите на тест внизу со странными названиями каталогов.

Чтобы изменить рабочий каталог на тот, в котором находится сценарий Bash, вам следует попробовать это простое, протестированное и проверенное с помощью решения shellcheck:

#!/bin/bash --
cd "$(dirname "${0}")"/. || exit 2

Тест:

$ ls 
application
$ mkdir "$(printf "\1\2\3\4\5\6\7\10\11\12\13\14\15\16\17\20\21\22\23\24\25\26\27\30\31\32\33\34\35\36\37\40\41\42\43\44\45\46\47testdir" "")"
$ mv application *testdir
$ ln -s *testdir "$(printf "\1\2\3\4\5\6\7\10\11\12\13\14\15\16\17\20\21\22\23\24\25\26\27\30\31\32\33\34\35\36\37\40\41\42\43\44\45\46\47symlink" "")"
$ ls -lb
total 4
lrwxrwxrwx 1 jay stacko   46 Mar 30 20:44 \001\002\003\004\005\006\a\b\t\n\v\f\r\016\017\020\021\022\023\024\025\026\027\030\031\032\033\034\035\036\037\ !"#$%&'symlink -> \001\002\003\004\005\006\a\b\t\n\v\f\r\016\017\020\021\022\023\024\025\026\027\030\031\032\033\034\035\036\037\ !"#$%&'testdir
drwxr-xr-x 2 jay stacko 4096 Mar 30 20:44 \001\002\003\004\005\006\a\b\t\n\v\f\r\016\017\020\021\022\023\024\025\026\027\030\031\032\033\034\035\036\037\ !"#$%&'testdir
$ *testdir/application && printf "SUCCESS\n" ""
SUCCESS
$ *symlink/application && printf "SUCCESS\n" ""
SUCCESS
person Community    schedule 31.03.2016

Обычно я включаю в начало своих скриптов следующее, что работает в большинстве случаев:

[ "$(dirname $0)" = '.' ] && SOURCE_DIR=$(pwd) || SOURCE_DIR=$(dirname $0);
ls -l $0 | grep -q ^l && SOURCE_DIR=$(ls -l $0 | awk '{print $NF}');

Первая строка назначает источник на основе значения pwd, если запускается с текущего пути или dirname если звонили из другого места.

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

Возможно, есть лучшие решения, но это самое чистое, что мне удалось придумать сам.

person Community    schedule 09.06.2013

Попробуйте что-то вроде этого:

function get_realpath() {

if [[ -f "$1" ]]
then
    # The file *must* exist
    if cd "$(echo "${1%/*}")" &>/dev/null
    then
        # The file *may* not be local.
        # The exception is ./file.ext
        # tTry 'cd .; cd -;' *works!*
        local tmppwd="$PWD"
        cd - &>/dev/null
    else
        # file *must* be local
        local tmppwd="$PWD"
    fi
else
    # The file *cannot* exist
    return 1 # Failure
fi

# Reassemble realpath
echo "$tmppwd"/"${1##*/}"
return 0 # Success

}

function get_dirname(){

local realpath="$(get_realpath "$1")"
if (( $? )) # True when non-zero.
then
    return $? # Failure
fi
echo "${realpath%/*}"
return 0 # Success

}

# Then from the top level:
get_dirname './script.sh'

# Or within a script:
get_dirname "$0"

# Can even test the outcome!
if (( $? )) # True when non-zero.
then
    exit 1 # Failure
fi

Эти функции и связанные с ними инструменты являются частью нашего продукта, который был предоставлен сообществу бесплатно и может быть найден на GitHub как realpath-lib. Он простой, чистый и хорошо документированный (отлично подходит для обучения), чистый Bash и не имеет зависимостей. Также подходит для кроссплатформенного использования. Итак, для приведенного выше примера в сценарии вы можете просто:

source '/path/to/realpath-lib'

get_dirname "$0"

if (( $? )) # True when non-zero.
then
    exit 1 # Failure
fi
person Community    schedule 03.10.2013

Ключевым моментом является то, что я уменьшаю масштаб проблемы: я запрещаю косвенное выполнение скрипта через путь (как в /bin/sh [script path relative to path component]).

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

Я также требую, чтобы имя пути и любые имена пути в цепочке символических ссылок содержали только разумное подмножество символов, в частности, не \n, >, * или ?. Это необходимо для логики синтаксического анализа.

Есть еще несколько неявных ожиданий, в которые я не буду вдаваться (см. этот ответ), и я не пытаюсь обрабатывать преднамеренный саботаж $0 (поэтому учитывайте любые последствия для безопасности). Я ожидаю, что это будет работать практически в любой Unix-подобной системе с Bourne-подобной /bin/sh.

#!/bin/sh
(
    path="${0}"
    while test -n "${path}"; do
        # Make sure we have at least one slash and no leading dash.
        expr "${path}" : / > /dev/null || path="./${path}"
        # Filter out bad characters in the path name.
        expr "${path}" : ".*[*?<>\\]" > /dev/null && exit 1
        # Catch embedded new-lines and non-existing (or path-relative) files.
        # $0 should always be absolute when scripts are invoked through "#!".
        test "`ls -l -d "${path}" 2> /dev/null | wc -l`" -eq 1 || exit 1
        # Change to the folder containing the file to resolve relative links.
        folder=`expr "${path}" : "\(.*/\)[^/][^/]*/*$"` || exit 1
        path=`expr "x\`ls -l -d "${path}"\`" : "[^>]* -> \(.*\)"`
        cd "${folder}"
        # If the last path was not a link then we are in the target folder.
        test -n "${path}" || pwd
    done
)
person Community    schedule 09.10.2015

Не существует 100% переносимого и надежного способа запросить путь к текущему каталогу сценария. Особенно между разными серверными ВМ, такими как Cygwin, MinGW, MSYS, Linux и т. д. Эта проблема долгое время не решалась в Bash должным образом и полностью.

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

В случае команды source я предлагаю заменить команду source на что-то вроде этого :

function include()
{
  if [[ -n "$CURRENT_SCRIPT_DIR" ]]; then
    local dir_path=... get directory from `CURRENT_SCRIPT_DIR/$1`, depends if $1 is absolute path or relative ...
    local include_file_path=...
  else
    local dir_path=... request the directory from the "$1" argument using one of answered here methods...
    local include_file_path=...
  fi
  ... push $CURRENT_SCRIPT_DIR in to stack ...
  export CURRENT_SCRIPT_DIR=... export current script directory using $dir_path ...
  source "$include_file_path"
  ... pop $CURRENT_SCRIPT_DIR from stack ...
}

С этого момента использование include(...) основано на предыдущем CURRENT_SCRIPT_DIR в вашем скрипте.

Это работает, только если вы можете заменить все команды source на команду include. Если вы не можете, то у вас нет выбора. По крайней мере, до тех пор, пока разработчики интерпретатора Bash не сделают явную команду для запроса текущего пути к каталогу запущенного скрипта.

Моя собственная ближайшая реализация к этому: https://sourceforge.net/p/tacklelib/tacklelib/HEAD/tree/trunk/bash/tacklelib/bash_entry

(ищите функцию tkl_include)

person Community    schedule 07.05.2019

На основании этого ответа, я предлагаю уточненную версию, которая получает SCRIPT_HOME как содержащую папку любого запущенного в данный момент сценария Bash:

s=${BASH_SOURCE[0]} ; s=`dirname $s` ; SCRIPT_HOME=`cd $s ; pwd`
echo $SCRIPT_HOME
person Community    schedule 24.10.2016

Я хочу прокомментировать предыдущий ответ там (Как я могу получить исходный каталог сценария Bash из самого сценария?), но у него нет достаточно репутации для этого.

Два года назад я нашел решение этой проблемы на сайте документации Apple: https://developer.apple.com/library/archive/documentation/OpenSource/Conceptual/ShellScripting/AdvancedTechniques/AdvancedTechniques.html. И впоследствии я придерживался этого метода. Он не может обрабатывать софт-ссылку, но в остальном работает для меня очень хорошо. Я размещаю его здесь для всех, кому это нужно, и как просьбу о комментариях.

#!/bin/sh

# Get an absolute path for the poem.txt file.
POEM="$PWD/../poem.txt"

# Get an absolute path for the script file.
SCRIPT="$(which $0)"
if [ "x$(echo $SCRIPT | grep '^\/')" = "x" ] ; then
    SCRIPT="$PWD/$SCRIPT"
fi

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

person Community    schedule 15.08.2019

Вот что я создавал на протяжении многих лет для использования в качестве заголовка в моих сценариях Bash:

## BASE BRAIN - Get where you're from and who you are.
MYPID=$$
ORIGINAL_DIR="$(pwd)" # This is not a hot air balloon ride..
fa="$0" # First Assumption
ta= # Temporary Assumption
wa= # Weighed Assumption
while true; do
    [ "${fa:0:1}" = "/" ] && wa=$0 && break
    [ "${fa:0:2}" = "./" ] && ta="${ORIGINAL_DIR}/${fa:2}" && [ -e "$ta" ] && wa="$ta" && break
    ta="${ORIGINAL_DIR}/${fa}" && [ -e "$ta" ] && wa="$ta" && break
done
SW="$wa"
SWDIR="$(dirname "$wa")"
SWBIN="$(basename "$wa")"
unset ta fa wa
( [ ! -e "$SWDIR/$SWBIN" ] || [ -z "$SW" ] ) && echo "I could not find my way around :( possible bug in the TOP script" && exit 1

На этом этапе ваши переменные SW, SWDIR и SWBIN содержат то, что вам нужно.

person Community    schedule 27.08.2019

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

cd $(dirname $(which $0) )

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

DIR=$(/usr/bin/pwd)
person Community    schedule 12.02.2009
comment
Чем это отличается от других ответов? - person mmmmmm; 20.01.2015

Этот однострочный работает с Cygwin, даже если сценарий был вызывается из Windows с помощью bash -c <script>:

set mydir="$(cygpath "$(dirname "$0")")"
person Community    schedule 03.02.2017

выбранный ответ работает очень хорошо. Я публикую свое решение для всех, кто ищет более короткие альтернативы, которые по-прежнему относятся к источникам, выполнению, полным путям, относительным путям и символическим ссылкам. Наконец, это будет работать с macOS, учитывая, что нельзя предположить, что версия GNU coreutils Доступна ссылка для чтения.

Проблема в том, что он не использует Bash, но его легко использовать в сценарии Bash. Хотя OP не накладывал никаких ограничений на язык решения, вероятно, лучше всего, чтобы большинство из них осталось в мире Bash. Это просто альтернатива, и, возможно, непопулярная.

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

export SOURCE_DIRECTORY="$(php -r 'echo dirname(realpath($argv[1]));' -- "${BASH_SOURCE[0]}")"
person Community    schedule 10.01.2019
comment
О MacOS речь не шла. Я думаю, что этот ответ должен начинаться с. Другой подход - использовать PHP вместо того, чтобы полагаться на BASH. Потому что это раскрывается только в конце ответа. - person DmitryBorodin; 25.02.2019

Будь проще.

#!/usr/bin/env bash
sourceDir=`pwd`
echo $sourceDir
person Community    schedule 24.02.2020

FOLDERNAME=${PWD##*/}

Это самый быстрый способ, который я знаю.

person Community    schedule 14.11.2014
comment
Это просто начинается с pwd и не возвращает путь, по которому находится текущий выполняемый скрипт. - person parvus; 02.01.2015

person    schedule
comment
Это намного короче выбранного ответа. И, похоже, работает так же хорошо. Это заслуживает 1000 голосов, чтобы люди не упускали это из виду. - person Patrick; 19.09.2013
comment
Как подробно объясняется во многих предыдущих ответах, ни $0, ни pwd не гарантируют, что у вас будет правильная информация, в зависимости от того, как запускается скрипт. - person IMSoP; 23.09.2013

person    schedule
comment
Я предпочитаю $BASH_SOURCE $0, потому что это явно даже для читателей, плохо разбирающихся в bash. $(dirname -- "$(readlink -f -- "$BASH_SOURCE")") - person blobmaster; 08.01.2020

person    schedule
comment
Я не тестировал его в разных системах. Но это решение работает сразу, по крайней мере, на Ubuntu, для меня! - person Natus Drew; 26.05.2020

person    schedule
comment
извините, я немного болван, могу ли я вызвать эту функцию, просто набрав getScriptAbsoluteDir или local currdir='getScriptAbsoluteDir'? - person Jiaaro; 30.11.2009
comment
-1: ключевое слово function недоступно в оболочках POSIX и является ненужно несовместимым bash / ksh / & c. расширение. Кроме того, если вы используете оболочку, достаточно новую для того, чтобы иметь это расширение, вам не нужен хак test x [...]. - person Charles Duffy; 09.06.2014
comment
Это почти похоже на Python ... Кажется, я не могу вспомнить, что я когда-либо использовал ключевое слово local в сценарии оболочки ... И на то, что сказал Чарльз, используйте if [[ "${script_invoke_path:0:1}" == "/" ]]; then и так далее. Обратите внимание на оператор логического двойного == равно. - person syntaxerror; 10.09.2014
comment
Объяснение было бы в порядке. - person Peter Mortensen; 20.01.2021
comment
@PeterMortensen, почему я должен? Я применил решение, которое работало у меня годами, и меня плевали на вещи, которые совершенно неуместны. Им не нравится функция, которую они могут отнять. Им не нравится хитрость, которую они могут удалить. - person Stefano Borini; 21.01.2021