web-dev-qa-db-ja.com

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

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ファイルがあります。したがって、${len}の結果として '2'が期待されます。ただし、1を出力します。理由は、findのすべての結果を1つの要素として取るためです。どうすれば修正できますか?

追伸
StackOverFlowで同様の問題に関するいくつかの解決策を見つけました。ただし、これらは少し異なるため、私の場合は適用できません。ループの前に結果を変数に保存する必要があります。再度、感謝します。

57
Juneyoung Oh

findの出力をbash配列に取得するための1つのソリューションを次に示します。

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

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

使い方

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

  2. readステートメントが実行されるたびに、ヌルで区切られたファイル名が標準入力から読み取られます。 -rオプションは、バックスラッシュ文字をそのままにするようreadに指示します。 -d $'\0'は、入力がヌルで区切られることをreadに伝えます。 readの名前を省略するため、シェルは入力をデフォルトの名前REPLYに入れます。

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

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

プロセス置換を使用する理由

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

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

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

プロセス置換は広く有用です。コマンドがファイルからreadを必要とする多くの場所で、ファイル名の代わりにプロセス置換<(...)を指定できます。コマンドにファイルへのwriteが必要なファイル名の代わりに使用できる類似の形式>(...)があります。

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

その他の注意事項

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

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

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

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

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

90
John1024

bash 4以降を使用している場合は、findの使用を次のように置き換えることができます。

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

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

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

14
chepner

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

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

空白、改行、グロビング文字を含む任意のファイル名で機能するメソッドの場合。

manual から(他のオプションは省略):

mapfile [-d delim] [array]

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

また、readarraymapfileの同義語です。

11
Benjamin W.

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

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