Для чого корисна змінна $ 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? Де зараз різниця? Чи встановлена ​​змінна лише тоді, коли сама поточна команда фактично змушує оболонку оцінювати змінну? Спробуємо щось інше. Можливо, якась зовнішня команда цього разу не зміна башти.

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

Хм, добре ... знову ж, змінна була встановлена. То чи правильна моя теперішня здогадка? Чи встановлюється змінна лише тоді, коли її потрібно оцінювати? Чому? Чому? З міркувань продуктивності? Зробимо ще одну спробу. Ми постараємося $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виконання. Я знаю, що існують інші способи.)

Це трохи схоже на принцип невизначеності Гейзенберга. Просто спостерігаючи за змінною, я її змінюю.


5
Приємно, але, мабуть, краще підходить для unix.stackexchange.com ... там є справжні губерbash -гуру.
Рмано

Ну, чи можна перенести його туди? Моди?
Malte Skoruppa

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

1
По-третє, це те, що ви шукаєте. Можливо, виходячи з цієї статті, ви можете знайти відповідь і за перші два пункти.
Раду Радеану

2
+1 плутав хека з мене, але читати було весело!
Джо

Відповіді:


15

Відповідаючи на третє запитання: звичайно, це може бути використане змістовно у спосіб, який у керівництві Баша чітко натякає - на пастку, наприклад:

$ 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

Ах, приємно справді. Так ви б сказали, що пастка - це єдиний контекст, в якому ця змінна має сенс? Це звучало як кутовий випадок у посібнику: "Команда виконується (...), якщо тільки оболонка не виконує команду в результаті пастки, ..."
Malte Skoruppa

Прочитавши статті, пов’язані у відповіді від @DocSalvager, зрозуміло, що $BASH_COMMANDдійсно можна використовувати сенс навіть у контекстах, відмінних від пастки. Однак використання, яке ви описуєте, мабуть, є найбільш типовим і прямим. Тож ваша відповідь, і відповідь DocSalvager, найкраще відповідайте на мій Q3 , і це було для мене найважливішим. Щодо Q1 та Q2, як зазначає @zwets, ці речі не визначені. Це може бути те, що $BASH_COMMANDйому надається значення лише за умови доступу до нього, а також деякі дивні внутрішні програмні умови можуть призвести до такої поведінки. Хто знає?
Malte Skoruppa

1
У стороні я виявив, що ви можете змусити $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

Тепер, коли відповіли Q3 (правильно, на мою думку: BASH_COMMANDкорисний у пастках і навряд чи десь більше), давайте дамо постріл Q1 та Q2.

Відповідь на питання Q1: правильність вашого припущення не можна визначити. Істину жодного з пунктів кулі не можна встановити, оскільки вони запитують про невказану поведінку. За його специфікацією значення " BASH_COMMANDзадається" тексту команди протягом тривалості виконання цієї команди. Специфікація не вказує, яке значення має бути в будь-якій іншій ситуації, тобто коли жодна команда не виконується. Це може мати будь-яке значення або взагалі жодне.

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

Постскрипт

Є один момент, коли я думаю, що ти справді потрапив на м'яке місце в специфікації. Тут ви говорите:

Навіть коли я роблю MYVARIABLE = $ BASH_COMMAND прямо на початку мого $ PROMPT_COMMAND, тоді MYVARIABLE містить рядок MYVARIABLE = $ BASH_COMMAND, тому що призначення також є командою .

Те, як я читаю сторінку bash man, шрифт курсивом не відповідає дійсності. У розділі SIMPLE COMMAND EXPANSIONпояснюється, як спочатку відміняються призначення змінних у командному рядку, а потім

Якщо ніяких результатів імен команд немає [іншими словами, були лише призначення змінних], присвоєння змінних впливає на поточне середовище оболонки.

Це підказує мені, що присвоєння змінних не є командами (і тому не звільняється від відображення в BASH_COMMAND), як в інших мовах програмування. Це також би пояснило, чому BASH_COMMAND=setна виході не існує рядка set, setпо суті є синтаксичним «маркером» для призначення змінних.

OTOH, в заключному пункті цього розділу йдеться

Якщо після розширення залишилося ім'я команди, виконання виконується, як описано нижче. В іншому випадку команда виходить.

... що говорить про інше, і змінні призначення також є командами.


