Comment fonctionne «cat << EOF» en bash?

632

J'avais besoin d'écrire un script pour entrer une entrée multiligne dans un programme ( psql).

Après un peu de recherche sur Google, j'ai trouvé les travaux de syntaxe suivants:

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

`pg_dump ----something`

update table .... statement ...;

END;
EOF

Cela construit correctement la chaîne multiligne (de BEGIN;à END;, inclus) et la redirige en tant qu'entrée vers psql.

Mais je n'ai aucune idée de comment / pourquoi cela fonctionne, quelqu'un peut-il expliquer?

Je me réfère principalement à cat << EOF, je connais les >sorties d'un fichier, les >>ajoute à un fichier, <lit les entrées du fichier.

Que fait <<exactement?

Et y a-t-il une page de manuel pour cela?

hasen
la source
26
C'est probablement une utilisation inutile de cat. Essayez psql ... << EOF ... Voir aussi "ici les chaînes". mywiki.wooledge.org/BashGuide/InputAndOutput?#Here_Strings
pause jusqu'à nouvel ordre.
1
Je suis surpris que cela fonctionne avec le chat mais pas avec l'écho. cat devrait attendre un nom de fichier comme stdin, pas une chaîne de caractères. psql << EOF semble logique, mais pas autrement. Fonctionne avec chat mais pas avec écho. Comportement étrange. Une idée à ce sujet?
Alex
Répondre à moi-même: cat sans paramètres s'exécute et se réplique à la sortie quel que soit envoyer via entrée (stdin), donc utiliser sa sortie pour remplir le fichier via>. En fait, un nom de fichier lu comme paramètre n'est pas un flux stdin.
Alex
@Alex echo imprime simplement ses arguments de ligne de commande pendant qu'il catlit stding (lorsqu'il y est canalisé) ou lit un fichier qui correspond à ses arguments de ligne de commande
The-null-Pointer -

Réponses:

519

C'est ce qu'on appelle le format heredoc pour fournir une chaîne dans stdin. Voir https://en.wikipedia.org/wiki/Here_document#Unix_shells pour plus de détails.


De man bash:

Ici Documents

Ce type de redirection demande au shell de lire les entrées de la source actuelle jusqu'à ce qu'une ligne contenant uniquement un mot (sans espaces de fin) soit vue.

Toutes les lignes lues jusqu'à ce point sont ensuite utilisées comme entrée standard pour une commande.

Le format des documents ici est:

          <<[-]word
                  here-document
          delimiter

