¿Cómo verificar si una tubería está vacía y ejecutar un comando en los datos si no lo está?

42

He canalizado una línea en el script bash y quiero verificar si la tubería tiene datos, antes de alimentarla a un programa.

Buscando encontré test -t 0pero no funciona aquí. Siempre devuelve falso. Entonces, ¿cómo estar seguro de que la tubería tiene datos?

Ejemplo:

echo "string" | [ -t 0 ] && echo "empty" || echo "fill"

Salida: fill

echo "string" | tail -n+2 | [ -t 0 ] && echo "empty" || echo "fill"

Salida: fill

¿A diferencia de la forma estándar / canónica de probar si la tubería anterior produjo salida? la entrada necesita ser preservada para pasarla al programa. Esto generaliza ¿Cómo canalizar la salida de un proceso a otro pero solo se ejecuta si el primero tiene salida? que se centra en enviar correos electrónicos.

zetah
fuente
Muy similar: haga que una tubería sea condicional en el retorno no vacío (en Super Usuario )
G-Man dice 'Reinstate Monica'

Respuestas:

37

No hay forma de ver el contenido de una tubería utilizando utilidades de shell comúnmente disponibles, ni hay una manera de leer un carácter en la tubería y luego volver a colocarlo. La única forma de saber que una tubería tiene datos es leer un byte, y luego debe llevar ese byte a su destino.

Así que haz eso: lee un byte; si detecta un final de archivo, haga lo que quiera hacer cuando la entrada esté vacía; si lee un byte, bifurque lo que quiere hacer cuando la entrada no esté vacía, canalice ese byte en él y canalice el resto de los datos.

first_byte=$(dd bs=1 count=1 2>/dev/null | od -t o1 -A n | tr -dc 0-9)
if [ -z "$first_byte" ]; then
  # stuff to do if the input is empty
else
  {
    printf "\\$first_byte"
    cat
  } | {
    # stuff to do if the input is not empty
  }      
fi

La ifneutilidad de moreutils de Joey Hess ejecuta un comando si su entrada no está vacía. Por lo general, no se instala de manera predeterminada, pero debería estar disponible o ser fácil de construir en la mayoría de las variantes de Unix. Si la entrada está vacía, ifneno hace nada y devuelve el estado 0, que no se puede distinguir del comando que se ejecuta correctamente. Si desea hacer algo si la entrada está vacía, debe hacer arreglos para que el comando no devuelva 0, lo que puede hacerse haciendo que el caso de éxito devuelva un estado de error distinguible:

ifne sh -c 'do_stuff_with_input && exit 255'
case $? in
  0) echo empty;;
  255) echo success;;
  *) echo failure;;
esac

test -t 0no tiene nada que ver con esto; prueba si la entrada estándar es un terminal. No dice nada de una forma u otra sobre si hay alguna entrada disponible.

Gilles 'SO- deja de ser malvado'
fuente
En sistemas con tuberías basadas en STREAMS (Solaris HP / UX), creo que puede usar el ioctl I_PEEK para ver lo que hay en una tubería sin consumirlo.
Stéphane Chazelas
@ StéphaneChazelas desafortunadamente no hay forma de ver los datos de una tubería / fifo en * BSD, por lo que no hay perspectiva para implementar una utilidad portátil peek que pueda devolver los datos reales de una tubería, no solo la cantidad que hay. (en 4.4 BSD, 386BSD, etc., las tuberías se implementaron como pares de zócalos , pero eso fue destripado en versiones posteriores de * BSD, aunque las mantuvieron bidireccionales).
mosvy
bash tiene una rutina para verificar la entrada expuesta a través de a read -t 0(t en este caso significa tiempo de espera, si se pregunta).
Isaac
11

Una solución simple es usar el ifnecomando (si la entrada no está vacía). En algunas distribuciones, no está instalado por defecto. Es una parte del paquete moreutilsen la mayoría de las distribuciones.

ifne ejecuta un comando dado si y solo si la entrada estándar no está vacía

