web-dev-qa-db-fra.com

Pourquoi printf est-il meilleur que l'écho?

J'ai entendu dire que printf est meilleur que echo. Je ne me souviens que d'une seule instance de mon expérience où j'ai dû utiliser printf parce que echo ne fonctionnait pas pour alimenter du texte dans un programme sur RHEL 5.8 mais printf l'a fait. Mais apparemment, il y a d'autres différences, et je voudrais savoir ce qu'elles sont ainsi que s'il y a des cas spécifiques quand utiliser l'un contre l'autre.

563
amphibient

Fondamentalement, c'est un problème de portabilité (et de fiabilité).

Initialement, echo n'acceptait aucune option et ne développait rien. Il ne faisait que produire ses arguments séparés par un caractère espace et terminés par un caractère de nouvelle ligne.

Maintenant, quelqu'un pensait que ce serait bien si nous pouvions faire des choses comme echo "\n\t" pour sortir des caractères de nouvelle ligne ou de tabulation, ou avoir une option pour ne pas sortir le caractère de nouvelle ligne de fin.

Ils ont ensuite réfléchi, mais au lieu d'ajouter cette fonctionnalité au shell (comme Perl où, entre guillemets, \t signifie en fait un caractère de tabulation), ils l'ont ajouté à echo.

David Korn a réalisé l'erreur et a introduit une nouvelle forme de citations Shell: $'...' qui a ensuite été copiée par bash et zsh mais il était beaucoup trop tard à ce moment-là.

