I am working on a Bash script that needs to take zero to multiple strings as an input but I am unsure how to do this because of the lack of a flag before the list.
The script usage:
script [ list ] [ -t <secs> ] [ -n <count> ]
The list takes zero, one, or multiple strings as input. When a space is encountered, that acts as the break between the strings in a case of two or more. These strings will eventually be input for a grep
command, so my idea is to save them in an array of some kind. I currently have the -t
and -n
working correctly. I have tried looking up examples but have been unable to find anything that is similar to what I want to do. My other concern is how to ignore string input after a flag is set so no other strings are accepted.
My current script:
while getopts :t:n: arg; do
case ${arg} in
t)
seconds=${OPTARG}
if ! [[ $seconds =~ ^[1-9][0-9]*$ ]] ; then
exit
fi
;;
n)
count=${OPTARG}
if ! [[ $count =~ ^[1-9][0-9]*$ ]] ; then
exit
fi
;;
:)
echo "$0: Must supply an argument to -$OPTARG" >&2
exit
;;
?)
echo "Invalid option: -${OPTARG}"
exit
;;
esac
done
Edit: This is for a homework assignment and am unsure if the order of arguments can change
CodePudding user response:
Would you please try the following:
#!/bin/bash
# parse the arguments before getopts
for i in "$@"; do
if [[ $i = "-"* ]]; then
break
else # append the arguments to "list" as long as it does not start with "-"
list =("$1")
shift
fi
done
while getopts :t:n: arg; do
: your "case" code here
done
# see if the variables are properly assigned
echo "seconds=$seconds" "count=$count"
echo "list=${list[@]}"
CodePudding user response:
Try:
#! /bin/bash -p
# Set defaults
count=10
seconds=20
args=( "$@" )
end_idx=$(($#-1))
# Check for '-n' option at the end
if [[ end_idx -gt 0 && ${args[end_idx-1]} == -n ]]; then
count=${args[end_idx]}
end_idx=$((end_idx-2))
fi
# Check for '-t' option at the (possibly new) end
if [[ end_idx -gt 0 && ${args[end_idx-1]} == -t ]]; then
seconds=${args[end_idx]}
end_idx=$((end_idx-2))
fi
# Take remaining arguments up to the (possibly new) end as the list of strings
strings=( "${args[@]:0:end_idx 1}" )
declare -p strings seconds count
- The basic idea is to process the arguments right-to-left instead of left-to-right.
- The code assumes that the only acceptable order of arguments is the one given in the question. In particular, it assumes that the
-t
and-n
options must be at the end if they are present, and they must be in that order if both are present. - It makes no attempt to handle option arguments combined with options (e.g.
-t5
instead of-t 5
). That could be done fairly easily if required. - It's OK for strings in the list to begin with
-
.
CodePudding user response:
My shorter version
Some remarks:
- Instead of loop over all argument**, then
break
if argument begin by-
, I simply use awhile
loop. - From How do I test if a variable is a number in Bash?, added efficient
is_int
test function - As any output (
echo
) done inwhile getopts ...
loop would be an error, redirection doSTDERR
(>&2
) could be addressed to the whole loop instead of repeated on eachecho
line. - ** Note doing a loop over all argument could be written
for varname ;do
. as$@
stand for default arguments,in "$@"
are implicit infor
loop.
#!/bin/bash
is_int() { case ${1#[- ]} in
'' | *[!0-9]* ) echo "Argument '$1' is not a number"; exit 3;;
esac ;}
while [[ ${1%%-*} ]];do
args =("$1")
shift
done
while getopts :t:n: arg; do
case ${arg} in
t ) is_int "${OPTARG}" ; seconds=${OPTARG} ;;
n ) is_int "${OPTARG}" ; count=${OPTARG} ;;
: ) echo "$0: Must supply an argument to -$OPTARG" ; exit 2;;
? ) echo "Invalid option: -${OPTARG}" ; exit 1;;
esac
done >&2
declare -p seconds count args