วิธีการกำหนดค่า heredoc ให้กับตัวแปรใน Bash


374

ฉันมีสตริงหลายบรรทัดนี้ (รวมคำพูด):

abc'asdf"
$(dont-execute-this)
foo"bar"''

ฉันจะกำหนดให้ตัวแปรโดยใช้ heredoc ใน Bash ได้อย่างไร

ฉันต้องการรักษาบรรทัดใหม่

ฉันไม่ต้องการที่จะหลบหนีตัวละครในสตริงที่จะน่ารำคาญ ...


@JohnM - ฉันเพิ่งลองมอบหมาย heredoc ด้วย single- quote 'EOF'ด้วย` in the content: if the second line has คำสั่งlinebreaks ที่มีการEscape ด้วยคำสั่ง cd` ฉันจะกลับมา: " .sh: line X: cd: command not found "; แต่ถ้าฉันพูดสองครั้ง"EOF"; ดังนั้นตัวแปร bash ${A}จะไม่ได้รับการเก็บรักษาไว้เป็นสตริง (จะถูกขยาย) แต่แล้วตัวแบ่งบรรทัดจะถูกเก็บรักษาไว้ - และฉันไม่มีปัญหาในการเรียกใช้คำสั่งด้วยcdในบรรทัดที่สอง ( และ 'EOF' และ "EOF" ดูเหมือนจะเล่นได้ดีเช่นกันevalสำหรับการเรียกใช้ชุดคำสั่งที่เก็บไว้ใน ตัวแปรสตริง ) ไชโย!
sdaau

1
... และเพื่อเพิ่มความคิดเห็นก่อนหน้าของฉัน: ความคิดเห็นทุบตี "#" ใน"EOF"ตัวแปรแบบ double-qouted หากเรียกผ่านeval $VARจะทำให้สคริปต์ที่เหลือทั้งหมดถูกคอมเม้นต์เนื่องจากที่นี่ $ VAR จะถูกมองเป็นบรรทัดเดียว ; เพื่อให้สามารถใช้#ความคิดเห็นทุบตีในสคริปต์หลายบรรทัด, เครื่องหมายคำพูดคู่ยังตัวแปรในeval call: eval "$ VAR" `
sdaau

@sdaau: ฉันมีปัญหากับevalวิธีนี้ แต่ไม่ได้ติดตามเพราะมันเป็นส่วนหนึ่งของแพคเกจที่evalตัวแปรบางอย่างที่กำหนดไว้ในมันเป็นไฟล์ config /usr/lib/network/network: eval: line 153: syntax error: unexpected end of fileเกิดข้อผิดพลาดคือ: ฉันเพิ่งเปลี่ยนไปใช้โซลูชันอื่น
Golar Ramblar

คำตอบ:


515

คุณสามารถหลีกเลี่ยงการใช้catและจัดการคำพูดที่ไม่ตรงกันได้ดีขึ้นด้วยวิธีนี้:

$ read -r -d '' VAR <<'EOF'
abc'asdf"
$(dont-execute-this)
foo"bar"''
EOF

หากคุณไม่ได้อ้างอิงตัวแปรเมื่อคุณ echo มันขึ้นบรรทัดใหม่จะหายไป การอ้างอิงจะเก็บรักษาไว้:

$ echo "$VAR"
abc'asdf"
$(dont-execute-this)
foo"bar"''

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

$ read -r -d '' VAR <<-'EOF'
    abc'asdf"
    $(dont-execute-this)
    foo"bar"''
    EOF
$ echo "$VAR"
abc'asdf"
$(dont-execute-this)
foo"bar"''

หาก IFSแต่คุณต้องการรักษาแท็บในเนื้อหาของตัวแปรที่ส่งผลให้คุณต้องเอาแท็บจาก เทอร์มินัลมาร์กเกอร์สำหรับ doc ที่นี่ ( EOF) จะต้องไม่ถูกเยื้อง

$ IFS='' read -r -d '' VAR <<'EOF'
    abc'asdf"
    $(dont-execute-this)
    foo"bar"''
EOF
$ echo "$VAR"
    abc'asdf"
    $(dont-execute-this)
    foo"bar"''

แท็บสามารถแทรกในบรรทัดคำสั่งโดยการกด-Ctrl V Tabหากคุณใช้โปรแกรมแก้ไขซึ่งอาจใช้งานได้หรือคุณอาจต้องปิดคุณสมบัติที่แปลงแท็บเป็นช่องว่างโดยอัตโนมัติ


