如何从 stdin 读取超过 4096 个字节,并复制粘贴到 Linux 上的终端?

问题描述 投票:0回答:1

我有这个代码:

import sys

binfile = "data.hex"
print("Paste ascii encoded data.")

line = sys.stdin.readline()
b = bytes.fromhex(line)

with open(binfile, "wb") as fp:
    fp.write(b)

问题是在

sys.stdin.readline()
调用中读取的字节数永远不会超过 4096 个字节。我怎样才能使缓冲区更大?我尝试为呼叫提供更大的号码,但这没有效果。

更新我将我的

stdin
阅读代码更改为:

line = ''
while True:
    b = sys.stdin.read(1)
    sys.stdin.flush()
    line += b

    if b == "\n":
        break

print(f"Read {len(line)} bytes")

仍然会遇到这个限制。

python stdin readline
1个回答
2
投票

这种将长行截断为 4096 字节的行为是由 Linux 内核中的终端 (TTY) 代码引起的。 (实际上,作为截断的一部分,4096 字节中的最后一个字节也被替换为换行符字节。)当 (Python) 进程从 TTY 读取其标准输入时,该行已被截断。对于您的用例,没有简单的修复方法,即在将长行复制粘贴到终端窗口时防止截断。作为解决方法,请复制粘贴到文件(例如

infile.dat
),然后运行
python script.py <infile.dat

即使没有 Python,通过运行

dd bs=65536 of=/dev/null
,复制粘贴长度超过 4096 字节的行,然后按 Ctrl-D 来指示 EOF,也可以轻松重现截断行为。输出的最后一行将以
4096 bytes (4.1 kB, 4.0 KiB) copied,
开头,表示仅读取了 4096 字节。如果您复制粘贴多行长行,您会发现每行都会被分别截断为 4096 字节(包括换行符字节)。

对此 Linux 内核行为的更多分析:

字节计数限制 4096 被硬编码到 Linux 内核,如 N_TTY_BUF_SIZE

我的答案的其余部分演示了 Python 和 shell(例如 Bash)如何在没有截断的情况下工作,因此它们不会导致问题。


这是为了证明 Python

sys.stderr.readline()
不会截断,因此无需更改您的 Python 代码。

Python

sys.stdin.readline()
有无限的缓冲区(假设有足够的可用内存)。我已经在 Linux 上使用 Python 2.7、3.6 和更新版本的 Python 进行了尝试。

这是我尝试过的:

  • 立即从管道读取短行(Python 中没有额外的缓冲延迟):

    $ (echo -n A; sleep .3; echo a; sleep .3; echo B; sleep .3) | python -c "if 1:
      for line in iter(__import__('sys').stdin.readline, ''): print([line])"
    ['Aa\n']
    ['B\n']
    

    要尝试一下,请运行不带前导

    $
    的命令。它对我来说适用于 Linux,我认为它也适用于 macOS、Windows 和其他系统。在 Windows 上,您可能需要删除
    if 1:
    和换行符。

  • 在 Python 3.x 中,使用

    sys.stdin.buffer.readline()
    :

    立即从管道中将短行读取为字节(而不是 Unicode 字符)
    $ (echo -n A; sleep .3; echo a; sleep .3; echo B; sleep .3) | python -c "if 1:
      for line in iter(__import__('sys').stdin.buffer.readline, b''): print([line])"
    [b'Aa\n']
    [b'B\n']
    

    要尝试一下,请运行不带前导

    $
    的命令。它对我来说适用于 Linux,我认为它也适用于 macOS、Windows 和其他系统。在 Windows 上,您可能需要删除
    if 1:
    和换行符。

  • 立即读取长行(超过 10 MiB),无需从管道截断:

    $ python -c "if 1:
          import sys, time; f = sys.stdout
          f.write('A' * 10987654); f.flush(); time.sleep(.3)
          f.write('aaa\n'); f.flush(); time.sleep(.3)
          f.write('B\n'); f.flush(); time.sleep(.3)" |
      python -c "if 1:
          for line in iter(__import__('sys').stdin.readline, ''): print(len(line))"
    10987658
    2
    

    要尝试一下,请运行不带前导

    $
    的命令。它对我来说适用于 Linux,我认为它也适用于 macOS、Windows 和其他系统。在 Windows 上,将 Python 代码放入文件
    a.py
    b.py
    ,然后运行
    python a.py | python b.py


在某些情况下,尽快(即进程接收到输入)将输入传递给 Python 程序很有用,即防止由于缓冲而导致延迟

sys.stdin

好消息:

sys.stdin.readline()
一旦进程可用就返回下一行,它不会等待后续行。在循环中,使用
for line in iter(sys.stdin.readline, ''):
,不要使用
for line in sys.stdin:
,因为即使有一行可用,后者也会等待更多输入。有关详细信息,请参阅 https://stackoverflow.com/a/28919832/97248 和其他答案。

sys.stdin.read(n)
通常有缓冲延迟:即使进程已经读取了
n
字节,
sys.stdin.read(n)
也会等待更多字节,直到其缓冲区(通常为8192字节)被填满。为了避免在 Python 3 中出现这种延迟,请改用
sys.stdin.buffer.raw.read(n)
。这将最多读取
n
字节(不是Unicode字符),并且一旦至少有1个字节可用,它就会返回。不要将其与
sys.stdin.readline()
混合。在 Python 2 和 3 中,使用
os.read(sys.stdin.fileno(), n)
来实现此目的。使用管道(例如
cat | python ...
)测试缓冲延迟,因为如果没有管道,系统可能会使用终端(TTY)设备,该设备默认具有行缓冲,在行尾更早返回数据。


这是为了证明导致截断的不是 shell。

TODO(分):写这个。

© www.soinside.com 2019 - 2024. All rights reserved.