Bash: 'find' の出力を 'readarray' にパイプする

Bash: 'find' の出力を 'readarray' にパイプする

私は を使用してファイルを検索し、それらのファイルを Bash 配列に入れて、それらに対して他の操作 (またはなど)findを実行できるようにしようとしています。 しかし、パイプされた出力が読み込まれない理由がわかりません。lsgrepreadarrayfind

現在のディレクトリに 2 つのファイルがあるとしますfile1.txtfile2.txt出力はfind次のようになります。

$ find . -name "file*"
./file1.txt
./file2.txt

"./file1.txt"そこで、これを、文字列と"./file2.txt"(もちろん引用符なし)の 2 つの要素を持つ配列にパイプしたいと思います。

私はこれを試しましたが、他にもいくつか試しました:

$ declare -a FILES
$ find . -name "file*" | readarray FILES
$ echo "${FILES[@]}"; echo "${#FILES[@]}"

0

出力からわかるようにecho、配列は空です。

では、ここで私が何を間違っているのでしょうか?の出力を標準入力としてreadarray読み取ってfind、それらの文字列を配列に格納しないのはなぜでしょうか?

答え1

パイプラインを使用する場合、bash はサブシェル¹ でコマンドを実行します。したがって、配列はサブシェル内で設定されますが、親シェルはそれにアクセスできません。また、-t行区切り文字はファイル名の一部ではないため、配列メンバーに行区切り文字を保存しないオプションが必要になる可能性があります。

プロセス置換を使用する:

readarray -t FILES < <(find .)

パスに改行が含まれるファイルでは機能しないことに注意してください。そうならないことを保証できない場合は、改行で区切られたレコードではなく、NUL で区切られたレコードを使用する必要があります。

readarray -td '' < <(find . -print0)

(この-dオプションは bash 4.4 で追加されました)


¹ オプションを使用する場合の最後のパイプ コンポーネントを除きますlastpipeが、これは の非対話型呼び出しにのみ適用されますbash

答え2

正しい解決策は次のとおりです。

unset a; declare -a a
while IFS= read -r -u3 -d $'\0' file; do
    a+=( "$file" )        # or however you want to process each file
done 3< <(find /tmp -type f -print0)

それはグレッグのバッシュFAQ 020詳細に説明し、この回答は

奇妙な名前のファイル(名前に NUL を含まない)、スペース、または改行があっても問題ありません。結果は配列に設定されるため、さらに処理する場合に便利です。

答え3

readarraystdinから読み込むこともできる

readarray FILES <<< "$(find . -name "file*")"; echo "${#FILES[@]}"

答え4

元の質問に示されている解決策ではshopt -s lastpipe、最後の部分が|スクリプトの大部分と同じプロセスで実行されるため、次のように機能します。

shopt -s lastpipe

# This is just the code form the original question.
# Of course it could be done with -print0, etc.
declare -a FILES
find . -name "name*" | readarray -t FILES
echo find and readarray exit status: "${PIPESTATUS[@]}"
echo "${FILES[@]}"
echo "${#FILES[@]}"

lastpipeただし、これは、 をプロジェクト全体の標準にできる場合にのみ実用的です(set -uなどに既にこのような共通設定がある可能性があります)。 また、 は非対話型シェルでのみ機能することに注意してくださいlastpipe

もう一つの典型的なパイプ置換< <(...)(プロセス置換) には、子プロセスが成功したかどうかを確認するのが非常に難しいという問題があります。

関連情報