เหตุใด $ '\ 0' จึงเหมือนกับ ''


10

วิธีทั่วไปในการทำสิ่งต่าง ๆ ที่มีไฟล์อยู่สองไฟล์คือ - และอย่าไปทำอย่างนั้นกับฉัน:

for f in $(ls); do 

ตอนนี้เพื่อความปลอดภัยต่อไฟล์ที่มีช่องว่างหรือตัวอักษรแปลก ๆ วิธีการที่ไร้เดียงสาจะทำ:

find . -type f -print0 | while IFS= read -r -d '' file; 

ที่นี่-d ''เป็นระยะสั้นสำหรับการตั้งค่า NUL ASCII -d $'\0'กับใน

แต่ทำไมถึงเป็นเช่นนั้น? ทำไม''และ$'\0'เหมือนกัน นั่นเป็นเพราะราก C ของ Bash ที่มีสตริงว่างจะถูกยกเลิกด้วยค่าว่างเสมอหรือไม่?


อ้างถึงวิธี "ไร้เดียงสา" มีวิธีที่ดีกว่าในการทำเช่นนี้หรือไม่?
iruvar

2
โดยวิธีการที่ถ้าคุณต้องการที่จะดำเนินการทำซ้ำที่ปลอดภัยกว่าชุดของแฟ้ม - ใช้แทนการแยกfor f in * ls

@htor ฉันรู้ว่าfor i in $(ls)โง่มากฉันเกือบละอายใจที่ฉันใช้มันเป็นตัวอย่างที่ไม่ดีที่นี่
slhck

@ChandraRavoori ใช่เช่นโดยใช้find … -execแทนการวนลูปรอบ ๆ ไฟล์ซึ่งใช้ได้กับกรณีส่วนใหญ่ที่คุณใช้เช่น for loop แทน ที่นี่findดูแลทุกอย่างให้คุณ
slhck

@slhck ขอบคุณ สิ่งที่เกี่ยวกับสถานการณ์ที่เกี่ยวข้องกับการดำเนินการหลายขั้นตอนในแต่ละไฟล์ที่อาจเป็นวงที่ดีกว่าสำหรับเหตุผลในการอ่าน? มีตัวเลือกวนซ้ำที่ดีกว่า "วิธีไร้เดียงสา" ด้านบนหรือไม่
iruvar

คำตอบ:


10

การman page of bashอ่าน:

          -d delim
                 The first character of delim is  used  to  terminate  the
                 input line, rather than newline.

เนื่องจากสตริงมักจะสิ้นสุดด้วยค่า null อักขระตัวแรกของสตริงว่างจึงเป็นค่าว่าง - ทำให้รู้สึกถึงฉัน :)

แหล่งที่มาอ่าน:

static unsigned char delim;
[...]
    case 'd':
      delim = *list_optarg;
      break;

สำหรับสตริงที่ว่างเปล่าdelimเป็นเพียงไบต์ว่าง


เมื่อคุณพูดว่า "สตริงมักจะเป็นโมฆะ" นั่นไม่ใช่กรณีที่อยู่ในสภาพแวดล้อม POSIX หรือไม่? จากวันที่ฉันเรียน C สำหรับโรงเรียนแน่นอนมันสมเหตุสมผลที่จะคิดเช่นนั้น ฉันแค่ตรวจสอบ
slhck

แต่เราสามารถพิจารณาว่าสตริงใด ๆ ที่มีสตริงว่างมากมายโดยพลการเช่นถ้าคุณต่อ "" และ "X" คุณจะได้ "X" ดังนั้นคุณสามารถยืนยันว่าการเผชิญหน้าสตริงย่อยครั้งแรกคือสตริงว่าง ตัวอย่างเช่นถ้าคุณใช้สตริงว่างในจาวาสคริปต์split()มันจะแบ่งระหว่างตัวละครแต่ละตัว ฉันสงสัยว่า "ด้วยเหตุผลทางประวัติศาสตร์" อาจเป็นคำอธิบายที่ดีที่สุดที่เราจะได้รับ
donothingsuccessfully

