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

547

J'ai entendu dire que printfc'est mieux que echo. Je ne me souviens que de mon expérience, où je devais utiliser, printfcar echocela ne fonctionnait pas pour insérer du texte dans un programme sur RHEL 5.8, mais ce fut le printfcas. Mais apparemment, il existe d’autres différences, et je voudrais demander en quoi elles consistent et s’il existe des cas spécifiques dans lesquels utiliser l’un ou l’autre.

amphibient
la source
Whab à propos echo -e?
neverMind9
1
@ neverMind9 echo -ene fonctionne pas dans sh, mais dans bash(pour une raison quelconque), et docker, par exemple, utilise shpar défaut pour ses RUNcommandes
Ciprian Date de publication:
@ CiprianTomoiagă echo -epour moi fonctionne dans Android Terminal, qui est essentiellement sh.
neverMind9

Réponses:

756

Fondamentalement, il s’agit d’un problème de portabilité (et de fiabilité).

Initialement, echon’a accepté aucune option et n’a rien développé. Tout ce qu'il faisait était de sortir ses arguments séparés par un espace et terminés par un caractère de nouvelle ligne.

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

Ils ont alors pensé plus fort, mais au lieu d’ajouter cette fonctionnalité au shell (comme à l’ perlintérieur de guillemets, cela \tsignifie en fait un caractère de tabulation), ils l’ont ajoutée echo.

David Korn a pris conscience de l’erreur et a introduit une nouvelle forme de guillemets: $'...'elle a ensuite été copiée par bashet zshmais c’était beaucoup trop tard à cette époque.

Désormais, lorsqu'un UNIX standard echoreçoit un argument contenant les deux caractères \et t, au lieu de les générer, il génère un caractère de tabulation. Et dès qu’il voit \cun argument, il arrête la sortie (la nouvelle ligne finale n’est donc pas sortie non plus).

D'autres shells / fournisseurs / versions d'Unix ont choisi de le faire différemment: ils ont ajouté une -eoption permettant de développer les séquences d'échappement et une -noption permettant de ne pas afficher le retour à la ligne final. Certains ont la -Epossibilité de désactiver les séquences d'échappement, d'autres -nnon mais -ela liste des séquences d'échappement prises en charge par une echoimplémentation n'est pas nécessairement la même que celle prise en charge par une autre.

Sven Mascheck a une belle page qui montre l'étendue du problème .

Sur ces echoimplémentations qui prennent en charge les options, il n'y a généralement pas de soutien d'un --pour marquer la fin des options (le echobuiltin de quelques obus non Bourne comme le font, et zsh soutient -pour que , bien), de sorte que , par exemple, il est difficile de sortie "-n"avec echoen beaucoup d'obus.

Sur certains shells comme bash¹ ou ksh93² ou yash( $ECHO_STYLEvariable), le comportement dépend même de la manière dont le shell a été compilé ou de l'environnement ( echole comportement de GNU changera également s'il $POSIXLY_CORRECTest dans l'environnement et avec la version 4 , zshavec son bsd_echooption, certains basés sur pdksh avec leur posixoption ou s’ils sont appelés comme shou non). Donc, deux bash echo, même de la même version de, bashne se comportent pas de la même manière.

POSIX dit: si le premier argument est -nou si l'un des arguments contient des barres obliques inverses, le comportement n'est pas spécifié . bashecho à cet égard n’est pas POSIX dans la mesure où, par exemple, echo -ene produit pas -e<newline>comme POSIX le requiert. La spécification UNIX est plus stricte, elle interdit -net nécessite le développement de certaines séquences d'échappement, y compris \ccelle destinée à arrêter la sortie.

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

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

Globalement, vous ne savez pas ce qui echo "$var"sera généré à moins que vous ne puissiez vous assurer qu'il $varne contient pas de caractère barre oblique inverse et ne commence pas par -. La spécification POSIX nous dit effectivement d’utiliser printfplutôt dans ce cas.

Cela signifie donc que vous ne pouvez pas utiliser echopour 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 sous forme d'arguments, ou des noms de fichiers du système de fichiers ...), vous ne pouvez pas utiliser echopour 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 echoimplémentations (non compatibles UNIX) telles que bashlorsque l' xpg_echooption n'a pas été activée d'une manière ou d'une autre, comme au moment de la compilation ou via l'environnement).

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

printf, d’autre part, est plus fiable, du moins quand il est limité à l’utilisation de base de echo.

printf '%s\n' "$var"

Affiche le contenu de $varsuivi d'un caractère de nouvelle ligne, quel que soit le caractère qu'il peut contenir.

printf '%s' "$var"

Le produira sans le caractère de fin de ligne suivant.

Maintenant, il y a aussi des différences entre les printfimplémentations. POSIX spécifie un noyau de fonctionnalités, mais il existe ensuite de nombreuses extensions. Par exemple, certains prennent en charge %qles arguments, mais la manière de procéder varie d’un shell à l’autre, certains \uxxxxprenant en charge les caractères unicode. Le comportement varie printf '%10s\n' "$var"dans les environnements locaux multi-octets, il existe au moins trois résultats différents pourprintf %b '\123'

