「find」コマンドの結果を配列としてBashに保存するにはどうすればよいですか?


96

結果findを配列として保存しようとしています。これが私のコードです:

#!/bin/bash

echo "input : "
read input

echo "searching file with this pattern '${input}' under present directory"
array=`find . -name ${input}`

len=${#array[*]}
echo "found : ${len}"

i=0

while [ $i -lt $len ]
do
echo ${array[$i]}
let i++
done

現在のディレクトリに2つの.txtファイルがあります。したがって、の結果として「2」が期待され${len}ます。ただし、1を出力します。理由は、すべての結果をfind1つの要素として受け取るためです。どうすればこれを修正できますか?

PS
同様の問題についてStackOverFlowでいくつかの解決策を見つけました。ただし、少し違うので応募できません。ループの前に結果を変数に格納する必要があります。再度、感謝します。

回答:


136

Linuxユーザー向けのアップデート2020:

Linuxを使用している場合のように、最新バージョンのbash(4.4-alpha以上)を使用している場合は、BenjaminW。の回答を使用する必要があります

Mac OSを使用していて、最後に確認したところ、まだbash 3.2を使用している場合、または古いbashを使用している場合は、次のセクションに進んでください。

bash4.3以前の回答

の出力を配列にfind取り込むための1つの解決策はbash次のとおりです。

array=()
while IFS=  read -r -d $'\0'; do
    array+=("$REPLY")
done < <(find . -name "${input}" -print0)

一般に、ファイル名にはスペース、新しい行、その他のスクリプトに反する文字を含めることができるため、これは注意が必要です。findファイル名を安全に使用して分離する唯一の方法は-print0、ヌル文字で区切られたファイル名を出力するを使用することです。bashのreadarray/mapfile関数がnullで区切られた文字列をサポートしていれば、これはそれほど不便ではありませんが、サポートしていません。Bashreadはそうしており、それが上記のループにつながります。

[この回答は元々2014年に書かれました。bashの最新バージョンをお持ちの場合は、以下の更新をご覧ください。]

使い方

  1. 最初の行は空の配列を作成します: array=()

  2. readステートメントが実行されるたびに、nullで区切られたファイル名が標準入力から読み取られます。この-rオプションはread、円記号をそのままにしておくように指示します。-d $'\0'伝えread入力がNULLで分離されること。に名前を省略しているためread、シェルは入力をデフォルト名に入れます:REPLY

  3. このarray+=("$REPLY")ステートメントは、新しいファイル名を配列に追加しますarray

  4. 最後の行は、リダイレクトとコマンド置換を組み合わせてfindwhileループの標準入力への出力を提供します。

なぜプロセス置換を使用するのですか?

プロセス置換を使用しなかった場合、ループは次のように記述できます。

array=()
find . -name "${input}" -print0 >tmpfile
while IFS=  read -r -d $'\0'; do
    array+=("$REPLY")
done <tmpfile
rm -f tmpfile

上記では、の出力はfind一時ファイルに保存され、そのファイルはwhileループへの標準入力として使用されます。プロセス置換の考え方は、そのような一時ファイルを不要にすることです。したがって、whileループにstdinをから取得させる代わりに、ループにstdinをからtmpfile取得させることができます<(find . -name ${input} -print0)

プロセス置換は広く役立ちます。コマンドがファイルから読み取りたい多くの場所で<(...)は、ファイル名の代わりにプロセス置換を指定できます。>(...)コマンドがファイルに書き込みたいファイル名の代わりに使用できる類似の形式があります。

配列と同様に、プロセス置換はbashやその他の高度なシェルの機能です。これはPOSIX標準の一部ではありません。

代替:ラストパイプ

必要に応じて、lastpipeプロセス置換の代わりに使用できます(ハットヒント:Caesar):

set +m
shopt -s lastpipe
array=()
find . -name "${input}" -print0 | while IFS=  read -r -d $'\0'; do array+=("$REPLY"); done; declare -p array

shopt -s lastpipe現在のシェル(バックグラウンドではない)のパイプラインの最後のコマンドを実行するようにbashに指示します。このようarrayに、パイプラインが完了した後も存在し続けます。lastpipeジョブ制御がオフになっている場合にのみ有効になるため、を実行しset +mます。(スクリプトでは、コマンドラインとは対照的に、ジョブ制御はデフォルトでオフになっています。)

その他の注意事項

次のコマンドは、シェル配列ではなく、シェル変数を作成します。

array=`find . -name "${input}"`

配列を作成したい場合は、findの出力の周りに親を配置する必要があります。したがって、素朴に、次のことができます。

array=(`find . -name "${input}"`)  # don't do this

問題は、シェルがの結果に対して単語分割を実行するfindため、配列の要素が希望どおりであることが保証されないことです。

2019年更新

バージョン4.4-alpha以降、bashは-dオプションをサポートするようになり、上記のループは不要になりました。代わりに、次のものを使用できます。

mapfile -d $'\0' array < <(find . -name "${input}" -print0)

これに関する詳細については、Benjamin W.の回答を参照(および賛成)してください。


1
@JuneyoungOhお役に立ててうれしいです。プロセス置換のセクションを追加しました。
John1024 2014

3
@Rockalliteこれは良い観察ですが、不完全です。複数の単語に分割しないのは事実ですIFS=が、入力行の最初または最後から空白を削除しないようにする必要があります。あなたはの出力を比較することによって、これを簡単にテストできる read var <<<' abc '; echo ">$var<"の出力とIFS= read var <<<' abc '; echo ">$var<"。前者の場合、前後のスペースabcが削除されます。後者では、そうではありません。空白で始まるまたは終わるファイル名は珍しいかもしれませんが、存在する場合は、正しく処理する必要があります。
John1024 2017

1
私はあなたのコードを実行した後こんにちは、私は近いメッセージ構文エラーを取得する予期しないトークン<' に行わ<<「(AAA / -not -newermt「$ last_build_timestamp_v」タイプF -print0見つける)
はPrzemyslaw Sienkiewicz

1
注:次の''代わりに、より単純なものを使用できます$'\0'n=0; while IFS= read -r -d '' line || [ "$line" ]; do echo "$((++n)):$line"; done < <(printf 'first\nstill first\0second\0third')
glennjackman19年

1
@theeagleあなたが書くつもりだったと思いますBLAH=$(find . -name '*.php')。回答で説明されているように、このアプローチは限られたケースで機能しますが、一般にすべてのファイル名で機能するわけではなく、OPが期待するように配列を生成しません。
John10 2419

36

Bash 4.4では/に-dオプションが導入されたため、これは次の方法で解決できます。readarraymapfile

readarray -d '' array < <(find . -name "$input" -print0)

空白、改行、グロブ文字など、任意のファイル名で機能するメソッドの場合。これには、たとえばGNU findのように、findサポートが必要です-print0

マニュアルから(他のオプションを省略):

mapfile [-d delim] [array]

-d
の最初の文字delimは、改行ではなく、各入力行を終了するために使用されます。delimが空の文字列の場合mapfile、NUL文字を読み取るときに行を終了します。

そしてreadarray、はの同義語ですmapfile


18

あなたが使用している場合はbash4以降を、あなたはあなたの使用のを置き換えることができfind

shopt -s globstar nullglob
array=( **/*"$input"* )

**パターンによって有効globstarパターンは、現在のディレクトリ内の任意の深さに一致することができ、マッチ0以上のディレクトリ。このnullglobオプションがない場合、パターン(パラメーター展開後)は文字通りに扱われるため、一致しない場合は、空の配列ではなく単一の文字列を持つ配列になります。

dotglob隠しディレクトリ(など.ssh)をトラバースし、隠しファイル(など.bashrc)も照合する場合は、最初の行にもオプションを追加します。


4
多分nullglobあまりにも…
kojiro 2014

1
ええ、私はいつもそれを忘れています。
chepner 2014

5
dotglobが設定されていない限り、これには隠しファイルと隠しディレクトリが含まれないことに注意してください(これは必要な場合と不要な場合がありますが、言及する価値もあります)。
gniourf_gniourf 2014

10

あなたは次のようなことを試すことができます

array=(`find . -type f | sort -r | head -2`)
、および配列値を出力するために、echoのようなものを試すことができます "${array[*]}"


8
スペースまたはグロブ文字を含むファイル名がある場合は中断します。
gniourf_gniourf 2017

2

以下は、macOS上のBashとZShellの両方で機能するようです。

#! /bin/sh

IFS=$'\n'
paths=($(find . -name "foo"))
unset IFS

printf "%s\n" "${paths[@]}"

これは、スペースやその他の特殊文字を含むファイルで機能しますが、名前に改行があるファイルの(明らかにまれな)ケースでは失敗します。あなたがテストのために作成することができますprintf "%b" "file name with spaces, a star * ...\012and a second line\0" | xargs -0 touch
ステフェイン・グーリッホン

ここで何かが足りないかもしれませんが、これは99%のケースではるかに明確で簡単な解決策のようです
MattKorostoff20年

-1

bashでは$(<any_shell_cmd>)、コマンドの実行と出力のキャプチャに役立ちます。これを区切り文字としてIFSwithに渡す\nと、配列に変換するのに役立ちます。

IFS='\n' read -r -a txt_files <<< $(find /path/to/dir -name "*.txt")

4
これにより、結果の最初のファイルのみfindが配列に取り込まれます。
ベンジャミン

-2

あなたはこのようにすることができます:

#!/bin/bash
echo "input : "
read input

echo "searching file with this pattern '${input}' under present directory"
array=(`find . -name '*'${input}'*'`)

for i in "${array[@]}"
do :
    echo $i
done

1
ありがとう。たくさん。しかし、@ anishsaneが指摘したように、私のプログラムではファイル名の空のスペースを考慮する必要があります。まあありがとう!
Juneyoung Oh 2014

-3

私にとって、これはcygwinでうまく機能しました:

declare -a names=$(echo "("; find <path> <other options> -printf '"%p" '; echo ")")
for nm in "${names[@]}"
do
    echo "$nm"
done

これはスペースでは機能しますが、ディレクトリ名に二重引用符( ")を使用することはできません(Windows環境では許可されていません)。

-printfオプションのスペースに注意してください。


3
壊れて危険:引用符を処理せず、任意のコードが挿入される可能性があります。使ってはいけません。
gniourf_gniourf 2017

2
誰かがこの投稿に削除のフラグを付けたようです。「間違っている」というのは、SOで削除する理由ではありません。ユーザーが回答を試みましたが、トピックに基づいており、回答の基準を満たしています。反対票ボタンは、削除ボタンではなく、有用性と正確性を測定するために使用されます。
フランボット2018年

3
gniourfが指摘したように、Webページなど、他の人がシステムのオプションを入力する環境向けではありません。しかし、誰もがその環境のためにプログラムしているわけではありません。ディレクトリ内のファイルの名前を変更するために使用しました。
RRisack19年
弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.