Чи завжди $ 0 буде містити шлях до сценарію?


11

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

Я думав про щось подібне:

grep '^#h ' -- "$0" | sed -e 's/#h //'

Але тоді я задумався, що буде, якщо скрипт буде розташований у каталозі, який знаходиться у PATH, і зателефонував без явного вказівки каталогу.

Я шукав пояснення спеціальних змінних і знайшов такі описи $0:

  • назва поточної оболонки або програми

  • ім'я поточного сценарію

  • назва самого сценарію

  • команда, як це було запущено

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

Тестування на моїй системі (Bash 4.1)

Я створив виконуваний файл у / usr / local / bin з назвою scriptname одним рядком echo $0і викликав його з різних місць.

Це мої результати:

> cd /usr/local/bin/test
> ../scriptname
../scriptname

> cd /usr/local/bin
> ./scriptname
./scriptname

> cd /usr/local
> bin/scriptname
bin/scriptname

> cd /tmp
> /usr/local/bin/scriptname
/usr/local/bin/scriptname

> scriptname
/usr/local/bin/scriptname

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

Але потім я натрапив на коментар щодо Stack Overflow, який мене збентежив. Відповідь пропонує використовувати $(dirname $0)для отримання каталогу поточного сценарію. У коментарі (оновленому 7 разів) сказано, що "не буде працювати, якщо сценарій стоїть на вашому шляху".

Запитання

  • Чи правильний цей коментар?
  • Чи відрізняється поведінка в інших системах?
  • Чи бувають ситуації, коли $0не включатимуть каталог?

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

Відповіді:


17

У найпоширеніших випадках $0буде містити шлях, абсолютний або відносно сценарію, так

script_path=$(readlink -e -- "$0")

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

$0 призначається з аргументу із зазначенням сценарію, переданого інтерпретатору.

Наприклад, у:

the-shell -shell-options the/script its args

$0отримує the/script.

Під час запуску:

the/script its args

Ваша оболонка виконає:

exec("the/script", ["the/script", "its", "args"])

Якщо, наприклад, скрипт містить #! /bin/sh -she-bang, система перетворить це на:

exec("/bin/sh", ["/bin/sh" or "the/script", "-", "the/script", "its", "args"])

(якщо він не містить she-bang, або загалом, якщо система повертає помилку ENOEXEC, то ваша оболонка буде робити те саме)

Існує виняток для встановлених / setgid-скриптів у деяких системах, де система відкриє сценарій на деяких fd xі запускає замість цього:

exec("/bin/sh", ["/bin/sh" or "the/script", "-", "/dev/fd/x", "its", "args"])

щоб уникнути перегонових умов (у цьому випадку вони $0будуть міститись /dev/fd/x).

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

Тепер є різниця, якщо ім'я команди скрипта як викликане не містить косу рису. В:

the-script its args

Ваша оболонка буде шукати the-scriptв $PATH. $PATHможе містити абсолютні або відносні (включаючи порожній рядок) шляхи до деяких каталогів. Наприклад, якщо він $PATHміститься /bin:/usr/bin:і the-scriptзнаходиться в поточному каталозі, оболонка виконає:

exec("the-script", ["the-script", "its", "args"])

що стане:

exec("/bin/sh", ["/bin/sh" or "the-script", "-", "the-script", "its", "args"]

Або якщо він знайдений у /usr/bin:

exec("/usr/bin/the-script", ["the-script", "its", "args"])
exec("/bin/sh", ["/bin/sh" or "the-script" or "/usr/bin/the-script",
     "-", "/usr/bin/the-script", "its", "args")

У всіх цих випадках, окрім встановленого кутового регістру, $0буде містити шлях (абсолютний або відносний) до сценарію.

Тепер сценарій також можна назвати так:

the-interpreter the-script its args

Якщо, the-scriptяк зазначено вище, символи косого рису не містять, поведінка трохи змінюється від оболонки до оболонки.

Старі реалізації AT&T kshнасправді шукали скрипт беззастережно в $PATH(який насправді був помилкою та захищеним отвором для встановлених сценаріїв), тому $0насправді не містив шлях до сценарію, якщо $PATHпошук фактично не трапився the-scriptв поточному каталозі.

Новіші AT&T kshнамагаються інтерпретувати the-scriptв поточному каталозі, якщо вони читаються. Якщо ні, то він знайде для читабельного та виконуваного файлу the-script в $PATH.

Бо bashвін перевіряє, чи the-scriptзнаходиться в поточному каталозі (а чи не є розірваним символьним посиланням), а якщо ні, шукає читабельний (не обов’язково виконуваний) the-scriptв $PATH.

zshв shемуляції хотілося б bashза винятком того, що якщо the-scriptв поточному каталозі є порушена символьна посилання, вона не шукатиме the-scriptв $PATHі замість цього повідомляє про помилку.

Всі інші Bourne-подібні снаряди не дивляться the-scriptв $PATH.

Для всіх цих оболонок у будь-якому випадку, якщо ви виявите, що $0він не містить /та не читабельний, то він, ймовірно, знайдений $PATH. Тоді, оскільки файли в $PATH, ймовірно, будуть виконуваними, можливо, це безпечне наближення, щоб command -v -- "$0"знайти його шлях (хоча це не буде працювати, якщо $0трапиться також ім'я вбудованої оболонки або ключове слово (у більшості оболонок)).

Тож якщо ви справді хочете прикрити цей випадок, можете написати його:

progname=$0
[ -r "$progname" ] || progname=$(
    IFS=:; set -f
    for i in ${PATH-$(getconf PATH)}""; do
      case $i in
        "") p=$progname;;
        */) p=$i$progname;;
        *) p=$i/$progname
      esac
      [ -r "$p" ] && exec printf '%s\n' "$p"
    done
    exit 1
  ) && progname=$(readlink -e -- "$progname") ||
  progname=unknown

