¿Cómo hacer que una tubería espere el final del archivo o se detenga después de un error?

12

Intenté el siguiente comando después de ver este video sobre travesuras de tuberías.

man -k . | dmenu -l 20 | awk '{print $1}' | xargs -r man -Tpdf | zathura -

Básicamente imprime una lista de páginas de manual en dmenu para que el usuario seleccione una de ellas, luego usa xargs para ejecutar man -Tpdf %(imprimir para stdout un pdf del git de página de manual desde la entrada de xargs) y pasar el pdf a un lector de pdf (zathura )

El problema es que (como puede ver en el video) el lector de PDF comienza incluso antes de seleccionar una página de manual en dmenu. Y si hago clic en Esc y no selecciono ninguno, el lector de PDF todavía está abierto y no muestra ningún documento.

¿Cómo puedo hacer que el lector de PDF (y cualquier otro comando en una cadena de tuberías) solo se ejecute cuando su entrada llegue al final del archivo o cuando reciba una entrada? O, alternativamente, ¿cómo puedo hacer que una cadena de tuberías se detenga después de que uno de los comandos encadenados devuelve un estado de salida distinto de cero (de modo que si dmenu devuelve un error por no seleccionar una opción, los siguientes comandos no se ejecutan)?

Seninha
fuente
1
¿Qué caparazón estás usando? ¿Es esto bash?
terdon
Lo he probado en bash, zsh y sh. Todos tenían el mismo comportamiento.
Seninha
2
Sí, el comportamiento es estándar, pregunté qué caparazón debido a la pipefailopción de bash mencionada en la respuesta de Kusalandanda.
terdon

Respuestas:

12

¿Cómo puedo hacer que el lector de PDF (y cualquier otro comando en una cadena de tuberías) solo se ejecute cuando su entrada llegue al final del archivo o cuando reciba una entrada?

Hay ifne(en Debian está en el moreutilspaquete):

ifne ejecuta el siguiente comando si y solo si la entrada estándar no está vacía.

En tu caso:

 | ifne zathura -
Kamil Maciorowski
fuente
Gracias por la respuesta, ¡no conocía este comando! Este comando (y los demás en moreutils) deberían haber estado en el Unix original y especificado por posix ... Es una herramienta tan básica y Unix-ish ...
Seninha
@Seninha La simplicidad de ifnees un poco engañosa. Unix no tiene ninguna operación de "vistazo de tubería", por lo que ifnedebe leer al menos un byte antes de decidir ejecutar el comando dependiente. Eso significa que no solo puede hacer la prueba y ejecutar el comando, sino que debe crear otra tubería, bifurcar otro proceso para ejecutar el comando dependiente y copiar todo el flujo de la tubería estándar a la tubería aguas abajo. Si el caso de "entrada vacía" no es común, ifnepodría costar fácilmente más recursos de los que ahorra, en promedio.
@ Wumpus.Q.Wumbley es un mito: no es necesario leer ningún byte para determinar si hay datos en una tubería. Ver aquí . Y en Linux en realidad se puede peek datos de una tubería (es decir, leer los datos sin tener que sacarlo). He mencionado esto y más en los comentarios a una respuesta "canónica" aquí, pero fueron modificados por los mods porque probablemente sintieron que esos hechos eran como restar valor a la genialidad de la respuesta.
mosvy
6

Se supone que los archivos PDF son buscables; cualquier visor de PDF tendrá que mirar primero el avance y, a partir de ahí, saltar a los desplazamientos de la tabla xref.

Dado que las tuberías no son buscables, zathuraestá utilizando un truco de ofuscación, donde está copiando toda la entrada a un archivo temporal, y luego usa ese archivo temporal como de costumbre. Este tipo de truco "inteligente" está creando falsas esperanzas y está llevando a las personas a suponer que los archivos pdf son transmisibles.

Pero de todos modos, zathurarealmente lo hace espera para el EOF antes de mostrar el documento, usted no tiene que hacer nada para que eso hapen:

