Como atribuir um valor heredoc a uma variável no Bash?


374

Eu tenho essa sequência de várias linhas (aspas incluídas):

abc'asdf"
$(dont-execute-this)
foo"bar"''

Como atribuiria a uma variável usando um heredoc no Bash?

Eu preciso preservar novas linhas.

Eu não quero escapar dos caracteres da string, isso seria irritante ...


@ JohnM - Acabei de tentar uma atribuição heredoc com aspas simples 'EOF', com quebras de linha escapadas com o ` in the content: if the second line has comando cd`, volto: " .sh: linha X: cd: comando não encontrado "; mas se eu citar duas vezes "EOF"; as variáveis ​​bash ${A}não são preservadas como seqüências de caracteres (elas são expandidas); mas então, as quebras de linha são preservadas - e, não tenho problemas ao executar um comando cdna segunda linha ( e 'EOF' e "EOF" parecem funcionar bem também com eval, para executar um conjunto de comandos armazenados em uma variável de string ). Felicidades!
Sdaau 24/05/12

11
... e para adicionar ao meu comentário anterior: bash comments "#" na "EOF"variável double qouted , se chamado via eval $VAR, fará com que todo o restante do script seja comentado, pois aqui $ VAR será visto como uma única linha ; para poder usar os #comentários do bash no script de múltiplas linhas, aspas duplas também variável no eval call: eval "$ VAR" `.
Sdaau

@sdaau: Eu tive problemas com evalesse método, mas não o localizei, pois fazia parte de algum pacote com evalalgumas variáveis ​​definidas no arquivo de configuração. Mensagem de erro foi: /usr/lib/network/network: eval: line 153: syntax error: unexpected end of file. Acabei de mudar para outra solução.
Golar Ramblar

Respostas:


515

Você pode evitar o uso inútil cate lidar melhor com aspas incompatíveis com isso:

$ read -r -d '' VAR <<'EOF'
abc'asdf"
$(dont-execute-this)
foo"bar"''
EOF

Se você não citar a variável ao repeti-la, as novas linhas serão perdidas. A citação preserva-os:

$ echo "$VAR"
abc'asdf"
$(dont-execute-this)
foo"bar"''

Se você deseja usar o recuo para facilitar a leitura no código-fonte, use um traço após os valores menores. O recuo deve ser feito usando apenas guias (sem espaços).

$ read -r -d '' VAR <<-'EOF'
    abc'asdf"
    $(dont-execute-this)
    foo"bar"''
    EOF
$ echo "$VAR"
abc'asdf"
$(dont-execute-this)
foo"bar"''

Se, em vez disso, você deseja preservar as guias no conteúdo da variável resultante, é necessário remover a guia IFS. O marcador de terminal para o documento aqui ( EOF) não deve ser recuado.

$ IFS='' read -r -d '' VAR <<'EOF'
    abc'asdf"
    $(dont-execute-this)
    foo"bar"''
EOF
$ echo "$VAR"
    abc'asdf"
    $(dont-execute-this)
    foo"bar"''

As guias podem ser inseridas na linha de comando pressionando Ctrl- V Tab. Se você estiver usando um editor, dependendo de qual, isso também poderá funcionar ou talvez seja necessário desativar o recurso que converte automaticamente as guias em espaços.


117
Eu acho que vale a pena mencionar que, se você tiver set -o errexit(aka set -e) em seu script e usar isso, ele encerrará seu script porque readretorna um código de retorno diferente de zero quando atinge o EOF.
Mark Byers

14
@ MarkByers: Essa é uma das razões pelas quais nunca uso set -ee sempre recomendo contra o seu uso. É melhor usar o tratamento adequado de erros. trapé seu amigo. Outros amigos: elsee ||entre outros.
Pausado até novo aviso.

7
A prevenção de catrealmente vale a pena nesse caso? Atribuir um heredoc a uma variável com caté um idioma bem conhecido. De alguma forma, o uso readofusca as coisas, trazendo poucos benefícios.
Gregory Pakosz

6
@ulidtko Isso ocorre porque você não tem espaço entre de a string vazia; bashentra -rd''em colapso para simplesmente -rdantes de readver seus argumentos, então VARé tratado como o argumento para -d.
Chepner #

6
Nesse formato, readretornará com um código de saída diferente de zero. Isso torna esse método abaixo do ideal em um script com a verificação de erros ativada (por exemplo set -e).
Suíço

246

Use $ () para atribuir a saída de catsua variável assim:

