Jak mogę używać bash, jeśli testujesz i znajdujesz polecenia razem?


25

Mam katalog z dziennikami awarii i chciałbym użyć instrukcji warunkowej w skrypcie bash opartym na poleceniu find.

Pliki dziennika są przechowywane w tym formacie:

/var/log/crashes/app-2012-08-28.log
/var/log/crashes/otherapp-2012-08-28.log

Chcę, aby instrukcja if zwróciła wartość true tylko wtedy, gdy istnieje dziennik awarii dla konkretnej aplikacji, która została zmodyfikowana w ciągu ostatnich 5 minut. findPoleceń, które używam to:

find /var/log/crashes -name app-\*\.log -mmin -5

Nie jestem pewien, jak ifpoprawnie włączyć to do oświadczenia. Myślę, że to może działać:

if [ test `find /var/log/crashes -name app-\*\.log -mmin -5` ] then
 service myapp restart
fi

Jest kilka obszarów, w których jestem niejasny:

  • Patrzyłem na flagi if, ale nie jestem pewien, który z nich, jeśli w ogóle, powinienem użyć.
  • Czy potrzebuję testdyrektywy, czy powinienem po prostu przetwarzać bezpośrednio wyniki polecenia find, czy może find... | wc -lzamiast tego użyć licznika wierszy?
  • Nie jest w 100% konieczne, aby odpowiedzieć na to pytanie, ale testczy do testowania kodów powrotu komendy return? I są jakby niewidoczne - poza stdout/ stderr? Czytam manstronę, ale wciąż nie jestem pewien, kiedy używać testi jak ją debugować.

Prawdziwą odpowiedzią na ogólny przypadek jest użycie find ... -exec. Zobacz także przykładowe polecenia w sekcji Dlaczego zapętlanie wyników wyszukiwania jest złą praktyką?
Wildcard

@Wildcard - niestety nie rozwiązuje to ogólnego przypadku: nie działa, jeśli występuje więcej niż jedno dopasowanie, a akcja musi zostać uruchomiona tylko raz, i nie działa, jeśli potrzebujesz akcji, aby była uruchomiona brak wyników. Pierwsze z nich można rozwiązać za pomocą ... -exec command ';' -quit, ale nie sądzę, że istnieje drugie rozwiązanie poza analizowaniem wyniku. W obu przypadkach główny problem z analizowaniem wyniku find(tj. Niemożność odróżnienia ograniczników od znaków w nazwach plików) nie ma zastosowania, ponieważ w takich przypadkach nie trzeba znajdować ograniczników.
Jules

Odpowiedzi:


27

