如何使 POSIX shell 和 awk 逐字符而不是逐行读取输入?

如何使 POSIX shell 和 awk 逐字符而不是逐行读取输入?

如何使 POSIX shell 和 awk 逐字符而不是逐行读取输入?

我正在制作一个从罗马字到日语假名的音译应用程序,我想将输入立即逐个字符处理到 awk 中。

我不知道在 awk 处理字符之前不需要按 return 或 Enter 来执行此操作的正确方法。

答案1

在 shell 脚本中,您可以使用该工具操作 TTY 状态stty

首先,stty -g生成一个表示当前状态的字符串。在执行其他操作之前,捕获此输出并将其保存在某个位置。稍后您可以将该字符串作为唯一参数传递以stty恢复 TTY 设置。该字符串不需要引号; POSIX 标准要求stty -g生成不需要在 shell 脚本中引用的表示形式。

stty raw是进入原始模式的一种方法,在该模式下可以一次输入一个字符。

savetty=$(stty -g)
stty raw
...
stty $savetty

使用该trap命令设置一个处理程序可能是一个好主意tty,即使脚本在某处退出或被中断,该处理程序也会恢复设置。

现在,假设我们将这段stty舞蹈包裹在一些 Awk 代码中。不失一般性,让我们在 Awk 之外进行此操作。我们怎样才能让 awk 一次读取一个字符?

awk 只能使用其隐式扫描策略或getline运算符来读取行。没有getchar。啊,但线条实际上意味着记录。在 GNU Awk 中,我们有两个不在 POSIX 标准中的工具可供我们使用:

  1. RS变量可以包含多个字符,在这种情况下它是一个正则表达式。

  2. RT变量保存与记录终止符匹配的文本片段。

看哪:

$ awk  'BEGIN { RS = "(.)" } { print NF, RT }'
How now brown cow.
0 H
0 o
0 w
0  
0 n
0 o
0 w
0  
0 b
0 r
0 o
0 w
0 n
0  
0 c
0 o
0 w
0 .
 

如果我们使用正则表达式(.)(匹配任何字符)作为记录分隔符,我们会得到根本不包含字段的空记录,并且终止空记录的字符在RTGNU Awk 中可用。

不幸的是,这并不完全有效。当我们将它集成到一个完整的程序中时:

#!/bin/sh

trap 'stty $ttysave' EXIT INT TERM
ttysave=$(stty -g)

stty raw -echo

awk  'BEGIN { RS = "(.)" }
      RT ~ /q/ { exit }
      { printf("[%s]", RT) }'

这显示了 Gawk 的记录分隔正则表达式机器正在读取前面一个字符的问题。例如,如果我们想通过输入立即退出q,这q是不够的。即使一条记录可以在此时定界,并且RT可以设置为q,Gawk 也会调用readTTY 上的另一个来读取字符,然后才分派该记录。

因此,我们必须诉诸一些非常丑陋的东西,比如在fororwhile循环中循环并调用dd实用程序:

#!/bin/sh

trap 'stty $ttysave' EXIT INT TERM
ttysave=$(stty -g)

stty raw -echo

awk  'BEGIN { cmd = "dd bs=1 count=1 2> /dev/null"
              for (;;)
              { cmd | getline ch
                close(cmd)
                if (ch == "q")
                  exit
                printf("[%s]", ch) } }'

相关内容