¿Cómo generar una cadena multilínea en Bash?

250

¿Cómo puedo generar una cadena de varias líneas en Bash sin usar múltiples llamadas de eco de esta manera?

echo "usage: up [--level <n>| -n <levels>][--help][--version]"
echo 
echo "Report bugs to: "
echo "up home page: "

Estoy buscando una forma portátil de hacer esto, usando solo los componentes integrados de Bash.

método de ayuda
fuente
44
Si está enviando un mensaje de uso en respuesta a una invocación incorrecta, normalmente enviaría ese mensaje a error estándar en lugar de salida estándar, conecho >&2 ...
Mark Reed
2
@ MarkReed El mensaje de uso se emite escribiendo --help(que debería ir a la salida estándar).
método de ayuda
Para otros que vienen, hay más información disponible sobre "documentos aquí": tldp.org/LDP/abs/html/here-docs.html
Jeffrey Martinez
Verifique la printfsolución basada en Gordon Davidson. A pesar de estar a la sombra de los enfoques echoo catbasados, parece ser mucho menos problemático. Es cierto que la sintaxis 'printf' representa una pequeña curva de aprendizaje, pero me gustaría conocer otros inconvenientes (? Compatibilidad, rendimiento? ...)
mjv
Relacionado: stackoverflow.com/questions/23929235/…
Anton Tarasenko

Respuestas:

296

Aquí los documentos se utilizan a menudo para este propósito.

cat << EOF
usage: up [--level <n>| -n <levels>][--help][--version]

Report bugs to: 
up home page:
EOF

Se admiten en todos los shells derivados de Bourne, incluidas todas las versiones de Bash.

Pausado hasta nuevo aviso.
fuente
44
Sí, pero catno está integrado.
Mark Reed
8
@ MarkReed: Eso es cierto, pero siempre está disponible (excepto posiblemente en circunstancias inusuales).
Pausado hasta nuevo aviso.
66
+1 Thx. Terminé usando read -d '' help <<- EOF ...para leer la cadena multilínea en una variable y luego hice eco del resultado.
helpermethod
3
¿Puedo guardar un HEREDOC en una variable?
chovy
177

o puedes hacer esto:

echo "usage: up [--level <n>| -n <levels>][--help][--version]

Report bugs to: 
up home page: "
Chris Mohr
fuente
1
@OliverWeiler: incluso funcionará en shells Bourne como Dash y Heirloom Bourne Shell .
Pausado hasta nuevo aviso.
66
No es genial si necesita esto en una función porque necesitará 1) aplicar sangría a la cadena completamente a la izquierda de su archivo o 2) mantener la sangría para alinearse con el resto de su código, pero luego se imprime con los guiones también
sg
43

Inspirado por las respuestas perspicaces de esta página, creé un enfoque mixto, que considero el más simple y flexible. ¿Qué piensas?

Primero, defino el uso en una variable, lo que me permite reutilizarlo en diferentes contextos. El formato es muy simple, casi WYSIWYG, sin la necesidad de agregar ningún carácter de control. Esto me parece razonablemente portátil (lo ejecuté en MacOS y Ubuntu)

__usage="
Usage: $(basename $0) [OPTIONS]

Options:
  -l, --level <n>              Something something something level
  -n, --nnnnn <levels>         Something something something n
  -h, --help                   Something something something help
  -v, --version                Something something something version
"

Entonces simplemente puedo usarlo como

echo "$__usage"

o incluso mejor, cuando analizo los parámetros, puedo repetirlo allí en una sola línea:

levelN=${2:?"--level: n is required!""${__usage}"}
Jorge
fuente
44
Esto funcionó para mí en un script donde la respuesta anterior no lo hace (sin modificación).
David Welch
3
Esto es mucho más limpio que el que implica un montón de personajes como \ t \ n que son difíciles de encontrar en el texto y ampliar para que la salida muy diferente de la cadena en el guión
SG
1
Por alguna razón, imprime todo en la misma línea para mí: /
Nicolas de Fontenay
2
@Nicolas: Usar comillas dobles echo "$__usage"fue necesario para mí. echo $__usageno funcionó.
Mario
24

Use la -eopción, luego puede imprimir un nuevo carácter de línea \nen la cadena.

Muestra (pero no estoy seguro si es buena o no)

Lo divertido es que esa -eopción no está documentada en la página de manual de MacOS mientras todavía se puede usar. Está documentado en la página del manual de Linux .

