Для нового вопроса этот скрипт работает:
#!/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
. Позволяет выбрать любой из них, возможно, х (на самом деле это байт значения 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
Но не кш или зш.
Однако это очень легко решить, давайте скажем всем этим оболочкам выполнить удаление байтов:
$ 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
to 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
, поэтому она не будет использоваться в качестве аргумента.