Pomijać śledzenie wykonania dla polecenia echo?


29

Używam skryptów powłoki od Jenkins, które uruchamiają skrypty powłoki z opcjami shebang #!/bin/sh -ex.

Według Bash Shebang dla manekinów? , -x„powoduje, że powłoka drukuje ślad wykonania”, co jest świetne w większości celów - z wyjątkiem ech:

echo "Message"

produkuje dane wyjściowe

+ echo "Message"
Message

co jest trochę zbędne i wygląda trochę dziwnie. Czy można pozostawić -xwłączone, ale tylko wyjście

Message

zamiast dwóch wierszy powyżej, np. poprzedzając polecenie echa specjalnym znakiem polecenia lub przekierowując wyjście?

Odpowiedzi:


20

Kiedy jesteś już przy szyi w aligatorach, łatwo zapomnieć, że celem było osuszenie bagna.                   - popularne powiedzenie

Pytanie dotyczy echo, a jednak większość dotychczasowych odpowiedzi koncentrowała się na tym, jak wprowadzić set +xpolecenie. Istnieje o wiele prostsze, bardziej bezpośrednie rozwiązanie:

{ echo "Message"; } 2> /dev/null

(Przyznaję, że nie pomyślałbym o tym, { …; } 2> /dev/null gdybym nie widział tego we wcześniejszych odpowiedziach).

Jest to nieco kłopotliwe, ale jeśli masz blok kolejnych echopoleceń, nie musisz robić tego dla każdego z osobna:

{
  echo "The quick brown fox"
  echo "jumps over the lazy dog."
} 2> /dev/null

Pamiętaj, że nie potrzebujesz średników, gdy masz nowe znaki.

Możesz zmniejszyć obciążenie związane z pisaniem, używając pomysłu kenorba na/dev/null stałe otwarcie na niestandardowym deskryptorze pliku (np. 3), a następnie mówienie 2>&3zamiast przez 2> /dev/nullcały czas.


Pierwsze cztery odpowiedzi w chwili pisania tego tekstu wymagają zrobienia czegoś specjalnego (i w większości przypadków kłopotliwego) za każdym razem, gdy robisz echo. Jeśli naprawdę chcesz, aby wszystkie echo polecenia pomijały ślad wykonania (a dlaczego byś tego nie zrobił?), Możesz to zrobić globalnie, bez mungowania dużej ilości kodu. Po pierwsze zauważyłem, że aliasy nie są śledzone:

$ myfunc()
> {
>     date
> }
$ alias myalias="date"
$ set -x
$ date
+ date
Mon, Oct 31, 2016  0:00:00 AM           # Happy Halloween!
$ myfunc
+ myfunc                                # Note that function call is traced.
+ date
Mon, Oct 31, 2016  0:00:01 AM
$ myalias
+ date                                  # Note that it doesn’t say  + myalias
Mon, Oct 31, 2016  0:00:02 AM

