Come posso usare bash's se test e trova i comandi insieme?


25

Ho una directory con i registri degli arresti anomali e vorrei usare un'istruzione condizionale in uno script bash basato su un comando find.

I file di registro sono memorizzati in questo formato:

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

Voglio che l'istruzione if ritorni vera solo se esiste un registro degli arresti anomali per un'app specifica che è stata modificata negli ultimi 5 minuti. Il findcomando che vorrei usare è:

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

Non sono sicuro di come incorporarlo ifcorrettamente in una dichiarazione. Penso che potrebbe funzionare:

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

Ci sono alcune aree in cui non sono chiaro:

  • Ho esaminato le bandiere if ma non sono sicuro di quale, se ce ne fosse, che avrei dovuto usare.
  • Ho bisogno della testdirettiva o dovrei semplicemente elaborare direttamente i risultati del comando find o utilizzare invece find... | wc -lper ottenere un conteggio di riga?
  • Non è necessario al 100% per rispondere a questa domanda, ma testè per testare i codici di ritorno che i comandi restituiscono? E sono in qualche modo invisibili - al di fuori di stdout/ stderr? Ho letto la manpagina ma non sono ancora chiaro su quando utilizzarlo teste su come eseguirne il debug.

La vera risposta al caso generale è usare find ... -exec. Vedi anche i comandi di esempio in Perché il looping sull'output di find è una cattiva pratica?
Wildcard il

@Wildcard - sfortunatamente ciò non risolve il caso generale: non funziona se c'è più di una partita e l'azione deve essere eseguita una sola volta, e non funziona se hai bisogno di un'azione da eseguire quando ci sono nessuna corrispondenza. Il primo può essere risolto utilizzando ... -exec command ';' -quit, ma non credo che ci sia alcuna soluzione per il secondo oltre all'analisi del risultato. Inoltre, in entrambi i casi, il problema principale con l'analisi del risultato di find(ovvero l'impossibilità di distinguere i delimitatori dai caratteri nei nomi dei file) non si applica, poiché in questi casi non è necessario trovare delimitatori.
Jules,

Risposte:


27

[e testsono sinonimi (tranne se [necessario ]), quindi non si desidera utilizzare [ test:

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

testrestituisce uno stato di uscita zero se la condizione è vera, altrimenti diversa da zero. Questo può effettivamente essere sostituito da qualsiasi programma per verificarne lo stato di uscita, dove 0 indica l'esito positivo e diverso da zero indica l'errore:

# 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

Tuttavia, tutti gli esempi sopra riportati verificano solo lo stato di uscita del programma e ignorano l'output del programma.

Per find, sarà necessario verificare se è stato generato alcun output. -ntest per una stringa non vuota:

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

Un elenco completo di argomenti di test è disponibile invocando help testdalla bashriga di comando.

Se stai usando bash(e non sh), puoi usare [[ condition ]], che si comporta in modo più prevedibile quando ci sono spazi o altri casi speciali nella tua condizione. Altrimenti è generalmente lo stesso dell'uso [ condition ]. Ho usato [[ condition ]]in questo esempio, come faccio ogni volta che è possibile.

Ho anche cambiato `command`in $(command), che generalmente si comporta in modo simile, ma è più bello con i comandi nidificati.


echopuò fallire: provare echo 'oops' > /dev/full.
derobert,

Questa risposta batte alla radice del problema, ma evita con garbo di menzionare esattamente di cosa si tratta.
bahamat,

9

finduscirà correttamente se non ci sono errori, quindi non puoi contare sul suo stato di uscita per sapere se ha trovato un file. Ma, come hai detto, puoi contare quanti file ha trovato e testare quel numero.

Sarebbe qualcosa del genere:

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

test(aka [) non controlla i codici di errore dei comandi, ha una sintassi speciale per eseguire i test, quindi esce con un codice di errore pari a 0 se il test ha avuto esito positivo o 1 in caso contrario. È ifquello che controlla il codice di errore del comando che gli viene passato ed esegue il suo corpo in base a esso.

Vedi man test(o help test, se usi bash), e help if(idem).

In questo caso, wc -lverrà emesso un numero. Usiamo l test'opzione -gtper verificare se quel numero è maggiore di 0. In tal caso, test(o [) verrà restituito con il codice di uscita 0. ifinterpreterà quel codice di uscita come successo e eseguirà il codice all'interno del suo corpo.


1

Questo sarebbe

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

o

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

I comandi teste [ … ]sono esattamente sinonimi. L'unica differenza è il loro nome e il fatto che [richiede una chiusura ]come ultimo argomento. Come sempre, usa le doppie virgolette attorno alla sostituzione del comando, altrimenti l'output del findcomando verrà suddiviso in parole e qui otterrai un errore di sintassi se c'è più di un file corrispondente (e quando non ci sono argomenti, [ -n ]è vero , mentre tu vuoi [ -n "" ]che sia falso).

In ksh, bash e zsh ma non in ash, puoi anche usare [[ … ]]che ha diverse regole di analisi: [è un comando ordinario, mentre [[ … ]]è un costrutto di analisi diverso. Non hai bisogno di doppie virgolette all'interno [[ … ]](anche se non fanno male). Hai ancora bisogno ;del comando after.

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

Questo può essere potenzialmente inefficiente: se ci sono molti file in /var/log/crashes, find li esplorerà tutti. Dovresti trovare stop non appena trova una corrispondenza, o subito dopo. Con GNU find (Linux non incorporato, Cygwin), usa il -quitprimario.

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

Con altri sistemi, tubi findin headalmeno uscire subito dopo la prima partita (per morirà di un tubo rotto).

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

(Puoi usarlo head -c 1se il tuo headcomando lo supporta.)


In alternativa, usa zsh.

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

1

Questo dovrebbe funzionare

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

è una soluzione appropriata qui.

-exec service myapp restart ';'fa findinvocare il comando che si desidera eseguire direttamente, piuttosto che richiedere alla shell di interpretare qualsiasi cosa.

-quitprovoca la findchiusura dopo l'elaborazione del comando, impedendo così l'esecuzione del comando in caso di più file che soddisfano i criteri.

Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.