Đối với câu hỏi mới, kịch bản này hoạt động:
#!/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
Thực hiện:
Output:$'\n\n\n'
Exit :25
Done
Mô tả dài hơn
Sự khôn ngoan thông thường đối với đạn POSIX để đối phó với việc loại bỏ \n
là:
thêm một x
s=$(printf "%s" "${1}x"); s=${s%?}
Điều đó là bắt buộc vì dòng mới ( S ) cuối cùng bị xóa bằng cách mở rộng lệnh trên mỗi đặc tả POSIX :
loại bỏ các chuỗi của một hoặc nhiều ký tự ở cuối thay thế.
Về một dấu vết x
.
Người ta đã nói trong câu hỏi này rằng x
có thể bị nhầm lẫn với byte theo sau của một số ký tự trong một số mã hóa. Nhưng làm thế nào chúng ta sẽ đoán những gì hoặc nhân vật nào là tốt hơn trong một số ngôn ngữ trong một số mã hóa có thể, đó là một đề xuất khó khăn, để nói rằng ít nhất.
Tuy nhiên; Điều đó chỉ đơn giản là không chính xác .
Quy tắc duy nhất mà chúng ta cần tuân theo là thêm chính xác những gì chúng ta xóa.
Điều dễ hiểu là nếu chúng ta thêm một cái gì đó vào một chuỗi hiện có (hoặc chuỗi byte) và sau đó chúng ta loại bỏ chính xác cùng một thứ gì đó, thì chuỗi gốc (hoặc chuỗi byte) phải giống nhau.
Chúng ta đi sai ở đâu? Khi chúng ta trộn các ký tự và byte .
Nếu chúng ta thêm một byte, chúng ta phải xóa một byte, nếu chúng ta thêm một ký tự, chúng ta phải xóa chính xác cùng một ký tự .
Tùy chọn thứ hai, thêm một ký tự (và sau đó loại bỏ chính xác cùng một ký tự) có thể trở nên phức tạp và phức tạp, và, vâng, các trang mã và mã hóa có thể gây cản trở.
Tuy nhiên, tùy chọn đầu tiên là hoàn toàn có thể, và sau khi giải thích nó, nó sẽ trở nên đơn giản.
Hãy thêm một byte, một byte ASCII (<127) và để giữ cho mọi thứ càng ít phức tạp càng tốt, giả sử một ký tự ASCII trong phạm vi az. Hoặc như chúng ta nên nói, một byte trong phạm vi hex 0x61
- 0x7a
. Cho phép chọn bất kỳ trong số đó, có thể là x (thực sự là một byte giá trị 0x78
). Chúng ta có thể thêm byte như vậy bằng cách nối một x thành một chuỗi (giả sử một é
):
$ a=é
$ b=${a}x
Nếu chúng ta xem chuỗi là một chuỗi byte, chúng ta sẽ thấy:
$ printf '%s' "$b" | od -vAn -tx1c
c3 a9 78
303 251 x
Một chuỗi chuỗi kết thúc bằng một x.
Nếu chúng ta loại bỏ x (giá trị byte 0x78
) đó, chúng ta sẽ nhận được:
$ printf '%s' "${b%x}" | od -vAn -tx1c
c3 a9
303 251
Nó hoạt động mà không có vấn đề.
Một ví dụ khó khăn hơn một chút.
Hãy nói rằng chuỗi chúng ta quan tâm đến kết thúc bằng byte 0xc3
:
$ a=$'\x61\x20\x74\x65\x73\x74\x20\x73\x74\x72\x69\x6e\x67\x20\xc3'
Và cho phép thêm một byte giá trị 0xa9
$ b=$a$'\xa9'
Chuỗi đã trở thành này bây giờ:
$ echo "$b"
a test string é
Chính xác những gì tôi muốn, hai byte cuối cùng là một ký tự trong utf8 (vì vậy bất kỳ ai cũng có thể sao chép kết quả này trong bảng điều khiển utf8 của họ).
Nếu chúng ta xóa một ký tự, chuỗi gốc sẽ được thay đổi. Nhưng đó không phải là những gì chúng tôi đã thêm, chúng tôi đã thêm một giá trị byte, được viết dưới dạng x, nhưng dù sao cũng là một byte.
Những gì chúng ta cần để tránh hiểu sai byte là ký tự. Những gì chúng ta cần là một hành động loại bỏ byte chúng ta đã sử dụng 0xa9
. Trong thực tế, tro, bash, lksh và mksh dường như làm chính xác điều đó:
$ 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
Nhưng không phải ksh hay zsh.
Tuy nhiên, điều đó rất dễ giải quyết, hãy nói với tất cả các shell đó để thực hiện loại bỏ byte:
$ LC_ALL=C; echo ${b%$c} | od -vAn -tx1c
chỉ vậy thôi, tất cả các shell đã thử nghiệm (trừ yash) (cho phần cuối của chuỗi):
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
Chỉ đơn giản như vậy, hãy nói với shell để loại bỏ ký tự LC_ALL = C, chính xác là một byte cho tất cả các giá trị byte từ 0x00
đến 0xff
.
Giải pháp cho ý kiến:
Đối với ví dụ được thảo luận trong các ý kiến, một giải pháp có thể (thất bại trong zsh) là:
#!/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
Điều đó sẽ loại bỏ vấn đề mã hóa.
$IFS
, vì vậy nó sẽ không được ghi lại làm đối số.