Я хочу использовать текущий скрипт, чтобы распечатать справку и информацию о версии из раздела комментариев вверху.
Я думал о чем-то вроде этого:
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
является абсолютным путем . Так что похоже, что было бы безопасно перейти к другой команде.
Но потом я наткнулся на комментарий о переполнении стека, который смутил меня. Ответ предлагает использовать, $(dirname $0)
чтобы получить каталог текущего скрипта. В комментарии (7 раз) проголосовало "это не сработает, если скрипт находится на вашем пути".
Вопросов
- Это комментарий правильный?
- Отличается ли поведение в других системах?
- Существуют ли ситуации, когда
$0
бы не включать каталог?
источник
$0
что-то отличалось от сценария, который действительно отвечал на заголовок вопроса. Однако меня также интересуют ситуации, в которых$0
находится сам скрипт, но не включается каталог. В частности, я пытаюсь понять комментарий к такому ответу.Ответы:
В наиболее распространенных случаях
$0
будет содержать путь, абсолютный или относительный к сценарию, поэтому(при условии, что
readlink
команда есть и она поддерживается-e
), как правило, является достаточно хорошим способом получения канонического абсолютного пути к сценарию.$0
присваивается из аргумента, определяющего скрипт как переданный интерпретатору.Например, в:
$0
получаетthe/script
.Когда вы бежите:
Ваша оболочка сделает:
Если в сценарии, например, есть
#! /bin/sh -
она, система преобразует это в:(если он не содержит ничего, или, в более общем случае, если система возвращает ошибку ENOEXEC, то ваша оболочка будет делать то же самое)
Существует исключение для сценариев setuid / setgid на некоторых системах, где система открывает сценарий на некоторых
fd
x
и запускает вместо этого:чтобы избежать условий гонки (в этом случае
$0
будет содержать/dev/fd/x
).Теперь, вы можете утверждать , что
/dev/fd/x
это путь к этому сценарию. Тем не менее, обратите внимание, что если вы читаете из$0
, вы нарушите сценарий при использовании ввода.Теперь есть разница, если имя команды сценария, которое вызывается, не содержит косой черты. В:
Ваша оболочка будет искать
the-script
в$PATH
.$PATH
может содержать абсолютные или относительные (включая пустую строку) пути к некоторым каталогам. Например, если$PATH
содержится/bin:/usr/bin:
иthe-script
находится в текущем каталоге, оболочка выполнит:который станет:
Или если он найден в
/usr/bin
:Во всех перечисленных выше случаях, за исключением углового случая setuid,
$0
будет содержаться путь (абсолютный или относительный) к сценарию.Теперь скрипт также можно назвать так:
Если,
the-script
как указано выше, символы косой черты не содержат, поведение немного меняется от оболочки к оболочке.Старые реализации AT & T
ksh
фактически безоговорочно искали сценарий$PATH
(который на самом деле являлся ошибкой и дырой в безопасности для сценариев setuid), поэтому$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
и вместо этого будет сообщать об ошибке.Все остальные борновоподобные снаряды не заглядывают
the-script
внутрь$PATH
.В любом случае, для всех этих оболочек, если вы обнаружите, что
$0
они не содержат/
и не читаются, то, вероятно, они были найдены$PATH
. Тогда, поскольку файлы в$PATH
, вероятно, будут исполняемыми, это, вероятно, безопасное приближение, чтобы использовать его,command -v -- "$0"
чтобы найти его путь (хотя это не сработало бы, если$0
бы также было имя встроенной оболочки или ключевое слово (в большинстве оболочек)).Так что, если вы действительно хотите покрыть это дело, вы можете написать это:
(
""
добавлено, чтобы$PATH
сохранить конечный пустой элемент с оболочками, которые$IFS
действуют как разделитель вместо разделителя ).Теперь есть более эзотерические способы вызова скрипта. Можно сделать:
Или:
В этом случае
$0
будет первый аргумент (argv[0]
), полученный интерпретатором (вышеthe-shell
, но это может быть что угодно, хотя обычно это либо базовое имя, либо один путь к этому интерпретатору).Обнаружение того, что вы находитесь в такой ситуации на основе значения,
$0
не является надежным. Вы можете посмотреть на вывод,ps -o args= -p "$$"
чтобы получить подсказку. В случае с конвейером, нет никакого реального способа вернуться к пути к сценарию.Можно также сделать:
Тогда, за исключением
zsh
(и некоторых старых реализаций оболочки Bourne),$0
будетblah
. Опять же, сложно найти путь к сценарию в этих оболочках.Или:
и т.п.
Чтобы убедиться, что у вас есть права
$progname
, вы можете найти в ней определенную строку, например:Но опять же я не думаю, что это стоит усилий.
источник
"-"
в приведенных выше примерах. По моему опыту,exec("the-script", ["the-script", "its", "args"])
становитсяexec("/the/interpreter", ["/the/interpreter", "the-script", "its", "args"])
, конечно же, возможность выбора переводчика.#! /bin/sh -
это «всегда используйте,cmd -- something
если вы не можете гарантировать, чтоsomething
не начнете с-
», здесь применена пословица передовой практики/bin/sh
(где-
маркер конца опции более переносим, чем--
) сsomething
указанием пути / имени скрипт. Если вы не используете , что для Setuid сценариев (на системах , которые поддерживают их , но не с / DEV / FD упоминается в ответе / х метод), то можно получить корневую оболочку, создавая символьную ссылку на свой сценарий под названием-i
или-s
для пример./bin/sh
следует отключить обработку собственного параметра, если он может обнаружить, что работает setuid. Я не вижу, как использование / dev / fd / x само по себе исправляет это. Вам все еще нужен одиночный / двойной дефис, я думаю./dev/fd/x
начинается с/
, а не-
. Основная цель состоит в том, чтобы удалить условие гонки между двумя символамиexecve()
(между ними,execve("the-script")
которые повышают привилегии, и последующими,execve("interpreter", "thescript")
гдеinterpreter
позже открывается сценарий (который вполне может быть заменен символической ссылкой на что-то еще в то же время). Сценарии suid правильно делаютexecve("interpreter", "/dev/fd/n")
вместо этого, где n был открыт как часть первого execve ().Вот две ситуации, когда каталог не будет включен:
В обоих случаях текущий каталог должен быть каталогом, в котором находится имя скрипта .
В первом случае значение
$0
еще можно передать,grep
поскольку предполагается, что аргумент FILE относится к текущему каталогу.Во втором случае, если справка и информация о версии выводятся только в ответ на конкретный параметр командной строки, это не должно быть проблемой. Я не уверен, почему кто-то вызвал бы скрипт таким способом, чтобы напечатать справку или информацию о версии.
Предостережения
Если скрипт изменяет текущий каталог, вы не захотите использовать относительные пути.
Если сценарий получен, значением
$0
обычно будет сценарий вызывающего, а не исходный сценарий.источник
При использовании
-c
опции для большинства (всех?) Оболочек может быть задан произвольный нулевой аргумент . Например:From
man bash
(выбран просто потому, что это описание лучше, чем myman sh
- использование одно и то же):источник
argv0
это первый неоперандный аргумент командной строки.ПРИМЕЧАНИЕ: другие уже объяснили механику,
$0
поэтому я пропущу все это.Я обычно делаю шаг в сторону всей этой проблемы и просто использую команду
readlink -f $0
. Это всегда вернет вам полный путь того, что вы дадите ему в качестве аргумента.Примеры
Скажи, что я здесь, чтобы начать с:
Создайте каталог + файл:
Теперь начните хвастаться
readlink
:Дополнительный обман
Теперь с последовательным результатом возвращается , когда мы опрашивать с
$0
помощьюreadlink
мы можем использовать простоdirname $(readlink -f $0)
получить абсолютный путь к сценарию -или- ,basename $(readlink -f $0)
чтобы получить реальное имя сценария.источник
Моя
man
страница говорит:Похоже, это
argv[0]
соответствует текущей оболочке или первому неоперандному аргументу командной строки, который интерпретируется в данный момент интерпретируемой оболочкой при вызове. Я ранее заявлял , чтоsh ./somescript
путь будет его переменной к , но это неправильно , потому что это новый процесс самостоятельно и вызывается с новым .$0 $ENV
sh
shell
$ENV
Этим
sh ./somescript.sh
отличается от того,. ./somescript.sh
который работает в текущей среде и$0
уже настроен.Вы можете проверить это, сравнив
$0
с/proc/$$/status
.Спасибо за исправление, @toxalot. Я кое-что узнал.
источник
. ./myscript.sh
илиsource ./myscript.sh
), то$0
это оболочка. Но если он передан в качестве аргумента в shell (sh ./myscript.sh
), то$0
это путь к скрипту. Конечно,sh
на моей системе стоит Bash. Так что я не знаю, имеет ли это значение или нет.exec
а вместо этого должна исходить , но с этим она должнаexec
../somescript
выполните команду bangline#!/bin/sh
, это будет эквивалентно запуску/bin/sh ./somescript
. В противном случае они не имеют никакого значения для оболочки.Несмотря
$0
на то, что он содержит имя сценария, он может содержать префиксный путь в зависимости от способа вызова сценария, я всегда использовал его${0##*/}
для вывода имени сценария в выводе справки, в котором удаляется любой начальный путь$0
.Взято из руководства Advanced Bash Scripting - Раздел 10.2 Замена параметров
Поэтому самой длинной частью
$0
этого совпадения*/
будет префикс полного пути, возвращающий только имя скрипта.источник
Для чего-то похожего я использую:
rPath
всегда имеет одинаковое значение.источник