为 bash 脚本提供像命令一样接受标志的选项
Giving a bash script the option to accept flags like a command
我正在编写一个简单的 bash 脚本,我希望它能以任何顺序接受来自命令行的参数。
我浏览了 Web 并在 while 循环中编写了一个带有 case 语句的简单函数。现在,'any order' 部分有效 - 但它只获取我设置的第一个参数。我当然做错了什么,但脚本对我来说是很新的,我一直无法弄清楚 - 非常感谢您的帮助。脚本的flags部分如下:
#Parameters - source,destination,credentials,bandwidth,timeout,port,help
flags () {
while test $# -gt 0; do
case "" in
-s|--source)
shift
if test $# -gt 0; then
export SOURCE=
else
echo "No source directory specified!"
exit 1
fi
;;
-d|--destination)
shift
if test $# -gt 0; then
export DESTINATION=
fi
;;
-c|--credentials)
shift
if test $# -gt 0; then
export CREDENTIALS=
fi
;;
-b|--bandwidth)
shift
if test $# -gt 0; then
export BANDWIDTH=
fi
;;
-t|--timeout)
shift
if test $# -gt 0; then
export TIMEOUT=
fi
;;
-p|--port)
shift
if test $# -gt 0; then
export PORT=
fi
;;
-h|--help)
shift
if test $# -gt 0; then
echo "Help goes here"
fi
;;
-l|--compression-level)
shift
if test $# -gt 0; then
export COMPRESS_LEVEL=
fi
;;
*)
break
;;
esac
done
}
flags "$@"
echo "source is $SOURCE, destination is $DESTINATION, credentials are $CREDENTIALS, bandwidth is $BANDWIDTH, timeout is $TIMEOUT, port is $PORT"
理想情况下,其中一些参数是强制性的,而其他参数是可选的 - 但这不是必须的。
如何修改此脚本以接受任何顺序的任何参数(最好是长格式和短格式)?
如评论中所述,在使用参数(例如凭据)后,您需要进行另一次转换。您应该在 non-existent 参数的错误报告中保持一致。如果你得到 -h
或 --help
,你应该简单地打印帮助并退出;你不应该测试更多的论点。如果需要帮助,你就给予帮助,什么都不做。您还应该将错误回显为标准错误:echo "message" >&2
。您的消息应以 script/program 名称作为前缀:arg0=$(basename "[=16=]" .sh)
和 echo "$arg0: message" >&2
等
将这些更改放在一起,您可能会想出这样的脚本:
#!/bin/sh
arg0=$(basename "[=10=]" .sh)
blnk=$(echo "$arg0" | sed 's/./ /g')
usage_info()
{
echo "Usage: $arg0 [{-s|--source} source] [{-d|--destination} destination] \"
echo " $blnk [{-c|--credentials} credentials] [{-b|--bandwidth} bandwidth] \"
echo " $blnk [{-t|--timeout} timeout] [{-p|--port} port] \"
echo " $blnk [-h|--help] [{-l|--compression-level} level]"
}
usage()
{
exec 1>2 # Send standard output to standard error
usage_info
exit 1
}
error()
{
echo "$arg0: $*" >&2
exit 1
}
help()
{
usage_info
echo
echo " {-s|--source} source -- Set source directory (default: .)"
echo " {-d|--destination} destination -- Set destination"
echo " {-c|--credentials} credentials -- Set credentials"
echo " {-b|--bandwidth} bandwidth -- Set maximum bandwidth"
echo " {-t|--timeout} timeout -- Set timeout (default: 60s)"
echo " {-p|--port} port -- Set port number (default: 1234)"
echo " {-l|--compression-level} level -- Set compression level (default: 1)"
echo " {-h|--help} -- Print this help message and exit"
# echo " {-V|--version} -- Print version information and exit"
exit 0
}
flags()
{
while test $# -gt 0
do
case "" in
(-s|--source)
shift
[ $# = 0 ] && error "No source directory specified"
export SOURCE=""
shift;;
(-d|--destination)
shift
[ $# = 0 ] && error "No destination specified"
export DESTINATION=""
shift;;
(-c|--credentials)
shift
[ $# = 0 ] && error "No credentials specified"
export CREDENTIALS=""
shift;;
(-b|--bandwidth)
shift
[ $# = 0 ] && error "No bandwidth specified"
export BANDWIDTH=""
shift;;
(-t|--timeout)
shift
[ $# = 0 ] && error "No timeout specified"
export TIMEOUT=""
shift;;
(-p|--port)
shift
[ $# = 0 ] && error "No port specified"
export PORT=""
shift;;
(-l|--compression-level)
shift
[ $# = 0 ] && error "No compression level specified"
export COMPRESS_LEVEL=""
shift;;
(-h|--help)
help;;
# (-V|--version)
# version_info;;
(*) usage;;
esac
done
}
flags "$@"
echo "source is $SOURCE"
echo "destination is $DESTINATION"
echo "credentials are $CREDENTIALS"
echo "bandwidth is $BANDWIDTH"
echo "timeout is $TIMEOUT"
echo "port is $PORT"
示例运行(脚本名称:flags53.sh
):
$ sh flags53.sh -c XYZ -d PQR -s 123 -l 4 -t 99 -b 12 -p 56789
source is 123
destination is PQR
credentials are XYZ
bandwidth is 12
timeout is 99
port is 56789
$ sh flags53.sh -c XYZ --destination PQR -s 123 -l 4 --timeout 99 -b 12 --port 56789
source is 123
destination is PQR
credentials are XYZ
bandwidth is 12
timeout is 99
port is 56789
$ sh flags53.sh -c XYZ -h
Usage: flags53 [{-s|--source} source] [{-d|--destination} destination] \
[{-c|--credentials} credentials] [{-b|--bandwidth} bandwidth] \
[{-t|--timeout} timeout] [{-p|--port} port] \
[-h|--help] [{-l|--compression-level} level]
{-s|--source} source -- Set source directory (default: .)
{-d|--destination} destination -- Set destination
{-c|--credentials} credentials -- Set credentials
{-b|--bandwidth} bandwidth -- Set maximum bandwidth
{-t|--timeout} timeout -- Set timeout (default: 60s)
{-p|--port} port -- Set port number (default: 1234)
{-l|--compression-level} level -- Set compression level (default: 1)
{-h|--help} -- Print this help message and exit
$
请注意,请求的帮助可以转到标准输出而不是标准错误,尽管将帮助发送到标准错误并不是严重的犯罪行为。帮助获取有关每个选项含义的用法消息和额外信息。注意默认值(并设置它们)也是一个好主意。可能没有必要导出设置——您可以简单地设置变量而无需显式 export
。在调用 flags
函数之前,或者在 flags
函数开始时,您应该真正将变量设置为默认值。这避免了意外继承导出的值(环境变量)。当然,除非你想接受环境变量,但你的名字可能应该被赋予一个适合脚本名称的系统前缀。大多数程序应该有 --version
或 -V
选项(对 'verbose' 使用 -v
,而不是版本)。如果该命令不接受任何 non-option(文件名)参数,请在解析循环后添加检查并抱怨不需要的参数。如果该命令必须至少有一个 non-option 参数,请检查它。不要在接收到 --
作为参数时报告错误;终止检查循环并将任何剩余参数视为 non-option 个参数。
一个遗留问题——函数中的移位影响函数的参数列表,而不是全局“$@”。你必须弄清楚如何从这个骨架中处理它。我想我可能会创建一个 $OPTIND 的类似物,它报告有多少参数要转移到 non-option 参数。 flags 函数中的代码应该跟踪它移动了多少个参数。
这导致修改后的代码:
#!/bin/sh
arg0=$(basename "[=12=]" .sh)
blnk=$(echo "$arg0" | sed 's/./ /g')
usage_info()
{
echo "Usage: $arg0 [{-s|--source} source] [{-d|--destination} destination] \"
echo " $blnk [{-c|--credentials} credentials] [{-b|--bandwidth} bandwidth] \"
echo " $blnk [{-t|--timeout} timeout] [{-p|--port} port] \"
echo " $blnk [-h|--help] [{-l|--compression-level} level]"
}
usage()
{
exec 1>2 # Send standard output to standard error
usage_info
exit 1
}
error()
{
echo "$arg0: $*" >&2
exit 1
}
help()
{
usage_info
echo
echo " {-s|--source} source -- Set source directory (default: .)"
echo " {-d|--destination} destination -- Set destination"
echo " {-c|--credentials} credentials -- Set credentials"
echo " {-b|--bandwidth} bandwidth -- Set maximum bandwidth"
echo " {-t|--timeout} timeout -- Set timeout (default: 60s)"
echo " {-p|--port} port -- Set port number (default: 1234)"
echo " {-l|--compression-level} level -- Set compression level (default: 1)"
echo " {-h|--help} -- Print this help message and exit"
# echo " {-V|--version} -- Print version information and exit"
exit 0
}
flags()
{
OPTCOUNT=0
while test $# -gt 0
do
case "" in
(-s|--source)
shift
[ $# = 0 ] && error "No source directory specified"
export SOURCE=""
shift
OPTCOUNT=$(($OPTCOUNT + 2));;
(-d|--destination)
shift
[ $# = 0 ] && error "No destination specified"
export DESTINATION=
shift
OPTCOUNT=$(($OPTCOUNT + 2));;
(-c|--credentials)
shift
[ $# = 0 ] && error "No credentials specified"
export CREDENTIALS=
shift
OPTCOUNT=$(($OPTCOUNT + 2));;
(-b|--bandwidth)
shift
[ $# = 0 ] && error "No bandwidth specified"
export BANDWIDTH=
shift
OPTCOUNT=$(($OPTCOUNT + 2));;
(-t|--timeout)
shift
[ $# = 0 ] && error "No timeout specified"
export TIMEOUT=""
shift
OPTCOUNT=$(($OPTCOUNT + 2));;
(-p|--port)
shift
[ $# = 0 ] && error "No port specified"
export PORT=
shift
OPTCOUNT=$(($OPTCOUNT + 2));;
(-l|--compression-level)
shift
[ $# = 0 ] && error "No compression level specified"
export COMPRESS_LEVEL=""
shift
OPTCOUNT=$(($OPTCOUNT + 2));;
(-h|--help)
help;;
# (-V|--version)
# version_info;;
(--)
shift
OPTCOUNT=$(($OPTCOUNT + 1))
break;;
(*) usage;;
esac
done
echo "DEBUG-1: [$*]" >&2
echo "OPTCOUNT=$OPTCOUNT" >&2
}
flags "$@"
echo "DEBUG-2: [$*]" >&2
echo "OPTCOUNT=$OPTCOUNT" >&2
shift $OPTCOUNT
echo "DEBUG-3: [$*]" >&2
echo "source is $SOURCE"
echo "destination is $DESTINATION"
echo "credentials are $CREDENTIALS"
echo "bandwidth is $BANDWIDTH"
echo "timeout is $TIMEOUT"
echo "port is $PORT"
如果您想试验一下,还有其他写算术的方法。不过不要使用 expr
。
我正在编写一个简单的 bash 脚本,我希望它能以任何顺序接受来自命令行的参数。
我浏览了 Web 并在 while 循环中编写了一个带有 case 语句的简单函数。现在,'any order' 部分有效 - 但它只获取我设置的第一个参数。我当然做错了什么,但脚本对我来说是很新的,我一直无法弄清楚 - 非常感谢您的帮助。脚本的flags部分如下:
#Parameters - source,destination,credentials,bandwidth,timeout,port,help
flags () {
while test $# -gt 0; do
case "" in
-s|--source)
shift
if test $# -gt 0; then
export SOURCE=
else
echo "No source directory specified!"
exit 1
fi
;;
-d|--destination)
shift
if test $# -gt 0; then
export DESTINATION=
fi
;;
-c|--credentials)
shift
if test $# -gt 0; then
export CREDENTIALS=
fi
;;
-b|--bandwidth)
shift
if test $# -gt 0; then
export BANDWIDTH=
fi
;;
-t|--timeout)
shift
if test $# -gt 0; then
export TIMEOUT=
fi
;;
-p|--port)
shift
if test $# -gt 0; then
export PORT=
fi
;;
-h|--help)
shift
if test $# -gt 0; then
echo "Help goes here"
fi
;;
-l|--compression-level)
shift
if test $# -gt 0; then
export COMPRESS_LEVEL=
fi
;;
*)
break
;;
esac
done
}
flags "$@"
echo "source is $SOURCE, destination is $DESTINATION, credentials are $CREDENTIALS, bandwidth is $BANDWIDTH, timeout is $TIMEOUT, port is $PORT"
理想情况下,其中一些参数是强制性的,而其他参数是可选的 - 但这不是必须的。
如何修改此脚本以接受任何顺序的任何参数(最好是长格式和短格式)?
如评论中所述,在使用参数(例如凭据)后,您需要进行另一次转换。您应该在 non-existent 参数的错误报告中保持一致。如果你得到 -h
或 --help
,你应该简单地打印帮助并退出;你不应该测试更多的论点。如果需要帮助,你就给予帮助,什么都不做。您还应该将错误回显为标准错误:echo "message" >&2
。您的消息应以 script/program 名称作为前缀:arg0=$(basename "[=16=]" .sh)
和 echo "$arg0: message" >&2
等
将这些更改放在一起,您可能会想出这样的脚本:
#!/bin/sh
arg0=$(basename "[=10=]" .sh)
blnk=$(echo "$arg0" | sed 's/./ /g')
usage_info()
{
echo "Usage: $arg0 [{-s|--source} source] [{-d|--destination} destination] \"
echo " $blnk [{-c|--credentials} credentials] [{-b|--bandwidth} bandwidth] \"
echo " $blnk [{-t|--timeout} timeout] [{-p|--port} port] \"
echo " $blnk [-h|--help] [{-l|--compression-level} level]"
}
usage()
{
exec 1>2 # Send standard output to standard error
usage_info
exit 1
}
error()
{
echo "$arg0: $*" >&2
exit 1
}
help()
{
usage_info
echo
echo " {-s|--source} source -- Set source directory (default: .)"
echo " {-d|--destination} destination -- Set destination"
echo " {-c|--credentials} credentials -- Set credentials"
echo " {-b|--bandwidth} bandwidth -- Set maximum bandwidth"
echo " {-t|--timeout} timeout -- Set timeout (default: 60s)"
echo " {-p|--port} port -- Set port number (default: 1234)"
echo " {-l|--compression-level} level -- Set compression level (default: 1)"
echo " {-h|--help} -- Print this help message and exit"
# echo " {-V|--version} -- Print version information and exit"
exit 0
}
flags()
{
while test $# -gt 0
do
case "" in
(-s|--source)
shift
[ $# = 0 ] && error "No source directory specified"
export SOURCE=""
shift;;
(-d|--destination)
shift
[ $# = 0 ] && error "No destination specified"
export DESTINATION=""
shift;;
(-c|--credentials)
shift
[ $# = 0 ] && error "No credentials specified"
export CREDENTIALS=""
shift;;
(-b|--bandwidth)
shift
[ $# = 0 ] && error "No bandwidth specified"
export BANDWIDTH=""
shift;;
(-t|--timeout)
shift
[ $# = 0 ] && error "No timeout specified"
export TIMEOUT=""
shift;;
(-p|--port)
shift
[ $# = 0 ] && error "No port specified"
export PORT=""
shift;;
(-l|--compression-level)
shift
[ $# = 0 ] && error "No compression level specified"
export COMPRESS_LEVEL=""
shift;;
(-h|--help)
help;;
# (-V|--version)
# version_info;;
(*) usage;;
esac
done
}
flags "$@"
echo "source is $SOURCE"
echo "destination is $DESTINATION"
echo "credentials are $CREDENTIALS"
echo "bandwidth is $BANDWIDTH"
echo "timeout is $TIMEOUT"
echo "port is $PORT"
示例运行(脚本名称:flags53.sh
):
$ sh flags53.sh -c XYZ -d PQR -s 123 -l 4 -t 99 -b 12 -p 56789
source is 123
destination is PQR
credentials are XYZ
bandwidth is 12
timeout is 99
port is 56789
$ sh flags53.sh -c XYZ --destination PQR -s 123 -l 4 --timeout 99 -b 12 --port 56789
source is 123
destination is PQR
credentials are XYZ
bandwidth is 12
timeout is 99
port is 56789
$ sh flags53.sh -c XYZ -h
Usage: flags53 [{-s|--source} source] [{-d|--destination} destination] \
[{-c|--credentials} credentials] [{-b|--bandwidth} bandwidth] \
[{-t|--timeout} timeout] [{-p|--port} port] \
[-h|--help] [{-l|--compression-level} level]
{-s|--source} source -- Set source directory (default: .)
{-d|--destination} destination -- Set destination
{-c|--credentials} credentials -- Set credentials
{-b|--bandwidth} bandwidth -- Set maximum bandwidth
{-t|--timeout} timeout -- Set timeout (default: 60s)
{-p|--port} port -- Set port number (default: 1234)
{-l|--compression-level} level -- Set compression level (default: 1)
{-h|--help} -- Print this help message and exit
$
请注意,请求的帮助可以转到标准输出而不是标准错误,尽管将帮助发送到标准错误并不是严重的犯罪行为。帮助获取有关每个选项含义的用法消息和额外信息。注意默认值(并设置它们)也是一个好主意。可能没有必要导出设置——您可以简单地设置变量而无需显式 export
。在调用 flags
函数之前,或者在 flags
函数开始时,您应该真正将变量设置为默认值。这避免了意外继承导出的值(环境变量)。当然,除非你想接受环境变量,但你的名字可能应该被赋予一个适合脚本名称的系统前缀。大多数程序应该有 --version
或 -V
选项(对 'verbose' 使用 -v
,而不是版本)。如果该命令不接受任何 non-option(文件名)参数,请在解析循环后添加检查并抱怨不需要的参数。如果该命令必须至少有一个 non-option 参数,请检查它。不要在接收到 --
作为参数时报告错误;终止检查循环并将任何剩余参数视为 non-option 个参数。
一个遗留问题——函数中的移位影响函数的参数列表,而不是全局“$@”。你必须弄清楚如何从这个骨架中处理它。我想我可能会创建一个 $OPTIND 的类似物,它报告有多少参数要转移到 non-option 参数。 flags 函数中的代码应该跟踪它移动了多少个参数。
这导致修改后的代码:
#!/bin/sh
arg0=$(basename "[=12=]" .sh)
blnk=$(echo "$arg0" | sed 's/./ /g')
usage_info()
{
echo "Usage: $arg0 [{-s|--source} source] [{-d|--destination} destination] \"
echo " $blnk [{-c|--credentials} credentials] [{-b|--bandwidth} bandwidth] \"
echo " $blnk [{-t|--timeout} timeout] [{-p|--port} port] \"
echo " $blnk [-h|--help] [{-l|--compression-level} level]"
}
usage()
{
exec 1>2 # Send standard output to standard error
usage_info
exit 1
}
error()
{
echo "$arg0: $*" >&2
exit 1
}
help()
{
usage_info
echo
echo " {-s|--source} source -- Set source directory (default: .)"
echo " {-d|--destination} destination -- Set destination"
echo " {-c|--credentials} credentials -- Set credentials"
echo " {-b|--bandwidth} bandwidth -- Set maximum bandwidth"
echo " {-t|--timeout} timeout -- Set timeout (default: 60s)"
echo " {-p|--port} port -- Set port number (default: 1234)"
echo " {-l|--compression-level} level -- Set compression level (default: 1)"
echo " {-h|--help} -- Print this help message and exit"
# echo " {-V|--version} -- Print version information and exit"
exit 0
}
flags()
{
OPTCOUNT=0
while test $# -gt 0
do
case "" in
(-s|--source)
shift
[ $# = 0 ] && error "No source directory specified"
export SOURCE=""
shift
OPTCOUNT=$(($OPTCOUNT + 2));;
(-d|--destination)
shift
[ $# = 0 ] && error "No destination specified"
export DESTINATION=
shift
OPTCOUNT=$(($OPTCOUNT + 2));;
(-c|--credentials)
shift
[ $# = 0 ] && error "No credentials specified"
export CREDENTIALS=
shift
OPTCOUNT=$(($OPTCOUNT + 2));;
(-b|--bandwidth)
shift
[ $# = 0 ] && error "No bandwidth specified"
export BANDWIDTH=
shift
OPTCOUNT=$(($OPTCOUNT + 2));;
(-t|--timeout)
shift
[ $# = 0 ] && error "No timeout specified"
export TIMEOUT=""
shift
OPTCOUNT=$(($OPTCOUNT + 2));;
(-p|--port)
shift
[ $# = 0 ] && error "No port specified"
export PORT=
shift
OPTCOUNT=$(($OPTCOUNT + 2));;
(-l|--compression-level)
shift
[ $# = 0 ] && error "No compression level specified"
export COMPRESS_LEVEL=""
shift
OPTCOUNT=$(($OPTCOUNT + 2));;
(-h|--help)
help;;
# (-V|--version)
# version_info;;
(--)
shift
OPTCOUNT=$(($OPTCOUNT + 1))
break;;
(*) usage;;
esac
done
echo "DEBUG-1: [$*]" >&2
echo "OPTCOUNT=$OPTCOUNT" >&2
}
flags "$@"
echo "DEBUG-2: [$*]" >&2
echo "OPTCOUNT=$OPTCOUNT" >&2
shift $OPTCOUNT
echo "DEBUG-3: [$*]" >&2
echo "source is $SOURCE"
echo "destination is $DESTINATION"
echo "credentials are $CREDENTIALS"
echo "bandwidth is $BANDWIDTH"
echo "timeout is $TIMEOUT"
echo "port is $PORT"
如果您想试验一下,还有其他写算术的方法。不过不要使用 expr
。