มีใครบางคนบอกฉันว่าฉันควรจะใส่เครื่องหมายคำพูดล้อมรอบตัวแปรในเชลล์สคริปต์หรือไม่
ตัวอย่างเช่นแก้ไขต่อไปนี้:
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" - (ไม่สามารถใช้ในสถานการณ์ดังกล่าวได้ดังนั้นควรระวังไว้ก่อนใช้)