ฉันจะหลีกเลี่ยงอักขระตัวแทน / เครื่องหมายดอกจันใน bash ได้อย่างไร


140

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

me$ FOO="BAR * BAR"
me$ echo $FOO
BAR file1 file2 file3 file4 BAR

และใช้\อักขระหนี:

me$ FOO="BAR \* BAR"
me$ echo $FOO
BAR \* BAR

เห็นได้ชัดว่าฉันทำอะไรโง่ ๆ

ฉันจะรับผลลัพธ์ได้BAR * BARอย่างไร

คำตอบ:


146

การอ้างถึงเมื่อการตั้งค่า$FOOไม่เพียงพอ คุณต้องอ้างการอ้างอิงตัวแปรด้วย:

me$ FOO="BAR * BAR"
me$ echo "$FOO"
BAR * BAR

9
มันลึกลับทำไมถึงเป็นเช่นนี้? เกิดอะไรขึ้น?
tofutim

เพราะตัวแปรขยาย
Daniel

109

คำตอบสั้น ๆ

เช่นเดียวกับที่คนอื่น ๆ กล่าว - คุณควรอ้างตัวแปรเพื่อป้องกันพฤติกรรมแปลก ๆ ดังนั้นการใช้echo "$ foo"ในแทนเพียงecho $

คำตอบยาว ๆ

ฉันคิดว่าตัวอย่างนี้รับประกันคำอธิบายเพิ่มเติมเพราะมีอะไรเกิดขึ้นมากกว่าที่จะเห็น

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

  1. การขยายพารามิเตอร์
  2. การขยายชื่อไฟล์

จากตัวอย่างแรกของคุณ:

me$ FOO="BAR * BAR"
me$ echo $FOO

หลังจากการขยายพารามิเตอร์เทียบเท่ากับ:

me$ echo BAR * BAR

และหลังจากการขยายชื่อไฟล์เทียบเท่ากับ:

me$ echo BAR file1 file2 file3 file4 BAR

และถ้าคุณพิมพ์echo BAR * BARลงในบรรทัดคำสั่งคุณจะเห็นว่าเทียบเท่ากัน

คุณคงคิดไปเองว่า "ถ้าฉันหนี * ฉันสามารถป้องกันการขยายชื่อไฟล์ได้"

จากตัวอย่างที่สองของคุณ:

me$ FOO="BAR \* BAR"
me$ echo $FOO

หลังจากการขยายพารามิเตอร์ควรเทียบเท่ากับ:

me$ echo BAR \* BAR

และหลังจากการขยายชื่อไฟล์ควรเทียบเท่ากับ:

me$ echo BAR \* BAR

และถ้าคุณลองพิมพ์ "echo BAR \ * BAR" ลงในบรรทัดคำสั่งโดยตรงมันจะพิมพ์ "BAR * BAR" เนื่องจากการขยายชื่อไฟล์ถูกป้องกันโดยการ Escape

เหตุใดการใช้ $ foo จึงไม่ได้ผล?

เป็นเพราะมีส่วนขยายที่สามเกิดขึ้น - การลบใบเสนอราคา จากการลบใบเสนอราคาด้วยตนเองคือ:

หลังจากการขยายก่อนหน้านี้อักขระ '\', '' 'และ' "'ที่ไม่ได้ใส่เครื่องหมายคำพูดทั้งหมดที่ไม่ได้เป็นผลจากการขยายข้างต้นจะถูกลบออก

สิ่งที่เกิดขึ้นคือเมื่อคุณพิมพ์คำสั่งลงในบรรทัดคำสั่งโดยตรงอักขระหนีไม่ได้เป็นผลลัพธ์ของส่วนขยายก่อนหน้าดังนั้น BASH จึงลบออกก่อนที่จะส่งไปยังคำสั่ง echo แต่ในตัวอย่างที่ 2 "\ *" คือ ผลลัพธ์ของการขยายพารามิเตอร์ก่อนหน้านี้จึงไม่ถูกลบออก ด้วยเหตุนี้เสียงสะท้อนจึงได้รับ "\ *" และนั่นคือสิ่งที่พิมพ์ออกมา