(Zauważ, że następujące fragmenty skryptu działają, jeśli shebang jest #!/bin/sh, nawet jeśli /bin/shjest linkiem do bash. Ale jeśli shebang jest #!/bin/bash, musisz dodać shopt -s expand_aliasespolecenie, aby aliasy działały w skrypcie.)

Tak więc dla mojej pierwszej sztuczki:

alias echo='{ set +x; } 2> /dev/null; builtin echo'

Teraz, kiedy mówimy echo "Message", wzywamy alias, który nie jest śledzony. Alias ​​wyłącza opcję śledzenia, jednocześnie tłumiąc komunikat śledzenia z setpolecenia (przy użyciu techniki przedstawionej najpierw w odpowiedzi użytkownika 5071535 ), a następnie wykonuje rzeczywiste echopolecenie. To pozwala nam uzyskać efekt podobny do odpowiedzi user5071535 bez konieczności edytowania kodu przy każdym echo poleceniu. Pozostawia to jednak tryb śledzenia wyłączony. Nie możemy wstawić a set -xdo aliasu (a przynajmniej niełatwo), ponieważ alias pozwala tylko na zastąpienie łańcucha słowem; żadna część ciągu aliasu nie może zostać wstrzyknięta do polecenia po argumentach (np "Message".). Na przykład, jeśli skrypt zawiera

date
echo "The quick brown fox"
echo "jumps over the lazy dog."
date

wynik byłby

+ date
Mon, Oct 31, 2016  0:00:03 AM
The quick brown fox
jumps over the lazy dog.
Mon, Oct 31, 2016  0:00:04 AM           # Note that it doesn’t say  + date

więc nadal musisz włączyć opcję śledzenia po wyświetleniu komunikatu (komunikatów) - ale tylko raz po każdym bloku kolejnych echopoleceń:

date
echo "The quick brown fox"
echo "jumps over the lazy dog."
set -x
date


Byłoby miło , gdybyśmy mogli zrobić set -xautomatykę po echo- i możemy, przy odrobinie więcej sztuczek. Ale zanim to przedstawię, zastanów się nad tym. OP zaczyna się od skryptów wykorzystujących #!/bin/sh -exshebang. W domyśle użytkownik może usunąć xz shebang i mieć skrypt, który działa normalnie, bez śledzenia wykonania. Byłoby miło, gdybyśmy mogli opracować rozwiązanie, które zachowałoby tę właściwość. Kilka pierwszych odpowiedzi tutaj zawodzi tej właściwości, ponieważ echobezwarunkowo włączają śledzenie instrukcji po , bez względu na to, czy była już włączona.  Ta odpowiedź wyraźnie nie rozpoznaje tego problemu, ponieważ zastępuje echodane wyjściowe z danymi wyjściowymi śledzenia; dlatego wszystkie wiadomości znikają, jeśli śledzenie jest wyłączone. Przedstawię teraz rozwiązanie, które warunkowo włącza śledzenie po echoinstrukcji - tylko jeśli była już włączona. Obniżenie tego poziomu do rozwiązania, które bezwarunkowo odwraca śledzenie, jest trywialne i pozostawia się jako ćwiczenie.

alias echo='{ save_flags="$-"; set +x;} 2> /dev/null; echo_and_restore'
echo_and_restore() {
        builtin echo "$*"
        case "$save_flags" in
         (*x*)  set -x
        esac
}

$-jest lista opcji; konkatenacja liter odpowiadających wszystkim ustawionym opcjom. Na przykład, jeśli ustawione są opcje ei x, to $-będzie mieszanka liter zawierająca ei x. Mój nowy alias (powyżej) zapisuje wartość $-przed wyłączeniem śledzenia. Następnie przy wyłączonym śledzeniu przekazuje kontrolę nad funkcją powłoki. Ta funkcja wykonuje rzeczywistą operację, echo a następnie sprawdza, czy xopcja została włączona po wywołaniu aliasu. Jeśli opcja była włączona, funkcja ponownie ją włącza; jeśli był wyłączony, funkcja go wyłącza.

Możesz wstawić powyższe siedem wierszy (osiem, jeśli dołączasz an shopt) na początku skryptu, a resztę pozostawić w spokoju.

To by ci pozwoliło

  1. aby użyć dowolnej z następujących linii shebang:
    #! / bin / sh -ex
    #! / bin / sh -e
    #! / bin / sh –x
    lub po prostu
    #! / bin / sh
    i powinno działać zgodnie z oczekiwaniami.
  2. mieć podobny kod
    (shebang) 
    polecenie 1
     polecenie 2
     polecenie 3
    ustaw -x
    polecenie 4
     polecenie 5
     polecenie 6
    ustaw + x
    polecenie 7
     polecenie 8
     polecenie 9
    i
    • Polecenia 4, 5 i 6 będą śledzone - chyba że jedno z nich jest echo, w takim przypadku będzie wykonywane, ale nie śledzone. (Ale nawet jeśli polecenie 5 jest echo, polecenie 6 nadal będzie śledzone.)
    • Polecenia 7, 8 i 9 nie będą śledzone. Nawet jeśli polecenie 8 jest echo, polecenie 9 nadal nie będzie śledzone.
    • Polecenia 1, 2 i 3 będą śledzone (jak 4, 5 i 6) lub nie (jak 7, 8 i 9) w zależności od tego, czy zawiera shebang x.

PS Odkryłem, że w moim systemie mogę pominąć builtinsłowo kluczowe w środkowej odpowiedzi (tej, dla której jest to tylko alias echo). Nie jest to zaskakujące; bash (1) mówi, że podczas rozwijania aliasu…

… Słowo identyczne z rozwijanym aliasem nie jest rozszerzane po raz drugi. Oznacza to, że jeden może alias lsdo ls -F, na przykład, a bash nie próbuje rekursywnie rozwiń tekst.

Nic dziwnego, że ostatnia odpowiedź (ta z echo_and_restore) kończy się niepowodzeniem, jeśli builtinsłowo kluczowe zostanie pominięte 1 . Ale dziwnie to działa, jeśli usunę builtini zmienię kolejność:

echo_and_restore() {
        echo "$*"
        case "$save_flags" in
         (*x*)  set -x
        esac
}
alias echo='{ save_flags="$-"; set +x;} 2> /dev/null; echo_and_restore'

__________
1  Wydaje się, że powoduje niezdefiniowane zachowanie. widziałem

  • nieskończona pętla (prawdopodobnie z powodu nieograniczonej rekurencji),
  • /dev/null: Bad addresskomunikat o błędzie i
  • zrzut rdzenia.

2
Widziałem niesamowite sztuczki magiczne z aliasami, więc wiem, że moja wiedza na ich temat jest niepełna. Jeśli ktoś może przedstawić sposób na zrobienie tego echo +x; echo "$*"; echo -xw aliasie, chciałbym to zobaczyć.
G-Man mówi „Przywróć Monikę”

11

Znalazłem częściowe rozwiązanie w InformIT :

#!/bin/bash -ex
set +x; 
echo "shell tracing is disabled here"; set -x;
echo "but is enabled here"

wyjścia

set +x; 
shell tracing is disabled here 
+ echo "but is enabled here"
but is enabled here

Niestety, to wciąż odbija się echem set +x , ale przynajmniej jest potem cicho. więc jest to przynajmniej częściowe rozwiązanie problemu.

Ale czy może być lepszy sposób na zrobienie tego? :)


