Retrieving multiple arguments for a single option using getopts in Bash
Solution 1
You can use the same option multiple times and add all values to an array.
For the very specific original question here, Ryan's mkdir -p
solution is obviously the best.
However, for the more general question of getting multiple values from the same option with getopts, here it is:
#!/bin/bash
while getopts "m:" opt; do
case $opt in
m) multi+=("$OPTARG");;
#...
esac
done
shift $((OPTIND -1))
echo "The first value of the array 'multi' is '$multi'"
echo "The whole list of values is '${multi[@]}'"
echo "Or:"
for val in "${multi[@]}"; do
echo " - $val"
done
The output would be:
$ /tmp/t
The first value of the array 'multi' is ''
The whole list of values is ''
Or:
$ /tmp/t -m "one arg with spaces"
The first value of the array 'multi' is 'one arg with spaces'
The whole list of values is 'one arg with spaces'
Or:
- one arg with spaces
$ /tmp/t -m one -m "second argument" -m three
The first value of the array 'multi' is 'one'
The whole list of values is 'one second argument three'
Or:
- one
- second argument
- three
Solution 2
I know this question is old, but I wanted to throw this answer on here in case someone comes looking for an answer.
Shells like BASH support making directories recursively like this already, so a script isn't really needed. For instance, the original poster wanted something like:
$ foo.sh -i test -d directory -s subdirectory -s subdirectory2 -f file1 file2 file3
/test/directory/subdirectory/file1
/test/directory/subdirectory/file2
/test/directory/subdirectory/file3
/test/directory/subdirectory2/file1
/test/directory/subdirectory2/file2
/test/directory/subdirectory2/file3
This is easily done with this command line:
pong:~/tmp
[10] rmclean$ mkdir -pv test/directory/{subdirectory,subdirectory2}/{file1,file2,file3}
mkdir: created directory ‘test’
mkdir: created directory ‘test/directory’
mkdir: created directory ‘test/directory/subdirectory’
mkdir: created directory ‘test/directory/subdirectory/file1’
mkdir: created directory ‘test/directory/subdirectory/file2’
mkdir: created directory ‘test/directory/subdirectory/file3’
mkdir: created directory ‘test/directory/subdirectory2’
mkdir: created directory ‘test/directory/subdirectory2/file1’
mkdir: created directory ‘test/directory/subdirectory2/file2’
mkdir: created directory ‘test/directory/subdirectory2/file3’
Or even a bit shorter:
pong:~/tmp
[12] rmclean$ mkdir -pv test/directory/{subdirectory,subdirectory2}/file{1,2,3}
mkdir: created directory ‘test’
mkdir: created directory ‘test/directory’
mkdir: created directory ‘test/directory/subdirectory’
mkdir: created directory ‘test/directory/subdirectory/file1’
mkdir: created directory ‘test/directory/subdirectory/file2’
mkdir: created directory ‘test/directory/subdirectory/file3’
mkdir: created directory ‘test/directory/subdirectory2’
mkdir: created directory ‘test/directory/subdirectory2/file1’
mkdir: created directory ‘test/directory/subdirectory2/file2’
mkdir: created directory ‘test/directory/subdirectory2/file3’
Or shorter, with more conformity:
pong:~/tmp
[14] rmclean$ mkdir -pv test/directory/subdirectory{1,2}/file{1,2,3}
mkdir: created directory ‘test’
mkdir: created directory ‘test/directory’
mkdir: created directory ‘test/directory/subdirectory1’
mkdir: created directory ‘test/directory/subdirectory1/file1’
mkdir: created directory ‘test/directory/subdirectory1/file2’
mkdir: created directory ‘test/directory/subdirectory1/file3’
mkdir: created directory ‘test/directory/subdirectory2’
mkdir: created directory ‘test/directory/subdirectory2/file1’
mkdir: created directory ‘test/directory/subdirectory2/file2’
mkdir: created directory ‘test/directory/subdirectory2/file3’
Or lastly, using sequences:
pong:~/tmp
[16] rmclean$ mkdir -pv test/directory/subdirectory{1..2}/file{1..3}
mkdir: created directory ‘test’
mkdir: created directory ‘test/directory’
mkdir: created directory ‘test/directory/subdirectory1’
mkdir: created directory ‘test/directory/subdirectory1/file1’
mkdir: created directory ‘test/directory/subdirectory1/file2’
mkdir: created directory ‘test/directory/subdirectory1/file3’
mkdir: created directory ‘test/directory/subdirectory2’
mkdir: created directory ‘test/directory/subdirectory2/file1’
mkdir: created directory ‘test/directory/subdirectory2/file2’
mkdir: created directory ‘test/directory/subdirectory2/file3’
Solution 3
getopts options can only take zero or one argument. You might want to change your interface to remove the -f option, and just iterate over the remaining non-option arguments
usage: foo.sh -i end -d dir -s subdir file [...]
So,
while getopts ":i:d:s:" opt; do
case "$opt" in
i) initial=$OPTARG ;;
d) dir=$OPTARG ;;
s) sub=$OPTARG ;;
esac
done
shift $(( OPTIND - 1 ))
path="/$initial/$dir/$sub"
mkdir -p "$path"
for file in "$@"; do
touch "$path/$file"
done
Solution 4
I fixed the same problem you had like this:
Instead of:
foo.sh -i test -d directory -s subdirectory -s subdirectory2 -f file1 file2 file3
Do this:
foo.sh -i test -d directory -s "subdirectory subdirectory2" -f "file1 file2 file3"
With the space separator you can just run through it with a basic loop. Here's the code:
while getopts ":i:d:s:f:" opt
do
case $opt in
i ) initial=$OPTARG;;
d ) dir=$OPTARG;;
s ) sub=$OPTARG;;
f ) files=$OPTARG;;
esac
done
for subdir in $sub;do
for file in $files;do
echo $subdir/$file
done
done
Here's a sample output:
$ ./getopts.sh -s "testdir1 testdir2" -f "file1 file2 file3"
testdir1/file1
testdir1/file2
testdir1/file3
testdir2/file1
testdir2/file2
testdir2/file3
Solution 5
If you want to specify any number of values for an option, you can use a simple loop to find them and stuff them into an array. For example, let's modify the OP's example to allow any number of -s parameters:
unset -v sub
while getopts ":i:d:s:f:" opt
do
case $opt in
i ) initial=$OPTARG;;
d ) dir=$OPTARG;;
s ) sub=("$OPTARG")
until [[ $(eval "echo \${$OPTIND}") =~ ^-.* ]] || [ -z $(eval "echo \${$OPTIND}") ]; do
sub+=($(eval "echo \${$OPTIND}"))
OPTIND=$((OPTIND + 1))
done
;;
f ) files=$OPTARG;;
esac
done
This takes the first argument ($OPTARG) and puts it into the array $sub. Then it will continue searching through the remaining parameters until it either hits another dashed parameter OR there are no more arguments to evaluate. If it finds more parameters that aren't a dashed parameter, it adds it to the $sub array and bumps up the $OPTIND variable.
So in the OP's example, the following could be run:
foo.sh -i test -d directory -s subdirectory1 subdirectory2 -f file1
If we added these lines to the script to demonstrate:
echo ${sub[@]}
echo ${sub[1]}
echo $files
The output would be:
subdirectory1 subdirectory2
subdirectory2
file1
vegasbrianc
Updated on July 05, 2022Comments
-
vegasbrianc almost 2 years
I need help with
getopts
.I created a Bash script which looks like this when run:
$ foo.sh -i env -d directory -s subdirectory -f file
It works correctly when handling one argument from each flag. But when I invoke several arguments from each flag I am not sure how to pull the multiple variable information out of the variables in
getopts
.while getopts ":i:d:s:f:" opt do case $opt in i ) initial=$OPTARG;; d ) dir=$OPTARG;; s ) sub=$OPTARG;; f ) files=$OPTARG;; esac done
After grabbing the options I then want to build directory structures from the variables
foo.sh -i test -d directory -s subdirectory -s subdirectory2 -f file1 file2 file3
Then the directory structure would be
/test/directory/subdirectory/file1 /test/directory/subdirectory/file2 /test/directory/subdirectory/file3 /test/directory/subdirectory2/file1 /test/directory/subdirectory2/file2 /test/directory/subdirectory2/file3
Any ideas?
-
vegasbrianc almost 13 yearsThis makes more sense but not 100% yet. My file list will pull the values from the flags to build a directory path to the file. if a second one is built then I need to rebuild a new path to the second directory/file
-
vegasbrianc almost 13 yearswould getopt be a better option for this or would you use somehting different?
-
glenn jackman almost 13 yearsI would use just what I wrote above. If you want to be able to provide the -f option with multiple arguments, or to be able to provide -f with one argument multiple times, I know you can do that in Perl with the Getopt::Long module.
-
frankc almost 13 yearsI agree with Glenn, this is normally what I use. However, another option is to just use another delimiter e.g. commas to separate the multiple arguments instead of spaces and then split $OPTARG on comma. For example -f file1,file2,file3. I tend to only do this on commands i plan to keep to myself as I don't trust others to realize they must not put spaces after the commas
-
choroba about 11 yearsAlso note that
in "$@"
can be deleted without changing the semantics. -
Zac Thompson over 9 yearsYah, except it's making directories instead of files as the leaf nodes.
-
cchamberlain about 9 yearsgetopts supports multiple for the same option per @mivk solution below -
usage: foo.sh -i end -d dir -s subdir1 -s subdir2 -s subdir3 file [...]
-
glenn jackman about 9 years@cole, yes, and in the case branch you would append to an array:
subdirs+=( "$OPTARG" )
-
chrBrd over 7 yearsThanks for this - I've been wondering if there was a neat way of avoiding getopts and while your answer's shown me that there isn't, it has shown me that my home-baked alternative is on the right lines.
-
jimh about 7 yearsI appreciate this thorough explanation. One addendum is that using "${multi[@]}" instead of ${multi[@]} prevents issues from arguments containing spaces for anyone who didn't know and was curious.
-
laur over 3 yearsThis is the way to do what OP asked in bash.
-
KoZm0kNoT over 3 yearsI also used this approach initally but soon ran into issues when one of my parameters had spaces requiring quotes within quotes. If you have multiple parameters and some of them need quotes it can get messy fast.
-
divinelemon over 2 yearsThank you for this code snippet, which might provide some limited, immediate help. A proper explanation would greatly improve its long-term value by showing why this is a good solution to the problem and would make it more useful to future readers with other, similar questions. Please edit your answer to add some explanation, including the assumptions you’ve made.
-
WhoAmI over 2 yearsIs there a way to do that without quotes?