¿Cómo espero un archivo en el script de shell?

14

Estoy tratando de escribir un script de shell que esperará a que aparezca un archivo en el /tmpdirectorio llamado sleep.txty una vez que se encuentre, el programa cesará, de lo contrario, quiero que el programa esté en estado de suspensión (suspendido) hasta que se encuentre el archivo . Ahora, supongo que usaré un comando de prueba. Entonces, algo como

(if [ -f "/tmp/sleep.txt" ]; 
then stop 
   else sleep.)

¡Soy nuevo en escribir script de shell y cualquier ayuda es muy apreciada!

Mo Gainz
fuente
Mira $MAILPATH.
mikeserv

Respuestas:

22

En Linux, puede usar el subsistema de kernel inotify para esperar eficientemente la aparición de un archivo en un directorio:

while read i; do if [ "$i" = sleep.txt ]; then break; fi; done \
   < <(inotifywait  -e create,open --format '%f' --quiet /tmp --monitor)
# script execution continues ...

(suponiendo Bash para la <()sintaxis de redirección de salida)

La ventaja de este enfoque en comparación con el sondeo de intervalo de tiempo fijo como en

while [ ! -f /tmp/sleep.txt ]; do sleep 1; done
# script execution continues ...

es que el kernel duerme más. Con una especificación de evento inotify como create,openla secuencia de comandos solo está programada para su ejecución cuando /tmpse crea o abre un archivo debajo . Con el sondeo de intervalo de tiempo fijo desperdicia ciclos de CPU por cada incremento de tiempo.

Incluí el openevento para registrar también touch /tmp/sleep.txtcuando el archivo ya existe.

maxschlepzig
fuente
Gracias, esto es genial! ¿Por qué necesita el ciclo while si conoce el nombre del archivo? ¿Por qué no podría haber sido así inotifywait -e create,open --format '%f' --quiet /tmp/sleep.txt --monitor > /dev/null ; # continues once /tmp/sleep.txt is created/opened?
NHDaly
Oh, jk. Después de instalar la herramienta ahora entiendo. inotifywaites en sí mismo un ciclo while y genera una línea cada vez que se abre / crea el archivo. Por lo tanto, necesita que el bucle while se rompa cuando se cambia.
NHDaly
excepto que esto produce una condición de carrera, a diferencia de la versión de prueba -e / sleep. No hay forma de garantizar que el archivo no se creará justo antes de ingresar el bucle, en cuyo caso se perderá el evento. Debería agregar algo como (dormir 1; [[-e /tmp/sleep.txt]] && touch /tmp/sleep.txt;) & antes del ciclo. Lo que, por supuesto, podría crear problemas en el futuro, dependiendo de la lógica comercial real
n-alexander el
@ n-alexander Bueno, la respuesta supone que ejecutas el fragmento antes de comenzar el proceso que producirá sleep.txten algún momento. Por lo tanto, no hay condición de carrera. La pregunta no contiene nada que sugiera un escenario que tenga en mente.
maxschlepzig
@maxschlepzig al contrario. A menos que se haga cumplir el pedido, no se puede suponer. Lo esencial.
n-alexander
10

Simplemente ponga su prueba en el whilecircuito:

while [ ! -f /tmp/sleep.txt ]; do sleep 1; done
# next command
jimmij
fuente
Entonces, como lo ha escrito, esta es la lógica: mientras el archivo no exista, el script se suspenderá. De lo contrario, está hecho. ¿Correcto?
Mo Gainz
@MoGainz Correcto. El número posterior sleepes el número de segundos que debe esperar en cada iteración; puede cambiar eso según sus necesidades.
jimmij
7

Hay algunos problemas con algunos de los inotifywaitenfoques basados ​​hasta ahora dados:

  • no logran encontrar un sleep.txtarchivo creado primero como un nombre temporal y luego renombrado a sleep.txt. Uno necesita coincidir para moved_toeventos además decreate
  • los nombres de archivo pueden contener caracteres de nueva línea, imprimir los nombres de los archivos delimitados por nueva línea no es suficiente para determinar si sleep.txtse ha creado uno. ¿Qué pasa si foo\nsleep.txt\nbarse ha creado un archivo, por ejemplo?
  • ¿Qué pasa si el archivo se crea antes de que inotifywait se haya iniciado e instalado el reloj? Luego inotifywaitesperaría para siempre un archivo que ya está aquí. Debería asegurarse de que el archivo ya no esté allí después de instalar el reloj.
  • algunas de las soluciones se dejan en inotifywaitejecución (al menos hasta que se crea otro archivo) después de que se haya encontrado el archivo.

Para abordarlos, podría hacer:

sh -c 'echo "$$" &&
        LC_ALL=C exec inotifywait -me create,moved_to --format=/%f/ . 2>&1' | {
  IFS= read pid &&
    while IFS= read -r line && [ "$line" != "Watches established." ]; do
      : wait for watches to be established
    done
  [ -e sleep.txt ] || [ -L sleep.txt ] || grep -qxF /sleep.txt/ && kill "$pid"
}

