如何使 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 标准中的工具可供我们使用:
该
RS
变量可以包含多个字符,在这种情况下它是一个正则表达式。该
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 .
如果我们使用正则表达式(.)
(匹配任何字符)作为记录分隔符,我们会得到根本不包含字段的空记录,并且终止空记录的字符在RT
GNU 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 也会调用read
TTY 上的另一个来读取字符,然后才分派该记录。
因此,我们必须诉诸一些非常丑陋的东西,比如在for
orwhile
循环中循环并调用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) } }'