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.
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 bash
echo
, 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 dire : si 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'
}
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_echo
bash
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/sh
. Aucune implémentation/distribution Unix/Linux dans leur bon sens ne ferait généralement cela pour . En fait, ce n'est pas vrai, le /bin/bash
/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).
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_FEATURES
7 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
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.
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 ).
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 \\
.
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"
.
_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
.
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:
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
.
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
.