Comment composer des fonctions bash en utilisant des pipes?

18

J'ai peu de fonctions définies de cette façon:

function f {
  read and process $1
  ...
  echo $result
}

Je veux les composer ensemble pour que l'invocation ressemble f | g | h.

Quel idiome dois-je utiliser pour convertir une fonction travaillant sur des arguments en un seul argument de lecture de stdin? Est-il possible de lire des paires, des tuples d'arguments à partir d'un flux sans avoir besoin de les échapper (par exemple, terminaison nulle)?

Rumca
la source
Soit vous voulez quelque chose comme, h(g(f(...)))soit chacune des fonctions lit à partir de l'entrée standard ( read x; ...) et écrit sur la sortie standard ( echo ...).
vonbrand

Réponses:

21

Une approche potentielle serait de mettre une while...readconstruction à l'intérieur de vos fonctions qui traiterait toutes les données entrées dans la fonction via STDIN, les exploiterait, puis émettrait les données résultantes via STDOUT.

function X {
  while read data; do
    ...process...
  done
}

Il faudra faire attention à la façon dont vous configurez vos while ..read..composants, car ils dépendront fortement des types de données qu'ils pourront consommer de manière fiable. Il peut y avoir une configuration optimale que vous pouvez trouver.

Exemple

$ logF() { while read data; do echo "[F:$(date +"%D %T")] $data"; done; }
$ logG() { while read data; do echo "G:$data";                    done; }
$ logH() { while read data; do echo "H:$data";                    done; }

Voici chaque fonction seule.

$ echo "hi" | logF
[F:02/07/14 20:01:11] hi

$ echo "hi" | logG
G:hi

$ echo "hi" | logH
H:hi

Les voici quand nous les utilisons ensemble.

$ echo "hi" | logF | logG | logH
H:G:[F:02/07/14 19:58:18] hi

$ echo -e "hi\nbye" | logF | logG | logH
H:G:[F:02/07/14 19:58:22] hi
H:G:[F:02/07/14 19:58:22] bye

Ils peuvent accepter différents styles d'entrée.

#-- ex. #1
$ cat <<<"some string of nonsense" | logF | logG | logH
H:G:[F:02/07/14 20:03:47] some string of nonsense

#-- ex. #2    
$ (logF | logG | logH) <<<"Here comes another string."
H:G:[F:02/07/14 20:04:46] Here comes another string.

#-- ex. #3
$ (logF | logG | logH)
Look I can even
H:G:[F:02/07/14 20:05:19] Look I can even
type to it
H:G:[F:02/07/14 20:05:23] type to it
live
H:G:[F:02/07/14 20:05:25] live
via STDIN
H:G:[F:02/07/14 20:05:29] via STDIN
..type Ctrl + D to stop..

#-- ex. #4
$ seq 5 | logF | logG | logH
H:G:[F:02/07/14 20:07:40] 1
H:G:[F:02/07/14 20:07:40] 2
H:G:[F:02/07/14 20:07:40] 3
H:G:[F:02/07/14 20:07:40] 4
H:G:[F:02/07/14 20:07:40] 5

#-- ex. #5
$ (logF | logG | logH) < <(seq 2)
H:G:[F:02/07/14 20:15:17] 1
H:G:[F:02/07/14 20:15:17] 2
slm
la source
4

En complément à la réponse de slm , j'ai fait quelques expériences avec des tuples séparés par des null comme arguments de fonction:

$ sayTuple() { 
    IFS= read -r -d $'\0' d1
    IFS= read -r -d $'\0' d2
    echo "sayTuple: -$d1- -$d2-"
}

Remarques: sayTuplelit deux fois un enregistrement terminé par un nul -d $'\0'gérant tout espace entourant l'entrée IFS=. echodossiers de retour entourés de-

Le résultat montre qu'il gère correctement les entrées à terminaison nulle contenant \net \t:

$ printf "%s\0%s\0" "Hello " $' Brave\n\tWorld' | sayTuple 
sayTuple: -Hello - - Brave
        World-

Veuillez ajouter des suggestions d'amélioration dans les commentaires, c'est un sujet intéressant.

grebneke
la source
+1 comme votre idée. Nous pourrions plutôt mettre une boucle à l'intérieur qui lui permettrait de prendre des # d'arguments arbitraires. sayTuple() { arr=() ; while IFS= read -r -d $'\0' arg; do arr+="$arg"; done; echo "sayTuple: ${arr[@]}"; }.
slm
On dirait que vous devriez pouvoir en faire un, IFS= read -r -d $'\0' -a argmais je n'ai pas réussi à le faire fonctionner. Cela permettrait de supprimer lewhile , ce qui semble inutile, mais c'était le seul modèle que je pouvais obtenir.
slm