Désormais, lorsqu'un UNIX standard echo reçoit un argument qui contient les deux caractères \ et t, au lieu de les sortir, il génère un caractère de tabulation. Et dès qu'il voit \c dans un argument, il arrête la sortie (donc la nouvelle ligne de fin n'est pas sortie non plus).

D'autres shells/fournisseurs/versions Unix ont choisi de le faire différemment: ils ont ajouté une option -e pour étendre les séquences d'échappement et une option -n pour ne pas sortir la nouvelle ligne de fin. Certains ont un -E pour désactiver les séquences d'échappement, certains ont -n mais pas -e, la liste des séquences d'échappement prises en charge par une implémentation de echo n'est pas nécessairement la même que celle prise en charge par une autre.

Sven Mascheck a un Belle page qui montre l'étendue du problème .

Sur ces implémentations echo qui prennent en charge les options, il n'y a généralement pas de prise en charge d'un -- pour marquer la fin des options (le echo intégré à certains shells non Bourne, et zsh prend en charge - pour cela), donc par exemple, c'est difficile pour afficher "-n" avec echo dans de nombreux shells.

Sur certains shells comme bash¹ ou ksh93² ou yash (variable $ECHO_STYLE), le comportement dépend même de la façon dont le shell a été compilé ou de l'environnement (le comportement de GNU echo changera également si $POSIXLY_CORRECT se trouve dans l'environnement et avec la version4, zsh avec son option bsd_echo, certains basés sur pdksh avec leur option posix ou qu'ils soient appelés ou non sh). Ainsi, deux bashecho, même à partir de la même version de bash ne sont pas garantis de se comporter de la même manière.

POSIX dit: si le premier argument est -n ou n'importe quel argument contient des barres obliques inverses, alors le comportement n'est pas spécifié . L'écho bash à cet égard n'est pas POSIX dans la mesure où, par exemple, echo -e ne génère pas -e<newline> comme POSIX l'exige. La spécification UNIX est plus stricte, elle interdit -n et nécessite l'extension de certaines séquences d'échappement, dont celle de \c pour arrêter la sortie.

Ces spécifications ne sont pas vraiment à la rescousse ici étant donné que de nombreuses implémentations ne sont pas conformes. Même certains systèmes certifiés comme macOS5 ne sont pas conformes.

Pour vraiment représenter la réalité actuelle, POSIX devrait en fait diresi le premier argument correspond à l'expression rationnelle étendue ^-([eEn]*|-help|-version)$ ou si un argument contient des barres obliques inverses (ou des caractères dont l'encodage contient l'encodage du caractère barre oblique inverse comme α dans les paramètres régionaux utilisant le jeu de caractères BIG5), le comportement n'est pas spécifié.

Dans l'ensemble, vous ne savez pas ce que echo "$var" produira, sauf si vous pouvez vous assurer que $var ne contient pas de barre oblique inverse et ne commence pas par -. La spécification POSIX nous dit en fait d'utiliser printf à la place dans ce cas.

Donc, cela signifie que vous ne pouvez pas utiliser echo pour afficher des données non contrôlées. En d'autres termes, si vous écrivez un script et qu'il prend des entrées externes (de l'utilisateur comme arguments ou des noms de fichiers du système de fichiers ...), vous ne pouvez pas utiliser echo pour l'afficher.

C'est acceptable:

echo >&2 Invalid file.

Ce n'est pas:

echo >&2 "Invalid file: $file"

(Bien que cela fonctionne correctement avec certaines implémentations echo (non conformes à UNIX) comme bash lorsque l'option xpg_echo n'a pas été activée d'une manière ou d'une autre comme lors de la compilation ou via l'environnement).

file=$(echo "$var" | tr ' ' _) n'est pas OK dans la plupart des implémentations (les exceptions étant yash avec ECHO_STYLE=raw (avec la mise en garde que les variables de yash ne peuvent pas contenir des séquences d'octets arbitraires, donc pas des noms de fichiers arbitraires)) et zsh's echo -E - "$var"6).

printf, en revanche, est plus fiable, du moins lorsqu'il se limite à l'utilisation de base de echo.

printf '%s\n' "$var"

Sortira le contenu de $var suivi d'un caractère de nouvelle ligne quel que soit le caractère qu'il peut contenir.

printf '%s' "$var"

Le sortira sans le caractère de fin de ligne.

Maintenant, il existe également des différences entre les implémentations de printf. Il y a un noyau de fonctionnalités qui est spécifié par POSIX, mais ensuite il y a beaucoup d'extensions. Par exemple, certains prennent en charge un %q pour citer les arguments mais la façon dont cela se fait varie de Shell à Shell, certains prennent en charge \uxxxx pour les caractères unicode. Le comportement varie pour printf '%10s\n' "$var" dans les paramètres régionaux multi-octets, il existe au moins trois résultats différents pour printf %b '\123'

Mais à la fin, si vous vous en tenez à l'ensemble de fonctionnalités POSIX de printf et n'essayez pas de faire quoi que ce soit de trop sophistiqué avec lui, vous n'avez aucun problème.

Mais rappelez-vous que le premier argument est le format, il ne doit donc pas contenir de données variables/non contrôlées.

Un echo plus fiable peut être implémenté à l'aide de printf, comme:

echo() ( # subshell for local scope for $IFS
  IFS=" " # needed for "$*"
  printf '%s\n' "$*"
)

echo_n() (
  IFS=" "
  printf %s "$*"
)

echo_e() (
  IFS=" "
  printf '%b\n' "$*"
)

Le sous-shell (qui implique la génération d'un processus supplémentaire dans la plupart des implémentations de Shell) peut être évité en utilisant local IFS avec de nombreux shells, ou en l'écrivant comme:

echo() {
  if [ "$#" -gt 0 ]; then
     printf %s "$1"
     shift
     if [ "$#" -gt 0 ]; then
       printf ' %s' "$@"
     fi
  fi
  printf '\n'
}

Remarques

1. comment le comportement de bash de echo peut être modifié.

Avec bash, au moment de l'exécution, il y a deux choses qui contrôlent le comportement de echo (à côté de enable -n echo ou redéfinissant echo en tant que fonction ou alias): l'option xpg_echobash et si bash est en mode posix. Le mode posix peut être activé si bash est appelé en tant que sh ou si POSIXLY_CORRECT est dans l'environnement ou avec l'option posix:

Comportement par défaut sur la plupart des systèmes:

$ bash -c 'echo -n "\0101"'
\0101% # the % here denotes the absence of newline character

xpg_echo étend les séquences comme UNIX requiert:

$ BASHOPTS=xpg_echo bash -c 'echo "\0101"'
A

Il honore toujours -n et -e (et -E):

$ BASHOPTS=xpg_echo bash -c 'echo -n "\0101"'
A%

Avec xpg_echo et le mode POSIX:

$ env BASHOPTS=xpg_echo POSIXLY_CORRECT=1 bash -c 'echo -n "\0101"'
-n A
$ env BASHOPTS=xpg_echo sh -c 'echo -n "\0101"' # (where sh is a symlink to bash)
-n A
$ env BASHOPTS=xpg_echo SHELLOPTS=posix bash -c 'echo -n "\0101"'
-n A

Cette fois, bash est à la fois conforme à POSIX et UNIX. Notez qu'en mode POSIX, bash n'est toujours pas conforme à POSIX car il ne produit pas -e dans:

$ env SHELLOPTS=posix bash -c 'echo -e'

$

Les valeurs par défaut de xpg_echo et posix peuvent être définies au moment de la compilation avec les options --enable-xpg-echo-default et --enable-strict-posix-default du script configure. C'est généralement ce que font les versions récentes d'OS/X pour construire leur /bin/shAucune implémentation/distribution Unix/Linux dans leur bon sens ne ferait généralement cela pour /bin/bash. En fait, ce n'est pas vrai, le /bin/bash fourni par Oracle avec Solaris 11 (dans un package facultatif) semble être construit avec --enable-xpg-echo-default (ce n'était pas le cas dans Solaris 10).

2. Comment le comportement echo de ksh93 peut être modifié.

Dans ksh93, que echo développe ou non les séquences d'échappement et reconnaisse les options dépend du contenu des variables d'environnement $PATH et/ou $_AST_FEATURES.

Si $PATH contient un composant qui contient /5bin ou /xpg avant le composant /bin ou /usr/bin alors il se comporte de la manière SysV/UNIX (développe les séquences, n'accepte pas les options). S'il trouve /ucb ou /bsd en premier ou si $_AST_FEATURES7 contient UNIVERSE = ucb, puis il se comporte comme BSD3 (-e pour activer l'expansion, reconnaît -n).

La valeur par défaut dépend du système, BSD sur Debian (voir la sortie de builtin getconf; getconf UNIVERSE dans les versions récentes de ksh93):

$ ksh93 -c 'echo -n' # default -> BSD (on Debian)
$ PATH=/foo/xpgbar:$PATH ksh93 -c 'echo -n' # /xpg before /bin or /usr/bin -> XPG
-n
$ PATH=/5binary:$PATH ksh93 -c 'echo -n' # /5bin before /bin or /usr/bin -> XPG
-n
$ PATH=/5binary:$PATH _AST_FEATURES='UNIVERSE = ucb' ksh93 -c 'echo -n' # -> BSD
$ PATH=/ucb:/foo/xpgbar:$PATH ksh93 -c 'echo -n' # /ucb first -> BSD
$ PATH=/bin:/foo/xpgbar:$PATH ksh93 -c 'echo -n' # /bin before /xpg -> default -> BSD

3. BSD pour écho -e?

La référence à BSD pour la gestion de l'option -e est un peu trompeuse ici. La plupart de ces comportements echo différents et incompatibles ont tous été introduits chez AT&T:

  • \n, \0ooo, \c dans Programmer's Work Bench UNIX (basé sur Unix V6), et le reste (\b, \r...) dans Unix System IIIRéf.
  • -n dans Unix V7 (par Dennis RitchieRéf)
  • -e dans Unix V8 (par Dennis RitchieRéf)
  • -E lui-même provient peut-être initialement de bash (CWRU/CWRU.chlog dans version 1.13.5 mentionne que Brian Fox l'ajoute le 1992-10-18, GNU echo le copie peu de temps après dans sh-utils-1.8 publié 10 jours plus tard)

Alors que le echo intégré au sh des BSD prend en charge -e depuis le jour où ils ont commencé à utiliser le shell Almquist pour cela au début des années 90, l'utilitaire autonome echo à ce jour ne le prend pas en charge ( FreeBSD echo = ne supporte toujours pas -e, bien qu'il supporte -n comme Unix V7 (et aussi \c mais seulement à la fin du dernier argument)).

La gestion de -e a été ajoutée à ksh93's echo dans l'univers BSD dans la version ksh93r publiée en 2006 et peut être désactivée au moment de la compilation.

4. GNU changement d'écho de comportement en 8.31

Depuis coreutils 8.31 (et cette validation ), GNU echo étend maintenant les séquences d'échappement par défaut lorsque POSIXLY_CORRECT est dans l'environnement, pour correspondre au comportement du echo intégré de bash -o posix -O xpg_echo (voir rapport de bogue ).

5. macOS echo

La plupart des versions de macOS ont reçu la certification UNIX de l'OpenGroup .

Leur sh intégré echo est conforme car il est bash (une très ancienne version) construit avec xpg_echo activé par défaut, mais leur utilitaire autonome echo ne l'est pas. env echo -n ne produit rien au lieu de -n<newline>, env echo '\n' génère \n<newline> au lieu de <newline><newline>.

Ce /bin/echo est celui de FreeBSD qui supprime la sortie de nouvelle ligne si le premier argument est -n ou (depuis 1995) si le dernier argument se termine par \c, mais ne prend en charge aucune autre séquence de barre oblique inverse requise par UNIX, pas même \\.

6. Implémentations de echo qui peuvent produire des données arbitraires textuellement

À strictement parler, vous pourriez également compter que FreeBSD/macOS /bin/echo ci-dessus (pas le echo intégré de leur Shell) où zsh's echo -E - "$var" ou yash's ECHO_STYLE=raw echo "$var" (printf '%s\n' "$var") pourrait être écrit:

/bin/echo "$var
\c"

Et le echo -nE - "$var" de zsh (printf %s "$var") pourrait être écrit

/bin/echo "$var\c"

Les implémentations qui prennent en charge -E et -n (ou peuvent être configurées pour) peuvent également:

echo -nE "$var
"

Pour l'équivalent de printf '%s\n' "$var".

7. _AST_FEATURES et le AST UNIVERSE

Le _AST_FEATURES n'est pas destiné à être manipulé directement, il est utilisé pour propager AST paramètres de configuration à travers l'exécution de la commande. La configuration doit être effectuée via l'API (non documentée) astgetconf(). À l'intérieur de ksh93, le programme intégré getconf (activé avec builtin getconf ou en appelant command /opt/ast/bin/getconf) est l'interface avec astgetconf()

Par exemple, vous feriez builtin getconf; getconf UNIVERSE = att pour changer le paramètre UNIVERSE en att (ce qui obligerait echo à se comporter de la manière SysV entre autres). Après cela, vous remarquerez que la variable d'environnement $_AST_FEATURES contient UNIVERSE = att.

785
Stéphane Chazelas

Vous souhaiterez peut-être utiliser printf pour ses options de formatage. echo est utile lorsqu'il s'agit d'imprimer la valeur d'une variable ou d'une ligne (simple), mais c'est tout ce qu'il y a à faire. printf peut essentiellement faire ce que sa version C peut faire.

Exemple d'utilisation et de capacités:

Echo:

echo "*** Backup Shell script ***"
echo
echo "Runtime: $(date) @ $(hostname)"
echo

printf:

vech="bike"
printf "%s\n" "$vech"

Sources:

28
NlightNFotis

Un "avantage", si vous voulez l'appeler ainsi, serait que vous n'avez pas à le dire comme echo pour interpréter certaines séquences d'échappement telles que \n. Il sait les interpréter et ne nécessitera pas de -e faire cela.

printf "some\nmulti-lined\ntext\n"

(NB: le dernier \n est nécessaire, echo l'implique, sauf si vous donnez le -n option)

versus

echo -e "some\nmulti-lined\ntext"

Notez le dernier \n dans printf. À la fin de la journée, c'est une question de goût et d'exigences ce que vous utilisez: echo ou - printf .

17
0xC0000022L

Un inconvénient de printf est la performance car le shell intégré echo est beaucoup plus rapide. Cela entre en jeu en particulier dans Cygwin où chaque instance d'une nouvelle commande entraîne une surcharge importante de Windows. Lorsque j'ai changé mon programme lourd en écho en utilisant /bin/echo à l'écho du Shell, les performances ont presque doublé. C'est un compromis entre portabilité et performance. Ce n'est pas un slam dunk de toujours utiliser printf.

5
John