Jak określa się status zwrotu przypisania zmiennej?

10

Widziałem konstrukcje w skryptach takich jak ten:

if somevar="$(somecommand 2>/dev/null)"; then
...
fi

Czy to gdzieś jest udokumentowane? W jaki sposób określa się status zwracany zmiennej i jaki ma ona związek z zastępowaniem poleceń? (Na przykład, czy uzyskałbym ten sam wynik if echo "$(somecommand 2>/dev/null)"; then?)

Dzika karta
źródło

Odpowiedzi:

13

Jest to udokumentowane (dla POSIX) w rozdziale 2.9.1 Proste polecenia specyfikacji bazowej grupy otwartej. Jest tam ściana tekstu; Zwracam uwagę na ostatni akapit:

Jeśli istnieje nazwa polecenia, wykonywanie będzie kontynuowane zgodnie z opisem w rozdziale Wyszukiwanie i wykonywanie poleceń . Jeżeli nie ma nazwy polecenia, ale polecenie zawierało podstawienie polecenia, polecenie powinno uzupełnić się o status wyjścia ostatniego wykonanego zastąpienia polecenia. W przeciwnym razie polecenie powinno zakończyć się z zerowym statusem wyjścia.

Na przykład

   Command                                         Exit Status
$ FOO=BAR                                   0 (but see also the note from icarus, below)
$ FOO=$(bar)                                Exit status from "bar"
$ FOO=$(bar) baz                            Exit status from "baz"
$ foo $(bar)                                Exit status from "foo"

Tak też działa bash. Ale patrz także sekcja „nie taka prosta” na końcu.

phk , w swoim pytaniu Przydziały są jak polecenia ze statusem wyjścia, z wyjątkiem przypadków zastąpienia poleceń? , wskazuje

… Wygląda na to, że samo przypisanie liczy się jako polecenie… z zerową wartością wyjściową, ale ma zastosowanie przed prawą stroną przypisania (np. Wywołanie zamiany polecenia…)

To nie jest okropny sposób patrzenia na to. Surowy schemat określania stanu powrotu prostego polecenia (nie zawierającą ;, &, |, &&i ||) stanowi:

  • Skanuj linię od lewej do prawej, aż dojdziesz do końca lub słowa polecenia (zwykle nazwy programu).
  • Jeśli zobaczysz przypisanie zmiennej, zwracanym stanem linii może być po prostu 0.
  • Jeśli zobaczysz podstawienie polecenia - tj. $(…)- weź status wyjścia z tego polecenia.
  • Jeśli osiągniesz rzeczywiste polecenie (nie zastępując polecenia), weź status wyjścia z tego polecenia.
  • Status zwracany dla linii to ostatni napotkany numer.
    Podstawienia poleceń jako argumenty polecenia, np. foo $(bar)Nie liczą się; otrzymujesz status wyjścia z foo. Parafrazując notację phk , zachowanie jest tutaj

    temporary_variable  = EXECUTE( "bar" )
    overall_exit_status = EXECUTE( "foo", temporary_variable )

Jest to jednak niewielkie uproszczenie. Ogólny status zwrotu z

A = $ ( cmd 1 ) B = $ ( cmd 2 ) C = $ ( cmd 3 ) D = $ ( cmd 4 ) E = mc 2
to status wyjścia z . Zadanie, które występuje po przydział nie określa ogólny stan wyjścia na 0.cmd4E=D=

Ikarus w swojej odpowiedzi na pytanie phk podnosi ważną kwestię: zmienne mogą być ustawione jako tylko do odczytu. Akapit od trzeciego do ostatniego w sekcji 2.9.1 standardu POSIX mówi:

Jeśli którekolwiek z przypisań zmiennych próbuje przypisać wartość do zmiennej, dla której ustawiono atrybut tylko do odczytu w bieżącym środowisku powłoki (niezależnie od tego, czy przypisanie jest wykonane w tym środowisku), wystąpi błąd przypisania zmiennej. Zobacz Konsekwencje błędów powłoki, aby poznać konsekwencje tych błędów.

