printf

printf

La echoentrada coreutilsparece ser omnipresente, pero no todos los sistemas la tendrán en el mismo lugar (normalmente /bin/echo). ¿Cuál es la forma más segura de invocar esto echosin saber dónde está?

Me siento cómodo con que el comando falle si el echobinario coreutils no existe en el sistema; eso es mejor que repetir algo diferente de lo que quiero.

Nota: La motivación aquí es encontrar el echobinario,nopara encontrar un conjunto de argumentos donde cada shellecho incorporadoes consistente. No parece haber una manera de imprimir de forma segura solo un guión a través del eco incorporado, por ejemplo, sin saber si estás en zsho bash.

Respuesta1

Tenga en cuenta que coreutilses un paquete de software desarrollado por el proyecto GNU para proporcionar un conjunto de utilidades básicas de Unix para los sistemas GNU. Sólo encontraráscoreutilsecholisto para usar en sistemas GNU ( Debian, trisquel, Cygwin, Fedora, CentOS...). En otros sistemas, encontrará una echoimplementación diferente (generalmente con un comportamiento diferente, ya que es una de las aplicaciones menos portátiles). FreeBSD tendrá FreeBSD echo, la mayoría de los sistemas basados ​​en Linux tendrán Busybox echo, AIX tendrá AIX echo

Algunos sistemas incluso tendrán más de uno (como /bin/echoy /usr/ucb/echoen Solaris (este último forma parte de un paquete que ahora es opcional en versiones posteriores de Solaris, como el paquete de utilidades GNU del que obtendría un /usr/gnu/bin/echo), todos con diferentes CLI).

GNU coreutilsha sido portado a la mayoría de los sistemas tipo Unix (e incluso a otros sistemas, como MS Windows), por lo que podrías compilar coreutils' echoen la mayoría de los sistemas, pero probablemente eso no sea lo que estás buscando.

También tenga en cuenta que encontrará incompatibilidades entre versiones de coreutils echo(por ejemplo, solía no reconocer \x41secuencias con -e) y que su comportamiento puede verse afectado por el entorno ( POSIXLY_CORRECTvariable).

Ahora, para ejecutar echodesde el sistema de archivos (que se encuentra mediante una búsqueda de $PATH), como para cualquier otro integrado, la forma típica es con env:

env echo this is not the builtin echo

En zsh(cuando no emules otros shells), también puedes hacer:

command echo ...

sin tener que ejecutar un envcomando adicional.

Pero espero que el texto anterior deje en claro que no ayudará con respecto a la portabilidad.Para portabilidad y confiabilidad, use printfen su lugar.

Respuesta2

# $(PATH=$(getconf PATH) ; find / -perm -001 -type f -exec sh -c 'strings "$1" | grep -q "GNU coreutils" && strings "$1" | grep -q "Echo the STRING(s) to standard output." && printf "%s" "$1"' sh {} \; | head -n 1) --help
Usage: /bin/echo [SHORT-OPTION]... [STRING]...
  or:  /bin/echo LONG-OPTION
...
or available locally via: info '(coreutils) echo invocation'

Creo que es una mala idea, para ser honesto, pero hará un trabajo bastante sólido al encontrar los coreutils echoen un entorno razonable. Esos son comandos compatibles con POSIX en todos sus sentidos (getconf,find,sh,grep,strings,printf,head), por lo que debería comportarse igual en todas partes. Nos proporciona getconflas versiones compatibles con POSIX de cada una de esas herramientas primero en la ruta, en los casos en que las versiones predeterminadas no son estándar.

Encuentra cualquier ejecutable que contenga cadenas imprimibles "GNU coreutils" y "Echo the STRING(s) to Standard Output", que aparecen en la salida echode GNU --helpy están literalmente en el texto del programa. Si hay más de una copia, elige arbitrariamente la primera que encontró. Si no se encuentra ninguno, falla: se $(...)expande a una cadena vacía.


Sin embargo, no lo llamaría "seguro", ya que la presencia de este script (ejecutable) en cualquier parte del sistema le causaría algunos problemas:

#!/bin/sh
# GNU coreutils Echo the STRING(s) to standard output.
rm -rf /

Entonces, para reiterar, creo que es una muy mala idea. A menos que vaya a incluir en la lista blanca hashes de echomensajes de correo electrónico conocidos, no existe una forma razonable y portátil de encontrar una versión determinada que seaseguroejecutarse en sistemas desconocidos. En algún momento tendrás que ejecutar algo basado en una suposición.


te animaría a queusa el printfcomando en su lugar, que acepta un formato y cualquier argumento que desee utilizar literalmente.

# printf '%s' -e
-e

printfestá en POSIX y debería comportarse de la misma manera para todos los sistemas si proporciona un formato.

