Do czego służy zmienna $ BASH_COMMAND?

25

Zgodnie z instrukcją Bash zmienna środowiskowa BASH_COMMANDzawiera

Komenda aktualnie wykonywana lub planowana do wykonania, chyba że powłoka wykonuje komendę w wyniku pułapki, w którym to przypadku jest to komenda wykonywana w chwili pułapki.

Pomijając tę ​​narożną wielkość liter, jeśli dobrze rozumiem, oznacza to, że kiedy wykonuję polecenie, zmienna BASH_COMMANDzawiera to polecenie. Nie jest absolutnie jasne, czy zmienna ta jest rozbrojona po wykonaniu polecenia (tj. Jest dostępna tylko podczas wykonywania polecenia, ale nie później), choć można argumentować, że ponieważ jest to „polecenie aktualnie wykonywane lub wkrótce wykonywane” , to nie polecenie zostało właśnie wykonane.

Ale sprawdźmy:

$ set | grep BASH_COMMAND=
$ 

Pusty. Spodziewałbym się zobaczyć, BASH_COMMAND='set | grep BASH_COMMAND='a może po prostu BASH_COMMAND='set', ale pusty mnie zaskoczył.

Spróbujmy czegoś innego:

$ echo $BASH_COMMAND
echo $BASH_COMMAND
$ 

To ma sens. Wykonuję polecenie, echo $BASH_COMMANDwięc zmienna BASH_COMMANDzawiera ciąg echo $BASH_COMMAND. Dlaczego tym razem zadziałało, ale nie wcześniej?

Zróbmy to jeszcze setraz:

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

Więc poczekaj. To było ustawione, że kiedy wykonywane echopolecenie, a on nie był wyłączony później. Ale kiedy wykonałem setponownie, BASH_COMMAND nie został ustawiony na setpolecenie. Bez względu na to, jak często wykonuję setpolecenie tutaj, wynik pozostaje taki sam. Czy zmienna jest ustawiona podczas wykonywania echo, ale nie podczas wykonywania set? Zobaczmy.

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

Co? Więc zmienna została ustawiona, kiedy wykonałem echo $BASH_COMMAND, ale nie kiedy wykonałem echo Hello AskUbuntu? Jaka jest teraz różnica? Czy zmienna jest ustawiona tylko wtedy, gdy bieżące polecenie faktycznie zmusza powłokę do oceny zmiennej? Spróbujmy czegoś innego. Może tym razem jakieś zewnętrzne polecenie, a nie wbudowane bash, dla odmiany.

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

Hmm, ok ... znowu, zmienna została ustawiona. Czy moje obecne przypuszczenie jest prawidłowe? Czy zmienna jest ustawiana tylko wtedy, gdy należy ją ocenić? Czemu? Czemu? Z powodów wydajnościowych? Spróbujmy jeszcze raz. Spróbujemy grepować $BASH_COMMANDw pliku, a ponieważ $BASH_COMMANDpowinien zawierać greppolecenie, greppowinien grep dla tego greppolecenia (tj. Dla siebie). więc stwórzmy odpowiedni plik:

$ 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'
$

OK, ciekawe. Polecenie grep $BASH_COMMAND tmpzostało rozszerzone do grep grep $BASH_COMMAND tmp tmp(zmienna jest rozwinięta tylko raz, oczywiście), więc poszukałem grep, raz w pliku, $BASH_COMMANDktóry nie istnieje, i dwa razy w pliku tmp.

P1: Czy moje obecne założenie jest prawidłowe, że:

  • BASH_COMMANDjest ustawiany tylko wtedy, gdy polecenie próbuje to ocenić; i
  • to jest nie rozbroić po wykonaniu polecenia, choć opis może doprowadzić nas do przekonania, tak?

P2: Jeśli tak, dlaczego? Wydajność? Jeśli nie, jak inaczej można wyjaśnić zachowanie w powyższej sekwencji poleceń?

