Bash 使用 getopts 将字符串存储为数组的脚本

Bash script using getopts to store strings as an array

我正在编写一个 Bash 脚本,它需要将零到多个字符串作为输入,但我不确定如何执行此操作,因为列表前缺少标志。

脚本用法:

script [ list ] [ -t <secs> ] [ -n <count> ]

该列表将零个、一个或多个字符串作为输入。当遇到 space 时,在两个或更多的情况下,它充当字符串之间的分隔符。这些字符串最终将作为 grep 命令的输入,所以我的想法是将它们保存在某种数组中。我目前的 -t-n 工作正常。我曾尝试查找示例,但无法找到与我想做的类似的任何事情。我的另一个问题是如何在设置标志后忽略字符串输入,以便不接受其他字符串。

我当前的脚本:

while getopts :t:n: arg; do
  case ${arg} in
    t)
      seconds=${OPTARG}
      if ! [[ $seconds =~ ^[1-9][0-9]*$ ]] ; then
        exit
      fi
      ;;
    n)
      count=${OPTARG}
      if ! [[ $count =~ ^[1-9][0-9]*$ ]] ; then
        exit
      fi
      ;;
    :)
      echo "[=12=]: Must supply an argument to -$OPTARG" >&2
      exit
      ;;
    ?)
      echo "Invalid option: -${OPTARG}"
      exit
      ;;
  esac
done

编辑: 这是家庭作业,我不确定参数的顺序是否可以更改

编辑2:选项可以任意顺序

请您尝试以下操作:

#!/bin/bash

# parse the arguments before getopts
for i in "$@"; do
    if [[ $i = "-"* ]]; then
        break
    else                # append the arguments to "list" as long as it does not start with "-"
        list+=("")
        shift
    fi
done

while getopts :t:n: arg; do
    : your "case" code here
done

# see if the variables are properly assigned
echo "seconds=$seconds" "count=$count"
echo "list=${list[@]}"

尝试:

#! /bin/bash -p

# Set defaults
count=10
seconds=20

args=( "$@" )
end_idx=$(($#-1))

# Check for '-n' option at the end
if [[ end_idx -gt 0 && ${args[end_idx-1]} == -n ]]; then
    count=${args[end_idx]}
    end_idx=$((end_idx-2))
fi

# Check for '-t' option at the (possibly new) end
if [[ end_idx -gt 0 && ${args[end_idx-1]} == -t ]]; then
    seconds=${args[end_idx]}
    end_idx=$((end_idx-2))
fi

# Take remaining arguments up to the (possibly new) end as the list of strings
strings=( "${args[@]:0:end_idx+1}" )

declare -p strings seconds count
  • 基本思想是处理参数 right-to-left 而不是 left-to-right。
  • 代码假定唯一可接受的参数顺序是问题中给出的顺序。特别是,它假定 -t-n 选项如果存在则必须在末尾,并且如果两者都存在则它们必须按该顺序。
  • 它不会尝试处理与选项结合的选项参数(例如 -t5 而不是 -t 5)。如果需要,这可以很容易地完成。
  • 列表中的字符串可以以 - 开头。

我的较短版本

一些备注:

  • 而不是遍历所有参数**,然后 break 如果参数以 - 开头,我只是使用 while 循环。
  • 来自How do I test if a variable is a number in Bash?,添加了高效的is_int测试函数
  • 由于在 while getopts ... 循环中完成的任何输出 (echo) 都是错误的,因此重定向会执行 STDERR (>&2) 可以寻址到整个循环,而不是在每个 echo 行上重复。
  • ** 注意对所有参数进行循环可以写成 for varname ;do。因为 $@ 代表 默认参数 in "$@" 隐含在 for 循环中。
#!/bin/bash

is_int() { case ${1#[-+]} in
               '' | *[!0-9]* ) echo "Argument '' is not a number"; exit 3;;
           esac ;}

while [[ ${1%%-*} ]];do
    args+=("")
    shift
done

while getopts :t:n: arg; do
    case ${arg} in
        t ) is_int "${OPTARG}" ; seconds=${OPTARG} ;;
        n ) is_int "${OPTARG}" ; count=${OPTARG} ;;
        : ) echo "[=10=]: Must supply an argument to -$OPTARG" ; exit 2;;
        ? ) echo "Invalid option: -${OPTARG}" ; exit 1;;
    esac
done >&2

declare -p seconds count args

标准做法是将选项参数放在任何 non-option 参数或变量参数之前。

getopts 本身将 -- 识别为选项开关定界符的结尾。 如果您需要传递以破折号 - 开头的参数,请使用 -- 分隔符,因此 getopts 停止尝试拦截选项参数。

这是一个实现:

#!/usr/bin/env bash

# SYNOPSIS
#   script [-t<secs>] [-n<count>] [string]...

# Counter of option arguments
declare -i opt_arg_count=0

while getopts :t:n: arg; do
  case ${arg} in
    t)
      seconds=${OPTARG}
      if ! [[ $seconds =~ ^[1-9][0-9]*$ ]] ; then
        exit
      fi
      opt_arg_count+=1
      ;;
    n)
      count=${OPTARG}
      if ! [[ $count =~ ^[1-9][0-9]*$ ]] ; then
        exit 1
      fi
      opt_arg_count+=1
      ;;
    ?)
      printf 'Invalid option: -%s\n' "${OPTARG}" >&2
      exit 1
      ;;
  esac
done

shift "$opt_arg_count" # Skip all option arguments

[[ "" == -- ]] && shift # Skip option argument delimiter if any

# Variable arguments strings are all remaining arguments
strings=("$@")

declare -p count seconds strings

用法示例

字符串不以破折号开头:

$ ./script -t45 -n10  foo bar baz qux
declare -- count="10"
declare -- seconds="45"
declare -a strings=([0]="foo" [1]="bar" [2]="baz" [3]="qux")

对于以短划线开头的字符串,需要 -- 个分隔符:

$ ./script -t45 -n10 -- '-dashed string'  foo bar baz qux
declare -- count="10"
declare -- seconds="45"
declare -a strings=([0]="-dashed string" [1]="foo" [2]="bar" [3]="baz" [4]="qux")