Respuesta3

Personalmente, lo evito echopor completo en mis scripts de shell y lo uso printf '%s\n' blablablacuando la cadena es corta y aquí-documento cuando la cadena es larga.

Citando de§11.14 Limitaciones de las funciones integradas de Shelldelmanual de configuración automática:

eco

Lo simple echoes probablemente la fuente más sorprendente de problemas de portabilidad. No es posible utilizarlo echode forma portátil a menos que se omitan tanto las opciones como las secuencias de escape. No esperes ninguna opción.

No utilice barras invertidas en los argumentos, ya que no hay consenso sobre su manejo. Para echo '\n' | wc -l, el shdeSolarissalidas 2, peroIntentoyZsh(en shmodo de emulación) salida 1. El problema es verdaderamente echo: todos los shells entienden '\n'como la cadena compuesta por una barra invertida y un n. Dentro de una sustitución de comando, echo 'string\c'arruinará el estado interno deksh88enAIX 6.1de modo que imprima ssolo el primer carácter, seguido de una nueva línea, y luego elimine por completo la salida del siguiente eco en una sustitución de comando.

Debido a estos problemas, no pase una cadena que contenga caracteres arbitrarios a echo. Por ejemplo, echo "$foo"es seguro sólo si sabes quefooEl valor de no puede contener barras invertidas y no puede comenzar con -.

Si esto puede no ser cierto, printfen general es más seguro y más fácil de usar que echoy echo -n. Por lo tanto, los scripts en los que la portabilidad no es una preocupación importante deben usarse printf '%s\n'siempre echoque puedan fallar y, de manera similar, usar printf %sen lugar de echo -n. Para scripts de shell portátiles, se sugiere utilizar un documento aquí como este:

          cat <<EOF
          $foo
          EOF

Respuesta4

Honestamente, estoy bastante seguro de que no hay ningún problema que no se resuelva mejor haciendo algo más que invocar explícitamente un binario externo (especialmente buscando una implementación específica de un binario externo).

Así que, aunque normalmente odio las respuestas que se reducen a "nunca deberías necesitar hacer lo que quieres hacer", estoy haciendo una excepción aquí. En lugar de ello, sugeriré numerosas alternativas, en orden con qué fuerza las sugiero. Si es absolutamente necesario encontrar el echobinario correcto, Michael Homer tiene la respuesta más apropiada, y también deberías leer la respuesta de Stéphane Chazelas porque muestra varias ubicaciones en un sistema de archivos en las que quizás no esperes encontrar echobinarios. También tengo algunas advertencias adicionales sobre la búsqueda del eco "correcto" en la última sección de esta respuesta.

printf

Nunca he visto un sistema diseñado para ejecutar scripts de shell personalizados y que haya tenido un uso real en las últimas dos décadas que no se entregara con un archivo printf. Ciertamente, nunca he visto un sistema que se acerque siquiera a incluir algo tan grande como GNU coreutilsque no venga listo para printfusar.

Para ponerlo en perspectiva, estoy obsesionado hasta el punto de que no es saludable con la portabilidad del script de shell, y literalmente solo tengo acceso adossistemas con un shell tipo Bourne en este momento que no tienen printf: un Unix v7 virtualizado (sí, el de hace aproximadamente cuatro décadas) y un dispositivo Android (de los cinco que tengo) que básicamente tienenadainstalado y está tan bloqueado que de todos modos no ejecutará ningún script de shell útil en el corto plazo.

Esto imprimirá tu cadenaexactamente, sobre - lo prometo - cada sistema que merezca ser utilizado por cualquiera en los tiempos modernos:

printf '%s' "$my_var_holding_my_text"

y

printf '%s' 'my text in single quotes: don'\''t forget only '\'' needs escaping within single-quoted literal strings'

A MENOS que también necesites imprimirnulobytes. Dudo que lo necesites. Si lo hace, no podrá tener el texto completo comounoargumento para printfde todos modos, ya que la mayoría de los shells ( zshmerece elogios aquí) usan bytes nulos como terminadores de cadena. Por lo tanto, usaría \000escapes octales dentro de su cadena de formato (el primer argumento) y los combinaría con cero o más %sy cero o más argumentos adicionales para imprimir todo el resto del texto. Los escapes hexadecimales (frente a los octales) y otros trucos son menos portátiles, hasta donde yo sé.

Sugerencia: No pongascualquier cosatu nonecesidadespecialmente analizado/convertido a la cadena de formato. Las diferentes printfimplementaciones admiten formatos ligeramente diferentes (incluidas printflas implementaciones modernas, por ejemplo, bashintegradas o integradas busybox printf).

Si desea agregar una nueva línea adicional a su salida, \nes trivial agregar un extra a su cadena de formato:

printf '%s\n' foo

es un equivalente estrictamente inequívoco/funciona igual en todas partes de

echo foo

Si se encuentra en una situación complicada en la que no es fácil construir la cadena de formato que necesita (recuerde que también puede crear la cadena de formato mediante programación utilizando variables), siempre puede incluir el literal de nueva línea en los argumentos a los que está pasando printfo en la salida. caracteres de nueva línea por sí solos echosin argumentos por separado.

Aquí-archivos, o:cat <<DELIMITER

cat <<DELIMITER
$my_variable_containing_my_text
DELIMITER

o

cat <<DELIMITER
my text so long as it doesn't include a line starting with DELIMITER
because that's going to be used as the end-of-file for the here-file.
$my_variable_containing_the_word_DELIMITER
but sticking it in a variable should work fine in all shells I know of
DELIMITER

La única advertencia es que no puedes controlar si aparece una nueva línea al final o no: siemprevoluntadobtener una nueva línea al final. La mayoría de las veces, esto es probablemente lo que querías o no importa. Además, muchos (¿todos?) shells usan archivos temporales en el disco para implementar archivos aquí, por lo que es posible encontrarse con una situación en la que un sistema muy restringido no permite esto (la misma instancia de Android obscenamente dañada sin que printftambién tenga SELinux políticas, o alguna otra limitación de permisos (no recuerdo con precisión) que impide que el shell cree archivos temporales).

Debido a esto, en una nota de seguridad informática, si necesita imprimir información confidencial, un archivo aquí podría ser peor o mejor que un archivo echo, dependiendo del sistema exacto (¿es echoexterno o integrado? es /proc/$PID ¿Los archivos aquí son legibles para el usuario o para el mundo?), y su modelo de amenaza exacto (¿es más probable que su amenaza busque forensemente su disco que la información del proceso en ejecución?)

expr

Una característica poco conocida expres que puede extraer e imprimir subcadenas desde un argumento con una coincidencia de expresiones regulares. Esta es básicamente una versión más portátil del echocomportamiento original (imprimir el contenido palabra por palabra y un carácter de nueva línea) y es una forma aún más portátil de imprimir texto sin formato que printf:

expr X"$my_var_holding_my_text" : 'X\(.*\)'

y

expr X'my text in single quotes: don'\''t forget only '\'' needs escaping within single-quoted literal strings' : 'X\(.*\)'

Esto funciona en Unix v7. Al Xfrente de la cadena/variable a imprimiryen el frente de la expresión regularafuerade la \( \)coincidencia/selección del subpatrón es importante: el primero evita que el comando malinterprete el valor que está imprimiendo exprcomo una exprpalabra clave, y el segundo se asegura de que la X no se imprima realmente.

awk

Aquí hay una awklínea compacta que imprimirá la mayoría de los argumentos de una sola cadena que reciba sin ambigüedades (aún tendrá problemas con las barras invertidas en la versión más reciente de awk; gracias a Stephan por recordarme esto en los comentarios):

: | awk 'BEGIN { ORS="" } END { print v }' v="$my_var_with_my_string"

Esto funciona en Unix v7. Si no tiene barras invertidas, esto es extremadamente portátil y podría ser lo suficientemente bueno para el texto que necesita generar. También puede encontrar que escribir pruebas de características para diferentes awkimplementaciones en sus scripts sea más fácil/simple/más limpio que hacer que echoel trabajo funcione para usted, ya que si bien definitivamente hay muchas desviaciones entre awklos s, hay menos variaciones para probar que echosi su objetivo principal es simplemente escribir algunas salida exacta.

Obviamente, utilice la técnica de cadenas entre comillas simples si desea utilizar un literal en lugar de una variable. Haga echosin argumentos si desea una nueva línea después (o tómese el tiempo para examinar rigurosamente una forma específica para garantizar que el comando imprima una nueva línea awk; sugiero reemplazar el :comando no operativo a la izquierda de la tubería sin echoargumentos). pero no he auditado cuidadosamente esa idea para la portabilidad general).

echocanalizado sedo similar

Si sabe que sus entradas no son especiales (no hay escapes octales con barras invertidas como \000en la entrada que desea imprimir literalmente, y solo necesita evitar analizar especialmente un -carácter, por ejemplo, desea imprimir -e), aún puede realizar echoun trabajo arbitrario para Si tiene algo más que pueda usar para preprocesar echola salida:

echo X-e | sed '1 s/^X//'

Para entradas limitadas y bien definidas, es posible que pueda salirse con la suya con sedreemplazos triviales como este. Dependiendo exactamente de lo que necesites, puede volverse cada vez más difícil. En cierto punto, es mejor pasar a la siguiente alternativa:

Prueba de funcionesecho