117
ฉันคิดว่าเป็นมูลค่าการกล่าวขวัญว่าถ้าคุณมีset -o errexit(aka set -e) ในสคริปต์ของคุณและคุณใช้มันก็จะยุติสคริปต์ของคุณเพราะreadส่งกลับรหัสส่งคืนที่ไม่เป็นศูนย์เมื่อมันมาถึง EOF
Mark Byers

14
@ MarkByers: นั่นเป็นหนึ่งในเหตุผลที่ฉันไม่เคยใช้set -eและแนะนำให้ใช้เสมอ ควรใช้การจัดการข้อผิดพลาดที่เหมาะสมแทน trapเป็นเพื่อนของคุณ. เพื่อนคนอื่น ๆ : elseและ||อื่น ๆ
หยุดชั่วคราวจนกว่าจะมีการแจ้งให้ทราบต่อไป

7
การหลีกเลี่ยงความcatคุ้มค่าจริงๆในกรณีเช่นนี้หรือไม่? การกำหนด heredoc ให้กับตัวแปรด้วยcatเป็นสำนวนที่รู้จักกันดี อย่างใดใช้readobfuscates สิ่งต่าง ๆ เพื่อผลประโยชน์เล็ก ๆ น้อย ๆ imho
เกรกอรี่ Pakosz

6
@ulidtko นั่นเป็นเพราะคุณไม่มีช่องว่างระหว่างdและสตริงว่าง bashพังทลายลงมา-rd''เพียงแค่-rdก่อนที่จะreadเคยเห็นข้อโต้แย้งของตนเพื่อจะถือว่าเป็นข้อโต้แย้งที่จะVAR -d
chepner

6
ในรูปแบบนี้readจะส่งคืนพร้อมรหัสออกที่ไม่เป็นศูนย์ วิธีนี้ทำให้วิธีนี้น้อยกว่าอุดมคติในสคริปต์ที่เปิดใช้งานการตรวจสอบข้อผิดพลาด (เช่นset -e)
สวิส

245

ใช้ $ () เพื่อกำหนดผลลัพธ์ของcatตัวแปรของคุณดังนี้:

VAR=$(cat <<'END_HEREDOC'
abc'asdf"
$(dont-execute-this)
foo"bar"''
END_HEREDOC
)

# this will echo variable with new lines intact
echo "$VAR"
# this will echo variable without new lines (changed to space character)
echo $VAR

ตรวจสอบให้แน่ใจว่าได้กำหนดขอบเขตเริ่มต้น END_HEREDOC ด้วยเครื่องหมายคำพูดเดี่ยว

โปรดทราบว่าการสิ้นสุดตัวคั่น heredoc END_HEREDOCต้องอยู่คนเดียวในบรรทัด (ดังนั้นเครื่องหมายวงเล็บปิดจึงอยู่ที่บรรทัดถัดไป)

ขอบคุณ@ephemientสำหรับคำตอบ


1
อันที่จริงอันนี้ให้ผ่านคำพูดในบางสถานการณ์ ฉันจัดการเพื่อทำเช่นนี้ใน Perl ได้อย่างง่ายดาย ...
นีล

32
+1 นี่เป็นวิธีการแก้ปัญหาที่อ่านง่ายที่สุดอย่างน้อยที่สุดสำหรับดวงตาของฉัน มันปล่อยให้ชื่อของตัวแปรที่ด้านซ้ายสุดของหน้าแทนที่จะฝังไว้ในคำสั่งอ่าน
Clayton Stanley

12
PSA: โปรดจำไว้ว่าต้องอ้างตัวแปรเพื่อรักษาบรรทัดใหม่ แทนecho "$VAR" echo $VAR
sevko

7
นี้เป็นสิ่งที่ดีด้วยashและ OpenWRT ที่ไม่สนับสนุนread -d
David Ehrmann

3
สำหรับเหตุผลที่ฉันไม่สามารถเข้าใจได้สิ่งนี้จะล้มเหลวด้วยข้อผิดพลาด "EOF ที่ไม่คาดคิด" หากคุณมีbacktick ที่ไม่มีคู่ใน heredoc
Radon Rosborough

79

นี่คือการแปรผันของวิธีเดนนิสดูมีความสง่างามยิ่งขึ้นในสคริปต์

นิยามฟังก์ชั่น:

define(){ IFS='\n' read -r -d '' ${1} || true; }

การใช้งาน:

define VAR <<'EOF'
abc'asdf"
$(dont-execute-this)
foo"bar"''
EOF

echo "$VAR"

สนุก

PS ทำ'ห่วงอ่าน'read -dรุ่นสำหรับเปลือกหอยที่ไม่สนับสนุน ควรจะทำงานกับset -euและbackticks unpairedแต่ไม่ได้รับการทดสอบอย่างดี:

