Je comprends évidemment que l’on peut ajouter de la valeur à la variable de séparateur de champ interne. Par exemple:
$ IFS=blah
$ echo "$IFS"
blah
$
Je comprends aussi que read -r line
les données de la stdin
variable seront sauvegardées line
:
$ read -r line <<< blah
$ echo "$line"
blah
$
Cependant, comment une commande peut affecter une valeur de variable? Et stocke-t-il d’abord les données de stdin
variable à variable line
, puis attribue la valeur de line
à IFS
?
bash
shell-script
Martin
la source
la source
Réponses:
Certaines personnes ont cette notion erronée qui
read
est la commande de lire une ligne. Ce n'est pas.read
lit les mots d'une ligne (éventuellement d'une barre oblique inversée), où les mots sont$IFS
délimités et une barre oblique inverse peut être utilisée pour échapper aux délimiteurs (ou aux lignes continues).La syntaxe générique est la suivante:
read
lit stdin un octet à la fois jusqu'à ce qu'il trouve un caractère de saut de ligne sans échappement (ou de fin d'entrée), qui se divise selon des règles complexes et stocke le résultat de cette division en$word1
,$word2
...$remaining_words
.Par exemple sur une entrée comme:
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 on ne passe qu'un seul argument, cela ne devient pas
read line
. C'est encoreread remaining_words
. Le traitement de la barre oblique inverse est toujours effectué, les caractères IFS sont toujours supprimés au début et à la fin.L'
-r
option supprime le traitement des barres obliques inverses. Donc, cette même commande ci-dessus avec-r
au lieu d'attribuer$a
⇐foo
$b
⇐bar\
$c
⇐baz bl\ah blah\
Maintenant, pour la partie scission, il est important de comprendre qu’il existe deux classes de caractères
$IFS
: les caractères IFS blancs (à savoir, espace et tabulation (et newline, bien que cela ne soit pas important sauf si vous utilisez -d), ce qui se produit également. être 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=:
(:
étant pas un caractère IFS des espaces), une entrée comme:foo::bar::
serait divisé en""
,"foo"
,""
,bar
et""
(et un supplément""
avec certaines implémentations si cela ne importe pas , sauf pourread -a
). Tandis que si nous remplaçons cela:
par de l’espace, la division se fait uniquement enfoo
etbar
. C’est-à-dire que les principales et les dernières sont ignorées et que leurs séquences sont traitées comme une seule. Il existe des règles supplémentaires lorsque des espaces et des caractères non-blancs sont combinés$IFS
. Certaines implémentations peuvent ajouter / supprimer le traitement spécial en doublant les caractères dans IFS (IFS=::
ouIFS=' '
).Donc, ici, si nous ne voulons pas que les caractères d’espace blanc qui restent et qui ne soient pas échappés soient supprimés, nous devons supprimer ces caractères d’espace blanc IFS de IFS.
Même avec les caractères IFS-non-blancs, si la ligne d’entrée contient un (et un seul) de ces caractères et que c’est le dernier caractère de la ligne (comme
IFS=: read -r word
sur une entrée similairefoo:
) avec des shells POSIX (zsh
ni certainespdksh
versions), cette entrée est considéré comme l' unfoo
mot parce que dans ces coquilles, les caractères$IFS
sont considérés comme terminateurs , doncword
contiendrafoo
, nonfoo:
.Ainsi, la manière canonique de lire une ligne d’entrée avec l’
read
intégré est:(notez que pour la plupart des
read
implémentations, cela ne fonctionne que pour les lignes de texte car le caractère NUL n'est pas pris en charge, sauf danszsh
).L'utilisation de la
var=value cmd
syntaxe permet de s'assurerIFS
que le paramétrage est différent pour la durée de cettecmd
commande.Note d'histoire
Le
read
construit a été introduit par le shell Bourne et devait déjà lire des mots , pas des lignes. Il existe quelques différences importantes avec les coques POSIX modernes.Le shell Bourne
read
ne prenant pas en charge une-r
option (introduite par le shell Korn), il n’ya donc aucun moyen de désactiver le traitement des barres obliques inverses autrement que de prétraiter l’entrée avec quelque chose de similairesed 's/\\/&&/g'
.Le shell Bourne n'avait pas cette notion de deux classes de caractères (qui a de nouveau été introduite par ksh). Dans le Bourne shell tous les personnages subissent le même traitement que IFS Espaces blancs font ksh, qui est
IFS=: read a b c
sur une entrée commefoo::bar
attribueraitbar
à$b
, pas la chaîne vide.Dans le shell Bourne, avec:
Si
cmd
est intégré (comme l'read
est),var
reste définivalue
après lacmd
fin. C’est particulièrement critique$IFS
car dans le shell Bourne, tout$IFS
est utilisé, pas seulement les extensions. De même, si vous supprimez le caractère d'espacement$IFS
dans le shell Bourne, cela"$@"
ne fonctionnera plus.Dans le shell Bourne, la redirection d’une commande composée l’exécute dans un sous-shell (dans les versions les plus anciennes, même si cela ne fonctionnait pas
read var < file
ouexec 3< file; read var <&3
ne fonctionnait pas). Il était donc rare que le shell Bourne soit utiliséread
pour autre chose que la saisie de l’utilisateur sur le terminal. (où le traitement de continuation de ligne était logique)Certains Unices (comme HP / UX, il y en a aussi un
util-linux
) ont toujours uneline
commande pour lire une ligne d’entrée (une commande UNIX standard jusqu’à la spécification Single UNIX version 2 ).C'est fondamentalement la même chose,
head -n 1
sauf qu'elle lit un octet à la fois pour s'assurer qu'elle ne lit pas plus d'une ligne. Sur ces systèmes, vous pouvez faire:Bien sûr, cela signifie créer un nouveau processus, exécuter une commande et lire sa sortie via un tube, ce qui est beaucoup moins efficace que celui de ksh
IFS= read -r line
, mais toujours beaucoup plus intuitif.la source
sh
différences régulières est également utile pour écrire des scripts portables!)bash-4.4.19
,while read -r; do echo "'$REPLY'"; done
fonctionne commewhile IFS= read -r line; do echo "'$line'"; done
.read
pour lire une ligne est erroné, il doit y avoir autre chose. Quelle pourrait être cette notion non erronée? Ou bien cette première affirmation est-elle techniquement correcte, mais en réalité, la notion non erronée est: "read est la commande permettant de lire les mots d'une ligne. En raison de sa puissance, vous pouvez l'utiliser pour lire les lignes d'un fichier en effectuant:IFS= read -r line
"La théorie
Deux concepts sont en jeu ici:
IFS
est le séparateur de champ de saisie, ce qui signifie que la chaîne lue sera fractionnée en fonction des caractères contenus dansIFS
. Sur une ligne de commande, ilIFS
y a normalement des caractères d'espacement, c'est pourquoi la ligne de commande se divise en espaces.VAR=value command
signifie "modifier l'environnement de commande pour qu'ilVAR
ait la valeurvalue
". Fondamentalement, la commandecommand
verraVAR
comme ayant la valeurvalue
, mais toute commande exécutée après sera toujours considéréeVAR
comme ayant sa valeur précédente. En d'autres termes, cette variable ne sera modifiée que pour cette instruction.Dans ce cas
Ainsi, lorsque vous le faites
IFS= read -r line
, vous définissezIFS
une chaîne vide (aucun caractère ne sera utilisé pour la scission, donc aucune scission ne se produira), de sorte queread
la ligne entière soit lue et affichée comme un mot attribué à laline
variable. Les modificationsIFS
ne concernent que cette instruction, de sorte que les commandes suivantes ne seront pas affectées par la modification.Comme note de côté
Bien que la commande est correcte et fonctionnera comme prévu, la mise
IFS
dans ce casn'est paspeut - 1 ne pas être nécessaire. Comme indiqué dans labash
page deread
manuel de la section Builtin:Puisque vous avez seulement la
line
variable tous les mots seront affectés à toute façon, donc si vous n'avez pas besoin des précédentes et de suivi des caractères blancs 1 vous pouvez simplement écrireread -r line
et faire avec elle.[1] À titre d'exemple de la manière dont une
unset
valeur ou une$IFS
valeur par défaut permettraread
de considérer les espaces IFS en tête / en fin , vous pouvez essayer:Exécutez-le et vous verrez que les caractères précédents et suivants ne survivront pas si ce
IFS
n'est pas défini. De plus, des choses étranges pourraient se produire si$IFS
on devait modifier quelque part plus tôt dans le script.la source
Vous devriez lire cette déclaration en deux parties, la première efface la valeur de la variable IFS, ce qui équivaut à plus lisible
IFS=""
, le second est en train de lire laline
variable d'stdin,read -r line
.Ce qui est spécifique dans cette syntaxe, c’est que l’affectation IFS est transcendante et n’est valable que pour la
read
commande.À moins que quelque chose ne me manque, dans ce cas particulier, l'effacementIFS
n'a aucun effet, même si ce quiIFS
est défini est configuré pour lire toute la ligne dans laline
variable. Un changement de comportement n'aurait eu lieu que dans le cas où plus d'une variable avait été passée en paramètre à l'read
instruction.Modifier:
Il
-r
existe un moyen de permettre à l’entrée se terminant par\
ne pas être traitée spécialement, c’est-à-dire que la barre oblique inversée soit incluse dans laline
variable et non comme un caractère de continuation pour permettre la saisie sur plusieurs lignes.L'effacement de l'IFS a pour effet secondaire d'empêcher lecture de supprimer les caractères d'espacement ou de tabulation, comme par exemple:
Merci à rici pour avoir souligné cette différence.
la source
read -r line
les espaces seront précédés et suivis avant d’affecter l’entrée à laline
variable.IFS= read a b <<< 'aa bb' ; echo "-$a-$b-"
montrera-aa bb--