วิธีกำหนดค่าสตริงให้กับตัวแปรผ่านหลายบรรทัดในขณะที่ย่อหน้า


54

ปัญหา:

  1. ฉันต้องการกำหนดค่าตัวแปรที่มีความยาวพอสมควร
  2. บรรทัดทั้งหมดของสคริปต์ของฉันต้องอยู่ภายใต้คอลัมน์จำนวนหนึ่ง

ดังนั้นฉันพยายามกำหนดโดยใช้มากกว่าหนึ่งบรรทัด

มันง่ายที่จะทำโดยไม่ต้องเยื้อง:

VAR="This displays without \
any issues."
echo "${VAR}"

ผลลัพธ์:

This displays without any issues.

อย่างไรก็ตามด้วยการเยื้อง:

    VAR="This displays with \
    extra spaces."
    echo "${VAR}"

ผลลัพธ์:

This displays with      extra spaces.

ฉันจะกำหนดอย่างหรูหราโดยไม่มีช่องว่างเหล่านี้ได้อย่างไร

คำตอบ:


30

นี่คือปัญหาที่ว่าคุณกำลังล้อมรอบตัวแปรด้วยเครื่องหมายคำพูดคู่ ("") ลบออกและสิ่งต่าง ๆ จะทำงานได้ดี

    VAR="This displays with \
    extra spaces."
    echo ${VAR}

เอาท์พุต

 This displays with extra spaces.

นี่คือปัญหาที่สองการอ้างอิงตัวแปรรักษาอักขระช่องว่างทั้งหมด สามารถใช้ในกรณีที่คุณต้องการอย่างชัดเจน

ตัวอย่างเช่น,

$ echo "Hello     World    ........ ...            ...."

จะพิมพ์

Hello     World    ........ ...            ....

และในการลบเครื่องหมายคำพูดมันต่างกัน

$ echo Hello     World    ........ ...            ....
Hello World ........ ... ....

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

การอ้างอิงตัวแปรจะมีประโยชน์ในขณะที่ส่งผ่านอาร์กิวเมนต์ไปยังคำสั่ง

ในคำสั่งด้านล่างechoเพียงรับอาร์กิวเมนต์เดียวเป็น"Hello World"

$ variable="Hello World"
$ echo "$variable"

แต่ในกรณีของสถานการณ์ด้านล่างechoจะได้รับสองอาร์กิวเมนต์เป็นHelloและWorld

$ variable="Hello World"
$ echo $variable

คำตอบส่วนใหญ่ทำงานได้ดี แต่นี่เป็นวิธีที่ง่ายที่สุด ขอบคุณ!
Sman865

12
@ Sman865 - โปรดเชื่อฉันเมื่อฉันบอกคุณว่าจริง ๆ แล้วนี่เป็นคำตอบที่ซับซ้อนที่สุดที่นำเสนอในที่นี้เกือบทั้งหมดและมันก็เป็นความผิดที่เกิดขึ้นในส่วนใหญ่ - โดยเฉพาะอย่างยิ่งในการประกาศเปิด ปัญหาใด ๆ ที่มีการกำหนดค่าโดยรอบไม่สามารถเกี่ยวข้องกับการขยายตัวในภายหลัง - ซึ่งเพิ่งย้อนกลับ ฉันขอโทษคันแนน แต่คำตอบนี้ทั้งผิดและผิด การขยายส่วนขยายของฟิลด์$IFSเป็นเครื่องมือที่มีประสิทธิภาพและเป็นสากล - แต่โค้ดที่เขียนด้วยวิธีนี้จะไม่สามารถให้ผลลัพธ์ที่เชื่อถือได้ทุกชนิด
mikeserv

2
ไม่สามารถใช้เครื่องหมายคำพูดเมื่อขยายตัวแปรทำให้เกิดปัญหาไม่ช้าก็เร็วเริ่มต้นด้วยการขยายชื่อไฟล์ (ใด ๆ* ? [])
mr.spuratic

ฉันยอมรับว่าตัวแปรจะต้องอยู่ในเครื่องหมายคำพูดคู่เพื่อป้องกันไม่ให้ bash ส่วนขยาย แต่สำหรับกรณีพิเศษเราสามารถหลีกเลี่ยงได้เพื่อป้องกันความซับซ้อนของรหัส
Kannan Mohan