Tenga en cuenta que si la entrada estándar no está vacía, se pasa ifneal comando dado

Nick Wirth
fuente
2
A partir de 2017, no está allí por defecto en Mac o Ubuntu.
Sridhar Sarnobat
7

Antigua pregunta, pero en caso de que alguien la encuentre como yo: mi solución es leer con tiempo de espera.

while read -t 5 line; do
    echo "$line"
done

Si stdinestá vacío, esto regresará después de 5 segundos. De lo contrario, leerá toda la entrada y podrá procesarla según sea necesario.

Ladd
fuente
Si bien me gusta la idea, -tlamentablemente no forma parte de POSIX: pubs.opengroup.org/onlinepubs/9699919799/utilities/read.html
JepZ
6

compruebe si el descriptor de archivo de stdin (0) está abierto o cerrado:

[ ! -t 0 ] && echo "stdin has data" || echo "stdin is empty"
mviereck
fuente
Cuando pasa algunos datos y desea verificar si hay algunos, pasa el FD de todos modos, por lo que tampoco es una buena prueba.
Jakuje
1
[ -t 0 ]comprueba si fd 0 está abierto en un tty , no si está cerrado o abierto.
Mosvy
@mosvy, ¿podría explicar cómo afectaría eso el uso de esa solución en un script? ¿Hay casos en que no funciona?
JepZ
@JepZ ¿eh? ./that_script </dev/null=> "stdin tiene datos". O ./that_script <&-tener el stdin realmente cerrado .
mosvy
5

También puede usar test -s /dev/stdin(en una subshell explícita).

# test if a pipe is empty or not
echo "string" | 
    (test -s /dev/stdin && echo 'pipe has data' && cat || echo 'pipe is empty')

echo "string" | tail -n+2 | 
    (test -s /dev/stdin && echo 'pipe has data' && cat || echo 'pipe is empty')

: | (test -s /dev/stdin && echo 'pipe has data' && cat || echo 'pipe is empty')
trent55
fuente
8
No funciona para mi Siempre dice que la tubería está vacía.
anfetamáquinas
2
Funciona en mi Mac, pero no en mi caja de Linux.
pico
3

En bash:

read -t 0 

Detecta si una entrada tiene datos (sin leer nada). Luego puede leer la entrada (si la entrada está disponible en el momento en que se ejecuta la lectura):

if     read -t 0
then   read -r input
       echo "got input: $input"
else   echo "No data to read"
fi

Nota: Comprenda que esto depende del tiempo. Esto detecta si la entrada ya tiene datos solo en el momento en que se read -tejecuta.

Por ejemplo, con

{ sleep 0.1; echo "abc"; } | read -t 0; echo "$?"

la salida es 1(falla de lectura, es decir: entrada vacía). El eco escribe algunos datos, pero no es muy rápido para iniciar y escribir su primer byte, por lo tanto, read -t 0informará que su entrada está vacía, ya que el programa aún no ha escrito nada.

Isaac
fuente
github.com/bminor/bash/blob/… - aquí está la fuente de cómo bash detecta que algo está en el descriptor de archivo.
Pavel Patrin
Gracias @PavelPatrin
Isaac
1
@PavelPatrin Eso no funciona . Como se ve claramente en su enlace, bashhará una select()o una ioctl(FIONREAD), o ninguna de ellas, pero no ambas, como debería para que funcione. read -t0está roto. No lo use
mosvy
¡Oooh, hoy trato de entender lo que está mal durante dos horas! ¡Gracias, @mosvy!
Pavel Patrin
3

Una manera fácil de verificar si hay datos disponibles para leer en Unix es con el FIONREADioctl.

No puedo pensar en ninguna utilidad estándar que haga exactamente eso, así que aquí hay un programa trivial que lo hace (mejor que el ifnede moreutils en mi humilde opinión ;-)).

fionread [ prog args ... ]

Si no hay datos disponibles en stdin, saldrá con el estado 1. Si hay datos, se ejecutará prog. Si no progse da, saldrá con el estado 0.