więc jeśli powiesz

readonly A
C=Garfield A=Felix T=Tigger

zwracany jest 1. Nie ma znaczenia, czy struny Garfield, Felixi / lub Tigger są zastępowane podstawiania poleceń (S) - ale patrz uwagi poniżej.

Sekcja 2.8.1 Konsekwencje błędów powłoki zawiera kolejną wiązkę tekstu oraz tabelę i kończy się na

We wszystkich przypadkach pokazanych w tabeli, w których wymagana jest powłoka interaktywna, aby nie wychodzić, powłoka nie wykonuje dalszego przetwarzania polecenia, w którym wystąpił błąd.

Niektóre szczegóły mają sens; niektórzy nie:

  • A=Przypisanie czasami przerywa linię poleceń, jako że ostatnie zdanie wydaje się określić. W powyższym przykładzie Cjest ustawione na Garfield, ale Tnie jest ustawione (i oczywiście nie jest  A).
  • Podobnie wykonuje się, ale nie . Ale w moich wersjach bash (które obejmują 4.1.x oraz 4.3.x), to ma wykonać . (Nawiasem mówiąc, utrudnia to interpretację phk, że wartość wyjściowa przypisania obowiązuje przed prawą stroną przypisania).C=$(cmd1) A=$(cmd2) T=$(cmd3)cmd1cmd3
    cmd2

Ale oto niespodzianka:

W moich wersjach bash

tylko do odczytu A
C = coś A = coś T = coś  cmd 0

ma wykonać. W szczególności,cmd0

C = $ ( cmd 1 ) A = $ ( cmd 2 ) T = $ ( cmd 3 )    cmd 0
wykonuje i , ale nie . (Zauważ, że jest to przeciwieństwo jego zachowania, gdy nie ma polecenia.) I ustawia (jak również ) w środowisku . Zastanawiam się, czy to błąd w bashu.cmd1cmd3cmd2TCcmd0


Nie takie proste:

Pierwszy akapit tej odpowiedzi odnosi się do „prostych poleceń”.  Specyfikacja mówi:

„Proste polecenie” jest sekwencją opcjonalnych przypisań zmiennych i przekierowań, w dowolnej sekwencji, opcjonalnie po słowach i przekierowaniach, zakończonych operatorem sterującym.

Są to instrukcje podobne do tych w moim pierwszym bloku przykładowym:

$ FOO=BAR
$ FOO=$(bar)
$ FOO=$(bar) baz
$ foo $(bar)

trzy pierwsze z nich zawierają przypisania zmiennych, a trzy ostatnie z nich zastępują polecenia.

Ale niektóre przypisania zmiennych nie są tak proste.  bash (1) mówi:

Instrukcje przypisania mogą również pojawić się jako argumenty do alias, declare, typeset, export, readonly, i localwbudowane polecenia ( deklaracji poleceń).

Dla export, specyfikacja POSIX mówi,

STATUS WYJŚCIA

    0
      Wszystkie operandy nazw zostały pomyślnie wyeksportowane.
    > 0
      Nie można wyeksportować co najmniej jednej nazwy lub -ppodano opcję i wystąpił błąd.

I POSIX nie obsługuje local, ale bash (1) mówi:

Jest to błąd do użycia, localgdy nie znajduje się w funkcji. Zwracany status to 0, chyba że localzostanie użyty poza funkcją, podana zostanie niepoprawna nazwa lub nazwa jest zmienną tylko do odczytu.

Czytając między wierszami, widzimy, że polecenia deklaracji jak

export FOO=$(bar)

i

local FOO=$(bar)

są bardziej jak

foo $(bar)

o ile ignorować stan wyjścia z bar i daje status wyjścia opartą na głównej komendy ( export, locallub foo). Mamy więc dziwactwo

   Command                                           Exit Status
$ FOO=$(bar)                                    Exit status from "bar"
                                                  (unless FOO is readonly)
