In bash, I know the following is roughly equivalent:
test=$(find . -iname '*.py')
when I echo $test[0]
however, I don't get the first item, so I think find returns a string like object.
For some values of "roughly". Rather rough values of "roughly", actually.
Pipes and command substitutions rely on reading the output (standard output, stdout) of commands, and that's something of a file-like object (except of course it isn't seekable, i.e. random access). It's a byte stream. It doesn't allow transferring data structures, except by somehow serializing them to a sequence of bytes.
What you're doing with test=$(find . -iname '*.py')
is to ask find
to print filenames, one per line, and then to have the shell read that as a string into a variable test
. The variable will contain something like hello.txt<newline>there.txt
. That's ok up to an extent, but it's not a shell array, so you can't index into it, and it'll break if your filenames contain newlines. (Also, for f in $(find...)
would also break if the filenames contain whitespace, because that's a different scenario and one where word splitting comes into play.)
If you want to read the output of find
into an array in Bash, you could use readarray
and a process substitution (the <(...)
thing):
readarray -d '' -t files < <(find ... -print0)
printf '%s\n' "Third element of array is '${files[2]}'"
-print0
tells find
to terminate each filename with a NUL byte in the output: as that's the only byte that can't appear in a filename, it's what works for unambiguous serialization. -d ''
tells readarray
to expect the NUL as the separator. Note the array indexing starts from zero in Bash (see http://mywiki.wooledge.org/BashGuide/Arrays).
(as for portability: not all shells support arrays, and while e.g. zsh also does, their behaviour is slightly different there. -print0
also isn't standard, but is widely supported.)
Or, if you just want a list of files that match a shell glob:
files=( *.py )
(with the caveat that in bash
, you'll get one element called *.py
if there's no matching file unless you set the nullglob
option).
Here, you could use globstar
, dotglob
, extglob
or whatever, but of course the options you have depend on the shell and are different from those find
gives.
bash
:test=( *.py ); echo "${test[0]}"
? A command substitution ($(...)
) always returns a single string. You generally don't want to store the output fromfind
though, see Why is looping over find's output bad practice? It's unclear whether this is a question about syntax or about solving a particular issue. – Kusalananda May 10 '22 at 09:22test=( *.py )
isn't recursive as far as I can tell – user32882 May 10 '22 at 09:25shopt -s globstar
followed bytest=( ./**/*.py )
, depending on what you want to achieve. This is sorted, whilefind
would not find the files in any particular order. It's still unclear what your actual, underlying problem you are trying to solve is. – Kusalananda May 10 '22 at 09:26find
happens to find, you could also use-print -quit
at the end (if you use GNUfind
) without storing the pathnames at all. – Kusalananda May 10 '22 at 09:29