Como escrever um heredoc em um arquivo no script Bash?

Respostas:

1073

Leia o Advanced Bash-Scripting Guide Capítulo 19. Aqui Documentos .

Aqui está um exemplo que gravará o conteúdo em um arquivo em /tmp/yourfilehere

cat << EOF > /tmp/yourfilehere
These contents will be written to the file.
        This line is indented.
EOF

Observe que o 'EOF' final (The LimitString) não deve ter nenhum espaço em branco na frente da palavra, porque isso significa que LimitStringele não será reconhecido.

Em um script de shell, convém usar o recuo para tornar o código legível, no entanto, isso pode ter o efeito indesejável de recuar o texto no documento aqui. Nesse caso, use <<-(seguido de um traço) para desativar as guias iniciais ( observe que para testar isso, você precisará substituir o espaço em branco inicial por um caractere de tabulação , pois não consigo imprimir caracteres de tabulação reais aqui.)

#!/usr/bin/env bash

if true ; then
    cat <<- EOF > /tmp/yourfilehere
    The leading tab is ignored.
    EOF
fi

Se você não quiser interpretar variáveis ​​no texto, use aspas simples:

cat << 'EOF' > /tmp/yourfilehere
The variable $FOO will not be interpreted.
EOF

Para canalizar o heredoc através de um pipeline de comando:

cat <<'EOF' |  sed 's/a/b/'
foo
bar
baz
EOF

Resultado:

foo
bbr
bbz

... ou para escrever o heredoc em um arquivo usando sudo:

cat <<'EOF' |  sed 's/a/b/' | sudo tee /etc/config_file.conf
foo
bar
baz
EOF
Stefan Lasiewski
fonte
11
Você nem precisa do Bash, esse recurso também está presente nos shells Bourne / Korn / POSIX.
Janus Troelsen
5
e quanto <<<, como eles são chamados?
precisa
18
@PineappleUndertheSea <<<são chamados 'Here Strings'. Código como tr a-z A-Z <<< 'one two three'resultará na string ONE TWO THREE. Mais informações em en.wikipedia.org/wiki/Here_document#Here_strings
Stefan Lasiewski 10/09/13
8
O EOF final também não deve ter nenhum espaço em branco depois dele. Pelo menos no bash, isso resulta em sendo não reconhecido como delimitador
carpii
10
Uma vez que este heredoc particular, destina-se a ser conteúdo literal, em vez do que contêm substituições, ele deve ser <<'EOF', em vez de <<EOF.
Charles Duffy
152

Em vez de usar cato redirecionamento de E / S, pode ser útil usá-lo tee:

tee newfile <<EOF
line 1
line 2
line 3
EOF

É mais conciso, além do operador de redirecionamento, ele pode ser combinado sudose você precisar gravar em arquivos com permissões de root.

Livven
fonte
18
Eu sugiro adicionar > /dev/nullno final da primeira linha para impedir que o conteúdo do arquivo aqui seja exibido em stdout quando ele é criado.
Joe Carroll
11
É verdade, mas a sua solução me atraiu por causa de sua compatibilidade com sudo, e não por causa de sua brevidade :-)
Joe Carroll
2
Como você usaria esse método para acrescentar a um arquivo existente?
MountainX
5
@MountainX Confira man tee. Use o -asinalizador para acrescentar em vez de substituir.
22413 Livven
3
Para uso em um script de configuração que às vezes preciso supervisionar, gosto mais deste porque imprime o conteúdo.
Alois Mahdal
59

Nota:

A pergunta (como escrever um documento aqui (aka heredoc ) em um arquivo em um script bash?) Tem (pelo menos) três principais dimensões ou subquestões independentes:

  1. Deseja sobrescrever um arquivo existente, anexá-lo a um arquivo existente ou gravar em um novo arquivo?
  2. Seu usuário ou outro usuário (por exemplo, root) possui o arquivo?
  3. Deseja escrever o conteúdo do seu heredoc literalmente ou fazer com que o bash interprete referências de variáveis ​​dentro do seu heredoc?