1
ฉันเห็นด้วยกับ @mikeserv นี่เป็นคำแนะนำที่แย่มากถ้าใช้ตามมูลค่า สิ่งนี้จะแตกหากใช้โดยไม่มีผลกระทบใด ๆ เช่นหากตัวแปรถูกส่งผ่านเป็นอาร์กิวเมนต์ไปยังคำสั่งคุณไม่ต้องการให้แต่ละคำแยกย่อยเป็นอาร์กิวเมนต์แยกต่างหาก
haridsv

28

วิธีแก้ปัญหาที่ได้รับจากesuoxuและMickaël Bucasเป็นวิธีการทั่วไปและพกพาได้มากกว่าในการทำเช่นนี้

ต่อไปนี้เป็นbashวิธีแก้ปัญหาบางอย่าง (ซึ่งบางอันควรทำงานกับเชลล์อื่นเช่นกันzsh) ประการแรกกับตัว+=ดำเนินการผนวก (ซึ่งทำงานในวิธีที่แตกต่างกันเล็กน้อยสำหรับแต่ละตัวแปรจำนวนเต็มตัวแปรปกติและอาร์เรย์)

text="Lorem ipsum dolor sit amet, consectetur adipisicing elit, sed do eiusmod "
text+="tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, "
text+="quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea ..." 

หากคุณต้องการบรรทัดใหม่ (หรือช่องว่าง / ช่องว่างอื่น ๆ ) ในข้อความให้ใช้$''ข้อความแทน:

text=$'Lorem ipsum dolor sit amet, consectetur adipisicing elit, sed do eiusmod\n'
text+=$'...'

ถัดไปใช้printf -vเพื่อกำหนดค่าการจัดรูปแบบให้กับตัวแปร

printf -v text "%s" "Lorem ipsum dolor sit amet, consectetur adipisicing elit, sed " \
                    "do eiusmod empor incididunt ut labore et dolore magna aliqua. "\
                    "Ut enim ad minim veniam ..."

เคล็ดลับที่นี่คือมีข้อโต้แย้งมากกว่าตัวระบุรูปแบบดังนั้นไม่เหมือนกับprintfฟังก์ชั่นส่วนใหญ่ทุบตีหนึ่งจะใช้สตริงรูปแบบใหม่จนกว่าจะหมด คุณสามารถใส่\nสตริงในรูปแบบหรือใช้ $ '', (หรือทั้งสองอย่าง) เพื่อจัดการกับช่องว่าง

ถัดไปโดยใช้อาร์เรย์:

text=("Lorem ipsum dolor sit amet, consectetur adipisicing elit, sed do eiusmod "
      "tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, "
      "quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea ..." )

คุณยังสามารถใช้+=สร้างข้อความขึ้นทีละบรรทัด (หมายเหตุ()):

text+=("post script")

แม้ว่าที่นี่คุณต้องจำไว้ว่า "เรียบ" อาเรย์ถ้าคุณต้องการเนื้อหาข้อความทั้งหมดในครั้งเดียว

echo "$text"      # only outputs index [0], the first line
echo "${text[*]}" # output complete text (joined by first character of IFS)

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

สุดท้ายใช้readหรือreadarrayและ "here-document":

read -r -d '' text <<-"EOT"
        Lorem ipsum dolor sit amet, consectetur adipisicing elit, sed do eiusmod
        tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, 
        quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea ...
EOT

readarray -t textarray <<-"EOT"
        Lorem [...]
EOT  

รูปแบบเอกสารที่นี่<<-หมายถึงฮาร์ดแท็บนำทั้งหมดจะถูกลบออกจากอินพุตดังนั้นคุณต้องใช้แท็บเพื่อเยื้องข้อความของคุณ "EOT"เครื่องหมายคำพูดล้อมรอบป้องกันคุณลักษณะการขยายเชลล์ดังนั้นอินพุตจะใช้คำต่อคำ ด้วยreadจะใช้อินพุตที่คั่นด้วย NUL ไบต์เพื่อที่จะอ่านข้อความที่คั่นด้วยบรรทัดใหม่ในครั้งเดียว ด้วยreadarray(aka mapfile, มีให้ตั้งแต่ bash-4.0) มันจะอ่านเป็นอาร์เรย์และตัด-tบรรทัดขึ้นบรรทัดใหม่ในแต่ละบรรทัด


