在Python中读取文件同时在屏幕上记录数据
背景
为了从逻辑控制器获取数据,我使用了screen这个终端模拟器,并通过KeySpan USA-19HS USB串口适配器连接我的MacBook。我写了一个bash脚本,这样我就可以输入talk2controller <filename>
,其中filename是数据文件的名称。
#!/bin/bash
if [ -z "$1" ]; then
echo Please provide the filename to save the logfile
exit
fi
LOGFILE=$1
echo "logfile $1" > screenrc # Set the logfile filename
echo "logfile flush 1" >> screenrc # Wait 1 sec before flushing buffer to filesystem
screen -L -c screenrc /dev/tty.KeySerial1 19200
我把日志文件的名称改了,并把默认的10秒等待时间改成了1秒,这样可以更快地把日志缓冲区的数据写入文件。我把这些命令保存到screenrc
文件里。然后我用以下命令启动screen:
-L
— 开启日志记录-c screenrc
— 使用自定义的配置文件/dev/tty.KeySerial1 19200
— 以19200的波特率与串口通信
每次记录的测试大约需要3到6分钟,内容包括速度、加速度和位置信息。我会根据加速度的变化来判断测试是否有效。目前,我是在测试结束后再运行一个Python的matplotlib脚本来绘制速度、加速度和位置的图表,以确认测试是否有效,然后再进行下一个测试。
为了节省时间,我希望能在测试进行到一半的时候就开始绘制数据,这样数据还在被捕获。
问题
在我看来,有两种方法可以在捕获更多数据的同时绘制数据:
- 选项1: 使用screen记录数据,然后让Python的matplotlib脚本读取部分日志文件。
- 问题1: 如果Python脚本在screen还在写数据的时候读取日志文件,会有什么问题吗?
- 选项2: 从使用screen切换到使用pySerial。不过,在测试期间绘制数据的优先级低于仅仅捕获数据。我不希望绘图部分的代码出错导致数据记录失败。这就是screen的好处——它只负责记录数据,不做其他事情。
- 问题2: 如果我切换到pySerial,能否运行两个线程来减少绘图部分对数据捕获的影响?这样做有什么好处吗?
问题3: 有没有我没想到的更好的选项?
3 个回答
我觉得第二种方案更好。这样你可以完全掌控每一个输入字节的处理方式,接收到数据后可以随意处理。你可以写一个非常简单的Python脚本,直接把读取到的数据写入磁盘。你的绘图代码可以在一个完全独立的进程中运行,这个进程是通过fork()
从第一个进程创建的。要把数据从一个进程传到另一个进程,你可以选择(a)让第一个进程也写入一个socketpair()
或者其他的进程间通信机制;或者(b)将输出文件对象配置为行缓冲,这样每写完一整行就会强制同步,然后在第二个进程中监控新内容。
第一种方案的问题在于你无法控制screen
的缓冲行为。你可以监控它的日志文件以获取新内容,但你的日志代码需要准备好处理不完整的行和一次性的大块数据。根据具体的缓冲行为,你甚至可能在screen
进程退出之前根本看不到任何数据!
我觉得选项1完全可行,因为你可以让Python以只读的方式“跟踪”日志文件,这样在screen
仍在写入日志时,文件不会受到影响。在跟踪文件的过程中,每当日志文件中出现新的日志事件时,你可以执行指定的操作。
如果你感兴趣,想看看一些实际的代码,我有一个个人项目使用了这个功能。这个项目叫做thrasher-logdrop,核心代码在logdrop.py。基本流程是:
- 用
do_tail()
跟踪一个文件 - 用
tail_lines()
监视日志事件 - 用
handle_line()
对事件执行操作
选项1和选项2都可以用,但为了所有美好的事物,尽量别用线程来做这个!这样你会遇到最糟糕的情况:锁定问题,而且如果图形线程出错,整个程序(包括日志线程)都会崩溃。正如其他人提到的,使用两个独立的进程来处理这个问题是可以的。screen
这个工具用来做这个有点奇怪,手动写Python代码也不太合适。我建议把talk2controller脚本重写成这样简单的版本:
stty -F /dev/tty.KeySerial1 19200 raw
cat </dev/tty.KeySerial1 >logfile
(如果你想让每次运行脚本时都把结果追加到文件里,而不是从头开始写,可以用>>logfile
。)
另一个问题是,程序在有人写文件的时候读取这个文件是否可以。更具体地说,如果你在读取的时候,日志的一行正好写了一半,那该怎么办?
答案是:这样做是可以的,但你说得对,你不能保证在你读取的时候那一行不会是半写的。(如果你自己写一个替代cat
或screen
的程序,其实可以通过总是用os.read()
来写文件,而不是用sys.stdout.write()
或print
来保证这一点。)
不过,其实不需要这种保证。你只需要在读取文件时小心,就不会有问题。基本上,不完整的行就是没有以\n
换行符结尾的行。因此:
for line in open('logfile'):
if not line.endswith('\n'): break
...handle valid line...
因为\n
字符是日志中每一行最后写的东西,所以你可以确定,如果你读到一个\n
字符,前面的内容都是正确写入的。