¿Por qué es printf mejor que echo?


547

He escuchado que printfes mejor que echo. Solo puedo recordar una instancia de mi experiencia en la que tuve que usarla printfporque echono funcionó para alimentar algún texto en algún programa en RHEL 5.8, pero lo printfhizo. Pero al parecer, hay otras diferencias, y me gustaría preguntar cuáles son, así como si hay casos específicos de cuándo usar uno frente al otro.


¿Qué tal echo -e?
neverMind9

1
@ neverMind9 echo -eno funciona sh, solo en bash(por alguna razón), y Docker, por ejemplo, usa shde forma predeterminada para sus RUNcomandos
Ciprian Tomoiagă

@ CiprianTomoiagă echo -epara mí funciona en Android Terminal, que es esencialmente sh.
neverMind9

Respuestas:


756

Básicamente, es un problema de portabilidad (y confiabilidad).

Inicialmente, echono aceptó ninguna opción y no expandió nada. Todo lo que estaba haciendo era generar sus argumentos separados por un carácter de espacio y terminados por un carácter de nueva línea.

Ahora, alguien pensó que sería bueno si pudiéramos hacer cosas como echo "\n\t"generar caracteres de nueva línea o tabulación, o tener una opción para no generar el carácter de nueva línea final.

Luego pensaron más, pero en lugar de agregar esa funcionalidad al shell (como perldonde dentro de las comillas dobles, en \trealidad significa un carácter de tabulación), lo agregaron echo.

David Korn se dio cuenta del error e introdujo una nueva forma de citas de shell: $'...'que luego fue copiada bashy zshque ya era demasiado tarde.

Ahora, cuando UNIX estándar echorecibe un argumento que contiene los dos caracteres \y t, en lugar de generarlos, genera un carácter de tabulación. Y tan pronto como ve \cen un argumento, deja de emitirse (por lo que la nueva línea final tampoco se genera).

Otros shells / proveedores / versiones de Unix decidieron hacerlo de manera diferente: agregaron una -eopción para expandir las secuencias de escape y una -nopción para no generar la nueva línea final. Algunos tienen -Eque deshabilitar las secuencias de escape, otros tienen, -npero no -e, la lista de secuencias de escape compatibles con una echoimplementación no es necesariamente la misma que admite otra.

Sven Mascheck tiene una buena página que muestra el alcance del problema .

En esas echoimplementaciones que soportan opciones, por lo general hay ningún apoyo de un --para marcar el final de las opciones (la echoorden interna de algunas conchas no Bourne-como hacer, y zsh apoya -para que aunque), por lo que, por ejemplo, es difícil salida "-n"con echoen Muchas conchas.

En algunos shells como bash¹ o ksh93² o yash( $ECHO_STYLEvariable), el comportamiento incluso depende de cómo se compiló el shell o del entorno ( echoel comportamiento de GNU también cambiará si $POSIXLY_CORRECTestá en el entorno y con la versión 4 , zshcon su bsd_echoopción, algunos basados ​​en pdksh con su posixopción o si se les llama como sho no). Por lo tanto, dos bash echos, incluso de la misma versión de bash, no tienen el mismo comportamiento.

POSIX dice: si el primer argumento es -no cualquier argumento contiene barras invertidas, entonces el comportamiento no está especificado . bashecho en ese sentido no es POSIX porque, por ejemplo, echo -eno se emite -e<newline>como lo requiere POSIX. La especificación UNIX es más estricta, prohíbe -ny requiere la expansión de algunas secuencias de escape, incluida la \cque detiene la salida.

Esas especificaciones realmente no vienen al rescate aquí dado que muchas implementaciones no son compatibles. Incluso algunos sistemas certificados como macOS 5 no son compatibles.

Para representar realmente la realidad actual, POSIX debería decir : si el primer argumento coincide con la ^-([eEn]*|-help|-version)$expresión regular extendida o algún argumento contiene barras diagonales inversas (o caracteres cuya codificación contiene la codificación del carácter de barra diagonal inversa como αen los entornos locales que utilizan el juego de caracteres BIG5), entonces el comportamiento es sin especificar