Tenga en cuenta que estamos esperando la creación sleep.txten el directorio actual .(por lo que haría un cd /tmp || exitantes en su ejemplo). El directorio actual nunca cambia, por lo que cuando esa línea de tubería regresa con éxito, se sleep.txtcrea un directorio en el directorio actual.

Por supuesto, puede reemplazarlo .con el /tmpanterior, pero mientras se inotifywaitestá ejecutando, /tmppodría haber cambiado de nombre varias veces (lo que es improbable /tmp, pero algo a tener en cuenta en el caso general) o un nuevo sistema de archivos montado en él, por lo que cuando la tubería regrese, puede que no ser un /tmp/sleep.txtque ha sido creado pero un /new-name-for-the-original-tmp/sleep.txten su lugar. También se /tmppodría haber creado un nuevo directorio en el intervalo y ese no se vería, por lo que uno sleep.txtcreado allí no se detectaría.

Stéphane Chazelas
fuente
1

La respuesta aceptada realmente funciona (gracias maxschlepzig) pero deja el monitoreo de inotifywait en segundo plano hasta que se cierra el script. La única respuesta que coincide exactamente con sus requisitos (es decir, esperar a que se muestre sleep.txt dentro de / tmp) parece ser la de Stephane, si el directorio a ser monitoreado por inotifywait cambia de punto (.) A '/ tmp'.

Sin embargo, si está dispuesto a usar un directorio temporal SOLAMENTE para colocar su bandera sleep.txt y puede apostar a que nadie más colocará ningún archivo en ese directorio, solo pedirle a inotifywait que vea este directorio para crear archivos sería suficiente:

1er paso: crea el directorio que monitorearás:

directoryToPutSleepFile=$(mktemp -d)

2do paso: asegúrese de que el directorio esté realmente allí

until [ -d $directoryToPutSleepFile ]; do sleep 0.1; done

3er paso: espere hasta que CUALQUIER archivo aparezca dentro $directoryToPutSleepFile

inotifywait -e create --format '%f' --quiet $directoryToPutSleepFile

El archivo que colocará $directoryToPutSleepFilepuede llamarse sleep.txt awake.txt, lo que sea. En el momento en que se cree un archivo dentro de $directoryToPutSleepFilesu script, continuará más allá de la inotifywaitdeclaración.

nikolaos
fuente
1
Pero eso podría pasar por alto la creación del archivo, si se creó otro justo antes, causando sleep.txtque se cree entre dos invocaciones de inotifywait.
Stéphane Chazelas
Vea mi respuesta para una forma alternativa de abordarlo.
Stéphane Chazelas
Por favor prueba mi código. inotifywait se invoca solo una vez. Cuando se crea sleep.txt (o se lee / escribe si ya está allí), inotifywait genera "sleep.txt" y la ejecución continúa después de la declaración 'hecho'.
nikolaos
Se invoca varias veces hasta que sleep.txtse crea un archivo llamado . Intente, por ejemplo touch /tmp/foo /tmp/sleep.txt, luego una instancia de inotifywaitreportará fooy saldrá , y para cuando se inicie el siguiente inotifywait, el sleep.txt ya habrá estado allí por lo que inotifywait no detectará su creación.
Stéphane Chazelas
Tenía razón sobre las invocaciones múltiples: una invocación para cada archivo creado bajo / tmp. Actualicé mi respuesta. Gracias por tu comentario.
nikolaos
0

en general, es bastante difícil hacer algo más que el simple test / sleep loop confiable. El principal problema es que la prueba se ejecutará con la creación del archivo; a menos que se repita la prueba, se puede perder el evento de creación. Esta es, de lejos, su mejor apuesta en la mayoría de los casos en los que estaría utilizando Shell para comenzar:

#!/bin/bash
while [[ ! -e /tmp/file ]] ; do
    sleep 1
done

Si encuentra que esto es insuficiente debido a problemas de rendimiento, entonces usar Shell probablemente no sea una gran idea en primer lugar.

n-alexander
fuente
2
Esto es solo un duplicado de la respuesta de jimmij .
maxschlepzig
0

Basado en la respuesta aceptada de maxschlepzig (y una idea de la respuesta aceptada en /superuser/270529/monitoring-a-file-until-a-string-is-found ) Propongo lo siguiente mejorado ( en mi opinión) respuesta que también puede funcionar con tiempo de espera:

# Enable pipefail, so if the left side of the pipe fails it does not get silently ignored
set -o pipefail
( timeout 120 inotifywait -e create,open --format '%f' --quiet /tmp --monitor & ) | while read i; do if [ "$i" == 'sleep.txt' ]; then break; fi; done
EXIT_STATUS=$?
if [ "${EXIT_STATUS}" == '124' ]; then
  echo "Timeout happened"
fi

En caso de que el archivo no se cree / abra dentro del tiempo de espera dado, el estado de salida es 124 (según la documentación de tiempo de espera (página del manual)). En caso de que se cree / abra, el estado de salida es 0 (éxito).

Sí, inotifywait se ejecuta en un sub-shell de esta manera y ese sub-shell solo terminará de ejecutarse cuando se agote el tiempo de espera o cuando salga el script principal (lo que ocurra primero).

Atila123
fuente