web-dev-qa-db-fra.com

Lire des valeurs dans une variable Shell à partir d'un tuyau

J'essaie de faire en sorte que bash traite les données de stdin qui sont transmises, mais pas de chance. Ce que je veux dire n’est aucun des travaux suivants:

echo "hello world" | test=($(< /dev/stdin)); echo test=$test
test=

echo "hello world" | read test; echo test=$test
test=

echo "hello world" | test=`cat`; echo test=$test
test=

où je veux que la sortie soit test=hello world. J'ai essayé de mettre "" des citations autour de "$test" qui ne fonctionne pas non plus.

189
ldog

Utilisation

IFS= read var << EOF
$(foo)
EOF

Vous pouvez tromper read en accepter un tuyau comme celui-ci:

echo "hello world" | { read test; echo test=$test; }

ou même écrire une fonction comme celle-ci:

read_from_pipe() { read "$@" <&0; }

Mais cela ne sert à rien - vos affectations variables peuvent ne pas durer! Un pipeline peut générer un sous-shell, où l'environnement est hérité par valeur et non par référence. C’est pourquoi read ne se soucie pas de l’entrée d’un tuyau, c’est indéfini.

FYI, http://www.etalabs.net/sh_tricks.html est un recueil astucieux de la cruauté nécessaire pour lutter contre les bizarreries et les incompatibilités des coquillages, sh.

157
yardena

si vous voulez lire beaucoup de données et travailler sur chaque ligne séparément, vous pouvez utiliser quelque chose comme ceci:

cat myFile | while read x ; do echo $x ; done

si vous voulez diviser les lignes en plusieurs mots, vous pouvez utiliser plusieurs variables à la place de x comme ceci:

cat myFile | while read x y ; do echo $y $x ; done

alternativement:

while read x y ; do echo $y $x ; done < myFile

Mais dès que vous commencez à vouloir faire quelque chose de vraiment intelligent avec ce genre de chose, vous ferez mieux de choisir un langage de script comme Perl, où vous pourrez essayer quelque chose comme ceci:

Perl -ane 'print "$F[0]\n"' < myFile

La courbe d'apprentissage de Perl est assez raide (ou de l'une de ces langues), mais vous le trouverez beaucoup plus facile à long terme si vous voulez faire autre chose que le plus simple des scripts. Je recommanderais le livre de recettes Perl et, bien sûr, le langage de programmation Perl de Larry Wall et al.

105
Nick

read ne lira pas à partir d'un canal (ou le résultat est éventuellement perdu car le canal crée un sous-shell). Vous pouvez cependant utiliser une chaîne here dans Bash:

$ read a b c <<< $(echo 1 2 3)
$ echo $a $b $c
1 2 3

Mais voir la réponse de @ chepner pour des informations sur lastpipe.

38
Dennis Williamson

C'est une autre option

$ read test < <(echo hello world)

$ echo $test
hello world
38
Steven Penny

Je ne suis pas un expert en Bash, mais je me demande pourquoi cela n'a pas été proposé:

stdin=$(cat)

echo "$stdin"

Preuve unique que cela fonctionne pour moi:

$ fortune | eval 'stdin=$(cat); echo "$stdin"'
30
djanowski

bash 4.2 introduit l'option lastpipe, qui permet à votre code de fonctionner tel quel, en exécutant la dernière commande d'un pipeline dans le Shell actuel, plutôt que dans un sous-shell.

shopt -s lastpipe
echo "hello world" | read test; echo test=$test
22
chepner

La syntaxe d'un canal implicite d'une commande Shell dans une variable bash est la suivante:

var=$(command)

ou

var=`command`

Dans vos exemples, vous dirigez des données vers une instruction d'affectation, qui n'attend aucune entrée.

13
mob

La première tentative fut assez serrée. Cette variation devrait fonctionner:

echo "hello world" | { test=$(< /dev/stdin); echo "test=$test"; };

et le résultat est:

test = bonjour le monde

Vous avez besoin d'accolades après le tuyau pour inclure l'affectation à tester et l'écho.

Sans les accolades, l'affectation à tester (après le tube) se trouve dans un shell et l'écho "test = $ test" se trouve dans un shell séparé qui ne connaît pas cette affectation. C'est pourquoi vous obtenez "test =" dans la sortie au lieu de "test = hello world".

6
jbuhacoff

Parce que je craque, je voudrais laisser tomber une note. J'ai trouvé ce fil, car je dois réécrire un ancien script sh pour qu'il soit compatible POSIX. Cela signifie fondamentalement de contourner le problème pipe/subshell introduit par POSIX en réécrivant le code comme ceci:

some_command | read a b c

dans:

