我希望使用shell脚本调用长形式和短形式的命令行选项。

我知道可以使用getopts,但是像在Perl中一样,我不能用shell做同样的事情。

关于如何做到这一点的任何想法,这样我就可以使用如下选项:

./shell.sh --copyfile abc.pl /tmp/
./shell.sh -c abc.pl /tmp/

在上面,这两个命令对我的shell意味着同样的事情,但使用getopts,我还不能实现这些?


当前回答

另一种方式…

# translate long options to short
for arg
do
    delim=""
    case "$arg" in
       --help) args="${args}-h ";;
       --verbose) args="${args}-v ";;
       --config) args="${args}-c ";;
       # pass through anything else
       *) [[ "${arg:0:1}" == "-" ]] || delim="\""
           args="${args}${delim}${arg}${delim} ";;
    esac
done
# reset the translated args
eval set -- $args
# now we can process with getopt
while getopts ":hvc:" opt; do
    case $opt in
        h)  usage ;;
        v)  VERBOSE=true ;;
        c)  source $OPTARG ;;
        \?) usage ;;
        :)
        echo "option -$OPTARG requires an argument"
        usage
        ;;
    esac
done

其他回答

这需要一些时间,但我想要全部:

短选项 长选项 不管有没有论点 非选项参数(没有“-”或“——”的参数) 顺序不重要(script.sh /file -V或script.sh -V /file) 抓住错误用法 在不同的脚本中使用它作为模块,而不需要更改多行代码

最后,我提出了下面的解决方案,它使用getopt来捕获错误并将非选项移动到列表的末尾,然后再使用getopts来解析短选项和长选项。

所有的选项都会自动解析,将它们的长选项名作为变量名(请看例子):

# create string of short options
opt_short=$(printf "%s" "${!options[@]}")

# create string of long options
opt_long="$(printf ",%s" "${options[@]}")"

# catch wrong options and move non-options to the end of the string
args=$(getopt -l "$opt_long" "$opt_short" "$@" 2> >(sed -e 's/^/stderr/g')) || echo -n "Error: " && echo "$args" | grep -oP "(?<=^stderr).*" && exit 1

# create new array of options
mapfile -t args < <(xargs -n1 <<< "$(echo "$args" | sed -E "s/(--[^ ]+) '/\1='/g")" )

# overwrite $@ (options)
set -- "${args[@]}"

# parse options ([h]=help sets the variable "$opt_help" and [V]="" sets the variable "$opt_V")
while getopts "$opt_short-:" opt; do

  echo "$opt:$OPTARG"

  # long option
  if [[ "$opt" == "-" ]]; then

    # extract long option name
    opt="${OPTARG%%=*}"

    # extract long option argument (may be empty)
    OPTARG="${OPTARG#"$opt"}"

    # remove "=" from long option argument
    OPTARG="${OPTARG#=}"

    # set variable name
    opt=opt_$opt

  # short option without argument uses long option name as variable name
  elif [[ "${options[$opt]+x}" ]] && [[ "${options[$opt]}" ]]; then
    opt=opt_${options[$opt]} 

  # short option with argument uses long option name as variable name
  elif [[ "${options[$opt:]+x}" ]] && [[ "${options[$opt:]}" ]]; then
    opt=opt_${options[$opt:]} 

  # short option without long option name uses short option name as variable name
  else
    opt=opt_$opt
  fi

  # remove double colon
  opt="${opt%:}" 

  # options without arguments are set to 1
  [[ ! $OPTARG ]] && OPTARG=1 

  # replace hyphen against underscore
  opt="${opt//-/_}"

  # set variable variables (replaces hyphen against underscore)
  printf -v "$opt" '%s' "$OPTARG" 

done

现在,我只需要定义所需的选项名称和源代码脚本:

# import options module
declare -A options=( [h]=help [f:]=file: [V]=verbose [0]=long_only: [s]="" )
source "/usr/local/bin/inc/options.sh";

# display help text
if [[ $opt_help ]]; then
  echo "help text"
  exit
fi

# output
echo "opt_help:$opt_help"
echo "opt_file:$opt_file"
echo "opt_verbose:$opt_verbose"
echo "opt_long_only:$opt_long_only"
echo "opt_short_only:$opt_s"
echo "opt_path:$1"
echo "opt_mail:$2"

在调用脚本时,可以完全随机地传递所有选项和非选项:

#             $opt_file     $1        $2       $opt_V  $opt_long_only $opt_s
# /demo.sh --file=file.txt /dir info@example.com -V --long_only=yes -s
opt_help:1
opt_file:file.txt
opt_verbose:1
opt_long_only:yes
opt_short_only:1
opt_path=/dir
opt_mail:info@example.com

笔记

在选项数组中,在选项名称后添加:以启用参数。 如果没有给出较长的选项名,则变量名将是$opt_X,其中X是较短的选项名。 如果您希望使用较长的选项名而不定义较短的选项名,则将数组索引设置为一个数字,如上面示例中使用[0]=long_only所做的那样。当然,每个数组下标必须是唯一的。

使用的技术

不使用临时文件捕获stderr 将字符串转换为数组 使用:来解析getopt参数 使用getopts解析长选项名

另一种方式…

# translate long options to short
for arg
do
    delim=""
    case "$arg" in
       --help) args="${args}-h ";;
       --verbose) args="${args}-v ";;
       --config) args="${args}-c ";;
       # pass through anything else
       *) [[ "${arg:0:1}" == "-" ]] || delim="\""
           args="${args}${delim}${arg}${delim} ";;
    esac
done
# reset the translated args
eval set -- $args
# now we can process with getopt
while getopts ":hvc:" opt; do
    case $opt in
        h)  usage ;;
        v)  VERBOSE=true ;;
        c)  source $OPTARG ;;
        \?) usage ;;
        :)
        echo "option -$OPTARG requires an argument"
        usage
        ;;
    esac
done
#!/bin/bash
while getopts "abc:d:" flag
do
  case $flag in
    a) echo "[getopts:$OPTIND]==> -$flag";;
    b) echo "[getopts:$OPTIND]==> -$flag";;
    c) echo "[getopts:$OPTIND]==> -$flag $OPTARG";;
    d) echo "[getopts:$OPTIND]==> -$flag $OPTARG";;
  esac
done

shift $((OPTIND-1))
echo "[otheropts]==> $@"

exit

.

#!/bin/bash
until [ -z "$1" ]; do
  case $1 in
    "--dlong")
      shift
      if [ "${1:1:0}" != "-" ]
      then
        echo "==> dlong $1"
        shift
      fi;;
    *) echo "==> other $1"; shift;;
  esac
done
exit

我是这样解决的:

# A string with command options
options=$@

# An array with all the arguments
arguments=($options)

# Loop index
index=0

for argument in $options
  do
    # Incrementing index
    index=`expr $index + 1`

    # The conditions
    case $argument in
      -a) echo "key $argument value ${arguments[index]}" ;;
      -abc) echo "key $argument value ${arguments[index]}" ;;
    esac
  done

exit;

我是不是太笨了?Getopt和getopts太让人困惑了。

内置的getopts不能做到这一点。有一个外部的getopt(1)程序可以做到这一点,但是您只能在Linux上从util-linux包中获得它。它附带一个示例脚本getop -parse.bash。

还有一个getopts_long被写成shell函数。