Come far attendere la fine del file o interrompere una pipeline dopo un errore?


12

Ho provato il seguente comando dopo aver visto questo video su pipe shenanigans.

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

Fondamentalmente stampa un elenco di manpage in dmenu per l'utente di selezionarne una, quindi utilizza xargs per eseguire man -Tpdf %(stampa per stdout un pdf del manpage git dall'input di xargs) e passare il pdf a un lettore pdf (zathura ).

Il problema è che (come puoi vedere nel video) il lettore pdf inizia anche prima di selezionare una manpage in dmenu. E se faccio clic su Esc e non seleziono nessuno, il lettore pdf è ancora aperto e non mostra alcun documento.

Come posso far funzionare il lettore pdf (e qualsiasi altro comando in una catena di pipe) solo quando il suo input raggiunge un end-of-file o quando riceve un input? Oppure, in alternativa, come posso arrestare una catena di condotte dopo che uno dei comandi concatenati restituisce uno stato di uscita diverso da zero (in modo che se dmenu restituisce un errore per non selezionare un'opzione, i seguenti comandi non vengono eseguiti)?


1
Che shell stai usando? Questo è bash?
terdon

L'ho provato su bash, zsh e sh. Tutti avevano lo stesso comportamento.
Seninha,

2
Sì, il comportamento è standard, ho chiesto quale shell a causa pipefaildell'opzione di bash menzionata nella risposta di Kusalandanda.
terdon

Risposte:


12

Come posso far funzionare il lettore pdf (e qualsiasi altro comando in una catena di pipe) solo quando il suo input raggiunge un end-of-file o quando riceve un input?

C'è ifne(in Debian è nel moreutilspacchetto):

ifne esegue il comando seguente se e solo se l'input standard non è vuoto.

Nel tuo caso:

 | ifne zathura -

Grazie per la risposta, non conoscevo questo comando! Questo comando (e gli altri in moreutils) avrebbe dovuto essere nell'Unix originale e specificato da Posix ... È un tale strumento di base e Unix-ish ...
Seninha,

@Seninha La semplicità di ifneè un po 'ingannevole. Unix non ha alcuna operazione "pipe peek", quindi ifnedeve effettivamente leggere almeno un byte prima di decidere di eseguire il comando dipendente. Ciò significa che non può semplicemente eseguire il test ed eseguire il comando, ma deve creare un altro pipe, fork un altro processo per eseguire il comando dipendente e copiare l'intero flusso dal pipe stdin al pipe downstream. Se il caso "input vuoto" non è comune, ifnepotrebbe facilmente costare più risorse di quante ne risparmi, in media.

@ Wumpus.Q.Wumbley che è un mito - non non deve leggere qualsiasi byte per determinare se ci sono dati in un tubo. Vedi qui . E su Linux puoi effettivamente dare un'occhiata ai dati da una pipe (cioè leggere i dati senza rimuoverli). Ho citato questo e altro nei commenti a una risposta "canonica" qui, ma sono stati rimossi dalle mod perché probabilmente ritenevano che quei fatti fossero come sminuire la bellezza della risposta.
mosvy,

6

I file PDF dovrebbero essere ricercabili; qualsiasi visualizzatore di pdf dovrà prima guardare il trailer e da lì saltare agli offset dalla tabella xrif.

Poiché le pipe non sono ricercabili, zathurasta usando un trucco offuscato, in cui sta copiando tutto l'input in un file temporaneo e quindi utilizza quel file temporaneo come al solito. Questo tipo di trucco "intelligente" sta creando false speranze e sta portando le persone ad assumere che i file pdf siano streaming.

Ma in ogni modo, zathurain realtà fa attendere l'EOF prima di visualizzare il documento, non c'è bisogno di fare qualcosa per quella a hapen:

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

Il problema è che zathuranon ha alcuna opzione per aprire la finestra solo se il file è OK e uscire con un errore se non è così - rimarrà lì come se tutto fosse OK:

$ 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?

Quindi, anche se stai reindirizzando l'output su un file temporaneo da solo e stai eseguendo solo zathurase tutto è andato bene, non c'è garanzia che l'utente non verrà servito con una finestra nera se zathural'output non piace per un motivo o per un altro .


btw,

man -X man

visualizzerà una manpage in una finestra X11 con gxditview, anche se sembra uscito dagli anni '70 ;-)

E, ovviamente, puoi sempre usare:

... | xargs xterm -e man

che, oltre a molti altri miglioramenti, ti consentirà di utilizzare espressioni regolari nelle ricerche e nella corretta selezione del testo.


6

Tutti i comandi in una pipeline iniziano praticamente contemporaneamente. È solo l'I / O sul pipe che li sincronizza. Inoltre, una pipe può contenere solo tutte le informazioni consentite dal buffer della pipe.

Pertanto, non è possibile evitare di eseguire una fase di una pipeline

  1. il comando in quella fase viene avviato non appena vengono avviate tutte le altre fasi, e
  2. se il comando non consumasse l'input che arriva sulla pipe, bloccherebbe le fasi precedenti della pipeline.

Invece, scrivi l'output in un file lasciando terminare la pipeline. Quindi utilizzare quel file.

Esempio (come funzione che accetta un argomento):

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"
}

Inoltre, il zathuraprogramma non verrebbe eseguito in caso di errore della pipeline (la xargsparte restituita diversa da zero) o se il file generato è vuoto.

Nella bashshell, si potrebbe anche voler impostare l' pipefailopzione shell set -o pipefailaffinché la pipeline restituisca lo stato di uscita del primo comando nella pipeline che ha esito negativo. E vorresti creare la tmpfilevariabile 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"
}

Questo imposta l' pipefailopzione per la durata della funzione, se non era già impostata, quindi la disinserisce se necessario. Elimina il -stest sul file di output.


1
Perché rm -f? Stai pensando ai casi in cui la pipe cambia i permessi del file tmp?
terdon

2
@terdon Sto pensando ai casi in cui il file temporaneo viene rimosso prematuramente. rm -fnon farebbe errori se il file fosse già stato rimosso (possibilmente da zathura, non lo so).
Kusalananda

La prima funzione non funziona come previsto: farà anche in modo che zathura mostri una finestra nera, ma ora zathura viene eseguito dopo la fine della pipeline, anziché correre lungo la pipeline. Questo perché la pipeline restituisce lo stato di uscita di xargs, che è 0. Il comando che ha esito negativo nella pipeline è dmenu (che restituisce 1 quando non seleziono nulla). La funzione bash con l' pipefailopzione funziona come previsto (e anche in zsh, che ha la stessa opzione).
Seninha,

1
@Seninha Ho corretto la prima funzione lasciandola controllare se il file generato non è vuoto.
Kusalananda
Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.