81

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?

24

The probably closest syntax to that is:

p_out='/some/path' arg_1='5' my_script
  • 7
    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.) – chepner May 14 '14 at 22:55
  • 11
    I used to love this syntax, but it has a BIG caveat: after the command/function execution, those variables will still be defined in the current scope ! E.g.: x=42 echo $x; echo $x Which means in the next execution of my_script, if p_out is omitted, it will stick to the value passed the last time !! ('/some/path') – Lucas Cimon Sep 1 '15 at 9:13
  • @LucasCimon Can you not unset them after the first execution, reset them before the next execution? – Nikos Alexandris Dec 18 '17 at 15:46
  • 1
    @LucasCimon That is not correct. x=42 echo $x does not even output anything if $x was not defined before. – Hauke Laging Mar 22 '18 at 11:02
  • You're right @HaukeLaging, thanks for correcting that – Lucas Cimon Apr 19 '18 at 9:01
118

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
    ;;
  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.

  • 20
    This should be the accepted answer – Kaushik Ghose May 21 '15 at 13:38
  • 3
    I know this is a bit old, but why only 1 letter for the arguments? – Kevin Apr 28 '17 at 15:54
  • 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. – Milkncookiez Feb 14 '18 at 22:48
  • 2
    @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 – Derek May 24 '18 at 15:30
26

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.

  • 4
    The caveat is not necessary. :) You can have the conditions as follows: -c|--condition) – Milkncookiez May 30 '17 at 14:27
10

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.

  • Do you know why the zparseopts uses just one dash for the arguments whereas in the [] it is 2 dashes? Does not make sense! – Timo Nov 7 '17 at 14:59
  • @Timo, see info zsh zparseopts for details – Stéphane Chazelas Nov 7 '17 at 16:20
9

This is my script and works:

for ARGUMENT in "$@"
do

    KEY=$(echo $ARGUMENT | cut -f1 -d=)
    VALUE=$(echo $ARGUMENT | cut -f2 -d=)   

    case "$KEY" in
            STEPS)              STEPS=${VALUE} ;;
            REPOSITORY_NAME)    REPOSITORY_NAME=${VALUE} ;;     
            *)   
    esac    


done

echo "STEPS = $STEPS"
echo "REPOSITORY_NAME = $REPOSITORY_NAME"

Usage

bash my_scripts.sh  STEPS="ABC" REPOSITORY_NAME="stackexchange"

Console result :

STEPS = ABC
REPOSITORY_NAME = stackexchange

STEPS and REPOSITORY_NAME are ready to use in the script.

It does not matter what order the arguments are in.

Hope this helps.

7

I just came up with this script

while [ $# -gt 0 ]; do

   if [[ $1 == *"--"* ]]; then
        v="${1/--/}"
        declare $v="$2"
   fi

  shift
done

pass it like my_script --p_out /some/path --arg_1 5 and then in the script you can use $arg_1 and $p_out.

  • 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) – hol Sep 24 '18 at 9:45
-1

If a function or an application has more than zero arguments, it always has a last argument.

If you want to read option flag and value pairs, as in: $ ./t.sh -o output -i input -l last

And you want to accept a variable number of option/value pairs,

And do not want a huge "if .. then .. else .. fi" tree,

Then after checking for an argument count of non-zero and even,

Write a while loop with these four eval statements as the body, followed by a case statement using the two values determined in each pass through the loop.

The tricky part of the scripting is demonstrated here:

#!/bin/sh    

# For each pair - this chunk is hard coded for the last pair.
eval TMP="'$'$#"
eval "PICK=$TMP"
eval TMP="'$'$(($#-1))"
eval "OPT=$TMP"

# process as required - usually a case statement on $OPT
echo "$OPT \n $PICK"

# Then decrement the indices (as in third eval statement) 

:<< EoF_test
$ ./t.sh -o output -i input -l last
-l 
last
$ ./t.sh -o output -l last
-l 
last
$ ./t.sh  -l last
-l 
last
EoF_test
-2
mitsos@redhat24$ my_script "a=1;b=mitsos;c=karamitsos"
#!/bin/sh
eval "$1"

you've just injected command line parameters inside script scope !!

  • 3
    This doesn't work with the syntax the OP specified; they want -a 1 -b mitsos -c karamitsos – Michael Mrozek Feb 18 '15 at 14:48

Your Answer

By clicking "Post Your Answer", you acknowledge that you have read our updated terms of service, privacy policy and cookie policy, and that your continued use of the website is subject to these policies.

Not the answer you're looking for? Browse other questions tagged or ask your own question.