Come funziona "cat << EOF" in bash?


631

Avevo bisogno di scrivere uno script per inserire input multilinea in un programma ( psql).

Dopo un po 'di ricerche su google, ho trovato la seguente sintassi funziona:

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

`pg_dump ----something`

update table .... statement ...;

END;
EOF

Questo costruisce correttamente la stringa multilinea (da BEGIN;a END;, inclusiva) e la reindirizza come input a psql.

Ma non ho idea di come / perché funzioni, qualcuno può spiegare?

Mi riferisco principalmente a cat << EOF, conosco l' >output in un file, >>accoda un file, <legge l'input dal file.

Che cosa fa <<esattamente?

E c'è una pagina man per questo?


26
Questo è probabilmente un uso inutile di cat. Prova psql ... << EOF ... Vedi anche "qui stringhe". mywiki.wooledge.org/BashGuide/InputAndOutput?#Here_Strings
In pausa fino a ulteriore avviso.

1
Sono sorpreso che funzioni con il gatto ma non con l'eco. cat dovrebbe aspettarsi un nome di file come stdin, non una stringa di caratteri. psql << EOF sembra logico, ma non altrimenti. Funziona con il gatto ma non con l'eco. Strano comportamento. Qualche idea a riguardo?
Alex

Rispondere a me stesso: cat senza parametri esegue e si replica all'output qualunque sia l'invio tramite input (stdin), quindi utilizzando il suo output per riempire il file tramite>. In effetti un nome file letto come parametro non è un flusso stdin.
Alex

@Alex echo stampa solo gli argomenti della sua riga di comando mentre catlegge stding (quando viene reindirizzato ad esso) o legge un file che corrisponde alla sua riga di comando args
The-null-Pointer-

Risposte:


519

Questo è chiamato formato ereditario per fornire una stringa in stdin. Vedi https://en.wikipedia.org/wiki/Here_document#Unix_shells per maggiori dettagli.


Da man bash:

Qui i documenti

Questo tipo di reindirizzamento indica alla shell di leggere l'input dalla sorgente corrente fino a quando non viene visualizzata una riga contenente solo una parola (senza spazi vuoti finali).

Tutte le righe lette fino a quel punto vengono quindi utilizzate come input standard per un comando.

Il formato dei documenti qui è:

          <<[-]word
                  here-document
          delimiter

Nessuna espansione di parametro, sostituzione di comando, espansione aritmetica o espansione del percorso viene eseguita su word . Se vengono citati caratteri in una parola , il delimitatore è il risultato della rimozione della citazione in una parola e le righe nel documento qui non vengono espanse. Se la parola non è quotata, tutte le righe del documento qui sono soggette all'espansione dei parametri, alla sostituzione dei comandi e all'espansione aritmetica. In quest'ultimo caso, la sequenza di caratteri \<newline>viene ignorata e \deve essere utilizzato per citare i personaggi \, $e `.

Se l'operatore di reindirizzamento lo è <<-, tutti i caratteri di tabulazione principali vengono rimossi dalle righe di input e dalla riga che contiene il delimitatore . Ciò consente di indentare in modo naturale i documenti qui contenuti negli script shell.


12
Stavo avendo difficoltà a disabilitare l'espansione di variabili / parametri. Tutto quello che dovevo fare era usare le "virgolette doppie" e questo l'ha risolto! Grazie per le informazioni!
Xeoncross,

11
Per quanto riguarda la <<-nota, vengono eliminati solo i caratteri di tabulazione iniziali, non i caratteri di tabulazione soft. Questo è uno di quei rari casi in cui è effettivamente necessario il carattere di tabulazione. Se il resto del documento utilizza schede morbide, assicurati di mostrare i caratteri invisibili e (ad esempio) di copiare e incollare un carattere di scheda. Se lo fai nel modo giusto, l'evidenziazione della sintassi dovrebbe catturare correttamente il delimitatore finale.
Trkoch,

1
Non vedo come questa risposta sia più utile di quelle qui sotto. Rigurgita semplicemente le informazioni che possono essere trovate in altri luoghi (che probabilmente sono già state verificate)
BrDaHa,

