Zaloguj kod wyjścia polecenia, podobny do polecenia czasu

10

za pomocą

time sleep 1

daje:

$ time sleep 1

real    0m1.005s
user    0m0.001s
sys     0m0.001s

czy istnieje polecenie, którego mogę użyć do wydrukowania kodu wyjścia, sleeplub dowolne polecenie, które chcę uruchomić?

Coś jak:

$ log-exit-code sleep 1

może to wystarczy?

sleep 1 && echo "$?"
Alexander Mills
źródło
3
Nie, sleep 1 && echo $?wydrukowałby kod komórki sypialnej tylko wtedy, gdy będzie zero ...
Satō Katsura
och lol masz rację
Alexander Mills,
1
sleep 1 && echo "$?" || echo "$?"
jesse_b
2
Dodałem odpowiedź, ponieważ wszystkie pozostałe do tej pory odrzucają pierwotną wartość zwracaną, która imo jest zła (po tych odpowiedziach $? Jest zawsze 0, ponieważ ostatnią rzeczą, którą robią, jest dysply, która zawsze działa. Wszyscy zapomnieli zachować oryginalną zwracaną wartość i zwracają ją po jej wyświetleniu. Dlatego pokazują ją tylko obserwatorowi, ale „ukrywają” do końca skryptu, który nie może teraz użyć „$?”, aby sprawdzić, czy to ważne polecenie zwróci 0 lub coś jeszcze ...)
Olivier Dulac
1
Również wszystkie poniższe rozwiązania mogą łatwo wdrożyć tę funkcjonalność. Na przykład Kusalananda to po prostu:EXIT_CODE=$(tellexit command)
jesse_b

Odpowiedzi:

4

W moich dotychczasowych testach zadziałało:

command && echo "$?" || echo "$?"

Po prostu każe powtórzyć kod wyjścia, jeśli się powiedzie lub jeśli się nie powiedzie.

Jak wskazał Sato poniżej, jest to w zasadzie to samo, co:

command; echo "$?"

Jedną z rzeczy, które mogą sprawić, że wartościowe i / lub polecenia będą warte zachodu, są:

command && echo "Success! Exit Code: $?" || echo "Failure! Exit Code: $?"

Jeśli potrzebujesz skryptu, aby działał na kodzie wyjścia, co jest problemem Oliviera, nie jest to problem. Twój skrypt może wyglądać mniej więcej tak:

command
case "$?" in; 
    0) echo "Command exited with: 0"
       <command if good>
       ;;
    1) echo "Command exited with: 1"
        <command if bad>
        ;;
    255) echo "Command exited with: 255"  # for ssh maybe?
         <command if 255>
         ;;
    *) echo "Command exited with: >2"
        <command for other exit code>
        ;;
esac
jesse_b
źródło
13
Err, jak to jest lepsze niż tylko command; echo $??
Satō Katsura,
4
Chyba nie. command; echo $?jest znacznie lepszą odpowiedzią.
jesse_b
uhhh wydaje się, że to najlepsza opcja, więc wybrałem ją jako odpowiedź, jeśli ktoś się nie zgadza, lmk
Alexander Mills,
@AlexanderMills Tylko ty wiesz, która jest najbardziej odpowiednia odpowiedź na twoje pytanie :-)
Kusalananda
1
Nie podoba mi się to: zmienia to kod zakończenia po, command ; echo $?ponieważ echo $?zawsze się to udaje, a zatem po nim nowa wartość $? jest teraz 0. Istnieje sposób na zachowanie oryginalnego kodu powrotu nawet po jego wyświetleniu, na wypadek, gdyby ten oryginalny kod przydał się później (na przykład: w skrypcie sprawdzenie kodu powrotu jest ważne przed podjęciem decyzji o kontynuacji). Zobacz moją odpowiedź na jedno z kilku rozwiązań (większość odpowiedzi tutaj można zmienić w ten sam sposób)
Olivier Dulac
11

cmd && echo "$?"nie działałby, ponieważ z konieczności drukowałby tylko zera ( echodziałałby tylko po pomyślnym zakończeniu poprzedniego polecenia).

Oto funkcja krótkiej powłoki:

tellexit () {
    "$@"

    local err="$?"
    printf 'exit code\t%d\n' "$err" >/dev/tty
    return "$err"
}

Spowoduje to wydrukowanie kodu wyjścia danego polecenia w podobny sposób jak timepolecenie.

$ tellexit echo "hello world"
hello world
exit code       0

$ tellexit false
exit code       1

Przez przekierowanie printfdo /dev/ttyw funkcji, możemy nadal korzystać tellexitz przekierowań bez uzyskiwania śmieci w naszych standardowych strumieni wyjściowych lub błędach:

