web-dev-qa-db-fra.com

Bash: redirige de manière dynamique l'entrée standard dans un script

J'essayais de le faire pour décider de 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, je ne veux pas non plus créer de fonction car elle n’est pas si longue non plus (ça ne vaut pas la peine).

Alors il m'est venu à l'idée 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’est-à-dire un sous-shell).
Y a-t-il un autre moyen plus simple et plus efficace?

26
GetFree

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 des noms de fichiers conditionnellement comme ceci:

[[ some_condition ]] && exec 3<"$filename" || exec 3<&0

some_long_command_line <&3

Notez que la commande affichée exécute la seconde exec si la condition est fausse ou la première exec échoue. Si vous ne voulez pas que cela ne se produise pas, vous devriez utiliser un if/else:

if [[ some_condition ]]
then
    exec 3<"$filename"
else
    exec 3<&0
fi

mais les redirections suivantes à partir du descripteur de fichier 3 échoueront si la première redirection échouait (après que la condition était vraie).

22
Dennis Williamson
(
    if [ ...some condition here... ]; then
        exec <$fileName
    fi
    exec ./myscript
)

Dans un sous-shell, redirigez de manière conditionnelle stdin et exécutez le script.

7
ephemient

L'entrée standard peut également être représentée par le fichier de périphérique spécial /dev/stdin; son utilisation comme nom de fichier fonctionnera donc.

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

Que diriez-vous

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

J'ai utilisé le signe moins pour indiquer l'entrée standard, car il s'agit de la tradition de nombreux programmes Unix.

Maintenant vous écrivez

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

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

2
Norman Ramsey

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 réalité une invocation de commande complexe; s'il s'agit d'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 'cat' ne vaut probablement pas la peine; il est peu probable que ce soit le goulot d'étranglement.

Mieux encore, concevez myScript de sorte qu'il fonctionne comme un filtre Unix normal: il lit à partir d'une entrée standard à moins qu'un ou plusieurs fichiers ne fonctionnent (par exemple, cat ou grep Cette conception est basée sur une longue et solide expérience - et mérite donc d’être émulée pour éviter de devoir traiter de tels problèmes.

2
Jonathan Leffler

Utilisez eval :

#! /bin/bash

[ $# -gt 0 ] && input="'"$1"'" || input="&1"

eval "./myScript <$input"

Ce remplacement simple pour myScript

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

produit la sortie suivante:

 $ ./myDemux myScript 
 pl- lrep/nib/rsu /! # 
 esrever = _ $ 

 $ ./myDemux. 
 rab 
 baz 
 zab 

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

 $ ./myDemux foo\bar 
 eman eht ni ecaps a htiw Elif 

Pour diriger l’entrée vers myScript, utilisez processus substitution :

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

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

 $ md5sum/etc/issue | ./myDemux

il attendra en attente d'une entrée du terminal, alors que la réponse de l'éphémien 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"
1
Greg Bacon

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

filename = '& 0' #right ls $ filename #wrong! ce substitut $ filename et interprète & 0 ls "$ filename" #right

un autre, les fichiers avec des espaces.

filename = 'un fichier avec des espaces' ls $ filename #wrong, bash coupe le premier et le dernier espace et réduit les espaces multiples entre les mots avec et les espacesls "$ filename" righ

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 pouvant être interprétés sont toujours des problèmes.

mais qu'en est-il de/dev/stdin? cela n'est utilisable que lorsque vous avez redirigé stdin et que vous voulez imprimer quelque chose sur stdin réel.

alors, votre script devrait montrer comme ceci:

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