将子进程的输出转换为 csv.reader 对象

Converting output from subprocess to csv.reader object

有没有办法从子进程中获取输出并将其转换为可迭代的 csv.reader 或 csv.DictReader 对象?这是我一直在尝试的代码:

p2 = subprocess.Popen("sort command...", stdout=subprocess.PIPE)
output = p2.communicate()[0]
edits = csv.reader(output, delimiter="\t")

基本上,我正在对一个大型 CSV 文件进行排序,然后我想将其作为 csv.reader 对象放入 Python。

我得到的错误是

Error: iterator should return strings, not int (did you open the file in text mode?)

有没有办法把这个字节流当作一个 csv.reader 对象,或者我是不是想错了?

这是Python中的一个问题 3. CSV模块需要unicode输入,而不是字节串。除此之外,csv.reader() 需要一个可迭代对象,例如一个打开的文件或一个字符串列表。试试这个:

encoding = 'ascii'    # specify the encoding of the CSV data
p2 = subprocess.Popen(['sort', '/tmp/data.csv'], stdout=subprocess.PIPE)
output = p2.communicate()[0].decode(encoding)
edits = csv.reader(output.splitlines(), delimiter=",")
for row in edits:
    print(row)

如果 /tmp/data.csv 包含(我使用逗号作为分隔符):

1,2,3,4
9,10,11,12
a,b,c,d
5,6,7,8

那么输出将是:

['1', '2', '3', '4']
['5', '6', '7', '8']
['9', '10', '11', '12']
['a', 'b', 'c', 'd']

以下对我有用(尽管文档警告阅读 stdout)。用 io.TextIOWrapper() 包装 stdout 支持在字段数据中嵌入换行符。

这样做允许使用生成器,其优点是允许 stdout 增量读取,一次读取一行。

p2 = subprocess.Popen(["sort", "tabbed.csv"], stdout=subprocess.PIPE)
output = io.TextIOWrapper(p2.stdout, newline=os.linesep)
edits = csv.reader((line for line in output), delimiter="\t")
for row in edits:
    print(row)

输出:

['1', '2', '3', '4']
['5', '6', '7', '8']
['9', '10', '11', '12']
['a', 'b\r\nx', 'c', 'd']

tabbed.csv 输入测试文件包含以下内容(其中 » 代表制表符, 代表换行符):

1»2»3»4
9»10»11»12
a»"b≡x"»c»d
5»6»7»8

要启用文本模式,传递universal_newlines=True参数:

#!/usr/bin/env python3
import csv
from subprocess import Popen, PIPE

with Popen(["sort", "a.csv"], stdout=PIPE, universal_newlines=True) as p:
    print(list(csv.reader(p.stdout, delimiter="\t")))

如果您需要解释嵌入在引号字段中的换行符,则创建 io.TextIOWrapper,以传递 newline='' 参数:

#!/usr/bin/env python3
import csv
import io
from subprocess import Popen, PIPE

with Popen(["sort", "a.csv"], stdout=PIPE) as p, \
     io.TextIOWrapper(p.stdout, newline='') as text_file:
    print(list(csv.reader(text_file, delimiter="\t")))

此外,TextIOWrapper 允许明确指定字符编码(否则使用默认 locale.getpreferredencoding(False))。

注意:您不需要外部 sort 命令。您可以按纯 Python:

对行进行排序
#!/usr/bin/env python3
import csv

with open('a.csv', newline='') as text_file:
    rows = list(csv.reader(text_file, delimiter="\t"))
    rows.sort()
    print(rows)

注意:更高版本对 csv 行而不是物理行进行排序(如果需要,您可以对行进行排序)。

如果您的 CSV 文件有列标题,此方法有效。

[ user@system currentDir ]$ ./ProgramThatCreatesCSVData
first,second,third,fourth
1,2,3,4
9,10,11,12
a,b,c,d
5,6,7,8
[ user@system currentDir ]$
[ user@system currentDir ]$
[ user@system currentDir ]$
[ user@system currentDir ]$ cat CSVtoDict.py
#!/usr/bin/python3
"""Sample program to open a pipe to run a command.
That command generates a CSV with heading names in the first row.
Output of this program is a conversion of that CSV to a list of dictionaries,
in pprint format."""

import csv, pprint, subprocess, io

pipe = subprocess.Popen(["./ProgramThatCreatesCSVData"], stdout=subprocess.PIPE)
pipeWrapper = io.TextIOWrapper(pipe.stdout)
pipeReader = csv.DictReader(pipeWrapper)
listOfDicts = [ dict(row) for row in pipeReader ]

pprint.pprint(listOfDicts)

[ user@system currentDir ]$
[ user@system currentDir ]$
[ user@system currentDir ]$
[ user@system currentDir ]$ python3 CSVtoDict.py
[{'first': '1', 'fourth': '4', 'second': '2', 'third': '3'},
 {'first': '9', 'fourth': '12', 'second': '10', 'third': '11'},
 {'first': 'a', 'fourth': 'd', 'second': 'b', 'third': 'c'},
 {'first': '5', 'fourth': '8', 'second': '6', 'third': '7'}]
[ user@system currentDir ]$