GNU awk的$1=$1到底有什么作用?$0=$0呢?

转载:http://www.codeweblog.com/gnu-awk%E7%9A%84-1-1%E5%88%B0%E5%BA%95%E6%9C%89%E4%BB%80%E4%B9%88%E4%BD%9C%E7%94%A8-0-0%E5%91%A2/

GNU awk的$1=$1到底有什么作用?$0=$0呢?

$1=$1 是用来激活$0的重新赋值,也就是说 字段$1...和字段数NF的改变会促使awk重新计算$0的值,通常是在改变OFS后而需要输出$0时这样做

echo "A B C D" | awk '{OFS="|";print $0;$1=$1;print $0;}' 
A B C D
A|B|C|D

即使OFS放在BEGIN里,也没有用
echo "A B C D" | awk 'BEGIN{OFS="|"}{print $0;$1=$1;print $0;}'
A B C D
A|B|C|D

$0=$0 刚好相反, 修改$0的值可以促使awk重新计算字段$1...和字段数NF的的值,通常是在改变FS后而需要输出$1...或者NF时这样做

echo "A|B|C|D" | awk '{FS="|";print $1,NF;$0=$0;print $1,NF}'
A|B|C|D 1
A 4

不过把修改FS放在BEGIN里,就没必要$0=$0了
echo "A|B|C|D" | awk 'BEGIN{FS="|"}{print $1,NF;$0=$0;print $1,NF}'
A 4
A 4

其实,在awk源码中.重新计算$0的值的函数名叫rebuild_record,重构记录的意思.默认情况下.这个函数是不可能执行的.因为$0是直接读入的.$1=$1等修改字段的行为会触发awk执行这个函数
重新计算$1...的值的函数名叫reset_record,重新分割记录到字段的意思.默认情况下.这个函数只在处理完当前行,读入下一行之前执行.中途是不会执行的.$0=$0等修改记录的行为会触发awk执行这个函数
通过在源码中添加一行标记重新编译awk.可以查看awk到底什么时候运行了这两个函数.

rebuild_record

  1. echo "A B C D" | awk '{print $0;OFS="|";print "OFS=\"|\"";print $0;print "$1=$1";$1=$1;print $0;}'
  2. -----------reset_record----------- #读入行之前,执行reset_record,把$0拆分开赋值给$1...
  3. A B C D #$0为读入的值
  4. OFS="|" #改变OFS
  5. A B C D #没有触发rebuild_record重构$0,$0仍为读入的值.
  6. $1=$1 #执行$1=$1,
  7. ----------rebuild_record------------ #awk不管$1是否真的改变,执行rebuild_record.
  8. A|B|C|D #$0的值成为各个字段和ORS计算出的值

reset_record

  1. echo "A|B|C|D" | awk '{print $1,NF;print "FS=\"|\"";FS="|";print $1,NF;print "$0=$0";$0=$0;print $1,NF}'
  2. -----------reset_record----------- #读入行之前,执行reset_record,把$0拆分开赋值给$1...
  3. A|B|C|D 1 #$1为刚才计算的值
  4. FS="|" #改变FS
  5. A|B|C|D 1 #没有触发reset_record重构$1和NF,仍为第一次计算的值.
  6. $0=$0 #执行$0=$0
  7. -----------reset_record----------- #awk不管$0是否真的改变,执行reset_record.
  8. A 4 #$1和NF的值成为重新计算出的值
