shell - Haz una cola-F hasta que coincida con un patrón
sed awk (9)
Quiero hacer una cola -F en un archivo hasta que coincida con un patrón. Encontré una forma de usar awk, pero en mi humilde opinión mi comando no está realmente limpio. El problema es que necesito hacerlo en una sola línea, debido a algunas limitaciones.
tail -n +0 -F /tmp/foo | /
awk -W interactive ''{if ($1 == "EOF") exit; print} END {system("echo EOF >> /tmp/foo")}''
La cola se bloqueará hasta que aparezca EOF en el archivo. Funciona bastante bien El bloque END es obligatorio porque la "salida" de awk no termina de inmediato. Hace awk evaluar el bloque END antes de abandonar. El bloque END se cuelga en una llamada de lectura (debido a la cola), por lo que lo último que tengo que hacer es escribir otra línea en el archivo para obligar a la cola a salir.
¿Alguien sabe una mejor manera de hacer eso?
¿Esto funciona para tí?
tail -n +0 -F /tmp/foo | sed ''/EOF/q''
Asumo que ''EOF'' es el patrón que estás buscando. El comando sed
cierra cuando lo encuentra, lo que significa que la tail
debe salir la próxima vez que se escribe.
Supongo que existe la posibilidad de que la tail
se mantenga si el patrón se encuentra al final del archivo, esperando que aparezca más en el archivo que nunca aparecerá. Si eso es realmente una preocupación, probablemente podrías matarlo: la tubería como un todo terminará cuando sed
termine (a menos que estés usando un gracioso caparazón que decida que ese no es el comportamiento correcto).
Grump sobre Bash
Como se temía, bash
(en MacOS X, al menos, pero probablemente en todas partes) es un caparazón que cree que debe esperar esperando que la tail
termine aunque sed
quit. Algunas veces, con más frecuencia de la que prefiero, prefiero el comportamiento del buen viejo shell de Bourne, que no era tan ingenioso y, por lo tanto, adivinaba mal con menos frecuencia que Bash. dribbler
es un programa que transmite mensajes uno por segundo (''1: Hello'', etc. en el ejemplo), y la salida va a la salida estándar. En Bash, esta secuencia de comandos se cuelga hasta que hice '' echo pqr >>/tmp/foo
'' en una ventana separada.
date
{ timeout -t 2m dribbler -t -m Hello; echo EOF; } >/tmp/foo &
echo Hi
sleep 1 # Ensure /tmp/foo is created
tail -n +0 -F /tmp/foo | sed ''/EOF/q''
date
Tristemente, no veo de inmediato una opción para controlar este comportamiento. Encontré shopt lithist
, pero eso no está relacionado con este problema.
Hooray para Korn Shell
Observo que cuando ejecuto ese script usando el shell Korn, funciona como esperaba, dejando una tail
acecho para que me maten de alguna manera. Lo que funciona allí es '' echo pqr >> /tmp/foo
'' después de que finalice el segundo comando de fecha.
Esto es algo en lo que Tcl es bastante bueno. Si lo siguiente es "tail_until.tcl",
#!/usr/bin/env tclsh
proc main {filename pattern} {
set pipe [open "| tail -n +0 -F $filename"]
set pid [pid $pipe]
fileevent $pipe readable [list handler $pipe $pattern]
vwait ::until_found
catch {exec kill $pid}
}
proc handler {pipe pattern} {
if {[gets $pipe line] == -1} {
if {[eof $pipe]} {
set ::until_found 1
}
} else {
puts $line
if {[string first $pattern $line] != -1} {
set ::until_found 1
}
}
}
main {*}$argv
Entonces harías tail_until.tcl /tmp/foo EOF
Prueba esto:
sh -c ''tail -n +0 -f /tmp/foo | { sed "/EOF/ q" && kill $$ ;}''
Toda la línea de comando saldrá tan pronto como se vea la cadena "EOF" en / tmp / foo.
Hay un efecto secundario: el proceso de cola se dejará en ejecución (en el fondo) hasta que se escriba algo en / tmp / foo.
Use la opción --pid de la cola y la cola se detendrá cuando el proyectil muera. No es necesario agregar extra al archivo de cola.
sh -c ''tail -n +0 --pid=$$ -f /tmp/foo | { sed "/EOF/ q" && kill $$ ;}''
Aquí hay una versión extendida de la solución de Jon que usa sed en lugar de grep para que la salida de tail vaya a stdout:
sed -r ''/EOF/q'' <( exec tail -n +0 -f /tmp/foo ); kill $! 2> /dev/null
Esto funciona porque sed se crea antes de la cola, ¡así que $! sostiene el PID de la cola
La principal ventaja de esto sobre las soluciones sh -c es que matar a sh parece imprimir algo en la salida, como ''Terminated'', lo que no es bienvenido.
No tengo resultados con la solución:
sh -c ''tail -n +0 -f /tmp/foo | { sed "/EOF/ q" && kill $$ ;}''
Hay algún problema relacionado con el búfer porque si no hay más líneas anexadas al archivo, entonces sed no leerá la entrada. Entonces, con un poco más de investigación, se me ocurrió esto:
sed ''/EOF/q'' <(tail -n 0 -f /tmp/foo)
El script está en https://gist.github.com/2377029
sh -c ''tail -n +0 --pid=$$ -f /tmp/foo | { sed "/EOF/ q" && kill $$ ;}''
Aquí el problema principal es con $$
. Si ejecuta el comando como está, $$
está configurado no para sh sino para el PID del shell actual donde se ejecuta el comando.
Para que kill funcione, debes cambiar kill $$
por kill /$$
Después de eso, puede deshacerse con seguridad de --pid=$$
pasado al comando tail.
Resumiendo, lo siguiente funcionará bien:
/bin/sh -c ''tail -n 0 -f /tmp/foo | { sed "/EOF/ q" && kill /$$ ;}
Opcionalmente puede pasar -n
a sed
para mantenerlo en silencio :)
tail -f <filename> | grep -q "<pattern>"
Para matar también el proceso de tail
colgante, puedes ejecutar el comando de tail
en un contexto de sustitución de proceso (Bash) que luego se puede matar como si se tratara de un proceso de fondo. (Código tomado de Cómo leer una línea de ''tail -f'' a través de una tubería, y luego terminar? ).
: > /tmp/foo
grep -m 1 EOF <( exec tail -f /tmp/foo ); kill $! 2> /dev/null
echo EOF > /tmp/foo # terminal window 2
Como alternativa, puede usar un tubo con nombre.
(
: > /tmp/foo
rm -f pidfifo
mkfifo pidfifo
sh -c ''(tail -n +0 -f /tmp/foo & echo $! > pidfifo) |
{ sed "/EOF/ q" && kill $(cat pidfifo) && kill $$ ;}''
)
echo EOF > /tmp/foo # terminal window 2