2

I have a shell script that accepts a variety of options, some with arguments, some without, some short, some long. It should handle some of these options itself and pass on the rest it does not know how to care about to another program. Can I get getopts or something along its lines to store away unknown arguments?

So for example, say my script is called program and should accept arguments

  • -n with a single argument,
  • --verbose without any argument and
  • -s without any argument.

It parses and prints all its options and their arguments and then calls echo rest: with anything that remains. The following output should be observed.

> program -sin42 --long --verbose
-s
-n with argument 42
--verbose
rest: -i --long 

> program -n --short
-n with argument --short

> program -n
error: -n without argument

Can something like this be achieved in a shell script?

6
  • Have you considered using -h or --help to list all the options?
    – eyoung100
    Apr 28, 2015 at 21:29
  • @eyoung100 Do you mean the options of the program I am passing to? (It is Arch Linux' makepkg.) It has many different options with and without arguments, so they would be very difficult to parse themselves.
    – XZS
    Apr 28, 2015 at 21:36
  • then type man makepkg that should give you a list of options.
    – eyoung100
    Apr 28, 2015 at 21:39
  • Indeed, i could also copy the complete parseopts routine and everything from makepkg's parsing into the wrapper. As this is not very portable (for example, when an option changes), I would consider this as a last resort. This question should be more generally applicable: how can a shell script pass on all options it does not want to parse itself?
    – XZS
    Apr 28, 2015 at 21:52
  • As a matter of programming practice, no option should ever be unknown. As such it will be in a manpage somewhere
    – eyoung100
    Apr 28, 2015 at 21:54

2 Answers 2

1

I don't think that there's a standard method (i.e. besides implementing it all from scratch) that would be available, let alone widely availabe in shells.

Though ksh supports a quite powerful getopts built-in. Based on this (and on your quite demanding requirements) I outlined a possible ksh based solution with the following code fragment:

while getopts ":[-][[n]:][99:verbose][s]" opt
do  case $opt in
    (n) n_arg=$OPTARG ;;
    (99) verbose=1 ;;
    (s) s=1 ;;
    (*) arg_rest+=( "${@:OPTIND-1:1}" ) ;;
    esac
done
shift OPTIND-1

printf "main opt(%s)=%s\n" "-n" "$n_arg"
printf "main opt(%s)=%s\n" "--verbose" "$verbose"
printf "main opt(%s)=%s\n" "-s" "$s"

function delegate
{
    while getopts ":[-][i][98:long]" opt
    do  case $opt in
        (i) int=1 ;;
        (98) long=1 ;;
        esac
    done
    shift OPTIND-1

    printf "func opt(%s)=%s\n" "-i" "$int"
    printf "func opt(%s)=%s\n" "--long" "$long"
}

printf "Delegate: '%s'\n" "${arg_rest[@]}"
delegate "${arg_rest[@]}"

The program first parses all options, sets the internal variables as necessary, and stores the unknown options in an array. Then you see a few printf's to control the settings. Then a function definition where the rest of the options are to be delegated to; the function can as well be replaced by a command. Finally the call of the function (or resp. some other command) with the rest of the arguments.

(For a description of ksh's getopts features call from within a ksh session getopts --man.)

Running that program produces this output:

$ ksh ./getopts_script -s -n 23 --verbose -i --long
main opt(-n)=23
main opt(--verbose)=1
main opt(-s)=1
Delegate: '-i'
Delegate: '--long'
func opt(-i)=1
func opt(--long)=1



For a shell implementation of a getopts function that supports long options see https://github.com/stephane-chazelas/misc-scripts/blob/master/getopts_long.sh

3
  • Very elegant, indeed. Unfortunately, ksh is not as widely installed for this to be sufficiently portable. I am still hoping for at least a bash compatible solution.
    – XZS
    Apr 29, 2015 at 11:25
  • @XZS; To be portable you'd need a POSIX feature, but there isn't any. AFAIKT, bash doesn't support what you want. You'd then need to implement it yourself, I fear.
    – Janis
    Apr 29, 2015 at 13:47
  • @XZS; I just remembered that Stephane has already taken the effort to implement a getopts with support of long options in shell. Since I use the ksh version I have no need for that and so I don't know whether it suits your requirements or not. But it may certainly be better than implementing everthing yourself. You can find it at: github.com/stephane-chazelas/misc-scripts/blob/master/…
    – Janis
    Apr 29, 2015 at 14:17
0

It is tempting to try getopt order known options up front. Unfortunately, it rejects all unknown arguments and stops parsing immediately.

But, as long as long options are not overly complex, bash's getopts can be tricked to do this. Some inspiration was taken from mkaurball. Caveats follow below the source.

#!/bin/bash

while [ $OPTIND -le $# ]
do
  if getopts ":sn:-:" argument
  then
    case $argument in
      s) echo "-$argument" ;;
      n) echo "-$argument with argument $OPTARG" ;;
      \?) pass+=("-$OPTARG") ;;
      -) lastarg=$((OPTIND - 1))
        case "${!lastarg}" in
          --verbose) echo "--verbose" ;;
          --*) pass+=("--$OPTARG") ;;
          *) echo "invalid argument: -"
            exit 1 ;;
        esac ;;
      :) echo "$OPTARG without argument"
        exit 1 ;;
    esac
  else
    pass+=("${!OPTIND}")
    let OPTIND++
  fi
done

echo pass: "${pass[@]}"
  • Unusually, getopts s not used as the condition to while, which would cause the loop to stop as soon as it encounters any positional parameter, that is anything which is not an option nor an argument to one. Instead, these cases are appended to the array collecting parameters to pass on.
  • The parameter specification includes :-, which makes getopts look at long options --long as a short option - with the argument -long. Used without caution, mixing up the dash into the middle of short option compounds (like -s-n) may break this, so it is better to check whether the actual parameter really begins with two dashes. So the approach would fail if the wrapped program wants to see options like -short-with-dashes as an option.
  • Though to the nature of getopts, double-dashes are also swallowed. This may be wanted, but can also be avoided by inserting [ "${!OPTIND}" != '--' ] && before getopts
  • Parsing of long arguments by the wrapper can be somewhat difficult as they have to be handled in the nested case.

You must log in to answer this question.

Not the answer you're looking for? Browse other questions tagged .