¿Cómo puedo usar los comandos if test y find de bash juntos?

25

Tengo un directorio con registros de bloqueo, y me gustaría usar una declaración condicional en un script bash basado en un comando de búsqueda.

Los archivos de registro se almacenan en este formato:

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

Quiero que la declaración if solo devuelva verdadero si hay un registro de bloqueo para una aplicación específica que se ha modificado en los últimos 5 minutos. El findcomando que usaría es:

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

No estoy seguro de cómo incorporar eso en una ifdeclaración correctamente. Creo que esto podría funcionar:

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

Hay algunas áreas donde no estoy claro:

  • He mirado las banderas if pero no estoy seguro de cuál, si es que hay alguna, debería usar.
  • ¿Necesito la testdirectiva o debería simplemente procesar directamente los resultados del comando find, o tal vez usar find... | wc -lpara obtener un recuento de líneas?
  • No es 100% necesario para responder esta pregunta, pero ¿ testes para probar los códigos de retorno que los comandos devuelven? Y son algo invisibles, fuera de stdout/ stderr? Leí la manpágina, pero aún no tengo muy claro cuándo usarla testy cómo depurarla.
cwd
fuente
La verdadera respuesta al caso general es usar find ... -exec. Vea también los comandos de ejemplo en ¿Por qué es un bucle sobre la salida de find una mala práctica?
Comodín el
@Wildcard: desafortunadamente eso no resuelve el caso general: no funciona si hay más de una coincidencia y la acción solo necesita ejecutarse una vez, y no funciona si necesita ejecutar una acción cuando hay No hay coincidencias. El primero se puede resolver mediante el uso ... -exec command ';' -quit, pero no creo que haya otra solución para el segundo que no sea analizar el resultado. Además, en cualquier caso, el problema principal con el análisis del resultado de find(es decir, la incapacidad para distinguir los delimitadores de los caracteres en los nombres de archivo) no se aplica, ya que no necesita encontrar delimitadores en estos casos.
Julio

Respuestas:

27

[y testson sinónimos (excepto que se [requieren ]), por lo que no desea usar [ test:

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

testdevuelve un estado de salida cero si la condición es verdadera, de lo contrario no es cero. En realidad, esto puede ser reemplazado por cualquier programa para verificar su estado de salida, donde 0 indica éxito y no cero indica falla:

# 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

Sin embargo, todos los ejemplos anteriores solo prueban el estado de salida del programa e ignoran la salida del programa.

Para find, deberá probar si se generó alguna salida. -nprueba para una cadena no vacía:

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

Una lista completa de argumentos de prueba está disponible invocando help testen la bashlínea de comandos.

Si está usando bash(y no sh), puede usar [[ condition ]], lo que se comporta de manera más predecible cuando hay espacios u otros casos especiales en su condición. De lo contrario, generalmente es lo mismo que usar [ condition ]. Lo he usado [[ condition ]]en este ejemplo, como lo hago siempre que sea posible.

También cambié `command`a $(command), que generalmente también se comporta de manera similar, pero es más agradable con los comandos anidados.

mrb
fuente
echopuede fallar: intente echo 'oops' > /dev/full.
derobert
Esta respuesta supera la raíz del problema, pero con gracia evita mencionar exactamente qué es eso.
bahamat
9

findsaldrá con éxito si no hubo ningún error, por lo que no puede contar con su estado de salida para saber si encontró algún archivo. Pero, como dijiste, puedes contar cuántos archivos encontró y probar ese número.

Sería algo como esto:

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

test(aka [) no verifica los códigos de error de los comandos, tiene una sintaxis especial para hacer pruebas y luego sale con un código de error de 0 si la prueba fue exitosa, o 1 en caso contrario. Es ifel que comprueba el código de error del comando que le pasa y ejecuta su cuerpo en función de él.

Ver man test(o help test, si lo usa bash) y help if(ídem).

En este caso, wc -lgenerará un número. Usamos testla opción -gtpara probar si ese número es mayor que 0. Si es así, test(o [) volverá con el código de salida0 . ifinterpretará ese código de salida como exitoso, y ejecutará el código dentro de su cuerpo.

angus
fuente
1

Esto sería

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

Los comandos testy [ … ]son exactamente sinónimos. La única diferencia es su nombre y el hecho de que [requiere un cierre ]como último argumento. Como siempre, use comillas dobles alrededor de la sustitución del comando, de lo contrario, la salida del findcomando se dividirá en palabras, y aquí obtendrá un error de sintaxis si hay más de un archivo coincidente (y cuando no hay argumentos, [ -n ]es cierto , mientras que quieres [ -n "" ]que es falso).

En ksh, bash y zsh pero no en ash, también puede usar [[ … ]]que tiene diferentes reglas de análisis: [es un comando ordinario, mientras que [[ … ]]es una construcción de análisis diferente. No necesita comillas dobles en el interior [[ … ]](aunque no duelen). Todavía necesitas el; después del comando.

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

Esto puede ser potencialmente ineficiente: si hay muchos archivos /var/log/crashes, find los explorará a todos. Debes hacer que stop se encuentre tan pronto como encuentre una coincidencia, o poco después. Con GNU find (Linux no incrustado, Cygwin), use el -quitprimario.

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

Con otros sistemas, tubo findenhead , al menos, dejar de fumar poco después del primer partido (Encontrar morirá de una tubería rota).

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

(Puedes usar head -c 1 si su headcomando lo admite).


Alternativamente, use zsh.

crash_files=(/var/log/crashes/**/app-*.log(mm-5[1]))
if (($#crash_files)); then
Gilles 'SO- deja de ser malvado'
fuente
1

Esto debería funcionar

if find /var/log/crashes -name 'app-\*\.log' -mmin -5 | read
then
  service myapp restart
fi
Steven Penny
fuente
0
find /var/log/crashes -name app-\*\.log -mmin -5 -exec service myapp restart ';' -quit

Es una solución apropiada aquí.

-exec service myapp restart ';' causas find que invoque el comando que desea ejecutar directamente, en lugar de necesitar que el shell interprete algo.

-quithace findque salga después de procesar el comando, evitando así que el comando se ejecute nuevamente si hay varios archivos que coinciden con los criterios.

Jules
fuente