如何传递文件中包含的命令行参数并保留该文件的名称?

How can I pass command line arguments contained in a file and retain the name of that file?

我有一个使用命令行参数的脚本,我想实现两个参数传递方案,即:

为此,我将参数 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 的方法)来获取参数文件的名称?

根据我的测试,使用 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)

您的 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')

仅作记录,这是我想出的一个快速而肮脏的解决方案。它基本上包括创建 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:]