โปรดสังเกตความแตกต่างระหว่างตัวอย่างแรก - "*" ไม่รวมอยู่ในอักขระที่จะถูกลบโดยการลบใบเสนอราคา

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

สำหรับคำอธิบายทั้งหมดของการขยาย BASH โปรดดูที่:

http://www.gnu.org/software/bash/manual/bashref.html#Shell-Expansions


1
ตอบโจทย์มาก! ตอนนี้ฉันไม่รู้สึกว่าฉันถามคำถามโง่ ๆ แบบนั้น :-)
andyuk

1
มียูทิลิตี้บางอย่างเพื่อหลีกเลี่ยงอักขระพิเศษหรือไม่?
Jigar Joshi

"คุณควรอ้างตัวแปรเพื่อป้องกันพฤติกรรมแปลก ๆ " - เมื่อคุณต้องการใช้เป็นสตริง
Angelo


แม้ว่าคำตอบจาก @finnw ด้านบนจะตอบคำถามได้โดยตรง แต่คำตอบนี้ดีกว่ามากในการอธิบายเหตุผลของคำถามนี้ซึ่งช่วยได้มากขึ้นในการคาดการณ์ว่าการใช้ในสถานการณ์ของเราจะได้ผลอย่างไร นี่คือคำตอบที่เราต้องการดูเพิ่มเติม
Nicolas Coombs

56

ฉันจะเพิ่มเล็กน้อยในกระทู้เก่านี้

โดยปกติคุณจะใช้

$ echo "$FOO"

อย่างไรก็ตามฉันมีปัญหากับไวยากรณ์นี้ พิจารณาสคริปต์ต่อไปนี้

#!/bin/bash
curl_opts="-s --noproxy * -O"
curl $curl_opts "$1"

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

curl: option -s --noproxy * -O: is unknown
curl: try 'curl --help' or 'curl --manual' for more information

ดังนั้นฉันขอแนะนำให้ใช้bashตัวแปร$GLOBIGNOREเพื่อป้องกันการขยายชื่อไฟล์โดยสิ้นเชิงหากใช้กับรูปแบบส่วนกลางหรือใช้set -fแฟล็กในตัว

#!/bin/bash
GLOBIGNORE="*"
curl_opts="-s --noproxy * -O"
curl $curl_opts "$1"  ## no filename expansion

นำไปใช้กับตัวอย่างเดิมของคุณ:

me$ FOO="BAR * BAR"

me$ echo $FOO
BAR file1 file2 file3 file4 BAR

me$ set -f
me$ echo $FOO
BAR * BAR

me$ set +f
me$ GLOBIGNORE=*
me$ echo $FOO
BAR * BAR

4
คำอธิบายที่ดีขอบคุณ! กรณีของฉันคือSELECT * FROM etc.นี่เป็นวิธีเดียวที่ใช้ได้ผล
knutole

1
ขอขอบคุณที่นำเสนอโซลูชัน set -f!
hachre

5
FOO='BAR * BAR'
echo "$FOO"

วิธีนี้ใช้ได้ผล แต่ไม่จำเป็นที่จะต้องเปลี่ยนเครื่องหมายคำพูดเดี่ยวในบรรทัดแรกเป็นเครื่องหมายคำพูดคู่
finnw

1
ไม่ไม่ใช่ แต่นิสัยในการใช้เครื่องหมายคำพูดเดี่ยวจะดีกว่าเมื่อคุณจะใส่อักขระเชลล์พิเศษและคุณไม่ต้องการให้มีการแทนที่
tzot


3

มันอาจจะมีมูลค่าที่ได้รับในนิสัยของการใช้printfมากกว่านั้นechoในบรรทัดคำสั่ง

ในตัวอย่างนี้ไม่ได้ให้ประโยชน์มากนัก แต่มีประโยชน์มากกว่าด้วยเอาต์พุตที่ซับซ้อนกว่า

FOO="BAR * BAR"
printf %s "$FOO"

ทำไมต้องอยู่ในนรก? printf เป็นกระบวนการแยกต่างหาก (อย่างน้อยก็ไม่ใช่ bash ในตัว) และการใช้ printf ที่คุณแสดงให้เห็นนั้นไม่มีประโยชน์อะไรกับเสียงสะท้อน
ddaa

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