[i testsą synonimami (z wyjątkiem [wymaga ]), więc nie chcesz używać [ test:

[ -x /bin/cat ] && echo 'cat is executable'
test -x /bin/cat && echo 'cat is executable'

testzwraca zerowy status wyjścia, jeśli warunek jest spełniony, w przeciwnym razie niezerowy. Można to faktycznie zastąpić dowolnym programem, aby sprawdzić jego status wyjścia, gdzie 0 oznacza sukces, a niezerowy oznacza błąd:

# echoes "command succeeded" because echo rarely fails
if /bin/echo hi; then echo 'command succeeded'; else echo 'command failed'; fi

# echoes "command failed" because rmdir requires an argument
if /bin/rmdir; then echo 'command succeeded'; else echo 'command failed'; fi

Jednak wszystkie powyższe przykłady testują tylko status wyjścia programu i ignorują dane wyjściowe programu.

Dla find, trzeba będzie testem gdy moc wyjściowa została wygenerowana. -ntesty niepustego ciągu:

if [[ -n $(find /var/log/crashes -name "app-*.log" -mmin -5) ]]
then
    service myapp restart
fi

Pełna lista argumentów testowych jest dostępna po wywołaniu help testw bashwierszu polecenia.

Jeśli używasz bash(i nie używasz sh), możesz użyć [[ condition ]], który zachowuje się bardziej przewidywalnie, gdy w twoim stanie są spacje lub inne specjalne przypadki. W przeciwnym razie jest to ogólnie to samo, co używanie [ condition ]. Użyłem [[ condition ]]tego przykładu, tak jak zawsze, gdy to możliwe.

Zmieniłem również `command`na $(command), który również ogólnie zachowuje się podobnie, ale jest ładniejszy dzięki zagnieżdżonym poleceniom.


echomoże zawieść: spróbuj echo 'oops' > /dev/full.
derobert

Ta odpowiedź uderza w sedno problemu, ale z wdziękiem unika wspominania, co to dokładnie jest.
bahamat

9

findzakończy się pomyślnie, jeśli nie wystąpiły żadne błędy, więc nie możesz liczyć na jego status wyjścia, aby wiedzieć, czy znalazł jakiś plik. Ale, jak powiedziałeś, możesz policzyć liczbę znalezionych plików i przetestować tę liczbę.

Byłoby to coś takiego:

if [ $(find /var/log/crashes -name 'app-*.log' -mmin -5 | wc -l) -gt 0 ]; then
    ...
fi

test(aka [) nie sprawdza kodów błędów poleceń, ma specjalną składnię do wykonywania testów, a następnie kończy działanie z kodem błędu 0, jeśli test się powiódł, lub 1 w przeciwnym razie. To iften, który sprawdza kod błędu polecenia, które mu przekazujesz, i wykonuje na nim swoje ciało.

Zobacz man test(lub help test, jeśli używasz bash) i help if(to samo).

W takim przypadku wc -lwyświetli liczbę. Używamy testopcji, -gtaby sprawdzić, czy liczba ta jest większa niż 0. Jeśli tak, test(lub [) zwróci kod wyjścia 0. ifzinterpretuje ten kod wyjścia jako sukces i uruchomi kod w swoim ciele.


1

To byłoby

if [ -n "$(find /var/log/crashes -name app-\*\.log -mmin -5)" ]; then

lub

if test -n "$(find /var/log/crashes -name app-\*\.log -mmin -5)"; then

Polecenia testi [ … ]są dokładnie synonimami. Jedyną różnicą jest ich nazwa i fakt, który [wymaga zamknięcia ]jako ostatniego argumentu. Jak zawsze, używaj podwójnych cudzysłowów wokół podstawiania polecenia, w przeciwnym razie dane wyjściowe findpolecenia zostaną podzielone na słowa, a tutaj otrzymasz błąd składniowy, jeśli istnieje więcej niż jeden pasujący plik (a gdy nie ma argumentów, [ -n ]to prawda , podczas gdy chcesz, [ -n "" ]co jest fałszywe).

W ksh, bash i zsh, ale nie w ash, możesz również użyć, [[ … ]]który ma różne reguły parsowania: [jest zwykłym poleceniem, podczas gdy [[ … ]]jest inną konstrukcją parsowania. W środku nie potrzebujesz podwójnych cudzysłowów [[ … ]](choć nie bolą). Nadal potrzebujesz polecenia ;po.

if [[ -n $(find /var/log/crashes -name app-\*\.log -mmin -5) ]]; then

Może to być potencjalnie nieefektywne: jeśli jest wiele plików /var/log/crashes, find zbada je wszystkie. Powinieneś zatrzymać wyszukiwanie, jak tylko znajdzie dopasowanie, lub wkrótce potem. W przypadku GNU find (niewbudowany Linux, Cygwin) użyj -quitpodstawowego.

if [ -n "$(find /var/log/crashes -name app-\*\.log -mmin -5 -print -quit)" ]; then

Z innymi systemami, rury findjęzyk headprzynajmniej rzucić wkrótce po pierwszym meczu (Znajdź umrze z powodu złamanego rury).

if [ -n "$(find /var/log/crashes -name app-\*\.log -mmin -5 -print | head -n 1)" ]; then

(Możesz użyć, head -c 1jeśli twoje headpolecenie to obsługuje.)


Alternatywnie użyj zsh.

crash_files=(/var/log/crashes/**/app-*.log(mm-5[1]))
if (($#crash_files)); then

1

To powinno działać

if find /var/log/crashes -name 'app-\*\.log' -mmin -5 | read
then
  service myapp restart
fi

0
find /var/log/crashes -name app-\*\.log -mmin -5 -exec service myapp restart ';' -quit

jest tutaj odpowiednim rozwiązaniem.

-exec service myapp restart ';'powoduje findwywołanie polecenia, które chcesz uruchomić bezpośrednio, zamiast konieczności interpretowania powłoki przez powłokę.

-quitpowoduje findzakończenie działania po przetworzeniu polecenia, co zapobiega ponownemu wykonaniu polecenia, jeśli zdarzy się, że będzie wiele plików spełniających kryteria.

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.