VAR=$(cat <<'END_HEREDOC'
abc'asdf"
$(dont-execute-this)
foo"bar"''
END_HEREDOC
)

# this will echo variable with new lines intact
echo "$VAR"
# this will echo variable without new lines (changed to space character)
echo $VAR

Certifique-se de delimitar o início de END_HEREDOC com aspas simples.

Observe que o delimitador heredoc final END_HEREDOCdeve estar sozinho na linha (portanto, o parêntese final fica na próxima linha).

Obrigado @ephemientpela resposta.


11
Na verdade, este deixa passar aspas em algumas circunstâncias. Eu consegui fazer isso em Perl facilmente ...
Neil

32
+1. Esta é a solução mais legível, pelo menos para os meus olhos. Ele deixa o nome da variável na extremidade esquerda da página, em vez de incorporá-la no comando read.
22613 Clayton Stanley

12
PSA: lembre-se de que a variável deve ser citada para preservar novas linhas. echo "$VAR"em vez de echo $VAR.
Sevko

7
Isso é bom com o ashOpenWRT, onde readnão suporta -d.
David Ehrmann

3
Por razões que não consigo entender, isso falha com um erro "EOF inesperado" se você tiver um backtick não emparelhado no heredoc.
Radon Rosborough

79

essa é a variação do método Dennis, parece mais elegante nos scripts.

definição de função:

define(){ IFS='\n' read -r -d '' ${1} || true; }

uso:

define VAR <<'EOF'
abc'asdf"
$(dont-execute-this)
foo"bar"''
EOF

echo "$VAR"

desfrutar

O ps criou uma versão de 'loop de leitura' para shells que não suportam read -d. deve trabalhar com set -eue backticks desemparelhados , mas não foi testado muito bem:

define(){ o=; while IFS="\n" read -r a; do o="$o$a"'
'; done; eval "$1=\$o"; }

11
Isso parece funcionar apenas superficialmente. A função define retornará um status 1, e não tenho certeza do que precisa ser corrigido.
fny 29/05

11
Isso também é superior à resposta aceita, porque pode ser modificado para oferecer suporte ao POSIX sh além do bash (um readloop na função, para evitar o -d ''bashismo necessário para preservar novas linhas).
ELLIOTTCABLE

Ao contrário da opção cat-in-a-subshell, isso funciona com backticks não emparelhados no heredoc. Obrigado!
Radon Rosborough

Esta solução funciona com o set -econjunto, enquanto a resposta selecionada não. Parece ser por causa dehttp://unix.stackexchange.com/a/265151/20650
ffledgling 8/16

2
O status de retorno do @fny ps foi corrigido há muito tempo
ttt

36
VAR=<<END
abc
END

não funciona porque você está redirecionando o stdin para algo que não se importa, ou seja, a atribuição

export A=`cat <<END
sdfsdf
sdfsdf
sdfsfds
END
` ; echo $A

funciona, mas há um tique lá dentro que pode impedi-lo de usar isso. Além disso, você deve realmente evitar o uso de backticks, é melhor usar a notação de substituição de comando $(..).

export A=$(cat <<END
sdfsdf
sdfsdf
sdfsfds
END
) ; echo $A

Atualizei minha pergunta para incluir $ (executável). Além disso, como você preserva as novas linhas?
214 Neil

2
@ l0st3d: Tão perto ... Use em seu $(cat <<'END'lugar. @ Neil: A última nova linha não fará parte da variável, mas o restante será preservado.
ephemient 22/07/09

11
Não parece que nenhuma nova linha seja preservada. Executando o exemplo acima, vejo: "sdfsdf sdfsdf sdfsfds" ... ah! Mas escrevendo echo "$A"(ou seja, colocando $ A entre aspas duplas) e você vê as novas linhas!
Darren Cook

11
@ Darren: aha! Eu havia notado o problema das novas linhas e o uso de aspas na variável de saída corrige o problema. valeu!
Javadba

11
Curiosamente, devido à peculiaridade do primeiro exemplo, em uma pitada você pode usá-lo para blocos de comentário improvisados como este: REM=<< 'REM' ... comment block goes here ... REM. Ou de forma mais compacta : << 'REM' .... Onde "REM" pode ser algo como "NOTES" ou "SCRATCHPAD", etc.
Beejor

34

Ainda não existe uma solução que preserve novas linhas.

Isso não é verdade - você provavelmente está apenas sendo enganado pelo comportamento do eco:

echo $VAR # strips newlines

echo "$VAR" # preserves newlines


5
Realmente este é o comportamento de como uma citação de variável funciona. Sem aspas, ele irá inseri-los como diferentes parâmetros, espaço deliminated, enquanto com citações todo o conteúdo variável será tratado como um argumento
Czipperz

11

Ramificando a resposta de Neil , muitas vezes você não precisa de um var, pode usar uma função da mesma maneira que uma variável e é muito mais fácil ler do que as readsoluções embutidas ou baseadas em.

$ complex_message() {
  cat <<'EOF'
abc'asdf"
$(dont-execute-this)
foo"bar"''
EOF
}

$ echo "This is a $(complex_message)"
This is a abc'asdf"
$(dont-execute-this)
foo"bar"''

9

Uma matriz é uma variável, portanto, nesse caso, o mapfile funcionará

mapfile y <<z
abc'asdf"
$(dont-execute-this)
foo"bar"''
z

Então você pode imprimir assim

printf %s "${y[@]}"

3

atribuir um valor heredoc a uma variável

VAR="$(cat <<'VAREOF'
abc'asdf"
$(dont-execute-this)
foo"bar"''
VAREOF
)"

usado como argumento de um comando

echo "$(cat <<'SQLEOF'
xxx''xxx'xxx'xx  123123    123123
abc'asdf"
$(dont-execute-this)
foo"bar"''
SQLEOF
)"

