There's a bit of a difference between the four script invocations you posted.
./script.sh -i "'foo bar' 'another file'"
./script.sh -i "foo\ bar another\ file"
The above two both pass to the script -i as the first argument, and a single string as the second. In the first one, that's 'foo bar' 'another file', and in the second, it's foo\ bar another\ file. In the shell language, both are valid ways to present the two strings (or filenames) foo bar and another file. But the quote and backslash processing only applies when the strings are on a raw command line, not when they're inside a variable, as they end up in the string.
./script.sh -i foo\ bar another\ file
./script.sh -i 'foo bar' 'another file'
On the other hand, these two pass a total of three arguments: -i, foo bar, and another file.
The difference is somewhat important in that it's much easier to deal safely with distinct arguments. You just need to keep them intact, and don't have to process the quotes and escapes embedded within.
Also, importantly, running something like script ./*.txt will pass the filenames as distinct arguments.
E.g. this would just call file on both files if called as script 'foo bar' another\ file:
#!/bin/sh
for f in "$@"; do
file "$f"
done
But you have the getopts there, too. And with the filenames as distinct arguments, only the first would appear as the argument to -i. Here, there's basically two common options.
Either have the user use the -i option repeatedly, collecting the filenames to an array, so:
#!/bin/bash
files=()
while getopts "i:" arg; do
case $arg in
i) files+=("$OPTARG");;
esac
done
for f in "${files[@]}"; do file "$f"; done
and run as script -i "foo bar" -i "another file". (Running script -i file1 file2 would have file2 ignored.) Similarly you could add another array to collect filenames given through another option.
Or, have the option set the "mode" the script works in, and take the filenames as a list distinct from the options. getopts leaves all the arguments intact, you'll just have to drop the ones it processed with shift. So:
#!/bin/bash
while getopts "ie" arg; do
case $arg in
i) mode=i;;
e) mode=e;;
esac
done
shift $(($OPTIND - 1))
if [ "$mode" = i ]; then
for f in "$@"; do file "$f"; done
elif [ "$mode" = e ]; then
echo "do something else with the files"
else
echo "error: invalid mode" >&2
fi
and then run it as script -i "foo bar" "another file".
I'm assuming here that you are also doing something else with getopts other than taking the -i in, since otherwise you could just drop the flag entirely. :) But what other options you have, somewhat affects what the most sensibly (or customary) solution is.
Also if your loop only calls file on the files, you could just run file "${files[@]}" or file "$@" and skip the loop.
However, if you want to be able to this:
script -i foo bar -e doo daa
and have the script to do one thing for the files foo and bar, and another thing for doo and daa, then that's a bit of a different issue. It can be done, sure, but getopts might not be the tool for that.
See also:
- https://mywiki.wooledge.org/BashGuide/Arrays for arrays in particular
- How can we run a command stored in a variable? which also discusses arrays, even though the title is about commands.
And of course:
- https://mywiki.wooledge.org/WordSplitting
- Why does my shell script choke on whitespace or other special characters?
for the issues with trying to deal with multiple distinct arbitrary strings (filenames) within a single variable.