define(){ o=; while IFS="\n" read -r a; do o="$o$a"'
'; done; eval "$1=\$o"; }

1
ดูเหมือนว่าจะใช้งานได้เพียงผิวเผินเท่านั้น ฟังก์ชั่น define จะคืนค่าสถานะ 1 และฉันไม่แน่ใจว่าต้องแก้ไขอะไร
fny

1
นี่ยังเหนือกว่าคำตอบที่ยอมรับเพราะสามารถแก้ไขเพื่อรองรับ POSIX sh นอกเหนือจาก bash (การreadวนซ้ำในฟังก์ชันเพื่อหลีกเลี่ยงการ-d ''bashism ที่จำเป็นในการรักษาบรรทัดใหม่)
ELLIOTTCABLE

ซึ่งแตกต่างจากตัวเลือก cat-in-a-subshell ซึ่งใช้ได้กับbackticks ที่ไม่มีการจับคู่ใน heredoc ขอบคุณ!
Radon Rosborough

วิธีนี้ใช้ได้กับset -eชุดในขณะที่คำตอบที่เลือกไม่ได้ ดูเหมือนจะเป็นเพราะhttp://unix.stackexchange.com/a/265151/20650
59

2
@fny ps สถานะการส่งคืนได้รับการแก้ไขแล้ว
ttt

36
VAR=<<END
abc
END

ไม่ทำงานเนื่องจากคุณเปลี่ยนเส้นทาง stdin ไปยังสิ่งที่ไม่สนใจนั่นคือการกำหนด

export A=`cat <<END
sdfsdf
sdfsdf
sdfsfds
END
` ; echo $A

ใช้งานได้ แต่มี back-tic อยู่ตรงนั้นซึ่งอาจทำให้คุณไม่สามารถใช้สิ่งนี้ได้ นอกจากนี้คุณควรหลีกเลี่ยงการใช้ backticks $(..)มันจะดีกว่าที่จะใช้สัญกรณ์คำสั่งเปลี่ยนตัว

export A=$(cat <<END
sdfsdf
sdfsdf
sdfsfds
END
) ; echo $A

ฉันได้อัปเดตคำถามของฉันเพื่อรวม $ (ปฏิบัติการ) นอกจากนี้คุณจะรักษาบรรทัดใหม่อย่างไร
Neil

2
@ l0st3d: ปิด ... ใช้$(cat <<'END'แทน @Neil: ขึ้นบรรทัดใหม่สุดท้ายจะไม่เป็นส่วนหนึ่งของตัวแปร แต่ส่วนที่เหลือจะถูกเก็บไว้
ephemient

1
ดูเหมือนว่าจะไม่มีการขึ้นบรรทัดใหม่ใด ๆ ใช้ตัวอย่างข้างต้นฉันเห็น: "sdfsdf sdfsdf sdfsfds" ... ah! แต่การเขียนecho "$A"(เช่นการใส่ $ A ในเครื่องหมายคำพูดคู่) และคุณเห็นบรรทัดใหม่!
Darren Cook

1
@Darren: aha! ฉันสังเกตเห็นปัญหาบรรทัดใหม่และการใช้เครื่องหมายคำพูดรอบ ๆ ตัวแปรเอาต์พุตจะแก้ไขปัญหาได้ ขอบคุณ!
javadba

1
REM=<< 'REM' ... comment block goes here ... REMที่น่าสนใจเนื่องจากมุมแหลมของตัวอย่างแรกในหยิกคุณสามารถใช้มันสำหรับบล็อกแสดงความคิดเห็นชั่วคราวเช่นนี้ : << 'REM' ...หรือมากกว่าดาน, ที่ไหน "REM" อาจเป็นอะไรเช่น "บันทึก" หรือ "SCRATCHPAD" ฯลฯ
Beejor

34

ยังไม่มีวิธีแก้ปัญหาสำหรับรักษาบรรทัดใหม่

สิ่งนี้ไม่เป็นความจริง - คุณอาจถูกพฤติกรรมของเสียงสะท้อนผิด ๆ :

echo $VAR # strips newlines

echo "$VAR" # preserves newlines


5
นี่เป็นพฤติกรรมของวิธีการอ้างอิงตัวแปร หากไม่มีเครื่องหมายอัญประกาศมันจะแทรกพารามิเตอร์ที่แตกต่างกันเว้นวรรคขณะที่เครื่องหมายคำพูดเนื้อหาตัวแปรทั้งหมดจะถือเป็นอาร์กิวเมนต์เดียว
Czipperz

11

แยกคำตอบของนีลบ่อยครั้งที่คุณไม่ต้องการ var เลยคุณสามารถใช้ฟังก์ชันในลักษณะเดียวกับตัวแปรและอ่านได้ง่ายกว่า inline หรือread-based solution

$ complex_message() {
  cat <<'EOF'
abc'asdf"
$(dont-execute-this)
foo"bar"''
EOF
}

$ echo "This is a $(complex_message)"
This is a abc'asdf"
$(dont-execute-this)
foo"bar"''

9

อาร์เรย์เป็นตัวแปรดังนั้นในกรณีนั้น mapfile จะทำงาน

mapfile y <<z
abc'asdf"
$(dont-execute-this)
foo"bar"''
z

จากนั้นคุณสามารถพิมพ์ได้เช่นนี้

printf %s "${y[@]}"

3

กำหนดค่า heredoc ให้กับตัวแปร

VAR="$(cat <<'VAREOF'
abc'asdf"
$(dont-execute-this)
foo"bar"''
VAREOF
)"

ใช้เป็นอาร์กิวเมนต์ของคำสั่ง

echo "$(cat <<'SQLEOF'
xxx''xxx'xxx'xx  123123    123123
abc'asdf"
$(dont-execute-this)
foo"bar"''
SQLEOF
)"

