Paramétrez les appels chaînés vers un programme utilitaire dans Bash

12

J'ai un programme UNIX de boîte noire utilisé dans un shell Bash qui lit les colonnes de données de stdin, les traite (en appliquant un effet de lissage) puis les sort vers stdout. Je l'utilise par des tuyaux UNIX, comme

generate | smooth | plot  

Pour plus de lissage, je peux répéter le lissage, il serait donc appelé depuis la ligne de commande Bash comme

generate | smooth | smooth | plot   

ou même

generate | smooth | smooth | smooth | smooth | smooth | smooth | smooth | smooth | smooth | smooth | plot

Cela devient peu sauvage. Je voudrais créer un wrapper Bash pour pouvoir diriger smoothet réinjecter sa sortie directement dans une nouvelle instance d' smoothun nombre arbitraire de fois, quelque chose comme

generate | newsmooth 5 | plot

au lieu de

generate | smooth | smooth | smooth | smooth | smooth | plot

Ma première tentative a été un script Bash qui a généré des fichiers temporaires dans le répertoire actuel et les a supprimés, mais qui s'est avéré laid lorsque je ne me trouvais pas dans un répertoire avec accès en écriture, et a également laissé des fichiers inutiles en cas d'interruption.

Il n'y a aucun argument au smoothprogramme.

Existe-t-il une manière plus élégante de "boucler" un tel programme pour paramétrer le nombre d'appels?

Diane Wilbor
la source
1
J'espère que votre exemple est un cas forcé pour le bien de la question et non un besoin réel
arielnmz

Réponses:

18

Vous pouvez l'envelopper dans une fonction récursive:

smooth() {
  if [[ $1 -gt 1 ]]; then # add another call to function
    command smooth | smooth $(($1 - 1)) 
  else
    command smooth # no further 
  fi
}

Vous utiliseriez cela comme

generate | smooth 5 | plot

ce qui équivaudrait à

generate | smooth | smooth | smooth | smooth | smooth | plot
muru
la source
C'est parfait, se comporte exactement comme nécessaire. Et maintenant, j'ai découvert le mot-clé bash "command".
Diane Wilbor
2
Soit dit en passant, c'est la même approche que j'utilise dans Comment puis-je coder une chaîne de tuyaux arbitrairement longue? - et, bien avant cela, dans la gestion des longues listes d'édition dans xmlstarlet .
Charles Duffy
5

Si vous pouvez vous permettre de taper autant de virgules que le nombre de smoothcommandes que vous souhaitez, vous pouvez profiter de l'extension d'accolade séparée par des virgules du shell.

TL; DR

La ligne de commande entière pour votre exemple de cas serait:

generate | eval 'smooth |'{,,,,} plot

Remarque:

  • ajoutez ou supprimez des virgules si vous voulez plus ou moins de répétitions de smooth |
  • il n'y a pas |avant plotparce que cela est inclus dans la dernière smooth |chaîne produite par l'extension Brace
  • vous pouvez également fournir des arguments smooth, tant que vous pouvez les inclure correctement dans la partie fixe citée qui précède l'accolade ouverte; rappelez-vous en tout cas que vous les fournirez à toutes les répétitions de la commande

Comment ça fonctionne

L'extension d'accolade séparée par des virgules vous permet de produire dynamiquement des chaînes, chacune composée d'une partie fixe spécifiée et des parties variables spécifiées. Il produit autant de chaînes qu'il y a de parties variables indiquées, comme a{b,c,d}produit ab ac ad.

La petite astuce ici est que si vous faites plutôt une liste de parties variables vides , c'est-à-dire avec seulement des virgules à l'intérieur des accolades, l'extension d'accolade ne produira que des copies de la partie fixe. Par exemple:

smooth{,,,,}

produira:

smooth smooth smooth smooth smooth

Notez que 4 virgules produisent 5 smoothchaînes. Voilà comment fonctionne cette extension d'accolade: elle produit des chaînes autant de virgules plus une.

Bien sûr, dans votre cas, vous avez également besoin d'un |séparateur smooth, alors ajoutez-le simplement dans la partie fixe mais veillez à le citer correctement pour que la coque ne l' interprète pas immédiatement. C'est:

'smooth|'{,,,,}

produira:

'smooth|' 'smooth|' 'smooth|' 'smooth|' 'smooth|'

Veillez à toujours placer la partie fixe immédiatement à côté de l'entretoise ouverte, c'est-à-dire sans espace entre le ' et le {.

(Notez également que pour former la partie fixe, vous pouvez également utiliser des guillemets doubles au lieu de guillemets simples, si vous devez développer des variables de shell dans la partie fixe. Prenez juste soin de l'échappement supplémentaire qui est requis lorsque certains caractères spéciaux du shell se produisent dans une chaîne entre guillemets).

À ce stade, vous avez besoin d'un eval appliqué à cette chaîne afin que le shell l'interprète enfin comme la commande en pipeline qu'il est censé être.

Ainsi, pour résumer le tout, la ligne de commande entière pour votre exemple de cas serait:

generate | eval 'smooth |'{,,,,} plot
LL3
la source
1
Il existe des problèmes de sécurité importants si celui-ci est utilisé dans des endroits où l'appel est paramétré. Voir ma réponse sur la fonction récursive bash vs la construction itérative de chaînes «eval»: qui fonctionne mieux? sur Stack Overflow.
Charles Duffy
1
@CharlesDuffy Je suis entièrement d'accord avec vos préoccupations concernant les risques implicites liés à l'utilisation evallorsque l'on fournit des chaînes non fiables, non filtrées à évaluer, c'est-à-dire lorsqu'elles sont utilisées avec des variables qui peuvent contenir du contenu "inconnu" comme le cas que vous avez lié. D'un autre côté, il evalpeut également être très pratique pour une "plomberie" rapide des commandes, en particulier lorsqu'il est utilisé à l'invite, comme le cas présent semble l'être, où evall'entrée ne serait qu'une chaîne littérale tapée manuellement par l'utilisateur dans personne
LL3
Comme déjà vu ailleurs, vous pouvez toujours remplacer eval strpar quelque chose de prétentieux et stupide . /dev/stdin <<<str. Non seulement cela fera une impression sur les imbéciles, mais cela gardera également @CharlesDuffy de votre dos ;-)
pizdelect
1
@pizdelect, vous pourriez lire attentivement le commentaire précédent de LL3 - il est équilibré, nuancé et sage. (En effet, mon propre commentaire initial avait des nuances que vous semblez ignorer; "s'il est utilisé dans les cas où l'appel est paramétré" est une distinction critique: l'instance de LL3 n'est pas paramétrée, ce qui la rend sûre).
Charles Duffy