Як отримати вихідний каталог сценарію Bash з самого сценарію


4948

Як отримати шлях до каталогу, в якому знаходиться сценарій Bash , всередині цього сценарію?

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

$ ./application

69
Жодне з поточних рішень не працює, якщо в кінці імені каталогу є якісь нові рядки - вони будуть позбавлені підстановки команд. Щоб вирішити це, ви можете додати символ, що не є новим рядком, всередині підстановки команди - DIR="$( cd "$( dirname "${BASH_SOURCE[0]}" )" && pwd && echo x)"- і видалити його без підстановки команди - DIR="${DIR%x}".
l0b0

80
@ jpmc26 Є дві дуже поширені ситуації: нещасні випадки та саботаж. Сценарій не повинен виходити з ладу непередбачуваними способами лише тому, що хтось десь робив це mkdir $'\n'.
l0b0

24
той, хто дозволяє людям таким чином саботажувати свою систему, не повинен залишати це для того, щоб виявляти подібні проблеми ... тим більше найняти людей, здатних зробити таку помилку. Я ніколи не бачив, щоб за 25 років використання башів такого роду щось не відбувалося десь .... саме тому у нас є такі речі, як перл і такі практики, як перевірка танти (я, мабуть, спалахнув би за це сказати :)
osirisgothra

3
@ l0b0 Подумайте, що вам потрібен такий самий захист dirname, і що каталог може починатися з -(наприклад --help). DIR=$(reldir=$(dirname -- "$0"; echo x); reldir=${reldir%?x}; cd -- "$reldir" && pwd && echo x); DIR=${DIR%?x}. Можливо, це надмірність?
Score_Under

55
Я настійно пропоную прочитати цей Bash FAQ щодо цієї теми.
Rany Albeg Wein

Відповіді:


6566
#!/bin/bash

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

є корисним одноклассником, який дасть вам повне ім'я каталогу скрипта незалежно від того, звідки він викликається.

Він буде працювати до тих пір, поки останній компонент шляху, який використовується для пошуку скрипту, не є символьним посиланням (посилання на каталоги в порядку). Якщо ви також хочете вирішити будь-які посилання на сам сценарій, вам потрібно багаторядкове рішення:

#!/bin/bash

