Bash $ 0 не соответствует команде, используемой в терминале - PullRequest
0 голосов
/ 10 мая 2018

В скрипте bash, который я создал, я пытаюсь распечатать некоторую информацию для пользователя (в ответ на конкретную ошибку), предлагая добавить переключатель в свою команду.Я думал, что $0 всегда содержит текст, который пользователь вводил в качестве команды на терминале, поэтому мой bash выглядел так: echo "try '$0 -s $@' instead."

Я обнаружил, что когда я вызываю этот скрипт из относительного пути, например ./script.sh, $0 - это "./script.sh", как я и ожидал.Однако, если script.sh находится в моем PATH, и я называю его с script.sh, $0 - это , а не "script.sh".Вместо этого он становится абсолютным путем к файлу сценария.

Я мог бы использовать basename $0 для исправления второго случая, но это могло бы испортить первый.Есть ли способ узнать, какой именно текст набрал пользователь для команды, запустившей файл скрипта?

Ответы [ 2 ]

0 голосов
/ 11 мая 2018

Я думаю, что ответ НЕТ.

Если я правильно понял, вам нужно создать переменную user_entry, чтобы вы могли написать:

echo "try '$user_entry -s $@' instead"

Я расскажу о том, почему я думаю, что вы не можете ниже, но сначала давайте попробуем понять поведение. Вкратце, $0 - это команда, данная субоболочке, которая выполняет сценарий.

Причина того, что вы описываете как происходящее , происходит , состоит в том, что выполнение скрипта запускает скрипт в под-оболочке. В этой под-оболочке аргументы, которые становятся $0, $1, $2, разрешаются и завершаются. Чтобы увидеть это, попробуйте следующее. Вот программа, которую я написал, которая покажет концепции, которые, я надеюсь, объяснят вещи. (Это также позаботится о «дополнительном флаге», который вы описываете.)

Перейдите в каталог, который НЕ находится в вашем PATH, и сохраните следующую программу. Я назвал это try_it.sh

#/bin/bash

echo "Welcome to the program!"

