Como atribuir um valor de seqüência de caracteres a uma variável em várias linhas enquanto recuado?

54

O problema:

  1. Preciso atribuir a uma variável um valor decentemente longo.
  2. Todas as linhas do meu script devem estar sob um certo número de colunas.

Então, eu estou tentando atribuí-lo usando mais de uma linha.

É simples fazer sem recuos:

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

Resultado:

This displays without any issues.

No entanto, com recuos:

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

Resultado:

This displays with      extra spaces.

Como posso atribuí-lo elegantemente sem esses espaços?

Sman865
fonte

Respostas:

31

Aqui, o problema é que você está cercando a variável entre aspas duplas (""). Remova e tudo funcionará bem.

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

Resultado

 This displays with extra spaces.

Aqui, o problema é que citar duas vezes uma variável preserva todos os caracteres de espaço em branco. Isso pode ser usado no caso, se você precisar explicitamente.

Por exemplo,

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

irá imprimir

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

E ao remover aspas, é diferente

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

Aqui, o Bash remove espaços extras no texto porque, no primeiro caso, o texto inteiro é considerado um argumento "único" e, assim, preserva espaços extras. Mas, no segundo caso, o echocomando recebe o texto como 5 argumentos.

Citar uma variável também será útil ao passar argumentos para comandos.

No comando abaixo, echoobtém apenas um argumento como"Hello World"

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

Mas, no caso do cenário abaixo, echoobtém dois argumentos como HelloeWorld

$ variable="Hello World"
$ echo $variable
Kannan Mohan
fonte
A maioria das respostas funcionou bem, mas essa foi a mais simples. Obrigado!
Sman865
12
@ Sman865 - acredite em mim quando digo que essa é, na verdade, quase definitivamente a resposta mais complicada oferecida aqui, e também é errada na maioria dos aspectos - especialmente em sua declaração de abertura. Qualquer problema relacionado à atribuição de valor não pode estar relacionado à sua expansão posterior - isso é apenas ao contrário. Sinto muito Kannan, mas esta resposta é errada e de cabeça errada. As expansões de divisão de campo $IFSsão uma ferramenta poderosa e universal - mas o código escrito dessa maneira nunca pode produzir resultados confiáveis ​​de qualquer tipo.
mikeserv
2
Deixar de usar aspas ao expandir uma variável causa problemas mais cedo ou mais tarde, começando com a expansão do nome do arquivo (qualquer um * ? []).
mr.spuratic
Concordo que as variáveis ​​precisam ser colocadas entre aspas duplas para impedir que a expansão do bash. Mas em casos especiais, podemos evitá-lo para evitar complicações de código.
precisa
11
Concordo com o @mikeserv, este é um péssimo conselho se for tomado pelo seu valor nominal. Isso será interrompido se usado sem as consequências. Por exemplo, se a variável for passada como argumento para um comando, você não deseja que cada palavra seja dividida como argumento separado.
haridsv
28

As soluções dadas por esuoxu e Mickaël Bucas são as maneiras mais comuns e portáteis de fazer isso.

