Comment faire en sorte qu'un pipeline attende la fin du fichier ou s'arrête après une erreur?

12

J'ai essayé la commande suivante après avoir regardé cette vidéo sur les manigances de tuyaux.

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

Il imprime essentiellement une liste de pages de manuel dans dmenu pour que l'utilisateur en sélectionne une, puis il utilise xargs pour exécuter man -Tpdf %(imprimer pour sortir un pdf de la page de manuel git à partir de l'entrée de xargs) et passer le pdf à un lecteur pdf (zathura ).

Le problème est que (comme vous pouvez le voir dans la vidéo) le lecteur pdf démarre avant même que je sélectionne une page de manuel dans dmenu. Et si je clique sur Échap et que je n'en sélectionne aucun, le lecteur pdf est toujours ouvert et ne montre aucun document.

Comment puis-je faire en sorte que le lecteur PDF (et toute autre commande d'une chaîne de tuyaux) ne s'exécute que lorsque son entrée atteint une fin de fichier ou lorsqu'il reçoit une entrée? Ou bien, comment puis-je faire en sorte qu'une chaîne de tuyaux s'arrête après qu'une des commandes chaînées renvoie un état de sortie non nul (de sorte que si dmenu renvoie une erreur pour ne pas avoir sélectionné une option, les commandes suivantes ne sont pas exécutées)?

Seninha
la source
1
Quel shell utilisez-vous? C'est bash?
terdon
Je l'ai essayé sur bash, zsh et sh. Tous avaient le même comportement.
Seninha
2
Oui, le comportement est standard, j'ai demandé quel shell en raison de l' pipefailoption de bash mentionnée dans la réponse de Kusalandanda.
terdon

Réponses:

12

Comment puis-je faire en sorte que le lecteur PDF (et toute autre commande d'une chaîne de tuyaux) ne s'exécute que lorsque son entrée atteint une fin de fichier ou lorsqu'il reçoit une entrée?

Il y a ifne(dans Debian c'est dans le moreutilspaquet):

ifne exécute la commande suivante si et seulement si l'entrée standard n'est pas vide.

Dans ton cas:

 | ifne zathura -
Kamil Maciorowski
la source
Merci pour la réponse, je ne connaissais pas cette commande! Cette commande (et les autres dans moreutils) aurait dû être dans l'Unix d'origine et spécifiée par posix ... C'est un outil tellement basique et Unix-ish ...
Seninha
@Seninha La simplicité de ifneest un peu trompeuse. Unix n'a pas d'opération "pipe peek", il ifnedoit donc lire au moins un octet avant de décider d'exécuter la commande dépendante. Cela signifie qu'il ne peut pas simplement faire le test et exécuter la commande, mais doit créer un autre canal, bifurquer un autre processus pour exécuter la commande dépendante et copier tout le flux du canal stdin vers le canal en aval. Si le cas «entrée vide» n'est pas courant, il ifnepourrait facilement coûter plus de ressources qu'il n'en économise, en moyenne.
@ Wumpus.Q.Wumbley c'est un mythe - vous n'avez pas à lire d'octet pour déterminer s'il y a des données dans un canal. Voyez ici . Et sur Linux vous pouvez réellement PEEK données à partir d' un tuyau (c. -à- lire les données sans l' enlever). J'ai mentionné cela et plus dans les commentaires d'une réponse "canonique" ici, mais ils ont été supprimés par les mods car ils pensaient probablement que ces faits étaient comme nuire à la génialité de la réponse.
Mosvy
6

Les fichiers PDF sont censés être recherchés; tout lecteur de PDF devra d'abord regarder la bande-annonce et de là, passer aux décalages de la table xréf.

Étant donné que les canaux ne sont pas recherchés, zathurautilise une astuce d'obscurcissement, où il copie toutes les entrées dans un fichier temporaire, puis utilise ce fichier temporaire comme d'habitude. Ce genre de truc «intelligent» crée de faux espoirs et amène les gens à supposer que les fichiers pdf sont diffusables.

Mais de toute façon, zathuravraiment fait attendre l'EOF avant d' afficher le document, vous ne devez rien faire pour que cela se Hapen:

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

Le problème est qu'il zathuran'a pas la possibilité d'ouvrir uniquement la fenêtre si le fichier est OK et de quitter avec une erreur si ce n'est pas le cas - il y restera comme si tout allait 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?

Donc, même si vous redirigez vous-même la sortie vers un fichier temporaire et que vous ne courez que zathurasi tout était OK, rien ne garantit que l'utilisateur ne sera pas prévenu d'une fenêtre noire s'il zathuran'aime pas la sortie pour une raison ou une autre .


Btw,

man -X man

affichera une page de manuel dans une fenêtre X11 avec gxditview, même si elle semble tout droit sortie du '70 ;-)

Et, bien sûr, vous pouvez toujours utiliser:

... | xargs xterm -e man

qui, en plus de nombreuses autres améliorations, vous permettra d'utiliser des expressions régulières dans les recherches et la sélection de texte appropriée.

mosvy
la source
6

Toutes les commandes d'un pipeline démarrent à peu près en même temps. Ce ne sont que les E / S sur le canal qui les synchronisent. En outre, un tuyau ne peut contenir autant d'informations que le tampon du tuyau le permet.

Vous ne pouvez donc pas éviter d'exécuter une étape d'un pipeline, car

  1. la commande dans cette étape est lancée dès que toutes les autres étapes sont démarrées de toute façon, et
  2. si la commande ne consommait pas l'entrée qui arrive par dessus le tuyau, elle bloquerait les étapes précédentes du pipeline.

Au lieu de cela, écrivez la sortie dans un fichier tout en laissant le pipeline se terminer. Utilisez ensuite ce fichier.

Exemple (en tant que fonction prenant un argument):

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

De plus, cela n'exécuterait pas le zathuraprogramme si le pipeline échouait (la xargspartie renvoyée non nulle) ou si le fichier généré était vide.

Dans le bashshell, vous pouvez également définir l' pipefailoption shell avec set -o pipefailpour que le pipeline renvoie l'état de sortie de la première commande du pipeline qui échoue. Et vous voudriez faire 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"
}

Cela définit l' pipefailoption pour la durée de la fonction, si elle n'était pas déjà définie, puis la désactive si nécessaire. Il supprime le -stest sur le fichier de sortie.

Kusalananda
la source
1
Pourquoi rm -f? Envisagez-vous des cas où le canal modifie les autorisations du fichier tmp?
terdon
2
@terdon Je pense aux cas où le fichier temporaire est supprimé prématurément. rm -fne ferait pas d'erreur si le fichier a déjà été supprimé (éventuellement par zathura, je ne sais pas).
Kusalananda
La première fonction ne fonctionne pas comme prévu: elle fera également apparaître zathura une fenêtre noire, mais maintenant zathura s'exécute après la fin du pipeline, plutôt que de courir le long du pipeline. Cela est dû au fait que le pipeline renvoie l'état de sortie de xargs, qui est 0. La commande qui échoue dans le pipeline est dmenu (qui renvoie 1 lorsque je ne sélectionne rien). La fonction bash avec l' pipefailoption fonctionne comme prévu (et dans zsh aussi, qui a la même option).
Seninha
1
@Seninha J'ai corrigé la première fonction en la laissant vérifier si le fichier généré n'est pas vide.
Kusalananda