#print out all of the arguments as given in the subshell
for (( i=0;i<=$#;i++ )); do
  echo "Argument ${i} is: ${!i}"
                         # bash's inderection feature, allows $0, $1, $2, ...

done #endof:  for (( i=0;i<=$#;i++ ))

if [[ $1 != "-s" ]]  ; then
  echo "try '$0 -s $@' instead"
fi #endof: if [[ $1 != "-s" ]]

echo "Working directory: $(pwd)"
echo '$'"0 = $0"
echo "Everything before the script name and last / : "\
'${'"0"'%/*}'" = "${0%/*}
echo "Just the script name: "'${'"0"'##*/}'" = "${0##*/}

while true; do
  echo "Hit Ctrl+C to stop this program's execution."
  sleep 10000 # infinite loop, with instructions on how to get out
done #endof:  while true

Запустите chmod +x try_it.sh, чтобы убедиться, что он исполняемый.

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

Откройте новый терминал (назовем его Terminal1) и введите ps. Я надеюсь, что имеет смысл написать следующее как еще один способ описания точно такого же действия:

Terminal1> ps

Этот ps сообщает вам, какие процессы выполняются в ядре Linux. Вы должны увидеть один bash и один ps на выходе. 1 bash в выходных данных говорит о том, что 1 оболочка открыта, что, я надеюсь, вы увидите на своем экране.

Откройте второй терминал, назовем его Terminal2.

Вернитесь к терминалу 1 и введите

Terminal1> ps

Теперь вы должны увидеть два bash s и один ps на выходе. (Могут быть и другие вещи, но есть не более двух bash с и не меньше.) 2 bash с сообщают, что открыты 2 оболочки, которые, я надеюсь, вы увидите на своем экране.

Хорошо. Давайте разберемся с процессом $0. Я не собираюсь выкладывать здесь весь вывод. Надеюсь, увидев вывод на вашем экране (как вы пытаетесь запустить скрипт, как описано,) вы можете выяснить, как создать переменную user_entry. На самом деле я не думаю, что вы можете сделать это, но я постараюсь дать вам знать, что происходит.

Пойдем в Терминал2. Перейдите в каталог, в котором находится скрипт try_it.sh.

Terminal2> cd /path/to/script/

Запустите скрипт (в Terminal2) как

Terminal2> ./try_it.sh 1 2 3

Вот что я увидел:

$ ./try_it.sh 1 2 3
Welcome to the program!
Argument 0 is: ./try_it.sh
Argument 1 is: 1
Argument 2 is: 2
Argument 3 is: 3
try './try_it.sh -s 1 2 3' instead
Working directory: /home/me/other_dir
$0 = ./try_it.sh
Everything before the script name and last / : ${0%/*} = .
Just the script name: ${0##*/} = try_it.sh
Hit Ctrl+C to stop this program's execution.

( Пока что не нажимайте "Ctrl + C". ) Обратите внимание, что мой /path/to/script/ равен /home/me/other_dir/

Вернитесь в Terminal1 и снова запустите ps.

Terminal1> ps

Вы должны увидеть 3 bash с. У вас есть 2 открытых снаряда и один субоболочка. Обратите внимание, что бесконечный цикл позволяет вам «увидеть» вложенную оболочку. Кроме того, sleep, который вы видите в выводе ps, сохраняет «Ctrl + C, чтобы остановить выполнение этой программы». от повторения на экране.

Здесь мы можем видеть, что команда, «переданная» суб-оболочке, была ./try_it.sh. $0 - команда, данная субоболочке.

Хорошо, Ctrl+C на Терминале2. Идем дальше и ps на Терминале1.

Terminal1> ps

Только 2 bash с. Подоболочка была закрыта.

Теперь попробуйте следующее:

Terminal2> /path/to/script/try_it.sh 1 2 3

Terminal1> ps

Количество bash с: 3; команда, передаваемая суб-оболочке: /path/to/script/try_it.sh

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

Terminal2> try_it.sh 1 2 3

Вы получаете ошибку. Если путь к исполняемому файлу не был добавлен в PATH, для его выполнения необходим относительный или абсолютный путь к сценарию.

Давайте поместим наш скрипт в PATH. Для меня (на Cygwin) это следующая команда. Убедитесь, что вы знаете, как правильно добавить PATH в вашей системе. Не используйте это без проверки!

export PATH="$(pwd):$PATH"

Не волнуйтесь, это исчезнет с вашего пути в следующий раз, когда вы закроете Терминал 2.

Теперь, пока вы все еще в /path/to/script, запустите

Terminal2> try_it.sh

Terminal1> ps

Количество bash с: 3; команда, передаваемая суб-оболочке: /path/to/script/try_it.sh

"Магия Linux" прошла через каталоги в PATH, ища тот, в котором был исполняемый файл try_it.sh. Волшебство Linux соединяет правильный каталог и имя сценария, а затем передает эту полную команду суб-оболочке.

Давайте создадим еще один каталог в /path/to/script/

Terminal2> mkdir another_directory
Terminal2> cd another_directory

Давайте запустим его с относительным путем.

Terminal2> ../try_it.sh 1 2 3

Terminal1> ps

Количество bash с: 3; команда, передаваемая суб-оболочке: ../try_it.sh

Безумный относительный путь. Мой /path/to/script/ был действительно /home/me/other_dir/, поэтому я пройду сумасшедший относительный путь следующим образом, отметив, что я нахожусь в каталоге another_directory.

Terminal2> ../../other_dir/another_directory/../try_it.sh 1 2 3

Terminal1> ps

Количество bash с: 3; команда, передаваемая суб-оболочке: ../../other_dir/another_directory/../try_it.sh

Мы поместили путь скрипта в PATH, поэтому давайте попробуем следующее из another_directory

Terminal2> try_it.sh

Terminal1> ps

Количество bash с: 3; команда, передаваемая суб-оболочке: /path/to/script/try_it.sh

Теперь абсолютный путь. Это от another_directory

Terminal2> /path/to/script/try_it.sh 1 2 3

Terminal1> ps

Количество bash с: 3; команда, передаваемая суб-оболочке: /path/to/script/try_it.sh

Еще раз, $0 - команда, данная субоболочке.

<Ч />

Мои мысли относительно ответа на ваш вопрос

Вы спросили,

Есть ли способ узнать, какой именно текст набрал пользователь для команды, запустившей файл скрипта?

Я думаю, что ответ - нет. Если вы пройдете шаги, которые я показал, я надеюсь, вы немного поймете, почему вы так поступили. Я думаю, что в большинстве случаев вы можете узнать, что набрал пользователь. Однако я не вижу способа провести различие между этими двумя командами (однажды /path/to/script был добавлен к вашему PATH)

Terminal2> try_it.sh 1 2 3

и

Terminal2> /path/to/script/try_it.sh 1 2 3

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

<Ч />

Примечание : См. этот пост SO и эту ссылку (поиск "Удаление подстроки"), чтобы получить пояснения по ${#%/*} и ${##*/}. Спасибо тем людям за то, что они сделали это, поэтому мне не нужно писать объяснение. <Ч />

Просто для удовольствия

Давайте посмотрим, действительно ли я запрограммировал правильное поведение, когда кто-то использует флаг -s.

$ /path/to/script/try_it.sh -s 1 2 3
Welcome to the program!
Argument 0 is: /home/dblack/other_dir/try_it.sh
Argument 1 is: -s
Argument 2 is: 1
Argument 3 is: 2
Argument 4 is: 3
Working directory: /home/dblack/other_dir
$0 = /home/dblack/other_dir/try_it.sh
Everything before the script name and last / : ${0%/*} = /home/dblack/other_dir
Just the script name: ${0##*/} = try_it.sh
Hit Ctrl+C to stop this program's execution.

Это сработало. Ура!

0 голосов
/ 11 мая 2018

Нет, вы обычно не можете узнать, что набрал пользователь.

Пользователь может иметь:

  • Вместо этого набрал функцию или оболочку, которая запускает ваши и другие команды,и которые могут не передавать параметры.
  • Ввели что-то в оболочку, отличную от Bourne, например, run_ "./script" ["--foo"].
  • Шелли, щелкнув меню рабочего стола вместо того, чтобы что-то набирать.
  • Выполнен сценарий из неповторяемого потока в фрагменте Java, запущенном на Android-телевизоре.

Каноническое поведение для вызывающего абонента - установить argv[0] в «строку имени файла, связанную спроцесс запускается "( POSIX ), что бы это ни значило, и чтобы вы повторили это в описаниях использования, подобных sh.

К сожалению для сценариев,argv[0] вызывающего абонента теряется в косвенном отношении к интерпретатору shebang, который вместо этого заканчивает тем, что устанавливает $0 в качестве аргумента имени файла, который он интерпретирует.

Это все еще хорошо, хотя.Для вас важно идентифицировать себя с basename "$0" или "$0" и указать, почему параметры неверны.Пользователь несет ответственность за внесение изменений в свой рабочий процесс.

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