Como "cat << EOF" funciona no bash?

631

Eu precisava escrever um script para inserir entrada de várias linhas em um programa ( psql).

Depois de pesquisar um pouco, descobri que a seguinte sintaxe funciona:

cat << EOF | psql ---params
BEGIN;

`pg_dump ----something`

update table .... statement ...;

END;
EOF

Isso constrói corretamente a sequência de linhas múltiplas (de BEGIN;para END;, inclusive) e a canaliza como uma entrada para psql.

Mas não tenho ideia de como / por que funciona, alguém pode explicar?

Estou me referindo principalmente a cat << EOF, eu sei >saídas para um arquivo, >>anexa a um arquivo, <lê as entradas do arquivo.

O que <<exatamente faz?

E existe uma página de manual para isso?

hasen
fonte
26
Provavelmente é um uso inútil cat. Tente psql ... << EOF ... Consulte também "aqui strings". mywiki.wooledge.org/BashGuide/InputAndOutput?#Here_Strings
Pausado até novo aviso.
1
Estou surpreso que funcione com gato, mas não com eco. O gato deve esperar um nome de arquivo como stdin, não uma string de caracteres. psql << EOF parece lógico, mas não de outra maneira. Funciona com gato, mas não com eco. Comportamento estranho. Alguma pista sobre isso?
23315 Alex
Respondendo a mim mesmo: cat sem parâmetros executa e replica para a saída o que for enviado via input (stdin), portanto, usando sua saída para preencher o arquivo via>. De fato, um nome de arquivo lido como parâmetro não é um fluxo stdin.
23415 Alex
@Alex eco apenas imprime de argumentos de linha de comando enquanto catlê stding (quando canalizada para ele) ou lê um arquivo que lhe corresponde de argumentos de linha de comando
O nulo-Pointer-

Respostas:

519

Isso é chamado de formato heredoc para fornecer uma string no stdin. Veja https://en.wikipedia.org/wiki/Here_document#Unix_shells para mais detalhes.


De man bash:

Aqui Documentos

Esse tipo de redirecionamento instrui o shell a ler a entrada da fonte atual até que uma linha contendo apenas a palavra (sem espaços em branco à direita) seja vista.

Todas as linhas lidas até esse ponto são usadas como entrada padrão para um comando.

O formato dos documentos aqui é:

          <<[-]word
                  here-document
          delimiter

