Cadena de varias líneas con espacio adicional (sangría conservada)

410

Quiero escribir algunos textos predefinidos en un archivo con lo siguiente:

text="this is line one\n
this is line two\n
this is line three"

echo -e $text > filename

Estoy esperando algo como esto:

this is line one
this is line two
this is line three

Pero tengo esto:

this is line one
 this is line two
 this is line three

Estoy seguro de que no hay espacio después de cada uno \n, pero ¿cómo sale el espacio extra?

cizixs
fuente
2
No estoy seguro, pero ... ¿y si acabas de escribir text="this is line one\nthis is line two\nthis is line three"en la misma línea ...? (sin entrar)
Yohanes Khosiawan 许先汉
44
Elimine el \nen cada línea, ya ha llegado a la nueva línea para pasar a la nueva línea
Mark Setchell
Ya has dado \n. Entonces, ¿por qué pones la siguiente línea en una nueva línea? Simplementetext="this is line one\nthis is line two\nthis is line three"
Jayesh Bhoi
1
Al eliminar \nal final de cada línea, la salida se ejecuta en una sola línea.
Jonathan Hartley
18
Ajá: poner comillas dobles alrededor "$text"de la línea de eco es crucial. Sin ellos, ninguna de las líneas nuevas (tanto literales como '\ n') funciona. Con ellos, todos lo hacen.
Jonathan Hartley

Respuestas:

663

Heredoc suena más conveniente para este propósito. Se utiliza para enviar múltiples comandos a un programa de intérprete de comandos como ex o cat

cat << EndOfMessage
This is line 1.
This is line 2.
Line 3.
EndOfMessage

La cadena después <<indica dónde detenerse.

Para enviar estas líneas a un archivo, use:

cat > $FILE <<- EOM
Line 1.
Line 2.
EOM

También puede almacenar estas líneas en una variable:

read -r -d '' VAR << EOM
This is line 1.
This is line 2.
Line 3.
EOM

Esto almacena las líneas a la variable nombrada VAR.

Al imprimir, recuerde las comillas alrededor de la variable; de ​​lo contrario, no verá los caracteres de nueva línea.

echo "$VAR"

Aún mejor, puede usar la sangría para que se destaque más en su código. Esta vez solo agrega un -after <<para detener la aparición de las pestañas.

read -r -d '' VAR <<- EOM
    This is line 1.
    This is line 2.
    Line 3.
EOM

Pero luego debe usar pestañas, no espacios, para la sangría en su código.

niño nuevo
fuente
38
¿por qué tienes << - en lugar de << en la primera línea del segundo y quinto bloque de código? ¿hace algo especial?
lohfu 01 de
35
@ sup3rman '-' Ignora las pestañas principales. Ver stackoverflow.com/questions/2500436/…
kervin
8
¿Cómo puedo hacer que la lectura salga con el estado 0? Parece que no puede encontrar el final de la línea (ya que es -d '') y sale con 1, lo que no ayuda cuando está en el script.
Misha Slyusarev
77
@MishaSlyusarev usa -d '\ 0' y agrega un \ 0 al final de tu última línea
Lars Schneider
11
Usar la -dopción en read -r -d '' VARIABLE <<- EOMno funcionó para mí.
dron22
186

Si está tratando de convertir la cadena en una variable, otra forma fácil es algo como esto:

USAGE=$(cat <<-END
    This is line one.
    This is line two.
    This is line three.
END
)

Si sangra su cadena con pestañas (es decir, '\ t'), la sangría se eliminará. Si sangra con espacios, la sangría se dejará adentro.

NOTA: es significativo que el último paréntesis de cierre es en otra línea. El ENDtexto debe aparecer en una línea por sí mismo.

Andrew Miner
fuente
1
¿Es eso significativo? Funciona en mi mac con )en la misma línea. Creo que es porque lo que se interpone $(y )se ejecutará en su propio universo, y el comando real no verá ')' de todos modos. Me pregunto si también funciona para otros.
deej
Es posible que diferentes shells interpreten las cosas de manera diferente. En la documentación de bash no parece decir nada de una manera u otra, pero todos los ejemplos lo tienen en su propia línea.
Andrew Miner
Lo divertido es que también encontré tu respuesta al intentar establecer el valor para la variable USAGE. Entonces su respuesta coincide exactamente. :)
Bunyk
1
@deej De la especificación POSIX : El documento aquí ... continúa hasta que haya una línea que contenga solo el delimitador y un <newline>
Fornost
1
@Fornost No contradice lo que dije
deej
84

echoagrega espacios entre los argumentos que se le pasan. $textestá sujeto a expansión variable y división de palabras, por lo que su echocomando es equivalente a:

echo -e "this" "is" "line" "one\n" "this" "is" "line" "two\n"  ...

Puede ver que se agregará un espacio antes de "esto". Puede eliminar los caracteres de nueva línea y citar $textpara preservar las nuevas líneas:

text="this is line one
this is line two
this is line three"

echo "$text" > filename

O podría usar printf, que es más robusto y portátil que echo:

printf "%s\n" "this is line one" "this is line two" "this is line three" > filename

En bash, que admite la expansión de llaves, incluso podría hacer:

printf "%s\n" "this is line "{one,two,three} > filename
Josh Jolly
fuente
1
Gracias por explicar por qué el mod ve espacio extra cuando se usa el comando "echo". Esta respuesta también funciona bien cuando se usa en script bash (en oposición a la sesión de shell interactiva). Siente que esta es la respuesta real y debería ser una respuesta aceptada.
onelaview
1
Esta debe ser la respuesta aceptada. Todas las otras respuestas proporcionaron toneladas de otras formas interesantes de resolver el dolor de OP, pero técnicamente la pregunta era "¿cómo sale el espacio extra" y nadie abordó esto :-)! ¡Gracias Josh por hacer -1 misterio!
Dmitry Shevkoplyas
45

en un script bash, los siguientes trabajos:

#!/bin/sh

text="this is line one\nthis is line two\nthis is line three"
echo -e $text > filename

alternativamente:

text="this is line one
this is line two
this is line three"
echo "$text" > filename

nombre de archivo del gato da:

this is line one
this is line two
this is line three
Chris Maes
fuente
Parece que el truco alternativo funciona mientras Shell Scripts, pero no parece funcionar para bash.
Macindows
3
@Macindows Parece que he olvidado la -eopción para echo. Inténtalo de nuevo.
Chris Maes
41

He encontrado más soluciones ya que quería tener cada línea correctamente sangrada:

  1. Puedes usar echo:

    echo    "this is line one"   \
        "\n""this is line two"   \
        "\n""this is line three" \
        > filename

    No funciona si coloca "\n"justo antes \al final de una línea.

  2. Alternativamente, puede usar printfpara una mejor portabilidad (tuve muchos problemas con echo):

    printf '%s\n' \
        "this is line one"   \
        "this is line two"   \
        "this is line three" \
        > filename
  3. Otra solución más podría ser:

    text=''
    text="${text}this is line one\n"
    text="${text}this is line two\n"
    text="${text}this is line three\n"
    printf "%b" "$text" > filename

    o

    text=''
    text+="this is line one\n"
    text+="this is line two\n"
    text+="this is line three\n"
    printf "%b" "$text" > filename
  4. Otra solución se logra mezclando printfy sed.

    if something
    then
        printf '%s' '
        this is line one
        this is line two
        this is line three
        ' | sed '1d;$d;s/^    //g'
    fi

    No es fácil refactorizar el código con este formato mientras codifica el nivel de sangría en el código.

  5. Es posible utilizar una función auxiliar y algunos trucos de sustitución de variables:

    unset text
    _() { text="${text}${text+
    }${*}"; }
    # That's an empty line which demonstrates the reasoning behind 
    # the usage of "+" instead of ":+" in the variable substitution 
    # above.
    _ ""
    _ "this is line one"
    _ "this is line two"
    _ "this is line three"
    unset -f _
    printf '%s' "$text"
Mateusz Piotrowski
fuente
3b es mi solución preferida cuando quiero preservar la sangría de código y la sangría de cadena de forma independiente, al menos cuando no puedo usar 2 en la asignación de variables, y es más legible que 3a. Cuando no me importa, simplemente mantengo la cadena entre comillas abierta sobre varias líneas.
Pysis
Muy buena respuesta especialmente 3b
Sumit Trehan
"No funciona si pones" \ n "justo antes de \ al final de una línea". - Es un gran consejo cuando se utiliza echo.
Noam Manos
6

La siguiente es mi forma preferida de asignar una cadena de varias líneas a una variable (creo que se ve bien).

read -r -d '' my_variable << \
_______________________________________________________________________________

String1
String2
String3
...
StringN
_______________________________________________________________________________

El número de guiones bajos es el mismo (aquí 80) en ambos casos.

Frank-Rene Schäfer
fuente
0

Solo por mencionar una concatenación simple de una línea, ya que puede ser útil a veces.

# for bash

v=" guga "$'\n'"   puga "

# Just for an example.
v2="bar "$'\n'"   foo "$'\n'"$v"

# Let's simplify the previous version of $v2.
n=$'\n'
v3="bar ${n}   foo ${n}$v"

echo "$v3" 

Obtendrás algo como esto

bar 
   foo 
 guga 
   puga 

Todos los espacios en blanco iniciales y finales se conservarán correctamente para

echo "$v3" > filename
it3xl
fuente
0

Hay muchas formas de hacerlo. Para mí, conectar la cuerda sangrada en sed funciona muy bien.

printf_strip_indent() {
   printf "%s" "$1" | sed "s/^\s*//g" 
}

printf_strip_indent "this is line one
this is line two
this is line three" > "file.txt"

Esta respuesta se basó en la respuesta de Mateusz Piotrowski pero refinada un poco.

Beni Trainor
fuente
-1

funcionará si lo pones de la siguiente manera:

AA='first line
\nsecond line 
\nthird line'
echo $AA
output:
first line
second line
third line
luk
fuente