Lire des valeurs dans une variable shell à partir d’un tube

J’essaie d’obtenir bash pour traiter les données de stdin qui est diffusé, 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 fonctionnent pas non plus.

Utilisation

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

Vous pouvez tromper la read en acceptant d'un tuyau comme ceci:

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

ou même écrire une fonction comme ceci:

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

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

Pour info, http://www.etalabs.net/sh_sortingcks.html est une collection astucieuse de la cruauté nécessaire pour combattre les bizarreries et les incompatibilités des coquilles de bourne, sh.

Si vous voulez lire beaucoup de données et travailler séparément sur chaque ligne, 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 xy ; do echo $y $x ; done 

alternativement:

 while read xy ; 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 feriez mieux de choisir un langage de script comme perl où vous pourriez essayer quelque chose comme ceci:

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

Il y a une courbe d'apprentissage assez raide avec perl (ou je suppose un de ces langages) mais vous trouverez cela beaucoup plus facile à long terme si vous voulez faire autre chose que le plus simple des scripts. Je recommande le livre de recettes Perl et, bien sûr, le langage de programmation Perl de Larry Wall et al.

Ceci est une autre option

 $ read test < <(echo hello world) $ echo $test hello world 

read ne lit pas dans un tube (ou peut-être le résultat est-il perdu car le tube crée un sous-shell). Vous pouvez cependant utiliser une chaîne ici dans Bash:

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

Je ne suis pas 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"' 

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

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

La syntaxe d’un tube implicite à partir d’une commande shell dans une variable bash est

 var=$(command) 

ou

 var=`command` 

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

A mes yeux, la meilleure façon de lire stdin dans 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 

La première tentative était assez proche. Cette variation devrait fonctionner:

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

et la sortie est la suivante:

test = salut monde

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

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

Passer quelque chose dans une expression impliquant une affectation ne se comporte pas comme ça.

Au lieu de cela, essayez:

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

Le code suivant:

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

va travailler aussi, mais il va ouvrir un autre nouveau sous-shell après le tuyau, où

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

habitude.


J'ai dû désactiver le contrôle du travail pour utiliser la méthode de chepnars ( j'exécutais cette commande à partir du terminal):

 set +m;shopt -s lastpipe echo "hello world" | read test; echo test=$test echo "hello world" | test="$( 

Le manuel de Bash dit :

dernier tuyau

S'il est défini et que le contrôle des travaux 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 actuel.

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

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

 some_command | read abc 

dans:

 read abc < < EOF $(some_command) EOF 

Et le code comme ceci:

 some_command | while read abc; do # something done 

dans:

 while read abc; do # something done < < EOF $(some_command) EOF 

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

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

Dans la plupart des cas, cela devrait être suffisant. Mais malheureusement, cela ne se comporte toujours pas comme l'ancienne notation si some_command imprime une ligne vide. Dans l'ancienne notation, le corps while est exécuté et en notation POSIX, nous nous cassons devant le corps.

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

 while read abc; 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 

Je pense que vous essayiez d’écrire un script shell qui pourrait entrer dans stdin. mais pendant que vous essayez de le faire en ligne, vous avez perdu en essayant de créer ce test = variable. Je pense que cela n’a pas beaucoup de sens de le faire en ligne, et c’est pourquoi cela ne fonctionne pas comme prévu.

J’essayais de réduire

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

pour obtenir une ligne spécifique à partir de diverses entrées. donc je pourrais taper …

 cat program_file.c | line 34 

J’ai donc besoin d’un petit programme shell capable de lire stdin. comme tu fais.

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

Que dis-tu de ça:

 echo "hello world" | echo test=$(cat)