Bash: redirect dynamicment l’entrée standard dans un script

J’essayais de le faire pour décider de redirect stdin vers un fichier ou non:

[ ...some condition here... ] && input=$fileName || input="&0" ./myScript < $input 

Mais cela ne fonctionne pas car lorsque la variable $ input est “& 0”, bash l’interprète comme un nom de fichier.

Cependant, je pourrais juste faire:

 if [ ...condition... ];then ./myScript <$fileName else ./myScript 

Le problème est que ./myScript est en fait une longue ligne de commande que je ne veux pas dupliquer, et je ne veux pas non plus créer une fonction parce que ce n’est pas très long non plus (ça ne vaut pas la peine).

Ensuite, j’ai pensé à faire ceci:

 [ ...condition... ] && input=$fileName || input= #empty cat $input | ./myScript 

Mais cela nécessite d’exécuter une commande supplémentaire et un canal (c’est-à-dire un sous-shell).
Y a-t-il une autre façon plus simple et plus efficace?

Tout d’abord stdin est le descripteur de fichier 0 (zéro) plutôt que 1 (qui est stdout).

Vous pouvez dupliquer les descripteurs de fichiers ou utiliser les noms de fichiers comme ceci:

 [[ some_condition ]] && exec 3< $filename || exec 3<&0 some_long_command_line <&3 
 ( if [ ...some condition here... ]; then exec < $fileName fi exec ./myscript ) 

Dans un sous-shell, redirigez conditionnellement stdin et exécutez le script.

L’entrée standard peut également être représentée par le fichier de périphérique spécial /dev/stdin , de sorte que l’utilisation de ce nom comme fichier fonctionnera.

 file="/dev/stdin" ./myscript < "$file" 

Que diriez-vous

 function runfrom { local input="$1" shift case "$input" in -) "$@" ;; *) "$@" < "$input" ;; esac } 

J'ai utilisé le signe moins pour désigner une entrée standard, ce qui est traditionnel pour de nombreux programmes Unix.

Maintenant vous écrivez

 [ ... condition ... ] && input="$fileName" || input="-" runfrom "$input" my-complicated-command with many arguments 

Je trouve que ces fonctions / commandes qui prennent des commandes comme arguments (comme xargs(1) ) peuvent être très utiles, et elles composent bien.

Si vous êtes prudent, vous pouvez utiliser ” eval ” et votre première idée.

 [ ...some condition here... ] && input=$fileName || input="&1" eval ./myScript < $input 

Cependant, vous dites que «myScript» est en fait un appel de commande complexe; si cela implique des arguments pouvant contenir des espaces, vous devez être très prudent avant de décider d'utiliser ' eval '.

Franchement, s'inquiéter du coût d'une commande de « cat » n'en vaut probablement pas la peine; il est peu probable que ce soit le goulot d'étranglement.

Mieux encore, il est préférable de concevoir myScript pour qu'il fonctionne comme un filtre Unix standard - il lit à partir d'une entrée standard, à moins qu'il ne lui soit atsortingbué un ou plusieurs fichiers (comme, par exemple, cat ou grep comme exemples). Cette conception est basée sur une longue et solide expérience et mérite donc d’être émulée pour éviter de devoir gérer des problèmes comme celui-ci.

Utilisez eval :

 #! /bin/bash [ $# -gt 0 ] && input="'"$1"'" || input="&1" eval "./myScript < $input" 

Ce simple remplaçant pour myScript

 #! /usr/bin/perl -lp $_ = reverse 

produit la sortie suivante:

 $ ./myDemux myScript
 plrep / nib / rsu /! #
 esrever = _ $

 $ ./myDemux
 foo
 oof
 bar
 rab
 baz
 zab

Notez qu'il gère également les espaces dans les entrées:

 $ ./myDemux foo \ bar
 eman eht ni ecaps a hifw elif

Pour myScript entrées vers myScript , utilisez la substitution de processus :

 $ ./myDemux < (md5sum / etc / issue)
 eussi / cte / 01672098e5a1807213d5ba16e00a7ad0

Notez que si vous essayez de diriger la sortie directement, comme dans

 $ md5sum / etc / issue |  ./mydemux

il attendra à l'entrée du terminal, alors que la réponse d'ephemient n'a pas cette lacune.

Un léger changement produit le comportement souhaité:

 #! /bin/bash [ $# -gt 0 ] && input="'"$1"'" || input=/dev/stdin eval "./myScript < $input" 

les gens vous montrent des scripts très longs, mais …. vous obtenez un piège bash 🙂 Vous devez citer tout en bash. Par exemple, vous voulez un fichier de liste nommé & 0.

filename = ‘& 0’ #right ls $ filename #wrong! cela remplace $ filename et interprète & 0 ls “$ filename” #right

un autre, des fichiers avec des espaces.

filename = ‘certains fichiers avec des espaces’ ls $ filename #wrong, bash coupe le premier et le dernier espace et réduit plusieurs espaces entre les mots avec et les espaces ls “$ filename” just

la même chose est dans votre script. s’il vous plaît changer:

 ./myScript < $input 

à

 ./myScript < "$input" 

c'est tout. bash a plus de pièges. Je suggère de faire une citation pour "$ file" avec la même raison. les espaces et autres caractères que l'on peut interpréter posent toujours problème.

mais qu'en est-il de / dev / stdin? Ceci est utilisable seulement lorsque vous redirigez stdin et que vous voulez imprimer quelque chose sur un fichier réel.

donc, votre script devrait afficher comme ceci:

 [ ...some condition here... ] && input="$fileName" || input="&0" ./myScript < "$input"