如何传递文件中包含的命令行参数并保留该文件的名称?
Posted
技术标签:
【中文标题】如何传递文件中包含的命令行参数并保留该文件的名称?【英文标题】:How can I pass command line arguments contained in a file and retain the name of that file? 【发布时间】:2019-11-10 15:47:30 【问题描述】:我有一个使用命令行参数的脚本,我想实现两个参数传递方案,即:
在命令行输入参数。 将参数列表存储在文件中,并通过命令行将该文件的名称传递给程序。为此,我将参数 fromfile_prefix_chars
传递给 ArgumentParser
构造函数。
script.py
from argparse import ArgumentParser
parser = ArgumentParser(fromfile_prefix_chars='@')
parser.add_argument('filename', nargs='?')
parser.add_argument('--foo', nargs='?', default=1)
parser.add_argument('--bar', nargs='?', default=1)
args = parser.parse_args()
print(args)
args.txt
--foo
2
--bar
2
示例用例
$ python script.py --foo 3
Namespace(bar=1, filename=None, foo='3')
$ python script.py @args.txt --foo 3
Namespace(bar='2', filename=None, foo='3')
我原以为args.filename
会保留文件名,但令人惊讶的是,它的值是None
。我知道我可以通过一些处理从sys.argv
获取文件名。是否有更简洁的方法(最好是基于argparse
的方法)来获取参数文件的名称?
【问题讨论】:
@args.txt 怎么了,为什么不是 args.txt,如果你删除了@,文件名存储 args.txt @G.LC 确实如此,但随后参数不会传递给程序。 哇。根本不知道@filename 功能。但是,你能调用 parse 两次吗?一个有@,一个没有?您可以从修改后的 sys.argv 中提供您自己的参数列表。然后从另一个调整其中一个命名空间,这是相当宽松的。 您的args.txt
没有为filename
提供任何值,您的命令行也没有。为什么你期望它的值不同于None
? fromfile_prefix_chars
做了一件非常具体的事情:当它看到 @something
用从文件中解析的参数替换 @something
时,句号。它设计对参数不可见,因为它是在实际命令行解析之前处理的。
来自文档“以任何指定字符开头的参数将被视为文件,并将被它们包含的参数替换。”
【参考方案1】:
为了记录,这是我想出的一个快速而肮脏的解决方案。它基本上包括创建parser
的副本并将其from_file_prefix_chars
属性设置为None
:
import copy
parser_dupe = copy.copy(parser)
parser_dupe.fromfile_prefix_chars = None
args_raw = parser_dupe.parse_args()
if args_raw.filename:
args.filename = args_raw.filename[1:]
【讨论】:
【参考方案2】:您的script.py
,以及文件。我已将文件名添加到文件本身。
args.txt
args.txt
--foo
2
--bar
2
测试:
1803:~/mypy$ python3 stack56811067.py --foo 3
Namespace(bar=1, filename=None, foo='3')
1553:~/mypy$ python3 stack56811067.py @args.txt --foo 3
Namespace(bar='2', filename='args.txt', foo='3')
【讨论】:
整洁而聪明!但是,您的方法的问题是,如果文件名发生更改,您需要记住相应地更改第一行。【参考方案3】:根据我的测试,使用 fromfile_prefix_chars
意味着 argparse 实际上不会将参数传递给您的程序。相反,argparse
看到 @args.txt
,拦截它,从中读取,然后将不带 @args.txt
的参数传递给您的程序。这大概是因为大多数人并不真正需要文件名,只需要其中的参数,所以argparse
省去了创建另一个参数来存储不需要的东西的麻烦。
不幸的是,所有参数都作为局部变量存储在argparse.py
中,因此我们无法访问它们。我想你可以重写一些 argparse 的函数。请记住,这是一个可怕、恶心、骇人的解决方案,我觉得解析 sys.argv
会好 100%。
from argparse import ArgumentParser
# Most of the following is copied from argparse.py
def customReadArgs(self, arg_strings):
# expand arguments referencing files
new_arg_strings = []
for arg_string in arg_strings:
# for regular arguments, just add them back into the list
if not arg_string or arg_string[0] not in self.fromfile_prefix_chars:
new_arg_strings.append(arg_string)
# replace arguments referencing files with the file content
else:
try:
fn = arg_string[1:]
with open(fn) as args_file:
# What was changed: before was []
arg_strings = [fn]
for arg_line in args_file.read().splitlines():
for arg in self.convert_arg_line_to_args(arg_line):
arg_strings.append(arg)
arg_strings = self._read_args_from_files(arg_strings)
new_arg_strings.extend(arg_strings)
except OSError:
err = _sys.exc_info()[1]
self.error(str(err))
# return the modified argument list
return new_arg_strings
ArgumentParser._read_args_from_files = customReadArgs
parser = ArgumentParser(fromfile_prefix_chars='@')
parser.add_argument('filename', nargs='?')
parser.add_argument('--foo', nargs='?', default=1)
parser.add_argument('--bar', nargs='?', default=1)
args = parser.parse_args()
print(args)
【讨论】:
确实,您的解决方案很老套……但效果很好!我终于继承了argparse.ArgumentParser
并覆盖了子类MyArgumentParser
中的_read_args_from_files
方法。以上是关于如何传递文件中包含的命令行参数并保留该文件的名称?的主要内容,如果未能解决你的问题,请参考以下文章