如何在Bash中使用标志获取参数

Sta*_*ann 260 bash shell

我知道我可以轻松地在bash中获取这样的定位参数:

$0 要么 $1

我希望能够使用这样的标志选项来指定使用每个参数的内容:

mysql -u user -h host
Run Code Online (Sandbox Code Playgroud)

通过国旗而不是按位置获得-u param价值和-h param价值的最佳方法是什么?

Den*_*nis 390

此示例使用Bash的内置getopts命令,来自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
Run Code Online (Sandbox Code Playgroud)

注意:如果一个字符后面跟一个冒号(例如f:),那么该选项应该有一个参数.

用法示例: ./script -v -a -b -f filename

使用getopts比接受的答案有几个优点:

  • while条件更具可读性,并显示了可接受的选项
  • 清洁代码; 不计算参数的数量和转移
  • 你可以加入选项(例如-a -b -c-abc)

但是,一个很大的缺点是它不支持长选项,只支持单字符选项.

  • 人们想知道为什么这个使用bash内置的答案不是最好的答案 (42认同)
  • 对于后代:在'abf:v'之后的冒号表示-f需要一个额外的参数(在这种情况下是文件名). (12认同)
  • 你可以添加关于冒号的注释吗?在每个字母之后,没有冒号意味着没有arg,一个冒号意味着一个arg,两个冒号意味着可选的arg? (6认同)
  • @nachtigal 这将是一个名为“error”的函数,在使用之前声明。例如,它可以打印传递给它的错误消息,然后以非零状态退出。抱歉造成混乱,我将更改示例以使其更加清晰。 (2认同)
  • @WillBarnwell应该注意,它是在问了问题三年后才添加的,而最重要的答案是在同一天添加的。 (2认同)

Fle*_*exo 267

这是我经常使用的习语:

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
Run Code Online (Sandbox Code Playgroud)

要点是:

  • $# 是参数的数量
  • while循环查看提供的所有参数,在case语句中匹配它们的值
  • 转移带走第一个.您可以在case语句中多次移动以获取多个值.

  • @Lucio超级老评论,但添加它以防其他人访问此页面.*(通配符)用于某人键入`--action = [ACTION]`的情况以及有人键入`--action [ACTION]的情况 (21认同)
  • `--action*`和`--output-dir*`的情况如何? (3认同)
  • @cooper 超级老的评论,但是 * 如何捕获“$1”中的“空格”? (3认同)
  • 为什么`*)`你在那里打破,你不应该退出或忽略错误选项吗?换句话说`-bad -o dir`,`-o dir`部分永远不会被处理. (2认同)
  • 您可以替换 `export PROCESS='echo $1 | sed -e 's/^[^=]*=//g'` 与 `export PROCESS="${1/*"="/}"` (2认同)

Shi*_*zmo 46

getopt是你的朋友..一个简单的例子:

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
Run Code Online (Sandbox Code Playgroud)

/ usr/bin目录中应该有各种示例.

  • 可以在目录`/ usr/share/doc/util-linux/examples`中找到更广泛的示例,至少在Ubuntu机器上. (3认同)

pij*_*olu 13

我提出了一个简单的 TLDR:; 未启动的示例。

创建一个名为greeter.sh的bash脚本

#!/bin/bash

while getopts "n:" arg; do
  case $arg in
    n) Name=$OPTARG;;
  esac
done

echo "Hello $Name!"
Run Code Online (Sandbox Code Playgroud)

然后,您可以-n在执行脚本时传递一个可选参数。

执行脚本如下:

$ bash greeter.sh -n 'Bob'
Run Code Online (Sandbox Code Playgroud)

输出

$ Hello Bob!
Run Code Online (Sandbox Code Playgroud)

笔记

如果您想使用多个参数:

  1. 扩展while getops "n:" arg: do更多参数,例如 while getops "n:o:p:" arg: do
  2. 使用额外的变量赋值扩展 case 开关。比如o) Option=$OPTARGp) Parameter=$OPTARG

要使脚本可执行:

chmod u+x greeter.sh
Run Code Online (Sandbox Code Playgroud)


Mat*_*ios 8

我认为这可以作为您想要实现的更简单的例子.无需使用外部工具.内置工具的Bash可以为您完成工作.

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";
 }
Run Code Online (Sandbox Code Playgroud)

这将允许您使用标志,因此无论您传递参数的顺序,您都将获得正确的行为.

示例:

 DOSOMETHING -last "Adios" -first "Hola"
Run Code Online (Sandbox Code Playgroud)

输出:

 First argument : Hola
 Last argument : Adios
Run Code Online (Sandbox Code Playgroud)

您可以将此功能添加到您的配置文件或将其放在脚本中.

谢谢!

编辑:将其另存为文件,然后执行 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";
Run Code Online (Sandbox Code Playgroud)

  • 对最后一个示例使用 `return 1;` 输出 `can only'return' from a function or sourced script` 在 macOS 上。切换到 `exit 1;` 可以正常工作。 (2认同)

小智 6

我最喜欢罗伯特·麦克马汉的答案,因为它似乎最容易制作成可共享的包含文件以供任何脚本使用。但抛出消息“变量:错误的数组下标”的行似乎有一个缺陷if [[ -n ${variables[$argument_label]} ]]。我没有代表发表评论,我怀疑这是否是正确的“修复”,但将其包含if在内if [[ -n $argument_label ]] ; then可以清理它。

这是我最终得到的代码,如果您知道更好的方法,请在罗伯特的答案中添加评论。

包含文件“flags-declares.sh”

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

# declaring an index integer
declare -i index=1;
Run Code Online (Sandbox Code Playgroud)

包含文件“flags-arguments.sh”

# $@ 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

  if [[ -n $argument_label ]] ; then
    # 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
  fi

  index=index+1;
done;
Run Code Online (Sandbox Code Playgroud)

你的“script.sh”

. bin/includes/flags-declares.sh

# 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";

. bin/includes/flags-arguments.sh

# then you could simply use the variables like so:
echo "$git_user";
echo "$git_branch";
echo "$db_fqdn";
echo "$environment";
Run Code Online (Sandbox Code Playgroud)


Rob*_*han 5

另一个替代方法是使用类似下面的示例,它允许您使用long --image或short -i标记,还允许编译-i ="example.jpg"或单独的-i example.jpg传入参数的方法.

# 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";
Run Code Online (Sandbox Code Playgroud)


Nis*_*gle 5

#!/bin/bash

if getopts "n:" arg; then
  echo "Welcome $OPTARG"
fi
Run Code Online (Sandbox Code Playgroud)

将其保存为sample.sh并尝试运行

sh sample.sh -n John
Run Code Online (Sandbox Code Playgroud)

在您的终端中。