Bagaimana cara "cat << EOF" bekerja di bash?

631

Saya perlu menulis skrip untuk memasukkan input multi-line ke suatu program ( psql).

Setelah sedikit googling, saya menemukan sintaks berikut berfungsi:

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

`pg_dump ----something`

update table .... statement ...;

END;
EOF

Ini benar membangun string multi-line (dari BEGIN;ke END;, inklusif) dan pipa itu sebagai input ke psql.

Tetapi saya tidak tahu bagaimana / mengapa itu bekerja, dapatkah seseorang menjelaskannya?

Saya merujuk terutama ke cat << EOF, saya tahu >output ke file, >>menambahkan file, <membaca input dari file.

Apa yang <<sebenarnya dilakukan?

Dan apakah ada halaman manual untuk itu?

Hasen
sumber
26
Itu mungkin penggunaan yang tidak berguna cat. Coba psql ... << EOF ... Lihat juga "di sini string". mywiki.wooledge.org/BashGuide/InputAndOutput?#Here_Strings
Dijeda hingga pemberitahuan lebih lanjut.
1
Saya terkejut ini bekerja dengan kucing tetapi tidak dengan gema. cat harus mengharapkan nama file sebagai stdin, bukan string char. psql << EOF terdengar logis, tetapi tidak dengan cara lain. Bekerja dengan kucing tetapi tidak dengan gema. Perilaku aneh. Ada petunjuk tentang itu?
Alex
Menjawab sendiri: cat tanpa parameter mengeksekusi dan mereplikasi ke output apa pun yang dikirim melalui input (stdin), maka menggunakan outputnya untuk mengisi file melalui>. Sebenarnya nama file yang dibaca sebagai parameter bukanlah aliran stdin.
Alex
@Alex echo hanya mencetak argumen command line-nya saat catmembaca stding (ketika di-pipe-kan) atau membaca file yang sesuai dengan args command line
The-null-Pointer-

Jawaban:

519

Ini disebut format heredoc untuk memberikan string ke stdin. Lihat https://en.wikipedia.org/wiki/Here_document#Unix_shells untuk detail lebih lanjut.


Dari man bash:

Di sini Dokumen

Jenis pengalihan ini memerintahkan shell untuk membaca input dari sumber saat ini sampai sebuah baris yang hanya berisi kata (tanpa trailing blank) terlihat.

Semua baris yang dibaca hingga titik itu kemudian digunakan sebagai input standar untuk suatu perintah.

Format di sini-dokumen adalah:

          <<[-]word
                  here-document
          delimiter