1
readตัวเลือกที่มีhere documentเป็นสิ่งที่ดีมาก! python -cมีประโยชน์ในการฝังสคริปต์หลามและดำเนินการกับ ฉันเคยทำscript=$(cat <here document>)มาก่อน แต่read -r -d '' script <here document>ดีกว่ามาก
haridsv

9

มีไวยากรณ์ heredoc พิเศษที่ลบแท็บที่จุดเริ่มต้นของทุกบรรทัด: "<< -" (สังเกตเห็นการเพิ่มขีดกลาง)

http://tldp.org/LDP/abs/html/here-docs.html

ตัวอย่างที่ 19-4 ข้อความหลายบรรทัดพร้อมแท็บที่ถูกระงับ

คุณสามารถใช้สิ่งนี้:

v="$(cat <<-EOF
    A
        B
    C
EOF
)"
echo "$v"

ผลลัพธ์ :

A
B
C

ใช้งานได้กับแท็บเท่านั้นไม่ใช่ช่องว่าง


ที่นี่บนอูบุนตูการย้อนกลับเป็นจริง - ช่องว่างใช้ได้ไม่ใช่แท็บ \tใช้งานได้ดีแม้ว่าคุณต้องการแท็บ เพียงใช้echo -e "$v"แทนecho "$v"การเปิดใช้งานอักขระเครื่องหมายแบ็กสแลช
will-ob

5

ปล่อยให้เชลล์กิน linefeeds ที่ไม่ต้องการและช่องว่างต่อไปนี้:

$ cat weird.sh 
#!/bin/sh

        var1="A weird(?) $(
             )multi line $(
             )text idea. $(
             )PID=$$"

        var2='You can '$(
            )'avoid expansion '$(
            )'too: PID=$$'

        var3='Or mix it: '$(
            )'To insert the PID use $$. '$(
            )"It expands to e.g. $$."

        echo "$var1"
        echo "$var2"
        echo "$var3"
$ sh weird.sh 
A weird(?) multi line text idea. PID=13960
You can avoid expansion too: PID=$$
Or mix it: To insert the PID use $$. It expands to e.g. 13960.

ดังนั้นจึงเป็นไปได้ ... แต่แน่นอนว่ามันเป็นเรื่องของรสนิยมที่จะชอบหรือไม่ชอบวิธีนี้ ...


3
ทุก ๆ อันเดียวคือส้อม
mikeserv


5

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

set -- 'Arg 1: Line 1.' \
       'Arg 2: Line 2.' \
       'and so on for'  \
       'as long as you might like.'