read a b c << EOF
$(some_command)
EOF

Et code comme ça:

some_command |
while read a b c; do
    # something
done

dans:

while read a b c; do
    # something
done << EOF
$(some_command)
EOF

Mais ce dernier ne se comporte pas de la même manière avec une entrée vide. Avec l'ancienne notation, la boucle while n'est pas entrée sur une entrée vide, mais en notation POSIX, c'est le cas! Je pense que cela est dû à la nouvelle ligne avant EOF, qui ne peut pas être omise. Le code POSIX qui se comporte plus comme l'ancienne notation ressemble à ceci:

while read a b c; do
    case $a in ("") break; esac
    # something
done << EOF
$(some_command)
EOF

Dans la plupart des cas, cela devrait suffire. Mais malheureusement, cela ne se comporte toujours pas exactement comme l'ancienne notation si une_commande affiche une ligne vide. Dans l'ancienne notation, le corps while est exécuté et dans la notation POSIX, nous nous cassons devant le corps.

Une approche pour résoudre ce problème pourrait ressembler à ceci:

while read a b c; do
    case $a in ("something_guaranteed_not_to_be_printed_by_some_command") break; esac
    # something
done << EOF
$(some_command)
echo "something_guaranteed_not_to_be_printed_by_some_command"
EOF
5
hd-quadrat

À mes yeux, la meilleure façon de lire stdin in bash est la suivante, qui vous permet également de travailler sur les lignes avant la fin de la saisie:

while read LINE; do
    echo $LINE
done < /dev/stdin
5
Jaleks

Introduire quelque chose dans une expression impliquant une tâche ne se comporte pas comme ça.

Au lieu de cela, essayez:

test=$(echo "hello world"); echo test=$test
3
bta

Le code suivant:

echo "hello world" | ( test=($(< /dev/stdin)); echo test=$test )

fonctionnera aussi, mais il ouvrira un autre nouveau sous-shell après le tuyau, où

echo "hello world" | { test=($(< /dev/stdin)); echo test=$test; }

habitude.


Je devais désactiver le contrôle du travail pour utiliser la méthode chepnars '(j'exécutais cette commande depuis le terminal):

set +m;shopt -s lastpipe
echo "hello world" | read test; echo test=$test
echo "hello world" | test="$(</dev/stdin)"; echo test=$test

Le manuel de Bash dit :

dernier tuyau

S'il est défini et que le contrôle de travail n'est pas actif , le shell exécute la dernière commande d'un pipeline non exécuté en arrière-plan dans l'environnement Shell en cours.

Remarque: le contrôle de travail est désactivé par défaut dans un shell non interactif. Vous n'avez donc pas besoin de set +m dans un script.

2
Jahid

Je pense que vous essayiez d'écrire un script Shell qui pourrait prendre en charge l'entrée de stdin. mais pendant que vous essayez de le faire en ligne, vous vous êtes perdu en essayant de créer cette variable test =. Je pense que cela n'a pas beaucoup de sens de le faire en ligne, et c'est pourquoi cela ne fonctionne pas comme vous le souhaitez.

J'essayais de réduire

$( ... | head -n $X | tail -n 1 )

pour obtenir une ligne spécifique à partir de différentes entrées. pour que je puisse taper ...

cat program_file.c | line 34

il me faut donc un petit programme Shell capable de lire stdin. comme vous le faites.

22:14 ~ $ cat ~/bin/line 
#!/bin/sh

if [ $# -ne 1 ]; then echo enter a line number to display; exit; fi
cat | head -n $1 | tail -n 1
22:16 ~ $ 

voilà.

1
Mathieu J.

Un script intelligent pouvant à la fois lire des données à partir de PIPE et des arguments de ligne de commande:

#!/bin/bash
if [[ -p /proc/self/fd/0 ]]
    then
    PIPE=$(cat -)
    echo "PIPE=$PIPE"
fi
echo "ARGS=$@"

Sortie:

$ bash test arg1 arg2
ARGS=arg1 arg2

$ echo pipe_data1 | bash test arg1 arg2
PIPE=pipe_data1
ARGS=arg1 arg2

Explication: Lorsqu'un script reçoit des données via un canal, stdin/proc/self/fd/0 sera un lien symbolique vers un canal.

/proc/self/fd/0 -> pipe:[155938]

Sinon, il pointe vers le terminal actuel:

/proc/self/fd/0 -> /dev/pts/5

L'option bash if -p peut vérifier si c'est un tuyau ou non.

cat - lit le depuis stdin.

Si nous utilisons cat - quand il n'y a pas stdin, il attendra toujours, c'est pourquoi nous le plaçons dans la condition if.

0
Seff