【问题标题】:bash: read discards terminal line input after 4096 bytesbash:读取在 4096 字节后丢弃终端行输入
【发布时间】:2018-09-10 01:45:48
【问题描述】:

为了演示这个问题,在 Linux 中运行此命令后粘贴一个长字符串(>4096 字节):

read foo && wc -c <<<"$foo"

结果为 4096,表示输入被截断。

Some research 透露有一个终端行缓冲区大小硬编码为 4096,这解释了截断。但是,当我尝试使用 -n 选项阅读时,它起作用了:

read -n 32768 foo && wc -c <<<"$foo"

结果是输入的实际长度(+1,但这是由于 here-string)而不是 4096。

所以我想知道-n 32768 选项有什么魔力。我没有在 bash 手册页中找到有关此的相关信息。这是我们可以依赖的功能吗?

【问题讨论】:

  • 您可以通过stty 或等效的ioctl 禁用终端缓冲。这就是 read 所做的,以允许读取不到一整行
  • @thatotherguy 有相关参考吗?如果read 旨在正确处理长输入(不仅仅是巧合或未指定的行为),那么也许我们可以在 bash 中依赖它。
  • 顺便说一句,我刚开始测试这个并没有看到任何截断。我会调查您是否提供读取输入(您提到粘贴......就像从剪贴板中一样?)是罪魁祸首。

标签: linux bash terminal


【解决方案1】:

Bash 的read 实现允许您使用-n 标志指定要读取的最大字符数,或者使用-d 标志指定替代终止字符。这些选项都不适用于标准终端输入,因为通常终端驱动程序将输入保存在自己的内部缓冲区中,直到用户键入 ENTER 键(或某些其他击键,如 Control-C 或 Control-D)。

例如,read -n1 char 背后的想法是您希望 read 在用户键入单个字符后立即返回,而不是您希望 read 等待用户键入完整的行,然后返回该行的第一个字符。同样,命令read -d';' command 应该在用户输入分号后立即返回;同样,等待用户输入完整的行,然后只返回分号之前的部分是出乎意料的。

因此,为了使这些选项按预期工作,read 内置函数需要告诉终端驱动程序在输入字符后立即返回。如果输入设备是终端,并且您指定最大输入长度或除换行符以外的分隔符,read 通过修改以下termios flags 将终端置于“原始”模式:

off: ICANON INLCR OCRNL ONOCR ONLRET
on: ISIG IEXTEN ICRNL OPOST ONLCR

关闭ICANON 后,终端驱动程序不再缓冲输入。

如原帖所述,Linux 内核驱动程序使用固定长度的 4096 输入缓冲区来实现行编辑,它会简单地忽略不适合此缓冲区的键入字符。因此,在终端处于正常输入模式时,您的输入将在 4096 个字符后被截断。关闭ICANON 后,驱动程序会尽快传递字符,并且不会截断输入。

但关闭输入规范化的副作用是终端驱动程序不再解释退格键和删除键,从而无法进行行编辑。你可以试试这个:

# I typed a, x, backspace, b, return
$ read -n 4 input
ax^?b
$ printf "%s" "$input" | hd
00000000  61 62 7f 78                                       |ab.x|
00000004

请注意,退格键 (0x7f) 发送的删除字符会保留在输入中。

这是一种不太理想的用户体验;您当然不希望它用于输入长输入。在大多数情况下,人们希望退格“起作用”。但是,它非常适合编写小型控制台游戏,其中脚本需要在键入时对每次击键做出反应。

Bash 本身使用readline 库来读取输入。 readline 也将终端置于原始模式,但与内置的 read 不同,它实际上处理退格字符、箭头键和大量其他字符,包括许多内核驱动程序显然一无所知的特殊字符,例如标签完成和历史搜索。

read 内置函数还有 -e 标志,这会导致它使用 readline(如果它是从终端读取的)。使用-e 进行上述实验可能会产生更方便的结果:

# I typed a, x, backspace, b, c, d
$ read -en4 input
abcd
$ printf "%s" "$input" | hd
00000000  61 62 63 64                                       |abcd|
00000004

这一次,readline 处理了退格,在输入四个“真实”字符后返回了read

【讨论】:

    【解决方案2】:

    从 bash 手册页部分阅读:

    -n 个字符

    read 在读取 nchars 字符后返回,而不是等待完整的输入行,但如果在分隔符之前读取的字符少于 nchars,则使用分隔符。

    (我很确定这是一个特定于 bash 的扩展,而不是使用其他 shell 时可以依赖的东西,除非您确认某个特定的 shell 也支持它)。编辑:例如,zsh 与-n 做了一些非常不同的事情。

    【讨论】:

    • 我在手册页中看到了这句话,但仍不清楚-n 究竟做了什么而不是等待完整的输入行。它是否暂时禁用行缓冲?
    • 这意味着如果你做一个read -n 50 并给它100个字符,它会返回50。
    猜你喜欢
    • 2017-01-23
    • 1970-01-01
    • 1970-01-01
    • 2023-03-17
    • 2013-08-03
    • 2011-07-26
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多