มีใครบางคนบอกฉันว่าฉันควรจะใส่เครื่องหมายคำพูดล้อมรอบตัวแปรในเชลล์สคริปต์หรือไม่
ตัวอย่างเช่นแก้ไขต่อไปนี้:
xdg-open $URL
[ $? -eq 2 ]
หรือ
xdg-open "$URL"
[ "$?" -eq "2" ]
และถ้าเป็นเช่นนั้นทำไม
มีใครบางคนบอกฉันว่าฉันควรจะใส่เครื่องหมายคำพูดล้อมรอบตัวแปรในเชลล์สคริปต์หรือไม่
ตัวอย่างเช่นแก้ไขต่อไปนี้:
xdg-open $URL
[ $? -eq 2 ]
หรือ
xdg-open "$URL"
[ "$?" -eq "2" ]
และถ้าเป็นเช่นนั้นทำไม
คำตอบ:
กฎทั่วไป: อ้างว่ามันสามารถว่างเปล่าหรือมีช่องว่าง (หรือช่องว่างใด ๆ จริง ๆ ) หรืออักขระพิเศษ (สัญลักษณ์) การไม่อ้างสตริงที่มีช่องว่างมักจะนำไปสู่เชลล์แยกอาร์กิวเมนต์เดี่ยวออกเป็นหลาย ๆ
$?
ไม่จำเป็นต้องมีเครื่องหมายคำพูดเนื่องจากเป็นค่าตัวเลข ไม่ว่าจะเป็น$URL
ความต้องการของมันขึ้นอยู่กับสิ่งที่คุณอนุญาตให้อยู่ในที่นั่นและไม่ว่าคุณยังคงต้องการอาร์กิวเมนต์ถ้ามันว่างเปล่า
ฉันมักจะอ้างสายอักขระไม่ให้นิสัยเสมอเพราะมันปลอดภัยกว่านี้
IFS=0
เช่นนั้นก็echo $?
น่าแปลกใจมาก
cp $source1 $source2 $dest
แต่ถ้าด้วยเหตุผลบางอย่างที่ไม่คาดคิดdest
ไม่ได้รับการตั้งค่าอาร์กิวเมนต์ที่สามเพียงแค่หายไปและมันเงียบจะคัดลอกsource1
มากกว่าsource2
แทนที่จะให้คุณ ข้อผิดพลาดที่เหมาะสมสำหรับปลายทางที่ว่างเปล่า (อย่างที่มันจะมีถ้าคุณได้อ้างแต่ละข้อโต้แย้ง)
quote it if...
มีกระบวนการคิดย้อนหลัง - คำพูดไม่ใช่สิ่งที่คุณเพิ่มเมื่อคุณต้องการมันเป็นสิ่งที่คุณลบเมื่อคุณต้องการ ห่อสตริงและสคริปต์ในเครื่องหมายคำพูดเดี่ยวทุกครั้งเว้นแต่คุณจะต้องใช้เครื่องหมายคำพูดคู่ (เช่นเพื่อให้ตัวแปรขยายได้) หรือไม่จำเป็นต้องใช้เครื่องหมายคำพูด (เช่นเพื่อทำการขยายแบบวงกลมและการขยายชื่อไฟล์)
ในระยะสั้นอ้างทุกอย่างที่คุณไม่ต้องการเปลือกเพื่อดำเนินการแยกโทเค็นและการขยายตัวสัญลักษณ์
เครื่องหมายคำพูดเดี่ยวปกป้องข้อความระหว่างคำต่อคำ เป็นเครื่องมือที่เหมาะสมเมื่อคุณต้องการให้แน่ใจว่าเชลล์ไม่ได้สัมผัสสตริงเลย โดยปกติแล้วมันเป็นกลไกการอ้างอิงที่เลือกเมื่อคุณไม่ต้องการการแก้ไขตัวแปร
$ echo 'Nothing \t in here $will change'
Nothing \t in here $will change
$ grep -F '@&$*!!' file /dev/null
file:I can't get this @&$*!! quoting right.
เครื่องหมายคำพูดคู่เหมาะสำหรับการแก้ไขตัวแปรที่จำเป็น ด้วยการดัดแปลงที่เหมาะสมจะเป็นการแก้ปัญหาที่ดีเมื่อคุณต้องการอัญประกาศเดี่ยวในสตริง (ไม่มีวิธีง่ายๆในการหลีกเลี่ยงการอ้างคำเดียวระหว่างคำพูดเดียวเพราะไม่มีกลไกการหลบหนีในเครื่องหมายคำพูดเดียว - ถ้ามีพวกเขาจะไม่พูดคำต่อคำอย่างสมบูรณ์)
$ echo "There is no place like '$HOME'"
There is no place like '/home/me'
ไม่มีเครื่องหมายอัญประกาศที่เหมาะสมเมื่อคุณต้องการให้เชลล์ทำการแยกโทเค็นและ / หรือการขยายสัญลักษณ์แทน
การแยกโทเค็น;
$ words="foo bar baz"
$ for word in $words; do
> echo "$word"
> done
foo
bar
baz
ตรงกันข้าม:
$ for word in "$words"; do echo "$word"; done
foo bar baz
(การวนซ้ำจะทำงานเพียงครั้งเดียวบนสตริงที่ยกมาเดี่ยว)
$ for word in '$words'; do echo "$word"; done
$words
(การวนซ้ำจะทำงานเพียงครั้งเดียวเหนือสตริงที่มีเครื่องหมายคำพูดเดียว)
การขยายตัวไวด์การ์ด:
$ pattern='file*.txt'
$ ls $pattern
file1.txt file_other.txt
ตรงกันข้าม:
$ ls "$pattern"
ls: cannot access file*.txt: No such file or directory
(ไม่มีไฟล์ชื่อแท้จริงfile*.txt
)
$ ls '$pattern'
ls: cannot access $pattern: No such file or directory
(ไม่มีไฟล์ชื่อ $pattern
เช่นกัน!)
ในแง่ที่เป็นรูปธรรมมากขึ้นสิ่งที่มีชื่อไฟล์ควรจะยกมา (เพราะชื่อไฟล์สามารถมีช่องว่างและ metacharacters เชลล์อื่น ๆ ) สิ่งใดก็ตามที่มี URL ควรจะเสนอราคา (เพราะ URL จำนวนมากมีอักขระเมตาของเชลล์เช่น?
และ&
) สิ่งที่มี regex ควรจะยกมา (ditto ditto) สิ่งใดก็ตามที่มีช่องว่างที่มีนัยสำคัญนอกเหนือจากช่องว่างเดี่ยวระหว่างอักขระที่ไม่ใช่ช่องว่างจำเป็นต้องอ้างถึง (เพราะมิเช่นนั้นเชลล์จะรวมช่องว่างเข้าไว้ในช่องว่างได้อย่างมีประสิทธิภาพช่องว่างเดี่ยวและตัดช่องว่างนำหน้า
เมื่อคุณรู้ว่าตัวแปรสามารถมีได้เฉพาะค่าที่ไม่มีเชลล์อักขระเมตาอักขระการอ้างอิงเป็นตัวเลือก ดังนั้น unquote $?
นั้นดีเพราะตัวแปรนี้สามารถมีจำนวนเดียวเท่านั้น อย่างไรก็ตาม"$?"
ก็ถูกต้องและแนะนำสำหรับความสอดคล้องและความถูกต้องทั่วไป (แม้ว่านี่จะเป็นคำแนะนำส่วนตัวของฉันไม่ใช่นโยบายที่ได้รับการยอมรับอย่างกว้างขวาง)
ค่าที่ไม่ใช่ตัวแปรโดยทั่วไปจะเป็นไปตามกฎเดียวกันแม้ว่าคุณจะสามารถหลีกเลี่ยงเมทาเตอเรเตอร์ใด ๆ แทนที่จะอ้างถึง สำหรับตัวอย่างทั่วไป URL ที่มี&
ในจะถูกแยกวิเคราะห์โดยเชลล์เป็นคำสั่งพื้นหลังยกเว้นเมตาอักขระจะหนีหรืออ้างอิง:
$ wget http://example.com/q&uack
[1] wget http://example.com/q
-bash: uack: command not found
(แน่นอนสิ่งนี้จะเกิดขึ้นหาก URL อยู่ในตัวแปรที่ไม่มีเครื่องหมายอัญประกาศ) สำหรับสตริงแบบคงที่เครื่องหมายอัญประกาศเดี่ยวมีความหมายมากที่สุดแม้ว่ารูปแบบของการอ้างอิงหรือการหลบหนีจะทำงานที่นี่
wget 'http://example.com/q&uack' # Single quotes preferred for a static string
wget "http://example.com/q&uack" # Double quotes work here, too (no $ or ` in the value)
wget http://example.com/q\&uack # Backslash escape
wget http://example.com/q'&'uack # Only the metacharacter really needs quoting
ตัวอย่างสุดท้ายยังเสนอแนวคิดที่มีประโยชน์อีกประการหนึ่งซึ่งฉันชอบเรียกว่า "กระดานหกอ้างอิง" หากคุณต้องการผสมคำพูดเดี่ยวและคู่คุณสามารถใช้คำพูดที่อยู่ติดกัน ตัวอย่างเช่นสตริงที่ยกมาต่อไปนี้
'$HOME '
"isn't"
' where `<3'
"' is."
สามารถวางรวมกันกลับไปกลับมาก่อตัวเป็นสตริงยาว ๆ เดียวหลังจาก tokenization และการลบเครื่องหมายคำพูด
$ echo '$HOME '"isn't"' where `<3'"' is."
$HOME isn't where `<3' is.
มันไม่ชัดเจนมากนัก แต่มันเป็นเทคนิคทั่วไปและน่ารู้
นอกเหนือจากนี้สคริปต์ไม่ควรใช้ls
เพื่อสิ่งใด ในการขยายไวด์การ์ดให้ใช้ ... ใช้มัน
$ printf '%s\n' $pattern # not ``ls -1 $pattern''
file1.txt
file_other.txt
$ for file in $pattern; do # definitely, definitely not ``for file in $(ls $pattern)''
> printf 'Found file: %s\n' "$file"
> done
Found file: file1.txt
Found file: file_other.txt
(ลูปมีความฟุ่มเฟือยอย่างสมบูรณ์ในตัวอย่างหลังprintf
ทำงานได้ดีกับอาร์กิวเมนต์หลายตัวโดยเฉพาะstat
เช่นกัน แต่การวนซ้ำในการจับคู่ไวด์การ์ดนั้นเป็นปัญหาที่พบบ่อยและทำผิดพลาดบ่อยครั้ง)
ตัวแปรที่มีรายการของโทเค็นที่จะวนซ้ำหรือมีการเพิ่มสัญลักษณ์แทนจะเห็นได้ไม่บ่อยนักดังนั้นบางครั้งเราจึงย่อให้ "พูดทุกอย่างยกเว้นว่าคุณรู้ว่าคุณกำลังทำอะไรอยู่"
นี่คือสูตรสามจุดสำหรับคำพูดโดยทั่วไป:
เครื่องหมายคำพูดคู่
ในบริบทที่เราต้องการยับยั้งการแยกคำและการโค้งคำ นอกจากนี้ในบริบทที่เราต้องการให้ตัวอักษรเป็นสตริงไม่ใช่ regex
คำพูดเดียว
ในสตริงตัวอักษรที่เราต้องการที่จะปราบปรามการแก้ไขและการรักษาพิเศษของแบ็กสแลช กล่าวอีกนัยหนึ่งสถานการณ์ที่ใช้เครื่องหมายคำพูดคู่จะไม่เหมาะสม
ไม่มีคำพูด
ในบริบทที่เรามั่นใจอย่างแน่นอนว่าไม่มีการแยกคำหรือการแยกประเด็นหรือเราต้องการการแยกคำและกลมglobbing
ตัวอย่าง
เครื่องหมายคำพูดคู่
"StackOverflow rocks!"
, "Steve's Apple"
)"$var"
, "${arr[@]}"
)"$(ls)"
, "`ls`"
)"/my dir/"*
)"single'quote'delimited'string"
)"${filename##*/}"
)คำพูดเดียว
'Really costs $$!'
, 'just a backslash followed by a t: \t'
)'The "crux"'
)$'\n\t'
)$'{"table": "users", "where": "first_name"=\'Steve\'}'
)ไม่มีคำพูด
$$
, $?
, $#
ฯลฯ )((count++))
, "${arr[idx]}"
,"${string:start:length}"
[[ ]]
การแสดงออกภายในซึ่งปราศจากปัญหาการแยกคำและการแยกเป็นวงกลม (นี่เป็นเรื่องของสไตล์และความคิดเห็นอาจแตกต่างกันอย่างกว้างขวาง)for word in $words
)for txtfile in *.txt; do ...
)~
ตีความว่าเป็น$HOME
( ~/"some dir"
แต่ไม่ใช่"~/some dir"
)ดูสิ่งนี้ด้วย:
"ls" "/"
วลี "บริบทสตริงทั้งหมด" จะต้องมีคุณสมบัติอย่างรอบคอบมากขึ้น
[[ ]]
อ้างอิงมีความสำคัญทางด้านขวามือของ=
/ ==
และ=~
: มันสร้างความแตกต่างระหว่างการตีความสตริงเป็นรูปแบบ / regex หรือแท้จริง
$'...'
) ควรมีส่วนของตัวเองอย่างแน่นอน
"ls" "/"
แทนที่จะเป็นเรื่องธรรมดามากขึ้นls /
และฉันถือว่าเป็นข้อบกพร่องที่สำคัญในแนวทาง
case
:)
ฉันมักจะใช้คำพูดที่อ้างถึง"$var"
เพื่อความปลอดภัยเว้นแต่ฉันจะแน่ใจ$var
ไม่มีที่ว่าง
ฉันใช้$var
เป็นวิธีง่ายๆในการเข้าร่วมรายการ:
lines="`cat multi-lines-text-file.txt`"
echo "$lines" ## multiple lines
echo $lines ## all spaces (including newlines) are zapped
สำหรับการใช้ตัวแปรในเชลล์สคริปต์ให้ใช้ตัวแปรที่ยกมา "" เป็นตัวแปรที่ยกมาหมายความว่าตัวแปรอาจมีช่องว่างหรืออักขระพิเศษซึ่งจะไม่ส่งผลต่อการทำงานของเชลล์สคริปต์ของคุณ มิฉะนั้นถ้าคุณแน่ใจว่าไม่มีช่องว่างหรืออักขระพิเศษในชื่อตัวแปรของคุณคุณสามารถใช้มันโดยไม่ต้อง ""
ตัวอย่าง:
echo "$ url name" - (สามารถใช้ได้ตลอดเวลา)
echo "$ url name" - (ไม่สามารถใช้ในสถานการณ์ดังกล่าวได้ดังนั้นควรระวังไว้ก่อนใช้)