新しい質問に対して、このスクリプトは機能します。
#!/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%?}
POSIX仕様ごとのコマンド展開により、最後の新しい行(S)が削除されるため、これが必要です。
置換の最後で1つ以上の文字のシーケンスを削除します。
末尾についてx。
この質問ではx、あるエンコーディングのある文字の後続バイトと混同される可能性があると言われています。しかし、いくつかの可能なエンコーディングでは、どの言語でどの文字が優れているのか、どの文字を推測するのか、それは控えめに言っても難しい命題です。
しかしながら; それは単に間違っています。
従う必要がある唯一のルールは、削除するものを正確に追加することです。
既存の文字列(またはバイトシーケンス)に何かを追加し、後でまったく同じものを削除する場合、元の文字列(またはバイトシーケンス)は同じでなければならないことを理解するのは簡単です。
どこがおかしいのでしょうか?文字とバイトを混ぜる とき。
バイトを追加する場合、バイトを削除する必要があります。文字を追加する場合、まったく同じ文字を削除する必要があります。
2番目のオプションである文字の追加(および後でまったく同じ文字の削除)は複雑で複雑になる可能性があり、はい、コードページとエンコードが邪魔になる場合があります。
ただし、最初のオプションは非常に可能性が高いため、説明した後は単純になります。
バイト、ASCIIバイト(<127)を追加し、物事を可能な限り複雑にしないように、a〜zの範囲のASCII文字を使用します。または、言っておくべきように、16進数の範囲のバイト0x61- 0x7aです。それらのいずれか、多分x(実際にはvalueのバイト0x78)を選択しましょう。xを文字列に連結することで、そのようなバイトを追加できます(を想定しますé):
$ a=é
$ b=${a}x
文字列を一連のバイトとして見ると、次のことがわかります。
$ printf '%s' "$b" | od -vAn -tx1c
c3 a9 78
303 251 x
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 é
まさに私が望んだもので、最後の2バイトはutf8の1文字です(だから誰でも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
しかし、kshやzshではありません。
ただし、これは非常に簡単に解決できます。これらすべてのシェルにバイト削除を行うように指示してみましょう。
$ 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文字を削除するようシェルに指示します。これは、0x00to からのすべてのバイト値に対して正確に1バイトです0xff。
コメントの解決策:
コメントで説明した例の場合、1つの可能な解決策(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あるため、引数としてキャプチャされません。