ค้นหา (1): สัญลักษณ์ตัวแทนของดาวมีการใช้งานอย่างไรเพื่อให้ล้มเหลวในชื่อไฟล์บางส่วน


31

ในระบบไฟล์ที่ชื่อไฟล์อยู่ใน UTF-8 ฉันมีไฟล์ที่มีชื่อผิดพลาด มันจะแสดงเป็น: D�sinstaller, ชื่อจริงตาม zsh D$'\351'sinstaller:, Latin1 สำหรับDésinstaller, ตัวเองป่าเถื่อนฝรั่งเศสสำหรับ "ถอนการติดตั้ง" Zsh จะไม่เข้าคู่กับมัน[[ $file =~ '^.*$' ]]แต่จะจับคู่กับก้อนกลม*- นี่คือพฤติกรรมที่ฉันคาดหวัง

ตอนนี้ฉันยังคงคาดหวังว่าจะพบมันเมื่อทำงานfind . -name '*'- ในความเป็นจริงฉันไม่เคยคาดหวังว่าชื่อไฟล์จะล้มเหลวในการทดสอบนี้ อย่างไรก็ตามด้วยLANG=en_US.utf8ไฟล์ไม่ปรากฏขึ้นและฉันต้องตั้งค่าLANG=C(หรือen_USหรือ'') เพื่อให้มันทำงาน

คำถาม: การดำเนินการที่อยู่เบื้องหลังคืออะไรและฉันจะคาดการณ์ผลลัพธ์ได้อย่างไร

ข้อมูล: Arch Linux 3.14.37-1-lts, find (GNU findutils) 4.4.2


1
คุณคิดว่าconvmvจะแปลงชื่อไฟล์เป็น utf-8 หรือไม่?
ctrl-alt-delor

@ Richard: ในความเป็นจริงฉันใช้เพื่อพึ่งพา[[ $file =~ '^.*$' ]]ความล้มเหลวในการใช้recodeชื่อไฟล์ แต่ตอนนี้ฉันจะดูconvmvถ้าจำเป็นต้องเป็น ขอบคุณ
Michaël

คำตอบ:


25

นั่นเป็นสิ่งที่ดีจริงๆ จากลักษณะอย่างรวดเร็วซอร์สโค้ดสำหรับ GNU หาผมจะพูดแบบนี้เดือดลงไปว่าfnmatchจะทำงานในลำดับไบต์ไม่ถูกต้อง ( pred_name_commonในpred.c):

b = fnmatch (str, base, flags) == 0;
(...)
return b;

รหัสนี้ทดสอบค่าส่งคืนของfnmatchเพื่อความเท่าเทียมกับ 0 แต่ไม่ได้ตรวจสอบข้อผิดพลาด; ผลลัพธ์นี้มีข้อผิดพลาดที่รายงานว่า "ไม่ตรงกัน"