2

W ten sposób ulepszysz własne rozwiązanie, pozbywając się set +xwyników:

#!/bin/bash -ex
{ set +x; } 2>/dev/null
echo "shell tracing is disabled here"; set -x;
echo "but is enabled here"

2

Położyć set +x w nawiasach, aby dotyczyło to tylko zakresu lokalnego.

Na przykład:

#!/bin/bash -x
exec 3<> /dev/null
(echo foo1 $(set +x)) 2>&3
($(set +x) echo foo2) 2>&3
( set +x; echo foo3 ) 2>&3
true

wyprowadziłby:

$ ./foo.sh 
+ exec
foo1
foo2
foo3
+ true

Popraw mnie, jeśli się mylę, ale nie sądzę, aby set +xwnętrze podpowłoki (lub w ogóle używanie podpowłoki) robiło coś pożytecznego. Możesz go usunąć i uzyskać ten sam wynik. Jest to przekierowanie stderr do / dev / null, które tymczasowo „wyłącza” śledzenie ... Wydaje się echo foo1 2>/dev/null, że itd. Byłoby równie skuteczne i bardziej czytelne.
Tyler Rick,

Śledzenie w skrypcie może mieć wpływ na wydajność. Po drugie, przekierowanie i 2 do NULL może nie być takie samo, gdy oczekujesz innych błędów.
kenorb,

Popraw mnie, jeśli się mylę, ale w twoim przykładzie masz już włączone śledzenie w swoim skrypcie (z bash -x) i już przekierowujesz &2na null (ponieważ &3został przekierowany na null), więc nie jestem pewien, jak ważny jest ten komentarz. Być może potrzebujemy tylko lepszego przykładu, który ilustruje twój punkt widzenia, ale przynajmniej w podanym przykładzie wydaje się, że można go uprościć bez utraty korzyści.
Tyler Rick,

1

Uwielbiam kompleksową i dobrze wyjaśnioną odpowiedź g-mana i uważam ją za najlepszą z dotychczasowych. Dba o kontekst skryptu i nie wymusza konfiguracji, gdy nie są one potrzebne. Tak więc, jeśli czytasz tę odpowiedź, sprawdź ją, wszystkie zalety są.

Jednak w tej odpowiedzi brakuje ważnego fragmentu: proponowana metoda nie będzie działać dla typowego przypadku użycia, tj. Zgłaszania błędów:

COMMAND || echo "Command failed!"

Ze względu na sposób budowy aliasu zostanie on rozszerzony do