@BrDaHa, forse no. Perché la domanda? a causa dei voti? è stato l'unico per diversi anni. si vede confrontando le date.
Alexei Martianov,

500

La cat <<EOFsintassi è molto utile quando si lavora con testo multilinea in Bash, ad es. quando si assegna una stringa multilinea a una variabile di shell, a un file oa una pipe.

Esempi di cat <<EOFutilizzo della sintassi in Bash:

1. Assegnare una stringa multilinea a una variabile di shell

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

La $sqlvariabile ora contiene anche i caratteri di nuova riga. Puoi verificare con echo -e "$sql".

2. Passare la stringa multilinea a un file in Bash

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

Il print.shfile ora contiene:

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

3. Passare una stringa multilinea a una pipe in Bash

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

Il b.txtfile contiene bare bazrighe. Lo stesso output viene stampato su stdout.


1. 1 e 3 possono essere eseguiti senza gatto; 2. L'esempio 1 può essere fatto con una semplice stringa multilinea
Daniel Alder,

269

Nel tuo caso, "EOF" è noto come "Here Tag". Fondamentalmente <<Heredice alla shell che stai per inserire una stringa multilinea fino al "tag" Here. Puoi nominare questo tag come vuoi, spesso EOFo STOP.

Alcune regole sui tag Here:

  1. Il tag può essere qualsiasi stringa, maiuscola o minuscola, sebbene la maggior parte delle persone utilizzi maiuscole per convenzione.
  2. Il tag non verrà considerato come tag Here se ci sono altre parole in quella riga. In questo caso, sarà semplicemente considerato parte della stringa. Il tag dovrebbe essere da solo su una riga separata, per essere considerato un tag.
  3. Il tag non dovrebbe avere spazi iniziali o finali in quella riga per essere considerato un tag. Altrimenti verrà considerato come parte della stringa.

esempio:

$ 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

31
questa è la migliore risposta effettiva ... tu definisci entrambi e dichiari chiaramente lo scopo primario dell'uso invece della teoria correlata ... che è importante ma non necessario ... grazie - super utile
oemb1905

5
@edelans devi aggiungere che quando <<-viene usato la tab iniziale non impedirà il riconoscimento del tag
The-null-Pointer-

1
la tua risposta mi ha cliccato su "stai per inserire una stringa multilinea"
Calcolo

79

POSIX 7

kennytm citato man bash, ma la maggior parte di questo è anche POSIX 7: http://pubs.opengroup.org/onlinepubs/9699919799/utilities/V3_chap02.html#tag_18_07_04 :

Gli operatori di reindirizzamento "<<" e "<< -" consentono entrambi il reindirizzamento delle righe contenute in un file di input della shell, noto come "documento qui", all'input di un comando.

Il documento qui deve essere trattato come una singola parola che inizia dopo la successiva e continua fino a quando non vi è una riga contenente solo il delimitatore e una, senza caratteri in mezzo. Quindi inizia il prossimo documento qui, se ce n'è uno. Il formato è il seguente:

[n]<<word
    here-document
delimiter

dove n opzionale rappresenta il numero del descrittore di file. Se il numero viene omesso, il documento qui si riferisce all'input standard (descrittore di file 0).

Se viene citato un carattere in una parola, il delimitatore deve essere formato eseguendo la rimozione della citazione in parola e le righe del documento qui non devono essere espanse. Altrimenti, il delimitatore deve essere la parola stessa.

Se non vengono citati caratteri in parola, tutte le righe del documento qui devono essere espanse per l'espansione dei parametri, la sostituzione dei comandi e l'espansione aritmetica. In questo caso, l'in input si comporta come le doppie virgolette interne (vedere le virgolette doppie). Tuttavia, il carattere tra virgolette doppie ('"') non deve essere trattato in modo particolare all'interno di un documento qui, tranne quando la virgoletta doppia appare tra" $ () "," `` "o" $ {} ".

