How to get arguments with flags in Bash

2020-02-16 05:29发布

I know that I can easily get positioned parameters like this in bash:

$0 or $1

I want to be able to use flag options like this to specify for what each parameter is used:

mysql -u user -h host

What is the best way to get -u param value and -h param value by flag instead of by position?

标签: bash shell
8条回答
来,给爷笑一个
2楼-- · 2020-02-16 05:38

getopt is your friend.. a simple example:

function f () {
TEMP=`getopt --long -o "u:h:" "$@"`
eval set -- "$TEMP"
while true ; do
    case "$1" in
        -u )
            user=$2
            shift 2
        ;;
        -h )
            host=$2
            shift 2
        ;;
        *)
            break
        ;;
    esac 
done;

echo "user = $user, host = $host"
}

f -u myself -h some_host

There should be various examples in your /usr/bin directory.

查看更多
霸刀☆藐视天下
3楼-- · 2020-02-16 05:40

If you're familiar with Python argparse, and don't mind calling python to parse bash arguments, there is a piece of code I found really helpful and super easy to use called argparse-bash https://github.com/nhoffman/argparse-bash

Example take from their example.sh script:

#!/bin/bash

source $(dirname $0)/argparse.bash || exit 1
argparse "$@" <<EOF || exit 1
parser.add_argument('infile')
parser.add_argument('outfile')
parser.add_argument('-a', '--the-answer', default=42, type=int,
                    help='Pick a number [default %(default)s]')
parser.add_argument('-d', '--do-the-thing', action='store_true',
                    default=False, help='store a boolean [default %(default)s]')
parser.add_argument('-m', '--multiple', nargs='+',
                    help='multiple values allowed')
EOF

echo required infile: "$INFILE"
echo required outfile: "$OUTFILE"
echo the answer: "$THE_ANSWER"
echo -n do the thing?
if [[ $DO_THE_THING ]]; then
    echo " yes, do it"
else
    echo " no, do not do it"
fi
echo -n "arg with multiple values: "
for a in "${MULTIPLE[@]}"; do
    echo -n "[$a] "
done
echo
查看更多
我命由我不由天
4楼-- · 2020-02-16 05:41

Another alternative would be to use something like the below example which would allow you to use long --image or short -i tags and also allow compiled -i="example.jpg" or separate -i example.jpg methods of passing in arguments.

# declaring a couple of associative arrays
declare -A arguments=();  
declare -A variables=();

# declaring an index integer
declare -i index=1;

# any variables you want to use here
# on the left left side is argument label or key (entered at the command line along with it's value) 
# on the right side is the variable name the value of these arguments should be mapped to.
# (the examples above show how these are being passed into this script)
variables["-gu"]="git_user";  
variables["--git-user"]="git_user";  
variables["-gb"]="git_branch";  
variables["--git-branch"]="git_branch";  
variables["-dbr"]="db_fqdn";  
variables["--db-redirect"]="db_fqdn";  
variables["-e"]="environment";  
variables["--environment"]="environment";

# $@ here represents all arguments passed in
for i in "$@"  
do  
  arguments[$index]=$i;
  prev_index="$(expr $index - 1)";

  # this if block does something akin to "where $i contains ="
  # "%=*" here strips out everything from the = to the end of the argument leaving only the label
  if [[ $i == *"="* ]]
    then argument_label=${i%=*} 
    else argument_label=${arguments[$prev_index]}
  fi

  # this if block only evaluates to true if the argument label exists in the variables array
  if [[ -n ${variables[$argument_label]} ]]
    then
        # dynamically creating variables names using declare
        # "#$argument_label=" here strips out the label leaving only the value
        if [[ $i == *"="* ]]
            then declare ${variables[$argument_label]}=${i#$argument_label=} 
            else declare ${variables[$argument_label]}=${arguments[$index]}
        fi
  fi

  index=index+1;
done;

# then you could simply use the variables like so:
echo "$git_user";
查看更多
再贱就再见
5楼-- · 2020-02-16 05:42

This is the idiom I usually use:

while test $# -gt 0; do
  case "$1" in
    -h|--help)
      echo "$package - attempt to capture frames"
      echo " "
      echo "$package [options] application [arguments]"
      echo " "
      echo "options:"
      echo "-h, --help                show brief help"
      echo "-a, --action=ACTION       specify an action to use"
      echo "-o, --output-dir=DIR      specify a directory to store output in"
      exit 0
      ;;
    -a)
      shift
      if test $# -gt 0; then
        export PROCESS=$1
      else
        echo "no process specified"
        exit 1
      fi
      shift
      ;;
    --action*)
      export PROCESS=`echo $1 | sed -e 's/^[^=]*=//g'`
      shift
      ;;
    -o)
      shift
      if test $# -gt 0; then
        export OUTPUT=$1
      else
        echo "no output dir specified"
        exit 1
      fi
      shift
      ;;
    --output-dir*)
      export OUTPUT=`echo $1 | sed -e 's/^[^=]*=//g'`
      shift
      ;;
    *)
      break
      ;;
  esac
done

Key points are:

  • $# is the number of arguments
  • while loop looks at all the arguments supplied, matching on their values inside a case statement
  • shift takes the first one away. You can shift multiple times inside of a case statement to take multiple values.
查看更多
做个烂人
6楼-- · 2020-02-16 05:42

This example uses Bash's built-in getopts command and is from the Google Shell Style Guide:

a_flag=''
b_flag=''
files=''
verbose='false'

print_usage() {
  printf "Usage: ..."
}

while getopts 'abf:v' flag; do
  case "${flag}" in
    a) a_flag='true' ;;
    b) b_flag='true' ;;
    f) files="${OPTARG}" ;;
    v) verbose='true' ;;
    *) print_usage
       exit 1 ;;
  esac
done

Note: If a character is followed by a colon (e.g. f:), that option is expected to have an argument.

Example usage: ./script -v -a -b -f filename

Using getopts has several advantages over the accepted answer:

  • the while condition is a lot more readable and shows what the accepted options are
  • cleaner code; no counting the number of parameters and shifting
  • you can join options (e.g. -a -b -c-abc)

However, a big disadvantage is that it doesn't support long options, only single-character options.

查看更多
叼着烟拽天下
7楼-- · 2020-02-16 05:53

I think this would serve as a simpler example of what you want to achieve. There is no need to use external tools. Bash built in tools can do the job for you.

function DOSOMETHING {

   while test $# -gt 0; do
           case "$1" in
                -first)
                    shift
                    first_argument=$1
                    shift
                    ;;
                -last)
                    shift
                    last_argument=$1
                    shift
                    ;;
                *)
                   echo "$1 is not a recognized flag!"
                   return 1;
                   ;;
          esac
  done  

  echo "First argument : $first_argument";
  echo "Last argument : $last_argument";
 }

This will allow you to use flags so no matter which order you are passing the parameters you will get the proper behavior.

Example :

 DOSOMETHING -last "Adios" -first "Hola"

Output :

 First argument : Hola
 Last argument : Adios

You can add this function to your profile or put it inside of a script.

Thanks!

Edit : Save this as a a file and then execute it as yourfile.sh -last "Adios" -first "Hola"

#!/bin/bash
while test $# -gt 0; do
           case "$1" in
                -first)
                    shift
                    first_argument=$1
                    shift
                    ;;
                -last)
                    shift
                    last_argument=$1
                    shift
                    ;;
                *)
                   echo "$1 is not a recognized flag!"
                   return 1;
                   ;;
          esac
  done  

  echo "First argument : $first_argument";
  echo "Last argument : $last_argument";
查看更多
登录 后发表回答