revs nhahtdh
fuente
66
Esas páginas de manual son para el echocomando proporcionado por el sistema /bin/echo, que en Mac OS no tiene -eopción. cuando usas bash en esos sistemas, su echocomando incorporado se hace cargo. Puede ver esto escribiendo explícitamente /bin/echo whatevery observando la diferencia de comportamiento. Para ver la documentación para el incorporado, escriba help echo.
Mark Reed
1
/bin/echoA menudo es diferente de un sistema operativo a otro y diferente de Bash's incorporado echo.
Pausado hasta nuevo aviso.
@ MarkReed: lo intentaré más tarde, pero gracias por la información. +1. Dejaré mi respuesta aquí, ya que hay muchas buenas discusiones en curso.
nhahtdh
77
echo -eno es portátil; por ejemplo, algunas implementaciones de echo imprimirán "-e" como parte de la salida. Si desea portabilidad, use printf en su lugar. Por ejemplo, / bin / echo en OS X 10.7.4 hace esto. IIRC, el eco integrado de bash también fue extraño en 10.5.0, pero ya no recuerdo los detalles.
Gordon Davisson
2
echo -eme ha mordido antes ... Definitivamente uso printfo catcon un heredoc. La <<-variante de los documentos aquí es especialmente buena porque puede eliminar la sangría principal en la salida, pero sangrar para
facilitar
22

Como recomendé printfen un comentario, probablemente debería dar algunos ejemplos de su uso (aunque para imprimir un mensaje de uso, es más probable que use las respuestas de Dennis o Chris). printfes un poco más complejo de usar que echo. Su primer argumento es una cadena de formato, en la que los escapes (como \n) siempre se interpretan; también puede contener directivas de formato que comienzan con %, que controlan dónde y cómo se incluyen argumentos adicionales. Aquí hay dos enfoques diferentes para usarlo para un mensaje de uso:

Primero, puede incluir el mensaje completo en la cadena de formato:

printf "usage: up [--level <n>| -n <levels>][--help][--version]\n\nReport bugs to: \nup home page: \n"

Tenga en cuenta que echo, a diferencia , debe incluir la nueva línea final explícitamente. Además, si el mensaje contiene %caracteres, deberían escribirse como %%. Si desea incluir el informe de errores y las direcciones de la página de inicio, se pueden agregar de forma natural:

printf "usage: up [--level <n>| -n <levels>][--help][--version]\n\nReport bugs to: %s\nup home page: %s\n" "$bugreport" "$homepage"

En segundo lugar, puede usar la cadena de formato para que imprima cada argumento adicional en una línea separada:

printf "%s\n" "usage: up [--level <n>| -n <levels>][--help][--version]" "" "Report bugs to: " "up home page: "

Con esta opción, agregar el informe de errores y las direcciones de la página de inicio es bastante obvio:

printf "%s\n" "usage: up [--level <n>| -n <levels>][--help][--version]" "" "Report bugs to: $bugreport" "up home page: $homepage"
Gordon Davisson
fuente
9

También con el código fuente sangrado puede usar <<-(con un guión final) para ignorar las pestañas iniciales (pero no los espacios iniciales). Por ejemplo esto:

if [ some test ]; then
    cat <<- xx
        line1
        line2
xx
fi

Produce texto sangrado sin el espacio en blanco inicial:

line1
line2
Vista elíptica
fuente
Eso no me funcionó. ¿Qué caparazón estás usando?
four43
No funcionó en bash 4.4.19 en Ubuntu. No eliminó el espacio antes de la línea 1 y la línea 2
cuatro43
1
@ four43, tenías razón. No funciona para eliminar espacios iniciales. Sin embargo, elimina las pestañas iniciales. Así que corregí mi respuesta de pestañas y espacios, a pestañas y no a espacios. Lo siento por el error. Revisé el manual y claramente dice que las pestañas se han eliminado. Gracias por llamar mi atención sobre esto.
Vista elíptica
0

Si usa la solución de @jorge y encuentra que todo está en la misma línea, asegúrese de incluir la variable entre comillas:

echo $__usage

imprimirá todo en una línea mientras que

echo "$__usage"

retendrá las nuevas líneas.

usuario1165471
fuente
Esta es realmente la única solución que funcionó para mí. Printf hace muchas cosas y con mi xml multilínea, probablemente requiera mucho escape porque destruye completamente el contenido. cat <<EOF .... EOF
Asigno