“ cat << EOF” ทำงานอย่างไรในการทุบตี?


629

ฉันต้องการเขียนสคริปต์เพื่อป้อนอินพุตหลายบรรทัดไปยังโปรแกรม ( psql)

หลังจาก googling ไปเล็กน้อยฉันพบว่าไวยากรณ์ต่อไปนี้ใช้งานได้:

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

`pg_dump ----something`

update table .... statement ...;

END;
EOF

นี้ได้อย่างถูกต้องสร้างสตริงหลายคู่สาย (จากBEGIN;การEND;รวม) และท่อมันเป็น input psqlไปยัง

แต่ฉันไม่รู้ว่ามันทำงานได้ยังไงบางคนช่วยอธิบายหน่อยได้ไหม?

ฉันหมายถึงส่วนใหญ่cat << EOFฉันรู้ว่าการ>ส่งออกไปยังไฟล์>>ผนวกกับไฟล์<อ่านอินพุตจากไฟล์

ทำอะไร<<กันแน่

และมีหน้าคนสำหรับมันหรือไม่?


26
catที่อาจใช้ไร้ประโยชน์ ลองpsql ... << EOF ... ดูด้วย "สายอักขระที่นี่" mywiki.wooledge.org/BashGuide/InputAndOutput?#Here_Strings
หยุดชั่วคราวจนกว่าจะมีประกาศเพิ่มเติม

1
ฉันประหลาดใจที่มันใช้งานได้กับแมว แต่ไม่ใช่กับเสียงสะท้อน cat ควรคาดหวังชื่อไฟล์ว่า stdin ไม่ใช่สตริงอักขระ char psql << EOF ฟังดูสมเหตุสมผล แต่ไม่ใช่ othewise ใช้งานได้กับแมว แต่ไม่สามารถใช้กับเสียงสะท้อนได้ พฤติกรรมแปลก ๆ เบาะแสใด ๆ เกี่ยวกับที่?
Alex

ตอบตัวเอง: cat ที่ไม่มีพารามิเตอร์เรียกใช้งานและทำซ้ำกับเอาต์พุตที่ส่งผ่านอินพุต (stdin) ดังนั้นการใช้เอาต์พุตเพื่อเติมไฟล์ผ่าน> ในความเป็นจริงชื่อไฟล์ที่อ่านเป็นพารามิเตอร์ไม่ใช่สตรีม stdin
Alex

@Alex echo เพียงพิมพ์อาร์กิวเมนต์บรรทัดคำสั่งในขณะที่catอ่าน stding (เมื่อไพพ์ไป) หรืออ่านไฟล์ที่สอดคล้องกับ args ของบรรทัดคำสั่ง
The-null-Pointer-

คำตอบ:


517

สิ่งนี้เรียกว่ารูปแบบheredocเพื่อให้สตริงเป็น stdin ดูhttps://en.wikipedia.org/wiki/Here_document#Unix_shellsสำหรับรายละเอียดเพิ่มเติม


จากman bash:

ที่นี่เอกสาร

การเปลี่ยนเส้นทางประเภทนี้สั่งให้เชลล์อ่านอินพุตจากแหล่งข้อมูลปัจจุบันจนกว่าจะเห็นบรรทัดที่มีคำเพียงคำเดียว (ที่ไม่มีช่องว่างต่อท้าย)

บรรทัดทั้งหมดที่อ่านจนถึงจุดนั้นจะถูกใช้เป็นอินพุตมาตรฐานสำหรับคำสั่ง

รูปแบบของเอกสารที่นี่คือ:

          <<[-]word
                  here-document
          delimiter

