Per la nuova domanda, questo script funziona:
#!/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
In esecuzione:
Output:$'\n\n\n'
Exit :25
Done
La descrizione più lunga
La solita saggezza per le shell POSIX da affrontare con la rimozione di \n
è:
aggiungi un x
s=$(printf "%s" "${1}x"); s=${s%?}
Che è richiesto perché l'ultima nuova linea ( S ) vengono rimosse dalla espansione comando per specifiche POSIX :
rimuovendo le sequenze di uno o più caratteri alla fine della sostituzione.
A proposito di un finale x
.
In questa domanda è stato detto che un x
potrebbe essere confuso con il byte finale di alcuni caratteri in alcune codifiche. Ma come indovineremo cosa o quale personaggio è meglio in qualche lingua in qualche possibile codifica, questa è una proposta difficile, per non dire altro.
Tuttavia; Questo è semplicemente errato .
L'unica regola che dobbiamo seguire è aggiungere esattamente ciò che rimuoviamo.
Dovrebbe essere facile capire che se aggiungiamo qualcosa a una stringa esistente (o sequenza di byte) e successivamente rimuoviamo esattamente lo stesso qualcosa, la stringa originale (o sequenza di byte) deve essere la stessa.
Dove sbagliamo? Quando mescoliamo caratteri e byte .
Se aggiungiamo un byte, dobbiamo rimuovere un byte, se aggiungiamo un carattere dobbiamo rimuovere esattamente lo stesso carattere .
La seconda opzione, l'aggiunta di un carattere (e la successiva rimozione dello stesso carattere esatto) può diventare complicata e complessa e, sì, le pagine di codice e le codifiche possono interferire.
Tuttavia, la prima opzione è del tutto possibile e, dopo averla spiegata, diventerà semplicemente semplice.
Aggiungiamo un byte, un byte ASCII (<127) e per mantenere le cose il meno contorte possibile, diciamo un carattere ASCII nell'intervallo di az. O come dovremmo dirlo, un byte nell'intervallo esadecimale 0x61
- 0x7a
. Consente di scegliere uno di quelli, forse una x (davvero un byte di valore 0x78
). Possiamo aggiungere tale byte concatenando una x a una stringa (supponiamo che un é
):
$ a=é
$ b=${a}x
Se consideriamo la stringa come una sequenza di byte, vediamo:
$ printf '%s' "$b" | od -vAn -tx1c
c3 a9 78
303 251 x
Una sequenza di stringhe che termina con una x.
Se rimuoviamo quella x (valore byte 0x78
), otteniamo:
$ printf '%s' "${b%x}" | od -vAn -tx1c
c3 a9
303 251
Funziona senza problemi.
Un esempio un po 'più difficile.
Diciamo che la stringa a cui siamo interessati termina in byte 0xc3
:
$ a=$'\x61\x20\x74\x65\x73\x74\x20\x73\x74\x72\x69\x6e\x67\x20\xc3'
E consente di aggiungere un byte di valore 0xa9
$ b=$a$'\xa9'
La stringa è diventata questa ora:
$ echo "$b"
a test string é
Esattamente quello che volevo, gli ultimi due byte sono un carattere in utf8 (quindi chiunque potrebbe riprodurre questi risultati nella propria console utf8).
Se rimuoviamo un carattere, la stringa originale verrà modificata. Ma non è quello che abbiamo aggiunto, abbiamo aggiunto un valore byte, che sembra essere scritto come una x, ma comunque un byte.
Ciò di cui abbiamo bisogno per evitare di interpretare erroneamente i byte come caratteri. Ciò di cui abbiamo bisogno è un'azione che rimuova il byte che abbiamo usato 0xa9
. In effetti, ash, bash, lksh e mksh sembrano fare esattamente questo:
$ 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
Ma non ksh o zsh.
Tuttavia, questo è molto facile da risolvere, diciamo a tutte quelle shell di fare la rimozione dei byte:
$ LC_ALL=C; echo ${b%$c} | od -vAn -tx1c
tutto qui, tutte le shell testate funzionano (tranne yash) (per l'ultima parte della stringa):
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
Proprio così semplice, dire alla shell di rimuovere un carattere LC_ALL = C, che è esattamente un byte per tutti i valori di byte da 0x00
a 0xff
.
Soluzione per i commenti:
Per l'esempio discusso nei commenti, una possibile soluzione (che fallisce in 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
Ciò eliminerà il problema della codifica.
$IFS
, quindi non verrà catturato come argomento.