(The ""додаються до $PATHє збереження косого порожнього елемента з оболонками, $IFSдіє як роздільник замість роздільник ).

Тепер є більше езотеричних способів викликати сценарій. Можна зробити:

the-shell < the-script

Або:

cat the-script | the-shell

У такому випадку $0буде першим аргументом ( argv[0]), який отримав інтерпретатор (вище the-shell, але це може бути що-небудь, хоча загалом або базове ім'я, або один шлях до цього перекладача).

Виявлення того, що ви знаходитесь у цій ситуації на основі значення $0, не є надійним. Ви можете подивитися на висновок, ps -o args= -p "$$"щоб отримати підказку. У випадку "pipe" немає реального способу повернутися до шляху до сценарію.

Можна також зробити:

the-shell -c '. the-script' blah blih

Тоді, крім zsh(і деякої старої реалізації оболонки Борна), $0було б blah. Знову ж таки, важко потрапити на шлях сценарію в тих оболонках.

Або:

the-shell -c "$(cat the-script)" blah blih

тощо.

Щоб переконатися, що ви маєте право $progname, ви можете шукати в ньому певний рядок, наприклад:

progname=$0
[ -r "$progname" ] || progname=$(
    IFS=:; set -f
    for i in ${PATH-$(getconf PATH)}:; do
      case $i in
        "") p=$progname;;
        */) p=$i$progname;;
        *) p=$i/$progname
      esac
      [ -r "$p" ] && exec printf '%s\n' "$p"
    done
    exit 1
  ) && progname=$(readlink -e -- "$progname") ||
  progname=unknown

[ -f "$progname" ] && grep -q 7YQLVVD3UIUDTA32LSE8U9UOHH < "$progname" ||
  progname=unknown

Але знову ж таки я не думаю, що варто важити зусиль.


Стефане, я не розумію вашого використання "-"в наведених вище прикладах. На мій досвід, exec("the-script", ["the-script", "its", "args"])стає exec("/the/interpreter", ["/the/interpreter", "the-script", "its", "args"]), звичайно, можливістю варіанту перекладача.
jrw32982 підтримує Моніку

@ jrw32982, #! /bin/sh -це "завжди використовувати, cmd -- somethingякщо ви не можете гарантувати, що somethingне почнеться з -", придатну тут добру практику /bin/sh(де -як маркер кінцевого варіанту є більш портативним, ніж --), somethingбудучи шлях / ім'ям сценарій. Якщо ви не використовуєте це для встановлених сценаріїв (у системах, які їх підтримують, але не з методом / dev / fd / x, зазначеним у відповіді), то можна отримати кореневу оболонку, створивши символьне посилання на ваш скрипт під назвою -iабо -sдля екземпляр.
Стефан Шазелас

Спасибі, Стефане. У вашому прикладі лінії шебанг я пропустив єдиний дефіс. Мені довелося полювати там, де зафіксовано, що окремий дефіс еквівалентний подвійному дефісу pubs.opengroup.org/onlinepubs/9699919799/utilities/sh.html .
jrw32982 підтримує Моніку

Занадто просто забути прострочений одиночний / подвійний дефіс у рядку shebang для встановленого сценарію; якимось чином система повинна дбати про це за вас. Заборонити налаштування сценаріїв взагалі або якимось чином /bin/shслід відключити обробку власних опцій, якщо він може виявити, що він працює налаштовано. Я не бачу, як використання / dev / fd / x виправляє це. Вам все одно потрібен дефіс / подвійний, думаю.
jrw32982 підтримує Моніку

