Używasz $? w instrukcji if

12
function foo {
   (cd $FOOBAR;
   <some command>
   if [$? -ne 0]
   then
      echo "Nope!"
   else
      echo "OK!"
   fi
   )
}

Próbuję napisać funkcję podobną do powyższej i umieścić ją w moim pliku .bashrc. Po źródle pliku i uruchomieniu otrzymuję:

Całkowity czas: 51 sekund
-bash: [1: polecenie nie znaleziono
OK!

Czy ktoś może mi pomóc zrozumieć, co zrobiłem źle?

Amir Afghani
źródło
4
Testowanie, czy wartość $?równa się 0 z ifinstrukcją jest bezcelowe, ifoczekuje polecenia, a jeśli to polecenie powróci 0, uruchamia kod w bloku. więc przywróci if true; then echo hello; fiecho, odkąd polecenie truepowróciło 0.
llua
1
@llua To nie ma sensu. $?przechowuje status ostatniego potoku , który nie jest poleceniem test( [) w ifinstrukcji. Przykładem jest sprawdzenie, czy się some commandudało. Możesz zrobić to samo z &&i ||, ale może to zrobić długie, nieczytelne linie w porównaniu do if [ $? -eq 0 ]. Ten sam argument dotyczyif some command
bonsaiviking
@ bonsaiviking Zdaję sobie sprawę z tego, co się $?rozwija, wskazuję, że nie ma sensu testbyć używanym; ponieważ if some commandrobi to samo z jedną instrukcją a dwiema osobnymi instrukcjami. jeśli polecenie jest już długie, dodanie trzech kolejnych znaków nie spowodowałoby, że „nieczytelne” byłoby znacznie bardziej „nieczytelne”.
llua

Odpowiedzi:

33

Dodaj spację po [, a drugą przed ]:

function foo {
   (cd $FOOBAR;
   <some command>
   if [ $? -ne 0 ]
   then
      echo "Nope!"
   else
      echo "OK!"
   fi
   )
}

[jest wbudowane powłoki, to podobnie jak komenda echo, read, expr... potrzebuje miejsca po nim, i wymaga dopasowania ].

Pisanie [ $? -ne 0 ]jest faktycznie wywoływanie [i nadając jej 4 parametry: $?, -ne, 0, i ].

Uwaga: fakt, że pojawia się komunikat o błędzie [1: command not foundoznacza, że $?miał on wartość 1.

aularon
źródło
1
Właśnie zweryfikowałem, że twoja odpowiedź jest poprawna na mojej maszynie wirtualnej z systemem Linux.
samiam
[jest link do testjak dobrze
Ricky Beam
2
@ RickyBeam w większości powłok [jest wbudowany w powłokę i /usr/bin/[jest rzadko używany.
Patrick
20

Lub możesz $?całkowicie pominąć . Jeśli twoje polecenie brzmi cmd, powinny działać:

function foo {
   (cd $FOOBAR;
   if cmd
   then
      echo "OK!"
   else
      echo "Nope!"
   fi
   )
}
unxnut
źródło
6

Dobrą praktyką jest przypisanie wartości zwracanej do zmiennej przed jej użyciem

retval="$?"
if [ $retval -ne 0 ]

Pozwala ponownie wykorzystać wartość zwracaną. np. w instrukcji if ... elif ... else ...

Abdul
źródło
-1: Zapomniałeś spacji po [(Bez spacji staje się to błędem składniowym bash). Cofa się, jeśli edytujesz i naprawisz swój błąd.
Samiam
Tak, masz rację. Naprawiłem to.
Abdul
@samiam że ostatnia uwaga była skierowana na ciebie
terdon
4
:) Czy możesz trochę rozszerzyć swoją odpowiedź. Dlaczego to dobra praktyka? Jakiego rodzaju błędów można uniknąć?
terdon
1
@terdon to zła praktyka, aby używać $?bezpośrednio, ponieważ zepsuje się, jeśli kiedykolwiek, podczas późniejszej edycji skryptu, umieścisz linię między poleceniem a $?czekiem.
samiam
3

Jedynym powodem, dla którego chcesz użyć $?jako argumentów [polecenia (niezależnie od tego, czy [polecenie to jest uruchamiane w części warunku ifinstrukcji, czy nie) jest, gdy chcesz rozróżnić konkretny status zwrotu, na przykład:

until
  cmd
  [ "$?" -gt 1 ]
do
  something
done

Składnia dla tych wszystkich if, while, until... sprawozdanie jest

if cmd-list1
then cmd-list2
else cmd-list3
fi

Który działa, cmd-list2jeśli cmd-list1się powiedzie lub w cmd-list3inny sposób.

[ "$?" -eq 0 ]Komenda jest no-op. Ustawia $?na 0, jeśli $?wynosi 0, i $?na niezerową, jeśli była niezerowa.

Jeśli chcesz coś uruchomić, jeśli się cmdnie powiedzie, to:

if ! cmd
then ...
fi

Ogólnie rzecz biorąc, nie musisz majstrować przy tym, nie $?mówiąc już o tym, która wartość oznacza truelub false. Jedynymi przypadkami są, jak powiedziałem powyżej, jeśli musisz rozróżnić określoną wartość lub jeśli musisz ją zachować na później (na przykład, aby zwrócić ją jako wartość zwracaną funkcji), na przykład:

f() {
  cmd; ret=$?
  some cleanup
  return "$ret"
}

Pamiętaj również, że pozostawienie zmiennej niecytowanej to operator split + glob. Przywołanie tego operatora tutaj nie ma sensu, więc powinno być:

[ "$?" -ne 0 ]

nie [ $? -ne 0 ], nie wspominając już o tym [$? -ne 0 ](który wywołałby [polecenie tylko wtedy, gdyby $IFSzawierał pierwszy znak $?).

Zauważ też, że sposobem Bourne'a na zdefiniowanie funkcji jest trzymanie function-name()się polecenia. To miało miejsce w każdym Bourne shell wyjątkiem jak bashi yash(i nowszych wersjach posh), które umożliwiają polecenia związek tylko (polecenia czym związek {...}lub (...)lub rzeczy jak for...done,if...fi ...

function foo { ... }to kshskładnia definicji funkcji. Nie ma powodu, dla którego chciałbyś go tutaj użyć.

Kod można zapisać przenośnie (POSIXly):

foo() (
  cd -P -- "$FOOBAR" || return # what if the cd failed!
  if
    <some command>
  then
    echo 'OK!'
  else
    echo 'Nope!'
  fi
)

Zauważ też, że cdbez -Pma bardzo specjalne znaczenie (obsługuje ścieżki, które zawierają ..komponenty inaczej niż jakiekolwiek inne polecenie), więc lepiej jest włączyć je do skryptów, aby uniknąć nieporozumień.

(ta funkcja zwraca, falsejeśli cdzawiedzie, ale nie, jeśli <some command>zawiedzie).

Stéphane Chazelas
źródło
1

Wierzę, że poniższe polecenie zrobi wszystko, co chcesz w jednym wierszu.

(( verify = $?!=0?'Nope!':'OK!' ))
Jeight
źródło