(Existem outras dimensões / subquestões que não considero importantes. Considere editar esta resposta para adicioná-las!) Aqui estão algumas das combinações mais importantes das dimensões da pergunta listada acima, com vários identificadores de delimitação diferentes - não há nada sagrado EOF, apenas certifique-se de que a string usada como seu identificador delimitante não ocorra dentro do seu heredoc:

  1. Para substituir um arquivo existente (ou gravar em um novo arquivo) que você possui, substituindo as referências de variáveis ​​dentro do heredoc:

    cat << EOF > /path/to/your/file
    This line will write to the file.
    ${THIS} will also write to the file, with the variable contents substituted.
    EOF
  2. Para anexar um arquivo existente (ou gravar em um novo arquivo) que você possui, substituindo referências de variáveis ​​dentro do heredoc:

    cat << FOE >> /path/to/your/file
    This line will write to the file.
    ${THIS} will also write to the file, with the variable contents substituted.
    FOE
  3. Para substituir um arquivo existente (ou gravar em um novo arquivo) que você possui, com o conteúdo literal do heredoc:

    cat << 'END_OF_FILE' > /path/to/your/file
    This line will write to the file.
    ${THIS} will also write to the file, without the variable contents substituted.
    END_OF_FILE
  4. Para anexar um arquivo existente (ou gravar em um novo arquivo) que você possui, com o conteúdo literal do heredoc:

    cat << 'eof' >> /path/to/your/file
    This line will write to the file.
    ${THIS} will also write to the file, without the variable contents substituted.
    eof
  5. Para substituir um arquivo existente (ou gravar em um novo arquivo) pertencente à raiz, substituindo as referências de variáveis ​​dentro do heredoc:

    cat << until_it_ends | sudo tee /path/to/your/file
    This line will write to the file.
    ${THIS} will also write to the file, with the variable contents substituted.
    until_it_ends
  6. Para acrescentar um arquivo existente (ou gravar em um novo arquivo) de propriedade de user = foo, com o conteúdo literal do heredoc:

    cat << 'Screw_you_Foo' | sudo -u foo tee -a /path/to/your/file
    This line will write to the file.
    ${THIS} will also write to the file, without the variable contents substituted.
    Screw_you_Foo
TomRoche
fonte
# 6 é o melhor. Mas como você substitui o conteúdo do arquivo existente pelo nº 6?
Aleksandr Makov
2
@Aleksandr Makov: como você substitui o conteúdo do arquivo existente com o # 6? Omita o -a== --append; ou seja, tee -a-> tee. Veja info tee(eu citá-lo aqui, mas comentário marcação é muito limitada.
TomRoche
1
Existe um benefício em # 6 usando gato e tubulação para tee em vez de sudo tee /path/to/your/file << 'Screw_you_Foo'?
precisa
Por que em FOEvez de EOFno exemplo anexado?
Becko
2
@ Becko: apenas para ilustrar que o rótulo é apenas um rótulo. Observe que eu usei um rótulo diferente em cada exemplo.
TomRoche
41

Para aproveitar a resposta de @ Livven , aqui estão algumas combinações úteis.

  1. substituição de variável, guia principal retido, sobrescrever arquivo, eco para stdout

    tee /path/to/file <<EOF
    ${variable}
    EOF
  2. nenhuma substituição de variável , guia principal retida, sobrescrever arquivo, eco para stdout

    tee /path/to/file <<'EOF'
    ${variable}
    EOF
  3. substituição de variável, guia inicial removido , sobrescrever arquivo, eco para stdout

    tee /path/to/file <<-EOF
        ${variable}
    EOF
  4. substituição de variável, guia principal retido, anexado ao arquivo , eco ao stdout

    tee -a /path/to/file <<EOF
    ${variable}
    EOF
  5. substituição de variável, guia principal retido, sobrescrever arquivo, sem eco para stdout

    tee /path/to/file <<EOF >/dev/null
    ${variable}
    EOF
  6. o acima pode ser combinado com sudotambém

    sudo -u USER tee /path/to/file <<EOF
    ${variable}
    EOF
go2null
fonte
16

Quando permissões de raiz são necessárias

Quando permissões de raiz são necessárias para o arquivo de destino, use em |sudo teevez de >:

cat << 'EOF' |sudo tee /tmp/yourprotectedfilehere
The variable $FOO will *not* be interpreted.
EOF
Serge Stroobandt
fonte
É possível passar variáveis ​​para os documentos aqui? Como você conseguiu que o $ FOO fosse interpretado?
user1527227
@ user1527227 Como o exemplo do arquivo aqui diz: A variável $ FOO não será interpretada.
Serge Stroobandt
Abaixo , tentei combinar e organizar essa resposta com a de Stefan Lasiewski .
TomRoche 18/09
3
@ user1527227 Apenas não coloque o EOF entre aspas simples. Então $ FOO será interpretado.
Imnd_neel
11

Para futuras pessoas que possam ter esse problema, o seguinte formato funcionou:

(cat <<- _EOF_
        LogFile /var/log/clamd.log
        LogTime yes
        DatabaseDirectory /var/lib/clamav
        LocalSocket /tmp/clamd.socket
        TCPAddr 127.0.0.1
        SelfCheck 1020
        ScanPDF yes
        _EOF_
) > /etc/clamd.conf
Joshua Enfield
fonte
6
Não precisa dos parênteses: cat << END > afileseguido pelo heredoc funciona perfeitamente bem.
Glenn Jackman
Obrigado, isso realmente resolveu outro problema que encontrei. Depois de alguns documentos aqui, houve alguns problemas. Eu acho que tinha a ver com os parênteses, como nos conselhos acima.
Joshua Enfield
2
Isso não vai funcionar. O redirecionamento de saída precisa estar no final da linha que começa com, catcomo mostrado na resposta aceita.
Pausado até novo aviso.
2
@DennisWilliamson Funciona, é para isso que servem os parênteses. Toda a catroda dentro de um subnível, e toda a saída do subshell é redirecionado para o arquivo
Izkata
2
@ Izkata: Se você olhar para o histórico de edições desta resposta, os parênteses foram removidos antes de eu fazer o meu comentário e adicionados novamente. o comentário de glenn jackman (e o meu) se aplica.
Pausado até novo aviso.
3

Como exemplo, você pode usá-lo:

Primeiro (fazendo a conexão ssh):

while read pass port user ip files directs; do
    sshpass -p$pass scp -o 'StrictHostKeyChecking no' -P $port $files $user@$ip:$directs
done <<____HERE
    PASS    PORT    USER    IP    FILES    DIRECTS
      .      .       .       .      .         .
      .      .       .       .      .         .
      .      .       .       .      .         .
    PASS    PORT    USER    IP    FILES    DIRECTS
____HERE

Segundo (executando comandos):

while read pass port user ip; do
    sshpass -p$pass ssh -p $port $user@$ip <<ENDSSH1
    COMMAND 1
    .
    .
    .
    COMMAND n
ENDSSH1
done <<____HERE
    PASS    PORT    USER    IP
      .      .       .       .
      .      .       .       .
      .      .       .       .
    PASS    PORT    USER    IP    
____HERE

Terceiro (executando comandos):

Script=$'
#Your commands
'

while read pass port user ip; do
    sshpass -p$pass ssh -o 'StrictHostKeyChecking no' -p $port $user@$ip "$Script"

done <<___HERE
PASS    PORT    USER    IP
  .      .       .       .
  .      .       .       .
  .      .       .       .
PASS    PORT    USER    IP  
___HERE

Quarto (usando variáveis):

while read pass port user ip fileoutput; do
    sshpass -p$pass ssh -o 'StrictHostKeyChecking no' -p $port $user@$ip fileinput=$fileinput 'bash -s'<<ENDSSH1
    #Your command > $fileinput
    #Your command > $fileinput
ENDSSH1
done <<____HERE
    PASS    PORT    USER    IP      FILE-OUTPUT
      .      .       .       .          .
      .      .       .       .          .
      .      .       .       .          .
    PASS    PORT    USER    IP      FILE-OUTPUT
____HERE
MLSC
fonte
-1

Eu gosto deste método para concisão, legibilidade e apresentação em um script recuado:

<<-End_of_file >file
       foo bar
End_of_file

Onde →       está um tabpersonagem.

dan
fonte