Mais à la fin, si vous vous en tenez à l'ensemble des fonctionnalités POSIX printfet que vous n'essayez rien de trop fantaisiste, vous ne rencontrerez aucun problème.

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

Un plus fiable echopeut être mis en œuvre en utilisant 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 (ce qui implique de générer un processus supplémentaire dans la plupart des implémentations de shell) peut être évité en utilisant local IFSplusieurs shell, ou en l'écrivant comme ceci:

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

Remarques

1. comment bashle echocomportement peut être modifié.

Avec bash, au moment de l'exécution, deux éléments contrôlent le comportement de echo(à côté de la enable -n echoredéfinition en echotant que fonction ou alias): l' xpg_echo bashoption et le bashmode posix. posixLe mode peut être activé si bashest appelé en tant que shou si se POSIXLY_CORRECTtrouve dans l'environnement ou avec l' posixoption:

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 car UNIX requiert:

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

Il honore encore -net -e(et -E):

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

Avec xpg_echoet en 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, bashest conforme à la fois à POSIX et à UNIX. Notez qu'en mode POSIX, il bashn'est toujours pas conforme à POSIX car il ne sort pas -een:

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

$

Les valeurs par défaut pour xpg_echo et posix peuvent être définies lors de la compilation avec les options --enable-xpg-echo-defaultet --enable-strict-posix-defaultdu configurescript. C'est typiquement ce que les versions récentes d'OS / X font pour construire leurs /bin/sh. Pas de mise en œuvre / la distribution Unix / Linux dans leur esprit serait généralement faire pour /bin/bashbien . En fait, ce n'est pas vrai, le produit /bin/bashfourni par Oracle avec Solaris 11 (dans un package facultatif) semble être construit --enable-xpg-echo-default(ce qui n'était pas le cas dans Solaris 10).

2. Comment ksh93le echocomportement peut être modifié.

Dans ksh93, le fait d' echoétendre les séquences d'échappement ou non et de reconnaître les options dépend du contenu des variables d'environnement $PATHet / ou $_AST_FEATURES.

S'il $PATHcontient un composant qui contient /5binou /xpgavant le composant /binou /usr/bin, il se comporte de la manière SysV / UNIX (développe les séquences, n'accepte pas les options). S'il trouve /ucbou /bsdpremier ou si $_AST_FEATURES7 contient UNIVERSE = ucb, alors il se comporte de la manière BSD 3 ( -epour permettre l'expansion, reconnaît -n).

