dim*_*414 8 python bash command-line-arguments
当我编写shell脚本时,我经常发现自己花费了大部分时间(特别是在调试时)处理参数处理.我编写或维护的许多脚本很容易超过80%的输入解析和清理.我将它与我的Python脚本进行比较,其中argparse为我处理大部分繁琐的工作,并让我轻松构建复杂的选项结构和清理/字符串解析行为.
因此,我希望能够让Python做这个繁重的工作,然后在我的shell脚本中获取这些简化和清理的值,而无需进一步担心用户指定的参数.
举一个具体的例子,我工作的许多shell脚本已被定义为按特定顺序接受它们的参数.你可以调用start_server.sh --server myserver --port 80但start_server.sh --port 80 --server myserver失败You must specify a server to start.- 它使解析代码更简单,但它很难直观.
所以第一遍解决方案可能就像让Python接受参数,对它们进行排序(将它们的参数保持在它们旁边)并返回已排序的参数一样简单.因此shell脚本仍然进行一些解析和清理,但是用户可以输入比shell脚本本身接受的更多任意内容,例如:
# script.sh -o -aR --dir /tmp/test --verbose
#!/bin/bash
args=$(order.py "$@")
# args is set to "-a --dir /tmp/test -o -R --verbose"
# simpler processing now that we can guarantee the order of parameters
Run Code Online (Sandbox Code Playgroud)
这里有一些明显的限制,特别是parse.py无法区分带参数的最终选项和索引参数的开头,但这似乎并不可怕.
所以这是我的问题:1)是否有任何现有的(优选的Python)实用程序通过比bash更强大的功能来启用CLI解析,然后可以在清理后通过我的其余bash脚本访问,或者2)之前是否有人这样做过?是否存在我不知道的问题或陷阱或更好的解决方案?关心分享您的实施?
一个(非常半生不熟)的想法:
#!/bin/bash
# Some sort of simple syntax to describe to Python what arguments to accept
opts='
"a", "append", boolean, help="Append to existing file"
"dir", str, help="Directory to run from"
"o", "overwrite", boolean, help="Overwrite duplicates"
"R", "recurse", boolean, help="Recurse into subdirectories"
"v", "verbose", boolean, help="Print additional information"
'
# Takes in CLI arguments and outputs a sanitized structure (JSON?) or fails
p=$(parse.py "Runs complex_function with nice argument parsing" "$opts" "$@")
if [ $? -ne 0 ]; exit 1; fi # while parse outputs usage to stderr
# Takes the sanitized structure and an argument to get
append=$(arg.py "$p" append)
overwrite=$(arg.py "$p" overwrite)
recurse=$(arg.py "$p" recurse)
verbose=$(arg.py "$p" verbose)
cd $(python arg.py "$p" dir)
complex_function $append $overwrite $recurse $verbose
Run Code Online (Sandbox Code Playgroud)
两行代码,以及对期望参数的简明描述,以及我们对实际脚本行为的看法.也许我疯了,但似乎这样比我觉得我现在要做的更好.
我已经看到解析shell脚本参数和像这个wiki页面这样简单的CLI参数解析,但是这些模式中的许多都感觉笨重且容易出错,我不喜欢每次编写shell脚本时都要重新实现它们,特别是当Python,Java等有很好的参数处理库.
编辑: 我还没有使用它(还),但如果我今天发布这个答案,我可能会推荐https://github.com/docopt/docopts而不是像下面描述的那样的自定义方法。
我编写了一个简短的 Python 脚本,它可以完成我想要的大部分功能。我还不相信它的生产质量(特别是缺乏错误处理),但总比没有好。我欢迎任何反馈。
它利用set内置函数重新分配位置参数,允许脚本的其余部分仍然根据需要处理它们。
bashparse.py
#!/usr/bin/env python
import optparse, sys
from pipes import quote
'''
Uses Python's optparse library to simplify command argument parsing.
Takes in a set of optparse arguments, separated by newlines, followed by command line arguments, as argv[2] and argv[3:]
and outputs a series of bash commands to populate associated variables.
'''
class _ThrowParser(optparse.OptionParser):
def error(self, msg):
"""Overrides optparse's default error handling
and instead raises an exception which will be caught upstream
"""
raise optparse.OptParseError(msg)
def gen_parser(usage, opts_ls):
'''Takes a list of strings which can be used as the parameters to optparse's add_option function.
Returns a parser object able to parse those options
'''
parser = _ThrowParser(usage=usage)
for opts in opts_ls:
if opts:
# yes, I know it's evil, but it's easy
eval('parser.add_option(%s)' % opts)
return parser
def print_bash(opts, args):
'''Takes the result of optparse and outputs commands to update a shell'''
for opt, val in opts.items():
if val:
print('%s=%s' % (opt, quote(val)))
print("set -- %s" % " ".join(quote(a) for a in args))
if __name__ == "__main__":
if len(sys.argv) < 2:
sys.stderr.write("Needs at least a usage string and a set of options to parse")
sys.exit(2)
parser = gen_parser(sys.argv[1], sys.argv[2].split('\n'))
(opts, args) = parser.parse_args(sys.argv[3:])
print_bash(opts.__dict__, args)
Run Code Online (Sandbox Code Playgroud)
用法示例:
#!/bin/bash
usage="[-f FILENAME] [-t|--truncate] [ARGS...]"
opts='
"-f"
"-t", "--truncate",action="store_true"
'
echo "$(./bashparse.py "$usage" "$opts" "$@")"
eval "$(./bashparse.py "$usage" "$opts" "$@")"
echo
echo OUTPUT
echo $f
echo $@
echo $0 $2
Run Code Online (Sandbox Code Playgroud)
如果运行为:./run.sh one -f 'a_filename.txt' "two' still two" three输出以下内容(请注意,内部位置变量仍然正确):
f=a_filename.txt
set -- one 'two'"'"' still two' three
OUTPUT
a_filename.txt
one two' still two three
./run.sh two' still two
Run Code Online (Sandbox Code Playgroud)
忽略调试输出,您将查看大约四行来构造一个强大的参数解析器。想法?