Se il simbolo di reindirizzamento è "<< -", tutti i <tab>caratteri iniziali devono essere rimossi dalle righe di input e dalla riga contenente il delimitatore finale. Se su una riga è specificato più di un operatore "<<" o "<< -", il documento qui associato al primo operatore deve essere fornito per primo dall'applicazione e deve essere letto per primo dalla shell.

Quando un documento qui viene letto da un dispositivo terminale e la shell è interattiva, deve scrivere il contenuto della variabile PS2, elaborata come descritto in Variabili di shell, a un errore standard prima di leggere ogni riga di input fino a quando il delimitatore non viene riconosciuto.

Esempi

Alcuni esempi non ancora forniti.

Le virgolette impediscono l'espansione dei parametri

Senza virgolette:

a=0
cat <<EOF
$a
EOF

Produzione:

0

Con virgolette:

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

o (brutto ma valido):

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

Uscite:

$a

Il trattino rimuove le schede iniziali

Senza trattino:

cat <<EOF
<tab>a
EOF

dove <tab>è una scheda letterale e può essere inserita conCtrl + V <tab>

Produzione:

<tab>a

Con trattino:

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

Produzione:

a

Questo esiste ovviamente in modo che tu possa rientrare catcome il codice circostante, che è più facile da leggere e mantenere. Per esempio:

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

Sfortunatamente, questo non funziona per i caratteri spaziali: POSIX ha favorito il tabrientro qui. Yikes.


Nel tuo ultimo esempio in cui si discute <<-e <tab>a, va notato che lo scopo era quello di consentire il rientro normale del codice all'interno dello script consentendo al testo ereditario presentato al processo di ricezione di iniziare nella colonna 0. È una caratteristica non troppo comune e un po ' più contesto può impedire una buona dose di grattarsi la testa ...
David C. Rankin,

1
Come devo evitare l'espansione se è necessario espandere parte del contenuto tra i miei tag EOF e altri no?
Jeanmichel Cote,

2
... basta usare la barra rovesciata di fronte al$
Jeanmichel Cote il

@JeanmichelCote Non vedo un'opzione migliore :-) Con le stringhe regolari puoi anche considerare di mescolare citazioni come "$a"'$b'"$c", ma qui non c'è analogo AFAIK.
Ciro Santilli 23 冠状 病 六四 事件 法轮功

25

Usando il tee invece del gatto

Non esattamente come una risposta alla domanda originale, ma volevo comunque condividerla: avevo la necessità di creare un file di configurazione in una directory che richiedesse i diritti di root.

Quanto segue non funziona per quel caso:

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

perché il reindirizzamento viene gestito al di fuori del contesto sudo.

Ho finito per usare questo invece:

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

nel tuo caso usa sudo bash -c 'cat << EOF> /etc/somedir/foo.conf # il mio file di configurazione foo = bar EOF'
likewhoa

5

Una piccola estensione alle risposte sopra. Il trailing >indirizza l'input nel file, sovrascrivendo il contenuto esistente. Tuttavia, un uso particolarmente conveniente è la doppia freccia >>che viene aggiunta, aggiungendo il nuovo contenuto alla fine del file, come in:

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

Questo si estende fstabsenza che tu debba preoccuparti di modificare accidentalmente alcuno dei suoi contenuti.


1

Questa non è necessariamente una risposta alla domanda originale, ma una condivisione di alcuni risultati dei miei test. Questo:

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

produrrà lo stesso file di:

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

Quindi, non vedo il punto di usare il comando cat.


2
quale shell? Ho provato con bash 4.4 su Ubuntu 18.04 e bash 3.2 su OSX. Entrambi hanno creato un file vuoto quando si utilizza solo <<testsenza cat <<test.
Wisbucky,

Questo ha funzionato per me su LInux Mint 19 Tara in zsh
Geoff Langenderfer,

0

Vale la pena notare che qui i documenti funzionano anche nei loop bash. Questo esempio mostra come ottenere l'elenco delle colonne della tabella:

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 ,

o anche senza la nuova linea

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
Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.