W przypadku nowego pytania skrypt działa:
#!/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
Po wykonaniu:
Output:$'\n\n\n'
Exit :25
Done
Dłuższy opis
Zwykłą mądrością dla powłok POSIX do radzenia sobie z usuwaniem \n
jest:
dodaj x
s=$(printf "%s" "${1}x"); s=${s%?}
Jest to wymagane, ponieważ ostatnia nowa linia ( S ) jest usuwana przez rozszerzenie komend zgodnie ze specyfikacją POSIX :
usuwanie sekwencji jednego lub więcej znaków na końcu podstawienia.
O wleczeniu x
.
W tym pytaniu powiedziano, że x
można pomylić z końcowym bajtem jakiegoś znaku w niektórych kodowaniach. Ale jak zgadniemy, która lub która postać jest lepsza w jakimś języku w jakimś możliwym kodowaniu, co jest co najmniej trudną propozycją.
Jednak; To jest po prostu nieprawidłowe .
Jedyną zasadą, którą musimy przestrzegać, jest dodawanie dokładnie tego , co usuwamy.
Powinno być łatwe do zrozumienia, że jeśli dodamy coś do istniejącego ciągu (lub sekwencji bajtów), a później usuniemy dokładnie to samo, oryginalny ciąg (lub sekwencja bajtów) musi być taki sam.
Gdzie popełniamy błąd? Kiedy mieszamy znaki i bajty .
Jeśli dodamy bajt, musimy usunąć bajt, jeśli dodamy znak, musimy usunąć dokładnie ten sam znak .
Druga opcja, dodawanie znaku (a później usunięcie dokładnie tego samego znaku) może stać się skomplikowane i złożone, i tak, strony kodowe i kodowanie mogą przeszkadzać.
Jednak pierwsza opcja jest całkiem możliwa, a po jej wyjaśnieniu stanie się po prostu prosta.
Dodajmy bajt, bajt ASCII (<127), i aby zachować jak najmniej skomplikowaną sytuację, powiedzmy znak ASCII w zakresie az. Albo jak należy go mówiąc, bajt w zakresie hex 0x61
- 0x7a
. Wybierzmy dowolny z nich, może x (naprawdę bajt wartości 0x78
). Możemy dodać taki bajt, łącząc x z ciągiem (załóżmy, że é
):
$ a=é
$ b=${a}x
Jeśli spojrzymy na ciąg jako sekwencję bajtów, zobaczymy:
$ printf '%s' "$b" | od -vAn -tx1c
c3 a9 78
303 251 x
Ciąg znaków kończący się na x.
Jeśli usuniemy ten x (wartość bajtu 0x78
), otrzymamy:
$ printf '%s' "${b%x}" | od -vAn -tx1c
c3 a9
303 251
Działa bez problemu.
Trochę trudniejszy przykład.
Powiedzmy, że ciąg, który nas interesuje, kończy się bajtem 0xc3
:
$ a=$'\x61\x20\x74\x65\x73\x74\x20\x73\x74\x72\x69\x6e\x67\x20\xc3'
I dodajmy bajt wartości 0xa9
$ b=$a$'\xa9'
Ciąg stał się teraz taki:
$ echo "$b"
a test string é
Dokładnie to, czego chciałem, ostatnie dwa bajty to jeden znak w utf8 (aby każdy mógł odtworzyć te wyniki w swojej konsoli utf8).
Jeśli usuniemy znak, oryginalny ciąg zostanie zmieniony. Ale to nie to, co dodaliśmy, dodaliśmy wartość bajtu, która przypadkowo jest zapisywana jako x, ale bajt i tak.
Czego potrzebujemy, aby uniknąć błędnej interpretacji bajtów jako znaków. Potrzebujemy działania, które usuwa użyty bajt 0xa9
. W rzeczywistości ash, bash, lksh i mksh wydają się robić dokładnie to:
$ 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
Ale nie ksh ani zsh.
Jest to jednak bardzo łatwe do rozwiązania, powiedzmy wszystkim tym powłokom, aby usunęły bajty:
$ LC_ALL=C; echo ${b%$c} | od -vAn -tx1c
to wszystko, wszystkie testowane powłoki działają (oprócz yash) (dla ostatniej części łańcucha):
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
Po prostu powiedz powłoce, aby usunęła znak LC_ALL = C, który jest dokładnie jednym bajtem dla wszystkich wartości bajtów od 0x00
do 0xff
.
Rozwiązanie dla komentarzy:
Dla przykładu omówionego w komentarzach jednym z możliwych rozwiązań (które nie działa w Zsh) jest:
#!/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
To usunie problem z kodowaniem.
$IFS
, więc nie zostanie przechwycony jako argument.