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.
files
will 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? – terdon Apr 08 '21 at 16:26getopts
in this script if you only have a single option that signals "use these files". The script could just loop over"$@"
to process the files. – Kusalananda Apr 08 '21 at 17:14-i
option the only one that takes what amounts to multiple values? – Kusalananda Apr 08 '21 at 17:38-i "foo bar" -i "another file"
and use an array to store each values (see @roiama's answer for that part) – Olivier Dulac Apr 09 '21 at 13:11