@ jrw32982, /dev/fd/xпочинається з /, ні -. Основна мета - усунути перегонну умову між двома execve()s, хоча (між тим, execve("the-script")що підвищує привілеї та наступним, execve("interpreter", "thescript")де interpreterвідкривається сценарій пізніше (що, можливо, тим часом було замінено символьним посиланням на щось інше). Системи, які реалізують сценарії suid правильно роблять execve("interpreter", "/dev/fd/n")замість них, де n було відкрито як частину першого execve ().
Stéphane Chazelas

6

Ось дві ситуації, коли каталог не включався:

> bash scriptname
scriptname

> bash <scriptname
bash

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

У першому випадку значення $0досі може бути передано, grepоскільки воно передбачає, що аргумент FILE є відносним до поточного каталогу.

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

Коваджі

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

  • Якщо скрипт розміщений, значенням $0, як правило, буде сценарій виклику, а не скрипт джерела.


3

Довільний нульовий аргумент можна вказати при використанні -cопції для більшості (всіх?) Оболонок. Наприклад:

sh -c 'echo $0' argv0

З man bash(вибрано виключно тому, що це кращий опис, ніж мій man sh- використання те саме, незалежно від):

-c

Якщо параметр -c присутній, то команди зчитуються з першого не-опційного аргументу command_string. Якщо після command_string є аргументи, вони призначаються позиційним параметрам, починаючи з $ 0.


Я думаю, що це працює тому, що -c 'command' є операндами, і argv0це його перший аргумент неоперанда командного рядка.
mikeserv

@mike правильно, я оновив фрагмент людини.
Graeme

3

ПРИМІТКА: Інші вже пояснили механіку, $0тому я пропускаю все це.

Я взагалі бічний крок усього цього питання і просто використовую команду readlink -f $0. Це завжди поверне вам повний шлях того, що ви дасте йому як аргумент.

Приклади

Скажи, що я тут для початку:

$ pwd
/home/saml/tst/119929/adir

Створіть каталог + файл:

$ mkdir adir
$ touch afile
$ cd adir/

Тепер почніть демонструвати readlink:

$ readlink -f ../adir
/home/saml/tst/119929/adir

$ readlink -f ../
/home/saml/tst/119929

$ readlink -f ../afile 
/home/saml/tst/119929/afile

$ readlink -f .
/home/saml/tst/119929/adir

Додаткова хитрість

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


0

На моїй manсторінці написано:

$0: Розширюється до nameз shellабо shell script.

Здається, це перекладається на argv[0]поточну оболонку - або перший аргумент командного рядка без операції, що в даний час оболонка інтерпретації подається при викликанні. Раніше я заявляв, що sh ./somescriptбуде спрямовувати свою змінну до цього, але це було неправильним, оскільки це новий власний процес і викликав нове .$0 $ENV shshell$ENV

Таким чином sh ./somescript.shвідрізняється від . ./somescript.shзапуску в поточному середовищі і $0вже встановлений.

Ви можете перевірити це порівнянням $0із /proc/$$/status.

echo 'script="/proc/$$/status"
    echo $0
    cat "$script"' \
    > ./script.sh
sh ./script.sh ; . ./script.sh

Дякую за виправлення, @toxalot. Я чомусь навчився.


У моїй системі, якщо вона отримана ( . ./myscript.shабо source ./myscript.sh), то $0оболонка. Але якщо він передається як аргумент shell ( sh ./myscript.sh), то $0це шлях до сценарію. Звичайно, shв моїй системі є Bash. Тож я не знаю, чи це має значення чи ні.
токсалот

У нього є хешбанг? Я думаю, що різниця має значення - і я можу додати це - без цього не слід execце, а замість цього джерело, але з ним воно повинно exec.
mikeserv

З хешбангом або без нього я отримую однакові результати. З або без виконання, я отримую однакові результати.
токсалот

Я також! Я думаю, це тому, що це вбудована оболонка. Я перевіряю ...
mikeserv

Лінії вибуху інтерпретуються ядром. Якщо ви ./somescriptвиконуєте чубку #!/bin/sh, це було б рівнозначно бігу /bin/sh ./somescript. В іншому випадку вони не мають ніякої різниці з оболонкою.
Graeme

0

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

Хоча $0містить ім'я скрипта, він може містити префіксований шлях на основі способу виклику сценарію, я завжди використовував ${0##*/}для друку імені сценарію у вихідному довіднику, який видаляє з нього будь-який провідний шлях $0.

Взяте з Посібника з розширеного сценарію Bash - Розділ 10.2 Заміна параметрів

${var#Pattern}

Видаліть із $varнайкоротшої частини, $Patternщо відповідає передньому краю $var.

${var##Pattern}

Видаліть із $varнайдовшої частини, $Patternщо відповідає передньому краю $var.

Таким чином, найдовшою частиною $0відповідності */буде весь префікс шляху, повертаючи лише ім'я сценарію.


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

0

Для чогось подібного я використовую:

rPath="$(dirname $(realpath $0))"
echo $rPath 

rPath=$(dirname $(readlink -e -- "$0"))
echo $rPath 

rPath завжди має однакове значення.

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