หลายปีที่ผ่านมาได้มีการแนะนำให้เปลี่ยนพฤติกรรมของฟังก์ชั่น libc นี้เพื่อกลับจริงใน*รูปแบบแม้ในชื่อไฟล์ที่แตกสลาย แต่จากสิ่งที่ฉันสามารถบอกความคิดจะต้องถูกปฏิเสธ (ดูกระทู้เริ่มต้นที่https : //sourceware.org/ml/libc-hacker/2002-11/msg00071.html ):

เมื่อ fnmatch ตรวจพบอักขระหลายไบต์ที่ไม่ถูกต้องมันควรกลับไปใช้การจับคู่ไบต์เดียวเพื่อให้ "*" มีโอกาสจับคู่สตริงดังกล่าว

และทำไมจึงดีกว่าหรือถูกกว่า? มีการปฏิบัติที่มีอยู่?

ดังกล่าวโดยStéphane Chazelas ในความคิดเห็นและในเธรด 2002 เดียวกันนี่ไม่สอดคล้องกับการขยาย glob ที่ดำเนินการโดยเชลล์ซึ่งไม่สำลักอักขระที่ไม่ถูกต้อง อาจทำให้งงมากขึ้นก็คือความจริงที่ว่าการย้อนกลับการทดสอบจะจับคู่เฉพาะไฟล์ที่มีชื่อแตก (สร้างไฟล์ใน bash ด้วยtouch $'D\351marrer' $'Touch\303\251' $'\346\227\245\346\234\254\350\252\236'):

$ find -name '*'
.
./Touché
./日本語

$ find -not -name '*'
./D?marrer

ดังนั้นเพื่อตอบคำถามของคุณคุณสามารถทำนายสิ่งนี้ได้ด้วยการรู้พฤติกรรมของคุณfnmatchในกรณีนี้และรู้วิธีfindจัดการกับค่าตอบแทนของฟังก์ชันนี้ คุณอาจไม่สามารถค้นพบโดยการอ่านเอกสารเพียงอย่างเดียว


ฉันเดาว่าทำไมมีการแก้ไขไม่ก็คือว่าแล้วมันจะไม่สอดคล้องกับ* D*staller
ctrl-alt-delor

7
@ Richard ความคิดนี้น่าD*stallerจะตรงกับ$'D\351sinstaller'ที่มันทำในรูปกลมของเปลือกหอยทั้งหมดที่ฉันทดสอบ เนื่องจากพฤติกรรมของ fnmatch ของ GNU นั้นไม่สอดคล้องกับเชลล์ของ GNU ฉันจะบอกว่ามันเป็นข้อผิดพลาด
Stéphane Chazelas

1
คำตอบที่ลึกมาก dhag; ชื่นชมมาก คุณจะช่วยชี้ให้เห็นสเป็คมาตรฐานที่ fnmatch นั้นสอดคล้องหรือไม่? ฉันสามารถค้นหาข้อมูลจำเพาะ POSEX regexp ปกติที่ระบุว่า.ควรตรงกับอักขระที่ถูกต้องในการเข้ารหัสเท่านั้นดังนั้นความคาดหวังของฉันที่.*ไม่ตรงกับสตริงที่ไม่ถูกต้อง - แต่ฉันไม่สามารถหาข้อมูลจำเพาะที่ตรงกันสำหรับดาวกลมได้
Michaël

1
สเปคใกล้เคียงที่สุดที่ฉันสามารถหาออนไลน์อยู่บนหน้า OpenGroup นี้ มันกล่าวว่าการจับคู่จะขึ้นอยู่กับรูปแบบบิตที่ใช้สำหรับการเข้ารหัสตัวละครไม่ได้อยู่ในการแสดงกราฟิกของตัวละคร และ<asterisk> เป็นรูปแบบที่จะจับคู่สตริงใด ๆ รวมถึงสตริงว่าง สิ่งนี้สามารถตีความได้ว่าเป็นคำแนะนำของ @ StéphaneChazelas 13 ปีต่อมาอาจถึงเวลาปิงทวนน้ำอีกครั้ง :-)
Michaël

@ Michaëlฉันไม่พบอะไรที่ดีไปกว่านี้แล้ว บางทีในฐานะของการเปรียบเทียบ GNU find บน Mac OS ทำงานในลักษณะที่สอดคล้องกับการทำให้กลมของเชลล์ (เช่น-name '*'จับคู่ไฟล์ทั้งหมดรวมถึงชื่อที่แตก) รวมถึงเวอร์ชัน BSD ของ BSD fnmatchซึ่งไม่ได้เรียกร้อง POSIX.2 cnoformance ต่างจากรุ่น GNU มีความแตกต่างและมีความสุขในเนื้อหาการตีความสิ่งที่ควรทำกับตัวละครที่ไม่ถูกต้อง
dhag

13

find -name option ใช้สัญกรณ์การจับคู่รูปแบบเชลล์เพื่อดำเนินการกับชื่อไฟล์ *เป็นรูปแบบที่ตรงกับตัวละครหลายตัวจะต้องตรงกับสายอักขระศูนย์หรือมากกว่า

findใช้fnmatchเพื่อตรวจสอบการจับคู่รูปแบบดังนั้นคุณสามารถใช้ltraceเพื่อตรวจสอบผลลัพธ์:

$ touch $'\U1212'aa
$ touch D$'\351'sinstaller
$ LC_ALL=en_US.utf8 ltrace -e fnmatch find -name '*'          
find->fnmatch("foo", "foo", 0)                   = 0
find->fnmatch("Foo", "foo", 0)                   = 1
find->fnmatch("Foo", "foo", 16)                  = 0
find->fnmatch("*", ".", 0)                       = 0
.
find->fnmatch("*", "D\351sinstaller", 0)         = -1
find->fnmatch("*", "\341\210\222aa", 0)          = 0
./ሒaa
+++ exited (status 0) +++

ด้วยD\351sinstaller, fnmatchย้อนกลับ-1, ระบุว่าไม่สามารถจับคู่ได้ อักขระที่ถูกต้องሒaaจะถูกจับคู่

ในกรณีของคุณด้วยUTF-8โลแคล\351เป็นอักขระที่ไม่ถูกต้องทำให้การจับคู่รูปแบบล้มเหลว


3
อย่างน้อยที่สุด +1 ltraceสำหรับการใช้งานของ ฉันรู้เกี่ยวกับstraceแต่ltraceเป็นของใหม่สำหรับฉัน น่ารัก!
Michaël
โดยการใช้ไซต์ของเรา หมายความว่าคุณได้อ่านและทำความเข้าใจนโยบายคุกกี้และนโยบายความเป็นส่วนตัวของเราแล้ว
Licensed under cc by-sa 3.0 with attribution required.