草庐IT

python - Python:subprocess.call,stdout到文件,stderr到文件,在屏幕上实时显示stderr

coder 2023-05-24 原文

我有一个命令行工具(实际上是几个),我正在用Python编写包装器。

该工具通常是这样使用的:

 $ path_to_tool -option1 -option2 > file_out

用户将输出写入file_out,并且还可以在工具运行时查看其各种状态消息。

我想复制此行为,同时还将stderr(状态消息)记录到文件中。

我所拥有的是:
from subprocess import call
call(['path_to_tool','-option1','option2'], stdout = file_out, stderr = log_file)

除未将stderr写入屏幕外,此方法都可以正常工作。
我当然可以添加代码以将log_file的内容打印到屏幕上,但是然后用户会在完成所有操作后而不是在执行操作时看到它。

概括地说,所需的行为是:
  • 使用call()或subprocess()
  • 将stdout定向到文件
  • 将stderr定向到文件,同时还将stderr实时写入屏幕,就像
    该工具已直接从命令行调用。

  • 我觉得我要么错过了一些非常简单的事情,要么比我想象的要复杂得多……感谢您的帮助!

    编辑:这只需要在Linux上工作。

    最佳答案

    您可以使用subprocess做到这一点,但这并不简单。如果您查看文档中的Frequently Used Arguments,您会看到可以将PIPE作为stderr参数传递,该参数创建一个新管道,将该管道的一侧传递给子进程,并使另一侧可用作stderr属性。*

    因此,您将需要维护该管道,并写入屏幕和文件。通常,正确地获取详细信息非常棘手。**在您的情况下,只有一个管道,并且您打算同步维护它,所以还不错。

    import subprocess
    proc = subprocess.Popen(['path_to_tool', '-option1', 'option2'],
                            stdout=file_out, stderr=subprocess.PIPE)
    for line in proc.stderr:
        sys.stdout.write(line)
        log_file.write(line)
    proc.wait()
    

    (请注意,使用for line in proc.stderr:会出现一些问题-基本上,如果您正在阅读的内容由于某种原因而没有被行缓冲,即使实际上有一半的数据需要处理,您也可以坐在那里等待换行您可以使用read(128)甚至read(1)一次读取块,以在需要时更平稳地获取数据。如果您需要在到达每个字节后立即获取每个字节,而又负担不起read(1)的费用,则需要将管道置于非阻塞模式并异步读取。)

    但是,如果您使用的是Unix,则使用tee命令为您完成操作可能会更简单。

    对于快速,肮脏的解决方案,您可以使用 shell 通过它进行管道传输。像这样:
    subprocess.call('path_to_tool -option1 option2 2|tee log_file 1>2', shell=True,
                    stdout=file_out)
    

    但是我不想调试 shell 管道。让我们在Python中进行操作,如in the docs所示:
    tool = subprocess.Popen(['path_to_tool', '-option1', 'option2'],
                            stdout=file_out, stderr=subprocess.PIPE)
    tee = subprocess.Popen(['tee', 'log_file'], stdin=tool.stderr)
    tool.stderr.close()
    tee.communicate()
    

    最后,在PyPI上的子进程和/或shell周围有十几个或更高级的包装器-shshellshell_commandshelloutiterpipessargecmd_utilscommandwrapper等。搜索“shell”,“subprocess”, “进程”,“命令行”等,然后找到您喜欢的解决方案,从而使问题变得微不足道。

    如果您需要同时收集stderr和stdout怎么办?

    正如Sven Marnach在评论中建议的那样,简单的方法是将一个重定向到另一个。只需像这样更改Popen参数:
    tool = subprocess.Popen(['path_to_tool', '-option1', 'option2'],
                            stdout=subprocess.PIPE, stderr=subprocess.STDOUT)
    

    然后在所有使用tool.stderr的地方,改用tool.stdout,例如,对于最后一个示例:
    tee = subprocess.Popen(['tee', 'log_file'], stdin=tool.stdout)
    tool.stdout.close()
    tee.communicate()
    

    但这需要权衡。最明显的是,将两个流混合在一起意味着您无法将stdout登录到file_out并将stderr登录到log_file,或者将stdout复制到stdout并将stderr复制到stderr。但这也意味着排序可能是不确定的-如果子进程在向stdout写入任何内容之前总是向stderr写两行,一旦混合了流,您可能最终在这两行之间得到一堆stdout。而且这意味着它们必须共享stdout的缓冲模式,因此,如果您依赖linux/glibc保证stderr被行缓冲(除非子进程显式更改了它)这一事实可能不再成立。

    如果您需要分别处理这两个过程,则将变得更加困难。之前,我说过,只要您只有一个管道并且可以同步维护管道,就可以轻松进行管道维护。如果您有两个管道,那显然不再正确了。想象您正在等待tool.stdout.read(),并且新数据来自tool.stderr。如果数据太多,则可能导致管道溢出和子进程阻塞。但是,即使那没有发生,您也显然无法读取和记录stderr数据,除非有stdout发出一些信息。

    如果使用pipe-through-tee解决方案,则可以避免最初的问题...但是只能通过创建一个同样糟糕的新项目来解决。您有两个tee实例,而当您在一个实例上调用communicate时,另一个实例就坐在那里永远等待。

    因此,无论哪种方式,您都需要某种异步机制。您可以通过线程,select react 堆,gevent之类的东西来执行此操作。

    这是一个快速而肮脏的例子:
    proc = subprocess.Popen(['path_to_tool', '-option1', 'option2'],
                            stdout=subprocess.PIPE, stderr=subprocess.PIPE)
    def tee_pipe(pipe, f1, f2):
        for line in pipe:
            f1.write(line)
            f2.write(line)
    t1 = threading.Thread(target=tee_pipe, args=(proc.stdout, file_out, sys.stdout))
    t2 = threading.Thread(target=tee_pipe, args=(proc.stderr, log_file, sys.stderr))
    t3 = threading.Thread(proc.wait)
    t1.start(); t2.start(); t3.start()
    t1.join(); t2.join(); t3.join()
    

    但是,在某些极端情况下,这些方法将不起作用。 (问题在于SIGCHLD和SIGPIPE/EPIPE/EOF到达的顺序。我认为这不会影响到我们,因为我们没有发送任何输入信息……但是请不要对此深信不疑。 3.3和更高版本的 subprocess.communicate 函数正确获取了所有复杂的细节。但是您可能会发现,使用一种可以在PyPI和ActiveState上找到的异步子流程包装器实现甚至是来自像Twisted之类的成熟的异步框架中的子流程东西,要简单得多。

    *文档并没有真正解释什么是管道,几乎就像他们希望您是Unix C的老手一样...但是某些示例,尤其是Replacing Older Functions with the subprocess Module部分中的示例展示了它们的用法,并且非常简单。

    **困难的部分是正确地对两个或多个管道进行排序。如果您在一根管道上等待,另一根管道可能会溢出并阻塞,从而导致对另一根管道的等待无法完成。解决此问题的唯一简单方法是创建一个线程来服务每个管道。 (在大多数* nix平台上,您可以改用selectpoll react 器,但是要使该跨平台变得异常困难。)模块的The source,尤其是communicate及其助手,显示了如何执行此操作。 (我链接到3.3,因为在较早的版本中,communicate本身会犯一些重要的错误……)因此,如果需要多个管道,则尽可能使用communicate。就您而言,您不能使用communicate,但是幸运的是,您不需要多个管道。

    关于python - Python:subprocess.call,stdout到文件,stderr到文件,在屏幕上实时显示stderr,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/18344932/

    有关python - Python:subprocess.call,stdout到文件,stderr到文件,在屏幕上实时显示stderr的更多相关文章

    1. ruby - 使用 RubyZip 生成 ZIP 文件时设置压缩级别 - 2

      我有一个Ruby程序,它使用rubyzip压缩XML文件的目录树。gem。我的问题是文件开始变得很重,我想提高压缩级别,因为压缩时间不是问题。我在rubyzipdocumentation中找不到一种为创建的ZIP文件指定压缩级别的方法。有人知道如何更改此设置吗?是否有另一个允许指定压缩级别的Ruby库? 最佳答案 这是我通过查看ruby​​zip内部创建的代码。level=Zlib::BEST_COMPRESSIONZip::ZipOutputStream.open(zip_file)do|zip|Dir.glob("**/*")d

    2. ruby - 其他文件中的 Rake 任务 - 2

      我试图在一个项目中使用rake,如果我把所有东西都放到Rakefile中,它会很大并且很难读取/找到东西,所以我试着将每个命名空间放在lib/rake中它自己的文件中,我添加了这个到我的rake文件的顶部:Dir['#{File.dirname(__FILE__)}/lib/rake/*.rake'].map{|f|requiref}它加载文件没问题,但没有任务。我现在只有一个.rake文件作为测试,名为“servers.rake”,它看起来像这样:namespace:serverdotask:testdoputs"test"endend所以当我运行rakeserver:testid时

    3. ruby-on-rails - 在 Rails 中将文件大小字符串转换为等效千字节 - 2

      我的目标是转换表单输入,例如“100兆字节”或“1GB”,并将其转换为我可以存储在数据库中的文件大小(以千字节为单位)。目前,我有这个:defquota_convert@regex=/([0-9]+)(.*)s/@sizes=%w{kilobytemegabytegigabyte}m=self.quota.match(@regex)if@sizes.include?m[2]eval("self.quota=#{m[1]}.#{m[2]}")endend这有效,但前提是输入是倍数(“gigabytes”,而不是“gigabyte”)并且由于使用了eval看起来疯狂不安全。所以,功能正常,

    4. python - 如何使用 Ruby 或 Python 创建一系列高音调和低音调的蜂鸣声? - 2

      关闭。这个问题是opinion-based.它目前不接受答案。想要改进这个问题?更新问题,以便editingthispost可以用事实和引用来回答它.关闭4年前。Improvethisquestion我想在固定时间创建一系列低音和高音调的哔哔声。例如:在150毫秒时发出高音调的蜂鸣声在151毫秒时发出低音调的蜂鸣声200毫秒时发出低音调的蜂鸣声250毫秒的高音调蜂鸣声有没有办法在Ruby或Python中做到这一点?我真的不在乎输出编码是什么(.wav、.mp3、.ogg等等),但我确实想创建一个输出文件。

    5. ruby-on-rails - rails : "missing partial" when calling 'render' in RSpec test - 2

      我正在尝试测试是否存在表单。我是Rails新手。我的new.html.erb_spec.rb文件的内容是:require'spec_helper'describe"messages/new.html.erb"doit"shouldrendertheform"dorender'/messages/new.html.erb'reponse.shouldhave_form_putting_to(@message)with_submit_buttonendendView本身,new.html.erb,有代码:当我运行rspec时,它失败了:1)messages/new.html.erbshou

    6. ruby-on-rails - Rails 3 中的多个路由文件 - 2

      Rails2.3可以选择随时使用RouteSet#add_configuration_file添加更多路由。是否可以在Rails3项目中做同样的事情? 最佳答案 在config/application.rb中:config.paths.config.routes在Rails3.2(也可能是Rails3.1)中,使用:config.paths["config/routes"] 关于ruby-on-rails-Rails3中的多个路由文件,我们在StackOverflow上找到一个类似的问题

    7. ruby - 在 Ruby 中实现 `call_user_func_array` - 2

      我怎样才能完成http://php.net/manual/en/function.call-user-func-array.php在ruby中?所以我可以这样做:classAppdeffoo(a,b)putsa+benddefbarargs=[1,2]App.send(:foo,args)#doesn'tworkApp.send(:foo,args[0],args[1])#doeswork,butdoesnotscaleendend 最佳答案 尝试分解数组App.send(:foo,*args)

    8. ruby - 将差异补丁应用于字符串/文件 - 2

      对于具有离线功能的智能手机应用程序,我正在为Xml文件创建单向文本同步。我希望我的服务器将增量/差异(例如GNU差异补丁)发送到目标设备。这是计划:Time=0Server:hasversion_1ofXmlfile(~800kiB)Client:hasversion_1ofXmlfile(~800kiB)Time=1Server:hasversion_1andversion_2ofXmlfile(each~800kiB)computesdeltaoftheseversions(=patch)(~10kiB)sendspatchtoClient(~10kiBtransferred)Cl

    9. ruby - 如何将脚本文件的末尾读取为数据文件(Perl 或任何其他语言) - 2

      我正在寻找执行以下操作的正确语法(在Perl、Shell或Ruby中):#variabletoaccessthedatalinesappendedasafileEND_OF_SCRIPT_MARKERrawdatastartshereanditcontinues. 最佳答案 Perl用__DATA__做这个:#!/usr/bin/perlusestrict;usewarnings;while(){print;}__DATA__Texttoprintgoeshere 关于ruby-如何将脚

    10. ruby - 使用 Vim Rails,您可以创建一个新的迁移文件并一次性打开它吗? - 2

      使用带有Rails插件的vim,您可以创建一个迁移文件,然后一次性打开该文件吗?textmate也可以这样吗? 最佳答案 你可以使用rails.vim然后做类似的事情::Rgeneratemigratonadd_foo_to_bar插件将打开迁移生成的文件,这正是您想要的。我不能代表textmate。 关于ruby-使用VimRails,您可以创建一个新的迁移文件并一次性打开它吗?,我们在StackOverflow上找到一个类似的问题: https://sta

    随机推荐