(sleep 10; cat file.pdf) | zathura -
# will really show the content of file.pdf after 10 seconds

El problema es que zathurano tiene la opción de abrir la ventana solo si el archivo está bien y salir con un error si ese no es el caso; simplemente permanecerá allí como si todo estuviera bien:

$ dd if=file.pdf bs=50000 count=1 status=none | zathura -
error: could not open document  # its window still hanging around showing nothing

$ echo $?
0  # really?

Entonces, incluso si está redirigiendo la salida a un archivo temporal usted mismo, y solo se está ejecutando zathurasi todo estaba bien, no hay garantía de que el usuario no reciba una ventana negra si zathurano le gusta la salida por una razón u otra .


Por cierto,

man -X man

mostrará una página de manual en una ventana X11 con gxditview, incluso si se ve directamente desde el '70 ;-)

Y, por supuesto, siempre puedes usar:

... | xargs xterm -e man

que, además de muchas otras mejoras, le permitirá usar expresiones regulares en las búsquedas y la selección de texto adecuada.

Mosvy
fuente
6

Todos los comandos en una tubería comienzan casi al mismo tiempo. Es solo la E / S sobre la tubería que los sincroniza. Además, una tubería solo puede contener tanta información como lo permita el búfer de la tubería.

Por lo tanto, no puede evitar ejecutar una etapa de una tubería, porque

  1. el comando en esa etapa se inicia tan pronto como se inician todas las demás etapas de todos modos, y
  2. Si el comando no consumiera la entrada que entra por la tubería, bloquearía las etapas anteriores de la tubería.

En su lugar, escriba la salida en un archivo mientras deja que finalice la canalización. Luego usa ese archivo.

Ejemplo (como una función que toma un argumento):

myman () {
    tmpfile=$( mktemp )

    if man -k "$1" | dmenu -l 20 | awk '{print $1}' | xargs -r man -Tpdf >"$tmpfile" && [ -s  "$tmpfile" ]
    then
        zathura "$tmpfile"
    fi

    rm -f "$tmpfile"
}

Además, esto no ejecutaría el zathuraprograma si la canalización fallara (la xargsparte devuelta no es cero) o el archivo generado está vacío.

En el bashshell, también puede configurar la pipefailopción del shell set -o pipefailpara que la tubería devuelva el estado de salida del primer comando en la tubería que falla. Y querrías hacer la tmpfilevariable local:

myman () {
    local tmpfile=$( mktemp )

    if [ -o pipefail ]; then
        set -o pipefail
        trap 'set +o pipefail' RETURN
    fi

    if man -k "$1" | dmenu -l 20 | awk '{print $1}' | xargs -r man -Tpdf >"$tmpfile"
    then
        zathura "$tmpfile"
    fi

    rm -f "$tmpfile"
}

Esto establece la pipefailopción para la duración de la función, si aún no estaba configurada, y luego la desarma si es necesario. Se deshace de la -sprueba en el archivo de salida.

Kusalananda
fuente
1
¿Por qué rm -f? ¿Está pensando en casos en los que la tubería cambia los permisos del archivo tmp?
terdon
2
@terdon Estoy pensando en casos en los que el archivo temporal se elimina prematuramente. rm -fno se produciría un error si el archivo ya se eliminó (posiblemente zathura, no lo sé)
Kusalananda
La primera función no funciona como se esperaba: también hará que zathura muestre una ventana negra, pero ahora zathura se ejecuta después del final de la tubería, en lugar de ejecutarse junto a la tubería. Esto se debe a que la canalización devuelve el estado de salida de xargs, que es 0. El comando que falla en la canalización es dmenu (que devuelve 1 cuando no selecciono nada). La función bash con la pipefailopción funciona como se esperaba (y también en zsh, que tiene la misma opción).
Seninha
1
@Seninha Arreglé la primera función dejándola verificar si el archivo generado no está vacío.
Kusalananda