ใช้เครื่องหมายคำพูดคู่รอบ ๆ การแทนที่ตัวแปรและการแทนที่คำสั่งเสมอ: "$foo"
,"$(foo)"
ถ้าคุณใช้$foo
unquoted สคริปต์ของคุณจะสำลักกับการป้อนข้อมูลหรือพารามิเตอร์ (หรือคำสั่งออกด้วย$(foo)
) \[*?
ที่มีช่องว่างหรือ
ที่นั่นคุณสามารถหยุดอ่าน ดีตกลงนี่คืออีกไม่กี่:
read
- หากต้องการอ่านอินพุตบรรทัดต่อบรรทัดโดยใช้read
บิวด์อินให้ใช้while IFS= read -r line; do …
Plain read
ถือแบ็กสแลชและช่องว่างพิเศษ
xargs
- หลีกเลี่ยงการ xargs
หากคุณต้องใช้xargs
ให้ทำสิ่งxargs -0
นั้น แทนที่จะfind … | xargs
, ต้องการ find … -exec …
xargs
ถือว่าช่องว่างและตัวละคร\"'
เป็นพิเศษ
คำตอบนี้นำไปใช้กับเปลือกหอยบอร์น / POSIX สไตล์ ( sh
, ash
, dash
, bash
, ksh
, mksh
, yash
... ) ผู้ใช้ Zsh ควรข้ามและอ่านจุดสิ้นสุดของการอ้างอิงสองครั้งเมื่อใด แทน. หากคุณต้องการ nitty-gritty ทั้งหมดให้อ่านมาตรฐานหรือคู่มือของเชลล์
โปรดทราบว่าคำอธิบายด้านล่างมีการประมาณสองสามประการ (ข้อความที่เป็นจริงในเงื่อนไขส่วนใหญ่ แต่อาจได้รับผลกระทบจากบริบทแวดล้อมหรือการกำหนดค่า)
ทำไมฉันต้องเขียน"$foo"
? จะเกิดอะไรขึ้นถ้าไม่มีคำพูด?
$foo
ไม่ได้หมายความว่า“ รับค่าของตัวแปรfoo
” มันหมายถึงบางสิ่งที่ซับซ้อนมากขึ้น:
- ก่อนอื่นให้รับค่าของตัวแปร
- การแบ่งฟิลด์: ถือว่าค่านั้นเป็นรายการเขตข้อมูลที่คั่นด้วยช่องว่างและสร้างรายการผลลัพธ์ ตัวอย่างเช่นถ้าตัวแปรมี
foo * bar
แล้วผลของขั้นตอนนี้คือรายการ 3 องค์ประกอบfoo
, ,*
bar
- การสร้างชื่อไฟล์: ถือว่าแต่ละฟิลด์เป็นแบบกลมเช่นรูปแบบไวด์การ์ดและแทนที่ด้วยรายการชื่อไฟล์ที่ตรงกับรูปแบบนี้ หากรูปแบบไม่ตรงกับไฟล์ใด ๆ แสดงว่าไม่มีการแก้ไข ในตัวอย่างของเรานี้ส่งผลในรายการมีดังต่อไปนี้ด้วยรายการของไฟล์ในไดเรกทอรีปัจจุบันและในที่สุดก็
foo
bar
หากไดเรกทอรีปัจจุบันเป็นที่ว่างเปล่า, ผลที่ได้คือfoo
, ,*
bar
โปรดทราบว่าผลที่ได้คือรายการของสตริง มีสองบริบทในไวยากรณ์ของเชลล์: บริบทรายการและบริบทสตริง การแบ่งฟิลด์และการสร้างชื่อไฟล์จะเกิดขึ้นเฉพาะในบริบทรายการ แต่ส่วนใหญ่แล้ว เครื่องหมายอัญประกาศคู่คั่นบริบทสตริง: สตริงที่มีเครื่องหมายคำพูดคู่ทั้งหมดเป็นสตริงเดี่ยวที่จะไม่แยก (ข้อยกเว้น: "$@"
เพื่อขยายไปยังรายการพารามิเตอร์ตำแหน่งเช่น"$@"
เทียบเท่ากับ"$1" "$2" "$3"
หากมีพารามิเตอร์ตำแหน่งสามพารามิเตอร์ดูความแตกต่างระหว่าง $ * และ $ @ คืออะไร )
เช่นเดียวกับที่เกิดขึ้นกับคำสั่งเปลี่ยนตัวด้วยหรือ$(foo)
`foo`
ในหมายเหตุด้านอย่าใช้`foo`
: กฎการอ้างถึงนั้นแปลกและไม่สามารถพกพาได้และการสนับสนุน shell ทันสมัยทั้งหมด$(foo)
ซึ่งเทียบเท่ากันอย่างแน่นอนยกเว้นการมีกฎการอ้างที่ใช้งานง่าย
เอาท์พุทของการทดแทนเลขคณิตก็ผ่านการขยายตัวเหมือนกัน แต่โดยทั่วไปไม่น่าเป็นห่วงเพราะมันมีเพียงตัวอักษรที่ไม่สามารถขยายได้ (สมมติว่าIFS
ไม่มีตัวเลขหรือ-
)
ดูเมื่อมีความจำเป็นต้องใช้การอ้างอิงสองครั้ง? สำหรับรายละเอียดเพิ่มเติมเกี่ยวกับกรณีต่างๆเมื่อคุณไม่ต้องใส่เครื่องหมายคำพูด
ยกเว้นว่าคุณหมายถึงการที่เรือยนต์ลำนี้จะเกิดขึ้นอย่าลืมใช้เครื่องหมายคำพูดคู่ล้อมรอบตัวแปรและคำสั่งแทนเสมอ ระวัง: การละทิ้งคำพูดไม่เพียง แต่จะทำให้เกิดข้อผิดพลาดเท่านั้น แต่ยังทำให้เกิดปัญหาด้านความปลอดภัยอีกด้วย
ฉันจะประมวลผลรายการชื่อไฟล์ได้อย่างไร
หากคุณเขียนmyfiles="file1 file2"
ด้วยช่องว่างเพื่อแยกไฟล์สิ่งนี้จะไม่สามารถใช้งานได้กับชื่อไฟล์ที่มีช่องว่าง ชื่อไฟล์ Unix สามารถมีอักขระอื่น ๆ นอกเหนือจาก/
(ซึ่งเป็นตัวคั่นไดเรกทอรีเสมอ) และ null ไบต์ (ซึ่งคุณไม่สามารถใช้ในเชลล์สคริปต์กับเชลล์ส่วนใหญ่)
myfiles=*.txt; … process $myfiles
ปัญหาเดียวกันกับ เมื่อคุณทำเช่นนี้ตัวแปรจะmyfiles
มีสตริง 5 ตัวอักษร*.txt
และเมื่อคุณเขียน$myfiles
ว่าอักขระตัวแทนจะถูกขยาย myfiles="$someprefix*.txt"; … process $myfiles
ตัวอย่างเช่นนี้จริงจะทำงานจนกว่าคุณจะเปลี่ยนสคริปต์ของคุณจะ หากsomeprefix
ตั้งค่าเป็นfinal report
สิ่งนี้จะไม่ทำงาน
ในการประมวลผลรายการใด ๆ (เช่นชื่อไฟล์) ให้ใส่ไว้ในอาร์เรย์ สิ่งนี้ต้องใช้ mksh, ksh93, yash หรือ bash (หรือ zsh ซึ่งไม่มีปัญหาการอ้างอิงทั้งหมด) POSIX เชลล์ธรรมดา (เช่นเถ้าหรือเส้นประ) ไม่มีตัวแปรอาเรย์
myfiles=("$someprefix"*.txt)
process "${myfiles[@]}"
Ksh88 มีตัวแปรอาร์เรย์ที่มีไวยากรณ์การกำหนดที่แตกต่างกันset -A myfiles "someprefix"*.txt
(ดูตัวแปรการกำหนดภายใต้สภาพแวดล้อม ksh ที่ต่างกันหากคุณต้องการความสะดวกในการพกพา ksh88 / bash) Bourne / POSIX-style shells มีหนึ่งอาเรย์เดียว, อาเรย์ของพารามิเตอร์ตำแหน่ง"$@"
ที่คุณตั้งค่าด้วยset
และที่อยู่ในท้องถิ่นของฟังก์ชั่น:
set -- "$someprefix"*.txt
process -- "$@"
แล้วชื่อไฟล์ที่ขึ้นต้นด้วย-
ล่ะ
ในบันทึกที่เกี่ยวข้องโปรดจำไว้ว่าชื่อไฟล์สามารถเริ่มต้นด้วย-
(ขีด / ลบ) ซึ่งคำสั่งส่วนใหญ่ตีความว่าเป็นการแสดงตัวเลือก หากคุณมีชื่อไฟล์ที่เริ่มต้นด้วยส่วนตัวแปรตรวจสอบให้แน่ใจว่าได้ส่ง--
ก่อนหน้าเช่นเดียวกับในตัวอย่างข้างต้น -
นี้บ่งชี้คำสั่งว่าจะได้ถึงจุดสิ้นสุดของตัวเลือกเพื่ออะไรหลังจากนั้นเป็นชื่อไฟล์แม้ว่าจะเริ่มต้นด้วย
-
หรือคุณสามารถตรวจสอบให้แน่ใจว่าชื่อไฟล์ของคุณเริ่มต้นด้วยตัวอักษรอื่นที่ไม่ใช่ ชื่อไฟล์ที่แน่นอนเริ่มต้นด้วย/
และคุณสามารถเพิ่ม./
ที่จุดเริ่มต้นของชื่อญาติ ตัวอย่างต่อไปนี้จะเปลี่ยนเนื้อหาของตัวแปรf
เป็นวิธีที่“ปลอดภัย” -
ของหมายถึงไฟล์เดียวกันที่รับประกันไม่ได้เริ่มต้นด้วย
case "$f" in -*) "f=./$f";; esac
ในบันทึกสุดท้ายในหัวข้อนี้ระวังว่าคำสั่งบางคนตีความเป็นความหมายเข้ามาตรฐานหรือมาตรฐานการส่งออกแม้หลังจากที่-
--
หากคุณต้องการอ้างถึงไฟล์จริงชื่อ-
หรือถ้าคุณกำลังเรียกโปรแกรมดังกล่าวและคุณไม่ต้องการให้มันอ่านจาก stdin หรือเขียนไปยัง stdout ตรวจสอบให้แน่ใจว่าได้เขียนใหม่-
ดังกล่าวข้างต้น ดูความแตกต่างระหว่าง "du -sh *" และ "du -sh ./*" คืออะไร สำหรับการสนทนาต่อไป
ฉันจะเก็บคำสั่งในตัวแปรได้อย่างไร
“ Command” อาจหมายถึงสามสิ่ง: ชื่อคำสั่ง (ชื่อเป็นไฟล์เรียกทำงานที่มีหรือไม่มีพา ธ เต็มหรือชื่อของฟังก์ชันบิวด์อินหรือนามแฝง) ชื่อคำสั่งที่มีอาร์กิวเมนต์หรือชิ้นส่วนของรหัสเชลล์ มีวิธีที่แตกต่างกันในการจัดเก็บไว้ในตัวแปร
หากคุณมีชื่อคำสั่งให้เก็บไว้และใช้ตัวแปรพร้อมเครื่องหมายคำพูดคู่ตามปกติ
command_path="$1"
…
"$command_path" --option --message="hello world"
หากคุณมีคำสั่งที่มีอาร์กิวเมนต์ปัญหาจะเหมือนกับรายการของชื่อไฟล์ด้านบน: นี่คือรายการของสตริงไม่ใช่สตริง คุณไม่สามารถคัดอาร์กิวเมนต์เป็นสตริงเดียวโดยมีช่องว่างในระหว่างเพราะถ้าคุณทำคุณไม่สามารถบอกความแตกต่างระหว่างช่องว่างที่เป็นส่วนหนึ่งของข้อโต้แย้งและช่องว่างที่แยกอาร์กิวเมนต์ หากเปลือกของคุณมีอาร์เรย์คุณสามารถใช้พวกเขา
cmd=(/path/to/executable --option --message="hello world" --)
cmd=("${cmd[@]}" "$file1" "$file2")
"${cmd[@]}"
ถ้าคุณใช้เชลล์ที่ไม่มีอาร์เรย์ คุณยังคงสามารถใช้พารามิเตอร์ตำแหน่งได้หากคุณไม่ต้องการแก้ไขค่าเหล่านั้น
set -- /path/to/executable --option --message="hello world" --
set -- "$@" "$file1" "$file2"
"$@"
ถ้าคุณต้องการเก็บคำสั่งเชลล์ที่ซับซ้อนเช่นการเปลี่ยนเส้นทางท่อ ฯลฯ หรือถ้าคุณไม่ต้องการแก้ไขพารามิเตอร์ตำแหน่ง? จากนั้นคุณสามารถสร้างสตริงที่มีคำสั่งและใช้eval
builtin
code='/path/to/executable --option --message="hello world" -- /path/to/file1 | grep "interesting stuff"'
eval "$code"
หมายเหตุ: คำพูดที่ซ้อนกันในความหมายของcode
: ราคาเดียว'…'
คั่นสตริงตัวอักษรเพื่อให้ค่าของตัวแปรเป็นสตริงcode
builtin บอกเปลือกจะแยกสตริงผ่านเป็นอาร์กิวเมนต์เป็นถ้ามันปรากฏในสคริปต์เพื่อให้จุดที่คำพูดและท่อมีการแยกวิเคราะห์ ฯลฯ/path/to/executable --option --message="hello world" -- /path/to/file1
eval
การใช้eval
เป็นเรื่องยุ่งยาก คิดให้รอบคอบเกี่ยวกับสิ่งที่ถูกแยกวิเคราะห์เมื่อ โดยเฉพาะอย่างยิ่งคุณไม่สามารถใส่ชื่อไฟล์ลงในรหัสได้: คุณจำเป็นต้องอ้างอิงมันเช่นเดียวกับที่คุณทำถ้ามันอยู่ในไฟล์ซอร์สโค้ด ไม่มีวิธีโดยตรงที่จะทำเช่นนั้น สิ่งที่ต้องการcode="$code $filename"
แบ่งถ้าชื่อไฟล์มีเปลือกอักขระพิเศษใด ๆ (พื้นที่, $
, ;
, |
, <
, >
ฯลฯ ) ยังคงแบ่งบนcode="$code \"$filename\""
"$\`
แม้จะหยุดพักหากชื่อไฟล์ที่มีcode="$code '$filename'"
'
มีสองวิธีแก้ไข
เพิ่มเลเยอร์ของเครื่องหมายคำพูดรอบ ๆ ชื่อไฟล์ วิธีที่ง่ายที่สุดที่จะทำคือการเพิ่มราคาเดียวรอบ ๆ '\''
มันและแทนที่คำพูดเดียวโดย
quoted_filename=$(printf %s. "$filename" | sed "s/'/'\\\\''/g")
code="$code '${quoted_filename%.}'"
คงการขยายตัวของตัวแปรไว้ในโค้ดเพื่อให้มันค้นหาเมื่อมีการประเมินโค้ดไม่ใช่เมื่อมีการสร้างแฟรกเมนต์โค้ด สิ่งนี้ง่ายกว่า แต่จะใช้ได้ก็ต่อเมื่อตัวแปรยังคงมีค่าเท่าเดิมในขณะที่มีการเรียกใช้งานรหัสไม่ใช่เช่นถ้ามีการสร้างรหัสในลูป
code="$code \"\$filename\""
สุดท้ายคุณต้องการตัวแปรที่มีรหัสหรือไม่? วิธีที่เป็นธรรมชาติที่สุดในการตั้งชื่อให้กับ code block คือการกำหนดฟังก์ชั่น
อะไรขึ้นกับread
?
โดยไม่ต้อง-r
, read
ช่วยให้เส้นต่อเนื่อง - นี้เป็นสายเชิงตรรกะเดียวของการป้อนข้อมูล:
hello \
world
read
แยกบรรทัดอินพุตออกเป็นฟิลด์ที่คั่นด้วยอักขระใน$IFS
(โดยไม่ใช้-r
แบ็กสแลชก็จะหลีกเลี่ยงสิ่งเหล่านั้น) ตัวอย่างเช่นหากอินพุตเป็นบรรทัดที่มีสามคำให้read first second third
ตั้งค่าfirst
เป็นคำแรกของอินพุตsecond
เป็นคำที่สองและthird
เป็นคำที่สาม หากมีคำมากกว่านี้ตัวแปรสุดท้ายจะมีทุกอย่างที่เหลือหลังจากตั้งค่าก่อนหน้า ช่องว่างนำหน้าและต่อท้ายถูกตัดแต่ง
การตั้งค่าIFS
เป็นสตริงว่างหลีกเลี่ยงการตัดแต่งใด ๆ ดูว่าเหตุใด `ในขณะที่ IFS = read` ใช้บ่อยๆแทนที่จะเป็น` IFS =; ในขณะที่อ่าน .. สำหรับคำอธิบายที่ยาวขึ้น
มีอะไรผิดปกติกับxargs
?
รูปแบบการป้อนข้อมูลของxargs
สตริงที่คั่นด้วยช่องว่างซึ่งอาจเป็นทางเลือกเดียวหรือสองครั้งที่ยกมา ไม่มีเครื่องมือมาตรฐานส่งออกรูปแบบนี้
อินพุตxargs -L1
หรือxargs -l
เกือบเป็นรายการของเส้น แต่ไม่มาก - หากมีช่องว่างที่ท้ายบรรทัดบรรทัดต่อไปนี้เป็นบรรทัดต่อเนื่อง
คุณสามารถใช้ในxargs -0
กรณีที่สามารถใช้งานได้(และหากมี: GNU (Linux, Cygwin), BusyBox, BSD, OSX แต่ไม่ได้อยู่ใน POSIX) ปลอดภัยเนื่องจากไบต์ null ไม่สามารถปรากฏในข้อมูลส่วนใหญ่โดยเฉพาะในชื่อไฟล์ ในการสร้างรายการชื่อไฟล์ที่คั่นด้วย null ให้ใช้find … -print0
(หรือคุณสามารถใช้find … -exec …
ตามที่อธิบายไว้ด้านล่าง)
ฉันจะประมวลผลไฟล์ที่พบได้find
อย่างไร
find … -exec some_command a_parameter another_parameter {} +
some_command
จำเป็นต้องเป็นคำสั่งภายนอกมันไม่สามารถเป็นฟังก์ชันของเชลล์หรือนามแฝงได้ หากคุณต้องการเรียกใช้เชลล์เพื่อประมวลผลไฟล์ให้เรียกsh
อย่างชัดเจน
find … -exec sh -c '
for x do
… # process the file "$x"
done
' find-sh {} +
ฉันมีคำถามอื่น
เรียกดูข้อความแท็กในเว็บไซต์นี้หรือเปลือกหรือเปลือกสคริปต์ (คลิกที่ปุ่ม“เรียนรู้เพิ่มเติม ...” เพื่อดูเคล็ดลับทั่วไปและรายการมือเลือกคำถามที่พบบ่อย.) หากคุณได้ค้นหาและคุณไม่สามารถหาคำตอบถามออกไป
shellcheck
ช่วยคุณปรับปรุงคุณภาพโปรแกรมของคุณ