¿Cómo asignar un valor de cadena a una variable en varias líneas mientras está sangrado?

54

La cuestión:

  1. Necesito asignar a una variable un valor que sea decentemente largo.
  2. Todas las líneas de mi script deben estar bajo un cierto número de columnas.

Entonces, estoy tratando de asignarlo usando más de una línea.

Es fácil de hacer sin sangría:

VAR="This displays without \
any issues."
echo "${VAR}"

Resultado:

This displays without any issues.

Sin embargo con sangrías:

    VAR="This displays with \
    extra spaces."
    echo "${VAR}"

Resultado:

This displays with      extra spaces.

¿Cómo puedo asignarlo elegantemente sin estos espacios?

Sman865
fuente

Respuestas:

31

Aquí el problema es que rodea la variable con comillas dobles (""). Quítalo y las cosas funcionarán bien.

    VAR="This displays with \
    extra spaces."
    echo ${VAR}

Salida

 This displays with extra spaces.

Aquí el problema es que la doble cita de una variable conserva todos los espacios en blanco. Esto se puede usar en caso de que lo necesite explícitamente.

Por ejemplo,

$ echo "Hello     World    ........ ...            ...."

imprimirá

Hello     World    ........ ...            ....

Y al eliminar comillas, es diferente

$ echo Hello     World    ........ ...            ....
Hello World ........ ... ....

Aquí el Bash elimina espacios adicionales en el texto porque en el primer caso todo el texto se toma como un argumento "único" y, por lo tanto, conserva espacios adicionales. Pero en el segundo caso, el echocomando recibe el texto como 5 argumentos.

Citar una variable también será útil al pasar argumentos a los comandos.

En el siguiente comando, echosolo obtiene un argumento único como"Hello World"

$ variable="Hello World"
$ echo "$variable"

Pero en el caso del siguiente escenario echoobtiene dos argumentos como HelloyWorld

$ variable="Hello World"
$ echo $variable
Kannan Mohan
fuente
La mayoría de las respuestas funcionaron bien, pero esta fue la más simple. ¡Gracias!
Sman865
12
@ Sman865: créame cuando le digo que esta es casi definitivamente la respuesta más complicada que se ofrece aquí, y también es incorrecta en la mayoría de los aspectos, especialmente en su declaración de apertura. Cualquier problema relacionado con la asignación de valor no puede estar relacionado de ninguna manera con su posterior expansión, eso es solo al revés. Lo siento Kannan, pero esta respuesta es incorrecta y equivocada. La expansión de división en campo $IFSes una herramienta poderosa y universal, pero el código escrito de esta manera nunca puede producir resultados confiables de ningún tipo.
mikeserv
2
No usar comillas al expandir una variable causa problemas tarde o temprano, comenzando con la expansión del nombre de archivo (cualquiera de * ? []).
Sr.Spuratic
Estoy de acuerdo en que las variables deben encerrarse entre comillas dobles para evitar que se expanda bash. Pero para casos especiales podemos evitarlo para evitar complicaciones del código.
Kannan Mohan
1
Estoy de acuerdo con @mikeserv, este es un consejo muy malo si se toma al pie de la letra. Esto se romperá si se usa sin las consecuencias. Por ejemplo, si la variable se pasa como argumento a un comando, no desea que cada palabra se divida como un argumento separado.
haridsv
28

Las soluciones dadas por esuoxu y Mickaël Bucas son las formas más comunes y portátiles de hacerlo.

Aquí hay algunas bashsoluciones (algunas de las cuales también deberían funcionar en otros shells, como zsh). En primer lugar, con el +=operador append (que funciona de forma ligeramente diferente para cada una de las variables enteras, una variable regular y una matriz).

text="Lorem ipsum dolor sit amet, consectetur adipisicing elit, sed do eiusmod "
text+="tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, "
text+="quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea ..." 

Si desea nuevas líneas (u otros espacios en blanco / escapes) en el texto, utilice $''comillas en su lugar:

text=$'Lorem ipsum dolor sit amet, consectetur adipisicing elit, sed do eiusmod\n'
text+=$'...'

A continuación, use printf -vpara asignar un valor formateado a una variable

printf -v text "%s" "Lorem ipsum dolor sit amet, consectetur adipisicing elit, sed " \
                    "do eiusmod empor incididunt ut labore et dolore magna aliqua. "\
                    "Ut enim ad minim veniam ..."

El truco aquí es que hay más argumentos que especificadores de formato, por lo que, a diferencia de la mayoría de las printffunciones, bash reutiliza la cadena de formato hasta que se agota. Puede poner un \ndentro de la cadena de formato, o usar $ '', (o ambos) para tratar con espacios en blanco.