P3: Na koniec, czy jest jakiś scenariusz, w którym ta zmienna mogłaby być rzeczywiście użytecznie użyta? W rzeczywistości próbowałem użyć go w $PROMPT_COMMANDcelu przeanalizowania wykonywanego polecenia (i zrobić kilka rzeczy w zależności od tego), ale nie mogę, ponieważ jak tylko $PROMPT_COMMANDwykonam wewnątrz siebie polecenie, aby spojrzeć na zmienną $BASH_COMMAND, zmienną pobiera zestawy do tego polecenia. Nawet jeśli robię to MYVARIABLE=$BASH_COMMANDna samym początku $PROMPT_COMMAND, MYVARIABLEzawiera ciąg MYVARIABLE=$BASH_COMMAND, ponieważ przypisanie jest również poleceniem. (To pytanie nie dotyczy tego, w jaki sposób mogę uzyskać bieżące polecenie w ramach $PROMPT_COMMANDwykonania. Wiem, że istnieją inne sposoby).

To trochę tak, jak w przypadku zasady nieoznaczoności Heisenberga. Po prostu obserwując zmienną, zmieniam ją.

Malte Skoruppa
źródło
5
Fajnie, ale prawdopodobnie lepiej nadaje się na unix.stackexchange.com ... są tam prawdziwi bashguru über-guru.
Rmano
Czy można go tam przenieść? Mody?
Malte Skoruppa,
Naprawdę nie jestem pewien, jak to zrobić --- Myślę, że to przywilej modyfikacji. Z drugiej strony nie wydaje mi się, żeby oflagowanie było rozsądne - zobaczmy, czy ktoś działa na flagę close.
Rmano
1
Po trzecie, właśnie tego szukasz. Być może na podstawie tego artykułu można znaleźć odpowiedź również na pierwsze dwa punkty.
Radu Rădeanu
2
+1 wprawiło mnie w zakłopotanie, ale fajnie było czytać!
Joe

Odpowiedzi:

15

Odpowiadając na trzecie pytanie: oczywiście można go w znaczący sposób wykorzystać w sposób, w jaki instrukcja Bash wyraźnie wskazuje - w pułapce, np .:

$ 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
Dmitrij Aleksandrow
źródło
Ach, naprawdę miło. Czy powiedziałbyś, że pułapka jest jedynym kontekstem, w którym ta zmienna ma sens? W instrukcji brzmiało to jak przypadek narożny: „ Wykonywane polecenie (...), chyba że powłoka wykonuje polecenie w wyniku pułapki, ...”
Malte Skoruppa
Po przeczytaniu artykułów połączonych w odpowiedzi przez @DocSalvager, jasne jest, że $BASH_COMMANDrzeczywiście można go sensownie wykorzystać nawet w kontekstach innych niż pułapka. Jednak opisywane zastosowanie jest prawdopodobnie najbardziej typowe i proste. Więc twoja odpowiedź, i ta autorstwa DocSalvager, odpowiedzą najlepiej na mój trzeci kwartał , a to było dla mnie najważniejsze. Co do Q1 i Q2, jak wskazują @zwets, te rzeczy są niezdefiniowane. Może być tak, że $BASH_COMMANDpodana jest tylko wartość, jeśli jest dostępna, dla wydajności - w przeciwnym razie niektóre dziwne wewnętrzne warunki programu mogą prowadzić do takiego zachowania. Kto wie?
Malte Skoruppa,
1
Na marginesie odkryłem, że możesz wymusić $BASH_COMMANDustawienie zawsze, wymuszając ocenę $BASH_COMMANDprzed każdym poleceniem. Aby to zrobić, możemy użyć pułapki DEBUG, która jest wykonywana przed każdym pojedynczym poleceniem (wszystkimi em). Jeśli wydamy np., trap 'echo "$BASH_COMMAND" > /dev/null' DEBUGWtedy $BASH_COMMANDzawsze będzie oceniany przed każdym poleceniem (i przypisywana jest jego wartość $COMMAND). Więc jeśli wykonam, set | grep BASH_COMMAND=gdy pułapka jest aktywna ... co wiesz? Teraz wynik jest BASH_COMMAND=set, zgodnie z oczekiwaniami :)
Malte Skoruppa,
6

