arrays - script - store the output of find command in a variable




How can I store find command result as arrays in Bash (4)

I am trying to save find result as arrays. Here is my code:

#!/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

I get 2 .txt files under current directory. So I expect '2' as result of ${len}. However, it prints 1. The reason why is it takes all result of find as one elements. How can I fix this? Thanks.

P.S I found several solutions in StackOverFlow about similar problem. However, it is little bit different so I can apply to mine. I need to store result to variable before loop. Thanks again.


For me, this worked fine on cygwin:

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

This works with spaces, but not with double quotes (") in the directory names.

Beware the space in the -printf option.


Here is one solution for getting the output of find into a bash array:

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

This is tricky because, in general, file names can have spaces, new lines, and other script-hostile characters. The only way to use find and have the file names safely separated from each other is to use -print0 which prints the file names separated with a null character. This would not be much of an inconvenience if bash's readarray/mapfile functions supported null-separated strings but they don't. Bash's read does and that leads us to the loop above.

How it works

  1. The first line creates an empty array: array=()

  2. Every time that the read statement is executed, a null-separated file name is read from standard input. The -r option tells read to leave backslash characters alone. The -d $'\0' tells read that the input will be null-separated. Since we omit the name to read, the shell puts the input into the default name: REPLY.

  3. The array+=("$REPLY") statement appends the new file name to the array array.

  4. The final line combines redirection and command substitution to provide the output of find to the standard input of the while loop.

Why use process substitution?

If we didn't use process substitution, the loop could be written as:

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

In the above the output of find is stored in a temporary file and that file is used as standard input to the while loop. The idea of process substitution is to make such temporary files unnecessary. So, instead of having the while loop get its stdin from tmpfile, we can have it get its stdin from <(find . -name ${input} -print0).

Process substitution is widely useful. In many places where a command wants to read from a file, you can specify process substitution, <(...), instead of a file name. There is an analogous form, >(...), that can be used in place of a file name where the command wants to write to the file.

Like arrays, process substitution is a feature of bash and other advanced shells. It is not part of the POSIX standard.

Additional notes

The following command creates a shell variable, not a shell array:

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

If you wanted to create an array, you would need to put parens around the output of find. So, naively, one could:

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

The problem is that the shell performs word splitting on the results of find so that the elements of the array are not guaranteed to be what you want.


In bash, $(<any_shell_cmd>) helps to run a command and capture the output. Passing this to IFS with \n as delimeter helps to convert that as to an array.

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

You could do like this:

#!/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






find