$ tellexit bash -c 'echo hello; echo world >&2' >out 2>err
exit code       0
$ cat out
hello
$ cat err
world

Zapisując kod wyjścia w zmiennej, możemy zwrócić go wywołującemu:

$ tellexit false || echo 'failed'
exit code       1
failed

Bardziej zaawansowana wersja tej samej funkcji drukuje również sygnał, który zabił polecenie, jeśli kod wyjścia jest większy niż 128 (co oznacza, że ​​został zakończony z powodu sygnału):

tellexit () {
    "$@"

    local err="$?"

    if [ "$err" -gt 128 ]; then
        printf 'exit code\t%d (%s)\n' "$err" "$(kill -l "$err")" >/dev/tty
    else
        printf 'exit code\t%d\n' "$err" >/dev/tty
    fi

    return "$err"
}

Testowanie:

$ tellexit sh -c 'kill $$'
exit code       143 (TERM)

$ tellexit sh -c 'kill -9 $$'
Killed
exit code       137 (KILL)

( localRzecz wymaga ash/ pdksh/ bash/ zsh, lub możesz to zmienić, na typesetco rozumie także kilka innych powłok).

Kusalananda
źródło
fajnie, jak używałbyś tellexitu, pokaż to w akcji
Alexander Mills,
1
@AlexanderMills Zobacz aktualizację.
Kusalananda
2
Powinien zachować pierwotną wartość zwracaną i zwrócić ją na końcu.
Olivier Dulac
@OlivierDulac Naprawdę dobra obserwacja! Zmienię.
Kusalananda
7

Użyj funkcji otoki powłoki. Prawdopodobnie pod inną nazwą.

$ exito() { "$@"; echo $?; }
$ exito true
0
$ exito false
1
$ exito echo "test test"      
test test
0
$ 

(To oczywiście spowoduje uszkodzenie standardowego wyjścia, więc albo użyj tego, ttyco pokazano @Kusalananda, albo nie używaj go poza interaktywnymi kontekstami.)

Zeskakując w nieznane terytorium, niektóre powłoki mogą raportować status wszystkich poleceń w potoku, nie tylko ostatniego, np. W ZSH, jeśli chcesz zgłaszać awarie z całego potoku:

% TRAPZERR() { print >/dev/tty $pipestatus }
% perl -e 'exit 42' | perl -e 'exit 99'
42 99
% false | perl -e 'exit 42' | perl -e 'exit 99'
1 42 99
% perl -e 'exit 42' | perl -e 'exit 99' | true
% 

TRAPZERR w przeciwnym razie nie będzie uruchamiany, gdy nie wystąpi błąd (na zasadzie „brak wiadomości to dobra wiadomość”).

gałązka
źródło
1
Powinien zachować pierwotną wartość zwracaną i zwrócić ją na końcu. Wyświetlanie (printf, echo) prostej rzeczy (nie zaczyna się od „-” itp.) Zawsze działa, a po tym wyświetla się „$?” ma teraz wartość „0” podczas wyświetlania
Olivier Dulac
7

GNU timema taką opcję:

time -f %x sleep 1
0

Przechodzi przez kod wyjścia 1 , chyba że zabije go sygnał 2 :

$ /usr/bin/time -f %x sleep 1; echo $?
0
0

$ /usr/bin/time -f %x sleep 2x; echo $?
sleep: invalid time interval ‘2x’
Try 'sleep --help' for more information.
1
1

$ /usr/bin/time -f %x sleep 60s; echo $? # Press ^C before a minute elapses
0
2

Jeśli chcesz znać / radzić sobie z sytuacją zabicia sygnału, przekaż -vstderrowi łańcucha znaków i grep Command terminated by signal.


1 Podziękowania dla Oliviera Dulaca za zauważenie, że kod wyjścia przechodzi.
2 Ponadto dziękuję Stéphane Chazelas za wskazanie, że kod wyjścia sygnału zabicia nie przechodzi.