Teraz, kiedy Q3 zostało już odebrane (poprawnie, moim zdaniem: BASH_COMMANDjest przydatne w pułapkach i prawie nigdzie indziej), dajmy szansę Q1 i Q2.

Odpowiedź na pytanie 1 brzmi: poprawność twojego założenia jest nierozstrzygalna. Nie można ustalić prawdziwości żadnego z punktów, ponieważ pytają o nieokreślone zachowanie. Zgodnie ze specyfikacją wartość parametru BASH_COMMANDjest ustawiona na tekst polecenia na czas wykonywania tego polecenia. Specyfikacja nie określa, jaka powinna być jej wartość w żadnej innej sytuacji, tj. Gdy nie jest wykonywane żadne polecenie. Może mieć dowolną wartość lub nie mieć jej wcale.

Odpowiedź na pytanie 2 „Jeśli nie, jak inaczej można wyjaśnić zachowanie w powyższej sekwencji poleceń?” następnie następuje logicznie (choć nieco pedantycznie): tłumaczy to fakt, że wartość nie BASH_COMMANDjest zdefiniowana. Ponieważ jego wartość jest niezdefiniowana, może mieć dowolną wartość, co dokładnie pokazuje sekwencja.

Postscriptum

Jest jeden punkt, w którym myślę, że rzeczywiście trafiasz na słabość w specyfikacji. To tam mówisz:

Nawet jeśli wykonam MYVARIABLE = $ BASH_COMMAND na początku mojego $ PROMPT_COMMAND, wtedy MYVARIABLE zawiera ciąg MYVARIABLE = $ BASH_COMMAND, ponieważ przypisanie jest również poleceniem .

Sposób, w jaki czytam stronę podręcznika użytkownika bash, nieco kursywą jest nieprawdziwy. Sekcja SIMPLE COMMAND EXPANSIONwyjaśnia, w jaki sposób najpierw odkładane są przypisania zmiennych w wierszu poleceń, a następnie

Jeśli żadna nazwa polecenia nie powoduje [innymi słowy, były tylko przypisania zmiennych], przypisania zmiennych wpływają na bieżące środowisko powłoki.

To sugeruje mi, że przypisania zmiennych nie są poleceniami (i dlatego są zwolnione z pokazywania się w BASH_COMMAND), jak w innych językach programowania. To wyjaśniałoby również, dlaczego nie ma linii BASH_COMMAND=setna wyjściu set, co setzasadniczo jest składniowym „znacznikiem” dla przypisania zmiennej.

OTOH, w ostatnim akapicie tego rozdziału jest napisane

Jeśli po rozwinięciu pozostała nazwa polecenia, wykonywanie przebiega zgodnie z opisem poniżej. W przeciwnym razie polecenie zakończy się.

... co sugeruje inaczej, a przypisania zmiennych są również poleceniami.

zwets
źródło
1
To, że nie można ustalić jakiegoś założenia, nie oznacza, że ​​jest ono nieprawidłowe. Einstein założył, że prędkość światła jest taka sama dla każdego obserwatora (przy dowolnej prędkości), jak jedna podstawowa hipoteza teorii względności; tego nie da się ustalić, ale to nie znaczy, że jest niepoprawne. Właściwości „można ustalić” i „jest poprawny” są ortogonalne. W najlepszym razie możesz powiedzieć „Nie wiemy, czy to prawda, ponieważ nie można tego ustalić”. Ponadto, jest określona: to obecny podczas wykonywania poleceń. Więc jeśli setBASH_COMMAND='set'
napiszę,
... jednak tak nie jest. Mimo, że dzieje się to podczas wykonywania setpolecenia . Dlatego zgodnie ze specyfikacjami powinno działać. Czy więc implementacja nie jest wiernie zgodna ze specyfikacjami, czy też źle ją rozumiem? Znalezienie odpowiedzi na to pytanie jest swego rodzaju celem pytania pierwszego. +1 za twój
postScript
@MalteSkoruppa Dobre punkty. Naprawiłem odpowiednio odpowiedź i dodałem uwagę na temat set.
zwets
1
Możliwe, że w interpretatorze bash setnie jest „poleceniem”. Tak jak =nie jest to „polecenie”. Przetwarzanie tekstu następującego / otaczającego te tokeny może mieć zupełnie inną logikę niż przetwarzanie „polecenia”.
DocSalvager,
Dobre punkty od was obu. :) Być może setnie liczy się to jako „polecenie”. Nie $BASH_COMMANDsądziłbym, że w wielu okolicznościach okaże się to tak nieokreślone.
Wydaje
2

