1. ホーム
  2. arrays

[解決済み] Bashでfindコマンドの結果を配列として保存するには?

2022-09-24 07:05:17

質問

の結果を保存しようとしています。 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を表示します。 その理由は find の結果を1つの要素として受け取ってしまうからです。 どうしたら直せますか?

P.S

StackOverFlowで似たような問題でいくつかの解決策を見つけました。しかし、彼らは少し異なっているので、私は私のケースに適用することはできません。私はループの前に結果を変数に格納する必要があります。本当にありがとうございます。

どのように解決するのですか?

Linux ユーザーのための 2020 年アップデート。

Linux であればおそらくそうであるように、最新バージョンの bash (4.4-alpha またはそれ以上) を使用している場合は Benjamin W. の回答 .

もしあなたがMac OSを使っていて、最後に確認したときはまだbash 3.2を使っていた、あるいはそれ以外の古いbashを使っているなら、次のセクションに進みます。

bash 4.3 またはそれ以前のバージョンに対する回答

の出力を取得するための1つの解決策です。 find の出力を bash の配列に変換します。

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

一般に、ファイル名には空白や改行などのスクリプトにとって好ましくない文字が含まれることがあるため、これは厄介なことです。 唯一の方法は find を使用し、ファイル名を安全に分離する唯一の方法は -print0 を使うことです。これはファイル名をヌル文字で区切って表示します。 これは、もしbashの readarray / mapfile のような関数がヌル文字列をサポートしていましたが、そうではありません。 Bashの read がそうで、それが上のループにつながるのです。

[この回答は2014年に書かれたものです。 最近のバージョンのbashをお持ちの方は、以下のアップデートをご覧ください] 。

どのように動作するか

  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) .

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

配列と同様、プロセス置換はbashや他の先進的なシェルの機能である。 POSIX標準の一部ではありません。

代替: lastpipe

必要であれば lastpipe をプロセス置換の代わりに使うことができます(脱帽。 シーザー ):

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の出力に括弧をつける必要があります。 だから、素朴に、1つはできた。

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

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

2019年更新

バージョン4.4-alphaから、bashが -d オプションが追加され、上記のループは必要なくなりました。 代わりに、1つは使用することができます。

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

これについての詳細は、以下を参照してください (そしてアップボートしてください)。 Benjamin W. さんの回答 .