biskup
źródło
1
Ciekawe GNU tylko, ale interesujący, ponieważ ten (prawdopodobnie) zachowa oryginalny kod powrotu do końca pozostałej części kodu (tj. Nie zastąpi go „0” jak inne odpowiedzi)
Olivier Dulac
2
Zwraca 0jednak, jeśli polecenie zostanie zabite.
Stéphane Chazelas,
2
@bishop, zgadzam się, że 0 w tych przypadkach jest mniej niż idealne. Pamiętaj jednak, że nie ma ogólnej zgody co do tego, co zgłosić, gdy polecenie zostanie zabite przez sygnał. Zwracany numer sygnału 128 + jest używany przez kilka powłok. Widzisz także rzeczy takie jak 256+signumlub 384+signumnawet, signum/256lub signum/256+0.5jeśli rdzeń został wygenerowany (w rzeczywistości status zwracany przez waitpid()podzielone przez 256). Niektórzy podają tekstową reprezentację (jak sigintlub sigquit+core). Prawdopodobnie nadal warto dyskutować na grupie dyskusyjnej gnu.coreutils.bugs Zgadzam się.
Stéphane Chazelas,
3
(tak naprawdę timenie jest częścią coreutils, jest samodzielnym pakietem z własną listą błędów ([email protected]))
Stéphane Chazelas
1
@ bishop, system()tradycyjny awks, taki jak ten nadal utrzymywany przez Briana Kernighana (the kin awk) lub nawkSolaris, awkFreeBSD, które z tego wywodzą. awk 'BEGIN{print system("kill -s ABRT $$")}'wyprowadza, 0.523438gdy wielkość zrzutu rdzenia nie jest ograniczona. Zachowanie gawkzmieniło się ostatnio, z różnym zachowaniem z --traditionallub --posix(patrz także wartość zwracana close(cmd)po więcej zmian)
Stéphane Chazelas
2

Nie lubię wszystkich innych odpowiedzi (choć bardzo lubię ich spryt): wyświetlają kod wyjścia, ale również ZMIENIAJĄ. (wyświetlana część jest prawdziwa, stąd kod powrotu to 0)

Oto zmodyfikowana wersja:

do_and_tell () {
   "$@"
   returncode="$?"
   printf "Execution of : \n%s\n yelded a return code of: \n%s\n" "$*" "$returncode"
   return $returncode  # the interresting addition... keeps the commands return value.
}

## usage:

prompt$ do_and_tell true
Execution of : 
true
 yelded a return code of: 
0

prompt$ echo $?
0

prompt$ do_and_tell false
Execution of : 
false
 yelded a return code of: 
1

prompt$ echo $?
1
Olivier Dulac
źródło
w rzeczywistości odpowiedź @bishop jest jedyną, która zachowuje tę wartość, ale opiera się na wersji GNU, timektóra nie zawsze jest dostępna.
Olivier Dulac
2

Aby być bardziej niezawodnym, wyślij status wyjścia do osobnego FD, aby można go było obsługiwać niezależnie od stdout / stderr:

exit_status() {
    "$@"
    rv=$?
    echo >&3 "$rv"
    return "$rv"
}

# exit status and stdout to stdout:
> exit_status echo "hello" 3>&1
hello
0

# exit_status to stdout, stdout to /dev/null
> exit_status echo "hello" 3>&1 1>/dev/null
0
> exit_status ehco "hello" 3>&1 1>/dev/null
ehco: command not found
127

# exit_status to stdout, stdout and stderr to /dev/null
> exit_status ehco "hello" 3>&1 &>/dev/null
127

Pamiętaj, że musisz coś zrobić z FD3, inaczej powie Bad file descriptor.

Można to dodatkowo skonfigurować w celu dostarczenia tych wyjść do wejść innego programu poprzez umożliwienie temu programowi nasłuchiwania na więcej niż jednym FD; możesz użyć tego jako czegoś w rodzaju Kibany z początku lat 90. :)

onwsk8r
źródło
Powinien zachować pierwotną wartość zwracaną i zwrócić ją na końcu. Wyświetlanie (printf, echo) prostej rzeczy (nie zaczyna się od „-” itp.) Zawsze działa, a po tym wyświetla się „$?” ma teraz wartość „0” podczas wyświetlania
Olivier Dulac
1

Podczas gdy wszystkie inne (bardzo interesujące) odpowiedzi dotyczą dokładnie pytania, które zadał OP, w praktyce rzeczy są zwykle prostsze. Po prostu zapisujesz status wyjścia w zmiennej (bezpośrednio po poleceniu) i zgłaszasz go lub logujesz w dowolny sposób. Np. Wydrukuj do / dev / stderr lub napisz / dołącz jako wiadomość tekstową do pliku dziennika. Jest także zachowany do użycia w procesie decyzyjnym / kontroli przepływu w kolejnym kodzie.

Jeśli jest w funkcji:

function some-function () {
    local rc ## to avoid side effects
    ...
    some-command ## run any command
    rc=$?  ## save the return status in a variable
    echo "some-command returned $rc" ## optionally tell the user about it
    ...
    return $rc  ## at the end, if appropriate
}

Jeśli jest bezpośrednio w skrypcie:

some-command ## run any command
rc=$?  ## save the return status in a variable
echo "some-command returned $rc" ## optionally tell the user about it
...
exit $rc    ## at the end to tell any calling script about it, if appropriate

(Chowanie i ukrywanie, bo to nie odpowiada na dokładne pytanie.)

Joe
źródło