Quando tentei o primeiro método, parece não haver terminadores de linha entre as linhas. Deve haver algum tipo de configuração na minha máquina Linux?
Kemin Zhou

Isso provavelmente significa que quando você estava ecoando sua variável, você não colocar aspas em torno dele ... Experimente assim:echo "$VAR"
Brad Parks

1

Eu me vi tendo que ler uma string com NULL, então aqui está uma solução que lê tudo o que você joga nela. Embora se você realmente esteja lidando com NULL, precisará lidar com isso no nível hexadecimal.

$ cat> read.dd.sh

read.dd() {
     buf= 
     while read; do
        buf+=$REPLY
     done < <( dd bs=1 2>/dev/null | xxd -p )

     printf -v REPLY '%b' $( sed 's/../ \\\x&/g' <<< $buf )
}

Prova:

$ . read.dd.sh
$ read.dd < read.dd.sh
$ echo -n "$REPLY" > read.dd.sh.copy
$ diff read.dd.sh read.dd.sh.copy || echo "File are different"
$ 

Exemplo HEREDOC (com ^ J, ^ M, ^ I):

$ read.dd <<'HEREDOC'
>       (TAB)
>       (SPACES)
(^J)^M(^M)
> DONE
>
> HEREDOC

$ declare -p REPLY
declare -- REPLY="  (TAB)
      (SPACES)
(^M)
DONE

"

$ declare -p REPLY | xxd
0000000: 6465 636c 6172 6520 2d2d 2052 4550 4c59  declare -- REPLY
0000010: 3d22 0928 5441 4229 0a20 2020 2020 2028  =".(TAB).      (
0000020: 5350 4143 4553 290a 285e 4a29 0d28 5e4d  SPACES).(^J).(^M
0000030: 290a 444f 4e45 0a0a 220a                 ).DONE

0

Graças à resposta de dimo414 , isso mostra como sua ótima solução funciona e mostra que você também pode ter aspas e variáveis ​​no texto:

saída de exemplo

$ ./test.sh

The text from the example function is:
  Welcome dev: Would you "like" to know how many 'files' there are in /tmp?

  There are "      38" files in /tmp, according to the "wc" command

test.sh

#!/bin/bash

function text1()
{
  COUNT=$(\ls /tmp | wc -l)
cat <<EOF

  $1 Would you "like" to know how many 'files' there are in /tmp?

  There are "$COUNT" files in /tmp, according to the "wc" command

EOF
}

function main()
{
  OUT=$(text1 "Welcome dev:")
  echo "The text from the example function is: $OUT"
}

main

Seria interessante ver uma citação inigualável no texto para ver como ele lida com isso. Talvez `Não surte, existem" $ COUNT "arquivos` para que o apóstrofo / aspas simples possa tornar as coisas interessantes.
dragon788

-10
$TEST="ok"
read MYTEXT <<EOT
this bash trick
should preserve
newlines $TEST
long live perl
EOT
echo -e $MYTEXT
Ao utilizar nosso site, você reconhece que leu e compreendeu nossa Política de Cookies e nossa Política de Privacidade.
Licensed under cc by-sa 3.0 with attribution required.