awk
此页面比较了 BusyBox 的 awk 实现(使用此配置文件)与 gawk(版本 >= 3.1.8)和 FreeBSD 9 的 nawk,后者基于 Bell Labs/Brian Kernighan 2007 年版本的 awk。
它并非旨在作为教程,而是作为技术摘要和 “gotchas” 列表(不同实现可能以不同或意外方式表现的地方)。
调用 awk
awk [-F char_or_regex] [-v var=value ...] [--] 'awk script...' [ ARGV[1] ... ARGV[ARGC-1] ] awk [-F char_or_regex] [-v var=value ...] -f scriptfile ... [--] [ ARGV[1] ... ARGV[ARGC-1] ]
Gawk 使第三种调用模式成为可能,它将 -f scriptfile
选项与命令行上的 'awk script...'
混合使用
gawk [-F char_or_regex] [-v var=value ...] -f scriptfile ... -e 'awk script...' [--] [ ARGV[1] ... ARGV[ARGC-1] ]
Awkenough 是一小组 Awk 实用程序例程和一个 C 存根,可以更轻松地使用 awk shebang 行编写 shell 脚本。(我很快会为此制作一个 Alpine 软件包。)它还允许混合使用 -f scriptfile
和命令行脚本,使用 gawk 提供的相同 -e
选项。在这两种情况下,长格式选项 --source
的作用与 -e
相同。
- 注释
- 所有实现都支持
-v FS=expr
中的\t
等。BusyBox 在-F expr
中不支持\t
;这可能是一个 bug。您可以使用 BusyBox ash 的$'\t'
转义来解决此问题。
nawk
和gawk --traditional
(但不包括gawk --posix
)将-F t
解释为-F '\t'
。这是一个为历史兼容性保留的奇怪的特殊情况。
- scriptfile 可以是
-
,表示 stdin
- ARGV 可以是任何形式,但 awk 只知道如何自动处理形式为以下内容的参数
var=unquoted_value
:当主循环到达该 ARGV 时,将进行赋值""
:将被跳过文件名
-
:将使用 stdin
- 如果没有处理任何文件(也没有
-
),则在所有命令行赋值之后将处理 stdin。
- 独立脚本应如下所示
内容 foo.awk
#!/usr/bin/awk -f # 将接收脚本的 ARGC、ARGV,其中 ARGV[0]=awk BEGIN { ... } ...- 或如下所示
内容 bar.awk
#!/usr/bin/runawk -f /usr/share/awkenough/library.awk ...
- gawk 将任何位于
--
之前的无法识别的短选项放入 ARGV;其他实现(和gawk --traditional
)会失败/警告
- 一些 gawk 独有的选项
--lint
:警告关于不可移植的构造--re-interval
:启用/pat{1,3}/
正则表达式;某些版本的 gawk 默认启用,而其他版本则不启用--traditional
或--compat
:使 gawk 的行为类似于 nawk--posix
:类似于--traditional --re-interval
,但有一些进一步的限制--exec scriptfile
:停止选项处理并禁用任何进一步的-v
;旨在用于不信任环境 (cgi) 中脚本的 shebang 行--sandbox
:禁用system("foo")
、"foo"|getline
、getline <"file"
、print|"foo"
、print >"file"
和 gawk 的extension(obj, func)
;因此只有 ARGV 中显式指定的本地资源才可访问。此功能仅在最新版本的 gawk 中可用。
- 当处理
-f path
选项时,如果 path 不包含/
,gawk 将首先在 AWKPATH 环境变量中指定的目录中搜索
Awk 语法
块
- 函数定义
- 函数具有全局作用域,并且可以从语法上先于其定义的位置调用它们。
- 标量(即非数组)按值传递,数组按引用传递。
- 函数调用可以是嵌套的或递归的。函数的每次调用都有其自己的本地环境:因此在返回时,调用者提供的标量参数的值应保持不变(数组参数可能已被修改)。
- 考虑如下定义的函数
function foo(x,y) { ... bar() return "result" } function bar(z) { y += 1 return }
这里变量
x
和y
是 foo 的本地变量,变量z
是 bar 的本地变量。所有其他变量引用都是全局的:与 shell 形成对比,在 shell 中,非本地变量引用而是动态的。也就是说,在 shell 中,当 foo 调用 bar 时,bar 将递增 foo 的本地变量y
。另一方面,在 awk 中,bar 始终只递增全局变量y
。另请注意,函数调用中的参数数量不必与函数定义中的参数数量匹配;如果提供了更多参数,它们将被忽略;如果提供的参数较少(如 foo 调用 bar 的情况),则根据它们在函数中的使用方式,将缺失的变量分配为 "" 或空数组。
另请注意,
return
语句不必给出显式的返回值(它将默认为 "")。return
语句也可以完全省略。
- BEGIN 和 END
BEGIN { ... } ... END { ... } ...
- 多个 BEGIN 块被合并;多个 END 块也是如此。
- 如果只有 BEGIN 块(没有主循环或 END 块),并且未使用 getline,则 POSIX 要求 awk 在不读取 stdin 或任何文件操作数的情况下终止。我检查过的实现都遵守这一点,但一些历史实现在这种情况下会丢弃输入。为了可移植性,您可以显式地将
< /dev/null
附加到您知道不应消耗 stdin 的 awk 调用中。
- 如果存在主循环或 END 块,并且 awk 对 stdin 的处理没有因在其 ARGV 列表中遇到文件而被抑制,则将在执行任何 END 块之前处理 stdin。
- 如果存在 END 块,但在它们之外调用
exit status
,则主循环停止,控制权立即传递到第一个 END 块。如果在 END 块内调用exit status
,或者如果没有 end 块,则脚本以结果 status 终止。
- POSIX 要求在 END 内部,只要不调用
getline
,所有FILENAME NR FNR NF
都保留其最近的值。我的实现符合要求,并且还保留了最近的字段$0 ...
,但早期版本的 nawk 没有;Darwin 的 awk 可能也没有。
- 主循环块
这些形式如下
pattern { action ... }
或
pattern,pattern { action ... }
其中 action 默认为 print $0
;pattern 默认为 1
(即 true)。
-
模式 可以采用以下任何形式
/regex/
关系
pattern && pattern
pattern || pattern
! pattern
( pattern )
pattern ? pattern : pattern
$0 ~ /regex/
相同。 -
关系表达式 可以采用以下任何形式
expr ~ RE
expr !~ RE
expr eqop expr
expr in arrayname
(expr, ...) in arrayname
expr
/a[bc]+/
,或任何求值为字符串的表达式,例如"a[bc]+"
。请注意,字符串需要额外的\
级别:您可以编写/\w+/
或"\\w+"
。模式 eqop 可以是以下任何一种:== != <= < > >=
。裸表达式(如最后一种形式)在表达式求值为0
或""
时被解释为 false,否则为 true。
- 其他块形式
BEGINFILE { ... } ENDFILE { ... }
这些仅在(最新版本的)gawk 中可用。
@include "filename"
这仅在某些版本的 gawk 中可用。另请参阅 Aleksey Cheusov 的 Runawk,它使用不同的方法。
语句
行可以在以下任何一项之后断开:\ , { && || ? :
(最后两个仅在 BusyBox 和 gawk 中,而不包括 gawk --posix
。)
- 函数调用
func(expr, ...)
始终求值为一个值,因此可以用作表达式,但也可以出现在语句上下文中。(某些 awk(如 nawk)不允许任意表达式出现在语句上下文中;但 BusyBox awk 允许。)
调用用户定义的函数时,函数名称和 (
之间不能有空格。
- 赋值
lvalue = expr lvalue += 1 (similarly for -= *= /= %= ^=) lvalue ++ (similarly for --) ++ lvalue (similarly for --)
lvalue 可以采用以下任何形式
var
arrayname[expr]
arrayname[expr, ...]
$expr
与函数调用类似,赋值始终求值为一个值,因此可以用作表达式,但也可以出现在语句上下文中,即使在 nawk 中也是如此。
delete arrayname[expr] delete arrayname[expr, ...] delete arrayname
最后一种形式在 gawk --traditional
中不可用。更具可移植性的是,您可以使用以下方法获得相同的效果
split("", arrayname)
删除数组后,它不再包含任何元素;但是,数组名称仍然不可用作标量变量。
- 控制运算符
if (test) action if (test) action; else action if (test) action; else if (test) action if (test) action; else if (test) action; else action if (test) { ... } if (test) { ... } else action
等等。
while (test) action ... do action while (test) ...
do...while 形式将至少执行一次 action。
for (i=1; i<=NF; i++) action ... for (k in arrayname) action ...
最后一种形式具有未指定的迭代顺序。此外,在迭代数组时添加或删除元素的效果是未定义的。
break continue
历史悠久的 awk 实现将 while/do/for 循环外部的 break
和 continue
解释为 next
。BusyBox 支持此用法;某些版本的 gawk --traditional
也支持。
next
这将读取下一行输入并从第一条规则重新启动主循环。例如,要特殊处理 awk 输入的第一行,您可以这样做
NR==1 {...handle first line...; next } {...handle other lines...}
POSIX 未指定 next
在 BEGIN 或 END 块中的行为。
nextfile
这将中止处理当前文件,并从第一条规则开始处理下一个文件(如果有)。它在 gawk --traditional
中不可用。
return [value]
value 默认为 ""
。POSIX 未指定函数外部 return
语句的行为。
exit status
这将开始执行 END 规则,如果在 END 块内部调用,则立即退出。status 默认为 0
。在 gawk 和 nawk 中,如下序列
{ ... exit n } END { exit }
将保留 n
状态代码。在 BusyBox awk 中,第二次退出将恢复为默认值 0
。
- 打印
对于 print
和 printf
,括号是可选的,但对于 sprintf
,括号是强制性的。此外,如果 print
或 printf
的任何参数包含 >
,则应使用括号。
尽管 print
和 printf
接受带括号的参数列表,但它们的调用是语句而不是表达式。它们不返回值,并且不能出现在非语句上下文中。(另一方面,sprintf
是一个函数;并且它的调用可以出现在语句和表达式上下文中。)
以下第一个
print "a" "b" print "a","b"
使用单个参数调用 print
,该参数是表达式 "a"
和 "b"
的简单连接 "ab"
。另一方面,第二种形式使用两个参数调用 print
。它们将在输出中由 OFS 的当前值分隔---OFS 可能是,但不必是 ""。OFS 的默认值是单个空格。
与 print $0
的解释相同。(另请注意,形式为 pattern
且没有 {...}
的块被解释为具有主体 { print $0 }
。)
print "" print("")
两者都打印换行符。
print (expr, ...) > "path" print (expr, ...) >> "path"
打印到指定的路径。(另请参阅下面的#内建文件名。)输出文件在执行 close "path"
或 awk 退出之前不会关闭。
print (expr, ...) | "shell pipeline"
打印到指定的管道。管道在执行 close "shell pipeline"
或 awk 退出之前不会关闭。
printf ( format, expr, ... )
可以后跟 > "path"
或 >> "path"
或 | "shell pipeline"
,就像 print
一样。
这通常实现 printf(3) 功能的子集。format 字符串可以包含
- 普通的 Awk 字符串转义代码,例如
\b
(0x8),退格一个空格\r
(0xd),在 Unix 上退格到当前行的开头\v
和\f
(0xb 和 0xc),在 Unix 上保持在当前列中,但转到下一个屏幕行\n
和\t
和\"
和\\
\a
(0x7)\c
,它忽略字符串的其余部分(我的 awk 实现均未遵守这一点)\000
,最多三个八进制数字
"\0"
;其他实现会将其视为终止字符串。(打印"\0"
的另一种方法是使用printf "%c", 0
。这适用于 gawk 的printf
和sprintf
以及 nawk 的printf
。) - 格式化代码,形式如下
%[n$][#][-|0][+| ]['][minwidth][.precision][wordsize specifier][format specifier]
其中
n$
表示使用 argv[n] 而不是序列中的下一个 argv。计数从 1 开始,只有 gawk 遵守这一点。#
强制八进制数具有初始0
,十六进制数具有初始0x
,浮点数始终具有小数点。-
表示左对齐0
表示像往常一样右对齐,但用零而不是空格填充+
强制包含符号'
表示将一百万写成1,000,000
。我的 awk 实现均未遵守这一点。minwidth
或precision
可以是*
,在这种情况下,值由下一个 argv 提供。只有 gawk 和 nawk 遵守这一点。precision
给出数字的小数位数,或字符串的最大宽度。wordsize specifier
只有 nawk 遵守这些,并且仅限:hh h l ll
format specifier
可以是任何有符号格式fegdi
(后两者等效),或任何无符号格式xuo
,或任何文本格式sc
。在 BusyBox 上,格式%c
仅处理高达 0x7fff 的值,并且结果始终屏蔽到范围 "\x00"..."\xff"。
我的 awk 实现均未遵守
%b
(类似于echo -e
)或任何其他格式。
表达式
![]()
|
字符串 vs 数值
![]()
|
数组
![]()
|
内建函数
![]()
|
内建文件名
只有 gawk 在内部处理这些文件名
"/dev/tty"
"/dev/stdin"
"/dev/stdout"
"/dev/stderr"
"/dev/fd/n"
但在许多 Unix 系统中,它们在外部仍然可用。作为 getline < "/dev/stdin"
的替代方案,您也可以使用 getline < "-"
。作为 print ... > "/dev/stderr"
的替代方案,您也可以使用:print ... | "cat 1>&2"
。
内建变量
![]()
|
- ENVIRON
- 这是一个关联数组,保存当前环境变量。该数组是可变的,但 POSIX 未指定对其进行的更改是否必须对从 awk 生成的子进程可见。(在我检查过的所有实现中,此类更改均不可见。)
- ARGC 和 ARGV
- awk 的命令行参数的数量,以及保存它们的数组。ARGV[0] 通常为 “awk”,ARGV[1]...ARGV[ARGC-1] 将是参数。Awk 特殊处理空参数,以及形式为
var=value
的参数。
- 如果您的脚本仅具有 BEGIN 块,而没有主循环规则或 END 块,则 awk 不会尝试以任何方式自动读取或处理 ARGV 参数或 stdin。
- 您可以递减 ARGC;然后 awk 将不会处理任何已丢失的参数。您还可以设置
ARGV[n] = ""
,或者,也可以delete ARGV[n]
。同样,您可以添加其他 ARGV 条目,但如果您这样做,则需要手动递增 ARGC。
- awk 处理的参数不应引用目录或不存在的文件(除非您要手动扫描 ARGV 列表并删除此类参数)。某些 awk 实现的某些版本在被要求处理目录时会发出警告;其他版本会引发错误。
- ARGIND
- 指示 awk 上次处理的 ARGV 条目。这仅在 gawk 和 BusyBox awk 中存在。该变量是可变的;但对其进行的更改在 gawk 中没有副作用。在 BusyBox 中,对其进行的更改会影响在当前条目完成后接下来将处理哪个 ARGV 条目。
- FILENAME
- 指示当前正在处理的 ARGV 条目的名称。当正在处理 stdin 时,这将是
"-"
。
仅限 Gawk 的扩展
![]()
|
有用的链接
这些链接可能也很有趣
- https://www.pement.org/awk/awk1line.txt
- https://www.catonmat.net/series/awk-one-liners-explained
- http://awk.freeshell.org/HomePage
- http://awk.freeshell.org/AwkFeatureComparison
Awk 和 Lua
请参阅 Lua 正则表达式摘要。
棕色 的命令 来自 awkenough。
在 lua 中 | 在 awk 中 |
---|---|
string.find(str, pattern, [startpos]) --> (start,stop) 或 nil |
match(str, pat) --> 返回并设置 RSTART,也设置 RLENGTH
|
string.match(str, pattern, [start=1]) --> (%0) 或 nil 或 (%1,%2,...) |
matchstr(str, pat, [nth=1]) --> \\0,设置 RSTART 和 RLENGTHgawk 的 |
string.gmatch(str, pattern) --> 遍历所有匹配项或匹配组集合的迭代器迭代序列将如下所示: (%0), (%0), (%0) ...; 或者像: (%1,%2...), (%1,%2...),... |
gmatch(str, pat, MATCHES, STARTS) --> nmatches |
string.gsub(str, pattern, replacement, [max#repls]) --> (newstr,nrepls) |
gensub(pat, repl, nth/"g", str) :最接近 Lua 的 gsub
|
string:len
|
length(str)
|
string:lower
|
tolower
|
string.rep(str, count,[5.2 adds sep])
|
rep(str,count,[sep])
|
string.sub(str, start,[stop])
|
substr(str,start,[len])
|
split(str,ITEMS,[seppat],[gawk's SEPS]) --> nitems
| |
table.concat(tbl,[sep],[start],[stop]) --> string |
concat([start=1], [len=to_end], [fs=OFS], [A]) --> string如果要保留现有的 FS,需要使用 |
string:reverse
|
reverse([A])
|
table.remove(tbl, [pos=from end]) --> 原先位于 tbl[pos] 的值 |
pop([start=from_end], [len=to_end], [A]) --> 由 SUBSEP 分隔的值 |
table.insert(tbl,[valpos=insert at end],value) --> nil |
insert(value, [start=after_end], [A]) --> 数组的新长度
|
table.sort(tbl, [lessthan])
|
sort(A)
|
isempty(A)
| |
includes(A, B, [onlykeys?]) :B <= A 吗?
|