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?
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).
(
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.
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"
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.
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.
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"
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"