Untuk pertanyaan baru, skrip ini berfungsi:
#!/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
Pada eksekusi:
Output:$'\n\n\n'
Exit :25
Done
Deskripsi yang lebih panjang
Kebijaksanaan biasa untuk kerang POSIX untuk menangani penghapusan \n
adalah:
tambahkan sebuah x
s=$(printf "%s" "${1}x"); s=${s%?}
Itu diperlukan karena baris baru terakhir ( S ) dihapus oleh perintah ekspansi per spesifikasi POSIX :
menghapus urutan satu atau lebih karakter di akhir substitusi.
Tentang trailing x
.
Telah dikatakan dalam pertanyaan ini bahwa sebuah x
dapat dikacaukan dengan byte trailing dari beberapa karakter dalam beberapa pengkodean. Tetapi bagaimana kita akan menebak karakter apa atau yang lebih baik dalam suatu bahasa dalam beberapa penyandian yang mungkin, itu adalah proposisi yang sulit, untuk sedikitnya.
Namun; Itu tidak benar .
Satu-satunya aturan yang perlu kita ikuti adalah menambahkan dengan tepat apa yang kita hapus.
Seharusnya mudah dipahami bahwa jika kita menambahkan sesuatu ke string yang sudah ada (atau urutan byte) dan kemudian kita menghapus sesuatu yang persis sama, string asli (atau urutan byte) harus sama.
Di mana kita salah? Ketika kita mencampur karakter dan byte .
Jika kita menambahkan byte, kita harus menghapus byte, jika kita menambahkan karakter, kita harus menghapus karakter yang sama persis .
Opsi kedua, menambahkan karakter (dan kemudian menghapus karakter yang sama persis) dapat menjadi berbelit-belit dan kompleks, dan, ya, halaman kode dan penyandian mungkin menghalangi.
Namun, opsi pertama sangat mungkin, dan, setelah menjelaskannya, itu akan menjadi sederhana.
Mari kita tambahkan byte, byte ASCII (<127), dan untuk menjaga hal-hal sesederhana mungkin, katakanlah karakter ASCII dalam kisaran az. Atau seperti yang seharusnya kita katakan, byte dalam kisaran hex 0x61
- 0x7a
. Mari kita pilih salah satunya, mungkin x (benar-benar nilai byte 0x78
). Kita dapat menambahkan byte tersebut dengan menggabungkan x ke sebuah string (mari kita asumsikan sebuah é
):
$ a=é
$ b=${a}x
Jika kita melihat string sebagai urutan byte, kita melihat:
$ printf '%s' "$b" | od -vAn -tx1c
c3 a9 78
303 251 x
Urutan string yang berakhiran x.
Jika kita menghapus x itu (nilai byte 0x78
), kita mendapatkan:
$ printf '%s' "${b%x}" | od -vAn -tx1c
c3 a9
303 251
Ini bekerja tanpa masalah.
Contoh yang sedikit lebih sulit.
Katakanlah bahwa string yang kita minati diakhiri dengan byte 0xc3
:
$ a=$'\x61\x20\x74\x65\x73\x74\x20\x73\x74\x72\x69\x6e\x67\x20\xc3'
Dan mari kita tambahkan satu byte nilai 0xa9
$ b=$a$'\xa9'
String telah menjadi ini sekarang:
$ echo "$b"
a test string é
Tepat seperti yang saya inginkan, dua byte terakhir adalah satu karakter di utf8 (sehingga siapa pun dapat mereproduksi hasil ini di konsol utf8 mereka).
Jika kita menghapus karakter, string asli akan berubah. Tapi bukan itu yang kami tambahkan, kami menambahkan nilai byte, yang kebetulan ditulis sebagai x, tetapi byte tetap.
Yang perlu kita hindari salah mengartikan byte sebagai karakter. Yang kami butuhkan adalah tindakan yang menghapus byte yang kami gunakan 0xa9
. Bahkan, abu, bash, lksh, dan mksh semuanya tampaknya melakukan hal itu:
$ 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
Tapi bukan ksh atau zsh.
Namun, itu sangat mudah dipecahkan, mari beri tahu semua shell untuk melakukan penghapusan byte:
$ LC_ALL=C; echo ${b%$c} | od -vAn -tx1c
itu saja, semua kerang yang diuji bekerja (kecuali yash) (untuk bagian terakhir dari string):
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
Sederhananya, beri tahu shell untuk menghapus karakter LC_ALL = C, yang persis satu byte untuk semua nilai byte dari 0x00
ke 0xff
.
Solusi untuk komentar:
Sebagai contoh yang dibahas dalam komentar, satu solusi yang mungkin (yang gagal dalam zsh) adalah:
#!/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
Itu akan menghapus masalah pengkodean.
$IFS
, sehingga tidak akan ditangkap sebagai argumen.