สำหรับคำถามใหม่สคริปต์นี้ใช้งานได้:
#!/bin/bash
f() { for i in $(seq "$((RANDOM % 3 ))"); do
echo;
done; return $((RANDOM % 256));
}
exact_output(){ out=$( $1; ret=$?; echo x; exit "$ret" );
unset OldLC_ALL ; [ "${LC_ALL+set}" ] && OldLC_ALL=$LC_ALL
LC_ALL=C ; out=${out%x};
unset LC_ALL ; [ "${OldLC_ALL+set}" ] && LC_ALL=$OldLC_ALL
printf 'Output:%10q\nExit :%2s\n' "${out}" "$?"
}
exact_output f
echo Done
ในการดำเนินการ:
Output:$'\n\n\n'
Exit :25
Done
คำอธิบายที่ยาว
ภูมิปัญญาปกติสำหรับ POSIX เชลล์ที่จะจัดการกับการลบ\n
คือ:
เพิ่ม x
s=$(printf "%s" "${1}x"); s=${s%?}
ที่จำเป็นต้องมีเพราะสุดท้ายบรรทัดใหม่ ( S ) จะถูกลบออกจากการขยายคำสั่งต่อข้อกำหนด POSIX :
การลบลำดับของอักขระหนึ่งตัวขึ้นไปเมื่อสิ้นสุดการแทนที่
x
เกี่ยวกับต่อท้าย
มันได้รับการกล่าวในคำถามนี้ว่าx
อาจจะสับสนกับไบต์ต่อท้ายของตัวละครในการเข้ารหัส แต่เราจะเดาได้อย่างไรว่าตัวอักษรใดหรือดีกว่าในบางภาษาในการเข้ารหัสที่เป็นไปได้นั่นเป็นข้อเสนอที่ยากที่จะพูดน้อยที่สุด
อย่างไรก็ตาม; ที่เป็นเพียงที่ไม่ถูกต้อง
เพียงกฎที่เราต้องติดตามคือการเพิ่มว่าสิ่งที่เราลบ
มันควรจะง่ายต่อการเข้าใจว่าถ้าเราเพิ่มบางสิ่งบางอย่างในสตริงที่มีอยู่ (หรือลำดับไบต์) และหลังจากนั้นเราลบสิ่งเดียวกันแน่นอนสตริงเดิม (หรือลำดับไบต์) จะต้องเหมือนกัน
เราไปผิดที่ไหน เมื่อเราผสม ตัวอักษรและไบต์
ถ้าเราเพิ่มไบต์ที่เราต้องเอาไบต์ถ้าเราเพิ่มตัวอักษรที่เราต้องเอาตัวอักษรเดียวกันแน่นอน
ตัวเลือกที่สองการเพิ่มตัวอักษร (และลบตัวอักษรที่เหมือนกันในภายหลัง) อาจกลายเป็นซับซ้อนและซับซ้อนและใช่โค้ดเพจและการเข้ารหัสอาจเข้ามาขวางทาง
อย่างไรก็ตามตัวเลือกแรกนั้นเป็นไปได้มากและหลังจากอธิบายแล้วมันจะกลายเป็นเรื่องง่าย
ให้เพิ่มไบต์, ASCII ไบต์ (<127), และเพื่อให้สิ่งต่าง ๆ มีความซับซ้อนน้อยที่สุดเท่าที่เป็นไปได้, สมมุติว่าตัวอักษร ASCII อยู่ในช่วงของ az หรือที่เราควรจะบอกว่ามันเป็นไบต์ในช่วงฐานสิบหก-0x61
0x7a
เลือกอย่างใดอย่างหนึ่งอาจจะเป็น x (เป็นมูลค่า0x78
จริง ๆ ) เราสามารถเพิ่มไบต์ดังกล่าวด้วยการเชื่อม x เข้ากับสตริง (สมมติว่ามีé
):
$ a=é
$ b=${a}x
ถ้าเราดูสตริงเป็นลำดับของไบต์เราจะเห็นว่า:
$ printf '%s' "$b" | od -vAn -tx1c
c3 a9 78
303 251 x
ลำดับของสตริงที่ลงท้ายด้วย x
หากเราลบ x (ค่าไบต์0x78
) เราจะได้รับ:
$ printf '%s' "${b%x}" | od -vAn -tx1c
c3 a9
303 251
มันทำงานได้โดยไม่มีปัญหา
ตัวอย่างที่ยากขึ้นอีกหน่อย
ให้บอกว่าสตริงที่เราสนใจลงท้ายด้วย byte 0xc3
:
$ a=$'\x61\x20\x74\x65\x73\x74\x20\x73\x74\x72\x69\x6e\x67\x20\xc3'
และให้เพิ่มไบต์ของค่า 0xa9
$ b=$a$'\xa9'
สตริงกลายเป็นสิ่งนี้ทันที:
$ echo "$b"
a test string é
สิ่งที่ฉันต้องการตรงสองไบต์สุดท้ายคืออักขระหนึ่งตัวใน utf8 (ดังนั้นทุกคนสามารถทำซ้ำผลลัพธ์นี้ในคอนโซล utf8 ของพวกเขา)
หากเราลบอักขระสตริงเดิมจะถูกเปลี่ยน แต่นั่นไม่ใช่สิ่งที่เราเพิ่มเราเพิ่มค่าไบต์ซึ่งเกิดขึ้นกับการเขียนเป็น x แต่เป็นไบต์ต่อไป
สิ่งที่เราต้องหลีกเลี่ยงการตีความผิดไบต์เป็นตัวละคร 0xa9
สิ่งที่เราต้องการคือการกระทำที่เอาไบต์ที่เราใช้ อันที่จริง, Ash, ทุบตี, lksh และ mksh ทั้งหมดดูเหมือนว่า:
$ c=$'\xa9'
$ echo ${b%$c} | od -vAn -tx1c
61 20 74 65 73 74 20 73 74 72 69 6e 67 20 c3 0a
a t e s t s t r i n g 303 \n
แต่ไม่ใช่ ksh หรือ zsh
อย่างไรก็ตามนั่นแก้ง่ายมากให้บอกshell ทั้งหมดว่าต้องทำการลบ byte:
$ LC_ALL=C; echo ${b%$c} | od -vAn -tx1c
นั่นคือมันเชลล์ที่ทดสอบทั้งหมดทำงาน (ยกเว้น yash) (สำหรับส่วนสุดท้ายของสตริง):
ash : s t r i n g 303 \n
dash : s t r i n g 303 \n
zsh/sh : s t r i n g 303 \n
b203sh : s t r i n g 303 \n
b204sh : s t r i n g 303 \n
b205sh : s t r i n g 303 \n
b30sh : s t r i n g 303 \n
b32sh : s t r i n g 303 \n
b41sh : s t r i n g 303 \n
b42sh : s t r i n g 303 \n
b43sh : s t r i n g 303 \n
b44sh : s t r i n g 303 \n
lksh : s t r i n g 303 \n
mksh : s t r i n g 303 \n
ksh93 : s t r i n g 303 \n
attsh : s t r i n g 303 \n
zsh/ksh : s t r i n g 303 \n
zsh : s t r i n g 303 \n
เพียงแค่ว่าง่ายบอกเปลือกเพื่อลบ LC_ALL = C ตัวละครซึ่งเป็นสิ่งหนึ่งไบต์ค่าไบต์ทั้งหมดจากการ0x00
0xff
โซลูชั่นสำหรับความคิดเห็น:
สำหรับตัวอย่างที่กล่าวถึงในข้อคิดเห็นข้อคิดเห็นวิธีแก้ปัญหาหนึ่งที่เป็นไปได้ (ซึ่งล้มเหลวใน zsh) คือ:
#!/bin/bash
LC_ALL=zh_HK.big5hkscs
a=$(printf '\210\170');
b=$(printf '\170');
unset OldLC_ALL ; [ "${LC_ALL+set}" ] && OldLC_ALL=$LC_ALL
LC_ALL=C ; a=${a%"$b"};
unset LC_ALL ; [ "${OldLC_ALL+set}" ] && LC_ALL=$OldLC_ALL
printf '%s' "$a" | od -vAn -c
ที่จะลบปัญหาการเข้ารหัส
$IFS
ดังนั้นจะไม่ถูกบันทึกเป็นอาร์กิวเมนต์