Passing named arguments to shell scripts

324,960

Solution 1

If you don't mind being limited to single-letter argument names i.e. my_script -p '/some/path' -a5, then in bash you could use the built-in getopts, e.g.

#!/bin/bash

while getopts ":a:p:" opt; do
  case $opt in
    a) arg_1="$OPTARG"
    ;;
    p) p_out="$OPTARG"
    ;;
    \?) echo "Invalid option -$OPTARG" >&2
    exit 1
    ;;
  esac

  case $OPTARG in
    -*) echo "Option $opt needs a valid argument"
    exit 1
    ;;
  esac
done

printf "Argument p_out is %s\n" "$p_out"
printf "Argument arg_1 is %s\n" "$arg_1"

Then you can do

$ ./my_script -p '/some/path' -a5
Argument p_out is /some/path
Argument arg_1 is 5

There is a helpful Small getopts tutorial or you can type help getopts at the shell prompt.

Edit: The second case statement in while loop triggers if the -p option has no arguments and is followed by another option, e.g. my_script -p -a5, and exits the program.

Solution 2

This is not a parser for positioned arguments, is for key=value aa=bb arguments;

for ARGUMENT in "$@"
do
   KEY=$(echo $ARGUMENT | cut -f1 -d=)

   KEY_LENGTH=${#KEY}
   VALUE="${ARGUMENT:$KEY_LENGTH+1}"

   export "$KEY"="$VALUE"
done

# use here your expected variables
echo "STEPS = $STEPS"
echo "REPOSITORY_NAME = $REPOSITORY_NAME"
echo "EXTRA_VALUES = $EXTRA_VALUES"

Usage

bash args_shell_parser.sh STEPS="ABC" REPOSITORY_NAME="stackexchange" \
           EXTRA_VALUES="KEY1=VALUE1 KEY2=VALUE2"

Console result:

STEPS = ABC
REPOSITORY_NAME = stackexchange
EXTRA_VALUES = KEY1=VALUE1 KEY2=VALUE2

Features:

  • It does not matter what order the arguments are in
  • Explicit declaration of all variables are not required
  • values could have spaces.
  • It handles complex cases, when the argument value contains "=" sign

Put this snippet at the start of your script.

Prior version of this script:

Solution 3

I stole this from drupal.org, but you could do something like this:

while [ $# -gt 0 ]; do
  case "$1" in
    --p_out=*)
      p_out="${1#*=}"
      ;;
    --arg_1=*)
      arg_1="${1#*=}"
      ;;
    *)
      printf "***************************\n"
      printf "* Error: Invalid argument.*\n"
      printf "***************************\n"
      exit 1
  esac
  shift
done

The only caveat is that you have to use the syntax my_script --p_out=/some/path --arg_1=5.

Solution 4

The probably closest syntax to that is:

p_out='/some/path' arg_1='5' my_script

Solution 5

With zsh, you'd use zparseopts:

#! /bin/zsh -
zmodload zsh/zutil
zparseopts -A ARGUMENTS -p_out: -arg_1:

p_out=$ARGUMENTS[--p_out]
arg1=$ARGUMENTS[--arg_1]

printf 'Argument p_out is "%s"\n' "$p_out"
printf 'Argument arg_1 is "%s"\n' "$arg_1"

But you'd call the script with myscript --p_out foo.

Note that zparseopts doesn't support abbreviating long options or the --p_out=foo syntax like GNU getopt(3) does.

Share:
324,960

Related videos on Youtube

Amelio Vazquez-Reina
Author by

Amelio Vazquez-Reina

I'm passionate about people, technology and research. Some of my favorite quotes: "Far better an approximate answer to the right question than an exact answer to the wrong question" -- J. Tukey, 1962. "Your title makes you a manager, your people make you a leader" -- Donna Dubinsky, quoted in "Trillion Dollar Coach", 2019.

Updated on September 18, 2022

Comments

  • Amelio Vazquez-Reina
    Amelio Vazquez-Reina over 1 year

    Is there any easy way to pass (receive) named parameters to a shell script?

    For example,

    my_script -p_out '/some/path' -arg_1 '5'
    

    And inside my_script.sh receive them as:

    # I believe this notation does not work, but is there anything close to it?
    p_out=$ARGUMENTS['p_out']
    arg1=$ARGUMENTS['arg_1']
    
    printf "The Argument p_out is %s" "$p_out"
    printf "The Argument arg_1 is %s" "$arg1"
    

    Is this possible in Bash or Zsh?

  • chepner
    chepner about 10 years
    Related to this, if the -k option is set in the calling shell, then my_script p_out='/some/path' arg_1='5' has the same effect. (All arguments in the form of an assignment are added to the environment, not just those assignments preceding the command.)
  • Kevin
    Kevin about 7 years
    I know this is a bit old, but why only 1 letter for the arguments?
  • Milkncookiez
    Milkncookiez almost 7 years
    The caveat is not necessary. :) You can have the conditions as follows: -c|--condition)
  • Timo
    Timo over 6 years
    Do you know why the zparseopts uses just one dash for the arguments whereas in the [] it is 2 dashes? Does not make sense!
  • Stéphane Chazelas
    Stéphane Chazelas over 6 years
    @Timo, see info zsh zparseopts for details
  • Milkncookiez
    Milkncookiez over 6 years
    I implemented this (but with i and d). When I run it with my_script -i asd -d asd I get an empty string for the d argument. When I run it my_script -d asd -i asd I get empty string for both arguments.
  • Derek
    Derek almost 6 years
    @Milkncookiez -- I had a similar problem -- I didn't include a ':' after the last argument (a 'w' in my case). Once I added the ':' it started working as expected
  • k6t back
    k6t back over 5 years
    I like this solution in KSH88 I had to v=``echo ${1} | awk '{print substr($1,3)}'`` typeset $v="$2" (Remove one backtick each side)
  • Astronaut
    Astronaut over 4 years
    This is a neat solution, but it seems to take a long time to parse, like 5 to 10 seconds on my PC, is this normal behavior?
  • JRichardsz
    JRichardsz over 4 years
    mmm in my case it is instantaneous.
  • Amelio Vazquez-Reina
    Amelio Vazquez-Reina over 4 years
    @KaushikGhose Just fixed it. I guess better late than never. I wish SO notified you when an answer other than the one you accepted got more accepted votes.
  • toraritte
    toraritte almost 4 years
    Docs to truncate string in variable (and to continue the trend in this thread, this should be the accepted answer).
  • Jerry Green
    Jerry Green over 3 years
    This is neat but assumes you have = delimiter between the key and the value. I.e. you can't pass a value with a space like so: -p 3000 or --port 3000
  • StayFoolish
    StayFoolish about 3 years
    @chepner, Hi, I like your solution, but I don't know where to put the '-k' option as you mentioned, can you show me the code? thanks
  • chepner
    chepner about 3 years
    @StayFoolish One way would be to run set -k, then run my_script p_out='/some/path' arg_1='5'.
  • PaulNUK
    PaulNUK about 3 years
    Unfortunately this breaks if the value has an equals in it like this MYVAR="PROPERTIES_LIST":"\"apim=true&whichvalue=345\""
  • JRichardsz
    JRichardsz about 3 years
    several systems will have problems with a variable like that. An equivalent could be PROPERTIES_LIST=apim:true&whichvalue:345
  • davolfman
    davolfman almost 3 years
    Arguably there's a second answer using getopt (no s) for long args. I'm not experienced to write it though.
  • JRichardsz
    JRichardsz over 2 years
    Thanks for your suggestion #BohdanShulha It had an error. I fixed: gist.github.com/jrichardsz/…