Для нового питання цей сценарій працює:
#!/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 (байтне значення 0x78
), отримаємо:
$ printf '%s' "${b%x}" | od -vAn -tx1c
c3 a9
303 251
Це працює без проблем.
Трохи складніший приклад.
Скажемо, що рядок, який нас цікавить, закінчується в байті 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, bash, 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.
Однак це дуже легко вирішити, давайте скажемо всім цих оболонок зробити видалення байтів:
$ LC_ALL=C; echo ${b%$c} | od -vAn -tx1c
це все, всі перевірені оболонки працюють (крім яшму) (для останньої частини рядка):
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
, тому він не буде сприйнятий як аргумент.