web-dev-qa-db-ja.com

null文字(\ 0)を含む文字列をBashの変数に割り当てる

区切り文字としてNULL文字を使用して、file-/foldernamesのリストを正しく処理しようとしているときに( 他の質問を参照 )、理解できないBashの奇妙な動作に遭遇しました:

1つ以上のNULL文字を含む文字列を変数に割り当てると、NULL文字が失われる/無視される/保存されません。

例えば、

echo -ne "n\0m\0k" | od -c   # -> 0000000   n  \0   m  \0   k

だが:

VAR1=`echo -ne "n\0m\0k"`
echo -ne "$VAR1" | od -c   # -> 0000000   n   m   k

つまり、直接パイプすることが望ましくない場合や実現できない場合は、その文字列をファイル(たとえば、/ tmp内)に書き込んで、そこから読み取る必要があります。

これらのスクリプトを Z Shell (zsh)で実行すると、\ 0を含む文字列はどちらの場合も保持されますが、残念ながら、Bashが存在するはずのスクリプトを実行しているシステムにzshが存在するとは想定できません。

(メタ)文字を失うことなく、\ 0文字を含む文字列を効率的に格納または処理するにはどうすればよいですか?

29
antiplex

Bashでは、NULL文字を変数に格納できません。

ただし、xxdコマンドを使用して、データのプレーンな16進ダンプを保存することができます(後でこの操作を元に戻すことができます)。

VAR1=`echo -ne "n\0m\0k" | xxd -p | tr -d '\n'`
echo -ne "$VAR1" | xxd -r -p | od -c   # -> 0000000    n  \0   m  \0   k
31
jeff

他の人がすでに述べたように、NUL文字を格納/使用することはできません

  • 変数内
  • コマンドラインの引数で。

ただし、バイナリデータを処理できます(NUL文字を含む):

  • パイプで
  • ファイル内

だから最後の質問に答えるには:

(メタ)文字を失うことなく、\ 0文字を含む文字列を効率的に格納または処理する方法について、誰かにヒントを教えてもらえますか?

ファイルまたはパイプを使用するを使用すると、メタ文字を含む任意の文字列を保存して効率的に処理できます。

データの処理を計画している場合は、次の点にも注意してください。

制限の回避

変数を使用したい場合は、NUL文字をエンコードして取り除く必要があります。他のさまざまなソリューションでは、それを行うための巧妙な方法を提供しています(明らかな方法は、たとえばbase64エンコード/デコードを使用することです)。

メモリや速度が気になる場合は、最小限のパーサーを使用して、NUL文字(および引用文字)だけを引用することをお勧めします。この場合、これはあなたを助けるでしょう:

quote() { sed 's/\\/\\\\/g;s/\x0/\\x00/g'; }

次に、機密データをquoteにパイプして変数とコマンドライン引数に格納する前にデータを保護します。これにより、NUL文字なしで安全なデータストリームが出力されます。標準出力に正しい文字列を送信するecho -en "$var_quoted"を使用すると、元の文字列(NUL文字を含む)を取得できます。

例:

## Our example output generator, with NUL chars
ascii_table() { echo -en "$(echo '\'0{0..3}{0..7}{0..7} | tr -d " ")"; }
## store
myvar_quoted=$(ascii_table | quote)
## use
echo -en "$myvar_quoted"

注:| hdを使用して、データを16進数で明確に表示し、NUL文字が失われていないことを確認してください。

ツールの変更

コマンドラインで変数や引数を使用しなくても、パイプでかなり遠くまで行けることを覚えておいてください。たとえば、名前付きパイプ(一時ファイルの一種)を作成する<(command ...)構造を忘れないでください。

編集:quoteの最初の実装は正しくなく、\によって解釈されるecho -en特殊文字を正しく処理しませんでした。それを発見してくれて@xhienneに感謝します。

EDIT2:quoteの2番目の実装には、\0\0\00および\000が同等であるため、実際にはより多くのゼロを消費するよりも\0000のみを使用するため、バグがありました。したがって、\0\x00に置き換えられました。これを見つけてくれた@MatthijsSteenに感謝します。

18
vaab

POSIXの移植性のためにuuencodeおよびuudecodeを使用します

xxdおよび_base64_ POSIX 7ではない ですが encodeは です。

_VAR="$(uuencode -m <(printf "a\0\n") /dev/stdout)"
uudecode -o /dev/stdout <(printf "$VAR") | od -tx1
_

出力:

_0000000 61 00 0a
0000003
_

残念ながら、ファイルへの書き込みを除いて、Bashプロセスの<()置換拡張機能のPOSIX 7の代替案はありません。また、デフォルトでUbuntu 12.04にインストールされていません(sharutilsパッケージ)。

したがって、本当の答えは次のとおりだと思います。これにはBashを使用せず、Pythonまたはその他のより適切な解釈言語を使用してください。

大好きです ジェフの答え 。私はxxdの代わりにBase64エンコーディングを使用します。それは少しスペースを節約し、意図されているものに関して(私は思う)より認識されます。

VAR=$(echo -ne "foo\0bar" | base64)
echo -n "$VAR" | base64 -d | xargs -0 ...

-eについては、エンコードされたnull( '\ 0')を含むリテラル文字列のエコーに必要ですが、ユーザー入力を彼らはエコーが解釈し、悪いことで終わるエスケープシーケンスを注入する可能性があります。エンコードされて保存された文字列をデコードにエコーする場合、-eフラグは必要ありません。

3
vontrapp