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.