Nenhuma expansão de parâmetro, substituição de comando, expansão aritmética ou expansão de nome de caminho é executada no word . Se algum caractere no word for citado, o delimitador é o resultado da remoção de aspas no word e as linhas no documento aqui não são expandidas. Se a palavra não estiver entre aspas, todas as linhas do documento aqui estão sujeitas a expansão de parâmetro, substituição de comando e expansão aritmética. Neste último caso, a seqüência de caracteres \<newline>é ignorada e \deve ser utilizado para citar os personagens \, $e `.

Se o operador de redirecionamento for <<-, todos os caracteres de tabulação iniciais serão removidos das linhas de entrada e da linha que contém o delimitador . Isso permite que os documentos aqui dentro dos scripts de shell sejam recuados de maneira natural.

kennytm
fonte
12
Eu estava tendo mais dificuldade para desativar a expansão de variáveis ​​/ parâmetros. Tudo o que eu precisava fazer era usar "aspas duplas" e isso foi corrigido! Obrigado pela informação!
Xeoncross
11
Em relação a <<-, observe que apenas os caracteres de tabulação iniciais são removidos - não os caracteres de tabulação flexível. Esse é um daqueles casos raros em que você realmente precisa do caractere de tabulação. Se o restante do documento usar guias flexíveis, certifique-se de mostrar caracteres invisíveis e (por exemplo) copiar e colar um caractere de tabulação. Se você fizer certo, o realce da sintaxe deve capturar corretamente o delimitador final.
trkoch
1
Não vejo como essa resposta é mais útil do que as abaixo. Ele simplesmente regurgitar informações que podem ser encontradas em outros lugares (que provavelmente já foi verificada)
BrDaHa
@BrDaHa, talvez não seja. Por que a pergunta? por causa de votos positivos? ele era o único para vários anos. é visto comparando datas.
Alexei Martianov
500

A cat <<EOFsintaxe é muito útil ao trabalhar com texto de várias linhas no Bash, por exemplo. ao atribuir uma sequência de linhas múltiplas a uma variável de shell, arquivo ou canal.

Exemplos de cat <<EOFuso de sintaxe no Bash:

1. Atribua seqüência de várias linhas a uma variável de shell

$ sql=$(cat <<EOF
SELECT foo, bar FROM db
WHERE foo='baz'
EOF
)

A $sqlvariável agora também contém os caracteres de nova linha. Você pode verificar com echo -e "$sql".

2. Passe uma string de várias linhas para um arquivo no Bash

$ cat <<EOF > print.sh
#!/bin/bash
echo \$PWD
echo $PWD
EOF

O print.sharquivo agora contém:

#!/bin/bash
echo $PWD
echo /home/user

3. Passe a sequência de linhas múltiplas para um tubo no Bash

$ cat <<EOF | grep 'b' | tee b.txt
foo
bar
baz
EOF

O b.txtarquivo contém bare bazlinhas. A mesma saída é impressa para stdout.

Vojtech Vitek
fonte
1. 1 e 3 podem ser feitos sem gato; 2. O exemplo 1 pode ser feito com uma sequência simples de várias linhas
Daniel Alder
269

No seu caso, "EOF" é conhecido como "Aqui Tag". <<HereDiz basicamente ao shell que você inserirá uma sequência de linhas múltiplas até a "tag" Here. Você pode nomear essa tag como desejar, geralmente é EOFou STOP.

Algumas regras sobre as tags Here:

  1. A tag pode ser qualquer sequência, maiúscula ou minúscula, embora a maioria das pessoas use maiúsculas por convenção.
  2. A tag não será considerada como aqui se houver outras palavras nessa linha. Nesse caso, será meramente considerado parte da string. A tag deve estar sozinha em uma linha separada, para ser considerada uma tag.
  3. A tag não deve ter espaços à esquerda ou à direita nessa linha para ser considerada uma tag. Caso contrário, será considerado como parte da string.

exemplo:

$ cat >> test <<HERE
> Hello world HERE <-- Not by itself on a separate line -> not considered end of string
> This is a test
>  HERE <-- Leading space, so not considered end of string
> and a new line
> HERE <-- Now we have the end of the string
edelans
fonte
31
esta é a melhor resposta real ... você definir tanto e indicar claramente o objetivo principal do uso em vez de teoria relacionada ... o que é importante mas não é necessário ... obrigado - super útil
oemb1905
5
@edelans, você deve adicionar que, quando <<-usado, a guia inicial não impedirá que a tag seja reconhecida #
The-null-Pointer-
1
sua resposta me clicou em "você vai inserir uma sequência multilinha"
Cálculo
79

POSIX 7

kennytm citado man bash, mas a maior parte disso também é POSIX 7: http://pubs.opengroup.org/onlinepubs/9699919799/utilities/V3_chap02.html#tag_18_07_04 :

Os operadores de redirecionamento "<<" e "<< -" permitem o redirecionamento de linhas contidas em um arquivo de entrada do shell, conhecido como "documento aqui", para a entrada de um comando.

O documento aqui deve ser tratado como uma única palavra que começa após a próxima e continua até que exista uma linha contendo apenas o delimitador e a, sem caracteres no meio. Então, o próximo documento aqui começa, se houver um. O formato é o seguinte:

[n]<<word
    here-document
delimiter

onde o n opcional representa o número do descritor de arquivo. Se o número for omitido, o documento aqui se refere à entrada padrão (descritor de arquivo 0).

Se qualquer caractere no word for citado, o delimitador será formado pela remoção da citação no word, e as linhas do documento aqui não serão expandidas. Caso contrário, o delimitador deve ser a própria palavra.

Se nenhum caractere no word for citado, todas as linhas do documento aqui serão expandidas para expansão de parâmetro, substituição de comando e expansão aritmética. Nesse caso, a entrada se comporta como aspas duplas internas (consulte aspas duplas). No entanto, o caractere de aspas duplas ('"') não deve ser tratado especialmente em um documento aqui, exceto quando as aspas duplas aparecerem em" $ () "," `` "" ou "$ {}".

Se o símbolo de redirecionamento for "<< -", todos os <tab>caracteres iniciais serão retirados das linhas de entrada e da linha que contém o delimitador à direita. Se mais de um operador "<<" ou "<< -" for especificado em uma linha, o documento aqui associado ao primeiro operador deve ser fornecido primeiro pelo aplicativo e deve ser lido primeiro pelo shell.

Quando um documento aqui é lido a partir de um dispositivo terminal e o shell é interativo, ele deve gravar o conteúdo da variável PS2, processada conforme descrito em Shell Variables, em erro padrão antes de ler cada linha de entrada até que o delimitador seja reconhecido.

Exemplos

Alguns exemplos ainda não dados.

As cotações impedem a expansão de parâmetros

Sem aspas:

a=0
cat <<EOF
$a
EOF

Resultado:

0

Entre aspas:

a=0
cat <<'EOF'
$a
EOF

ou (feio, mas válido):

a=0
cat <<E"O"F
$a
EOF

Saídas:

$a

O hífen remove as guias principais

Sem hífen:

cat <<EOF
<tab>a
EOF

onde <tab>está uma guia literal e pode ser inserido comCtrl + V <tab>

Resultado:

<tab>a

Com hífen:

cat <<-EOF
<tab>a
<tab>EOF

Resultado:

a

Isso existe, é claro, para que você possa recuar seu catcódigo semelhante, que é mais fácil de ler e manter. Por exemplo:

if true; then
    cat <<-EOF
    a
    EOF
fi

Infelizmente, isso não funciona para caracteres de espaço: o POSIX favoreceu o tabrecuo aqui. Caramba.

Ciro Santilli adicionou uma nova foto
fonte
Em seu último exemplo, discutindo <<-e <tab>a, note-se que o objetivo era permitir a indentação normal de código no script, enquanto permitia que o texto heredoc apresentado ao processo de recebimento iniciasse na coluna 0. É um recurso pouco comumente visto e um pouco mais contexto pode impedir uma boa dose de cabeça coçar ...
David C. Rankin
1
Como devo escapar da despesa se algum conteúdo entre minhas tags EOF precisa ser expandido e outros não?
Jeanmichel Cote 23/09/2015
2
... basta usar a barra invertida na frente do$
Jeanmichel Cote 23/09
@ JeanmichelCote Não vejo uma opção melhor :-) Com cadeias regulares, você também pode considerar misturar citações como "$a"'$b'"$c", mas não há analógico aqui no AFAIK.
Ciro Santilli escreveu
25

Usando tee em vez de gato

Não é exatamente uma resposta à pergunta original, mas eu queria compartilhar isso de qualquer maneira: eu precisava criar um arquivo de configuração em um diretório que exigisse direitos de root.

O seguinte não funciona para esse caso:

$ sudo cat <<EOF >/etc/somedir/foo.conf
# my config file
foo=bar
EOF

porque o redirecionamento é tratado fora do contexto do sudo.

Acabei usando isso:

$ sudo tee <<EOF /etc/somedir/foo.conf >/dev/null
# my config file
foo=bar
EOF
Andreas Maier
fonte
no seu caso, use sudo bash -c 'cat << EOF> /etc/somedir/foo.conf # meu arquivo de configuração foo = bar EOF'
likewhoa
5

Uma pequena extensão para as respostas acima. O final >direciona a entrada para o arquivo, substituindo o conteúdo existente. No entanto, um uso particularmente conveniente é a seta dupla >>anexada, adicionando seu novo conteúdo ao final do arquivo, como em:

cat <<EOF >> /etc/fstab
data_server:/var/sharedServer/authority/cert /var/sharedFolder/sometin/authority/cert nfs
data_server:/var/sharedServer/cert   /var/sharedFolder/sometin/vsdc/cert nfs
EOF

Isso prolonga a sua situação fstabsem que você precise se preocupar em modificar acidentalmente qualquer conteúdo.

Lefty G Balogh
fonte
1

Isso não é necessariamente uma resposta para a pergunta original, mas um compartilhamento de alguns resultados dos meus próprios testes. Este:

<<test > print.sh
#!/bin/bash
echo \$PWD
echo $PWD
test

produzirá o mesmo arquivo que:

cat <<test > print.sh
#!/bin/bash
echo \$PWD
echo $PWD
test

Portanto, não vejo o objetivo de usar o comando cat.


fonte
2
qual shell? Eu testei com o bash 4.4 no Ubuntu 18.04, bem como o bash 3.2 no OSX. Ambos criaram um arquivo vazio ao apenas usar <<testsem cat <<test.
wisbucky
Isso funcionou para mim no Linux Mint 19 Tara no zsh
Geoff Langenderfer 03/02
0

Vale a pena notar que aqui os documentos também funcionam em loops de festa. Este exemplo mostra como obter a lista de colunas da tabela:

export postgres_db_name='my_db'
export table_name='my_table_name'

# start copy 
while read -r c; do test -z "$c" || echo $table_name.$c , ; done < <(cat << EOF | psql -t -q -d $postgres_db_name -v table_name="${table_name:-}"
SELECT column_name
FROM information_schema.columns
WHERE 1=1
AND table_schema = 'public'
AND table_name   =:'table_name'  ;
EOF
)
# stop copy , now paste straight into the bash shell ...

output: 
my_table_name.guid ,
my_table_name.id ,
my_table_name.level ,
my_table_name.seq ,

ou mesmo sem a nova linha

while read -r c; do test -z "$c" || echo $table_name.$c , | perl -ne 
's/\n//gm;print' ; done < <(cat << EOF | psql -t -q -d $postgres_db_name -v table_name="${table_name:-}"
 SELECT column_name
 FROM information_schema.columns
 WHERE 1=1
 AND table_schema = 'public'
 AND table_name   =:'table_name'  ;
 EOF
 )

 # output: daily_issues.guid ,daily_issues.id ,daily_issues.level ,daily_issues.seq ,daily_issues.prio ,daily_issues.weight ,daily_issues.status ,daily_issues.category ,daily_issues.name ,daily_issues.description ,daily_issues.type ,daily_issues.owner
Yordan Georgiev
fonte