Problème général
Je veux écrire un script qui interagit avec l'utilisateur même s'il se trouve au milieu d'une chaîne de tuyaux.
Exemple concret
Concrètement, il faut un file
ou stdin
, affiche les lignes (avec des numéros de ligne), demande à l'utilisateur de saisir une sélection ou des numéros de ligne, puis imprime les lignes correspondantes dans stdout
. Appelons ce script selector
. Alors en gros, je veux pouvoir faire
grep abc foo | selector > myfile.tmp
Si foo
contient
blabcbla
foo abc bar
quux
xyzzy abc
selector
me présente ensuite (sur le terminal, pas dedans myfile.tmp
!) des options
1) blabcbla
2) foo abc bar
3) xyzzy abc
Select options:
après quoi je tape
2-3
et finir avec
foo abc bar
xyzzy abc
comme contenu de myfile.tmp
.
J'ai un script de sélection opérationnel et, fondamentalement, il fonctionne parfaitement si je ne redirige pas l'entrée et la sortie. Donc
selector foo
se comporte comme je veux. Cependant, lors de l'assemblage des éléments comme dans l'exemple ci-dessus, selector
imprime les options présentées myfile.tmp
et essaie de lire une sélection à partir de l'entrée reçue.
Mon approche
J'ai essayé d'utiliser le -u
drapeau de read
, comme dans
exec 4< /proc/$PPID/fd/0
exec 4> /proc/$PPID/fd/1
nl $INPUT >4
read -u4 -p"Select options: "
mais cela ne fait pas ce que j'espérais.
Q: Comment obtenir une interaction réelle avec l'utilisateur?
cmd | { some processing; read var </dev/tty; } | cmd
alias selector='{ TMPFILE=$(mktemp); cat > $TMPFILE; nl -s") " $TMPFILE | column -c $(tput cols); read -e -p"Select options: " < /dev/tty; rangeselect -v range="$REPLY" $TMPFILE; rm $TMPFILE; }'
qui fonctionne assez bien. Mais segrep b foo | selector | wc -l
casse ici. Des idées pour réparer celà? Soit dit en passant, celuirangeselect
que j'ai utilisé se trouve sur pastebin.com/VAxTSSHs . Il s'agit d'un simple script AWK qui imprime les lignes d'un fichier correspondant à une plage donnée de linumbers. (Les plages peuvent être des choses comme "3-10, 12,14,16-20".)alias
cela, plutôtselector() { all of that stuff...; }
dans une fonction.alias
es renomme les commandes simples tandis que les fonctions regroupent une commande composée en une seule commande simple .Réponses:
L'utilisation
/proc/$PPID/fd/0
n'est pas fiable: le parent duselector
processus peut ne pas avoir le terminal comme entrée.Il y a un chemin standard qui se réfère toujours au terminal du processus en cours:
/dev/tty
.ou
la source
J'ai écrit une petite fonction: elle ne répondra pas à ce que vous avez demandé de chaîner des tuyaux mais résoudra votre problème.
La fonction retourne tous les arguments auxquels vous lui donnez immédiatement
grep
. Si vous utilisez un glob de shell pour spécifier les fichiers qu'il doit lire, il retournera toutes les correspondances dans tous les fichiers, en commençant par le premier dans l'ordre global et en terminant par la dernière correspondance.grep
passe sa sortie ànl
laquelle numérote chaque ligne et qui passe sa sortie àtee
laquelle duplique sa sortie à la fois versstdout
et vers/dev/tty
. Cela signifie que la sortie du pipeline est imprimée simultanément à la fois dans le tableau d'arguments de la fonction où elle est divisée sur les\n
lignes électroniques et sur le terminal pendant son fonctionnement.Ensuite, la
_in()
fonction tenteread
dans une sélection s'il y a au moins 1 résultat de l'action précédente un maximum de cinq fois. La sélection peut être composée uniquement de nombres séparés par des espaces, ou bien des plages de nombres séparées par-
. Si quelque chose d'autre estread
(y compris une ligne vierge), il réessayera - mais seulement, comme précédemment, au maximum cinq fois.Enfin, la
_out()
fonction analyse la sélection de l'utilisateur et étend toutes les plages qui s'y trouvent. Il imprime ses résultats sous la forme"${[num]}"
de chacun - correspondant ainsi à la valeur des lignes stockées dansinf()
le tableau arg de. Cette sortie esteval
éditée sous forme d'arguments surprintf
lesquels n'imprime donc que les lignes sélectionnées par l'utilisateur.Il
read
provient explicitement du terminal et n'imprime que leSelect:
menu versstderr
et il est donc très convivial pour le pipeline. Par exemple, les travaux suivants:Mais vous pouvez utiliser toutes les options que vous donneriez
grep
et n'importe quel nombre de noms de fichiers que vous pourriez également lui donner . Autrement dit, vous pouvez utiliser n'importe quel type, mais un effet secondaire de son entrée d'analyse avec$IFS
elle ne fonctionnera pas si vous recherchez des lignes vides. Mais qui voudrait choisir parmi une liste numérotée de lignes vierges?Enfin, comme cela fonctionne en traduisant directement l'entrée utilisateur numérique dans les paramètres positionnels numériques stockés dans le tableau d'arguments de la fonction, la sortie sera alors ce que l'utilisateur sélectionne, autant de fois que l'utilisateur le sélectionne et dans l'ordre que l'utilisateur sélectionne il.
Par exemple:
la source