bash - bash如何解析命令行参数?

  显示原文与译文双语对照的内容
0 0

假设我有一个使用此行调用的脚本:


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

或者这个:


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

解析这种方法的方法是什么,在每种情况下,( 或者两者的组合) $v$f$d 都将被设置为真,$outFile 将等于 /fizz/someOtherFile

时间: 原作者:

0 0

首选方法:使用不带 getopt [s ]的直接 bash

我最初回答这个问题的原因是。 这个q/a 引起了很多关注,所以我也应该提供non-magic的方法。 我将扩展回答guneysus修复的sed和包括托拜厄斯建议 kienzler

传递键值对参数的两种最常用方法是:

分离的直接Bash空间

用法 ./myscript.sh -e conf -s/etc -l/usr/lib/etc/hosts


#!/bin/bash
while [[ $#> 1 ]]
do
key="$1"

case $key in
 -e|--extension)
 EXTENSION="$2"
 shift
 ;;
 -s|--searchpath)
 SEARCHPATH="$2"
 shift
 ;;
 -l|--lib)
 LIBPATH="$2"
 shift
 ;;
 --default)
 DEFAULT=YES
 shift
 ;;
 *)
 # unknown option
 ;;
esac
shift
done
echo FILE EXTENSION ="${EXTENSION}"
echo SEARCH PATH ="${SEARCHPATH}"
echo LIBRARY PATH ="${LIBPATH}"
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

直接Bash等于分离


#!/bin/bash

for i in"$@"
do
case $i in
 -e=*|--extension=*)
 EXTENSION="${i#*=}"
 shift
 ;;
 -s=*|--searchpath=*)
 SEARCHPATH="${i#*=}"
 shift
 ;;
 -l=*|--lib=*)
 LIBPATH="${i#*=}"
 shift
 ;;
 --default)
 DEFAULT=YES
 shift
 ;;
 *)
 # unknown option
 ;;
esac
done
echo"FILE EXTENSION = ${EXTENSION}"
echo"SEARCH PATH = ${SEARCHPATH}"
echo"LIBRARY PATH = ${LIBPATH}"
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

为了更好地理解 ${i#*=} 搜索"子字符串删除" 这个指南。 它功能相当于 `sed 's/[^=]*=//' <<<"$i"` 调用一个不需要的子进程或者 `echo"$i" | sed 's/[^=]*=//'` 调用两不必要的子流程。

使用 getopt [s ]

来自:http://mywiki.wooledge.org/BashFAQ/035#getopts web

getopt(1) 从未使用。getopt 不能处理空参数字符串,或参数嵌入空格。 请忘记它曾经存在过。

POSIX外壳( 等等) 提供了 getopts,它可以安全使用。 下面是一个简单的getopts 示例:


#!/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: $@"

# End of file

getopts的优点是:

  1. 它是可移植的,可以在 比如 dash中工作。
  2. 它可以按照预期的Unix方式处理像 -vf filename 这样的事情。

getopts的缺点是它只能处理没有欺骗的短选项( -h,不是 --help ) 。

有一个 getopts教程,它解释了所有的语法和变量。 在bash中,也有 help getopts,它可能是信息丰富的。

原作者:
0 0

getopt()/getopts() 是一个不错的选项。 从这里偷取的:

"getopt"的简单用法在这里mini-script中显示:


#!/bin/bash
echo"Before getopt"
for i
do
 echo $i
done
args=`getopt abc:d $*`
set -- $args
echo"After getopt"
for i
do
 echo"-->$i"
done

我们所说的是,任何 -a,-b,-c或者-d都将被允许,但该-c后面是一个参数(":"表示) 。

如果我们调用这个"g"并尝试它:


bash-2.05a$./g -abc foo
Before getopt
-abc
foo
After getopt
-->-a
-->-b
-->-c
-->foo
-->--

我们从两个参数开始,"getopt"分解选项,并把它们放在自己的参数中。 它还添加了"- - - - - - - - - - - -"。

原作者:
0 0

来自:digitalpeer.com,修改小

用法 myscript.sh -p=my_prefix -s=dirname -l=libname


#!/bin/bash
for i in"$@"
do
case $i in
 -p=*|--prefix=*)
 PREFIX="${i#*=}"

 ;;
 -s=*|--searchpath=*)
 SEARCHPATH="${i#*=}"
 ;;
 -l=*|--lib=*)
 DIR="${i#*=}"
 ;;
 --default)
 DEFAULT=YES
 ;;
 *)
 # unknown option
 ;;