COMMAND || { save_flags="$-"; set +x; } 2>/dev/null; echo_and_restore "Command failed!"

i zgadliście, echo_and_restorezostajecie straceni zawsze bezwarunkowo. Ponieważ set +xczęść nie uruchomiła się, oznacza to, że treść tej funkcji również zostanie wydrukowana.

Zmiana ostatniego ;na &&nie działałaby również, ponieważ w Bash ||i &&lewostronne .

Znalazłem modyfikację, która działa dla tego przypadku użycia:

echo_and_restore() {
    echo "$(cat -)"
    case "$save_flags" in
        (*x*) set -x
    esac
}
alias echo='({ save_flags="$-"; set +x; } 2>/dev/null; echo_and_restore) <<<'

Używa podpowłoki ( (...)części) w celu grupowania wszystkich poleceń, a następnie przekazuje ciąg wejściowy przez stdin jako ciąg tutaj ( <<<rzecz), który jest następnie drukowany przez cat -. Jest -to opcjonalne, ale wiesz, „ wyraźne jest lepsze niż niejawne ”.

Możesz także użyć cat -bezpośrednio bez echa , ale podoba mi się to, ponieważ pozwala mi dodawać inne ciągi do wyniku. Na przykład zwykle używam tego w następujący sposób:

BASENAME="$(basename "$0")"  # Complete file name
...
echo "[${BASENAME}] $(cat -)"

A teraz działa pięknie:

false || echo "Command failed"
> [test.sh] Command failed

0

Śledzenie wykonania idzie do stderr, filtruj w ten sposób:

./script.sh 2> >(grep -v "^+ echo " >&2)

Kilka wyjaśnień, krok po kroku:

  • stderr zostaje przekierowany… - 2>
  • … Na polecenie. ->(…)
  • grep to polecenie…
  • … Co wymaga rozpoczęcia linii… - ^
  • … A następnie + echo
  • … Następnie grepodwraca dopasowanie… --v
  • … A to odrzuca wszystkie linie, których nie chcesz.
  • Wynik zwykle byłby przeznaczony na stdout; przekierowujemy go stderrtam, gdzie należy. ->&2

Problemem jest (tak sądzę) to rozwiązanie może zsynchronizować strumienie. Z powodu filtrowania stderrmoże być trochę spóźniony w stosunku do stdout(gdzie echowyjście należy domyślnie). Aby to naprawić, możesz najpierw dołączyć do strumieni, jeśli nie masz nic przeciwko, aby oba były w stdout:

./script.sh > >(grep -v "^+ echo ") 2>&1

Możesz zbudować takie filtrowanie w samym skrypcie, ale takie podejście z pewnością jest podatne na desynchronizację (tj. Miało to miejsce w moich testach: ślad wykonania polecenia może pojawić się po wyjściu polecenia zaraz po nim echo).

Kod wygląda następująco:

#!/bin/bash -x

{
 # original script here
 # …
} 2> >(grep -v "^+ echo " >&2)

Uruchom go bez żadnych sztuczek:

./script.sh

Ponownie użyj, > >(grep -v "^+ echo ") 2>&1aby utrzymać synchronizację kosztem dołączenia do strumieni.


Inne podejście. Otrzymujesz „nieco redundantne” i dziwnie wyglądające wyjście, ponieważ twój terminal miksuje stdoutistderr . Te dwa strumienie są z różnych powodów zwierzętami. Sprawdź, czy analiza stderrpasuje tylko do twoich potrzeb; odrzucić stdout:

./script.sh > /dev/null

Jeśli masz w skrypcie echokomunikat o błędzie drukowania / błędu stderr, możesz pozbyć się nadmiarowości w sposób opisany powyżej. Pełna komenda:

./script.sh > /dev/null 2> >(grep -v "^+ echo " >&2)

Tym razem pracujemy stderrtylko, więc desynchronizacja nie stanowi już problemu. Niestety w ten sposób nie zobaczysz śladu ani wyników echotych wydruków do stdout(jeśli w ogóle). Możemy spróbować odbudować nasz filtr w celu wykrycia przekierowania ( >&2), ale jeśli spojrzysz na niego echo foobar >&2, echo >&2 foobara echo "foobar >&2"następnie prawdopodobnie zgodzisz się, że wszystko się komplikuje.