var="$*"

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

    string="some stuff here \
            some more stuff here."
    echo $string ${#string} 
    echo "$string" "${#string}"

เอาท์พุท

some stuff here some more stuff here. 53
some stuff here                 some more stuff here. 53

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

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

    IFS=sf
    echo $string ${#string} 
    echo "$string" "${#string}"

เดียวกัน$string- สภาพแวดล้อมที่แตกต่างกัน

เอาท์พุท

 ome  tu   here                  ome more  tu   here. 53
some stuff here                 some more stuff here. 53

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

$IFSช่องว่างจะลบออกตามลำดับไปยังเขตข้อมูลเดียวและนี่คือเหตุผลที่echoการขยายที่มีลำดับของช่องว่างใด ๆ เมื่อ$IFSมีช่องว่างจะประเมินเป็นพื้นที่เดียว - เพราะechoเชื่อมต่อข้อโต้แย้งบนช่องว่าง แต่ค่าที่ไม่ใช่ช่องว่างใด ๆ จะไม่ลบในลักษณะเดียวกันและตัวคั่นที่เกิดขึ้นแต่ละตัวจะได้รับเขตข้อมูลของตัวเองเสมอดังที่สามารถเห็นได้ในการขยายสิ่งด้านบน

นี่ไม่ใช่สิ่งที่เลวร้ายที่สุดของมัน $stringพิจารณาอื่น ๆ

IFS=$space$tab$newline
cd emptydir
    string=" * * * \
             * * * "
    echo $string ${#string}
    echo "$string" "${#string}"    

เอาท์พุท

* * * * * * 30
 * * *                  * * *  30

ดูโอเคใช่ไหม เรามาเปลี่ยนสภาพแวดล้อมอีกครั้ง

    touch file1 file2 file3 file4 file5
    echo $string ${#string}
    echo "$string" "${#string}"    

เอาท์พุท

file1 file2 file3 file4 file5 file1 file2 file3 file4 file5 file1 file2 file3 file4 file5 file1 file2 file3 file4 file5 file1 file2 file3 file4 file5 file1 file2 file3 file4 file5 30
 * * *                  * * *  30

ว้าว

โดยค่าเริ่มต้นเชลล์จะขยายชื่อไฟล์ globs หากสามารถจับคู่พวกเขา สิ่งนี้เกิดขึ้นหลังจากการขยายพารามิเตอร์และการแยกฟิลด์ในการวิเคราะห์คำสั่งและดังนั้นสตริงใด ๆ ที่ไม่ได้กล่าวถึงจะมีความเสี่ยงในลักษณะนี้ คุณสามารถสลับพฤติกรรมนี้ได้set -fหากต้องการ แต่เชลล์ที่ทำงานร่วมกันได้กับ POSIX ใด ๆ จะทำงานโดยอัตโนมัติ

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

set -- 'Arg 1: Line 1.' \
       'Arg 2: Line 2.' \
       'and so on for'  \
       'as long as you might like.'
var="$*"
echo "$var" "${#var}"

เอาท์พุท

Arg 1: Line 1. Arg 2: Line 2. and so on for as long as you might like. 70

ฉันเชื่อว่านี่เป็นวิธีที่คุ้นเคยอย่างยิ่งในการปรับเปลี่ยนไวยากรณ์ของเชลล์ให้เข้ากับความต้องการของคุณ สิ่งที่ฉันทำข้างต้นจะกำหนดแต่ละสายแต่ละพารามิเตอร์ตำแหน่ง - ซึ่งสามารถแต่ละคนจะอ้างอิงจากจำนวนเหมือน$1หรือ${33}- แล้วการกำหนดค่าตัดแบ่งของพวกเขาไปโดยใช้พารามิเตอร์เปลือกพิเศษ$var$*

วิธีการนี้ไม่ได้เป็นภูมิคุ้มกันที่จะ$IFSฉันใด แต่ถึงกระนั้นฉันก็พิจารณาความสัมพันธ์ของมันกับ$IFSผลประโยชน์เพิ่มเติมในส่วนนี้ พิจารณา:

IFS=\ ;space_split="$*"
IFS=/; slash_split="$*";IFS='
';new_line_split="$*"

echo "$space_split"
echo "$slash_split"
echo "$new_line_split"

เอาท์พุท

Arg 1: Line 1. Arg 2: Line 2. and so on for as long as you might like.
Arg 1: Line 1./Arg 2: Line 2./and so on for/as long as you might like.
Arg 1: Line 1.
Arg 2: Line 2.
and so on for
as long as you might like.

ในขณะที่คุณสามารถดู$*เชื่อมหาเรื่องแต่ละคนในในไบต์แรกใน"$@" $IFSดังนั้นการบันทึกค่าในขณะที่$IFSกำหนดต่างกันจะได้รับตัวคั่นฟิลด์ที่แตกต่างกันสำหรับแต่ละค่าที่บันทึก สิ่งที่คุณเห็นด้านบนคือค่าตัวอักษรสำหรับแต่ละตัวแปร หากคุณไม่ต้องการตัวคั่นใด ๆ เลย:

IFS=;delimitless="$*"
echo "$delimitless" "${#delimitless}"

เอาท์พุท

Arg 1: Line 1.Arg 2: Line 2.and so on foras long as you might like. 67

2

คุณอาจต้องการลอง:

echo $VAR | tr -s " "

หรือ

myArr=($VAL)
VAL=${myArr[@]}
echo "$VAL"

และยังให้คุณสามารถตรวจสอบนี้ออก


2

ใช้การขยายการทดแทน Bash

หากคุณกำลังใช้ทุบตีคุณสามารถใช้การขยายตัวของการทดแทน ตัวอย่างเช่น:

$ echo "${VAR//  /}"
This displays with extra spaces.

1

นี่เป็นตัวแปรสำหรับการตั้งค่าตัวแปรที่เหมือนพา ธ :

set -- "${MYDIR}/usr/local/lib" \
      :"${MYDIR}/usr/lib" \
      :"${MYDIR}/lib" \
       "${LD_LIBRARY_PATH:+:$LD_LIBRARY_PATH}"
    export LD_LIBRARY_PATH="$*"
    LD_LIBRARY_PATH=$(sed 's/ :/:/g' <<< $LD_LIBRARY_PATH)

การใช้การsetเขียนทับ$@ซึ่งสามารถบันทึกและใช้ในภายหลังดังต่อไปนี้:

ARGV=("$@")
exec foo "${ARGV[@]}"

LD_LIBRARY_PATH=$(sed 's/ :/:/g' <<< $LD_LIBRARY_PATH)สายจะช่วยลดช่องว่างก่อนทวิภาคเช่นเดียวกับช่องว่างต่อท้ายที่เป็นไปได้ ถ้าเพียงกำจัดช่องว่างต่อท้ายใช้LD_LIBRARY_PATH=${LD_LIBRARY_PATH%% }แทน

วิธีการทั้งหมดนี้เป็นคำตอบที่ยอดเยี่ยมของ mikeserv


0

อย่ากลัวช่องว่างของตัวละคร เพียงลบออกก่อนที่จะพิมพ์ข้อความหลายบรรทัด

$ cat ./t.sh
#!/bin/bash

NEED_HELP=1
if [[ $NEED_HELP -eq 1 ]]; then

    lucky_number=$((1 + RANDOM % 10 + 10))

    read -r -d '' text <<'    EOF'
    NAME says hello

    Look at this helpful text:

                                                 * -**
                                 Eyjafjallajokull        Eyjafjallajokull
                            Eyjafjallajokull Eyjafjallajokull Eyjafjallajokull
                        Eyjafjallajokull Eyjafjallajokull Eyjafjallajokull Eyjafjallajokull
                    Eyjafjallajokull Eyjafjallajokull Eyjafjallajokull Eyjafjallajokull
                Eyjafjallajokull Eyjafjallajokull Eyjafjallajokull Eyjafjallajokull Eyjafjallajokull
            Eyjafjallajokull Eyjafjallajokull Eyjafjallajokull Eyjafjallajokull Eyjafjallajokull Eyjafjallajokull
        Eyjafjallajokull Eyjafjallajokull Eyjafjallajokull Eyjafjallajokull Eyjafjallajokull Eyjafjallajokull
    Eyjafjallajokull Eyjafjallajokull Eyjafjallajokull Eyjafjallajokull Eyjafjallajokull Eyjafjallajokull Eyjafjallajokull

    Don't go away, please rate your experience... It'll just take two minutes.

    EOF
    text=$(echo "$text" | sed -r 's!^\s{4}!!')
    text=$(echo "$text" | sed -r "s!\bNAME\b!$0!") # Bash: text=${text//NAME/$0}
    text=$(echo "$text" | sed -r "s!\btwo\b!$lucky_number!")

    echo "$text"

fi

เอาท์พุท:

$ ./t.sh
./t.sh says hello

Look at this helpful text:

                                             * -**
                             Eyjafjallajokull        Eyjafjallajokull
                        Eyjafjallajokull Eyjafjallajokull Eyjafjallajokull
                    Eyjafjallajokull Eyjafjallajokull Eyjafjallajokull Eyjafjallajokull
                Eyjafjallajokull Eyjafjallajokull Eyjafjallajokull Eyjafjallajokull
            Eyjafjallajokull Eyjafjallajokull Eyjafjallajokull Eyjafjallajokull Eyjafjallajokull
        Eyjafjallajokull Eyjafjallajokull Eyjafjallajokull Eyjafjallajokull Eyjafjallajokull Eyjafjallajokull
    Eyjafjallajokull Eyjafjallajokull Eyjafjallajokull Eyjafjallajokull Eyjafjallajokull Eyjafjallajokull
Eyjafjallajokull Eyjafjallajokull Eyjafjallajokull Eyjafjallajokull Eyjafjallajokull Eyjafjallajokull Eyjafjallajokull

Don't go away, please rate your experience... It'll just take 16 minutes.

ไม่จำเป็นต้องใช้<<-heredoc และแบ่งการเว้นวรรค 4 ช่องของคุณด้วยอักขระแท็บ

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