Для чего нужна переменная $ BASH_COMMAND?

25

Согласно руководству по Bash , переменная окружения BASH_COMMANDсодержит

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

Если оставить в стороне этот случай с ловушкой, если я правильно понимаю, это означает, что когда я выполняю команду, переменная BASH_COMMANDсодержит эту команду. Не совсем ясно, является ли эта переменная неустановленной после выполнения команды (т. Е. Доступна ли она только во время выполнения команды, но не после), хотя можно утверждать, что, поскольку она является «командой , выполняемой в настоящее время или готовой к выполнению» , это не команда, которая была только что выполнена.

Но давайте проверим:

$ set | grep BASH_COMMAND=
$ 

Пустой. Я бы ожидал увидеть BASH_COMMAND='set | grep BASH_COMMAND='или, может быть, просто BASH_COMMAND='set', но пусто удивил меня.

Давайте попробуем что-то еще:

$ echo $BASH_COMMAND
echo $BASH_COMMAND
$ 

Ну, это имеет смысл. Я выполняю команду, echo $BASH_COMMANDи поэтому переменная BASH_COMMANDсодержит строку echo $BASH_COMMAND. Почему это сработало на этот раз, но не раньше?

Давайте сделаем setэто снова:

$ set | grep BASH_COMMAND=
BASH_COMMAND='echo $BASH_COMMAND'
$

Так что ждите. Он был установлен, когда я выполнил эту echoкоманду, и впоследствии он не был сброшен. Но когда я setснова выполнил , BASH_COMMAND не было установлено setкоманды. Независимо от того, как часто я выполняю setкоманду здесь, результат остается неизменным. Итак, установлена ​​ли переменная при выполнении echo, но не при выполнении set? Посмотрим.

$ echo Hello AskUbuntu
Hello AskUbuntu
$ set | grep BASH_COMMAND=
BASH_COMMAND='echo $BASH_COMMAND'
$

Какая? Таким образом, переменная была установлена, когда я выполнял echo $BASH_COMMAND, но не когда я выполнял echo Hello AskUbuntu? Где разница сейчас? Переменная устанавливается только тогда, когда текущая команда сама заставляет оболочку вычислять переменную? Давайте попробуем что-то другое. Может быть, на этот раз какая-то внешняя команда, а не встроенная команда bash, для разнообразия.

$ /bin/echo $BASH_COMMAND
/bin/echo $BASH_COMMAND
$ set | grep BASH_COMMAND=
BASH_COMMAND='/bin/echo $BASH_COMMAND'
$

Хм, ладно ... опять переменная была установлена. Так верна ли моя нынешняя догадка? Переменная устанавливается только тогда, когда она должна быть оценена? Зачем? Зачем? По причинам производительности? Давайте сделаем еще одну попытку. Мы попытаемся выполнить поиск grep для $BASH_COMMANDфайла и, поскольку он $BASH_COMMANDдолжен содержать grepкоманду, grepдолжен выполнить grep для этой grepкоманды (т. Е. Для себя). Итак, давайте сделаем соответствующий файл:

$ echo -e "1 foo\n2 grep\n3 bar\n4 grep \$BASH_COMMAND tmp" > tmp
$ grep $BASH_COMMAND tmp
grep: $BASH_COMMAND: No such file or directory
tmp:2 grep                                      <-- here, the word "grep" is RED
tmp:4 grep $BASH_COMMAND tmp                    <-- here, the word "grep" is RED
tmp:2 grep                                      <-- here, the word "grep" is RED
tmp:4 grep $BASH_COMMAND tmp                    <-- here, the word "grep" is RED
$ set | grep BASH_COMMAND=
BASH_COMMAND='grep --color=auto $BASH_COMMAND tmp'
$

Ок, интересно. Команда grep $BASH_COMMAND tmpбыла расширена до grep grep $BASH_COMMAND tmp tmp(переменная раскрывается только один раз, конечно), и поэтому я нашел grep, один раз в файле, $BASH_COMMANDкоторый не существует, и два раза в файле tmp.

Q1: верно ли мое текущее предположение, что:

  • BASH_COMMANDустанавливается только тогда, когда команда пытается его реально оценить; а также
  • оно не сбрасывается после выполнения команды, хотя описание может заставить нас поверить в это?

Q2: если да, то почему? Спектакль? Если нет, как еще можно объяснить поведение в приведенной выше последовательности команд?

Q3: Наконец, есть ли сценарий, в котором эта переменная могла бы реально использоваться осмысленно? Я на самом деле пытался использовать его внутри $PROMPT_COMMANDдля анализа выполняемой команды (и делать что-то в зависимости от этого), но я не могу, потому что, как только внутри $PROMPT_COMMAND, я выполняю команду, чтобы посмотреть на переменную $BASH_COMMAND, переменную получает наборы для этой команды. Даже когда я делаю MYVARIABLE=$BASH_COMMANDпрямо в начале моего $PROMPT_COMMAND, тогда MYVARIABLEсодержит строку MYVARIABLE=$BASH_COMMAND, потому что назначение тоже команда. (Этот вопрос не о том, как я могу получить текущую команду в ходе $PROMPT_COMMANDвыполнения. Я знаю, что есть и другие способы.)