Luego, usando una matriz:

text=("Lorem ipsum dolor sit amet, consectetur adipisicing elit, sed do eiusmod "
      "tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, "
      "quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea ..." )

También puede usar +=para construir el texto línea por línea (tenga en cuenta lo siguiente ()):

text+=("post script")

Aquí, sin embargo, debe recordar "aplanar" la matriz si desea todo el contenido del texto de una vez

echo "$text"      # only outputs index [0], the first line
echo "${text[*]}" # output complete text (joined by first character of IFS)

(las matrices indexadas enteras están ordenadas implícitamente, a diferencia de las matrices asociativas) Esto le brinda un poco más de flexibilidad, ya que puede manipular líneas e incluso cortar y cortar si es necesario.

Finalmente, usando reado readarrayy un "documento aquí":

read -r -d '' text <<-"EOT"
        Lorem ipsum dolor sit amet, consectetur adipisicing elit, sed do eiusmod
        tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, 
        quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea ...
EOT

readarray -t textarray <<-"EOT"
        Lorem [...]
EOT  

La forma de documento aquí <<-significa que todas las pestañas duras principales se eliminan de la entrada, por lo que debe usar pestañas para sangrar el texto. Las comillas alrededor "EOT"evitan las funciones de expansión de shell, por lo que la entrada se usa textualmente. Con readél utiliza la entrada delimitada por bytes NUL, de modo que leerá el texto delimitado por nueva línea de una vez. Con readarray(también conocido como mapfile, disponible desde bash-4.0), se lee en una matriz y -telimina nuevas líneas en cada línea.

Sr. púrpura
fuente
1
¡La readopción con here documentes muy bonita! Útil para incrustar scripts de python y ejecutar con python -c. Solía ​​hacerlo script=$(cat <here document>)antes, pero read -r -d '' script <here document>es mucho mejor.
haridsv
9

Hay una sintaxis especial heredoc que elimina las pestañas al principio de todas las líneas: "<< -" (observe el guión agregado)

http://tldp.org/LDP/abs/html/here-docs.html

Ejemplo 19-4. Mensaje multilínea, con pestañas suprimidas

Puedes usarlo así:

v="$(cat <<-EOF
    A
        B
    C
EOF
)"
echo "$v"

Resultado:

A
B
C

Funciona solo con pestañas, no espacios.

Mickaël Bucas
fuente
Aquí en ubuntu, lo contrario es cierto: los espacios funcionan, no las pestañas. \tfunciona muy bien si necesitas pestañas. Solo use en echo -e "$v"lugar de echo "$v"habilitar los caracteres de barra invertida
will-ob
5

Deje que el caparazón coma los avances de línea no deseados y los siguientes espacios:

$ cat weird.sh 
#!/bin/sh

        var1="A weird(?) $(
             )multi line $(
             )text idea. $(
             )PID=$$"

        var2='You can '$(
            )'avoid expansion '$(
            )'too: PID=$$'

        var3='Or mix it: '$(
            )'To insert the PID use $$. '$(
            )"It expands to e.g. $$."

        echo "$var1"
        echo "$var2"
        echo "$var3"
$ sh weird.sh 
A weird(?) multi line text idea. PID=13960
You can avoid expansion too: PID=$$
Or mix it: To insert the PID use $$. It expands to e.g. 13960.

Entonces es posible ... pero seguro que es cuestión de gustos gustar o no esta solución ...

yeti
fuente
3
Cada uno de estos es un tenedor.
mikeserv
5

Quizás puedas probar esto.

          echo "Test" \
               "Test2" \
               "Test3"
esuoxu
fuente
5

Así te sugiero que lo hagas, y te explicaré por qué, pero primero quiero hablar de otra cosa ...

set -- 'Arg 1: Line 1.' \
       'Arg 2: Line 2.' \
       'and so on for'  \
       'as long as you might like.'
var="$*"