ไม่มีการขยายตัวพารามิเตอร์แทนคำสั่งขยายตัวทางคณิตศาสตร์หรือการขยายตัวของพา ธ ที่จะดำเนินการใน คำ หากอักขระใด ๆ ในคำถูกยกมา ตัวคั่นเป็นผลมาจากการลบคำพูดในคำและบรรทัดในที่นี่เอกสารจะไม่ขยาย หากคำไม่ได้อยู่ในเครื่องหมายคำพูดบรรทัดทั้งหมดของ here-document จะอยู่ภายใต้การขยายพารามิเตอร์การทดแทนคำสั่งและการขยายเลขคณิต ในกรณีหลังลำดับตัวอักษรที่\<newline>ถูกละเว้นและ\ต้องใช้ในการพูดของตัวละคร\, และ$`

หากผู้ประกอบการเปลี่ยนเส้นทางเป็น<<-แล้วทั้งหมดอักขระแท็บชั้นนำที่ถอดออกมาจากเส้นที่นำเข้าและสายที่มีตัวคั่น สิ่งนี้ช่วยให้เอกสารที่นี่ภายในเชลล์สคริปท์ถูกเยื้องในรูปแบบธรรมชาติ


12
ฉันมีเวลาที่ยากที่สุดในการปิดใช้งานการขยายตัวแปร / พารามิเตอร์ สิ่งที่ฉันต้องทำคือใช้ "เครื่องหมายคำพูดคู่" และแก้ไขมัน! ขอบคุณสำหรับข้อมูล!
Xeoncross

11
เกี่ยวกับการ<<-บันทึกว่ามีเพียงโปรดนำแท็บตัวอักษรมีการปล้น - อักขระแท็บไม่อ่อน นี่เป็นหนึ่งในกรณีที่หายากเหล่านั้นเมื่อคุณต้องการอักขระแท็บ หากเอกสารส่วนที่เหลือของคุณใช้แท็บนุ่มตรวจสอบให้แน่ใจว่าได้แสดงตัวอักษรที่มองไม่เห็นและ (เช่น) คัดลอกและวางอักขระแท็บ หากคุณทำถูกต้องการเน้นไวยากรณ์ของคุณควรจับตัวคั่นสิ้นสุดอย่างถูกต้อง
trkoch

1
ฉันไม่เห็นว่าคำตอบนี้มีประโยชน์มากกว่าคำตอบด้านล่าง เป็นเพียงข้อมูลใหม่ที่สามารถพบได้ในที่อื่น (ซึ่งมีการตรวจสอบแล้ว)
BrDaHa

@ BrDaHa อาจจะไม่ใช่ ทำไมคำถาม เพราะ upvotes? มันเป็นเพียงหนึ่งเดียวเป็นเวลาหลายปี มันเห็นได้จากการเปรียบเทียบวันที่
Alexei Martianov

501

cat <<EOFไวยากรณ์เป็นประโยชน์อย่างมากเมื่อทำงานกับข้อความหลายบรรทัดในทุบตีเช่น เมื่อกำหนดสตริงหลายบรรทัดให้กับตัวแปรเชลล์ไฟล์หรือไพพ์

ตัวอย่างของcat <<EOFการใช้ไวยากรณ์ใน Bash:

1. กำหนดสตริงหลายบรรทัดให้กับตัวแปรเชลล์

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

$sqlตัวแปรในขณะนี้ถือเป็นตัวละครใหม่สายเกินไป echo -e "$sql"คุณสามารถตรวจสอบกับ

2. ส่งสตริงหลายบรรทัดไปยังไฟล์ใน Bash

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

print.shไฟล์ในขณะนี้ประกอบด้วย:

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

3. ผ่านสตริงหลายบรรทัดไปยังไพพ์ใน Bash

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

b.txtไฟล์มีbarและbazสาย stdoutเอาท์พุทเดียวกันถูกพิมพ์ไป


1. 1 และ 3 สามารถทำได้โดยไม่มีแมว 2. ตัวอย่างที่ 1 สามารถทำได้ด้วยสตริงหลายบรรทัดอย่างง่าย
Daniel Alder

269

ในกรณีของคุณ "EOF" เรียกว่า "Here Tag" โดยทั่วไป<<Hereบอกเปลือกที่คุณจะป้อนสตริงหลายจนกระทั่ง Here"แท็ก" คุณสามารถตั้งชื่อแท็กนี้ตามที่คุณต้องการก็มักจะหรือEOFSTOP

กฎบางข้อเกี่ยวกับแท็กที่นี่:

  1. แท็กสามารถเป็นสตริงตัวพิมพ์ใหญ่หรือตัวพิมพ์เล็กได้ แต่คนส่วนใหญ่ใช้ตัวพิมพ์ใหญ่ตามแบบแผน
  2. แท็กจะไม่ถูกพิจารณาว่าเป็นแท็กที่นี่หากมีคำอื่น ๆ ในบรรทัดนั้น ในกรณีนี้มันจะถือว่าเป็นเพียงส่วนหนึ่งของสตริง แท็กควรอยู่ในบรรทัดแยกต่างหากเพื่อพิจารณาแท็ก
  3. แท็กไม่ควรมีช่องว่างนำหน้าหรือต่อท้ายในบรรทัดนั้นเพื่อพิจารณาว่าเป็นแท็ก มิฉะนั้นจะถือว่าเป็นส่วนหนึ่งของสตริง

ตัวอย่าง:

$ 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
นี่คือคำตอบที่แท้จริงที่ดีที่สุด ... คุณให้คำจำกัดความทั้งสองและระบุวัตถุประสงค์หลักของการใช้งานแทนทฤษฎีที่เกี่ยวข้อง ... ซึ่งสำคัญ แต่ไม่จำเป็น ... ขอบคุณ - มีประโยชน์มาก
oemb1905

5
@edelans คุณต้องเพิ่มว่าเมื่อ<<-มีการใช้แท็บนำจะไม่ป้องกันการจำแท็ก
The-null-Pointer-

1
คำตอบของคุณคลิกฉันที่ "คุณจะป้อนสตริงหลายบรรทัด"
แคลคูลัส

79

POSIX 7

kennytm ยกมาman bashแต่ส่วนใหญ่ก็เป็น POSIX 7: http://pubs.opengroup.org/onlinepubs/9699919799/utilities/V3_chap02.html#tag_18_07_04 :

ตัวดำเนินการเปลี่ยนเส้นทาง "<<" และ "<< -" ทั้งคู่อนุญาตการเปลี่ยนเส้นทางของบรรทัดที่มีอยู่ในไฟล์อินพุตเชลล์หรือที่เรียกว่า "here-document" ไปยังอินพุตของคำสั่ง

เอกสารที่นี่จะถือว่าเป็นคำเดียวที่เริ่มต้นหลังจากถัดไปและดำเนินต่อไปจนกว่าจะมีบรรทัดที่มีเพียงตัวคั่นและ a โดยไม่มีตัวอักษรอยู่ในระหว่าง จากนั้นเอกสารต่อไปนี้จะเริ่มขึ้นถ้ามี รูปแบบดังต่อไปนี้:

[n]<<word
    here-document
delimiter

โดยที่ n ซึ่งเป็นทางเลือกแทนหมายเลขไฟล์ descriptor หากไม่ระบุหมายเลขเอกสารที่นี่จะอ้างถึงอินพุตมาตรฐาน (ตัวอธิบายไฟล์ 0)

หากมีการอ้างถึงอักขระในคำใด ๆ ตัวคั่นจะถูกสร้างโดยการลบเครื่องหมายคำพูดในบรรทัดและบรรทัดเอกสารที่นี่จะไม่ถูกขยาย มิฉะนั้นตัวคั่นจะเป็นคำของตัวเอง

หากไม่มีการอ้างถึงอักขระในคำพูดบรรทัดทั้งหมดของ here-document จะถูกขยายสำหรับการขยายพารามิเตอร์การแทนที่คำสั่งและการขยายเลขคณิต ในกรณีนี้อินพุทจะทำหน้าที่เป็นเครื่องหมายคำพูดคู่ด้านใน (ดูที่เครื่องหมายคำพูดคู่) อย่างไรก็ตามอักขระเครื่องหมายคำพูดคู่ ('' ') จะไม่ได้รับการปฏิบัติเป็นพิเศษภายในเอกสารที่นี่ยกเว้นเมื่อเครื่องหมายคำพูดคู่ปรากฏภายใน "$ ()", "` `" หรือ "$ {}"

หากสัญลักษณ์การเปลี่ยนเส้นทางคือ "<< -" <tab>อักขระนำหน้าทั้งหมดจะต้องถูกแยกออกจากบรรทัดอินพุตและบรรทัดที่มีตัวคั่นต่อท้าย หากมีการระบุโอเปอเรเตอร์ "<<" หรือ "<< -" มากกว่าหนึ่งรายการในบรรทัดเอกสารที่นี่ที่เชื่อมโยงกับโอเปอเรเตอร์แรกจะถูกส่งเป็นครั้งแรกโดยแอปพลิเคชันและจะอ่านโดยเชลล์ก่อน

เมื่อเอกสารที่นี่ถูกอ่านจากอุปกรณ์ปลายทางและเชลล์เป็นแบบโต้ตอบมันจะเขียนเนื้อหาของตัวแปร PS2 ซึ่งถูกประมวลผลตามที่อธิบายไว้ใน Shell Variables ไปยังข้อผิดพลาดมาตรฐานก่อนที่จะอ่านแต่ละบรรทัดของอินพุตจนกระทั่งตัวคั่นได้รับการยอมรับ

ตัวอย่าง

ตัวอย่างบางส่วนยังไม่ได้รับ

คำพูดป้องกันการขยายพารามิเตอร์

ไม่มีคำพูด:

a=0
cat <<EOF
$a
EOF

เอาท์พุท:

0

ด้วยคำพูด:

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

หรือ (น่าเกลียด แต่ถูกต้อง):

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

ขาออก:

$a

ยัติภังค์เอาแท็บนำ

ไม่มียัติภังค์:

cat <<EOF
<tab>a
EOF

ที่<tab>เป็นแท็บตัวอักษรและสามารถแทรกด้วยCtrl + V <tab>

เอาท์พุท:

<tab>a

ด้วยยัติภังค์:

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

เอาท์พุท:

a

มีอยู่นี้แน่นอนเพื่อให้คุณสามารถเยื้องรหัสของคุณcatโดยรอบซึ่งง่ายต่อการอ่านและบำรุงรักษา เช่น:

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

ขออภัยนี่ใช้ไม่ได้กับอักขระช่องว่าง: POSIX การtabเยื้องที่ชื่นชอบที่นี่ Yikes


ในตัวอย่างสุดท้ายของคุณพูดคุย<<-และ<tab>aควรสังเกตว่าจุดประสงค์คือเพื่อให้การเยื้องรหัสปกติภายในสคริปต์ในขณะที่อนุญาตให้ข้อความ heredoc ที่นำเสนอต่อกระบวนการรับเริ่มต้นในคอลัมน์ 0 เป็นคุณลักษณะที่ไม่ได้เห็นบ่อยเกินไป บริบทมากขึ้นอาจป้องกันไม่ให้เกิดการจัดการที่ดีของหัวเกา ...
เดวิดซีคิ่

1
ฉันควรหลีกเลี่ยงการหมดอายุหากเนื้อหาบางอย่างอยู่ระหว่างแท็ก EOF ของฉันต้องมีการขยายออกหรือไม่
Jeanmichel Cote

2
... เพียงใช้แบ็กสแลชที่อยู่ข้างหน้า$
Jeanmichel Cote

@JeanmichelCote ฉันไม่เห็นตัวเลือกที่ดีกว่า :-) ด้วยสตริงปกติคุณสามารถพิจารณาผสมคำพูดเช่น"$a"'$b'"$c"แต่ไม่มีอะนาล็อกที่นี่ AFAIK
Ciro Santilli 法轮功冠状病六四事件法轮功

25

ใช้เสื้อยืดแทนแมว

ไม่ตรงกับคำตอบของคำถามเดิม แต่ฉันต้องการแบ่งปันสิ่งนี้ต่อไป: ฉันต้องการสร้างไฟล์ปรับแต่งในไดเรกทอรีที่ต้องการสิทธิ์รูท

ต่อไปนี้ใช้ไม่ได้กับกรณีดังกล่าว:

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

เนื่องจากการเปลี่ยนเส้นทางได้รับการจัดการนอกบริบท sudo

ฉันลงเอยด้วยการใช้สิ่งนี้แทน:

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

ในกรณีของคุณใช้ sudo bash -c 'cat << EOF> /etc/somedir/foo.conf # ไฟล์ config ของฉัน foo = bar EOF'
likewhoa

5

ส่วนขยายเล็กน้อยสำหรับคำตอบข้างต้น การติดตามจะ>นำอินพุตไปยังไฟล์แทนที่การเขียนเนื้อหาที่มีอยู่ อย่างไรก็ตามการใช้งานที่สะดวกอย่างยิ่งหนึ่งคือลูกศรคู่>>ที่ต่อท้ายโดยเพิ่มเนื้อหาใหม่ของคุณลงในตอนท้ายของไฟล์ดังเช่นใน:

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

สิ่งนี้จะขยายขอบเขตของคุณfstabโดยที่คุณไม่ต้องกังวลกับการดัดแปลงเนื้อหาใด ๆ ของมันโดยไม่ตั้งใจ


1

นี่ไม่ใช่คำตอบสำหรับคำถามเดิม แต่เป็นการแบ่งปันผลลัพธ์บางอย่างจากการทดสอบของฉันเอง นี้:

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

จะสร้างไฟล์เดียวกันกับ:

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

ดังนั้นฉันไม่เห็นจุดที่ใช้คำสั่ง cat


2
เปลือกไหน ฉันทดสอบกับ bash 4.4 บน Ubuntu 18.04 และ bash 3.2 บน OSX ทั้งสร้างไฟล์ที่ว่างเปล่าเมื่อเพียงแค่ใช้โดยไม่ต้อง<<test cat <<test
wisbucky

สิ่งนี้ใช้ได้กับฉันใน LInux Mint 19 Tara ใน zsh
Geoff Langenderfer

0

น่าสังเกตว่าที่นี่เอกสารทำงานในลูป bash เกินไป ตัวอย่างนี้แสดงวิธีรับรายการคอลัมน์ของตาราง:

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 ,

หรือแม้กระทั่งโดยไม่ต้องขึ้นบรรทัดใหม่

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
โดยการใช้ไซต์ของเรา หมายความว่าคุณได้อ่านและทำความเข้าใจนโยบายคุกกี้และนโยบายความเป็นส่วนตัวของเราแล้ว
Licensed under cc by-sa 3.0 with attribution required.