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
Or, even simpler and more portable as for loops over the positional parameters by default:
#! /bin/sh -
for f 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 -
unset -v mode
while getopts "ie" arg; do
case $arg in
(i) mode=i;;
(e) mode=e;;
(*) : handle usage error;;
esac
done
shift "$((OPTIND - 1))"
case "$mode" in
(i) for f do file -- "$f"; done;;
(e) echo "do something else with the files";;
(*) echo "error: mode not specified" >&2;;
esac
and then run it as script -i "foo bar" "another file" or script -i -- -file-starting-with-dash- -other-file- or script -i -- *.
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 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:
And of course:
for the issues with trying to deal with multiple distinct arbitrary strings (filenames) within a single variable.
fileswill be populated. Will it be all arguments? Some? A command line option? Also, this is almost certainly a dupe of Why does my shell script choke on whitespace or other special characters?. Does it answer your question?getoptsin this script if you only have a single option that signals "use these files". The script could just loop over"$@"to process the files.-ioption the only one that takes what amounts to multiple values?