Je comprends évidemment que l'on peut ajouter de la valeur à la variable de séparation de champ interne. Par exemple :
$ IFS=blah
$ echo "$IFS"
blah
$
Je comprends également que read -r line
enregistrera les données de stdin
à la variable nommée line
:
$ read -r line <<< blah
$ echo "$line"
blah
$
Cependant, comment une commande peut-elle affecter une valeur variable ? Et stocke-t-il d'abord les données de stdin
à la variable line
puis donner la valeur de line
à IFS
?
Réponse acceptée :
Dans les shells POSIX, read
, sans aucune option ne lit pas une ligne , il lit mots à partir d'une ligne (éventuellement suivie d'une barre oblique inverse), où les mots sont $IFS
délimité et barre oblique inverse peuvent être utilisés pour échapper les délimiteurs (ou continuer les lignes).
La syntaxe générique est :
read word1 word2... remaining_words
read
lit stdin un octet à la fois¹ jusqu'à ce qu'il trouve un caractère de retour à la ligne sans échappement (ou de fin d'entrée), le divise selon des règles complexes et stocke le résultat de cette division dans $word1
, $word2
… $remaining_words
.
Par exemple sur une entrée comme :
<tab> foo bar baz blah blah
whatever whatever
et avec la valeur par défaut de $IFS
, read a b c
attribuerait :
$a
⇐foo
$b
⇐bar baz
$c
⇐blah blahwhatever whatever
Maintenant, si un seul argument est passé, cela ne devient pas read line
. C'est toujours read remaining_words
. Le traitement de la barre oblique inverse est toujours effectué, les caractères d'espacement IFS² sont toujours supprimés du début et de la fin.
Le -r
L'option supprime le traitement de la barre oblique inverse. Donc, cette même commande ci-dessus avec -r
attribuerait plutôt
$a
⇐foo
$b
⇐bar
$c
⇐baz blah blah
Maintenant, pour la partie fractionnement, il est important de réaliser qu'il existe deux classes de caractères pour $IFS
:les caractères d'espacement IFS² (y compris l'espace et la tabulation (et la nouvelle ligne, bien qu'ici cela n'a pas d'importance à moins que vous n'utilisiez -d), qui se trouvent également dans la valeur par défaut de $IFS
) et les autres. Le traitement de ces deux classes de caractères est différent.
Avec IFS=:
(:
n'étant pas un caractère d'espace blanc IFS), une entrée comme :foo::bar::
serait divisé en ""
, "foo"
, ""
, bar
et ""
(et un ""
supplémentaire avec certaines implémentations, cela n'a pas d'importance sauf pour read -a
). Alors que si nous remplaçons ce :
avec un espace, le découpage se fait uniquement en foo
et bar
. C'est-à-dire que ceux de début et de fin sont ignorés, et leurs séquences sont traitées comme une seule. Il existe des règles supplémentaires lorsque des espaces blancs et des caractères non blancs sont combinés dans $IFS
. Certaines implémentations peuvent ajouter/supprimer le traitement spécial en doublant les caractères dans IFS (IFS=::
ou IFS=' '
).
Donc ici, si nous ne voulons pas que les caractères d'espacement non échappés de début et de fin soient supprimés, nous devons supprimer ces caractères d'espacement IFS d'IFS.
Même avec des caractères IFS non blancs, si la ligne d'entrée contient un (et un seul) de ces caractères et qu'il s'agit du dernier caractère de la ligne (comme IFS=: read -r word
sur une entrée comme foo:
) avec des shells POSIX (pas zsh
ni certains pdksh
versions), cette entrée est considérée comme un foo
mot car dans ces shells, les caractères $IFS
sont considérés comme des terminateurs , donc word
contiendra foo
, pas foo:
.
Ainsi, la manière canonique de lire une ligne d'entrée avec le read
intégré est :
IFS= read -r line
(notez que pour la plupart des read
implémentations, qui ne fonctionnent que pour les lignes de texte car le caractère NUL n'est pas pris en charge sauf dans zsh
).
Utilisation de var=value cmd
la syntaxe s'assure que IFS
n'est défini différemment que pour la durée de cette cmd
commande.
Note historique
Le read
builtin a été introduit par le shell Bourne et devait déjà lire des mots , pas de lignes. Il existe quelques différences importantes avec les shells POSIX modernes.
Le read
du shell Bourne n'a pas pris en charge un -r
option (qui a été introduite par le shell Korn), il n'y a donc aucun moyen de désactiver le traitement de la barre oblique inverse autre que le prétraitement de l'entrée avec quelque chose comme sed 's/\/&&/g'
là.
Le shell Bourne n'avait pas cette notion de deux classes de caractères (qui a de nouveau été introduite par ksh). Dans le shell Bourne, tous les caractères subissent le même traitement que les caractères d'espacement IFS dans ksh, c'est-à-dire IFS=: read a b c
sur une entrée comme foo::bar
attribuerait bar
à $b
, pas la chaîne vide.
Dans le shell Bourne, avec :
var=value cmd
Si cmd
est un élément intégré (comme read
est), var
reste défini sur value
après cmd
avoir fini. C'est particulièrement critique avec $IFS
car dans le shell Bourne, $IFS
est utilisé pour tout diviser, pas seulement les expansions. Aussi, si vous supprimez le caractère espace de $IFS
dans le shell Bourne, "[email protected]"
ne fonctionne plus.
Dans le shell Bourne, la redirection d'une commande composée la fait s'exécuter dans un sous-shell (dans les premières versions, même des choses comme read var < file
ou exec 3< file; read var <&3
n'a pas fonctionné), il était donc rare dans le shell Bourne d'utiliser read
pour tout sauf l'entrée de l'utilisateur sur le terminal (où cette gestion de la continuation de ligne avait du sens)
Certains Unix (comme HP/UX, il y en a aussi un dans util-linux
) ont toujours une line
commande pour lire une ligne d'entrée (qui était une commande UNIX standard jusqu'à la version 2 de la spécification UNIX unique).
C'est fondamentalement la même chose que head -n 1
sauf qu'il lit un octet à la fois pour s'assurer qu'il ne lit pas plus d'une ligne. Sur ces systèmes, vous pouvez :
line=`line`
Bien sûr, cela signifie générer un nouveau processus, exécuter une commande et lire sa sortie via un tube, donc beaucoup moins efficace que la ligne IFS= read -r line
de ksh , mais toujours beaucoup plus intuitif.