ก็ไม่มากนักเพราะ "การต่อ" สไตล์ C '\0'ด้วย'X\0'ควรให้คุณ'X\0'ถ้าทำถูกต้อง สิ่งนี้ไม่ได้เกี่ยวอะไรกับฟังก์ชั่นระดับสูงในภาษาต่าง ๆ เช่น JavaScript @don
slhck

ขอบคุณ michas สำหรับการเพิ่มแหล่งที่มา delim = *list_optarg;ทำให้ชัดเจนว่าทำไมมันเป็นอย่างนั้น
slhck

@slhck: ขอโทษฉันไม่ได้ทำให้ตัวเองชัดเจน คุณถามว่า "ทำไม''และ$'\0'เหมือนกัน?" มิชาให้คำอธิบายใกล้เคียงกับ "นั่นคือสิ่งที่รหัสทำ" ฉันระบุวิธีอื่นในการจัดการกับสตริงว่างที่ฉันเห็นว่ามีเหตุผลพอ ๆ กันและแนะนำว่าการเลือกอย่างใดอย่างหนึ่งนั้นเป็นเพียงเรื่องของการประชุมหรือเหตุการณ์ที่เกิดขึ้น
donothingsuccessfully

6

มีสองข้อบกพร่องในการทุบตีที่ชดเชยซึ่งกันและกัน

เมื่อคุณเขียน$'\0'นั่นจะเป็นการปฏิบัติภายในเหมือนกับสตริงว่าง ตัวอย่างเช่น:

$ a=$'\0'; echo ${#a}
0

นั่นเป็นเพราะภายในทุบตีเก็บสตริงทั้งหมดเป็นสตริงCซึ่งเป็นโมฆะ - ไบต์ null ทำเครื่องหมายจุดสิ้นสุดของสตริง Bash ตัดสตริงอย่างเงียบ ๆ เป็นไบต์แรก (ซึ่งไม่ได้เป็นส่วนหนึ่งของสตริง!)

# a=$'foo\0bar'; echo "$a"; echo ${#a}
foo
3

เมื่อคุณส่งสตริงเป็นอาร์กิวเมนต์ให้กับ-dตัวเลือกของreadบิวอิน bash จะดูที่ไบต์แรกของสตริงเท่านั้น แต่ไม่ได้ตรวจสอบจริง ๆ ว่าสตริงนั้นไม่ว่างเปล่า ภายในสตริงว่างจะแสดงเป็นอาร์เรย์ 1 องค์ประกอบที่มีเพียงไบต์ว่าง ดังนั้นแทนที่จะอ่านไบต์แรกของสตริง bash จะอ่านค่า null นี้

จากนั้นภายในเครื่องจักรหลังreadbuiltin ทำงานได้ดีกับ null ไบต์; มันอ่านไบต์ต่อไบต์จนกว่าจะพบตัวคั่น

กระสุนอื่น ๆ มีพฤติกรรมแตกต่างกัน ตัวอย่างเช่น ash และ ksh ละเว้นไบต์ที่เป็นค่าว่างเมื่ออ่านอินพุต ด้วย ksh ksh -d ""อ่านจนถึงบรรทัดใหม่ เชลล์ออกแบบมาเพื่อรับมือกับข้อความได้ดีไม่ใช่กับข้อมูลไบนารี Zsh เป็นข้อยกเว้น: มันใช้การแทนค่าสตริงที่ copes ด้วยไบต์โดยพลการรวมถึง null null; ใน zsh $'\0'คือสตริงที่มีความยาว 1 (แต่read -d ''แปลก ๆ มีพฤติกรรมเหมือนread -d $'\0')


พฤติกรรมของการreadเปลี่ยนแปลงใน 4.3 ทุบตีดังนั้นในขณะนี้มันข้ามไบต์ null ตัวอย่างเช่นread x< <(printf a\\0a)ชุดxไปแทนaa a
Lri
โดยการใช้ไซต์ของเรา หมายความว่าคุณได้อ่านและทำความเข้าใจนโยบายคุกกี้และนโยบายความเป็นส่วนตัวของเราแล้ว
Licensed under cc by-sa 3.0 with attribution required.