Puede eliminar la pollllamada si solo le interesan los datos disponibles de inmediato . Esto debería funcionar con la mayoría de los tipos de fds, no solo con tuberías.

fionread.c

#include <unistd.h>
#include <poll.h>
#include <sys/ioctl.h>
#ifdef __sun
#include <sys/filio.h>
#endif
#include <err.h>

int main(int ac, char **av){
        int r; struct pollfd pd = { 0, POLLIN };
        if(poll(&pd, 1, -1) < 0) err(1, "poll");
        if(ioctl(0, FIONREAD, &r)) err(1, "ioctl(FIONREAD)");
        if(!r) return 1;
        if(++av, --ac < 1) return 0;
        execvp(*av, av);
        err(1, "execvp %s", *av);
}
Mosvy
fuente
¿Este programa realmente funciona? ¿No necesita esperar un POLLHUPevento también para manejar el caso vacío? ¿Funciona eso si hay varias descripciones de archivos en el otro extremo de la tubería?
Gilles 'SO- deja de ser malvado'
Si, funciona. POLLHUP solo se devuelve por sondeo, debe usar POLLIN para esperar un POLLHUP. No importa cuántas asas abiertas haya en ningún extremo de la tubería.
mosvy
Consulte unix.stackexchange.com/search?q=FIONREAD+user%3A22565 para saber cómo ejecutar FIONREAD desde perl (más comúnmente disponible que los compiladores)
Stéphane Chazelas
La rutina que usa FIONREAD (o HAVE_SELECT) se implementa en bash aquí .
Isaac
2

Si te gustan las frases cortas y crípticas:

$ echo "string" | grep . && echo "fill" || echo "empty"
string
fill
$ echo "string" | tail -n+2 | grep . && echo "fill" || echo "empty"
empty

Usé los ejemplos de la pregunta original. Si no desea la -qopción de uso de datos canalizados con grep

yashma
fuente
0

Esto parece ser una implementación razonable de ifne en bash si estás de acuerdo con leer toda la primera línea

ifne () {
        read line || return 1
        (echo "$line"; cat) | eval "$@"
}


echo hi | ifne xargs echo hi =
cat /dev/null | ifne xargs echo should not echo
Eric Woodruff
fuente
55
readtambién devolverá falso si la entrada no está vacía pero no contiene caracteres de nueva línea, readrealiza algún procesamiento en su entrada y puede leer más de una línea a menos que la llame como IFS= read -r line. echono se puede usar para datos arbitrarios.
Stéphane Chazelas
0

Esto funciona para mí usando read -rt 0

ejemplo de la pregunta original, sin datos:

echo "string" | tail -n+2 | if read -rt 0 ; then echo has data ; else echo no data ; fi
usuario333755
fuente
No, eso no funciona. intente con { sleep .1; echo yes; } | { read -rt0 || echo NO; cat; }(falso negativo) y true | { sleep .1; read -rt0 && echo YES; }(falso positivo). De hecho, Bash readse deje engañar incluso por FDS abiertas en sólo escritura modo: { read -rt0 && echo YES; cat; } 0>/tmp/foo. Lo único que parece hacer es select(2)en ese fd.
Mosvy
... y selectdevolverá un fd como "listo" si un read(2)en él no se bloquea, no importa si regresará EOFo si se produjo un error. Conclusión: read -t0se rota en bash. No lo uses
Mosvy
@mosvy ¿Lo has informado con bashbug?
Isaac
@mosvy The { sleep .1; echo yes; } | { read -rt0 || echo NO; cat; }no es un falso negativo porque (en el momento en que se ejecuta la lectura) no hay entrada. Más tarde (suspensión .1) esa entrada está disponible (para el gato).
Isaac
@mosvy ¿Por qué la opción r afecta la detección ?: echo "" | { read -t0 && echo YES; }imprime SÍ pero echo "" | { read -rt0 && echo YES; }no lo hace.
Isaac