esac
done
echo PREFIX = ${PREFIX}
echo SEARCH PATH = ${SEARCHPATH}
echo DIRS = ${DIR}

为了更好地理解 ${i#*=} 搜索"子字符串删除" 这个指南。 它功能相当于 `sed 's/[^=]*=//' <<<"$i"` 调用一个不需要的子进程或者 `echo"$i" | sed 's/[^=]*=//'` 调用两不必要的子流程。

原作者:
0 0

我大约有 4年的时间了,但是我想放弃。 我使用早期的答案作为起点来整理我的旧的adhoc参数解析。 然后我重新构造了以下模板代码。 它同时处理长和短参数,使用= 或者空格分隔的参数,以及多个短参数组合在一起。 最后它re-inserts任何non-param参数回 $1,$2. 变量。我希望它是有用的。


#!/usr/bin/env bash

# NOTICE: Uncomment if your script depends on bashisms.
#if [ -z"$BASH_VERSION" ]; then bash $0 $@ ; exit $? ; fi

echo"Before"
for i ; do echo - $i ; done


# Code template for parsing command line parameters using only portable shell
# code, while handling both long and short params, handling '-f file' and
# '-f=file' style param data and also capturing non-parameters to be inserted
# back into the shell positional parameters.

while [ -n"$1" ]; do
 # Copy so we can modify it (can't modify $1)
 OPT="$1"
 # Detect argument termination
 if [ x"$OPT" = x"--" ]; then
 shift
 for OPT ; do
 REMAINS="$REMAINS "$OPT""
 done
 break
 fi
 # Parse current opt
 while [ x"$OPT"!= x"-" ] ; do
 case"$OPT" in
 # Handle --flag=value opts like this
 -c=* | --config=* )
 CONFIGFILE="${OPT#*=}"
 shift
 ;;
 # and --flag value opts like this
 -c* | --config )
 CONFIGFILE="$2"
 shift
 ;;
 -f* | --force )
 FORCE=true
 ;;
 -r* | --retry )
 RETRY=true
 ;;
 # Anything unknown is recorded for later
 * )
 REMAINS="$REMAINS "$OPT""
 break
 ;;
 esac
 # Check for multiple short options
 # NOTICE: be sure to update this pattern to match valid options
 NEXTOPT="${OPT#-[cfr]}" # try removing single short opt
 if [ x"$OPT"!= x"$NEXTOPT" ] ; then
 OPT="-$NEXTOPT" # multiple short opts, keep going
 else
 break # long form, exit inner loop
 fi
 done
 # Done with that param. move to next
 shift
done
# Set the non-parameters back into the positional parameters ($1 $2.. )
eval set -- $REMAINS


echo -e"After: n configfile='$CONFIGFILE' n force='$FORCE' n retry='$RETRY' n remains='$REMAINS'"
for i ; do echo - $i ; done

0 0

我认为这个很简单,可以使用:


#!/bin/bash
#

readopt='getopts $opts opt;rc=$?;[ $rc$opt == 0? ]&&exit 1;[ $rc == 0 ]||{ shift $[OPTIND-1];false; }'

opts=vfdo:

# Enumerating options
while eval $readopt
do
 echo OPT:$opt ${OPTARG+OPTARG:$OPTARG}
done

# Enumerating arguments
for arg
do
 echo ARG:$arg
done

调用示例:


./myscript -v -do/fizz/someOtherFile -f./foo/bar/someFile
OPT:v 
OPT:d 
OPT:o OPTARG:/fizz/someOtherFile
OPT:f 
ARG:./foo/bar/someFile

原作者:
0 0

通过 @guneysus,的优秀答案,可以让用户使用他们喜欢的任何语法,比如


command -x=myfilename.ext --another_switch 

yf_terminology_vs@#@#@#vs_yf_terminology


command -x myfilename.ext --another_switch

也就是说,equals可以用空格替换。

这里"模糊解释"可能不符合你的喜好,但是如果你正在制作与其他实用程序( 和我的情况一样,它必须使用 ffmpeg ) 兼容的脚本,那么灵活性很有用。


STD_IN=0

