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で同様の問題に関するいくつかの解決策を見つけました。ただし、これらは少し異なるため、私の場合は適用できません。ループの前に結果を変数に保存する必要があります。再度、感謝します。
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
は、上記のループにつながります。
最初の行は空の配列を作成します:array=()
read
ステートメントが実行されるたびに、ヌルで区切られたファイル名が標準入力から読み取られます。 -r
オプションは、バックスラッシュ文字をそのままにするようread
に指示します。 -d $'\0'
は、入力がヌルで区切られることをread
に伝えます。 read
の名前を省略するため、シェルは入力をデフォルトの名前REPLY
に入れます。
array+=("$REPLY")
ステートメントは、新しいファイル名を配列array
に追加します。
最後の行は、リダイレクトとコマンド置換を組み合わせて、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分割を実行するため、配列の要素が必要なものであることが保証されないことです。
bash
4以降を使用している場合は、find
の使用を次のように置き換えることができます。
shopt -s globstar nullglob
array=( **/*"$input"* )
globstar
によって有効にされた**
パターンは、0個以上のディレクトリに一致し、パターンを現在のディレクトリの任意の深さに一致させることができます。 nullglob
オプションを使用しない場合、パターン(パラメーター展開後)は文字どおりに処理されるため、一致しない場合、空の配列ではなく単一の文字列を持つ配列になります。
隠しディレクトリ(.ssh
など)をトラバースし、隠しファイル(.bashrc
など)にも一致させる場合は、dotglob
オプションも最初の行に追加します。
Bash 4.4ではreadarray
/mapfile
に-d
オプションが導入されたため、次の方法で解決できます。
readarray -d '' array < <(find . -name "$input" -print0)
空白、改行、グロビング文字を含む任意のファイル名で機能するメソッドの場合。
manual から(他のオプションは省略):
mapfile [-d delim] [array]
-d
delim
の最初の文字は、改行ではなく各入力行を終了するために使用されます。delim
が空の文字列の場合、mapfile
はNUL文字を読み取ったときに行を終了します。
また、readarray
はmapfile
の同義語です。
Bashでは、$(<any_Shell_cmd>)
はコマンドの実行と出力のキャプチャに役立ちます。 \n
を区切り文字としてIFS
に渡すと、配列に変換するのに役立ちます。
IFS='\n' read -r -a txt_files <<< $(find /path/to/dir -name "*.txt")