Wiele zależy od echa, które masz w swoich skryptach. Zastanów się dwa razy, zanim zaimplementujesz jakiś złożony filtr, może się on odwrócić. Lepiej mieć trochę redundancji niż przypadkowo pominąć niektóre kluczowe informacje.


Zamiast odrzucić ślad wykonania echomożemy odrzucić jego wynik - i każdy wynik oprócz śladów. Aby analizować tylko ślady wykonania, spróbuj:

./script.sh > /dev/null 2> >(grep "^+ " >&2)

Niezawodny? Nie. Pomyśl, co się stanie, jeśli jest echo "+ rm -rf --no-preserve-root /" >&2w skrypcie. Ktoś może dostać zawału serca.


I w końcu…

Na szczęście istnieje BASH_XTRACEFDzmienna środowiskowa. Od man bash:

BASH_XTRACEFD
Jeśli zostanie ustawiony na liczbę całkowitą odpowiadającą prawidłowemu deskryptorowi pliku, bash zapisze dane wyjściowe śledzenia wygenerowane, gdy set -xzostanie włączony do tego deskryptora pliku.

Możemy użyć tego w następujący sposób:

(exec 3>trace.txt; BASH_XTRACEFD=3 ./script.sh)
less trace.txt

Uwaga: pierwsza linia odradza podpowłokę. W ten sposób deskryptor pliku nie pozostanie ważny ani zmienna przypisana później w bieżącej powłoce.

Dzięki temu BASH_XTRACEFDmożesz analizować ślady wolne od echa i wszelkich innych danych wyjściowych, niezależnie od tego, jakie mogą być. To nie jest dokładnie to, czego chciałeś, ale moja analiza sprawia, że ​​myślę, że to (ogólnie) właściwa droga.

Oczywiście możesz użyć innej metody, szczególnie gdy potrzebujesz analizować stdouti / lub stderrśledzić swoje ślady. Trzeba tylko pamiętać, że istnieją pewne ograniczenia i pułapki. Próbowałem je pokazać.


0

W Makefile możesz użyć tego @symbolu.

Przykładowe użycie: @echo 'message'

Z tego dokumentu GNU:

Kiedy linia zaczyna się od „@”, echo tej linii jest tłumione. Znak „@” jest odrzucany przed przekazaniem wiersza do powłoki. Zwykle używasz tego do polecenia, którego jedynym efektem jest wydrukowanie czegoś, takiego jak polecenie echa, aby wskazać postęp przez plik makefile.


Cześć, dokument, do którego się odwołujesz, dotyczy gnu make, a nie powłoki. Jesteś pewien, że to działa? Pojawia się błąd ./test.sh: line 1: @echo: command not found, ale używam bash.

Wow, przepraszam, że całkowicie źle odczytałem pytanie. Tak, @działa tylko wtedy, gdy echo w plikach makefile.
derek

@derek Zredagowałem twoją oryginalną odpowiedź, więc teraz wyraźnie stwierdza, że ​​rozwiązanie jest ograniczone tylko do plików Makefiles. Tak naprawdę tego szukałem, więc chciałem, aby twój komentarz nie miał negatywnej reputacji. Mamy nadzieję, że ludzie również uznają to za pomocne.
Shakaron,

-1

Edytowano 29 października 2016 na moderatora sugestie, że oryginał nie zawierał wystarczających informacji, aby zrozumieć, co się dzieje.

Ta „sztuczka” tworzy tylko jeden wiersz wyjścia komunikatu na terminalu, gdy xtrace jest aktywne:

Pierwotne pytanie brzmi: czy istnieje sposób, aby pozostawić włączone -x, ale wypisuje tylko komunikat zamiast dwóch wierszy . To jest dokładny cytat z pytania.

Rozumiem pytanie, jak „pozostawić włączone ustawienie -x ORAZ utworzyć pojedynczą linię dla wiadomości”?

  • W sensie globalnym to pytanie dotyczy przede wszystkim estetyki - pytający chce stworzyć pojedynczą linię zamiast dwóch praktycznie zduplikowanych linii wytworzonych, gdy xtrace jest aktywny.

Podsumowując, PO wymaga:

  1. Aby mieć set -x w efekcie
  2. Wyprodukuj wiadomość czytelną dla człowieka
  3. Utwórz tylko jeden wiersz wyjścia komunikatu.

