在Python中读取文件同时在屏幕上记录数据

2 投票
3 回答
1041 浏览
提问于 2025-04-16 04:08

背景

为了从逻辑控制器获取数据,我使用了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:

  1. -L — 开启日志记录
  2. -c screenrc — 使用自定义的配置文件
  3. /dev/tty.KeySerial1 19200 — 以19200的波特率与串口通信

每次记录的测试大约需要3到6分钟,内容包括速度、加速度和位置信息。我会根据加速度的变化来判断测试是否有效。目前,我是在测试结束后再运行一个Python的matplotlib脚本来绘制速度、加速度和位置的图表,以确认测试是否有效,然后再进行下一个测试。

为了节省时间,我希望能在测试进行到一半的时候就开始绘制数据,这样数据还在被捕获。

问题

在我看来,有两种方法可以在捕获更多数据的同时绘制数据:

  • 选项1: 使用screen记录数据,然后让Python的matplotlib脚本读取部分日志文件。
    • 问题1: 如果Python脚本在screen还在写数据的时候读取日志文件,会有什么问题吗?
  • 选项2: 从使用screen切换到使用pySerial。不过,在测试期间绘制数据的优先级低于仅仅捕获数据。我不希望绘图部分的代码出错导致数据记录失败。这就是screen的好处——它只负责记录数据,不做其他事情。
    • 问题2: 如果我切换到pySerial,能否运行两个线程来减少绘图部分对数据捕获的影响?这样做有什么好处吗?

问题3: 有没有我没想到的更好的选项?

3 个回答

1

我觉得第二种方案更好。这样你可以完全掌控每一个输入字节的处理方式,接收到数据后可以随意处理。你可以写一个非常简单的Python脚本,直接把读取到的数据写入磁盘。你的绘图代码可以在一个完全独立的进程中运行,这个进程是通过fork()从第一个进程创建的。要把数据从一个进程传到另一个进程,你可以选择(a)让第一个进程也写入一个socketpair()或者其他的进程间通信机制;或者(b)将输出文件对象配置为行缓冲,这样每写完一整行就会强制同步,然后在第二个进程中监控新内容。

第一种方案的问题在于你无法控制screen的缓冲行为。你可以监控它的日志文件以获取新内容,但你的日志代码需要准备好处理不完整的行和一次性的大块数据。根据具体的缓冲行为,你甚至可能在screen进程退出之前根本看不到任何数据!

1

我觉得选项1完全可行,因为你可以让Python以只读的方式“跟踪”日志文件,这样在screen仍在写入日志时,文件不会受到影响。在跟踪文件的过程中,每当日志文件中出现新的日志事件时,你可以执行指定的操作。

如果你感兴趣,想看看一些实际的代码,我有一个个人项目使用了这个功能。这个项目叫做thrasher-logdrop,核心代码在logdrop.py。基本流程是:

  • do_tail()跟踪一个文件
  • tail_lines()监视日志事件
  • handle_line()对事件执行操作
3

选项1和选项2都可以用,但为了所有美好的事物,尽量别用线程来做这个!这样你会遇到最糟糕的情况:锁定问题,而且如果图形线程出错,整个程序(包括日志线程)都会崩溃。正如其他人提到的,使用两个独立的进程来处理这个问题是可以的。screen这个工具用来做这个有点奇怪,手动写Python代码也不太合适。我建议把talk2controller脚本重写成这样简单的版本:

stty -F /dev/tty.KeySerial1 19200 raw
cat </dev/tty.KeySerial1 >logfile

(如果你想让每次运行脚本时都把结果追加到文件里,而不是从头开始写,可以用>>logfile。)

另一个问题是,程序在有人写文件的时候读取这个文件是否可以。更具体地说,如果你在读取的时候,日志的一行正好写了一半,那该怎么办?

答案是:这样做是可以的,但你说得对,你不能保证在你读取的时候那一行不会是半写的。(如果你自己写一个替代catscreen的程序,其实可以通过总是用os.read()来写文件,而不是用sys.stdout.write()print来保证这一点。)

不过,其实不需要这种保证。你只需要在读取文件时小心,就不会有问题。基本上,不完整的行就是没有以\n换行符结尾的行。因此:

for line in open('logfile'):
    if not line.endswith('\n'): break
    ...handle valid line...

因为\n字符是日志中每一行最后写的东西,所以你可以确定,如果你读到一个\n字符,前面的内容都是正确写入的。

撰写回答