Para a nova pergunta, este script funciona:
#!/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
Na execução:
Output:$'\n\n\n'
Exit :25
Done
A descrição mais longa
A sabedoria usual dos shells POSIX para lidar com a remoção de \né:
adicione um x
s=$(printf "%s" "${1}x"); s=${s%?}
Isso é necessário porque a última nova linha ( S ) é removida pela expansão do comando de acordo com a especificação POSIX :
removendo sequências de um ou mais caracteres no final da substituição.
Sobre uma trilha x.
Já foi dito nesta pergunta que um xpoderia ser confundido com o byte à direita de algum caractere em alguma codificação. Mas como vamos adivinhar qual ou qual personagem é melhor em alguma linguagem em alguma codificação possível, que é uma proposição difícil, para dizer o mínimo.
Contudo; Isso é simplesmente incorreto .
A única regra que precisamos seguir é adicionar exatamente o que removemos.
Deve ser fácil entender que, se adicionarmos algo a uma string existente (ou sequência de bytes) e depois removermos exatamente o mesmo, a string original (ou sequência de bytes) deverá ser a mesma.
Onde erramos? Quando misturamos caracteres e bytes .
Se adicionarmos um byte, devemos remover um byte; se adicionarmos um caractere, removeremos exatamente o mesmo caractere .
A segunda opção, adicionar um caractere (e depois remover exatamente o mesmo caractere) pode se tornar complicada e complexa e, sim, páginas de código e codificações podem atrapalhar.
No entanto, a primeira opção é bem possível e, depois de explicada, se tornará simples.
Vamos adicionar um byte, um byte ASCII (<127), e para manter as coisas o menos complicado possível, digamos um caractere ASCII no intervalo de az. Ou, como deveríamos dizer, um byte no intervalo hexadecimal 0x61- 0x7a. Vamos escolher um desses, talvez um x (realmente um byte de valor 0x78). Podemos adicionar esse byte concatenando um x a uma string (vamos assumir um é):
$ a=é
$ b=${a}x
Se olharmos para a string como uma sequência de bytes, veremos:
$ printf '%s' "$b" | od -vAn -tx1c
c3 a9 78
303 251 x
Uma sequência de cadeias que termina em um x.
Se removermos esse x (valor de byte 0x78), obtemos:
$ printf '%s' "${b%x}" | od -vAn -tx1c
c3 a9
303 251
Funciona sem problemas.
Um exemplo um pouco mais difícil.
Digamos que a string em que estamos interessados termine em byte 0xc3:
$ a=$'\x61\x20\x74\x65\x73\x74\x20\x73\x74\x72\x69\x6e\x67\x20\xc3'
E vamos adicionar um byte de valor 0xa9
$ b=$a$'\xa9'
A string tornou-se agora:
$ echo "$b"
a test string é
Exatamente o que eu queria, os últimos dois bytes são um caractere no utf8 (para que qualquer um possa reproduzir esses resultados em seu console utf8).
Se removermos um caractere, a string original será alterada. Mas não foi isso que adicionamos, adicionamos um valor de byte, que passa a ser escrito como x, mas de qualquer maneira.
O que precisamos para evitar interpretar mal bytes como caracteres. O que precisamos é de uma ação que remova o byte que usamos 0xa9. De fato, ash, bash, lksh e mksh parecem fazer exatamente isso:
$ 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
Mas não ksh ou zsh.
No entanto, isso é muito fácil de resolver, vamos dizer a todos os shells para remover o byte:
$ LC_ALL=C; echo ${b%$c} | od -vAn -tx1c
é isso, todos os shells testados funcionam (exceto yash) (para a última parte da 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
Simples assim, diga ao shell para remover um caractere LC_ALL = C, que é exatamente um byte para todos os valores de by 0x00a 0xff.
Solução para comentários:
Para o exemplo discutido nos comentários, uma solução possível (que falha no 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
Isso removerá o problema da codificação.
$IFS, portanto, não será capturada como argumento.