Home > front end >  Bash script using getopts to store strings as an array
Bash script using getopts to store strings as an array

Time:03-16

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 a while 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 in while getopts ... loop would be an error, redirection do STDERR (>&2) could be addressed to the whole loop instead of repeated on each echo line.
  • ** Note doing a loop over all argument could be written for varname ;do. as $@ stand for default arguments, in "$@" are implicit in for 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
  • Related