<think>我们正在讨论如何在awk命令中调用shell命令。根据用户的问题,他/她想知道如何通过awk命令调用shell命令。 在awk中,有两种主要方法可以调用shell命令: 1. 使用system()函数:这个函数可以执行一个字符串作为shell命令,并返回命令的退出状态。 2. 使用管道:通过管道将数据传递给shell命令,并读取命令的输出。 需要注意的是,awk本身是一种文本处理工具,但有时我们需要在awk脚本中执行一些外部命令。 我们将分别给出示例: 1. 使用system()函数: 语法:system("shell command") 示例:假设我们有一个文件,我们想用awk处理每一行,同时对于每一行,我们使用shell的echo命令打印一个额外的信息。 awk '{print $0; system("echo 这一行有 " length($0) " 个字符")}' filename 注意:system()函数会启动一个shell来执行命令,所以可以执行任何在shell中能执行的命令。但是,它不会将awk的变量直接传递给shell命令,我们需要通过字符串拼接的方式将awk变量嵌入命令字符串中。 2. 使用管道(getline): 我们可以通过管道将命令的输出读入awk变量中。 示例:我们想要将当前目录下的文件列表读入awk,然后打印每个文件名和其大小。 awk 'BEGIN { command = "ls -l" while ( (command | getline result) > 0 ) { print result } close(command) }' 这里,我们使用`command | getline`来读取命令的输出,每次读取一行。注意,使用完后要关闭命令(close(command)),否则可能会造成资源泄漏。 另外,我们也可以将awk的输出通过管道传递给外部命令,例如: awk '{print $1 | "sort -r"}' filename # 将第一列排序后输出 但是,用户的问题是关于在awk中调用shell命令,所以重点在于如何执行shell命令。 根据引用[2]:AWK是按一行一行地读取输入的。所以,在每行处理中调用system()是常见的做法。 然而,需要注意的是,频繁调用system()会降低awk的效率,因为每个system()调用都会启动一个新的shell进程。 因此,如果可能,尽量在awk中完成文本处理,或者考虑将数据收集起来,然后一次调用外部命令处理多个数据。 现在,我们根据用户的问题,给出一个具体的例子: 假设我们有一个文件data.txt,内容如下: apple 5 banana 3 orange 4 我们想使用awk计算每种水果的总价(假设单价为每公斤10元),并且使用shell的bc命令来进行浮点数乘法(因为awk本身不直接支持浮点数计算,但实际awk也可以做浮点运算,这里仅作为调用shell命令的示例)。 我们可以这样写: awk '{ # 使用system调用bc计算,但注意system不返回命令的输出,只返回退出状态 # 所以我们需要将结果写入一个临时文件,然后再读取,这样很麻烦 # 因此,这里我们使用getline的方式 cmd = "echo " $2 "*10 | bc" cmd | getline result close(cmd) print $1, $2, result }' data.txt 但是,实际上这个计算用awk本身就可以完成:$2*10,所以这里只是演示。 更常见的场景是调用一些外部工具,比如调用date命令转换日期格式等。 另外,注意在awk中调用shell命令时,命令字符串的构造要小心,特别是当字段中包含空格或特殊字符时,需要做适当的转义。 因此,我们给出一个更实际的例子:在awk中调用shell命令来转换日期格式。 假设文件dates.txt包含: 2023-01-01 2023-02-15 我们想将日期格式转换成“月/日/年”格式,即01/01/2023。我们可以使用系统的date命令(注意:这个例子在GNU date下有效)。 awk '{ cmd = "date -d \"" $0 "\" +%m/%d/%Y" cmd | getline newdate close(cmd) print newdate }' dates.txt 注意:这里我们使用了-d选项(GNU date),如果是其他系统的date命令可能不支持。 总结: - 使用system()函数:当不需要获取命令输出,只需要执行命令(如重定向输出到文件,或者执行一些操作)时。 - 使用管道和getline:当需要获取命令输出时。 最后,我们提醒用户,在awk中调用shell命令会带来性能开销,并且可能引入安全风险(如果命令字符串来自不可信的输入,则可能执行任意命令),因此请谨慎使用。 根据要求,我们生成相关问题: 1. 如何在awk中安全地构造shell命令字符串? 2. 除了system和getline,还有没有其他方法在awk中调用外部命令? 3. 如何避免在awk中频繁调用外部命令导致的性能问题?</think>### 如何在awk命令中调用Shell命令 在awk中调用Shell命令主要有两种方式,通过`system()`函数或管道配合`getline`实现: #### 1. **使用 `system()` 函数** 直接执行Shell命令,返回值为命令的退出状态码(0表示成功): ```awk # 示例:每处理一行就执行一次Shell命令 awk '{ print "当前行:", $0; status = system("echo '执行Shell命令' > log.txt"); if (status != 0) print "命令执行失败" }' input.txt ``` - **说明**: - `system("command")` 会启动子Shell执行命令 - 命令输出默认直接显示到终端(可通过重定向捕获) - 适合无需获取命令输出的场景(如创建文件) #### 2. **使用管道与 `getline`** 捕获命令输出到awk变量: ```awk # 示例:将命令输出读入变量 awk 'BEGIN { cmd = "date +%F"; cmd | getline today; # 将date命令结果存入today close(cmd); # 关闭管道 print "当前日期:", today }' ``` - **关键点**: - `command | getline var` 将命令输出保存到变量`var` - 必须显式`close(command)`关闭管道,避免资源泄漏 - 适合需要处理命令输出的场景(如提取系统信息) --- ### ⚠️ 注意事项 1. **性能影响**:频繁调用Shell命令会显著降低效率,尽量用awk内置函数替代 - 例如用`gsub()`代替`sed`,用算术运算代替`bc` 2. **命令注入风险**:避免直接使用未过滤的外部数据构造命令 ```awk # 危险示例(用户输入未过滤) awk -v user_input="$1" '{ system("rm " user_input) }' ``` 3. **返回值处理**: - `system()`返回退出状态码(非输出内容) - 管道错误需检查`getline`返回值(成功=1, 失败=-1) --- ### 典型应用场景 1. **动态获取系统信息** ```awk awk 'BEGIN { "uname -m" | getline arch; print "系统架构:", arch }' ``` 2. **结合外部工具处理数据** ```awk # 用gzip压缩匹配行 awk '/error/ { print | "gzip > errors.log.gz" } END { close("gzip > errors.log.gz") }' logfile ``` 3. **文件操作** ```awk # 找到大文件后移动 awk 'BEGIN { cmd="find . -size +1G" } while ((cmd | getline file) > 0) { system("mv -v \"" file "\" /backup/") } close(cmd)' ``` --- ### 常见问题解决 **Q:命令输出包含空格时如何处理?** A:用双引号包裹变量,例如: ```awk system("grep \"" $1 "\" /etc/passwd") ``` **Q:如何并发执行命令?** A:在END块中统一处理,避免行处理时阻塞: ```awk { data[$1]++ } END { for (key in data) { print key, data[key] | "sort -k2n > result.txt" } close("sort -k2n > result.txt") } ``` > 参考文档:AWK执行Shell命令时,每次调用`system()`或管道都会创建新进程。在数据量较大时,建议批量处理命令以减少开销[^1]。AWK的管道操作需显式关闭,否则可能导致资源耗尽[^2]。 --- ###
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值