As you noticed, the -name
option (and its friends) of find(1) award special treatment to some characters as described in glob(7). Fortunately, all these special characters can be escaped with a backslash. Therefore, one way to work around this issue is to escape these characters before passing them to -name
.
The most portable way to escape these characters on the command-line is probably sed(1):
printf '%s' "some s[t]ra*nge? string" | sed -e 's/[][?*\\]/\\&/g'
Example:
$ song='R. Kelly - Step In The Name Of Love [mp3clan.com].mp3'
$ printf '%s' "$song" | sed -e 's/[][?*\\]/\\&/g'; echo
R. Kelly - Step In The Name Of Love \[mp3clan.com\].mp3
Putting it together using command substitution:
find "$musicroot" -name "$(printf '%s' "$song" | sed -e 's/[][?*\\]/\\&/g')"
If you need to do this to a lot of strings this may take quite a while because each invocation of sed
spawns a new process which is very tedious for the operating system. An approach with better performance would be to do all the substitutions with only one invocation of sed
and read them in a loop:
printf '%s\0' "s[o]me" "s?range" "str*ngs" "${string_array[@]}" |
sed -z -e 's/[][?*\\]/\\&/g' |
while read -r -d '' escaped_string; do
do_something_with "$escaped_string"
done
I’m using null-terminated lines here because line breaks are character in file path names. sed -z
and read -d ''
are used to handle these correctly but they’re not so portable.
If you need both the escaped and the unescaped string inside the loop we’ll use another trick:
printf '%s\0' "s[o]me" "s?range" "str*ngs" "${string_array[@]}" |
awk 'BEGIN{ RS=ORS="\0"; } { print; gsub(/[][?*\\]/, "\\\\&"); print; }' |
while read -r -d '' unescaped_string && read -r -d '' escaped_string; do
do_something_with "$escaped_string"
do_something_else_with "$unescaped_string"
done