Pomysłowe zastosowanie dla $ BASH_COMMAND

Ostatnio odkryłem to imponujące zastosowanie $ BASH_COMMAND we wdrażaniu funkcji podobnej do makra.

Jest to podstawowa sztuczka aliasu i zastępuje użycie pułapki DEBUG. Jeśli przeczytasz część z poprzedniego postu dotyczącą pułapki DEBUG, rozpoznasz zmienną $ BASH_COMMAND. W tym poście powiedziałem, że jest ustawiony na tekst polecenia przed każdym wywołaniem pułapki DEBUG. Okazuje się, że jest ustawiony przed wykonaniem każdego polecenia, pułapki DEBUG lub nie (np. Uruchom „echo” to polecenie = $ BASH_COMMAND ”, aby zobaczyć o czym mówię). Przypisując jej zmienną (tylko dla tej linii), przechwytujemy BASH_COMMAND w najbardziej zewnętrznym zakresie polecenia, który będzie zawierał całe polecenie.

Poprzedni artykuł autora zapewnia również dobre tło podczas wdrażania techniki za pomocą DEBUGI trap. Został trapwyeliminowany w ulepszonej wersji.

DocSalvager
źródło
+1 W końcu udało mi się przeczytać te dwa artykuły. Łał! Co za lektura! Bardzo pouczające. Ten facet jest guru bash ! Sława! Odpowiada to również na mój komentarz do odpowiedzi Dmitry'ego na temat tego, czy $BASH_COMMANDmożna go sensownie wykorzystać w kontekście innym niż trap. Cóż to za użytek! Używanie go do wdrażania makropoleceń w bash - kto by pomyślał, że to możliwe? Dzięki za wskaźnik.
Malte Skoruppa,
0

Pozornie powszechne zastosowanie pułapki ... debugowanie polega na poprawianiu tytułów pojawiających się w oknie dialogowym (^ A ”) podczas korzystania z„ screen ”.

Próbowałem uczynić „screen”, „windowlist” bardziej użytecznym, więc zacząłem znaleźć artykuły odnoszące się do „trap… debugowania”.

Odkryłem, że metoda wykorzystująca PROMPT_COMMAND do wysłania „pustej sekwencji tytułowej” nie działała tak dobrze, więc wróciłem do metody pułapki… debugowania.

Oto jak to zrobić:

1 Powiedz „screen”, aby szukał sekwencji ucieczki (włącz ją), umieszczając „shelltitle” $ | bash: '”w„ $ HOME / .screenrc ”.

2 Wyłącz propagację pułapki debugowania w podpowłokach. Jest to ważne, ponieważ jeśli jest włączone, wszystko się psuje, użyj: „set + o funkrace”.

3 Wyślij sekwencję zmiany znaczenia dla „ekranu” do interpretacji: pułapka „printf” \ ek $ (data +% Y% m% d% H% M% S) $ (whoami) @ $ (nazwa hosta): $ (pwd) $ {BASH_COMMAND} \ e \ "" „DEBUG”

To nie jest idealne, ale pomaga, i możesz użyć tej metody, aby dosłownie dodać do tytułu dosłownie wszystko, co chcesz

Zobacz następujący „windowlist” (5 ekranów):

Num Nazwa Flagi

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 mbb @ 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 $

Malcolm Boekhoff
źródło