วิธีการทำเช่นนี้อย่างถูกต้อง
ก่อนอื่นให้อ้างอิงตัวแปรของคุณเสมอ สิ่งที่คุณพยายามจะทำงานได้ดีถ้าคุณพูดอย่างถูกต้อง:
$ pwd
/home/terdon/foo/\e[92mM@r|< +'|'|_e|\|\|0rth [`-_-"]
$ ls
pullingATerdon
ฉันได้เก็บชื่อไฟล์แปลก ๆ ที่คุณเลือกไว้ ( แม้ว่าฉันจะไม่รู้ว่าทำไมคุณถึงเลือก ) เพื่อความมั่นคง
ทีนี้มากำหนดเส้นทางของpullingATerdon
ตัวแปรแล้วลองเปิดไฟล์:
$ bacon="$(realpath pullingATerdon)"
$ echo "$bacon"
/home/terdon/foo/\e[92mM@r|< +'|'|_e|\|\|0rth [`-_-"]/pullingATerdon
$ ls $bacon
ls: cannot access '+'\''|'\''|_e|\|\|0rth': No such file or directory
ls: cannot access '[`-_-"]/pullingATerdon': No such file or directory
'/home/terdon/foo/\e[92mM@r|<':
ที่ล้มเหลวตามที่คาดไว้ แต่ถ้าตอนนี้เราพูดถูกต้อง:
$ ls -l "$bacon"
-rw-r--r-- 1 terdon terdon 0 Mar 14 23:15 '/home/terdon/foo/\e[92mM@r|< +'\''|'\''|_e|\|\|0rth [`-_-"]/pullingATerdon'
มันทำงานได้ตามที่คาดไว้ และใช่คุณยังสามารถเปิดเส้นทางในตัวแก้ไข (เหมาะสม): emacs "$bacon"
จะทำงานได้ดี ตกลงดังนั้นจะvim
และสิ่งอื่นใด ตัวเลือกการแก้ไขที่คุณโชคร้ายนั้นไม่เกี่ยวข้องกัน
ทำไมคุณล้มเหลว
วิธีที่รวดเร็วในการติดตามสิ่งที่เกิดขึ้นจริงในกรณีของคุณคือการใช้set -x
(ปิดอีกครั้งด้วยset +x
) ซึ่งทำให้เชลล์พิมพ์คำสั่งแต่ละคำสั่งที่มันจะทำงานก่อนรัน เปิดข้อความการดีบักของเชลล์ด้วยset -x
:
$ set -x
$ /bin/ls $bacon
+ ls '/home/terdon/foo/\e[92mM@r|<' '+'\''|'\''|_e|\|\|0rth' '[`-_-"]/pullingATerdon'
ls: cannot access '+'\''|'\''|_e|\|\|0rth': No such file or directory
ls: cannot access '[`-_-"]/pullingATerdon': No such file or directory
'/home/terdon/foo/\e[92mM@r|<':
แสดงให้เห็นว่าเราว่าls
กำลังวิ่งสามข้อโต้แย้งเฉพาะกิจการ: '/home/terdon/foo/\e[92mM@r|<'
, และ'+'\''|'\''|_e|\|\|0rth'
'[`-_-"]/pullingATerdon'
สิ่งนี้เกิดขึ้นเนื่องจากเชลล์ทำการแยกคำและการขยายแบบกลมบนสตริงที่ไม่ได้ระบุ ในกรณีนี้ปัญหาคือการแยกคำเนื่องจากเชลล์เห็นช่องว่างในเส้นทางและอ่านแต่ละสตริงที่คั่นด้วยช่องว่างเป็นอาร์กิวเมนต์แยก
mkdir
ตัวอย่างแตกต่างกันเล็กน้อย แต่นั่นเป็นเพราะคุณกำลังแสดงให้เราเห็นข้อผิดพลาดจากที่สองภาวนาของคำสั่ง ฉันเดาว่าคุณลองครั้งเดียวแล้วก็วิ่งอีกเป็นครั้งที่สองเพื่อให้ได้ผลลัพธ์สำหรับคำถามของคุณ ครั้งแรกที่คุณวิ่งมันจะมีลักษณะเช่นนี้:
$ mkdir $(realpath pullingATerdon)
++ realpath pullingATerdon
+ mkdir '/home/terdon/foo/\e[92mM@r|<' '+'\''|'\''|_e|\|\|0rth' '[`-_-"]/pullingATerdon'
mkdir: cannot create directory ‘[`-_-"]/pullingATerdon’: No such file or directory
อีกครั้งที่จะพยายามสร้างสามไดเรกทอรีไม่ใช่หนึ่งเนื่องจากการแยกคำ ก่อนอื่นมันสร้าง (สำเร็จ) ไดเรกทอรี/home/terdon/foo/\e[92mM@r|<
:
$ ls -l /home/terdon/foo/
total 8
drwxr-xr-x 2 terdon terdon 4096 Mar 15 00:20 '\e[92mM@r|<'
drwxr-xr-x 3 terdon terdon 4096 Mar 15 00:20 '\e[92mM@r|< +'\''|'\''|_e|\|\|0rth [`-_-"]'
สร้างไดเรกทอรีที่เรียกว่า+'|'|_e|\|\|0rth
ในไดเรกทอรีปัจจุบันของคุณได้สำเร็จแล้ว:
$ ls -l
total 4
drwxr-xr-x 2 terdon terdon 4096 Mar 15 00:37 '+'\''|'\''|_e|\|\|0rth'
-rw-r--r-- 1 terdon terdon 0 Mar 15 00:36 pullingATerdon
[`-_-"]/pullingATerdon
และจากนั้นก็พยายามที่จะสร้างไดเรกทอรี สิ่งนี้ล้มเหลวเพราะmkdir
ตามค่าเริ่มต้นจะไม่สร้างไดเรกทอรีย่อย (สามารถทำได้หากคุณเรียกใช้ด้วย-p
):
$ mkdir baz/bar
mkdir: cannot create directory ‘baz/bar’: No such file or directory
เนื่องจากสตริงที่ไม่มีเครื่องหมายอัญประกาศของคุณประกอบด้วย a /
, ให้mkdir
พิจารณาว่าพา ธ ของสองไดเรกทอรีพยายามค้นหาหนึ่งอันดับแรกและล้มเหลว
นั่นเป็นสาเหตุที่มันล้มเหลว แต่สิ่งที่เกิดขึ้นนั้นซับซ้อนกว่า สตริงที่คุณใช้เป็นจริง glob เปลือกเฉพาะช่วง globซึ่งตรงกับไฟล์ทั้งหมดในไดเรกทอรีปัจจุบันที่มีชื่อเป็นหนึ่งใน 5 ตัวอักษร`
, -
, หรือ_
"
เนื่องจากคุณไม่มีไฟล์ดังกล่าวในไดเรกทอรีปัจจุบันของคุณ glob จึงไม่ตรงกับสิ่งใดเลยและเช่นเดียวกับพฤติกรรมเริ่มต้นใน bash จึงส่งคืนตัวเอง:
$ echo "[\`-_-\"]/pullingATerdon" ## some escaping is needed here
+ echo '[`-_-"]/pullingATerdon' ## but it echoes the right thing
[`-_-"]/pullingATerdon ## and matches nothing, so returns itself.
หากต้องการอธิบายให้ชัดเจนนี่คือสิ่งที่จะเกิดขึ้นหากคุณให้รูปกลมที่ตรงกับบางสิ่ง:
$ echo [p]* ## any filename starting with a p
pullingATerdon
$ echo "[p]*" ## the string "[p]*"
[p]*
unquoted [p*]
มีการขยายไปยังรายการของชื่อไฟล์การจับคู่ (เพียงหนึ่งในกรณีนี้) echo
และนั่นคือสิ่งที่ถูกส่งไปยัง อีกเหตุผลที่ทำไมคุณควรพูดทุกสิ่ง
สุดท้ายข้อผิดพลาดจริงที่คุณแสดงนั้นมาจากครั้งที่สองที่คุณรันคำสั่งและล้มเหลวในขั้นตอนแรกเมื่อพยายามสร้าง/home/terdon/foo/\e[92mM@r|<
เนื่องจากการเรียกใช้ก่อนหน้านี้ได้สร้างไดเรกทอรีนั้นแล้ว
โดยทั่วไปเมื่อใดก็ตามที่คุณพบว่าตัวเองทำงานกับชื่อไฟล์เองให้ใช้คำสั่ง globs ทุกครั้ง สิ่งนี้:
for file in *; do command "$file"; done
มันจะใช้ได้กับชื่อไฟล์ใด ๆ ไม่ว่าจะเกิดอะไรขึ้นกับการบรรจุ ในตัวอย่างของเราด้านบนคุณสามารถทำได้:
emacs /home/terdon/*92mM*/pullingATerdon
glob ใด ๆ ที่ระบุไฟล์เป้าหมายจะทำเฉพาะ ด้วยวิธีนี้คุณไม่ต้องกังวลกับตัวละครพิเศษและปล่อยให้เชลล์จัดการกับมันได้
ข้อมูลอ้างอิงที่มีประโยชน์บางประการ:
ฉันจะค้นหาและจัดการชื่อไฟล์ที่มีการขึ้นบรรทัดใหม่เว้นวรรคหรือทั้งสองอย่างปลอดภัยได้อย่างไร : หนึ่งในคำถามที่พบบ่อยเกี่ยวกับ Grey Cat's Wiki ที่ยอดเยี่ยม
ผลกระทบด้านความปลอดภัยของการลืมอ้างตัวแปรในเชลล์ bash / POSIX : โพสต์เดียวกับที่ฉันอ้างถึงตอนต้นของคำตอบนี้ คำอธิบายที่ยอดเยี่ยมและมีรายละเอียดมากสำหรับทุกสิ่งที่อาจผิดพลาดหากคุณไม่สามารถอ้างอิงตัวแปรเชลล์ได้อย่างถูกต้อง
ทำไมเชลล์สคริปต์ของฉันถึงสำลักในช่องว่างหรืออักขระพิเศษอื่น ๆ : ทุกสิ่งที่คุณอยากรู้เกี่ยวกับการจัดการชื่อไฟล์โดยพลการในเชลล์
การอ้างอิงสองครั้งจำเป็นเมื่อใด : ข้อมูลเพิ่มเติมเกี่ยวกับคำพูดและตัวแปรและโดยเฉพาะกรณีที่คุณไม่จำเป็นต้องพูดพวกเขา
vim "$bacon"