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?

25
demandé sur GetFree 2010-01-01 00:01:09

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).

22
répondu Dennis Williamson 2018-09-17 13:53:05
(
    if [ ...some condition here... ]; then
        exec <$fileName
    fi
    exec ./myscript
)

dans une sous-couche, redirigez conditionnellement stdin et exec le script.

7
répondu ephemient 2009-12-31 23:23:49

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"
4
répondu Ignacio Vazquez-Abrams 2010-01-01 00:43:54

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.

2
répondu Norman Ramsey 2009-12-31 21:37:40

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.

2
répondu Jonathan Leffler 2009-12-31 22:08:20

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"
1
répondu Greg Bacon 2017-05-23 12:25:45

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"
0
répondu Znik 2013-08-22 13:59:06