如何将“打印”输出重定向到文件?

Posted

技术标签:

【中文标题】如何将“打印”输出重定向到文件?【英文标题】:How to redirect 'print' output to a file? 【发布时间】:2011-11-01 10:31:34 【问题描述】:

我想使用 Python 将打印重定向到 .txt 文件。我有一个for 循环,它将print 我的每个 .bam 文件的输出,而我想将 all 输出重定向到一个文件。所以我试着说:

f = open('output.txt','w')
sys.stdout = f

在我的脚本的开头。但是我在 .txt 文件中一无所获。 我的脚本是:

#!/usr/bin/python

import os,sys
import subprocess
import glob
from os import path

f = open('output.txt','w')
sys.stdout = f

path= '/home/xxx/nearline/bamfiles'
bamfiles = glob.glob(path + '/*.bam')

for bamfile in bamfiles:
    filename = bamfile.split('/')[-1]
    print 'Filename:', filename
    samtoolsin = subprocess.Popen(["/share/bin/samtools/samtools","view",bamfile],
                                  stdout=subprocess.PIPE,bufsize=1)
    linelist= samtoolsin.stdout.readlines()
    print 'Readlines finished!'

那么问题是什么?除了这个sys.stdout还有什么办法?

我需要我的结果看起来像:

Filename: ERR001268.bam
Readlines finished!
Mean: 233
SD: 10
Interval is: (213, 252)

【问题讨论】:

为什么不使用f.write(data) 是的,但是我每个bam文件都有几个数据(mean,SD,interval...),我该如何把这些数据一个一个放? @Eran Zimmerman:f.write(line) 不会在数据中添加换行符。 你是对的,我的错。但是,总是可以f.write(line+'\n').. 【参考方案1】:

您可以使用 file 参数重定向打印(在 Python 2 中有 >> 运算符代替)。

f = open(filename,'w')
print('whatever', file=f) # Python 3.x
print >>f, 'whatever'     # Python 2.x

在大多数情况下,您最好只正常写入文件。

f.write('whatever')

或者,如果你有几个项目要写,之间有空格,比如print

f.write(' '.join(('whatever', str(var2), 'etc')))

【讨论】:

如果有很多输出语句,这些语句会很快变旧。海报原创想法有效;脚本还有其他问题。 海报的原意是绝对无效的。没有理由在这里重定向标准输出,因为他已经将数据放入变量中。 我认为他的意思是“技术上有效”,因为实际上您可以重定向 sys.stdout,这并不是一个好主意。【参考方案2】:

最明显的方法是打印到文件对象:

with open('out.txt', 'w') as f:
    print('Filename:', filename, file=f)  # Python 3.x
    print >> f, 'Filename:', filename     # Python 2.x

但是,重定向标准输出也适用于我。像这样的一次性脚本可能没问题:

import sys

orig_stdout = sys.stdout
f = open('out.txt', 'w')
sys.stdout = f

for i in range(2):
    print('i = ', i)

sys.stdout = orig_stdout
f.close()

从 Python 3.4 开始,有一个简单的上下文管理器可用于执行此操作in the standard library:

from contextlib import redirect_stdout

with open('out.txt', 'w') as f:
    with redirect_stdout(f):
        print('data')

从外壳本身外部重定向是另一种选择,通常更可取:

./script.py > out.txt

其他问题:

脚本中的第一个文件名是什么?我没有看到它初始化。

我的第一个猜测是 glob 没有找到任何 bamfile,因此 for 循环没有运行。检查文件夹是否存在,并在脚本中打印出 bamfile。

另外,使用os.path.join and os.path.basename 来操作路径和文件名。

【讨论】:

如果您不需要,更改 sys.stdout 是一种不好的做法。 @my 我不相信这样的简单脚本会不好。 +1 哈哈好吧,你可以得到我的支持,因为如果你绝对必须以错误的方式去做,这是正确的做法......但我仍然说你应该用常规文件输出来做。 如何在控制台上重定向和打印输出?重定向stdrr时似乎无法显示Python中的“print()”? 为什么这是一个坏习惯?实际上,这是将积极开发的脚本打印到日志文件的最简单方法。【参考方案3】:

更改 sys.stdout 的值确实会更改所有打印调用的目标。如果您使用其他方式更改打印目的地,您将得到相同的结果。

您的错误在其他地方:

它可能在您为您的问题删除的代码中(文件名从哪里来以调用打开?) 也可能是您没有等待刷新数据:如果您在终端上打印,数据会在每个新行之后刷新,但如果您打印到文件,则仅在标准输出缓冲区已满时才刷新(在大多数系统上为 4096 字节)。

【讨论】:

【参考方案4】:

您可能不喜欢这个答案,但我认为这是正确的答案。除非绝对必要,否则不要更改您的 stdout 目标(也许您正在使用仅输出到 stdout 的库???显然不是这里的情况)。

我认为作为一个好习惯,您应该提前将数据准备为字符串,然后打开文件并立即编写整个内容。这是因为输入/输出操作打开文件句柄的时间越长,该文件发生错误的可能性就越大(文件锁定错误、i/o 错误等)。只需在一次操作中完成所有操作,就不会怀疑何时会出错。

这是一个例子:

out_lines = []
for bamfile in bamfiles:
    filename = bamfile.split('/')[-1]
    out_lines.append('Filename: %s' % filename)
    samtoolsin = subprocess.Popen(["/share/bin/samtools/samtools","view",bamfile],
                                  stdout=subprocess.PIPE,bufsize=1)
    linelist= samtoolsin.stdout.readlines()
    print 'Readlines finished!'
    out_lines.extend(linelist)
    out_lines.append('\n')

然后,当您全部收集完“数据行”每个列表项一行后,您可以将它们与一些 '\n' 字符连接起来,以使整个内容可输出;甚至可以将您的输出语句包装在 with 块中,以提高安全性(即使出现问题也会自动关闭您的输出句柄):

out_string = '\n'.join(out_lines)
out_filename = 'myfile.txt'
with open(out_filename, 'w') as outf:
    outf.write(out_string)
print "YAY MY STDOUT IS UNTAINTED!!!"

但是,如果您有大量数据要写入,您可以一次写入一份。我认为这与您的应用程序无关,但可以选择以下方法:

out_filename = 'myfile.txt'
outf = open(out_filename, 'w')
for bamfile in bamfiles:
    filename = bamfile.split('/')[-1]
    outf.write('Filename: %s' % filename)
    samtoolsin = subprocess.Popen(["/share/bin/samtools/samtools","view",bamfile],
                                  stdout=subprocess.PIPE,bufsize=1)
    mydata = samtoolsin.stdout.read()
    outf.write(mydata)
outf.close()

【讨论】:

加上原版的磁盘缓存性能应该可以接受。但是,如果有大量输出,此解决方案的缺点是内存需求激增。虽然这里可能没什么好担心的,但如果可能的话,通常最好避免这种情况。与使用 xrange (py3 range) 而不是 range 等的想法相同。 @Gringo:他没有具体说明这个要求。我很少将足够的数据写入与此相关的文件。这与 xrange 不同,因为 xrange 不处理文件 i/o。磁盘缓存可能会有所帮助,但为大量代码保持打开文件句柄仍然是一种不好的做法。 您的评论自相矛盾。老实说,这两种方法的性能方面与非大量数据无关。 xrange 当然是类似的,它一次只在一块上工作,而不是在内存中一次全部工作。不过,也许生成器 vs 列表是一个更好的例子。 @Gringo:我看不出我的评论是如何自相矛盾的。也许性能方面无关紧要,长时间保持文件句柄打开总是会增加出错的风险。在编程中,文件 i/o 本质上总是比在自己的程序中做某事更有风险,因为这意味着您必须通过操作系统接触并弄乱文件锁。打开文件的时间越短越好,这仅仅是因为您不能通过代码控制文件系统。 xrange 不同,因为它与文件 i/o 无关,仅供参考,我也很少使用 xrange;欢呼 @Gringo:我很欣赏你的批评,也很享受这场激烈的辩论。尽管我们在某些方面存在分歧,但我仍然尊重您的观点,因为很明显您有充分的理由表明自己的立场。感谢您合理地结束它,并祝您度过一个愉快的夜晚。 :P【参考方案5】:

最简单的解决方案不是通过 python;它通过外壳。从文件的第一行 (#!/usr/bin/python) 我猜你是在 UNIX 系统上。只需像往常一样使用print 语句,并且根本不要在脚本中打开文件。当你去运行文件时,而不是

./script.py

要运行文件,请使用

./script.py > <filename>

您将&lt;filename&gt; 替换为您希望输出进入的文件的名称。 &gt; 标记告诉(大多数)shell 将标准输出设置为以下标记描述的文件。

这里需要提到的一件重要的事情是,“script.py”需要成为可执行文件,./script.py 才能运行。

所以在运行./script.py之前,执行这个命令

chmod a+x script.py (使脚本对所有用户都可执行)

【讨论】:

./script.py > 2>&1 您还需要捕获 stderr。 2>&1 会这样做 @rtaft 为什么?该问题特别希望将print 的输出通过管道传输到文件中。期望标准输出(堆栈跟踪等)仍然打印到终端是合理的。 他说不行,我的也不行。后来我发现我正在开发的这个应用程序被配置为将所有内容都定向到 stderr...idk 为什么。【参考方案6】:

这很好用:

import sys
sys.stdout=open("test.txt","w")
print ("hello")
sys.stdout.close()

现在 hello 将被写入 test.txt 文件。确保使用close 关闭stdout,否则内容将不会保存在文件中

【讨论】:

但是即使我们执行 sys.stdout.close() ,如果您在 python shell 中输入任何内容,它也会显示错误为 ValueError: I/O operation on closed file. imgur.com/a/xby9P。处理此问题的最佳方法是遵循@Gringo Suave 发布的内容【参考方案7】:

Python 2 或 Python 3 API 参考:

print(*objects, sep=' ', end='\n', file=sys.stdout, flush=False)

file 参数必须是具有write(string) 方法的对象;如果它不存在或None,将使用sys.stdout。由于打印的参数被转换为文本字符串,print() 不能与二进制模式文件对象一起使用。对于这些,请改用file.write(...)

由于file object 通常包含write() 方法,您只需将file object 传递给它的参数即可。

写入/覆盖到文件

with open('file.txt', 'w') as f:
    print('hello world', file=f)

写入/追加到文件

with open('file.txt', 'a') as f:
    print('hello world', file=f)

【讨论】:

我只是很困惑为什么那些早期的答案是猴子补丁全球sys.stdout :( 说,你有一些遗留代码(2.7),文件中到处都是打印语句(而不是日志)。现在,您希望将这些登录到文件中以进行调试/调查。在这种情况下,修改全局标准输出优于修改每个打印语句。只是我的意见。【参考方案8】:

为循环扩展打印功能的东西

x = 0
while x <=5:
    x = x + 1
    with open('outputEis.txt', 'a') as f:
        print(x, file=f)
    f.close()

【讨论】:

不用while,用with时不用关闭文件【参考方案9】:

不要使用print,使用logging

您可以更改sys.stdout 以指向一个文件,但这是处理此问题的一种非常笨拙且不灵活的方法。不要使用print,而是使用logging 模块。

使用logging,您可以像对stdout 一样进行打印,也可以将输出写入文件。例如,您甚至可以使用不同的消息级别(criticalerrorwarninginfodebug)仅将主要问题打印到控制台,但仍将次要代码操作记录到文件。

一个简单的例子

导入logging,获取logger,并设置处理级别:

import logging
logger = logging.getLogger()
logger.setLevel(logging.DEBUG) # process everything, even if everything isn't printed

如果你想打印到标准输出:

ch = logging.StreamHandler()
ch.setLevel(logging.INFO) # or any other level
logger.addHandler(ch)

如果您还想写入文件(如果您只想写入文件,请跳过最后一部分):

fh = logging.FileHandler('myLog.log')
fh.setLevel(logging.DEBUG) # or any level you want
logger.addHandler(fh)

然后,无论您在哪里使用print,请使用logger 方法之一:

# print(foo)
logger.debug(foo)

# print('finishing processing')
logger.info('finishing processing')

# print('Something may be wrong')
logger.warning('Something may be wrong')

# print('Something is going really bad')
logger.error('Something is going really bad')

要详细了解如何使用更高级的logging 功能,请阅读优秀的logging tutorial in the Python docs。

【讨论】:

嗨,我想使用此日志记录将控制台数据写入日志文件,时间与获取数据的时间相同。但我无法正确理解日志记录功能或库。你能帮我解决这个问题吗 @haris 通读Python docs' logging tutorial 并查看 Stack Overflow 上其他问题中的示例(有很多)。如果您仍然无法使其正常工作,请提出一个新问题。【参考方案10】:

如果您使用的是 Linux,我建议您使用 tee 命令。实现是这样的:

python python_file.py | tee any_file_name.txt

如果您不想更改代码中的任何内容,我认为这可能是最好的解决方案。您也可以实现记录器,但您需要对代码进行一些更改。

【讨论】:

太棒了;正在寻找它【参考方案11】:

如果重定向stdout 可以解决您的问题,Gringo Suave's answer 是一个很好的演示。

为了使它更容易,我使用contextmanagers 制作了一个版本,使用with 语句来获得简洁的通用调用语法:

from contextlib import contextmanager
import sys

@contextmanager
def redirected_stdout(outstream):
    orig_stdout = sys.stdout
    try:
        sys.stdout = outstream
        yield
    finally:
        sys.stdout = orig_stdout

要使用它,您只需执行以下操作(源自 Suave 的示例):

with open('out.txt', 'w') as outfile:
    with redirected_stdout(outfile):
        for i in range(2):
            print('i =', i)

当模块以您不喜欢的方式使用它时,它对于选择性地重定向 print 很有用。唯一的缺点(这是许多情况下的交易破坏者)是,如果想要多个线程具有不同的stdout 值,它就不起作用,但这需要一种更好、更通用的方法:间接模块访问。你可以在这个问题的其他答案中看到它的实现。

【讨论】:

好主意。这个上下文管理器存在于 contextlib 模块中,但是文档说从 Python 3.4 开始。 @GringoSuave 是的,contextmanager 函数应该被导入用作装饰器;因此我将from contextlib import contextmanager 放在代码块的顶部。【参考方案12】:

在python 3中,你可以重新分配print

#!/usr/bin/python3

def other_fn():
    #This will use the print function that's active when the function is called
    print("Printing from function")

file_name = "test.txt"
with open(file_name, "w+") as f_out:
    py_print = print #Need to use this to restore builtin print later, and to not induce recursion
   
    print = lambda out_str : py_print(out_str, file=f_out)
    
    #If you'd like, for completeness, you can include args+kwargs
    print = lambda *args, **kwargs : py_print(*args, file=f_out, **kwargs)
    
    print("Writing to %s" %(file_name))

    other_fn()  #Writes to file

    #Must restore builtin print, or you'll get 'I/O operation on closed file'
    #If you attempt to print after this block
    print = py_print

print("Printing to stdout")
other_fn() #Writes to console/stdout

请注意,other_fn 中的 print 仅切换输出,因为 print 正在全局范围内重新分配。如果我们在函数中分配 printother_fn 中的 print 通常不会受到影响。如果我们想影响所有 print 调用,我们可以使用 global 关键字:

import builtins

def other_fn():
    #This will use the print function that's active when the function is called
    print("Printing from function")

def main():
    global print #Without this, other_fn will use builtins.print
    file_name = "test.txt"
    with open(file_name, "w+") as f_out:

        print = lambda *args, **kwargs : builtins.print(*args, file=f_out, **kwargs)

        print("Writing to %s" %(file_name))

        other_fn()  #Writes to file

        #Must restore builtin print, or you'll get 'I/O operation on closed file'
        #If you attempt to print after this block
        print = builtins.print

    print("Printing to stdout")
    other_fn() #Writes to console/stdout

就个人而言,我更愿意通过将输出文件描述符烘焙到一个新函数中来回避使用print 函数的要求:

file_name = "myoutput.txt"
with open(file_name, "w+") as outfile:
    fprint = lambda pstring : print(pstring, file=outfile)
    print("Writing to stdout")
    fprint("Writing to %s" % (file_name))

【讨论】:

【参考方案13】:

我可以使用以下方法破解它。它将使用此打印功能而不是内置打印功能并将内容保存到文件中。

from __future__ import print_function
import builtins as __builtin__

log = open("log.txt", "a")

def print(*args):
    newLine = ""
    for item in args:
        newLine = newLine + str(item) + " "
    newLine = (
        newLine
        + """
"""
    )
    log.write(newLine)
    log.flush()
    __builtin__.print(*args)
    return

【讨论】:

哈哈这招不错,你是不是一直用这种招? :)

以上是关于如何将“打印”输出重定向到文件?的主要内容,如果未能解决你的问题,请参考以下文章

kermit - 如何将输出重定向到文件中

如果提供了任何命令行参数,则将打印语句输出重定向到文件

如何将控制台输出重定向到文本文件

将程序输出重定向到文件和shell [重复]

将 gulp lint 标准输出重定向到文件

Unix shell输入输出重定向