SOURCE="${BASH_SOURCE[0]}"
while [ -h "$SOURCE" ]; do # resolve $SOURCE until the file is no longer a symlink
  DIR="$( cd -P "$( dirname "$SOURCE" )" >/dev/null 2>&1 && pwd )"
  SOURCE="$(readlink "$SOURCE")"
  [[ $SOURCE != /* ]] && SOURCE="$DIR/$SOURCE" # if $SOURCE was a relative symlink, we need to resolve it relative to the path where the symlink file was located
done
DIR="$( cd -P "$( dirname "$SOURCE" )" >/dev/null 2>&1 && pwd )"

Це останній буде працювати з будь-якою комбінацією псевдонімів, source, bash -c, символьні посилання і т.д.

Остерігайтеся: якщо ви cdпереходите до іншого каталогу перед запуском цього фрагмента, результат може бути неправильним!

Також слідкуйте за побічними ефектами $CDPATHgotchas та stderr, якщо користувач розумно перекрив cd, щоб замість цього переадресувати вихід на stderr (включаючи послідовності евакуації, наприклад, при виклику 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'

27
Ви можете з'єднати цей підхід з відповіддю на user25866 , щоб прийти до вирішення , яке працює з source <script>і bash <script>: DIR="$(cd -P "$(dirname "${BASH_SOURCE[0]}")" && pwd)".
Dan Molding

21
Іноді cdдрукує щось на STDOUT! Наприклад, якщо у вас $CDPATHє .. Щоб висвітлити цю справу, використовуйтеDIR="$( cd "$( dirname "${BASH_SOURCE[0]}" )" > /dev/null && pwd )"
user716468

182
Ця прийнята відповідь не нормальна, вона не працює з посиланнями і надмірно складна. dirname $(readlink -f $0)- правильна команда. Дивіться gist.github.com/tvlooy/cbfbdb111a4ebad8b93e про тестовий зразок
tvlooy

167
@tvlooy IMO ваша відповідь не зовсім нормальна, як є, тому що вона не відповідає, коли на шляху є пробіл. На відміну від символу нового рядка, це не малоймовірно і навіть не рідко. dirname "$(readlink -f "$0")"не додає складності і є справедливою мірою більш надійною для мінімальної кількості проблем.
Адріан Гюнтер

10
@tvlooy ваш коментар не сумісний із macOS (або, мабуть, BSD взагалі), хоча відповідь прийнята. readlink -f $0дає readlink: illegal option -- f.
Олександр Люнгберг

875

Використання dirname "$0":

#!/bin/bash
echo "The script you are running has basename `basename "$0"`, dirname `dirname "$0"`"
echo "The present working directory is `pwd`"

pwdсамостійне використання не буде працювати, якщо ви не запустите скрипт із каталогу, в якому він міститься.

[matt@server1 ~]$ pwd
/home/matt
[matt@server1 ~]$ ./test2.sh
The script you are running has basename test2.sh, dirname .
The present working directory is /home/matt
[matt@server1 ~]$ cd /tmp
[matt@server1 tmp]$ ~/test2.sh
The script you are running has basename test2.sh, dirname /home/matt
The present working directory is /tmp

25
Для портативності за межі bash, 0 доларів може не завжди вистачити. Можливо, вам доведеться замінити "type -p $ 0", щоб зробити цю роботу, якщо команда була знайдена на шляху.
Даррон

10
@Darron: ви можете використовувати лише type -pякщо виконується сценарій. Це також може відкрити тонкий отвір, якщо сценарій виконується за допомогою, bash test2.shа є ще один сценарій з тим же ім'ям, який можна виконати десь в іншому місці.
Д.Шоулі

90
@Darron: але оскільки питання позначено тегом bashі чітко згадується хеш- /bin/bashбаг, я б сказав, що це досить безпечно залежати від башизмів.
Йоахім Зауер

34
+1, але проблема з використанням dirname $0полягає в тому, що якщо каталог є поточним каталогом, ви отримаєте .. Це добре, якщо ви не збираєтесь змінювати каталоги в сценарії і не очікуєте, що ви використаєте шлях, який ви отримали, dirname $0як би він був абсолютним. Для того, щоб отримати абсолютний шлях: pushd `dirname $0` > /dev/null, SCRIPTPATH=`pwd`, popd > /dev/null: pastie.org/1489386 (Але , звичайно , є кращий спосіб розширити цей шлях?)
TJ Crowder

9
@TJ Crowder Я не впевнений, що dirname $0це проблема, якщо призначити її змінній, а потім використати її для запуску такого сценарію $dir/script.sh; Я б міг уявити, що це випадки використання для такого типу речей у 90% часу. ./script.shспрацювало б добре.
мат б

515

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

13
readlinkне буде доступний на певній платформі при встановленні за замовчуванням. Постарайтеся уникати його використання, якщо зможете
TL

43
будьте обережні, щоб процитувати все, щоб уникнути проблем із пробілами:export SCRIPT_DIR="$(dirname "$(readlink -f "$0")")"
Catskul

13
В OSX Yosemite 10.10.1 -fне визнається як опція для readlink. Використання stat -fзамість цього виконує роботу. Спасибі
cucu8

11
В OSX є greadlink, що в основному readlinkми всі знайомі. Ось незалежна версія платформи:dir=`greadlink -f ${BASH_SOURCE[0]} || readlink -f ${BASH_SOURCE[0]}`
robert

6
Гарний дзвінок, @robert. FYI, greadlinkможна легко встановити через домашню мову:brew install coreutils
phatblat

184
pushd . > /dev/null
SCRIPT_PATH="${BASH_SOURCE[0]}"
if ([ -h "${SCRIPT_PATH}" ]); then
  while([ -h "${SCRIPT_PATH}" ]); do cd `dirname "$SCRIPT_PATH"`; 
  SCRIPT_PATH=`readlink "${SCRIPT_PATH}"`; done
fi
cd `dirname ${SCRIPT_PATH}` > /dev/null
SCRIPT_PATH=`pwd`;
popd  > /dev/null

Працює для всіх версій, у тому числі

  • при виклику через м'яке посилання на кілька глибин
  • коли файл
  • коли сценарій викликається командою " source" .оператор aka (dot).
  • коли аргумент $0модифікується з виклику.
  • "./script"
  • "/full/path/to/script"
  • "/some/path/../../another/path/script"
  • "./some/folder/script"

Крім того, якщо сценарій bash сам по собі є відносним символьним посиланням, ви хочете перейти за ним і повернути повний шлях пов'язаного до сценарію:

pushd . > /dev/null
SCRIPT_PATH="${BASH_SOURCE[0]}";
if ([ -h "${SCRIPT_PATH}" ]) then
  while([ -h "${SCRIPT_PATH}" ]) do cd `dirname "$SCRIPT_PATH"`; SCRIPT_PATH=`readlink "${SCRIPT_PATH}"`; done
fi
cd `dirname ${SCRIPT_PATH}` > /dev/null
SCRIPT_PATH=`pwd`;
popd  > /dev/null

SCRIPT_PATHдається повним шляхом, як би це не називалося.
Просто переконайтесь, що ви знайдете це на початку сценарію.

Цей коментар та код Copyleft, ліцензія, що вибирається відповідно до GPL2.0 або новішої версії, або CC-SA 3.0 (CreativeCommons Share Alike) або пізнішої версії. (c) 2008. Усі права захищені. Ніякої гарантії. Вас попередили
http://www.gnu.org/licenses/gpl-2.0.txt
http://creativecommons.org/licenses/by-sa/3.0/
18eedfe1c99df68dc94d4a94712a71aaa8e1e9e36cacf421b9463dd2bbaa02906d0d6656


4
Приємно! Можна скоротити заміну "pushd [...] popd / dev / null" SCRIPT_PATH = readlink -f $(dirname "${VIRTUAL_ENV}");
e-satis

5
І замість того, щоб використовувати pushd ...; не було б краще використовувати $ (cd dirname "${SCRIPT_PATH}"&& pwd)? Але все одно чудовий сценарій!
ovanes

6
Сценарій небезпечно cdвиходити зі свого поточного каталогу, сподіваючись cdзнову повернутися пізніше: сценарій може не мати дозволу на зміну каталогу в той каталог, який був поточним під час його виклику. (Те саме стосується і pushd / popd)
Адріан Пронк

7
readlink -fє специфічним для GNU BSD readlinkне має такої можливості.
Кара Брайтвелл

3
Що з усіма непотрібними передплатками? ([ ... ])є менш ефективним, ніж [ ... ], і немає переваги, що використовується ізоляція, яка пропонується натомість за цей показник.
Чарльз Даффі

110

Коротка відповідь:

`dirname $0`

або ( бажано ):

$(dirname "$0")

17
Він не працює, якщо ви надсилаєте скрипт. "джерело мого / script.sh"
Arunprasad Rajkumar

Я використовую це весь час у своїх баш-скриптах, які автоматизують матеріали та часто викликають інші сценарії в тому ж режисі. Я б ніколи не користувався sourceцим і cd $(dirname $0)легко запам’ятався.
kqw

16
@vidstige: ${BASH_SOURCE[0]}замість того, $0щоб працювати зsource my/script.sh
Тімоті Джонс

@TimothyJones, який не вийде 100% часу, якщо він буде отриманий з будь-якої іншої оболонки, ніж баш. ${BASH_SOURCE[0]}зовсім не задовільний. ${BASH_SOURCE:-0}набагато краще.
Матьє КАРОФФ

106

Ви можете використовувати $BASH_SOURCE:

#!/bin/bash

scriptdir=`dirname "$BASH_SOURCE"`

Зауважте, що вам потрібно користуватися, #!/bin/bashа не #!/bin/shтому, що це розширення Bash.


14
Коли я це роблю ./foo/script, то $(dirname $BASH_SOURCE)є ./foo.
До

1
@ Досі, у цьому випадку ми можемо використовувати realpathкоманду, щоб отримати повний шлях ./foo/script. Так dirname $(realpath ./foo/script) дасть шлях сценарію.
purushothaman poovai

73

Це слід зробити:

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

Це працює з посиланнями та пробілами в шляху.

Див. Довідкові сторінки для dirnameта readlink.

З треку коментарів, схоже, це не працює з Mac OS. Я поняття не маю, чому це так. Будь-які пропозиції?


6
у своєму рішенні, виклик сценарію, як ./script.shшоу, .замість повного шляху до каталогу
Bruno Negrão Zica

5
На MacOS немає можливості для читання посилань. Використовуйте statзамість цього. Але все-таки це показує, .чи перебуваєте ви в "цьому" режисері.
Денис Загроза

2
Вам потрібно встановити coreutilsз Homebrew і використовувати, greadlinkщоб отримати -fопцію на MacOS, оскільки це * BSD під обкладинками, а не Linux.
dragon788

Вам слід додати подвійні лапки, що оточують всю праву частину:DIR="$(dirname "$(readlink -f "$0")")"
hagello

60

pwdможе бути використаний для пошуку поточного робочого каталогу та dirnameпошуку каталогу певного файлу (команда, яка була запущена, є $0, тому dirname $0повинна дати вам каталог поточного сценарію).

Тим не менш, dirnameнадає саме частину імені файлу, яка, швидше за все, не буде відносно поточного робочого каталогу. Якщо вашому сценарію потрібно змінити каталог з якихось причин, то вихід з нього dirnameстає безглуздим.

Я пропоную наступне:

#!/bin/bash

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

echo "Directory is $directory"

Таким чином, ви отримуєте абсолютний, а не відносний каталог.

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

Хоча просто

cd `dirname $0`

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


9
Ви можете зробити це в один рядок, як це: DIRECTORY = $ (cd dirname $0&& pwd)
dogbane

Це не спрацьовує, якщо сценарій джерело іншого сценарію, і ви хочете знати ім'я останнього.
reinierpost

52

Мені набридло приходити на цю сторінку знову і знову, щоб скопіювати вставити однокласичний знак у прийняті відповіді. Проблема в тому, що це не просто зрозуміти і запам'ятати.

Ось простий у запам'ятовуванні сценарій:

DIR="$(dirname "${BASH_SOURCE[0]}")"  # get the directory name
DIR="$(realpath "${DIR}")"    # resolve its full path if need be

2
Або, більш туманно, на одній лінії: DIR=$(realpath "$(dirname "${BASH_SOURCE[0]}")")
АРУ

Чому це не прийнята відповідь? Чи є якась різниця в застосуванні realpathвід вирішення "вручну" з циклом readlink? Навіть на readlinkчоловіковій сторінці написаноNote realpath(1) is the preferred command to use for canonicalization functionality.
User9123

1
І до речі, чи не слід подавати заявки realpathраніше dirname, не після? Якщо файл сценарію сам по собі є символьним посиланням ... Це дало б щось подібне DIR="$(dirname "$(realpath "${BASH_SOURCE[0]}")")". Насправді дуже близький до відповіді, запропонованої Саймоном.
User9123

@ User9123 Я думаю, що прийняти це спробувати бути сумісним з усіма популярними оболонками / дистрибутивами. Більше того, залежно від того, що ви намагаєтеся зробити, у більшості випадків люди хочуть отримати каталог, де розташоване символьне посилання замість каталогу фактичного джерела.
Ван

37

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

./script

/usr/bin/script

script

У першому та третьому способах $0немає повної інформації про шлях. У другій і третій pwdне працює. Єдиним способом отримати каталог третім способом було б пробігти шлях і знайти файл з правильним збігом. В основному код повинен був повторити те, що робить ОС.

Один із способів зробити те, що ви просите, - це просто жорсткий код даних у /usr/shareкаталозі та посилання на нього повним шляхом. Дані не повинні бути в /usr/binкаталозі, так що, мабуть, це потрібно зробити.


9
Якщо ви маєте намір спростувати його коментар, ДОКАЖІТЬ, що скрипт МОЖЕ отримати доступ там, де він зберігається, з прикладом коду.
Річард Дуерр

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

Це спосіб коротший, ніж обрана відповідь. І, здається, працює так само добре. Це заслуговує на 1000 голосів просто для того, щоб люди не помічали цього.
Патрік

2
Оскільки багато попередніх відповідей детально пояснюють, жоден з них $0не pwdгарантовано має правильну інформацію, залежно від того, як викликається сценарій.
IMSoP

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

Я вважаю $BASH_SOURCEза краще, $0тому що це явно навіть для читачів, які не вміють башти. $(dirname -- "$(readlink -f -- "$BASH_SOURCE")")
блобмейстер

32

Це отримує поточний робочий каталог в Mac OS X 10.6.6:

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

27

Це специфічно для Linux, але ви можете використовувати:

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

1
Це також характерно для башти, але можливо поведінка баша змінилася? /proc/fd/$$/255Здається, вказує на tty, а не на каталог. Наприклад, у моїй поточній оболонці входу всі описуються дескриптори файлів 0, 1, 2 та 255 /dev/pts/4. У будь-якому випадку керівництво з bash не згадує fd 255, тому, мабуть, нерозумно залежати від такої поведінки. \
Кіт Томпсон,

2
Інтерактивна оболонка! = Сценарій. У будь-якому випадку realpath ${BASH_SOURCE[0]};, здається, це найкращий шлях.
Стів Бейкер

23

Ось один-вкладиш, сумісний з POSIX:

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

# test
echo $SCRIPT_PATH

4
Я мав успіх у цьому, коли запускав сценарій самостійно або використовував sudo, але не під час виклику джерела ./script.sh
Michael R

І не вдається, коли cdналаштовано друкувати нове ім'я шляху.
Аарон Дігулла

18

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

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

Спробуйте ввести цей каталог для розміру:

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

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

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

Отже, щоб зробити його справді корисним, ось як перейти до каталогу запущеного сценарію:

cd "`dirname "$0"`"

4
Не працює, якщо сценарій отримується з іншого сценарію.
reinierpost

Це не працює, якщо остання частина $ 0 є символічним посиланням, що вказує на запис іншого каталогу ( ln -s ../bin64/foo /usr/bin/foo).
hagello

17

Ось простий і правильний спосіб:

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

Пояснення:

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

  • readlink -f- Рекурсивно вирішує будь-які посилання на вказаному шляху. Це розширення GNU і не доступне (наприклад) у системах BSD. Якщо ви використовуєте Mac, ви можете використовувати Homebrew для встановлення GNU coreutilsі замінити це greadlink -f.

  • І звичайно dirnameотримує батьківський каталог шляху.


1
greadlink -fна жаль, не працює ефективно під sourceчас написання сценарію на Mac :(
Gabe Kopley

17

Найкоротший і елегантний спосіб це зробити:

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

Це працюватиме на всіх платформах і дуже чисто.

Більш детальну інформацію можна знайти в "У якому каталозі знаходиться цей скрипт bash? ".


чудове чисте рішення, але це не спрацює, якщо файл буде позначено символом.
ruuter

16

Я б використав щось подібне:

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

# check whether the path is a link or not
if [ -L $scriptPath ]; then

    # it is a link then retrieve the target path and get the directory name
    sourceDir=$(dirname $(readlink -f $scriptPath))

else

    # otherwise just get the directory name of the script path
    sourceDir=$(dirname $scriptPath)

fi

Це справжнє! Працює і з простими sh! Проблема з простими dirname "$0"рішеннями на основі: Якщо скрипт знаходиться у програмі $PATHта викликається без шляху, вони дадуть неправильний результат.
Notinlist

@Notinlist Не так. Якщо скрипт буде знайдено через PATH, $0буде містити абсолютне ім'я файлу. Якщо сценарій викликається відносним або абсолютним іменем файлу, що містить a /, $0буде містити це.
Ніл Мейхью

Не знайдеться для джерела сценарію.
Аміт Найду

16

Це невеликий перегляд рішення e-satis і 3bcdnlklvc04a, зазначене у їхній відповіді :

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

Це все одно має працювати у всіх перерахованих ними випадках.

Це запобіжить popdпісля невдачі pushd, завдяки konsolebox.


Це прекрасно працює, щоб отримати "справжнє" ім'я, а не просто ім'я символьної посилання. Дякую!
Джей Тейлор

1
КращеSCRIPT_DIR=''; pushd "$(dirname "$(readlink -f "$BASH_SOURCE")")" > /dev/null && { SCRIPT_DIR=$PWD; popd > /dev/null; }
konsolebox

@konsolebox, від чого ти намагаєшся захищатись? Я, як правило, шанувальник вбудовування логічних умов, але яка конкретна помилка ви бачили в кнопці? Я б скоріше знайшов спосіб поправити це безпосередньо, а не повертати порожній SCRIPT_DIR.
Fuwjax

@Fuwjax Природна практика уникати дій popdу випадках, навіть коли вони рідкісні pushd. А в разі pushdневдачі, на вашу думку, якою має бути цінність SCRIPT_DIR? Дія може відрізнятися залежно від того, що може здатися логічним або того, що може скористатися одним користувачем, але, звичайно, робити popdце неправильно.
konsolebox

Усіх цих pushd popdнебезпек можна було б уникнути, просто відкинувши їх та використовуючи натомість cd+, що pwdдодається до заміни команди. SCRIPT_DIR=$(...)
Аміт Найду

16

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

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

Немає потреби використовувати, BASH_SOURCEколи $0містить ім'я файлу сценарію.


2
якщо тільки сценарій не був використаний. або "джерело", і в цьому випадку це все ще буде будь-який скрипт, який отримано, або, якщо з командного рядка, "-bash" (tty логін) або "bash" (викликається через 'bash -l') або '/ bin / bash '(викликається як інтерактивна оболонка без входу)
osirisgothra

Я додав другу пару цитат навколо dirnameдзвінка. Необхідно, якщо шлях до каталогу містить пробіли.
користувач1338062

14
#!/bin/sh
PRG="$0"

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

scriptdir=`dirname "$PRG"`

Я не перевіряв його в різних системах. Але це рішення - це те, що працює одразу, принаймні, на Ubuntu, для мене!
Natus Drew

$0не буде працювати за джерелом пошуку
Аміт Найду

13

$_Варто згадати як альтернативу $0. Якщо ви використовуєте сценарій від Bash, прийняту відповідь можна скоротити до:

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

Зауважте, що це має бути першим твердженням у вашому сценарії.


4
Він порушується, якщо ви sourceабо .сценарій. У цих ситуаціях $_буде міститися останній параметр останньої команди, яку ви запустили перед .. $BASH_SOURCEпрацює щоразу.
клак

11

Я порівняв багато відповідей і запропонував кілька більш компактних рішень. Вони, здається, справляються з усіма шаленими крайніми випадками, які виникають із улюбленої комбінації:

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

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

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

Це мало потворності, але виправлення є компактним і зрозумілим. Ми не використовуємо лише bash примітивів, але я з цим добре, бо readlinkзначно спрощує завдання. echo XДодає Xдо кінця змінної рядка , так що все кінцеві прогалини в імені файлу не з'їдять, а підстановки параметрів ${VAR%X}в кінці лінії позбавляється від X. Тому що представляти нові рядки (це також те, як ви можете легко створити жахливо названі каталоги та файли).readlink додає власний новий рядок (який, як правило, не було б замінено командою, якби не наша попередня хитрість), ми повинні позбутися і цього. Це найпростіше здійснити за допомогою $''схеми цитування, яка дозволяє нам використовувати послідовності втечі, такі як\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"

Я отримую /dev/pts/30з bash на Ubuntu 14.10 Desktop.
Дан Даскалеску

@DanDascalescu Використовуючи однолінійку? Або повний фрагмент коду внизу? А ви годували його якимись хитрими іменами?
billyjmc

Одна лінія плюс ще одна лінія , щоб echo $resolvedя зберіг його як d, chmod +x d, ./d.
Дан Даскалеску

@DanDascalescu Першим рядком у вашому сценарії має бути#!/bin/bash
billyjmc

10

Спробуйте використовувати:

real=$(realpath $(dirname $0))

1
Все, що я хочу знати, це чому цей спосіб не є добрим? Мені це здалося не поганим і правильним. Хтось може пояснити, чому це знижено?
Шоу Я

7
realpath не є стандартною утилітою.
Стів Беннетт

2
В Linux realpath - це стандартна утиліта (частина пакета coreutils GNU), але це не вбудований bash (тобто функція, що надається самим bash). Якщо ви використовуєте Linux, цей метод, ймовірно , працювати, хоча я б підставити $0для ${BASH_SOURCE[0]}так що цей спосіб буде працювати в будь-якому місці, в тому числі і в функції.
Doug Richardson

3
Порядок операцій у цій відповіді неправильний. Спочатку потрібно розв’язати посилання, а потім зробити, dirnameтому що остання частина $0символу може бути символьним посиланням, яке вказує на файл, який не знаходиться в тому ж каталозі, що і саме посилання. Рішення, описане у цій відповіді, просто отримує шлях до каталогу, де зберігається символьне посилання, а не каталог цілі. Крім того, у цьому рішенні відсутнє цитування. Він не працюватиме, якщо шлях містить спеціальні символи.
hagello

3
dir="$(realpath "$(dirname "${BASH_SOURCE[0]}")")"
Костянтин Пономаренко

9

Спробуйте таке крос-сумісне рішення:

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

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

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

Крім того, ви можете спробувати наступну функцію в bash:

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

Ця функція бере 1 аргумент. Якщо аргумент вже має абсолютний шлях, надрукуйте його таким, яким він є, інакше надрукуйте $PWDзмінну + аргумент імені файлу (без ./префікса).

Пов'язані:


Будь ласка, поясніть більше про функцію realpath.
Кріс

1
realpathФункція @Chris бере 1 аргумент. Якщо аргумент вже має абсолютний шлях, надрукуйте його таким, яким він є, інакше надрукуйте $PWD+ ім'я файлу (без ./префікса).
kenorb

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

9

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

#!/bin/sh # dash bash ksh # !zsh (issues). G. Nixon, 12/2013. Public domain.

## 'linkread' or 'fullpath' or (you choose) is a little tool to recursively
## dereference symbolic links (ala 'readlink') until the originating file
## is found. This is effectively the same function provided in stdlib.h as
## 'realpath' and on the command line in GNU 'readlink -f'.

## Neither of these tools, however, are particularly accessible on the many
## systems that do not have the GNU implementation of readlink, nor ship
## with a system compiler (not to mention the requisite knowledge of C).

## This script is written with portability and (to the extent possible, speed)
## in mind, hence the use of printf for echo and case statements where they
## can be substituded for test, though I've had to scale back a bit on that.

## It is (to the best of my knowledge) written in standard POSIX shell, and
## has been tested with bash-as-bin-sh, dash, and ksh93. zsh seems to have
## issues with it, though I'm not sure why; so probably best to avoid for now.

## Particularly useful (in fact, the reason I wrote this) is the fact that
## it can be used within a shell script to find the path of the script itself.
## (I am sure the shell knows this already; but most likely for the sake of
## security it is not made readily available. The implementation of "$0"
## specificies that the $0 must be the location of **last** symbolic link in
## a chain, or wherever it resides in the path.) This can be used for some
## ...interesting things, like self-duplicating and self-modifiying scripts.

## Currently supported are three errors: whether the file specified exists
## (ala ENOENT), whether its target exists/is accessible; and the special
## case of when a sybolic link references itself "foo -> foo": a common error
## for beginners, since 'ln' does not produce an error if the order of link
## and target are reversed on the command line. (See POSIX signal ELOOP.)

## It would probably be rather simple to write to use this as a basis for
## a pure shell implementation of the 'symlinks' util included with Linux.

## As an aside, the amount of code below **completely** belies the amount
## effort it took to get this right -- but I guess that's coding for you.

##===-------------------------------------------------------------------===##

for argv; do :; done # Last parameter on command line, for options parsing.

## Error messages. Use functions so that we can sub in when the error occurs.

recurses(){ printf "Self-referential:\n\t$argv ->\n\t$argv\n" ;}
dangling(){ printf "Broken symlink:\n\t$argv ->\n\t"$(readlink "$argv")"\n" ;}
errnoent(){ printf "No such file: "$@"\n" ;} # Borrow a horrible signal name.

# Probably best not to install as 'pathfull', if you can avoid it.

pathfull(){ cd "$(dirname "$@")"; link="$(readlink "$(basename "$@")")"

## 'test and 'ls' report different status for bad symlinks, so we use this.

 if [ ! -e "$@" ]; then if $(ls -d "$@" 2>/dev/null) 2>/dev/null;  then
    errnoent 1>&2; exit 1; elif [ ! -e "$@" -a "$link" = "$@" ];   then
    recurses 1>&2; exit 1; elif [ ! -e "$@" ] && [ ! -z "$link" ]; then
    dangling 1>&2; exit 1; fi
 fi

## Not a link, but there might be one in the path, so 'cd' and 'pwd'.

 if [ -z "$link" ]; then if [ "$(dirname "$@" | cut -c1)" = '/' ]; then
   printf "$@\n"; exit 0; else printf "$(pwd)/$(basename "$@")\n"; fi; exit 0
 fi

## Walk the symlinks back to the origin. Calls itself recursivly as needed.

 while [ "$link" ]; do
   cd "$(dirname "$link")"; newlink="$(readlink "$(basename "$link")")"
   case "$newlink" in
    "$link") dangling 1>&2 && exit 1                                       ;;
         '') printf "$(pwd)/$(basename "$link")\n"; exit 0                 ;;
          *) link="$newlink" && pathfull "$link"                           ;;
   esac
 done
 printf "$(pwd)/$(basename "$newlink")\n"
}

## Demo. Install somewhere deep in the filesystem, then symlink somewhere 
## else, symlink again (maybe with a different name) elsewhere, and link
## back into the directory you started in (or something.) The absolute path
## of the script will always be reported in the usage, along with "$0".

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

# Yay ANSI l33t codes! Fancy.
 printf "\n\033[3mfrom/as: \033[4m$0\033[0m\n\n\033[1mUSAGE:\033[0m   "
 printf "\033[4m$scriptname\033[24m [ link | file | dir ]\n\n         "
 printf "Recursive readlink for the authoritative file, symlink after "
 printf "symlink.\n\n\n         \033[4m$scriptname\033[24m\n\n        "
 printf " From within an invocation of a script, locate the script's "
 printf "own file\n         (no matter where it has been linked or "
 printf "from where it is being called).\n\n"

else pathfull "$@"
fi

8

Ось короткі способи отримання інформації про сценарій:

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

    Script: "/tmp/src dir/test.sh"
    Calling folder: "/tmp/src dir/other"

Використовуючи ці команди:

    echo Script-Dir : `dirname "$(realpath $0)"`
    echo Script-Dir : $( cd ${0%/*} && pwd -P )
    echo Script-Dir : $(dirname "$(readlink -f "$0")")
    echo
    echo Script-Name : `basename "$(realpath $0)"`
    echo Script-Name : `basename $0`
    echo
    echo Script-Dir-Relative : `dirname "$BASH_SOURCE"`
    echo Script-Dir-Relative : `dirname $0`
    echo
    echo Calling-Dir : `pwd`

І я отримав цей вихід:

     Script-Dir : /tmp/src dir
     Script-Dir : /tmp/src dir
     Script-Dir : /tmp/src dir

     Script-Name : test.sh
     Script-Name : test.sh

     Script-Dir-Relative : ..
     Script-Dir-Relative : ..

     Calling-Dir : /tmp/src dir/other

Також дивіться: https://pastebin.com/J8KjxrPF



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

8

Це працює в bash-3.2:

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

Якщо у вас є ~/binкаталог, у $PATHвас є Aвсередині цього каталогу. Він джерело сценарію ~/bin/lib/B. Ви знаєте, де включений скрипт є відносно вихідного, у libпідкаталозі, але не там, де він відповідає поточному каталогу користувача.

Це вирішується наступним чином (всередині A):

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

Не має значення, де знаходиться користувач або як він називає сценарій, це завжди буде працювати.


3
Точка щодо whichдуже дискусійна. type, hashта інші побудови роблять те саме, що краще в баш. whichє kindof більш портативним, хоча він насправді не той самий, що whichвикористовується в інших оболонках, як tcsh, у ньому є вбудований.
Відновіть Моніку. Будь ласка,

"Завжди"? Зовсім ні. whichбудучи зовнішнім інструментом, у вас немає підстав вважати, що він поводиться ідентично батьківській оболонці.
Чарльз Даффі

7

Найкращим компактним рішенням, на мій погляд, було б:

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

Нічого, крім Баша, немає. Використання dirname, readlinkі basenameв кінцевому підсумку призвести до проблем з сумісністю, тому вони краще уникати , якщо це взагалі можливо.


2
Ви , ймовірно , слід додати слеш до цього: "$( cd "$( echo "${BASH_SOURCE[0]%/*}/" )"; pwd )". У вас виникнуть проблеми з кореневим каталогом, якщо ви цього не зробите. Крім того, чому ви навіть повинні використовувати ехо?
konsolebox

dirnameі basenameстандарти POSIX стандартизовані, то чому б уникати їх використання? Посилання: dirname,basename
myrdd

Попередити два додаткових вилки для процесів та дотримуватися вбудованих оболонок може бути однією з причин того.
Аміт Найду
Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.