zshからNULで区切られた単語の添字変数の拡張

zshからNULで区切られた単語の添字変数の拡張

fileディレクトリ(またはディレクトリ)内のすべてのファイルのMIMEタイプを識別するデータが作成されました。結果は、左側にコマンドがあり、右側にMIMEタイプがあるリストです。デフォルトでは、2つの値(または文字列?)はコロン(:)で区切られています。 NUL文字を使用してMIMEタイプとコマンドを区別したいと思います。だから、

files=( ${(f)"$(file --print0 --mime-type **/*)"} )

これはうまくいくようです。

print -l ${files[@]}以下を提供します。

cs:           text/x-shellscript
da:           text/x-shellscript
dns-test:     text/x-shellscript

または次のようprint -l ${(V)files[@]}に与えられます。

cs^@:           text/x-shellscript
da^@:           text/x-shellscript
dns-test^@:     text/x-shellscript

ただし、最初の項目と一致させるために下付き文字を使用する場合言葉、出力が最初です。特徴代わりに。だから、

for f in "${files[@]}"; do
  print "${f[(pws:\0:)1]}"
done

これは最初のものだけを印刷します。特徴各配列要素について(出力の各行ごとにfile):

c
d
d

指定を省略してデフォルトの(ps:\0:)スペース区切りを使用すると、各要素の最初の単語が得られます。だから、

for f in "${files[@]}"; do
  print "${f[(w)1]}"
done

これは働きます:

cs
da
dns-test

しかし、私はスペースに依存したくありません。 NULなどのより安全な区切り文字を使用したいです。私も添字を試しましたが、[(pws:\000:)1]これも最初の添え字だけが作成されました。特徴

下付き文字を使用するときにNULで区切られた単語を識別する方法は?

答え1

ファイルパスには改行を含めることができます。これは、ファイルまたはファイル関連情報を報告するいくつかのユーティリティが改行の代わりにNULを使用してレコードを区別できるようにする理由です。 0は、ファイルパスには現れない唯一のバイト値だからです。

を使用するのと同じように、改行文字で出力を分割すると${(f)"$(file...)"}目的が失われます。

fileNULをレコード区切り文字として使用せず、代わりにNULを使用して出力レコード(まだ緩く定義されている)からファイルパスを区切るため、少し奇妙です。

$ ls
':\0:'$'\n'':\1:'  'a'$'\n''b'
$ file --print0 --mime-type -- ./* | sed -n l
./:\\0:$
:\\1:\000: application/x-xz$
./a$
b\000:       application/zip$

したがって、ロギングは<file-path><NUL><colon><some-spaces><mime-type><newline>解析を不必要に困難にします(またはテキストに改行文字が含まれている場合は不可能かもしれませんが、幸いにも--mime-typeそうではありません)。

ここでは、次のことができます。

typeset -A mime_type
file --print0 --mime-type --no-pad --separator '' ./**/* |
  while
    IFS= read -rd $'\0' file &&
      IFS=' ' read -r type
  do
    mime_type[$file]=$type
  done

1 つ目は NUL を区切り文字readとして NUL で区切られたファイルパスを読み取り-d、2 つ目は改行で区切られた MIME タイプ (IFS 処理によって先行スペースが切り捨てられます) を読み取ります。

1つの空白--no-padになるので、コロンを削除するだけです。<some-spaces>--separator ''<file-path><NUL><space><mime-type><newline>

ループでは、$mime_type連想配列を入力してを使用して特定のファイルの種類を取得する$mime_type[./given/file]か、またはを使用して特定の種類のファイルを一覧表示できますprint -rC1 -- ${(k)mime_type[(R)text/plain]}


$scalar[(pws[\0])1]NULに分割しない理由は次のとおりです。現在のzshバージョンのバグ。この動作は通常、そうでない場合はzshがNULをエスケープできないという提案と同じであるため、$array[(pws[])1]最終的には空の文字列で分割されます。エスケープ機能が不足すると、0x83から0xa2までのバイト値を含むすべての区切り文字(áUTF-8でエンコードされた0xc3 0xa1など)などの他の区切り文字に影響します。

そうでない場合は、文字(スペース以外のバグ、今回はドキュメント)に分割され、NULはデフォルト値であるため、次のことがs[separator]できますが(の略語)を使用することもできます。$IFS$IFSIFS=$'\0'; first_non_empty_NUL_separated_field=$scalar[(w)1]0ps[\0]パラメータ拡張フラグ:

non_empty_NUL_separated_fields=( ${(0)scalar} )
NUL_separated_fields=( "${(0@)scalar}" )

(その後first=$NUL_separated_fields[1])。

または一気にfirst=${${(0)scalar}[1]}

${scalar%%pattern}または、Kornシェル演算子を使用できます。

before_first_NUL=${scalar%%$'\0'*}

またはIFS分割:

IFS=$'\0'
NUL_separated_fields=( $=scalar )

またはksh93スタイル${param/pattern[/replacement]}

first=${scalar/$'\0'*}

コードに関するいくつかのコメント:

  • file --print0 --mime-type **/*ファイルパスがで始まると、-そのパスはとして扱われますfile。これを防ぐためにこれを変更することはできますが、呼び出しfile --print0 --mime-type -- **/*中の現在の作業ディレクトリにファイルが存在する場合はまだ機能しません。-を使用すると、./**/*これらの問題(および追加の問題)の両方を回避できます。
  • @、パラメータ拡張フラグまたは空の要素が削除されないように引用する場合にのみ意味があります[@]$@リストコンテキストと同じ${files[@]}で、配列の非NULL要素に展開されます。 Kornに似たシェルでは、ヌルの削除を防ぎ、分割+グローブを防ぐために引用符も必要です。 Kornに似たシェル(他のほとんどのシェルや言語とは異なり)にも中括弧が必要で、すべての要素ではなくインデックス0の要素に個別に拡張されます。$files$files[@]$files[*]$files
  • を使用するときはprint通常、オプションを使用したい、そうでない場合は、いくつかのバックスラッシュ処理を実行-rし、ほぼ常にまたはオプションの区切り文字をprint使用しようとします。そうしないと、コマンド注入の脆弱性が発生します。 ACEの脆弱性です。 Kornシェル(この組み込み機能のソース)と同様に、orが必要です(kshでは必要です)。---print $varprint -r - $varprint -r -- $varprint -r - "$var"
  • print -rC1 -- $listprint -rl -- $list通常、olumnにrリストawを印刷するよりも優れています。1 C後者は、引数が渡されないと空白行を印刷するためです。

関連情報