Muchas de las otras soluciones ofrecidas aquí parecen sugerir que de alguna manera puede afectar el contenido de una variable de shell al alterar sus métodos de expansión. Les puedo asegurar que este no es el caso.

    string="some stuff here \
            some more stuff here."
    echo $string ${#string} 
    echo "$string" "${#string}"

SALIDA

some stuff here some more stuff here. 53
some stuff here                 some more stuff here. 53

Lo que ve arriba es primero una expansión dividida en campo, luego un informe sobre el conteo de bytes para la variable fuente de la expansión, luego una expansión delimitada por comillas y el mismo conteo de bytes. Si bien la salida puede diferir, el contenido de la variable de shell $stringnunca cambia en absoluto, excepto en la asignación.

Lo que es más, si no comprende por qué esto es así, seguramente encontrará sorpresas muy desagradables más temprano que tarde. Intentemos eso de nuevo, pero en condiciones ligeramente diferentes.

    IFS=sf
    echo $string ${#string} 
    echo "$string" "${#string}"

Mismo $string- ambiente diferente.

SALIDA

 ome  tu   here                  ome more  tu   here. 53
some stuff here                 some more stuff here. 53

La división de campos se produce en función de los delimitadores de campo definidos en $IFS. Hay dos tipos de delimitadores: $IFSespacios en blanco y $IFScualquier otra cosa. Por defecto $IFSse le asigna la nueva línea de la pestaña de espacio de valores , que son los tres posibles $IFSvalores de espacio en blanco. Sin embargo, se puede cambiar fácilmente, como puede ver arriba, y puede tener efectos drásticos en las expansiones divididas en campos.

$IFSlos espacios en blanco se eluirán por secuencia a un solo campo, y es por eso que echouna expansión que contenga cualquier secuencia de espacios cuando $IFScontenga un espacio se evaluará a un solo espacio, porque echoconcatena sus argumentos en los espacios. Pero cualquier valor que no sea un espacio en blanco no se eliminará de la misma manera, y cada delimitador que aparece siempre obtiene un campo en sí mismo, como se puede ver en la expansión de cosas anterior.

Esto no es lo peor de todo. Considera este otro $string.

IFS=$space$tab$newline
cd emptydir
    string=" * * * \
             * * * "
    echo $string ${#string}
    echo "$string" "${#string}"    

SALIDA

* * * * * * 30
 * * *                  * * *  30

Se ve bien, ¿verdad? Bueno, alteremos el medio ambiente nuevamente.

    touch file1 file2 file3 file4 file5
    echo $string ${#string}
    echo "$string" "${#string}"    

SALIDA

file1 file2 file3 file4 file5 file1 file2 file3 file4 file5 file1 file2 file3 file4 file5 file1 file2 file3 file4 file5 file1 file2 file3 file4 file5 file1 file2 file3 file4 file5 30
 * * *                  * * *  30

Woah

Por defecto, el shell expandirá los globos de nombre de archivo si puede coincidir con ellos. Esto ocurre después de la expansión de parámetros y la división de campos en su orden de análisis y, por lo tanto, cualquier cadena sin comillas es vulnerable de esta manera. Puede desactivar este comportamiento set -fsi lo desea, pero cualquier shell compatible con POSIX siempre se bloqueará de forma predeterminada.

Este es el tipo de cosas con las que te enfrentas cuando sueltas citas en expansiones para satisfacer tus preferencias de sangría. Y aun así, en todos los casos, independientemente de su comportamiento de expansión, el valor real de $stringsiempre es el que fue la última vez que lo asignó. Así que volvamos a lo primero.

set -- 'Arg 1: Line 1.' \
       'Arg 2: Line 2.' \
       'and so on for'  \
       'as long as you might like.'
var="$*"
echo "$var" "${#var}"

SALIDA

Arg 1: Line 1. Arg 2: Line 2. and so on for as long as you might like. 70

Creo que esta es una forma mucho más sensata de adaptar la sintaxis de shell a sus preferencias de sangría. Lo que estoy haciendo arriba es asignar cada cadena individual a un parámetro posicional, al que se puede hacer referencia por número como $1o ${33}, y luego asignar sus valores concatenados para $varusar el parámetro de shell especial $*.

Este enfoque no es inmune $IFS, aun así. Aún así, considero su relación con $IFSun beneficio adicional a este respecto. Considerar:

IFS=\ ;space_split="$*"
IFS=/; slash_split="$*";IFS='
';new_line_split="$*"

echo "$space_split"
echo "$slash_split"
echo "$new_line_split"

SALIDA

Arg 1: Line 1. Arg 2: Line 2. and so on for as long as you might like.
Arg 1: Line 1./Arg 2: Line 2./and so on for/as long as you might like.
Arg 1: Line 1.
Arg 2: Line 2.
and so on for
as long as you might like.

Como puede ver, $*concatena cada arg en "$@"el primer byte en $IFS. Por lo tanto, guardar su valor mientras $IFSestá asignado de manera diferente obtiene delimitadores de campo diferentes para cada valor guardado. Lo que ves arriba es el valor literal de cada variable, por cierto. Si no quisieras delimitador, harías lo siguiente:

IFS=;delimitless="$*"
echo "$delimitless" "${#delimitless}"

SALIDA

Arg 1: Line 1.Arg 2: Line 2.and so on foras long as you might like. 67
mikeserv
fuente
2

Es posible que desee probar:

echo $VAR | tr -s " "

o

myArr=($VAL)
VAL=${myArr[@]}
echo "$VAL"

y también puedes ver esto .

Vivian Maya
fuente
2

Use una expansión de sustitución de bash

Si está usando Bash, puede usar una expansión de sustitución . Por ejemplo:

$ echo "${VAR//  /}"
This displays with extra spaces.
CodeGnome
fuente
1

Esta es una variante para establecer variables de tipo ruta:

set -- "${MYDIR}/usr/local/lib" \
      :"${MYDIR}/usr/lib" \
      :"${MYDIR}/lib" \
       "${LD_LIBRARY_PATH:+:$LD_LIBRARY_PATH}"
    export LD_LIBRARY_PATH="$*"
    LD_LIBRARY_PATH=$(sed 's/ :/:/g' <<< $LD_LIBRARY_PATH)

Uso de setsobrescrituras $@, que se pueden guardar y usar más tarde de la siguiente manera:

ARGV=("$@")
exec foo "${ARGV[@]}"

La LD_LIBRARY_PATH=$(sed 's/ :/:/g' <<< $LD_LIBRARY_PATH)línea elimina espacios antes de los dos puntos, así como posibles espacios en blanco al final. Si solo elimina los espacios finales, use LD_LIBRARY_PATH=${LD_LIBRARY_PATH%% }en su lugar.

Todo este enfoque es una variante de la excelente respuesta de mikeserv.

rsanden
fuente
0

No tengas miedo de los espacios en blanco. Simplemente quítelos antes de imprimir su texto multilínea.

$ cat ./t.sh
#!/bin/bash

NEED_HELP=1
if [[ $NEED_HELP -eq 1 ]]; then

    lucky_number=$((1 + RANDOM % 10 + 10))

    read -r -d '' text <<'    EOF'
    NAME says hello

    Look at this helpful text:

                                                 * -**
                                 Eyjafjallajokull        Eyjafjallajokull
                            Eyjafjallajokull Eyjafjallajokull Eyjafjallajokull
                        Eyjafjallajokull Eyjafjallajokull Eyjafjallajokull Eyjafjallajokull
                    Eyjafjallajokull Eyjafjallajokull Eyjafjallajokull Eyjafjallajokull
                Eyjafjallajokull Eyjafjallajokull Eyjafjallajokull Eyjafjallajokull Eyjafjallajokull
            Eyjafjallajokull Eyjafjallajokull Eyjafjallajokull Eyjafjallajokull Eyjafjallajokull Eyjafjallajokull
        Eyjafjallajokull Eyjafjallajokull Eyjafjallajokull Eyjafjallajokull Eyjafjallajokull Eyjafjallajokull
    Eyjafjallajokull Eyjafjallajokull Eyjafjallajokull Eyjafjallajokull Eyjafjallajokull Eyjafjallajokull Eyjafjallajokull

    Don't go away, please rate your experience... It'll just take two minutes.

    EOF
    text=$(echo "$text" | sed -r 's!^\s{4}!!')
    text=$(echo "$text" | sed -r "s!\bNAME\b!$0!") # Bash: text=${text//NAME/$0}
    text=$(echo "$text" | sed -r "s!\btwo\b!$lucky_number!")

    echo "$text"

fi

Salida:

$ ./t.sh
./t.sh says hello

Look at this helpful text:

                                             * -**
                             Eyjafjallajokull        Eyjafjallajokull
                        Eyjafjallajokull Eyjafjallajokull Eyjafjallajokull
                    Eyjafjallajokull Eyjafjallajokull Eyjafjallajokull Eyjafjallajokull
                Eyjafjallajokull Eyjafjallajokull Eyjafjallajokull Eyjafjallajokull
            Eyjafjallajokull Eyjafjallajokull Eyjafjallajokull Eyjafjallajokull Eyjafjallajokull
        Eyjafjallajokull Eyjafjallajokull Eyjafjallajokull Eyjafjallajokull Eyjafjallajokull Eyjafjallajokull
    Eyjafjallajokull Eyjafjallajokull Eyjafjallajokull Eyjafjallajokull Eyjafjallajokull Eyjafjallajokull
Eyjafjallajokull Eyjafjallajokull Eyjafjallajokull Eyjafjallajokull Eyjafjallajokull Eyjafjallajokull Eyjafjallajokull

Don't go away, please rate your experience... It'll just take 16 minutes.

No es necesario usar un <<-heredoc y romper su sangría de 4 espacios con caracteres de tabulación.

c0xc
fuente