Pour la nouvelle question, ce script fonctionne:
#!/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
À l'exécution:
Output:$'\n\n\n'
Exit :25
Done
La description plus longue
La sagesse habituelle pour les coques POSIX pour gérer la suppression de \n:
ajouter un x
s=$(printf "%s" "${1}x"); s=${s%?}
Cela est nécessaire car les dernières nouvelles lignes ( S ) sont supprimées par l'extension de commande selon la spécification POSIX :
suppression des séquences d'un ou plusieurs caractères à la fin de la substitution.
À propos d'une fuite x.
Il a été dit dans cette question qu'un xpourrait être confondu avec l'octet de fin d'un caractère dans un codage. Mais comment allons-nous deviner quel ou quel caractère est meilleur dans une langue dans un codage possible, c'est une proposition difficile, pour dire le moins.
Pourtant; C'est tout simplement incorrect .
La seule règle que nous devons suivre est d'ajouter exactement ce que nous supprimons.
Il devrait être facile de comprendre que si nous ajoutons quelque chose à une chaîne existante (ou une séquence d'octets) et que nous supprimons plus tard exactement le même quelque chose, la chaîne d'origine (ou la séquence d'octets) doit être la même.
Où allons-nous mal? Quand on mélange des caractères et des octets .
Si nous ajoutons un octet, nous devons supprimer un octet, si nous ajoutons un caractère, nous devons supprimer exactement le même caractère .
La deuxième option, l'ajout d'un caractère (et la suppression ultérieure du même caractère exact) peut devenir compliquée et complexe, et, oui, les pages de code et les encodages peuvent gêner.
Cependant, la première option est tout à fait possible et, après l'avoir expliquée, elle deviendra simple.
Ajoutons un octet, un octet ASCII (<127), et pour garder les choses aussi peu alambiquées que possible, disons un caractère ASCII dans la plage de az. Ou comme nous devrions le dire, un octet dans la plage hexadécimale 0x61- 0x7a. Permet de choisir l'un de ceux-ci, peut-être un x (vraiment un octet de valeur 0x78). Nous pouvons ajouter un tel octet avec en concaténant un x à une chaîne (supposons un é):
$ a=é
$ b=${a}x
Si nous regardons la chaîne comme une séquence d'octets, nous voyons:
$ printf '%s' "$b" | od -vAn -tx1c
c3 a9 78
303 251 x
Une séquence de chaînes qui se termine par un x.
Si nous supprimons ce x (valeur d'octet 0x78), nous obtenons:
$ printf '%s' "${b%x}" | od -vAn -tx1c
c3 a9
303 251
Cela fonctionne sans problème.
Un exemple un peu plus difficile.
Disons que la chaîne qui nous intéresse se termine en octets 0xc3:
$ a=$'\x61\x20\x74\x65\x73\x74\x20\x73\x74\x72\x69\x6e\x67\x20\xc3'
Et permet d'ajouter un octet de valeur 0xa9
$ b=$a$'\xa9'
La chaîne est devenue ceci maintenant:
$ echo "$b"
a test string é
Exactement ce que je voulais, les deux derniers octets sont un caractère dans utf8 (donc n'importe qui pourrait reproduire ces résultats dans sa console utf8).
Si nous supprimons un caractère, la chaîne d'origine sera modifiée. Mais ce n'est pas ce que nous avons ajouté, nous avons ajouté une valeur d'octet, qui se trouve être écrite comme un x, mais un octet de toute façon.
Ce dont nous avons besoin pour éviter de mal interpréter les octets en tant que caractères. Ce dont nous avons besoin, c'est d'une action qui supprime l'octet que nous avons utilisé 0xa9. En fait, ash, bash, lksh et mksh semblent tous faire exactement cela:
$ 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
Mais pas ksh ou zsh.
Cependant, c'est très facile à résoudre, disons à tous ces shells de supprimer les octets:
$ LC_ALL=C; echo ${b%$c} | od -vAn -tx1c
c'est tout, tous les shells testés fonctionnent (sauf yash) (pour la dernière partie de la chaîne):
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
Aussi simple que cela, dites au shell de supprimer un caractère LC_ALL = C, qui est exactement un octet pour toutes les valeurs d'octets de 0x00à 0xff.
Solution pour commentaires:
Pour l'exemple discuté dans les commentaires, une solution possible (qui échoue dans zsh) est:
#!/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
Cela supprimera le problème d'encodage.
$IFS, donc elle ne sera pas capturée comme argument.