Tidak ada perluasan parameter, substitusi perintah, ekspansi aritmatika, atau ekspansi pathname dilakukan pada kata . Jika ada karakter dalam kata yang dikutip, pembatas adalah hasil dari penghapusan kutipan pada kata , dan garis-garis dalam dokumen di sini tidak diperluas. Jika kata tidak dikutip, semua baris dokumen di sini mengalami perluasan parameter, penggantian perintah, dan ekspansi aritmatika. Dalam kasus terakhir, urutan karakter \<newline>diabaikan, dan \harus digunakan untuk mengutip karakter \, $dan `.

Jika operator redirection <<-, maka semua karakter tab terkemuka dihapus dari jalur input dan garis yang mengandung pembatas . Hal ini memungkinkan dokumen di dalam skrip shell diindentasikan secara alami.

kennytm
sumber
12
Saya mengalami kesulitan menonaktifkan ekspansi variabel / parameter. Yang perlu saya lakukan adalah menggunakan "tanda kutip ganda" dan memperbaikinya! Terimakasih atas infonya!
Xeoncross
11
Mengenai <<-harap dicatat bahwa hanya karakter tab utama yang dilucuti - bukan karakter tab lunak. Ini adalah salah satu kasus langka ketika Anda benar-benar membutuhkan karakter tab. Jika sisa dokumen Anda menggunakan tab lunak, pastikan untuk menunjukkan karakter yang tidak terlihat dan (misalnya) menyalin dan menempelkan karakter tab. Jika Anda melakukannya dengan benar, penyorotan sintaks Anda harus menangkap pembatas akhir dengan benar.
trkoch
1
Saya tidak melihat bagaimana jawaban ini lebih bermanfaat daripada jawaban di bawah ini. Ini hanya memuntahkan informasi yang dapat ditemukan di tempat lain (yang kemungkinan sudah diperiksa)
BrDaHa
@ BrDaHa, mungkin tidak. Mengapa pertanyaan? karena upvotes? itu adalah satu-satunya selama beberapa tahun. itu terlihat dengan membandingkan tanggal.
Alexei Martianov
500

The cat <<EOFsintaks ini sangat berguna ketika bekerja dengan teks multi-baris dalam Bash, misalnya. saat menetapkan string multi-baris ke variabel shell, file atau pipa.

Contoh cat <<EOFpenggunaan sintaks di Bash:

1. Tetapkan string multi-baris ke variabel shell

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

The $sqlvariabel sekarang memegang karakter baru-line juga. Anda dapat memverifikasi dengan echo -e "$sql".

2. Pass string multi-line ke file di Bash

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

The print.shFile sekarang berisi:

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

3. Pass string multi-line ke pipa di Bash

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

The b.txtfile berisi bardan bazgaris. Output yang sama dicetak ke stdout.

Vojtech Vitek
sumber
1. 1 dan 3 dapat dilakukan tanpa kucing; 2. Contoh 1 dapat dilakukan dengan string multi-line sederhana
Daniel Alder
269

Dalam kasus Anda, "EOF" dikenal sebagai "Tag Di Sini". Pada dasarnya <<Herememberitahu shell bahwa Anda akan memasukkan string multiline sampai "tag" Here. Anda dapat memberi nama tag ini seperti yang Anda inginkan, sering EOFatau STOP.

Beberapa aturan tentang tag Di Sini:

  1. Tag dapat berupa string, huruf besar atau huruf kecil, meskipun sebagian besar orang menggunakan huruf besar berdasarkan kebiasaan.
  2. Tag tidak akan dianggap sebagai tag Di Sini jika ada kata lain di baris itu. Dalam hal ini, itu hanya akan dianggap sebagai bagian dari string. Tag harus dengan sendirinya pada baris yang terpisah, untuk dianggap sebagai tag.
  3. Tag tidak boleh memiliki spasi awal atau jejak di baris itu untuk dianggap sebagai tag. Kalau tidak, itu akan dianggap sebagai bagian dari string.

contoh:

$ 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
sumber
31
ini adalah jawaban aktual terbaik ... Anda mendefinisikan keduanya dan dengan jelas menyatakan tujuan utama penggunaan alih-alih teori terkait ... yang penting tetapi tidak perlu ... terima kasih - sangat membantu
oemb1905
5
@edelans Anda harus menambahkan bahwa ketika <<-digunakan tab terkemuka tidak akan mencegah tag dari dikenali
The-null-Pointer-
1
jawaban Anda mengklik saya pada "Anda akan memasukkan string multiline"
Kalkulus
79

POSIX 7

kennytm dikutip man bash, tetapi sebagian besar juga POSIX 7: http://pubs.opengroup.org/onlinepubs/9699919799/utilities/V3_chap02.html#tag_18_07_04 :

Operator pengalihan "<<" dan "<< -" keduanya memungkinkan pengalihan garis yang terkandung dalam file input shell, yang dikenal sebagai "dokumen-sini", ke input perintah.

Dokumen di sini akan diperlakukan sebagai satu kata yang dimulai setelah kata berikutnya dan berlanjut sampai ada garis yang hanya berisi pembatas dan a, tanpa karakter di antaranya. Kemudian di sini dokumen selanjutnya dimulai, jika ada. Formatnya adalah sebagai berikut:

[n]<<word
    here-document
delimiter

di mana opsional n menunjukkan nomor deskriptor file. Jika nomornya dihilangkan, dokumen di sini mengacu pada input standar (file descriptor 0).

Jika ada karakter dalam kata yang dikutip, pembatas akan dibentuk dengan melakukan penghapusan kutipan pada kata, dan baris dokumen di sini tidak akan diperluas. Jika tidak, pembatas akan menjadi kata itu sendiri.

Jika tidak ada karakter dalam kata yang dikutip, semua baris dokumen di sini harus diperluas untuk ekspansi parameter, penggantian perintah, dan ekspansi aritmatika. Dalam hal ini, in input berperilaku sebagai tanda kutip ganda di dalam (lihat Kutipan Ganda). Namun, karakter kutipan ganda ('"') tidak akan diperlakukan secara khusus dalam dokumen-dokumen di sini, kecuali ketika kutipan ganda muncul dalam" $ () "," `` ", atau" $ {} ".

Jika simbol redirection adalah "<< -", semua <tab>karakter utama harus dilucuti dari jalur input dan garis yang mengandung pembatas trailing. Jika lebih dari satu operator "<<" atau "<< -" ditentukan pada satu baris, dokumen di sini yang terkait dengan operator pertama harus disediakan terlebih dahulu oleh aplikasi dan harus dibaca terlebih dahulu oleh shell.

Ketika dokumen di sini dibaca dari perangkat terminal dan shell bersifat interaktif, ia harus menulis konten variabel PS2, diproses seperti yang dijelaskan dalam Variabel Shell, menjadi kesalahan standar sebelum membaca setiap baris input hingga pembatas telah dikenali.

Contohnya

Beberapa contoh belum diberikan.

Kutipan mencegah ekspansi parameter

Tanpa kutipan:

a=0
cat <<EOF
$a
EOF

Keluaran:

0

Dengan kutipan:

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

atau (jelek tapi valid):

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

Output:

$a

Tanda hubung menghapus tab utama

Tanpa tanda hubung:

cat <<EOF
<tab>a
EOF

di mana <tab>tab literal, dan dapat dimasukkan denganCtrl + V <tab>

Keluaran:

<tab>a

Dengan tanda hubung:

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

Keluaran:

a

Ini ada tentu saja sehingga Anda dapat indentasi catseperti kode di sekitarnya, yang lebih mudah dibaca dan dipelihara. Misalnya:

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

Sayangnya, ini tidak berfungsi untuk karakter luar angkasa: POSIX tabindentasi disukai di sini. Astaga.

Ciro Santilli 郝海东 冠状 病 六四 事件 法轮功
sumber
Dalam contoh terakhir Anda membahas <<-dan <tab>a, harus dicatat bahwa tujuannya adalah untuk memungkinkan lekukan normal kode dalam skrip sambil memungkinkan teks heredoc disajikan untuk proses penerimaan dimulai pada kolom 0. Ini adalah fitur yang tidak terlalu umum terlihat dan sedikit lebih banyak konteks dapat mencegah banyak hal menggaruk kepala ...
David C. Rankin
1
Bagaimana saya harus menghindari expension jika beberapa konten di antara tag EOF saya perlu diperluas dan beberapa tidak?
Jeanmichel Cote
2
... cukup gunakan garis miring terbalik di depan$
Jeanmichel Cote
@JeanmichelCote Saya tidak melihat opsi yang lebih baik :-) Dengan string biasa Anda juga dapat mempertimbangkan mencampur kutipan seperti "$a"'$b'"$c", tetapi tidak ada analog di sini AFAIK.
Ciro Santilli 郝海东 冠状 病 六四 事件 法轮功
25

Menggunakan tee bukan kucing

Tidak persis sebagai jawaban untuk pertanyaan awal, tetapi saya tetap ingin berbagi ini: Saya harus membuat file konfigurasi di direktori yang memerlukan hak root.

Berikut ini tidak berfungsi untuk kasus itu:

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

karena pengalihan ditangani di luar konteks sudo.

Saya akhirnya menggunakan ini sebagai gantinya:

$ sudo tee <<EOF /etc/somedir/foo.conf >/dev/null
# my config file
foo=bar
EOF
Andreas Maier
sumber
dalam kasus Anda gunakan sudo bash -c 'cat << EOF> /etc/somedir/foo.conf # file config saya foo = bar EOF'
likewhoa
5

Sedikit ekstensi untuk jawaban di atas. Trailing >mengarahkan input ke file, menimpa konten yang ada. Namun, satu penggunaan khusus yang nyaman adalah panah ganda >>yang ditambahkan, menambahkan konten baru Anda ke akhir file, seperti pada:

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

Ini meluas fstabtanpa Anda harus khawatir tentang memodifikasi kontennya secara tidak sengaja.

Lefty G Balogh
sumber
1

Ini tidak selalu merupakan jawaban untuk pertanyaan awal, tetapi berbagi beberapa hasil dari pengujian saya sendiri. Ini:

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

akan menghasilkan file yang sama dengan:

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

Jadi, saya tidak melihat gunanya menggunakan perintah cat.


sumber
2
shell yang mana? Saya diuji dengan bash 4.4 di Ubuntu 18.04 serta bash 3.2 di OSX. Keduanya membuat file kosong ketika hanya menggunakan <<testtanpa cat <<test.
wisbucky
Ini bekerja untuk saya di LInux Mint 19 Tara di zsh
Geoff Langenderfer
0

Perlu dicatat bahwa di sini dokumen juga berfungsi dalam bash loop. Contoh ini menunjukkan cara mendapatkan daftar kolom tabel:

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 ,

atau bahkan tanpa baris baru

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
sumber