OP nie wymaga użycia polecenia echo . Przytoczyli go jako przykład produkcji wiadomości, używając skrótu np. Co oznacza Latin exempli gratia lub „na przykład”.

Myśląc „nieszablonowo” i rezygnując z używania echa do generowania wiadomości, zauważam, że instrukcja przypisania może spełnić wszystkie wymagania.

Wykonaj przypisanie ciągu tekstowego (zawierającego wiadomość) do nieaktywnej zmiennej.

Dodanie tej linii do skryptu nie zmienia żadnej logiki, ale tworzy pojedynczy wiersz, gdy śledzenie jest aktywne: (Jeśli używasz zmiennej $ echo , po prostu zmień nazwę na inną nieużywaną zmienną)

echo="====================== Divider line ================="

Na terminalu zobaczysz tylko 1 linię:

++ echo='====================== Divider line ================='

nie dwa, jak PO nie lubi:

+ echo '====================== Divider line ================='
====================== Divider line =================

Oto przykładowy skrypt do zademonstrowania. Zauważ, że podstawienie zmiennych w komunikacie (nazwa katalogu $ HOME 4 linie od końca) działa, więc możesz śledzić zmienne za pomocą tej metody.

#!/bin/bash -exu
#
#  Example Script showing how, with trace active, messages can be produced
#  without producing two virtually duplicate line on the terminal as echo does.
#
dummy="====================== Entering Test Script ================="
if [[ $PWD == $HOME ]];  then
  dummy="*** "
  dummy="*** Working in home directory!"
  dummy="*** "
  ls -la *.c || :
else
  dummy="---- C Files in current directory"
  ls -la *.c || :
  dummy="----. C Files in Home directory "$HOME
  ls -la  $HOME/*.c || :
fi

A oto wyjście z uruchomienia go w katalogu głównym, a następnie w katalogu domowym.

$ cd /&&DemoScript
+ dummy='====================== Entering Test Script ================='
+ [[ / == /g/GNU-GCC/home/user ]]
+ dummy='---- C Files in current directory'
+ ls -la '*.c'
ls: *.c: No such file or directory
+ :
+ dummy='----. C Files in Home directory /g/GNU-GCC/home/user'
+ ls -la /g/GNU-GCC/home/user/HelloWorld.c /g/GNU-GCC/home/user/hw.c
-rw-r--r-- 1 user Administrators 73 Oct 10 22:21 /g/GNU-GCC/home/user/HelloWorld.c
-rw-r--r-- 1 user Administrators 73 Oct 10 22:21 /g/GNU-GCC/home/user/hw.c
+ dummy=---------------------------------

$ cd ~&&DemoScript
+ dummy='====================== Entering Test Script ================='
+ [[ /g/GNU-GCC/home/user == /g/GNU-GCC/home/user ]]
+ dummy='*** '
+ dummy='*** Working in home directory!'
+ dummy='*** '
+ ls -la HelloWorld.c hw.c
-rw-r--r-- 1 user Administrators 73 Oct 10 22:21 HelloWorld.c
-rw-r--r-- 1 user Administrators 73 Oct 10 22:21 hw.c
+ dummy=---------------------------------

1
Uwaga: nawet zredagowana wersja usuniętej odpowiedzi (której jest to kopia) nadal nie odpowiada na pytanie, ponieważ odpowiedź nie wyświetla tylko wiadomości bez powiązanego polecenia echo, o co poprosił OP.
DavidPostill


@David Poszerzyłem wyjaśnienie na komentarze na meta forum.
HiTechHiTouch

@David Znowu zachęcam do bardzo uważnego przeczytania OP, zwracając uwagę na łacińskie „.eg”. Plakat NIE wymaga użycia polecenia echo. PO odnosi się tylko do echa jako przykładu (wraz z przekierowaniem) potencjalnych metod rozwiązania problemu. Okazuje się, że ty też nie potrzebujesz,
HiTechHiTouch

A co jeśli OP chce zrobić coś takiego echo "Here are the files in this directory:" *?
G-Man mówi „Przywróć Monikę”
Korzystając z naszej strony potwierdzasz, że przeczytałeś(-aś) i rozumiesz nasze zasady używania plików cookie i zasady ochrony prywatności.
Licensed under cc by-sa 3.0 with attribution required.