如何在Bash中解析命令行参数?

2024-05-15 01:31:00 发布

您现在位置:Python中文网/ 问答频道 /正文

比如说,我有一个脚本,它通过以下行被调用:

./myscript -vfd ./foo/bar/someFile -o /fizz/someOtherFile

或者这个:

./myscript -v -f -d -o /fizz/someOtherFile ./foo/bar/someFile 

在每种情况下(或两者的组合)$v$f$d都将被设置为true,并且$outFile将等于/fizz/someOtherFile,这是一种公认的解析方式吗


Tags: 脚本truefoo方式bar情况outfilesomefile
3条回答
分开的Bash空间(例如--option argument
cat >/tmp/demo-space-separated.sh <<'EOF'
#!/bin/bash

POSITIONAL=()
while [[ $# -gt 0 ]]; do
  key="$1"

  case $key in
    -e|--extension)
      EXTENSION="$2"
      shift # past argument
      shift # past value
      ;;
    -s|--searchpath)
      SEARCHPATH="$2"
      shift # past argument
      shift # past value
      ;;
    -l|--lib)
      LIBPATH="$2"
      shift # past argument
      shift # past value
      ;;
    --default)
      DEFAULT=YES
      shift # past argument
      ;;
    *)    # unknown option
      POSITIONAL+=("$1") # save it in an array for later
      shift # past argument
      ;;
  esac
done

set -- "${POSITIONAL[@]}" # restore positional parameters