Aqui estão algumas bashsoluções (algumas das quais também devem funcionar em outras conchas, como zsh). Primeiramente, com o +=operador append (que funciona de uma maneira ligeiramente diferente para cada uma de uma variável inteira, uma variável regular e uma 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 ..." 

Se você deseja novas linhas (ou outros espaços em branco / escapes) no texto, use $''aspas:

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

Em seguida, use printf -vpara atribuir um valor formatado a uma variável

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 ..."

O truque aqui é que existem mais argumentos do que especificadores de formato; portanto, ao contrário da maioria das printffunções, o bash reutiliza a sequência de formatação até que ela se esgote. Você pode colocar um \ndentro da string de formato ou usar $ '', (ou ambos) para lidar com espaços em branco.

Em seguida, usando uma 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 ..." )

Você também pode usar +=para criar o texto linha por linha (observe o ()):

text+=("post script")

Aqui, porém, você deve se lembrar de "achatar" a matriz se quiser todo o conteúdo de texto de uma só vez

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

(matrizes indexadas por número inteiro são classificadas implicitamente, ao contrário de matrizes associativas) Isso oferece um pouco mais de flexibilidade, pois você pode manipular linhas e até fatiar e cortar dados, se necessário.

Por fim, usando reador readarraye um "documento aqui":

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  

A forma de documento aqui <<-significa que todas as guias físicas principais são removidas da entrada; portanto, você deve usar as guias para recuar o texto. As aspas "EOT"impedem os recursos de expansão do shell; portanto, a entrada é usada literalmente. Com readele, usa entrada NUL delimitada por bytes, para que ele leia o texto delimitado por nova linha de uma só vez. Com readarray(aka mapfile, disponível desde o bash-4.0), ele lê em uma matriz e -tretira novas linhas em cada linha.

mr.spuratic
fonte
11
A readopção com here documenté muito boa! Útil para incorporar scripts python e executar com python -c. Eu costumava fazer script=$(cat <here document>)antes, mas read -r -d '' script <here document>é muito melhor.
haridsv
9

Existe uma sintaxe heredoc especial que remove as guias no início de todas as linhas: "<< -" (observe o traço adicionado)

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

Exemplo 19-4. Mensagem de várias linhas, com abas suprimidas

Você pode usá-lo assim:

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

Resultado:

A
B
C

Funciona apenas com guias, não espaços.

Mickaël Bucas
fonte
Aqui no ubuntu, o inverso é verdadeiro - os espaços funcionam, não as guias. \tfunciona muito bem se você precisar de guias. Basta usar echo -e "$v"ao invés echo "$v"de habilitar caracteres de barra invertida
will-ob
5

Deixe a casca comer as linhas indesejadas e os seguintes espaços:

$ 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.

Portanto, é possível ... mas com certeza é uma questão de gosto gostar ou não dessa solução ...

yeti
fonte
3
cada um deles é um garfo.
mikeserv
5

Talvez você possa tentar isso.

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

É assim que eu sugiro que você faça, e vou explicar o porquê, mas primeiro quero falar sobre outra coisa ...

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

Muitas das outras soluções oferecidas aqui parecem sugerir que você pode, de alguma forma, afetar o conteúdo de uma variável de shell alterando seus métodos de expansão. Posso garantir que esse não é o caso.

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

RESULTADO

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

O que você vê acima é primeiro uma expansão de divisão de campo, depois um relatório sobre a contagem de bytes para a variável de origem da expansão, depois uma expansão delimitada por aspas e a mesma contagem de bytes. Embora a saída possa diferir, o conteúdo da variável do shell $stringnunca muda, exceto na atribuição.

Além do mais, se você não entende por que isso ocorre, é provável que encontre surpresas muito desagradáveis, mais cedo ou mais tarde. Vamos tentar novamente, mas em condições ligeiramente diferentes.

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

Mesmo $string- ambiente diferente.

RESULTADO

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

A divisão de campo ocorre com base nos delimitadores de campo definidos em $IFS. Existem dois tipos de delimitadores - $IFSespaço em branco e $IFSqualquer outra coisa. Por padrão, $IFSé atribuída a nova linha da guia do espaço de valor - que são os três $IFSvalores possíveis de espaço em branco. Porém, é facilmente alterado, como você pode ver acima, e pode ter efeitos drásticos nas expansões divididas em campo.

$IFSo espaço em branco será eliminado por sequência em um único campo - e é por isso que echouma expansão contendo qualquer sequência de espaços quando $IFScontiver um espaço será avaliada em apenas um espaço - porque echoconcatena seus argumentos em espaços. Mas quaisquer valores que não sejam de espaço em branco não serão exibidos da mesma maneira, e cada delimitador sempre terá um campo em si - como pode ser visto na expansão de coisas acima.

Isso não é o pior. Considere esse outro $string.

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

RESULTADO

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

Parece ok, certo? Bem, vamos alterar o ambiente novamente.

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

RESULTADO

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 padrão, o shell expandirá os globs do nome do arquivo, se puder correspondê-los. Isso ocorre após a expansão do parâmetro e a divisão do campo em sua ordem de análise e, portanto, qualquer string não citada fica vulnerável dessa maneira. Você pode desativar esse comportamento set -fse quiser, mas qualquer shell compatível com POSIX sempre será exibido por padrão.

Esse é o tipo de coisa contra a qual você solta citações em expansões para se adequar às suas preferências de recuo. E mesmo assim, em todos os casos, independentemente do seu comportamento de expansão, o valor real $stringainda é sempre o que era quando você o atribuiu pela última vez. Então, voltemos à primeira coisa.

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

RESULTADO

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

Acredito que esta é uma maneira muito mais sadia de adaptar a sintaxe do shell às suas preferências de recuo. O que estou fazendo acima é atribuir cada sequência individual a um parâmetro posicional - que pode ser referenciado por número como $1ou ${33}- e depois atribuir seus valores concatenados ao $varuso do parâmetro shell especial $*.

Essa abordagem não é imune $IFS, mesmo assim. Ainda assim, considero sua relação com $IFSum benefício adicional a esse respeito. Considerar:

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

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

RESULTADO

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 você pode ver, $*concatena cada argumento no "$@"primeiro byte $IFS. Assim, salvar seu valor enquanto $IFSé atribuído de forma diferente obtém delimitadores de campo diferentes para cada valor salvo. O que você vê acima é o valor literal de cada variável, a propósito. Se você não quisesse delimitador, faria:

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

RESULTADO

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

Você pode tentar:

echo $VAR | tr -s " "

ou

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

e você também pode verificar isso .

Vivian Maya
fonte
2

Use uma expansão de substituição de bash

Se você estiver usando o Bash, poderá usar uma expansão de substituição . Por exemplo:

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

Esta é uma variante para definir variáveis ​​semelhantes a caminhos:

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)

Usando setsubstituições $@, que podem ser salvas e usadas posteriormente, da seguinte maneira:

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

A LD_LIBRARY_PATH=$(sed 's/ :/:/g' <<< $LD_LIBRARY_PATH)linha elimina espaços antes de dois pontos, bem como possíveis espaços em branco à direita. Se apenas eliminar espaços finais, use-o LD_LIBRARY_PATH=${LD_LIBRARY_PATH%% }.

Toda essa abordagem é uma variante da excelente resposta do mikeserv.

rsanden
fonte
0

Não tenha medo de caracteres em branco. Basta removê-los antes de imprimir o texto de várias linhas.

$ 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

Resultado:

$ ./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.

Não há necessidade de usar um <<-heredoc e interromper seu recuo de 4 espaços com caracteres de tabulação.

c0xc
fonte