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

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 提供任何值,您的命令行也没有。为什么你期望它的值不同于Nonefromfile_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 方法。

以上是关于如何传递文件中包含的命令行参数并保留该文件的名称?的主要内容,如果未能解决你的问题,请参考以下文章

什么是命令行参数?本人是c++菜鸟

cmd如何创建读取数据库文件

git列出本地文件夹中包含的所有repos的所有活动分支

如何根据命令行输入包含py文件?

Numpy: arr[...,0,:] 有效。但是如何存储 slice 命令中包含的数据(...、0、:)?

如何将数据传递到spring批处理ItemProcessor?