find
will use the first arguments (up to the first argument that starts with -
or that is !
or (
) that it gets as the top-level paths to search. You are giving find
a single argument when you call it in your function, the string $1 $dir_pattern $file_pattern
(with the variables expanded). This path is not found.
You also include literal double quotes in the arguments that you intend do give to find
. Double quoting is done to prevent the shell from expanding glob patterns and from splitting on whitespaces (or whatever the IFS
variable contains), but if you use e.g. ! -name \"thing\"
then the double quotes would be part of the pattern that find
uses to compare against the filenames.
Use arrays, and quote the separate arguments to find
properly:
myfind () {
local ignore_paths=( "$1" "*foo*" )
local ignore_names=( "one.txt" "two.txt" "*three*.txt" )
local path_args=()
for string in "${ignore_paths[@]}"; do
path_args+=( ! -path "$string" )
done
local name_args=()
for string in "${ignore_names[@]}"; do
name_args+=( ! -name "$string" )
done
find "$1" "${path_args[@]}" "${name_args[@]}"
}
Each time we append to path_args
and name_args
above, we add three elements to the list, !
, -path
or -name
, and "$string"
. When expanding "${path_args[@]}"
and "${name_args[@]}"
(note the double quotes), the elements will be individually quoted.
Equivalent implementation suitable for /bin/sh
:
myfind () (
topdir=$1
set --
# paths to ignore
for string in "$topdir" "*foo*"; do
set -- "$@" ! -path "$string"
done
# names to ignore
for string in "one.txt" "two.txt" "*three*.txt"; do
set -- "$@" ! -name "$string"
done
find "$topdir" "$@"
)
In the sh
shell we only have a single array available to us, which is the list of positional parameters, $@
, so we collect our find
options in that. The bash
-specific solution could also be written to use a single array, obviously, and the sh
variation would run in bash
too.
And lastly, the output of your echo
test is not an accurate representation of the command that would have been executed by your function.
Consider this:
cat "my file name"
which runs cat
on something called my file name
, and
echo cat "my file name"
which outputs the string cat my file name
. This is due to the fact that the shell removes quotes around strings before executing the command. Running that command, cat
would look for three files, not one.
Your command worked well when you copy-pasted it into the shell, because you included the literal double quotes in the string that was outputted by echo
(by escaping them), but that was not the actual command executed by your function.