linux - Bizarreries de redirection d'entrée de script shell

Translate

Quelqu'un peut-il expliquer ce comportement? Fonctionnement:

#!/bin/sh
echo "hello world" | read var1 var2
echo $var1
echo $var2

ne produit rien, alors que:

#!/bin/sh
echo "hello world" > test.file
read var1 var2 < test.file
echo $var1
echo $var2

produit le résultat attendu:

hello
world

Le tube ne devrait-il pas faire en une seule étape ce que la redirection vers test.file a fait dans le deuxième exemple? J'ai essayé le même code avec les shells dash et bash et j'ai obtenu le même comportement des deux.

This question and all comments follow the "Attribution Required."

Toutes les réponses

Translate

Un ajout récent àbashest lelastpipeoption, qui permet à la dernière commande d'un pipeline de s'exécuter dans le shell actuel, et non dans un sous-shell, lorsque le contrôle des travaux est désactivé.

#!/bin/bash
set +m      # Deactiveate job control
shopt -s lastpipe
echo "hello world" | read var1 var2
echo $var1
echo $var2

produira en effet

hello
world
La source
Translate
#!/bin/sh
echo "hello world" | read var1 var2
echo $var1
echo $var2

ne produit aucune sortie car les pipelines exécutent chacun de leurs composants dans un sous-shell. Sous-coquilleshériter de copiesdes variables du shell parent, plutôt que de les partager. Essaye ça:

#!/bin/sh
foo="contents of shell variable foo"
echo $foo
(
    echo $foo
    foo="foo contents modified"
    echo $foo
)
echo $foo

Les parenthèses définissent une région de code qui s'exécute dans un sous-shell, et $ foo conserve sa valeur d'origine après avoir été modifiée à l'intérieur.

Maintenant, essayez ceci:

#!/bin/sh
foo="contents of shell variable foo"
echo $foo
{
    echo $foo
    foo="foo contents modified"
    echo $foo
}
echo $foo

Les accolades sont purement pour le regroupement, aucun sous-shell n'est créé, et le $ foo modifié à l'intérieur des accolades est le même $ foo modifié à l'extérieur.

Maintenant, essayez ceci:

#!/bin/sh
echo "hello world" | {
    read var1 var2
    echo $var1
    echo $var2
}
echo $var1
echo $var2

À l'intérieur des accolades, la fonction intégrée read crée correctement $ var1 et $ var2 et vous pouvez voir qu'ils sont renvoyés. En dehors des accolades, elles n'existent plus. Tout le code entre accolades a été exécuté dans un sous-shellparce que c'est un composant d'un pipeline.

Vous pouvez mettre des quantités arbitraires de code entre accolades, vous pouvez donc utiliser cette construction de piping-into-a-block chaque fois que vous avez besoin d'exécuter un bloc de script shell qui analyse la sortie de quelque chose d'autre.

La source
Translate

Cela a déjà été répondu correctement, mais la solution n'a pas encore été énoncée. Utilisez ksh, pas bash. Comparer:

$ echo 'echo "hello world" | read var1 var2
echo $var1
echo $var2' | bash -s

À:

$ echo 'echo "hello world" | read var1 var2
echo $var1
echo $var2' | ksh -s
hello
world

ksh est un shell de programmation supérieur en raison de petites subtilités comme celle-ci. (bash est le meilleur shell interactif, à mon avis.)

La source
Translate
read var1 var2 < <(echo "hello world")
La source
Bob Lee
Translate

Le message a reçu une réponse correcte, mais je voudrais proposer une autre doublure qui pourrait peut-être être utile.

Pour affecter des valeurs séparées par des espaces de echo (ou stdout d'ailleurs) aux variables shell, vous pouvez envisager d'utiliser des tableaux shell:

$ var=( $( echo 'hello world' ) )
$ echo ${var[0]}
hello
$ echo ${var[1]}
world

Dans cet exemple, var est un tableau et le contenu est accessible en utilisant la construction $ {var [index]}, où index est l'index du tableau (commence par 0).

De cette façon, vous pouvez affecter autant de paramètres que vous le souhaitez à l'index de tableau approprié.

La source
Translate

Mon avis sur ce problème (en utilisant Bash):

read var1 var2 <<< "hello world"
echo $var1 $var2
La source
Translate

Très bien, je l'ai compris!

C'est un bogue difficile à attraper, mais il résulte de la façon dont les tubes sont gérés par le shell. Chaque élément d'un pipeline s'exécute dans un processus distinct. Lorsque la commande read définit var1 et var2, elle les définit dans son propre sous-shell, pas dans le shell parent. Ainsi, lorsque le sous-shell se termine, les valeurs de var1 et var2 sont perdues. Vous pouvez cependant essayer de faire

var1=$(echo "Hello")
echo var1

qui renvoie la réponse attendue. Malheureusement, cela ne fonctionne que pour des variables uniques, vous ne pouvez pas en définir plusieurs à la fois. Afin de définir plusieurs variables à la fois, vous devez soit lire une variable et la découper en plusieurs variables, soit utiliser quelque chose comme ceci:

set -- $(echo "Hello World")
var1="$1" var2="$2"
echo $var1
echo $var2

Bien que j'admette que ce n'est pas aussi élégant que d'utiliser un tuyau, cela fonctionne. Bien sûr, vous devez garder à l'esprit que la lecture était destinée à lire des fichiers dans des variables, donc le faire lire à partir d'une entrée standard devrait être un peu plus difficile.

La source
Betty Lee
Translate

C'est parce que la version du tube crée un sous-shell, qui lit la variable dans son espace local qui est ensuite détruit lorsque le sous-shell se termine.

Exécutez cette commande

$ echo $$;cat | read a
10637

et utilisez pstree -p pour regarder les processus en cours, vous verrez un shell supplémentaire suspendu à votre shell principal.

    |                       |-bash(10637)-+-bash(10786)
    |                       |             `-cat(10785)
La source
pjz
Translate

Essayer:

echo "hello world" | (read var1 var2 ; echo $var1 ; echo $var2 )

Le problème, comme plusieurs personnes l'ont déclaré, est que var1 et var2 sont créés dans un environnement de sous-shell qui est détruit lorsque ce sous-shell se termine. Ce qui précède évite de détruire le sous-shell tant que le résultat n'a pas été répercuté. Une autre solution est:

result=`echo "hello world"`
read var1 var2 <<EOF
$result
EOF
echo $var1
echo $var2
La source