En general, no sabe qué echo "$var"generará a menos que pueda asegurarse de que $varno contenga caracteres de barra invertida y no comience con -. La especificación POSIX realmente nos dice que usemos printfen su lugar en ese caso.

Entonces, lo que eso significa es que no se puede usar echopara mostrar datos no controlados. En otras palabras, si está escribiendo un script y está recibiendo información externa (del usuario como argumentos, o nombres de archivo del sistema de archivos ...), no puede usar echopara mostrarlo.

Esto esta bien:

echo >&2 Invalid file.

Esto no es:

echo >&2 "Invalid file: $file"

(Aunque funcionará bien con algunas echoimplementaciones (no compatibles con UNIX) como bash's cuando la xpg_echoopción no se ha habilitado de una forma u otra, como en el momento de la compilación o en el entorno).

file=$(echo "$var" | tr ' ' _)no está bien en la mayoría de las implementaciones (las excepciones son yashcon ECHO_STYLE=raw(con la advertencia de que yashlas variables no pueden contener secuencias arbitrarias de bytes, por lo que no son nombres de archivos arbitrarios) y zsh's echo -E - "$var"6 ).

printf, por otro lado, es más confiable, al menos cuando se limita al uso básico de echo.

printf '%s\n' "$var"

Producirá el contenido $varseguido de un carácter de nueva línea, independientemente de qué carácter pueda contener.

printf '%s' "$var"

Lo generará sin el carácter de nueva línea final.

Ahora, también hay diferencias entre las printfimplementaciones. POSIX especifica un núcleo de características, pero hay muchas extensiones. Por ejemplo, algunos admiten a %qpara citar los argumentos, pero la forma en que se realiza varía de un shell a otro, algunos admiten \uxxxxcaracteres Unicode. El comportamiento varía printf '%10s\n' "$var"en entornos locales de varios bytes, hay al menos tres resultados diferentes paraprintf %b '\123'

Pero al final, si te apegas al conjunto de características POSIX printfy no intentas hacer nada demasiado elegante con él, estás fuera de problemas.

Pero recuerde que el primer argumento es el formato, por lo que no debe contener datos variables / no controlados.

Se echopuede implementar una más confiable usando printf, como:

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

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

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

El subshell (que implica generar un proceso adicional en la mayoría de las implementaciones de shell) puede evitarse local IFScon muchos shells, o escribiéndolo así:

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

Notas

1. ¿Cómo bash's echocomportamiento puede ser alterado.

Con bash, en tiempo de ejecución, hay dos cosas que controlan el comportamiento de echo(al lado enable -n echoo redefinir echocomo una función o alias): la xpg_echo bashopción y si bashestá en modo posix. posixEl modo se puede habilitar si bashse llama como sho si POSIXLY_CORRECTestá en el entorno o con la posixopción:

Comportamiento predeterminado en la mayoría de los sistemas:

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

xpg_echo expande las secuencias como UNIX requiere:

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

Todavía honra -ny -e(y -E):

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

Con xpg_echoy modo 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

Esta vez, bashes compatible con POSIX y UNIX. Tenga en cuenta que en el modo POSIX, bashtodavía no es compatible con POSIX ya que no se genera -een:

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

$

Los valores predeterminados para xpg_echo y posix se pueden definir en el momento de la compilación con las opciones --enable-xpg-echo-defaulty --enable-strict-posix-defaultpara el configurescript. Eso suele ser lo que hacen las versiones recientes de OS / X para construir su /bin/sh. Sin /bin/bashembargo, ninguna implementación / distribución de Unix / Linux en su sano juicio lo haría normalmente . En realidad, eso no es cierto, /bin/bashparece que Oracle con Solaris 11 (en un paquete opcional) está construido --enable-xpg-echo-default(ese no fue el caso en Solaris 10).

2. ¿Cómo ksh93's echocomportamiento puede ser alterado.

En ksh93, si echose expande secuencias de escape o no y reconoce opciones depende del contenido de las $PATHy / o $_AST_FEATURESvariables de entorno.

Si $PATHcontiene un componente que contiene /5bino /xpgantes del componente /bino /usr/bin, entonces se comporta de la manera SysV / UNIX (expande secuencias, no acepta opciones). Si encuentra /ucbo /bsdprimero o si contiene $_AST_FEATURES7UNIVERSE = ucb , entonces se comporta de la manera BSD 3 ( -epara permitir la expansión, reconoce -n).

El valor predeterminado depende del sistema, BSD en Debian (consulte la salida de builtin getconf; getconf UNIVERSElas versiones recientes 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 para echo -e?

La referencia a BSD para el manejo de la -eopción es un poco engañosa aquí. La mayoría de esos echocomportamientos diferentes e incompatibles se introdujeron en AT&T:

  • \n, \0ooo, \cEn el banco de trabajo del programador de UNIX (basado en Unix V6), y el resto ( \b, \r...) en UNIX System III Ref .
  • -nen Unix V7 (por Dennis Ritchie Ref )
  • -een Unix V8 (por Dennis Ritchie Ref )
  • -Eposiblemente inicialmente provenía de bash(CWRU / CWRU.chlog en la versión 1.13.5 menciona que Brian Fox lo agregó en 1992-10-18, GNU lo echocopió poco después en sh-utils-1.8 lanzado 10 días después)

Mientras que la echoorden interna de los sho BSD han apoyado -edesde el día en que comenzaron a utilizar la cáscara Almquist para ello en los años 90, la versión autónoma de echoutilidad para el día de hoy no lo soporta allí ( FreeBSDecho todavía no admite -e, aunque lo hace de soporte -ncomo Unix V7 (y también \cpero solo al final del último argumento)).

El manejo de -ese agregó a ksh93's echocuando está en el universo BSD en la versión ksh93r lanzada en 2006 y puede deshabilitarse en el momento de la compilación.

4. GNU echo cambio de comportamiento en 8.31

Desde coreutils 8.31 (y este commit ), GNU echoahora expande las secuencias de escape por defecto cuando POSIXLY_CORRECT está en el entorno, para que coincida con el comportamiento de bash -o posix -O xpg_echo's echoincorporado (ver informe de error ).

5. macOS echo

La mayoría de las versiones de macOS han recibido la certificación UNIX de OpenGroup .

Su shconstrucción echoes compatible, ya que está bash(una versión muy antigua) construida con xpg_echohabilitada por defecto, pero su echoutilidad independiente no lo es. env echo -nno produce nada en lugar de -n<newline>, env echo '\n'produce en \n<newline>lugar de <newline><newline>.

Ese /bin/echoes el de FreeBSD que suprime la salida de nueva línea si el primer argumento es -no (desde 1995) si el último argumento termina en \c, pero no admite ninguna otra secuencia de barra invertida requerida por UNIX, ni siquiera \\.

6. echoimplementaciones que pueden generar datos arbitrarios textualmente

Hablando estrictamente, también podría contar que FreeBSD / macOS /bin/echoarriba (no el de su shell echo) donde zsh's echo -E - "$var"o yash' s ECHO_STYLE=raw echo "$var"( printf '%s\n' "$var") podrían escribirse:

/bin/echo "$var
\c"

Las implementaciones que admiten -Ey -n(o pueden configurarse para) también pueden hacer:

echo -nE "$var
"

Y zsh' echo -nE - "$var"( printf %s "$var"podría escribirse)

/bin/echo "$var\c"

7. _AST_FEATURESy la ASTUNIVERSE

No _AST_FEATURESestá destinado a ser manipulado directamente, se usa para propagar las configuraciones de configuración AST a través de la ejecución de comandos. La configuración debe hacerse a través de la astgetconf()API (no documentada) . En ksh93el interior , el getconfincorporado (habilitado con builtin getconfo invocando command /opt/ast/bin/getconf) es la interfaz paraastgetconf()

Por ejemplo, harías builtin getconf; getconf UNIVERSE = attpara cambiar la UNIVERSEconfiguración a att(haciendo echoque se comporte como SysV entre otras cosas). Después de hacer eso, notará que la $_AST_FEATURESvariable de entorno contiene UNIVERSE = att.


13
Muchos de los primeros desarrollos de Unix ocurrieron de forma aislada, y no se aplicaron buenos principios de ingeniería de software como 'cuando cambia la interfaz, cambie el nombre' .
Henk Langeveld

77
Como nota, la única (y quizás única) ventaja de echoexpandir las \xsecuencias en lugar del shell como parte de la sintaxis de las citas es que puede generar un byte NUL (otro diseño incorrectamente discutible de Unix son aquellos delimitados por nulo) cadenas, donde la mitad de las llamadas del sistema (como execve()) no pueden tomar secuencias arbitrarias de bytes)
Stéphane Chazelas

Como puede ver en la página web de Sven Maschek, incluso printfparece ser un problema ya que la mayoría de las implementaciones lo hacen mal. La única implementación correcta que conozco está en boshy la página de Sven Maschek no enumera los problemas con bytes nulos a través de \0.
schily

1
Esta es una gran respuesta: gracias @ StéphaneChazelas por escribirlo.
JoshuaRLi

28

Es posible que desee utilizar printfpara sus opciones de formato. echoes útil cuando se trata de imprimir el valor de una variable o una línea (simple), pero eso es todo. printfbásicamente puede hacer lo que la versión C puede hacer.

Ejemplo de uso y capacidades:

Echo:

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

printf:

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

Fuentes:


@ 0xC0000022L Estoy corregido gracias. No me di cuenta de que estaba vinculado al sitio equivocado en mi apuro por responder la pregunta. Gracias por tu contribución y la corrección.
NlightNFotis

77
El uso echopara imprimir una variable puede fallar si el valor de la variable contiene metacaracteres.
Keith Thompson

17

Una "ventaja", si quiere llamarlo así, sería que no tiene que decirle cómo echointerpretar ciertas secuencias de escape como \n. Sabe interpretarlos y no requerirá -eque lo haga.

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

(Nota: lo último \nes necesario, lo echoimplica, a menos que des la -nopción)

versus

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

Tenga \nen cuenta el último en printf. Al final del día, es cuestión de gustos y requisitos lo que usas: echoo printf.


1
Es cierto para /usr/bin/echoy el bashincorporado. El dash, kshy la zshorden interna echono necesita -ecambiar a ampliar caracteres escapados.
manatwork

¿Por qué las citas de miedo? Su redacción implica que no es necesariamente una ventaja real.
Keith Thompson

2
@KeithThompson: en realidad todo lo que se pretende dar a entender es que no todo el mundo puede considerar una ventaja.
0xC0000022L

¿Puede explicar más? ¿Por qué no sería una ventaja? La frase "si quieres llamarlo así" implica bastante fuertemente que crees que no lo es.
Keith Thompson

2
O simplemente podrías hacerlo printf '%s\n' 'foo\bar.
nyuszika7h

6

Una desventaja printfes el rendimiento porque el shell incorporado echoes mucho más rápido. Esto entra en juego particularmente en Cygwin, donde cada instancia de un nuevo comando causa una gran sobrecarga de Windows. Cuando cambié mi programa de eco pesado de usar /bin/echoal eco del shell, el rendimiento casi se duplicó. Es una compensación entre portabilidad y rendimiento. No es una volcada para usar siempre printf.


15
printfestá construido en la mayoría de los shells hoy en día (bash, dash, ksh, zsh, yash, algunos derivados de pdksh ... por lo que también incluye los shells que generalmente se encuentran en cygwin). Las únicas excepciones notables son algunos pdkshderivados.
Stéphane Chazelas

Pero muchas de esas implementaciones de printf están rotas. Esto es esencial cuando desea utilizar printfpara generar bytes nulos, pero algunas implementaciones interpretan `\ c 'en la cadena de formato aunque no deberían hacerlo.
schily

2
@schily, printfPOSIX no especifica el comportamiento de si lo usa \cen la cadena de formato, por lo que las printfimplementaciones pueden hacer lo que quieran a ese respecto. Por ejemplo, algunos lo tratan igual que en el PWB echo(hace que printf salga), ksh lo usa para \cAcaracteres de control (para el argumento de formato printf y para $'...'pero no para echoni print!). ¿No está seguro de qué tiene que ver eso con la impresión de bytes NUL, o tal vez se refiere a ksh's printf '\c@'?
Stéphane Chazelas
Al usar nuestro sitio, usted reconoce que ha leído y comprende nuestra Política de Cookies y Política de Privacidad.
Licensed under cc by-sa 3.0 with attribution required.