Rendre une pipe conditionnelle à un retour non vide


Réponses:


9

La ifnotemptyfonction suivante redirige son entrée vers la commande passée en argument, sauf qu'elle ne fait rien si l'entrée est vide. Il permet au tuyau source --fooen sink --barpar écrit source --foo | pipe_if_not_empty sink --bar.

pipe_if_not_empty () {
  head=$(dd bs=1 count=1 2>/dev/null; echo a)
  head=${head%a}
  if [ "x$head" != x"" ]; then
    { printf %s "$head"; cat; } | "$@"
  fi
}

Notes de conception:

  • Je m'attendrais à ce que cette implémentation fonctionne sur toutes les plates-formes POSIX / Unix, bien ddqu'à proprement parler elle ne soit pas conforme aux normes: elle repose sur le fait de ne pas lire plus que le seul octet qu'on lui a dit de lire sur son entrée standard.
  • Je pense que ce head -c 1serait un remplacement approprié pour dd bs=1 count=1 2>/dev/nullLinux.
  • D'un autre côté, head -n 1ne conviendrait pas, car il met headgénéralement en mémoire tampon son entrée et peut lire plus que la seule ligne qu'il sort - et comme il lit à partir d'un canal, les octets supplémentaires sont simplement perdus.
  • read -r headet même read -r -n 1 headne conviennent pas ici parce que si le premier caractère est une nouvelle ligne, headserait défini sur la chaîne vide, ce qui rend impossible la distinction entre une entrée vide et une entrée commençant par une ligne vierge.
  • Nous ne pouvons pas simplement écrire head=$(head -c 1)parce que si le premier caractère est une nouvelle ligne, la substitution de commande supprimerait la nouvelle ligne finale, ce qui rendrait impossible la distinction entre une entrée vide et une entrée commençant par une ligne vierge.
  • En bash, ksh ou zsh, vous pouvez remplacer catpar </dev/stdinpour un gain de performance microscopique.

Si cela ne vous dérange pas de stocker la totalité des données intermédiaires en mémoire, voici une implémentation très légèrement plus simple de pipe_if_not_empty.

pipe_if_not_empty () {
  input=$(cat; echo a);
  if [ "x$input" != x"a" ]; then
    { printf %s "${input%a}"; } | "$@"
  fi
}

Voici une implémentation légèrement plus simple avec les mises en garde suivantes:

  • Les données produites par la source sont considérées comme vides si et seulement si elles se composent uniquement de caractères de nouvelle ligne. (Cela peut en fait être souhaitable.)
  • Les données introduites dans le récepteur se terminent par exactement un caractère de nouvelle ligne, quel que soit le nombre de nouvelles lignes avec lesquelles se terminent les données produites par la source. (Cela pourrait être un problème.)

Encore une fois, toutes les données sont stockées en mémoire.

pipe_if_not_empty () {
  input=$(cat);
  if [ "x$input" != x"" ]; then
    { printf '%s\n' "${input}"; } | "$@"
  fi
}

17

Cela devrait fonctionner pour vous

$ --a function-- | [ xargs -r ] --another function--

Un exemple

$ echo -e "\n\n" | xargs -r ls
$ # No output. ls did not run.
$ echo -e "\n\n1" | xargs -r ls
ls: cannot access 1: No such file or directory

C'est simple mais ça devrait marcher pour vous. Si votre "une fonction" envoie une chaîne vide ou même une nouvelle ligne dans le pipeline, xargs -r, empêchera le passage à "une autre fonction".

Référence pour xargs: http://www.oreillynet.com/linux/cmd/cmd.csp?path=x/xargs

-r, --no-run-if-empty
Do not run command if standard input contains only blanks.


4

La fonction ci-dessous essaie de lire le 1er octet, et si elle réussit, elle fait écho à cet octet et renvoie le reste. Doit être efficace et 100% portable.

if_read() {
    IFS="" read -rN 1 BYTE && { echo -nE "$BYTE"; cat; } | "$@";
}

Cas de test:

$ echo -n | if_read wc -c
$ echo | if_read wc -c
1
$ echo -en "\nX" | if_read wc -c
2
$

Cette fonction échoue pour moi lorsque je cours echo -en "\nX" | pipe_if_not_empty mail -s "Subject line here" foo@bar.com. Il pense que lineet heresont tous deux destinataires de l'e-mail, pas des jetons dans le sujet. Je dois échapper au "sujet environnant pour le faire fonctionner. Cependant, la pipe_if_not_emptyfonction de la réponse acceptée fonctionne pour moi même sans rien échapper.
kuzzooroo

2

Au moins quelque chose comme ça fonctionne:

yourcommand | if [ $(wc -c) -gt "0" ]; then yourothercommand; fi

Veuillez noter que ce qui précède considérera les sauts de ligne et autres caractères spéciaux comme sortie, donc une ligne vide passée à cette instruction if sera considérée comme une sortie. Augmentez simplement la limite -gt si votre sortie doit généralement être supérieure à 1 octet :)


yourothercommandne voit jamais la sortie de yourcommand.
pause jusqu'à nouvel ordre.

2

Au lieu de sender | receiver:

tester () { local a=$(</dev/stdin); if [[ $a ]]; then printf '%s\n' "$a" | receiver; fi; }
sender | tester

Ou vous pouvez le rendre plus général en le modifiant pour accepter le programme récepteur comme argument comme dans la réponse de Gilles:

tester () { local a=$(</dev/stdin); if [[ $a ]]; then printf '%s\n' "$a" | "$@"; fi; }
sender | tester receiver
En utilisant notre site, vous reconnaissez avoir lu et compris notre politique liée aux cookies et notre politique de confidentialité.
Licensed under cc by-sa 3.0 with attribution required.