tl;dr Always quote globs in find: find . -maxdepth 1 -type f -name "*.jpg"
(notice the "
characters surrounding *
).
In your case, the shell is interpreting *.jpg
(note the *
character) and trying to match file names within the current directory that end in .jpg
. There is a file named pic1 (1).jpg
so that file name replaces *.jpg
. The command the system is given by the shell becomes
find . -maxdepth 1 -type f -name 'pic1 (1).jpg'
To see in depth, try tracing the original command using strace
. What is actually executed is:
touch foo.jpg bar.jpg
strace find . -maxdepth 1 -type f -name *.jpg 2>&1 | grep jpg
execve("/usr/bin/find", ["find", ".", "-maxdepth", "1", "-type", "f", "-name", "bar.jpg", "foo.jpg"], [/* 62 vars */]) = 0
...
Unfortunately the find
command's -name
predicate only accepts a single pattern. If you want to search for multiple files by name you'd need to chain them with the -o
(logical OR
) operator - something like:
find Documents/ \( -name "file2.txt" -o -name "file5.txt" -o -name "file9.txt" \) -print
This makes it tricky to construct the search programmatically from a list; the closest I can come to your attempted command is:
read the list into a shell array
mapfile -t files < list.txt
use the bash shell's printf
to construct the predicate list
printf -- '-name "%s" -o ' "${files[@]}"
use eval
to evaluate the resulting command string
There's a wrinkle here inasmuch as if we use printf
's format re-use feature to construct the list in this way, we're left with a 'dangling' -o
; we can work around this by terminating the list with a -false
test (since -o -false
is a Boolean no-op) so that our final predicate string becomes
"\( $(printf -- '-name "%s" -o ' "${files[@]}") -false \)"
Putting it all together - given
$ tree dir
dir
├── Folder_1
│ ├── file1.txt
│ ├── file2.txt
│ ├── file3.txt
│ └── file4.txt
├── Folder_2
│ ├── file5.txt
│ ├── file6.txt
│ └── file7.txt
└── Folder_3
├── file8.txt
└── file9.txt
3 directories, 9 files
and
$ cat list.txt
file2.txt
file5.txt
file9.txt
then
$ mapfile -t files < list.txt
$ eval find dir/ "\( $(printf -- '-name "%s" -o ' "${files[@]}") -false \)" -print
dir/Folder_1/file2.txt
dir/Folder_2/file5.txt
dir/Folder_3/file9.txt
To copy files instead of just listing them, you could then do
$ mkdir newdir
$ eval find dir/ "\( $(printf -- '-name "%s" -o ' "${files[@]}") -false \)" -exec cp -t newdir/ -- {} +
resulting in
$ tree newdir
newdir
├── file2.txt
├── file5.txt
└── file9.txt
0 directories, 3 files
Note: the eval
command is powerful and potentially open to abuse: use with care.
In practice, given that you appear to want to find only a small number of files, the KISS approach would be to accept the performance hit of multiple find
calls and just use a loop:
while read -r f; do
find dir/ -name "$f" -exec cp -v -- {} newdir/ \;
done < list.txt
or even using xargs
xargs -a list.txt -n1 -I@ find dir/ -name @ -exec cp -v -- {} newdir/ \;
Best Answer
find
returns the root path because it matches your criteria—i.e. it is a directory, and it doesn't start with_
.You're looking for
-mindepth 1
, I suspect:Reference: find manpage