$ export FOO=$(bar)                             0 (unless FOO is readonly,
                                                  or other error from “export”)
$ local FOO=$(bar)                              0 (unless FOO is readonly,
                                                  statement is not in a function,
                                                  or other error from “local”)

co możemy zademonstrować

$ export FRIDAY=$(date -d tomorrow)
$ echo "FRIDAY   = $FRIDAY, status = $?"
FRIDAY   = Fri, May 04, 2018  8:58:30 PM, status = 0
$ export SATURDAY=$(date -d "day after tomorrow")
date: invalid date ‘day after tomorrow’
$ echo "SATURDAY = $SATURDAY, status = $?"
SATURDAY = , status = 0

i

myfunc() {
    local x=$(echo "Foo"; true);  echo "x = $x -> $?"
    local y=$(echo "Bar"; false); echo "y = $y -> $?"
    echo -n "BUT! "
    local z; z=$(echo "Baz"; false); echo "z = $z -> $?"
}

$ myfunc
x = Foo -> 0
y = Bar -> 0
BUT! z = Baz -> 1

Na szczęście ShellCheck wyłapuje błąd i podnosi SC2155 , który to zaleca

export foo="$(mycmd)"

należy zmienić na

foo=$(mycmd)
export foo

i

local foo="$(mycmd)"

należy zmienić na

local foo
foo=$(mycmd)
G-Man mówi „Przywróć Monikę”
źródło
1
Wielkie dzięki! Jeśli chodzi o punkty bonusowe, czy wiesz, jak localsię z tym wiąże? Na przykład local foo=$(bar)?
Wildcard,
1
W drugim przykładzie (tylko FOO=$(bar)) warto zauważyć, że teoretycznie zarówno status wyjścia, jak i przypisanie mogą odgrywać pewną rolę, patrz unix.stackexchange.com/a/341013/117599
phk
1
@Wildcard: Cieszę się, że ci się podoba. Właśnie go zaktualizowałem; duża część właśnie przeczytanej wersji była niepoprawna. Jak długo tu jesteś, co myślisz? Czy to błąd w bash, który A=foo cmddziała, cmdnawet jeśli Ajest tylko do odczytu?
G-Man mówi „Przywróć Monikę”
1
@phk: (1) Ciekawa teoria, ale nie jestem pewien, jak to ma sens. Jeszcze raz spójrz na przykład tuż przed nagłówkiem „niespodzianka”. Jeśli Ajest tylko do odczytu, polecenie C=value₁ A=value₂ T=value₃ustawia się, Cale nie T(i oczywiście Anie jest ustawione) - powłoka zakończyła przetwarzanie wiersza poleceń, ignorując T=value₃, ponieważ A=value₂jest to błąd. (2) Dzięki za link do pytania dotyczącego przepełnienia stosu - opublikowałem komentarze na jego temat.
G-Man mówi „Przywróć Monikę”
1
@Wildcard „Aby uzyskać punkty bonusowe, czy wiesz, jak lokalne powiązania z tym?”. Tak ... local=$(false)ma wartość wyjścia 0ponieważ (od strony man) It is an error to use local when not within a function. The return status is 0 unless local is used outside a function, an invalid name is supplied, or name is a readonly variable.. To nie wystarczy trollface na świecie dla geniuszu, który zaprojektował go w ten sposób.
David Tonhofer
2

Jest to udokumentowane w Bash ( LESS=+/'^SIMPLE COMMAND EXPANSION' bash):

Jeśli po rozwinięciu pozostała nazwa polecenia ... W przeciwnym razie polecenie zostanie zakończone. ... Jeśli nie było podstawień poleceń, polecenie kończy pracę ze statusem zero.

Innymi słowy (moje słowa):

Jeśli po rozwinięciu nie pozostanie żadna nazwa polecenia i nie zostaną wykonane żadne zamiany, linia poleceń zakończy działanie ze statusem zero.

phk
źródło