【问题标题】:Python subprocess - saving output in a new filePython子进程 - 将输出保存在新文件中
【发布时间】:2017-12-20 10:36:18
【问题描述】:

我使用以下命令重新格式化文件并创建一个新文件:

sed -e '1s/^/[/' -e 's/$/,/' -e '$s/,$/]/' toto> toto.json

它在命令行上运行良好。

我尝试通过 python 脚本使用它,但它不会创建新文件。

我试试:

subprocess.call(["sed", "-e","1s/^/[/","-e", "s/$/,/","-e","$s/,$/]/ ",sys.argv[1], " > ",sys.argv[2]]) 

问题是:它在标准输出中给了我输出并引发错误:

sed: can't read >: No such file or directory
Traceback (most recent call last):
File "test.py", line 14, in <module>
subprocess.call(["sed", "-e","1s/^/[/","-e", "s/$/,/","-e","$s/,$/]/", 
sys.argv[1], ">",sys.argv[2])
File "C:\Users\Anaconda3\lib\subprocess.py", line 291, in 
check_call raise CalledProcessError(retcode, cmd)
subprocess.CalledProcessError: Command '['sed', '-e', '1s/^/[/', '-e', 
's/$/,/', '-e', '$s/,$/]/', 'toto.txt, '>', 'toto.json']' returned non-zero 
exit status 2.

我阅读了子进程的其他问题并尝试使用选项 shell=True 的其他命令,但它也不起作用。 我使用 python 3.6

有关信息,该命令在第一行和最后一行添加一个括号,并在除最后一行之外的每一行的末尾添加一个逗号。所以,它确实:

from
a
b
c

到:

[a,
b,
c]

【问题讨论】:

  • 不要在传递给subprocess.call()(即" &gt; ")的参数中添加空格,subprocess 模块会为您执行此操作。此外,根据sed 处理STDOUT 转发的方式,您可能需要添加shell=True 以通过您的shell 调用命令。

标签: python subprocess


【解决方案1】:

在 Linux 和其他 Unix 系统上,重定向字符不是命令的一部分,而是由 shell 解释,因此将其作为 参数 传递给子进程是没有意义的。

希望subprocess.call 允许stdout 参数成为文件对象。所以你应该这样做:

subprocess.call(["sed", "-e","1s/^/[/","-e", "s/$/,/","-e","$s/,$/]/ ",sys.argv[1]],
    stdout=open(sys.argv[2], "w"))

【讨论】:

    【解决方案2】:

    我有一种预感,Python 可以比 sed 快得多,但直到现在我才有时间检查,所以...根据您对 Arount 回答的评论:

    我的真实文件其实很大,命令行比python脚本快得多

    这不一定是真的,事实上,在你的情况下,我怀疑 Python 可以比sed 快很多、很多倍,因为使用 Python,你不仅限于迭代你的通过行缓冲区文件,也不需要一个完整的正则表达式引擎来获取行分隔符。

    我不确定你的文件有多大,但我生成了我的测试示例:

    with open("example.txt", "w") as f:
        for i in range(10**8):  # I would consider 100M lines as "big" enough for testing
            print(i, file=f)
    

    这实际上创建了一个 100M 行长 (888.9MB) 的文件,每行都有不同的数字。

    现在,单独计时您的 sed 命令,以最高优先级 (chrt -f 99) 运行会导致:

    [zwer@testbed ~]$ sudo chrt -f 99 /usr/bin/time --verbose \
    > sed -e '1s/^/[/' -e 's/$/,/' -e '$s/,$/]/' example.txt > output.txt
        Command being timed: "sed -e 1s/^/[/ -e s/$/,/ -e $s/,$/]/ example.txt"
        User time (seconds): 56.89
        System time (seconds): 1.74
        Percent of CPU this job got: 98%
        Elapsed (wall clock) time (h:mm:ss or m:ss): 0:59.28
        Average shared text size (kbytes): 0
        Average unshared data size (kbytes): 0
        Average stack size (kbytes): 0
        Average total size (kbytes): 0
        Maximum resident set size (kbytes): 1044
        Average resident set size (kbytes): 0
        Major (requiring I/O) page faults: 1
        Minor (reclaiming a frame) page faults: 313
        Voluntary context switches: 7
        Involuntary context switches: 29
        Swaps: 0
        File system inputs: 1140560
        File system outputs: 1931424
        Socket messages sent: 0
        Socket messages received: 0
        Signals delivered: 0
        Page size (bytes): 4096
        Exit status: 0
    

    如果您实际上是从 Python 调用它,结果会更糟,因为它还会带来 subprocess 和 STDOUT 重定向开销。

    但是,如果我们让 Python 代替 sed 来完成所有工作:

    import sys
    
    CHUNK_SIZE = 1024 * 64  # 64k, tune this to the FS block size / platform for best performance
    
    with open(sys.argv[2], "w") as f_out:  # open the file from second argument for writing
        f_out.write("[")  # start the JSON array
        with open(sys.argv[1], "r") as f_in:  # open the file from the first argument for reading
            chunk = None
            last_chunk = ''  # keep a track of the last chunk so we can remove the trailing comma
            while True:
                chunk = f_in.read(CHUNK_SIZE)  # read the next chunk
                if chunk:
                    f_out.write(last_chunk)  # write out the last chunk
                    last_chunk = chunk.replace("\n", ",\n")  # process the new chunk
                else:  # EOF
                    break
        last_chunk = last_chunk.rstrip()  # clear out the trailing whitespace
        if last_chunk[-1] == ",":  # clear out the trailing comma
            last_chunk = last_chunk[:-1]
        f_out.write(last_chunk)  # write the last chunk
        f_out.write("]")  # end the JSON array
    

    不接触外壳会导致:

    [zwer@testbed ~]$ sudo chrt -f 99 /usr/bin/time --verbose \
    > python process_file.py example.txt output.txt
        Command being timed: "python process_file.py example.txt output.txt"
        User time (seconds): 1.75
        System time (seconds): 0.72
        Percent of CPU this job got: 93%
        Elapsed (wall clock) time (h:mm:ss or m:ss): 0:02.65
        Average shared text size (kbytes): 0
        Average unshared data size (kbytes): 0
        Average stack size (kbytes): 0
        Average total size (kbytes): 0
        Maximum resident set size (kbytes): 4716
        Average resident set size (kbytes): 0
        Major (requiring I/O) page faults: 3
        Minor (reclaiming a frame) page faults: 14835
        Voluntary context switches: 16
        Involuntary context switches: 0
        Swaps: 0
        File system inputs: 3120
        File system outputs: 1931424
        Socket messages sent: 0
        Socket messages received: 0
        Signals delivered: 0
        Page size (bytes): 4096
        Exit status: 0
    

    考虑到利用率,瓶颈实际上是 I/O,留给它自己的设备(或者使用非常快的存储而不是像我的测试平台上的虚拟化 HDD 工作)Python 可以做得更快。

    因此,sed 花费了 32.5 倍 来完成与 Python 相同的任务。即使您要稍微优化您的sed,Python 仍然会运行得更快,因为sed 仅限于行缓冲区,因此会在输入 I/O 上浪费大量时间(比较上述基准中的数字) 并且没有(简单的)方法。

    结论:对于这个特定的任务,Pythonsed

    【讨论】:

    • 阅读后,您的基准。我收回我说的话。 Python 要快得多。这里不需要子流程。我会牢记这一点;)
    • @youpi - 在我遇到的大多数人寻求外部工具的情况下,原生 Python 解决方案更快。大多数时候 - 快很多倍。这就是为什么每当我发现自己在输入import subprocess 时,我都会停下来思考我是否只是懒得输入 Python 解决方案,还是它实际上会提高我的性能 - 在 90% 的情况下是前者;)
    【解决方案3】:

    不要那样做。如果可以避免,请不要使用任何操作系统调用。

    如果您使用的是 Python,只需执行 pythonic Python 脚本即可。

    类似:

    input_filename = 'toto'
    output_filename = 'toto.json'
    
    with open(input_filename, 'r') as inputf:
        lines = ['{},\n'.format(line.rstrip()) for line in inputf]
        lines = ['['] + lines + [']']
    
        with open(output_filename, 'w') as outputf:
            outputf.writelines(lines)
    

    它基本上和你的命令行一样。

    相信这段代码有点脏,仅用于示例目的。我建议你自己做,避免像我一样使用单行。

    【讨论】:

    • 感谢您的回复,但我的真实文件实际上相当大,命令行比 python 脚本快得多。
    • 为什么要导入re 模块?在这种情况下你为什么要打电话给inputf.readlines()?它只会吞噬内存而没有任何好处。另外,为什么不将数据直接流式传输到输出文件,而不是将所有内容加载到工作内存中呢?哦,这么多问题...
    • @zwer 对,对,再对。我的观点是“你可以而且应该以 Python 的方式来做,而不是使用子进程”。我并没有试图生成一段干净高效的代码,而只是证明你不需要在这里调用 os。
    • @Arount - 我同意你的观点(检查我上面的基准),我刚刚发现执行,嗯,有问题;)
    猜你喜欢
    • 2016-03-05
    • 1970-01-01
    • 2019-04-18
    • 1970-01-01
    • 2017-04-21
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多