Aucune expansion de paramètre, substitution de commande, expansion arithmétique ou expansion de nom de chemin n'est effectuée sur Word . Si des caractères dans word sont cités, le délimiteur est le résultat de la suppression des guillemets sur word , et les lignes du document ici ne sont pas développées. Si le mot n'est pas cité, toutes les lignes du document ici sont soumises à l'expansion des paramètres, à la substitution de commandes et à l'expansion arithmétique. Dans ce dernier cas, la séquence de caractères \<newline>est ignoré et \doit être utilisé pour protéger les caractères \, $et `.

Si l'opérateur de redirection l'est <<-, tous les caractères de tabulation en tête sont supprimés des lignes d'entrée et de la ligne contenant le délimiteur . Cela permet aux documents ici dans les scripts shell d'être indentés de manière naturelle.

kennytm
la source
12
J'avais le plus de mal à désactiver l'expansion des variables / paramètres. Tout ce que je devais faire était d'utiliser des "guillemets doubles" et cela l'a corrigé! Merci pour l'info!
Xeoncross
11
Concernant, <<-veuillez noter que seuls les caractères de tabulation principaux sont supprimés - pas les caractères de tabulation logicielle. C'est l'un de ces rares cas où vous avez réellement besoin du caractère de tabulation. Si le reste de votre document utilise des onglets souples, assurez-vous d'afficher les caractères invisibles et (par exemple) copiez et collez un caractère de tabulation. Si vous le faites correctement, la mise en évidence de votre syntaxe devrait correctement intercepter le délimiteur de fin.
trkoch
1
Je ne vois pas en quoi cette réponse est plus utile que celles ci-dessous. Il régurgite simplement les informations qui peuvent être trouvées dans d'autres endroits (qui ont probablement déjà été vérifiées)
BrDaHa
@BrDaHa, ce n'est peut-être pas le cas. Pourquoi cette question? à cause des votes positifs? il était le seul depuis plusieurs années. on le voit en comparant les dates.
Alexei Martianov
500

La cat <<EOFsyntaxe est très utile lorsque vous travaillez avec du texte multiligne dans Bash, par exemple. lors de l'affectation d'une chaîne multiligne à une variable shell, un fichier ou un tube.

Exemples d' cat <<EOFutilisation de la syntaxe dans Bash:

1. Affectez une chaîne multiligne à une variable shell

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

La $sqlvariable contient désormais également les caractères de nouvelle ligne. Vous pouvez vérifier avec echo -e "$sql".

2. Passer une chaîne multi-lignes à un fichier dans Bash

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

Le print.shfichier contient désormais:

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

3. Passer une chaîne multi-ligne à un tuyau dans Bash

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

Le b.txtfichier contient baret des bazlignes. La même sortie est imprimée sur stdout.

Vojtech Vitek
la source
1. 1 et 3 peuvent se faire sans chat; 2. L'exemple 1 peut être fait avec une simple chaîne multi-lignes
Daniel Alder
270

Dans votre cas, "EOF" est appelé "Here Tag". Indique fondamentalement <<Hereau shell que vous allez entrer une chaîne multiligne jusqu'au "tag" Here. Vous pouvez nommer cette balise comme vous le souhaitez, c'est souvent EOFou STOP.

Quelques règles concernant les balises Here:

  1. La balise peut être n'importe quelle chaîne, en majuscules ou en minuscules, bien que la plupart des gens utilisent des majuscules par convention.
  2. La balise ne sera pas considérée comme une balise Here s'il y a d'autres mots sur cette ligne. Dans ce cas, il sera simplement considéré comme faisant partie de la chaîne. La balise doit être seule sur une ligne distincte, pour être considérée comme une balise.
  3. La balise ne doit avoir aucun espace de début ou de fin sur cette ligne pour être considérée comme une balise. Sinon, il sera considéré comme faisant partie de la chaîne.

exemple:

$ 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
la source
31
c'est la meilleure réponse réelle ... vous définissez les deux et énoncez clairement le but principal de l'utilisation au lieu de la théorie connexe ... ce qui est important mais pas nécessaire ... merci - super utile
oemb1905
5
@edelans vous devez ajouter que lorsque le <<-premier onglet est utilisé n'empêchera pas le tag d'être reconnu
The-null-Pointer-
1
votre réponse m'a cliqué sur "vous allez entrer une chaîne multiligne"
Calcul
79

POSIX 7

kennytm cité man bash, mais l'essentiel est également POSIX 7: http://pubs.opengroup.org/onlinepubs/9699919799/utilities/V3_chap02.html#tag_18_07_04 :

Les opérateurs de redirection "<<" et "<< -" permettent tous deux de rediriger des lignes contenues dans un fichier d'entrée shell, appelé "ici-document", vers l'entrée d'une commande.

Le document ici doit être traité comme un seul mot qui commence après le suivant et continue jusqu'à ce qu'il y ait une ligne contenant uniquement le délimiteur et a, sans aucun caractère entre les deux. Ensuite, le prochain document ici commence, s'il y en a un. Le format est le suivant:

[n]<<word
    here-document
delimiter

où le n facultatif représente le numéro de descripteur de fichier. Si le nombre est omis, le document ici fait référence à l'entrée standard (descripteur de fichier 0).

Si un caractère dans word est cité, le délimiteur doit être formé en effectuant la suppression des guillemets sur word, et les lignes du document ne doivent pas être développées. Sinon, le délimiteur sera le mot lui-même.

Si aucun caractère dans le mot n'est cité, toutes les lignes du document ici doivent être développées pour l'expansion des paramètres, la substitution de commandes et l'expansion arithmétique. Dans ce cas, l'entrée se comporte comme des guillemets internes (voir Double-guillemets). Cependant, le caractère guillemet double ('"') ne doit pas être traité spécialement dans un document ici, sauf lorsque le guillemet double apparaît dans" $ () "," `` "ou" $ {} ".

Si le symbole de redirection est "<< -", tous les <tab>caractères de tête doivent être supprimés des lignes d'entrée et de la ligne contenant le délimiteur de fin. Si plusieurs opérateurs "<<" ou "<< -" sont spécifiés sur une ligne, le document associé au premier opérateur doit être fourni en premier par l'application et doit être lu en premier par le shell.

Lorsqu'un document ici est lu à partir d'un terminal et que le shell est interactif, il doit écrire le contenu de la variable PS2, traité comme décrit dans Variables du shell, dans l'erreur standard avant de lire chaque ligne d'entrée jusqu'à ce que le délimiteur ait été reconnu.

Exemples

Quelques exemples pas encore donnés.

Les citations empêchent l'expansion des paramètres

Sans citations:

a=0
cat <<EOF
$a
EOF

Production:

0

Avec des citations:

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

ou (moche mais valide):

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

Les sorties:

$a

Le tiret supprime les onglets de tête

Sans trait d'union:

cat <<EOF
<tab>a
EOF

<tab>est un onglet littéral, et peut être inséré avecCtrl + V <tab>

Production:

<tab>a

Avec trait d'union:

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

Production:

a

Cela existe bien sûr pour que vous puissiez mettre en retrait votre catcode similaire, ce qui est plus facile à lire et à maintenir. Par exemple:

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

Malheureusement, cela ne fonctionne pas pour les caractères d'espace: POSIX a favorisé l' tabindentation ici. Oui.

Ciro Santilli 郝海东 冠状 病 六四 事件 法轮功
la source
Dans votre dernier exemple de discussion sur <<-et <tab>a, il convient de noter que le but était de permettre une indentation normale du code dans le script tout en permettant au texte hérédoc présenté au processus de réception de commencer dans la colonne 0. C'est une fonctionnalité peu courante et un peu plus de contexte peut empêcher de se gratter la tête ...
David C. Rankin
1
Comment devrais-je échapper à l'expansion si une partie du contenu entre mes balises EOF doit être développée et d'autres non?
Jeanmichel Cote
2
... il suffit d'utiliser la barre oblique inverse devant le$
Jeanmichel Cote
@JeanmichelCote Je ne vois pas de meilleure option :-) Avec des chaînes régulières, vous pouvez également envisager de mélanger des citations comme "$a"'$b'"$c", mais il n'y a pas d'analogue ici AFAIK.
Ciro Santilli 郝海东 冠状 病 六四 事件 法轮功
25

Utiliser un tee au lieu d'un chat

Pas exactement comme réponse à la question d'origine, mais je voulais quand même la partager: j'avais besoin de créer un fichier de configuration dans un répertoire qui nécessitait des droits root.

Ce qui suit ne fonctionne pas dans ce cas:

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

car la redirection est gérée en dehors du contexte sudo.

J'ai fini par utiliser ceci à la place:

$ sudo tee <<EOF /etc/somedir/foo.conf >/dev/null
# my config file
foo=bar
EOF
Andreas Maier
la source
dans votre cas, utilisez sudo bash -c 'cat << EOF> /etc/somedir/foo.conf # mon fichier de configuration foo = bar EOF'
likewhoa
5

Une petite extension des réponses ci-dessus. La fin >dirige l'entrée dans le fichier, écrasant le contenu existant. Cependant, une utilisation particulièrement pratique est la double flèche >>qui s'ajoute, ajoutant votre nouveau contenu à la fin du fichier, comme dans:

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

Cela étend votre fstabsans que vous ayez à vous soucier de modifier accidentellement l'un de ses contenus.

Lefty G Balogh
la source
1

Ce n'est pas nécessairement une réponse à la question d'origine, mais un partage de certains résultats de mes propres tests. Cette:

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

produira le même fichier que:

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

Donc, je ne vois pas l'intérêt d'utiliser la commande cat.


la source
2
quelle coquille? J'ai testé avec bash 4.4 sur Ubuntu 18.04 ainsi que bash 3.2 sur OSX. Les deux ont créé un fichier vide lors de leur utilisation <<testsans cat <<test.
wisbucky
Cela a fonctionné pour moi sur LInux Mint 19 Tara in zsh
Geoff Langenderfer
0

Il convient de noter qu'ici, les documents fonctionnent également dans les boucles bash. Cet exemple montre comment obtenir la liste des colonnes du tableau:

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 même sans la nouvelle ligne

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
la source