Это немного похоже на принцип неопределенности Гейзенберга. Просто наблюдая за переменной, я меняю ее.

Malte Skoruppa
источник
5
Хорошо , но, вероятно, лучше подходит для unix.stackexchange.com ... там есть настоящие юберbash -гуру.
Rmano
Ну, можно ли переместить его туда? Модификации?
Malte Skoruppa
Я действительно не уверен, как это сделать - я полагаю, это привилегия мода. С другой стороны, я не думаю, что помечать это имеет смысл - давайте посмотрим, действует ли кто-то на флаг закрытия.
Rmano
1
В-третьих, это то, что вы ищете. Возможно, основываясь на этой статье, вы можете найти ответ и для первых двух пунктов.
Раду Рэдяну
2
+1 смутил меня до чертиков, но читать было весело!
Джо

Ответы:

15

Отвечая на третий вопрос: конечно, его можно использовать осмысленно, так как в руководстве по Bash четко намекает - в ловушку, например:

$ trap 'echo ‘$BASH_COMMAND’ failed with error code $?' ERR
$ fgfdjsa
fgfdjsa: command not found
fgfdjsa failed with error code 127
$ cat /etc/fgfdjsa
cat: /etc/fgfdjsa: No such file or directory
cat /etc/fgfdjsa failed with error code 1
Дмитрий Александров
источник
Ах, действительно хорошо. Итак, вы бы сказали, что ловушка - единственный контекст, в котором эта переменная имеет смысл? В руководстве это звучало как угловой случай: «Команда выполняется (...), если оболочка не выполняет команду в результате ловушки, ...»
Малте Скоруппа
После прочтения статей, связанных в ответе @DocSalvager, становится ясно, что он $BASH_COMMANDдействительно может использоваться осмысленно даже в других контекстах, кроме ловушки. Тем не менее, использование, которое вы описываете, является, вероятно, наиболее типичным и простым. Итак, ваш ответ, а также ответ DocSalvager, ответьте лучше всего на мой Q3 , и это было самым важным для меня. Что касается Q1 и Q2, как указано @zwets, эти вещи не определены. Может случиться так, что $BASH_COMMANDдля доступа к нему дается только значение, если оно используется, или же некоторые странные внутренние условия программы могут привести к такому поведению. Кто знает?
Malte Skoruppa
1
Что касается sidenote, я обнаружил, что вы можете принудительно $BASH_COMMANDустанавливать значение всегда, форсируя оценку $BASH_COMMANDперед каждой командой. Для этого мы можем использовать ловушку DEBUG, которая выполняется перед каждой отдельной командой (все из них). Если мы выдадим, например, trap 'echo "$BASH_COMMAND" > /dev/null' DEBUGто $BASH_COMMAND, всегда будет оцениваться перед каждой командой (и присваивается значение этого $COMMAND). Так что, если я выполню, set | grep BASH_COMMAND=пока эта ловушка активна ... что ты знаешь? Теперь результат такой BASH_COMMAND=set, как и ожидалось :)
Malte Skoruppa
6

Теперь, когда на вопрос 3 ответили (правильно, на мой взгляд: BASH_COMMANDполезно в ловушках и вряд ли где-нибудь еще), давайте попробуем дать Q1 и Q2.

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

Ответ на вопрос 2 «Если нет, то как еще можно объяснить поведение в приведенной выше последовательности команд?» затем следует логически (если несколько педантично): это объясняется тем фактом, что значение BASH_COMMANDне определено. Поскольку его значение не определено, оно может иметь любое значение, которое в точности соответствует последовательности.

постскриптум

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

Даже когда я выполняю MYVARIABLE = $ BASH_COMMAND прямо в начале моего $ PROMPT_COMMAND, тогда MYVARIABLE содержит строку MYVARIABLE = $ BASH_COMMAND, потому что назначение также является командой .

То, как я читаю справочную страницу bash, немного выделено курсивом. В этом разделе SIMPLE COMMAND EXPANSIONобъясняется, как сначала назначаются переменные в командной строке, а затем

Если имя команды не получается [другими словами, были только назначения переменных], назначения переменных влияют на текущую среду оболочки.

Это наводит меня на мысль, что присвоение переменных не является командами (и, следовательно, освобождается от показа в них BASH_COMMAND), как и в других языках программирования. Это также объясняет, почему BASH_COMMAND=setна выходе нет строки set, по setсути являющейся синтаксическим «маркером» для назначения переменных.

OTOH, в последнем абзаце этого раздела говорится

Если после раскрытия остается имя команды, выполнение продолжается, как описано ниже. В противном случае команда завершается.