1
Тільки тому, що деяке припущення неможливо встановити, не означає, що воно є невірним. Ейнштейн припускав, що швидкість світла однакова для будь-якого спостерігача (з будь-якою швидкістю), як одна фундаментальна гіпотеза теорії відносності; що неможливо встановити, але це не означає, що це неправильно. Властивості "можна встановити" і "правильно" є ортогональними. У кращому випадку можна сказати "Ми не знаємо, чи правильно це, оскільки його неможливо встановити". Крім того , він буде вказано: це поточна команда під час виконання. Тож якщо я setBASH_COMMAND='set'
набираю,

... однак це не так. Незважаючи на те, що це під час виконання setкоманди . Отже, відповідно до специфікацій, це повинно працювати. Так це те, що реалізація не відповідає дійсним характеристикам, чи я неправильно розумію характеристики? Пошук відповіді на це питання є своєрідною метою Q1. +1 для вашого постскрипту :)
Malte Skoruppa

@MalteSkoruppa Добрі бали. Відповідно виправили відповідь та додали зауваження про set.
zwets

1
Можливо, що в інтерпретаторі bash setне є "командою". Так само, як =це не "команда". Обробка тексту, що слідує / оточує ці лексеми, може мати зовсім іншу логіку, ніж обробка "команди".
DocSalvager

Гарні бали від вас обох. :) Цілком може бути, що setце не вважається "командою". Я б не подумав, що $BASH_COMMANDце виявиться настільки недооціненим у багатьох обставинах. Думаю, щонайменше, ми встановили, що сторінка man, безумовно, може бути зрозумілішою;)
Malte Skoruppa,

2

Винахідливе використання для $ BASH_COMMAND

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

Це головна хитрість псевдоніма і замінює використання пастки DEBUG. Якщо ви прочитаєте частину попереднього допису про пастку DEBUG, ви розпізнаєте змінну $ BASH_COMMAND. У цій публікації я сказав, що вона була встановлена ​​в тексті команди перед кожним викликом у пастку DEBUG. Ну, виявляється, він встановлений перед виконанням кожної команди, DEBUG trap або ні (наприклад, запустіть "echo" ця команда = $ BASH_COMMAND "', щоб побачити, про що я говорю). Присвоюючи йому змінну (саме для цього рядка), ми захоплюємо BASH_COMMAND у самій зовнішній області дії команди, яка буде містити всю команду.

Попередня стаття автора також дає хороший досвід при застосуванні методики за допомогою DEBUG trap. У trapвдосконаленій версії ліквідовано.


+1 Я нарешті обійшов прочитати ці дві статті. Оце Так! Що за прочитане! Дуже освічуюче. Цей хлопець - гуру баш ! Кудо! Це також відповідає моєму коментарю до відповіді Дмитра про те, чи $BASH_COMMANDможна використовувати сенс у контексті, відмінному від trap. І яке це користь! Використовуючи його для реалізації макрокоманд у bash - хто вважав би це можливим? Дякуємо за вказівник.
Malte Skoruppa

0

Начебто поширене використання для налагодження пастки ... полягає в поліпшенні заголовків, які відображаються у списку вікон (^ A "), коли ви використовуєте" екран ".

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

Я виявив, що метод, що використовує PROMPT_COMMAND для надсилання "нульової заголовкової послідовності", не працює так добре, тому я повернувся до методу налагодження пастки ...

Ось як це зробити:

1 Скажіть "екрану" шукати послідовність втечі (увімкніть його), додавши "shelltitle '$ | bash:'" у "$ HOME / .screenrc".

2 Вимкніть розповсюдження пастки для налагодження в підкорпуси. Це важливо, оскільки якщо це ввімкнено, все мутиться, використовуйте: "set + o functrace".

3 Відправте послідовність заголовка заголовка для "екрана" для інтерпретації: trap 'printf "\ ek $ (дата +% Y% m% d% H% M% S) $ (whoami) @ $ (ім'я хоста): $ (pwd) $ {BASH_COMMAND} \ e \ "" "ДЕБУГ"

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

Дивіться наступний "список вікон" (5 екранів):

Прапори з назвою числа

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 котячий кошик / psg.sh $ 4 bash: 20161115222415 mcb @ ken007: / home / mcb / ken007 ssh ken009 $ 5 баш: 20161115222450 mcb @ ken007: / home / mcb / ken007 mycommoncleanup $

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