เมื่อฉันลองวิธีแรกดูเหมือนจะไม่มีตัวยุติบรรทัดระหว่างบรรทัด จะต้องมีการกำหนดค่าบางอย่างบนเครื่อง linux ของฉันหรือไม่
Kemin Zhou

นี้น่าจะหมายความว่าเมื่อคุณได้รับการสะท้อนตัวแปรของคุณคุณไม่ได้ใส่คำพูดรอบมัน ... ลองชอบดังนั้น:echo "$VAR"
แบรดสวนสาธารณะ

1

ฉันพบว่าตัวเองต้องอ่านสตริงที่มีค่า NULL ดังนั้นจึงเป็นวิธีแก้ปัญหาที่จะอ่านอะไรคุณต้องการ แม้ว่าถ้าคุณกำลังจัดการกับ NULL จริง ๆ คุณจะต้องจัดการกับมันที่ระดับฐานสิบหก

$ cat> read.dd.sh

read.dd() {
     buf= 
     while read; do
        buf+=$REPLY
     done < <( dd bs=1 2>/dev/null | xxd -p )

     printf -v REPLY '%b' $( sed 's/../ \\\x&/g' <<< $buf )
}

พิสูจน์:

$ . read.dd.sh
$ read.dd < read.dd.sh
$ echo -n "$REPLY" > read.dd.sh.copy
$ diff read.dd.sh read.dd.sh.copy || echo "File are different"
$ 

ตัวอย่าง HEREDOC (ด้วย ^ J, ^ M, ^ I):

$ read.dd <<'HEREDOC'
>       (TAB)
>       (SPACES)
(^J)^M(^M)
> DONE
>
> HEREDOC

$ declare -p REPLY
declare -- REPLY="  (TAB)
      (SPACES)
(^M)
DONE

"

$ declare -p REPLY | xxd
0000000: 6465 636c 6172 6520 2d2d 2052 4550 4c59  declare -- REPLY
0000010: 3d22 0928 5441 4229 0a20 2020 2020 2028  =".(TAB).      (
0000020: 5350 4143 4553 290a 285e 4a29 0d28 5e4d  SPACES).(^J).(^M
0000030: 290a 444f 4e45 0a0a 220a                 ).DONE

0

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

เอาท์พุทตัวอย่าง

$ ./test.sh

The text from the example function is:
  Welcome dev: Would you "like" to know how many 'files' there are in /tmp?

  There are "      38" files in /tmp, according to the "wc" command

test.sh

#!/bin/bash

function text1()
{
  COUNT=$(\ls /tmp | wc -l)
cat <<EOF

  $1 Would you "like" to know how many 'files' there are in /tmp?

  There are "$COUNT" files in /tmp, according to the "wc" command

EOF
}

function main()
{
  OUT=$(text1 "Welcome dev:")
  echo "The text from the example function is: $OUT"
}

main

มันน่าสนใจที่จะเห็นคำพูดที่ไม่มีใครเทียบในข้อความเพื่อดูว่ามันจัดการอย่างไร บางที `อย่าประหลาดใจมีไฟล์" $ COUNT "เพื่อให้เครื่องหมายอัญประกาศเดี่ยว / คำพูดเดียวสามารถทำให้สิ่งต่าง ๆ น่าสนใจ
dragon788

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