La valeur par défaut dépend du système, BSD sous Debian (voir le résultat builtin getconf; getconf UNIVERSEdans 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 echo -e?

La référence à BSD pour le traitement de l' -eoption est un peu trompeuse ici. La plupart de ces echocomportements différents et incompatibles ont tous été introduits chez AT & T:

  • \n, \0ooo, \cDans le banc de travail du programmeur UNIX (basé sur Unix V6), et le reste ( \b, \r...) dans Unix System III Réf .
  • -nsous Unix V7 (par Dennis Ritchie Ref )
  • -esous Unix V8 (par Dennis Ritchie Ref )
  • -Eelle-même provient peut-être initialement de bash(CWRU / CWRU.chlog dans la version 1.13.5 mentionne l’ajout de Brian Fox le 1992-10-18, GNU le echocopiant peu de temps après dans sh-utils-1.8 publié 10 jours plus tard)

Alors que le echobuiltin des shou BSDs ont soutenu -edepuis le jour où ils ont commencé à utiliser le shell Almquist pour elle au début des années 90, la version autonome echoutilitaire à ce jour ne supporte pas là ( FreeBSDecho ne supporte toujours pas -e, même si elle prend en charge -ncomme Unix V7 (et aussi \cmais seulement à la fin du dernier argument)).

La manipulation de -ea été ajouté à ksh93« s echoquand dans le BSD univers dans la version ksh93r sorti en 2006 et peut être désactivé au moment de la compilation.

4. GNU echo changement de comportement en 8.31

Depuis coreutils 8.31 (et cette validation ), GNU echoétend désormais les séquences d'échappement par défaut lorsque POSIXLY_CORRECT est dans l'environnement, afin de correspondre au comportement de bash -o posix -O xpg_echola commande echointégrée de s (voir le rapport de bogue ).

5. macOS echo

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

Leur version shintégrée echoest conforme car elle est bash(une très ancienne version) construite avec xpg_echoactivé par défaut, mais leur echoutilitaire autonome ne l’est pas. env echo -nne sort rien au lieu de -n<newline>, env echo '\n'sort \n<newline>au lieu de <newline><newline>.

C’est /bin/echocelui de FreeBSD qui supprime la sortie de nouvelle ligne si le premier argument est -nou (depuis 1995) si le dernier argument se termine par \c, mais ne supporte aucune autre séquence de barre oblique inverse requise par UNIX, même \\.

6. des echoimplémentations qui peuvent produire des données arbitraires telles quelles

À proprement parler, vous pourriez aussi compter que FreeBSD / macOS /bin/echoci-dessus (et non dans le shell de leur shell echo) où zshs echo -E - "$var"ou yash's ECHO_STYLE=raw echo "$var"( printf '%s\n' "$var") pourrait être écrit:

/bin/echo "$var
\c"

Les mises en œuvre qui prennent en charge -Eet -n(ou peuvent être configurées pour) peuvent également faire:

echo -nE "$var
"

Et zsh« s echo -nE - "$var"( printf %s "$var") peuvent être écrites

/bin/echo "$var\c"

7. _AST_FEATURESet l'ASTUNIVERSE

Il _AST_FEATURESn'est pas destiné à être manipulé directement, il est utilisé pour propager les paramètres de configuration AST lors de l'exécution de commandes. La configuration doit être effectuée via l' astgetconf()API (non documentée) . À l’intérieur ksh93, l’ getconfintégré (activé avec builtin getconfou par appel command /opt/ast/bin/getconf) est l’interface pourastgetconf()

Par exemple, vous feriez builtin getconf; getconf UNIVERSE = attchanger le UNIVERSEréglage en att(faisant echose comporter de manière SysV entre autres). Après cela, vous remarquerez que la $_AST_FEATURESvariable d’environnement contient UNIVERSE = att.

Stéphane Chazelas
la source
13
Beaucoup des premiers développements Unix se sont déroulés de manière isolée et de bons principes d'ingénierie logicielle, tels que "lorsque vous modifiez l'interface, changez le nom", n'ont pas été appliqués.
Henk Langeveld
7
En guise de remarque, le seul (et peut-être le seul) avantage à echodévelopper les \xséquences par opposition au shell dans le cadre de la syntaxe de citation est que vous pouvez alors générer un octet NUL chaînes, où la moitié des appels système (comme execve()) ne peuvent pas prendre de séquences d'octets arbitraires)
Stéphane Chazelas
Comme vous pouvez le constater sur la page Web de Sven Maschek, cela printfsemble même poser problème, car la plupart des implémentations le font mal. La seule implémentation correcte que je connaisse se trouve dans boshet la page de Sven Maschek ne répertorie pas les problèmes liés aux octets nuls via \0.
Schily
1
C'est une excellente réponse - merci @ StéphaneChazelas pour l'avoir écrit.
JoshuaRLi
28

Vous voudrez peut-être utiliser printfpour ses options de formatage. echoest utile pour imprimer la valeur d’une variable ou d’une ligne (simple), mais c’est tout. printfpeut fondamentalement faire ce que la version C de celui-ci 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:

NlightNFotis
la source
@ 0xC0000022L Je reste corrigé merci. Je n'ai pas remarqué que je me suis connecté au mauvais site lorsque je me suis précipité pour répondre à la question. Merci pour votre contribution et la correction.
NlightNFotis
7
Utiliser echopour imprimer une variable peut échouer si la valeur de la variable contient des méta-caractères.
Keith Thompson
17

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

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

(NB: le dernier \nest nécessaire, echoimplique, sauf si vous donnez l' -noption)

contre

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

Notez le dernier \ndans printf. A la fin de la journée , il est une question de goût et les exigences que vous utilisez: echoou printf.

0xC0000022L
la source
1
Vrai pour /usr/bin/echoet le bashintégré. Le paramètre dash, kshet zshintégré echon'a pas besoin de -ecommutateur pour développer des caractères d' échappement avec une barre oblique inversée.
manatwork
Pourquoi les citations de peur? Votre formulation implique que ce n'est pas nécessairement un avantage réel.
Keith Thompson
2
@ KeithThompson: en réalité, tout ce qu'ils veulent dire, c'est que tout le monde ne peut pas considérer cela comme un avantage.
0xC0000022L
Pourriez-vous développer ceci? Pourquoi ne serait-ce pas un avantage? L'expression "si vous voulez l'appeler comme cela" implique assez fortement que vous pensez que ce n'est pas le cas.
Keith Thompson
2
Ou vous pouvez simplement faire printf '%s\n' 'foo\bar.
Nyuszika7h
6

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

John
la source
15
printfest construit dans la plupart des shells de nos jours (bash, dash, ksh, zsh, yash, certains dérivés de pdksh ... inclut donc les shells que l'on trouve généralement sur cygwin). Les seules exceptions notables sont certains pdkshdérivés.
Stéphane Chazelas
Mais beaucoup de ces implémentations de printf sont cassées. Ceci est essentiel lorsque vous souhaitez utiliser printfpour générer des octets nuls, mais certaines implémentations interprètent "\ c" dans la chaîne de format, même si elles ne le devraient pas.
Schily
2
@schily, le comportement de printfn'est pas spécifié par POSIX si vous utilisez \cla chaîne de formatage, ainsi les printfimplémentations peuvent faire ce qu'elles veulent à cet égard. Par exemple, certains le traitent de la même manière que dans PWB echo(provoque la fermeture de printf), ksh l'utilise pour \cAles caractères de contrôle (pour l'argument de format printf et pour $'...'mais pas pour echoni print!). Je ne sais pas ce que cela a à voir avec l' impression d' octets NUL, ou peut - être vous référant à kshl » printf '\c@'?
Stéphane Chazelas