Como atribuir um valor de string a uma variável em várias linhas enquanto indentado?

37

A questão:

  1. Eu preciso atribuir a uma variável um valor decentemente longo.
  2. Todas as linhas do meu script devem estar abaixo de um determinado número de colunas.

Então, 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 elegantemente atribuí-lo sem esses espaços?

    
por Sman865 24.10.2014 / 02:12

9 respostas

19

Aqui, o problema é que você está cercando a variável com aspas duplas (""). Remova-o e as coisas funcionarão bem.

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

Saída

 This displays with extra spaces.

Aqui, o problema é que a duplicação de uma variável preserva todos os caracteres em branco. Isso pode ser usado caso você precise explicitamente.

Por exemplo,

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

imprimirá

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

E ao remover citações, é 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, portanto, preserva espaços extras. Mas no segundo caso, o comando echo recebe o texto como 5 argumentos.

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

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

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

Mas, no caso do cenário abaixo, echo obtém dois argumentos como Hello e World

$ variable="Hello World"
$ echo $variable
    
por 24.10.2014 / 09:15
22

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

Aqui estão algumas soluções de bash (algumas das quais também devem funcionar em outros shells, como zsh ). Em primeiro lugar, com o operador += append (que funciona de maneira um pouco diferente para cada variável de número inteiro, 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ê quiser novas linhas (ou outro espaço em branco) no texto, use $'' quoting:

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

Em seguida, usando printf -v para 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, então, diferentemente da maioria das funções printf , o bash reutiliza a string de formato até que ela se esgote. Você pode colocar um \n dentro da string de formato, ou usar $ '', (ou ambos) para lidar com o espaço 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 construir o texto linha por linha. 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 inteiras são classificadas implicitamente, ao contrário de matrizes associativas) Isso lhe dá um pouco mais de flexibilidade, pois você pode manipular as linhas, se necessário.

Por fim, usando read ou readarray e 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  

O formulário here-document de <<- significa que todas as guias principais são removidas da entrada, portanto, você deve usar as guias para recuar seu texto. As cotações em torno de "EOT" impedem os recursos de expansão do shell, portanto, a entrada é usada textualmente. Com read , utiliza a entrada delimitada por byte NUL, de modo a ler o texto delimitado por nova linha de uma só vez. Com readarray (também conhecido como mapfile , disponível desde o bash-4.0), ele é lido em uma matriz e -t remove novas linhas em cada linha.

    
por 24.10.2014 / 14:40
7

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

link

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 separadores, não espaços.

    
por 24.10.2014 / 10:42
4

Deixe o shell alimentar os alinhamentos indesejados 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.

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

    
por 24.10.2014 / 04:03
4

É assim que eu sugiro que você faça isso, e eu vou explicar o porquê, mas primeiro eu 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 assegurar-lhe que este não é o caso.

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

OUTPUT

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 da variável de origem da expansão, uma expansão delimitada por aspas e a mesma contagem de bytes. Embora a saída possa diferir o conteúdo da variável% shell,$string nunca muda, exceto na atribuição.

Além do mais, se você não entende por que isso acontece, você provavelmente encontrará algumas surpresas desagradáveis, mais cedo ou mais tarde. Vamos tentar de novo, mas em condições ligeiramente diferentes.

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

Mesmo $string - ambiente diferente.

OUTPUT

 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 - $IFS whitespace e $IFS mais alguma coisa. Por padrão, $IFS recebe o valor espaço newline da guia - quais são os três possíveis valores% espaço em branco$IFS. Ele é facilmente alterado, como você pode ver acima, e pode ter efeitos drásticos em expansões de divisão de campo.

$IFS whitespace será eliminado por sequência em um único campo - e é por isso que echo ing uma expansão contendo qualquer seqüência de espaços quando $IFS contém um espaço será avaliado apenas por um espaço único - porque echo concatena seus argumentos em espaços. Mas qualquer valor que não seja espaço em branco não será elidido da mesma maneira, e cada delimitador que ocorre sempre terá um campo em si - como pode ser visto na expansão coisas acima.

Este não é o pior de tudo. Considere este outro $string .

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

OUTPUT

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

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

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

OUTPUT

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 de nome de arquivo se ele puder combiná-los. Isso ocorre após expansão do parâmetro e divisão do campo em sua ordem de análise e, portanto, qualquer sequência não citada é vulnerável dessa maneira. Você pode alternar esse comportamento com set -f , se desejar, mas qualquer shell compatível com POSIX sempre será glob por padrão.

Este é o tipo de coisa que você está enfrentando quando você deixa cair as cotações em expansões para atender às suas preferências de indentação. E mesmo assim, em todos os casos, independentemente do seu comportamento de expansão, o valor real para $string é sempre o que quer que tenha sido quando você o atribuiu pela última vez. Então, vamos voltar para a 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}"

OUTPUT

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 simples de adaptar a sintaxe do shell às suas preferências de recuo. O que estou fazendo acima é atribuir cada string individual a um parâmetro posicional - que pode ser referenciado por um número como $1 ou ${33} - e então atribuir seus valores concatenados a $var usando o parâmetro especial shell $* .

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

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

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

OUTPUT

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

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

OUTPUT

Arg 1: Line 1.Arg 2: Line 2.and so on foras long as you might like. 67
    
por 25.10.2014 / 11:51
3

Talvez você possa tentar isso.

          echo "Test" \
               "Test2" \
               "Test3"
    
por 24.10.2014 / 04:41
1

Você pode tentar:

echo $VAR | tr -s " "

ou

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

e você também pode verificar este fora.

    
por 24.10.2014 / 02:37
1

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

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

$ echo "${VAR//  /}"
This displays with extra spaces.
    
por 24.10.2014 / 02:43
0

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)

O uso de set substitui $@ , que pode ser salvo e usado mais tarde da seguinte maneira:

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

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

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

    
por 20.01.2018 / 17:23

Tags