echo "FILE EXTENSION  = ${EXTENSION}"
echo "SEARCH PATH     = ${SEARCHPATH}"
echo "LIBRARY PATH    = ${LIBPATH}"
echo "DEFAULT         = ${DEFAULT}"
echo "Number files in SEARCH PATH with EXTENSION:" $(ls -1 "${SEARCHPATH}"/*."${EXTENSION}" | wc -l)
if [[ -n $1 ]]; then
    echo "Last line of file specified as non-opt/last argument:"
    tail -1 "$1"
fi
EOF

chmod +x /tmp/demo-space-separated.sh

/tmp/demo-space-separated.sh -e conf -s /etc -l /usr/lib /etc/hosts
复制粘贴上面块的输出
FILE EXTENSION  = conf
SEARCH PATH     = /etc
LIBRARY PATH    = /usr/lib
DEFAULT         =
Number files in SEARCH PATH with EXTENSION: 14
Last line of file specified as non-opt/last argument:
#93.184.216.34    example.com
用法
demo-space-separated.sh -e conf -s /etc -l /usr/lib /etc/hosts

Bash等于分离(例如--option=argument

cat >/tmp/demo-equals-separated.sh <<'EOF'
#!/bin/bash

for i in "$@"; do
  case $i in
    -e=*|--extension=*)
      EXTENSION="${i#*=}"
      shift # past argument=value
      ;;
    -s=*|--searchpath=*)
      SEARCHPATH="${i#*=}"
      shift # past argument=value
      ;;
    -l=*|--lib=*)
      LIBPATH="${i#*=}"
      shift # past argument=value
      ;;
    --default)
      DEFAULT=YES
      shift # past argument with no value
      ;;
    *)
      # unknown option
      ;;
  esac
done
echo "FILE EXTENSION  = ${EXTENSION}"
echo "SEARCH PATH     = ${SEARCHPATH}"
echo "LIBRARY PATH    = ${LIBPATH}"
echo "DEFAULT         = ${DEFAULT}"
echo "Number files in SEARCH PATH with EXTENSION:" $(ls -1 "${SEARCHPATH}"/*."${EXTENSION}" | wc -l)
if [[ -n $1 ]]; then
    echo "Last line of file specified as non-opt/last argument:"
    tail -1 $1
fi
EOF

chmod +x /tmp/demo-equals-separated.sh

/tmp/demo-equals-separated.sh -e=conf -s=/etc -l=/usr/lib /etc/hosts
复制粘贴上面块的输出
FILE EXTENSION  = conf
SEARCH PATH     = /etc
LIBRARY PATH    = /usr/lib
DEFAULT         =
Number files in SEARCH PATH with EXTENSION: 14
Last line of file specified as non-opt/last argument:
#93.184.216.34    example.com
用法
demo-equals-separated.sh -e=conf -s=/etc -l=/usr/lib /etc/hosts

为了更好地理解${i#*=},请在this guide中搜索“删除子字符串”。它在功能上相当于`sed 's/[^=]*=//' <<< "$i"`调用一个不必要的子进程或`echo "$i" | sed 's/[^=]*=//'`调用两个不必要的子进程


使用bash和getopt[s]

getopt(1)限制(较旧、相对较新的getopt版本):

  • 无法处理空字符串的参数
  • 无法处理带有嵌入空格的参数

最近的getopt版本没有这些限制。有关更多信息,请参见这些docs


POSIX getopts

此外,POSIX shell和其他shell提供了getopts,但没有这些限制。我已经包括了一个简单的getopts示例

cat >/tmp/demo-getopts.sh <<'EOF'
#!/bin/sh

# A POSIX variable
OPTIND=1         # Reset in case getopts has been used previously in the shell.

# Initialize our own variables:
output_file=""
verbose=0

while getopts "h?vf:" opt; do
  case "$opt" in
    h|\?)
      show_help
      exit 0
      ;;
    v)  verbose=1
      ;;
    f)  output_file=$OPTARG
      ;;
  esac
done

shift $((OPTIND-1))

[ "${1:-}" = "--" ] && shift

echo "verbose=$verbose, output_file='$output_file', Leftovers: $@"
EOF

chmod +x /tmp/demo-getopts.sh

/tmp/demo-getopts.sh -vf /etc/hosts foo bar
复制粘贴上面块的输出
verbose=1, output_file='/etc/hosts', Leftovers: foo bar
用法
demo-getopts.sh -vf /etc/hosts foo bar

{}的优点是:

  1. 它更便于携带,并且可以在其他shell中工作,如dash
  2. 它可以以典型的Unix方式自动处理多个单一选项,如-vf filename

getopts的缺点是它只能处理短选项(-h,而不是--help),而不需要额外的代码

有一个getopts tutorial解释了所有语法和变量的含义。在bash中,还有help getopts,这可能是有用的

无应答显示增强的getopt。而且top-voted answer具有误导性:它要么忽略-⁠vfd样式的短选项(由OP请求),要么忽略位置参数后的选项(也由OP请求);它会忽略解析错误。相反:

  • 使用util linux或以前的GNU glibc的增强版getopt1
  • 它与GNU glibc的C函数一起工作
  • 本页上没有其他解决方案可以完成所有这一切
    • 在参数2中处理空格、引用字符甚至二进制(非增强型getopt无法执行此操作)
    • 它可以在末尾处理选项:script.sh -o outFile file1 file2 -vgetopts不这样做)
    • 允许=样式的长选项:script.sh --outfile=fileOut --infile fileIn(如果自解析,则允许这两个选项都很长)
    • 允许组合短选项,例如-vfd(如果自解析,则实际工作)
    • 允许触摸选项参数,例如-oOutfile-vfdoOutfile
  • 它已经非常古老了3,以至于没有任何GNU系统缺少它(例如,任何Linux都有它)
  • 您可以使用:getopt --test测试它是否存在→ 返回值4
  • 其他{}或shell内置{}的用途有限

以下电话

myscript -vfd ./foo/bar/someFile -o /fizz/someOtherFile
myscript -v -f -d -o/fizz/someOtherFile -- ./foo/bar/someFile
myscript --verbose --force --debug ./foo/bar/someFile -o/fizz/someOtherFile
myscript --output=/fizz/someOtherFile ./foo/bar/someFile -vfd
myscript ./foo/bar/someFile -df -v --output /fizz/someOtherFile

全部返回

verbose: y, force: y, debug: y, in: ./foo/bar/someFile, out: /fizz/someOtherFile

用下面的myscript

#!/bin/bash
# More safety, by turning some bugs into errors.
# Without `errexit` you don’t need ! and can replace
# PIPESTATUS with a simple $?, but I don’t do that.
set -o errexit -o pipefail -o noclobber -o nounset

# -allow a command to fail with !’s side effect on errexit
# -use return value from ${PIPESTATUS[0]}, because ! hosed $?
! getopt --test > /dev/null 
if [[ ${PIPESTATUS[0]} -ne 4 ]]; then
    echo 'I’m sorry, `getopt --test` failed in this environment.'
    exit 1
fi

OPTIONS=dfo:v
LONGOPTS=debug,force,output:,verbose

# -regarding ! and PIPESTATUS see above
# -temporarily store output to be able to check for errors
# -activate quoting/enhanced mode (e.g. by writing out “--options”)
# -pass arguments only via   -- "$@"   to separate them correctly
! PARSED=$(getopt --options=$OPTIONS --longoptions=$LONGOPTS --name "$0" -- "$@")
if [[ ${PIPESTATUS[0]} -ne 0 ]]; then
    # e.g. return value is 1
    #  then getopt has complained about wrong arguments to stdout
    exit 2
fi
# read getopt’s output this way to handle the quoting right:
eval set -- "$PARSED"

d=n f=n v=n outFile=-
# now enjoy the options in order and nicely split until we see --
while true; do
    case "$1" in
        -d|--debug)
            d=y
            shift
            ;;
        -f|--force)
            f=y
            shift
            ;;
        -v|--verbose)
            v=y
            shift
            ;;
        -o|--output)
            outFile="$2"
            shift 2
            ;;
        --)
            shift
            break
            ;;
        *)
            echo "Programming error"
            exit 3
            ;;
    esac
done

# handle non-option arguments
if [[ $# -ne 1 ]]; then
    echo "$0: A single input file is required."
    exit 4
fi

echo "verbose: $v, force: $f, debug: $d, in: $1, out: $outFile"

1增强型getopt可用于大多数“bash系统”,包括Cygwin;在OSX上尝试brew install gnu-getoptsudo port install getopt
POSIX exec()约定没有可靠的方法在命令行参数中传递二进制NULL;这些字节过早地结束了参数
31997年或之前发布的第一个版本(我只追溯到1997年)

deploy.sh

#!/bin/bash

while [[ "$#" -gt 0 ]]; do
    case $1 in
        -t|--target) target="$2"; shift ;;
        -u|--uglify) uglify=1 ;;
        *) echo "Unknown parameter passed: $1"; exit 1 ;;
    esac
    shift
done

echo "Where to deploy: $target"
echo "Should uglify  : $uglify"

用法:

./deploy.sh -t dev -u

# OR:

./deploy.sh --target dev --uglify

相关问题 更多 >

    热门问题