... что говорит об обратном, а присвоение переменных тоже команды.

zwets
источник
1
То, что какое-то предположение не может быть установлено, не означает, что оно неверно. Эйнштейн предположил, что скорость света одинакова для любого наблюдателя (с любой скоростью), как одна фундаментальная гипотеза для теории относительности; это не может быть установлено, но это не означает, что это неправильно. Свойства «можно установить» и «правильно» являются ортогональными. В лучшем случае вы могли бы сказать: «Мы не знаем, правильно ли это, так как это невозможно установить». Кроме того , он будет указано: это текущая команда во время выполнения. Так что, если я setBASH_COMMAND='set'
наберу,
... однако это не так. Хотя это во время выполнения setкоманды . Следовательно, согласно спецификациям это должно работать. Так что, реализация не совсем соответствует спецификациям или я неправильно понимаю спецификации? Поиск ответа на этот вопрос является своего рода целью Q1. +1 за постскриптум :)
Malte Skoruppa
@MalteSkoruppa Хорошие очки. Исправил ответ соответственно, и добавил замечание о set.
zwets
1
Вполне возможно, что в интерпретаторе bash setэто не «команда». Так же, как =это не «команда». Обработка текста, следующего за этими токенами или окружающего его, может иметь совершенно иную логику, чем обработка «команды».
DocSalvager
Хорошие моменты от вас обоих. :) Вполне может быть, что setэто не считается «командой». Я бы не подумал, что $BASH_COMMANDэто окажется слишком неопределенным во многих обстоятельствах. Я думаю, по крайней мере, мы установили, что
справочная
2

Изобретательское использование за $ BASH_COMMAND

Недавно было обнаружено впечатляющее использование $ BASH_COMMAND для реализации макроподобных функций.

Это основной трюк псевдонима, который заменяет использование ловушки DEBUG. Если вы прочитаете часть предыдущего поста о ловушке DEBUG, вы узнаете переменную $ BASH_COMMAND. В этом посте я сказал, что перед каждым вызовом ловушки DEBUG было задано значение команды. Ну, оказывается, он установлен перед выполнением каждой команды, ловушки DEBUG или нет (например, запустите 'echo “this command = $ BASH_COMMAND”', чтобы увидеть, о чем я говорю). Присваивая ему переменную (только для этой строки), мы фиксируем BASH_COMMAND в самой внешней области действия команды, которая будет содержать всю команду.

Предыдущая статья автора также дает хороший опыт при реализации техники с использованием DEBUG trap. trapУстраняется в улучшенной версии.

DocSalvager
источник
+1 Я наконец-то нашел время, чтобы прочитать эти две статьи. Вот Это Да! Что за чтение! Очень поучительно. Этот парень - гуру Баш ! Престижность! Это также отвечает на мой комментарий к ответу Дмитрия о том, $BASH_COMMANDможет ли оно использоваться осмысленно в контексте, отличном от a trap. И что это за польза! Используя его для реализации макрокоманд в bash - кто бы мог подумать, что это возможно? Спасибо за указатель.
Malte Skoruppa
0

По-видимому, обычное использование отладки trap ... заключается в улучшении заголовков, которые появляются в списке окон (^ A "), когда вы используете" screen ".

Я пытался сделать "screen", "windowlist" более удобным, поэтому я начал находить статьи, ссылающиеся на "trap ... debug".

Я обнаружил, что метод, использующий PROMPT_COMMAND для отправки «последовательности нулевых заголовков», не работал так хорошо, поэтому я вернулся к методу trap ... debug.

Вот как вы это делаете:

1 Скажите «screen» найти escape-последовательность (включите ее), поместив «shelltitle» $ | bash: '«в« $ HOME / .screenrc ».

2 Отключите распространение отладочной ловушки на вложенные оболочки. Это важно, потому что, если он включен, все испорчено, используйте: "set + o functrace".

3 Отправьте escape-последовательность заголовка для «screen» для интерпретации: trap 'printf "\ ek $ (дата +% Y% m% d% H% M% S) $ (whoami) @ $ (имя хоста): $ (pwd) $ {BASH_COMMAND} \ e \ "'" DEBUG "

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

Смотрите следующий «список окон» (5 экранов):

Num Name Flags

1 bash: 20161115232035 mcb @ ken007: / home / mcb / ken007 RSYNCCMD = "sudo rsync" myrsync.sh -R -r "$ {1}" "$ {BACKUPDIR} $ {2: +" / $ {2} " } "$ 2 bash: 20161115230434 mcb @ ken007: / home / mcb / ken007 ls --color = auto -la $ 3 bash: 20161115230504 mcb @ ken007: / home / mcb / ken007 cat bin / psg.sh $ 4 bash: 20161115222415 mcb @ ken007: / home / mcb / ken007 ssh ken009 $ 5 bash: 20161115222450 mcb @ ken007: / home / mcb / ken007 mycommoncleanup $

Малкольм Бекхофф
источник