prefix=""
key=""
value=""
for keyValue in"$@"
do
 case"${prefix}${keyValue}" in
 -i=*|--input_filename=*) key="-i"; value="${keyValue#*=}";; 
 -ss=*|--seek_from=*) key="-ss"; value="${keyValue#*=}";;
 -t=*|--play_seconds=*) key="-t"; value="${keyValue#*=}";;
 -|--stdin) key="-"; value=1;;
 *) value=$keyValue;;
 esac
 case $key in
 -i) MOVIE=$(resolveMovie"${value}"); prefix=""; key="";;
 -ss) SEEK_FROM="${value}"; prefix=""; key="";;
 -t) PLAY_SECONDS="${value}"; prefix=""; key="";;
 -) STD_IN=${value}; prefix=""; key="";; 
 *) prefix="${keyValue}=";;
 esac
done

0 0

这就是我如何在函数中避免中断getopts在堆栈中更高的地方运行:


function waitForWeb () {
 local OPTIND=1 OPTARG OPTION
 local host=localhost port=8080 proto=http
 while getopts"h:p:r:" OPTION; do
 case"$OPTION" in
 h)
 host="$OPTARG"
 ;;
 p)
 port="$OPTARG"
 ;;
 r)
 proto="$OPTARG"
 ;;
 esac
 done
...
}

原作者:
0 0

使用模块"实参"来自 bash-modules

例如:


#!/bin/bash
. import.sh log arguments

NAME="world"

parse_arguments"-n|--name)NAME;S" --"$@" || {
 error"Cannot parse command line."
 exit 1
}

info"Hello, $NAME!"

0 0

这也可能是有用的了解,你可以设置一个值,如果有人提供输入,覆盖默认值。

myscript.sh -f 。/serverlist 。txt或者只是。/myscript 。sh ( 并且它接受默认值)


 #!/bin/bash
 # --- set the value, if there is inputs, override the defaults.

 HOME_FOLDER="${HOME}/owned_id_checker"
 SERVER_FILE_LIST="${HOME_FOLDER}/server_list.txt"

 while [[ $#> 1 ]]
 do
 key="$1"
 shift

 case $key in
 -i|--inputlist)
 SERVER_FILE_LIST="$1"
 shift
 ;;
 esac
 done


 echo"SERVER LIST = ${SERVER_FILE_LIST}"

原作者:
0 0

如果你安装了 #1,并且你打算在同一个平台上运行它,getopts工作得很好。 在这方面,OSX和 Linux ( 例如)的行为不同。

这是( 非 getopts ) 解决方案,支持=,non-equals,布尔标志。 例如你可以这种方式运行脚本:


./script --arg1=value1 --arg2 value2 --shouldClean

# parse the arguments.
COUNTER=0
ARGS=("$@")
while [ $COUNTER -lt $# ]
do
 arg=${ARGS[$COUNTER]}
 let COUNTER=COUNTER+1
 nextArg=${ARGS[$COUNTER]}

 if [[ $skipNext -eq 1 ]]; then
 echo"Skipping"
 skipNext=0
 continue
 fi

 argKey=""
 argVal=""
 if [["$arg" =~ ^- ]]; then
 # if the format is: -key=value
 if [["$argKey" =~ = ]]; then
 argVal=$(echo"$argKey" | cut -d'=' -f2)
 argKey=$(echo"$argKey" | cut -d'=' -f1)
 skipNext=0

 # if the format is: -key value
 elif [[!"$nextArg" =~ ^- ]]; then
 argKey="$arg"
 argVal="$nextArg"
 skipNext=1

 # if the format is: -key (a boolean flag)
 elif [["$nextArg" =~ ^- ]] || [[ -z"$nextArg" ]]; then
 argKey="$arg"
 argVal=""
 skipNext=0
 fi
 # if the format has not flag, just a value.
 else
 argKey=""
 argVal="$arg"
 skipNext=0
 fi

 case"$argKey" in 
 --source-scmurl)
 SOURCE_URL="$argVal"
 ;;
 --dest-scmurl)
 DEST_URL="$argVal"
 ;;
 --version-num)
 VERSION_NUM="$argVal"
 ;;
 -c|--clean)
 CLEAN_BEFORE_START="1"
 ;;
 -h|--help|-help|--h)
 showUsage
 exit
 ;;
 esac
done

...