Ce n'est pas seulement écho vs printf
Tout d'abord, comprenons ce qui se passe avec la read a b c
pièce. read
effectuera la division des mots en fonction de la valeur par défaut de la IFS
variable qui est espace-tab-newline, et adaptera tout en fonction de cela. S'il y a plus d'entrée que les variables pour le contenir, il adaptera les pièces divisées aux premières variables, et ce qui ne peut pas être ajusté - ira en dernier. Voici ce que je veux dire:
bash-4.3$ read a b c <<< "one two three four"
bash-4.3$ echo $a
one
bash-4.3$ echo $b
two
bash-4.3$ echo $c
three four
C'est exactement comme cela qu'il est décrit dans bash
le manuel de (voir la citation à la fin de la réponse).
Dans votre cas, ce qui se passe est que 1 et 2 correspondent aux variables a et b et c prend tout le reste, ce qui est 3 4 5 6
.
Ce que vous verrez aussi souvent, c'est que les gens utilisent while IFS= read -r line; do ... ; done < input.txt
pour lire les fichiers texte ligne par ligne. Encore une fois, IFS=
est là pour une raison de contrôler le fractionnement de mots, ou plus précisément - de le désactiver et de lire une seule ligne de texte dans une variable. S'il n'était pas là, read
j'essaierais d'adapter chaque mot individuel en line
variable. Mais c'est une autre histoire, que je vous encourage à étudier plus tard, car while IFS= read -r variable
c'est une structure très fréquemment utilisée.
écho vs comportement printf
echo
fait ce que vous attendez ici. Il affiche vos variables exactement comme les read
a disposées. Cela a déjà été démontré dans la discussion précédente.
printf
est très spécial, car il continuera à ajuster les variables dans la chaîne de format jusqu'à ce qu'elles soient toutes épuisées. Ainsi, lorsque vous printf "%d, %d, %d \n" $a $b $c
imprimez, la chaîne de formatage comporte 3 décimales, mais il y a plus d'arguments que 3 (car vos variables se développent en fait jusqu'à 1,2,3,4,5,6). Cela peut sembler déroutant, mais existe pour une raison comme un comportement amélioré par rapport à ce que fait la fonction réelle printf()
en langage C.
Ce que vous avez également fait ici qui affecte la sortie, c'est que vos variables ne sont pas citées, ce qui permet au shell (pas printf
) de décomposer les variables en 6 éléments distincts. Comparez cela en citant:
bash-4.3$ read a b c <<< "1 2 3 4"
bash-4.3$ printf "%d %d %d\n" "$a" "$b" "$c"
bash: printf: 3 4: invalid number
1 2 3
Exactement parce que la $c
variable est citée, elle est maintenant reconnue comme une chaîne entière 3 4
, et elle ne correspond pas au %d
format, qui est juste un seul entier
Faites maintenant la même chose sans citer:
bash-4.3$ printf "%d %d %d\n" $a $b $c
1 2 3
4 0 0
printf
dit encore: "OK, vous avez 6 éléments mais le format affiche seulement 3, donc je vais continuer à ajuster les trucs et laisser vide tout ce que je ne peux pas faire correspondre à l'entrée réelle de l'utilisateur".
Et dans tous ces cas, vous n'avez pas à me croire sur parole. Il suffit de courir strace -e trace=execve
et de voir par vous-même ce que la commande "voit" réellement:
bash-4.3$ strace -e trace=execve printf "%d %d %d\n" $a $b $c
execve("/usr/bin/printf", ["printf", "%d %d %d\\n", "1", "2", "3", "4"], [/* 80 vars */]) = 0
1 2 3
4 0 0
+++ exited with 0 +++
bash-4.3$ strace -e trace=execve printf "%d %d %d\n" "$a" "$b" "$c"
execve("/usr/bin/printf", ["printf", "%d %d %d\\n", "1", "2", "3 4"], [/* 80 vars */]) = 0
1 2 printf: ‘3 4’: value not completely converted
3
+++ exited with 1 +++
Notes complémentaires
Comme Charles Duffy l'a correctement souligné dans les commentaires, bash
a sa propre fonction intégrée printf
, qui est ce que vous utilisez dans votre commande, strace
appellera en fait la /usr/bin/printf
version, pas la version du shell. Mis à part des différences mineures, pour notre intérêt pour cette question particulière, les spécificateurs de format standard sont les mêmes et le comportement est le même.
Ce qui doit également être gardé à l'esprit, c'est que la printf
syntaxe est beaucoup plus portable (et donc préférée) que echo
, sans compter que la syntaxe est plus familière au C ou à tout langage de type C qui a une printf()
fonction. Voir cette excellente réponse par terdon au sujet de printf
vs echo
. Bien que vous puissiez faire la sortie adaptée à votre shell spécifique sur votre version spécifique d'Ubuntu, si vous allez porter des scripts sur différents systèmes, vous devriez probablement préférerprintf
plutôt qu'écho. Peut-être êtes-vous un administrateur système débutant travaillant avec des machines Ubuntu et CentOS, ou peut-être même FreeBSD - qui sait - dans de tels cas, vous devrez faire des choix.
Citation du manuel bash, section SHELL BUILTIN COMMANDS
lire [-ers] [-a aname] [-d delim] [-i texte] [-n nchars] [-N nchars] [-p prompt] [-t timeout] [-u fd] [nom ... ]
Une ligne est lue à partir de l'entrée standard ou du descripteur de fichier fd fourni comme argument à l'option -u, et le premier mot est affecté au premier nom, le deuxième mot au deuxième nom, et ainsi de suite, avec le reste mots et leurs séparateurs intermédiaires affectés au nom de famille. S'il y a moins de mots lus dans le flux d'entrée que de noms, les noms restants reçoivent des valeurs vides. Les caractères dans IFS sont utilisés pour diviser la ligne en mots en utilisant les mêmes règles que le shell utilise pour l'expansion (décrites ci-dessus sous la division des mots).
strace
cas et l'autre -strace printf
utilise/usr/bin/printf
, tandis queprintf
directement dans bash utilise le shell intégré du même nom. Ils ne seront pas toujours identiques - par exemple, l'instance bash a des spécificateurs de format%q
et, dans les nouvelles versions,$()T
pour le formatage de l'heure.