Bash: rediriger l'entrée standard dynamiquement dans un script
j'essayais de le faire pour décider s'il fallait rediriger 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, ni créer une fonction pour elle parce que ce n'est pas si long non plus (c'est pas la peine).
alors il m'est venu à l'esprit de faire ceci:
[ ...condition... ] && input=$fileName || input= #empty
cat $input | ./myScript
mais cela nécessite d'exécuter une commande supplémentaire et un tuyau (c.-à-d. un sous-puits).
Est-il une autre façon, plus simple et plus efficace?
7 réponses
tout d'abord stdin est le descripteur de fichier 0 (zéro) plutôt que 1 (qui est stdout).
vous pouvez dupliquer des descripteurs de fichier ou utiliser des noms de fichiers conditionnellement comme ceci:
[[ some_condition ]] && exec 3<"$filename" || exec 3<&0
some_long_command_line <&3
Notez que la commande s'execute la deuxième exec
si la condition est fausse ou la première exec
échoue. Si vous ne voulez pas un échec potentiel de faire cela, alors vous devriez utiliser un if
/ else
:
if [[ some_condition ]]
then
exec 3<"$filename"
else
exec 3<&0
fi
mais les redirections subséquentes du descripteur de fichier 3 échoueront si la première redirection échoue (après que la condition était vraie).
(
if [ ...some condition here... ]; then
exec <$fileName
fi
exec ./myscript
)
dans une sous-couche, redirigez conditionnellement stdin et exec le script.
entrée Standard peut également être représenté par le fichier de périphérique spécial /dev/stdin
, de sorte que l'utilisation de celui-ci comme un nom de fichier fonctionnera.
file="/dev/stdin"
./myscript < "$file"
Que Diriez-vous de
function runfrom {
local input=""
shift
case "$input" in
-) "$@" ;;
*) "$@" < "$input" ;;
esac
}
j'ai utilisé le signe moins pour indiquer l'entrée standard car c'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 une invocation de commande complexe; si elle implique des arguments qui pourraient contenir des espaces, alors vous devez être très prudent avant de décider d'utiliser ' eval
".
franchement, s'inquiéter du coût d'une commande cat
" ne vaut probablement pas la peine; il est peu probable que ce soit le goulot d'étranglement.
est encore mieux de concevoir myScript
de sorte qu'il fonctionne comme un filtre Unix régulier - il lit à partir de l'entrée standard à moins qu'il ne soit donné un ou plusieurs fichiers à travailler (comme, par exemple, cat
ou grep
). Cette conception est basée sur une longue et solide expérience - et vaut donc la peine d'émuler pour éviter d'avoir à faire face à des problèmes tels que celui-ci.
Utilisation eval
:
#! /bin/bash
[ $# -gt 0 ] && input="'""'" || input="&1"
eval "./myScript <$input"
ce simple stand-in pour myScript
#! /usr/bin/perl -lp
$_ = reverse
produit la sortie suivante:
$ ./myDemux myScript pl- lrep/nib/rsu/ !# esrever = _$ $ ./myDemux foo oof bar rab baz zab
noter qu'il gère les espaces dans les entrées aussi:
$ ./myDemux foo\ bar eman eht ni ecaps a htiw elif
pour ramener le débit à myScript
, utiliser substitution de procédé :
$ ./myDemux <(md5sum /etc/issue) eussi/cte/ 01672098e5a1807213d5ba16e00a7ad0
noter que si vous essayez de rediriger la sortie directement, comme dans
$ md5sum /etc/issue | ./myDemux
il sera suspendu à l'attente de l'entrée du terminal, tandis que la réponse d'ephemient n'a pas cette lacune.
un léger changement produit le comportement désiré:
#! /bin/bash
[ $# -gt 0 ] && input="'""'" || input=/dev/stdin
eval "./myScript <$input"
les gens vous montrent des scripts très longs, mais.... vous obtenez piège de bash :) Vous devez citer tout à bash. par exemple, vous voulez le fichier list named &0 .
filename='&0' #droit ls $filename #faux! ce substitut $filename et interpret &0 ls "$filename" #droit
un autre, des fichiers avec des espaces.
filename=' certains de fichier avec des espaces" ls $filename #faux, bash couper première et la dernière de l'espace, et de réduire de plusieurs espaces entre avec et des espaces de paroles ls "$filename" droit
c'est la même chose 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 des devis pour "$fichier" avec la même raison. les espaces et autres caractères qui peuvent être interprétés posent toujours des problèmes.
mais qu'en est-il de /dev/stdin ? c'est utilisable uniquement lorsque vous redirigé stdin et que vous voulez imprimer quelque chose de réel stdin.
ainsi, votre script devrait montrer comme ceci:
[ ...some condition here... ] && input="$fileName" || input="&0"
./myScript < "$input"