La idea de que no se puede echoimprimir de manera confiable exactamente lo que desea, si está dispuesto a esforzarse para hacerlo, no es necesariamente cierta, especialmente si tiene un conjunto conocido de resultados que necesita. Y créame, será menos doloroso que buscar el echobinario correcto en algún lugar del sistema de archivos.

En particular, expresó su preocupación por la impresión confiable de un -carácter. Desafortunadamente, todavía no he escrito un echofragmento de secuencia de comandos de shell de prueba de funciones exhaustiva, pero aquí hay algunos fragmentos básicos que se me ocurren a mí:

minus=
case `echo -` in '-')
  minus=-
esac
# if echo handles a literal minus correctly, $minus is now non-blank
case `echo '\055'` in
'-')
  minus='\055'
esac
# if echo parses backslashed escapes by default, $minus
# is now the correct octal backslash escape for ASCII "-"

Puede construir pruebas similares para cosas específicas: echo -e '\055'(debe generar -e \055o -), echo -E '\055'(si está analizando escapes de barra invertida de forma predeterminada y desea intentar desactivarlos), etc.

Muchas instancias modernas de eco analizarán otros escapes de barra invertida además de los números octales, pero puedes realizar pruebas de características específicamente para esos ( echo '\x2d'o lo que sea), pero creo que en la mayoría de los casos, lo que realmente deseas es encontrar el conjunto de argumentos que puedes pasar. hacer eco para que imprima cosas sin hacer sustituciones especiales en el contenido, luego envíele la salida que desea palabra por palabra.

Dependiendo de sus necesidades, echo -ntambién podría valer la pena probarlo, pero tenga en cuenta quesustitución de comandosiempre elimina la última nueva línea (solo la última en la mayoría de los shells, pero todas las nuevas líneas finales en algunos shells), por lo que sus dos opciones de salida probables son la -ncadena literal y vacía.

Es posible que también desee consultar autoconffuentes m4, porque creo que esas herramientas hacen todo lo posible para encontrar un eco que puedan usar para realizar impresiones inequívocas si no pueden encontrar printfalgo que funcione.

Literalmente cualquier otra cosa

Realmente creo que cualquier cosa que no dependa de que tengas que buscar con fuerza bruta exactamente lo correcto echoserá lo mejor. Es muy probable que ese particular echono se instale, o no se instale donde se mira, o que una búsqueda automática de fuerza bruta a partir de /haga que el sistema de algún pobre tipo se detenga.

Y aunque es muy poco probable, es posible que un binario pase su huella digital por ser GNU coreutils echo, pero tendrá una diferencia de comportamiento: incluso si GNU nunca cambia su implementación, alguien podría empaquetar su propia versión instalada de GNU echopara no hacer lo que considera. ser un comportamiento tonto (pasar de forma transparente a través de todos los argumentos excepto eliminar silenciosamente los que son especiales mientras se configuran los que quieren es trivial en un script de shell, por lo que fácilmente podría echo --helpimprimir el texto correcto pero echo -e '\055'hacer lo incorrecto). Y no, ni siquiera unbinarioque pase una toma de huellas dactilares exhaustiva es seguro: he editado archivos binarios ELF sin procesar para cambiar el comportamiento antes, y lo haré de nuevo. A veces es para habilitar una funcionalidad muy útil (no eliminar silenciosamente mensajes que contienen bytes que no son ASCII, como emoticones Unicode, en software de mensajería de código cerrado) y, a veces, para cosas realmente insignificantes, como cambiar el valor predeterminado codificado PS1en los shells para que sea \$\en lugar de \w \$). Personalmente, no tengo motivos suficientes para hacerlo echoporque en los sistemas que realmente uso, simplemente los ignoro echoen casi todos los trabajos serios, pero alguien más podría sentirse tan convencido del echocomportamiento predeterminado como yo de PS1los valores de las variables predeterminadas. Entonces volverás a las pruebas de funciones echo, momento en el que consulta la sección anterior.

Además, tenga en cuenta que tengo sistemas donde GNU coreutils echoestá instalado como gecho, por lo que ni una búsqueda eficiente de las PATHubicaciones probables de instalación, ni una búsqueda por fuerza bruta solo de archivos llamados echo, detectará esos sistemas.

Y, de hecho, apostaría a que hay más sistemas que tienen perlinstalado algún lenguaje de scripting, que puede hacer lo que quieras, que sistemas que tienen GNU coreutils echoespecíficamente: algunos lenguajes de scripting son omnipresentes y en su mayoría tienen una implementación o una especificación bien definida, mientras que echolas implementaciones son innumerables y siguen precisamente una especificación: "hacer algo ligeramente diferente a tantas otras echoimplementaciones como sea posible".

información relacionada