awk将相同键值的字符串拼接一起输出

awk将相同键值的字符串拼接一起输出。每一行中第一列的数据是键,其余列为值。

文本1.txt中的内容是

abc 1 2 3 
abc a1 b1 c1
abc a2 b2 c2
abd a2 b2 c2
hello  hello_value1 hello_value2
hello  hello_value3 hello_value456
awk '{key=$1;$1="";value=$0;sum[key]=sum[key]""value} END{for(i in sum) print i,"=",sum[i]}' 1.txt 

注意,其中字符串拼接的操作是双引号””。

输出结果为

[root@localhost ~]# awk '{key=$1;$1="";value=$0;sum[key]=sum[key]""value} END{for(i in sum) print i,"=",sum[i]}' 2.txt 
hello =  hello_value1 hello_value2 hello_value3 hello_value456
abc =  1 2 3 a1 b1 c1 a2 b2 c2
abd =  a2 b2 c2
[root@localhost ~]#

实践中学习 awk

内置变量

awk 中预先定义好的,内置在 awk 内部的变量。

未分类

记录

1、awk 把每一个换行符结束的行称为一个记录,$0 变量:它指的是整条记录。

# 输出 test.txt 文件中的所有记录 
awk '{print $0}' test.txt 

2、变量 NR:一个计数器,每处理完一条记录,NR 的值就增加 1。

# 输出 test.txt 中的所有记录,并在记录前显示行号
awk '{print NR, $0}' test.txt

记录中的每个单词都称作「域」,默认情况下以空格分隔。awk 可跟踪域的个数,并在内建变量 NF 中保存该值。

# 打印第一和第三个以空格分开的列(域)
awk '{print $1, $3}' test.txt

awk 命令格式

有了上边域和记录的概念,来看看 awk 的命令格式:

awk [options] 'pattern{action}' file

eg1:

# $3 == 0:是 pattern
# print $0:是 action 
awk '$3==0 {print $0}' employee  # 如果第三个域等于 0,则将这行打印

eg2:
下面这个 awk 脚本没有指定 action,但结果和 eg1 一样,没有指定 action 时默认是 {print $0}(打印整行)。

awk '$3 == 0' employee

eg3:

#将结果重定向到文件
awk '$3 == 0' employee > other.txt

域分隔符

1、输入分隔符(field separator),就是 test.txt 中每个列是以什么进行分隔的,awk 默认以空格对每一行进行分隔,分隔符得值保存在内建变量 FS中,可以通过-F命令行选项修改FS的值。

# 指定 : 作为分隔符
awk -F: '{print $1, $3}' test.txt
# 显示指定空格作为分隔符 
awk -F'[ ]' '{print $1, $3}' test.txt
# 指定空格、冒号、tab 作为分隔符
awk -F'[:t ]' '{print $1, $3}' test.txt

# 指定以逗号(,)作为分隔符
awk -F, '{print $1, $2}' separator.txt
# 使用 -v 选项对内建变量设置分隔符,和 awk -F, 效果一样  
awk -v FS=',' '{print $1, $2}' separator.txt

2、输出分隔符(out field separator), 大白话表示就是 awk 在处理完文本后以什么字符作为分隔符将每行输出,默认也是空格,保存在内建变量 OFS 中。

eg1:

# 对内建变量 OFS 赋值
awk -v OFS="->" '{print $1, $2, $3}' other.txt

输出如:

Beth->4.00->0
Dan->3.75->0

eg2:

# 同时指定输入和输出分隔符 
awk -v FS=',' -v OFS='->' '{print $1, $2}' separator.txt

模式-Pattern

根据前面的一些例子,awk 的语法如下:

awk [options] 'Pattern {Action}' file1 file2 
options(选项): 如前面使用过的 -v -F
Action(动作):如 print 

Pattern:也就是条件,一个关系表达式,awk 会逐行处理文本,处理完当前行,然后再处理下一行。如果不指定任何的「条件」,awk 会一行一行的处理完文件的每一行,如果指定了「条件」,只处理满足条件的行。这即 awk 中的模式。

# 将有四列的行打印出来 
awk 'NF == 4 {print $0}' column.txt
# 没有指定模式则是空模式,空模式会匹配文本中每一行,每一行都满足条件 
awk '{print $0}' test.txt

1、正则模式

# 将包含 in 的记录行进行打印 
awk '/in/ {print $0}' pattern.txt

2、行范围模式

eg1:

# 从第一行 到 正则匹配到的第一行 之间的所有行进行打印
awk 'NR == 1, /in/{print $0}' pattern.txt
# /xx/ 没有匹配到第二个模式,打印第一个模式出现的行到文本末尾
awk 'NR == 1, /xx/{print NR, $0}' pattern.txt

看一下结果:

未分类

eg2:

# 将第一行 到 正则匹配到的第一行中的 in 替换为 on(从记录行的左边开始,只替换一次)
# 怎么理解这个 1 呢?
# 这里有两个模式,awk 读出每行记录都会经过这两个模式的判断
# 1 表示这个模式为真,没有指定模式默认的 action 就是打印整行 
awk 'NR == 1,/in/{sub(/in/, "on")} 1' pattern.txt

未分类

再来一波例子估计就懂 eg2 中的用法了:

未分类

在 1 处,模式指定为 1 表示为真,走默认的 action 打印整行。
在 2 处,指定了两个模式,每行记录都会经过这两个模式的处理,然后分别执行模式自己的动作。
在 3 处,有 3 个模式:BEGIN 模式和 END 模式,中间的是空模式(没有指定模式)。BEGIN 模式:处理文本之前先执行的操作;END 模式:处理完所有行后需要执行的操作。

范围模式的第一个模式和第二个模式都以第一次匹配到的行为准。

awk 内建函数

字符串函数

1、sub

sub (regular expression, substitution string):
sub (regular expression, substitution string, target string)

对每一个记录行从左到右第一个匹配到的域进行替换,每一行只会匹配替换一次。

# 将每行第一次出现的域 hello 替换为 hi,每行只会匹配替换一次 
awk '{sub(/hello/, "hi"); print}' test.txt
# 对每行的第一个域进行替换 
awk '{sub(/hello/, "hi", $1); print}' test.txt

linux 用awk gsub将一行变成多行

原数据

103153926#1180545867#1337681140#1358188028#1445076068#1527059220#1625614569#1890561581#19584762#2214121812#2342720441#2500121842#25326345#2640225664#409811166#461094177#596719064#611522202#741345319#759125996#98001626#997750230
1420151716#184147046#2785699737#344498087

目标结果

103153926   1
1180545867  1
1337681140  1
1358188028  1
1445076068  1
1527059220  1
1625614569  1
1890561581  1
19584762    1
2214121812  1
2342720441  1
2500121842  1
25326345    1
2640225664  1
409811166   1
461094177   1
596719064   1
611522202   1
741345319   1
759125996   1
98001626    1
997750230   1
1420151716  2
184147046   2
2785699737  2
344498087   2

对数据进行修改,在最后加一个#

103153926#1180545867#1337681140#1358188028#1445076068#1527059220#1625614569#1890561581#19584762#2214121812#2342720441#2500121842#25326345#2640225664#409811166#461094177#596719064#611522202#741345319#759125996#98001626#997750230#
1420151716#184147046#2785699737#344498087#
awk 'BEGIN{i=1}{gsub(/#/,"t"i"n");i++;print}' uids|sed '/^$/d' > uids_seg

Linux使用awk文本处理工具实现多行合并的实例

在Linux系统中使用awk文本处理工具,有时需要将多行合并,这就需要用到awknext语句了,下面小编就给大家介绍下Linux中使用awk实现多行合并的方法,需要的朋友可以来了解下。

awknext语句使用:在循环逐行匹配,如果遇到next,就会跳过当前行,直接忽略下面语句。而进行下一行匹配。

代码如下:

text.txt 内容是:

a

b

c

d

e

  

[chengmo@centos5 shell]$ awk ‘NR%2==1{next}{print NR,$0;}’ text.txt

2 b

4 d

当记录行号除以2余 1,就跳过当前行。下面的print NR,$0也不会执行。 下一行开始,程序有开始判断NR%2 值。这个时候记录行号是:2 ,就会执行下面语句块:‘print NR,$0’

awk next使用实例:

代码如下:

要求:

文件:text.txt 格式:

web01[192.168.2.100]

httpd ok

tomcat ok

sendmail ok

web02[192.168.2.101]

httpd ok

postfix ok

web03[192.168.2.102]

mysqld ok

httpd ok

  
需要通过awk将输出格式变成:

web01[192.168.2.100]: httpd ok

web01[192.168.2.100]: tomcat ok

web01[192.168.2.100]: sendmail ok

web02[192.168.2.101]: httpd ok

web02[192.168.2.101]: postfix ok

web03[192.168.2.102]: mysqld ok

web03[192.168.2.102]: httpd ok

分析:

分析发现需要将包含有“web”行进行跳过,然后需要将内容与下面行合并为一行。

[chengmo@centos5 shell]$ awk ‘/^web/{T=$0;next;}{print T“:t”$0;}’ test.txt

web01[192.168.2.100]: httpd ok

web01[192.168.2.100]: tomcat ok

web01[192.168.2.100]: sendmail ok

web02[192.168.2.101]: httpd ok

web02[192.168.2.101]: postfix ok

web03[192.168.2.102]: mysqld ok

web03[192.168.2.102]: httpd ok

  
上面就是Linux使用awk进行多行合并的方法介绍了,在文中使用next语句是十分方便的,在循环匹配中,如果遇到next,就会自动跳过,从而实现多行合并。

Linux命令之grep/sed/awk等行转列

行转列

样例文件如下

cat file.txt
a b c
d e f
g h i

1、cat file.txt |xargs -n1

2、xargs -n 1 < file.txt

3、tr " " "n" < file.txt

4、sed 's/ /n/g' file.txt
此命令在Linux上执行正常,在Mac上执行无效,原因是因为Mac上的sed是BSD版本,Linux上的是Gnu版本,在Mac上需要使用sed -e ‘s/ /’$’n/g’ file.txt

5、sed 's/ /n/g' file.txt 同上

6、awk '{for(i=1;i<=NF;i++)print $i}' file.txt

7、awk -vOFS="n" '$1=$1' file.txt Linux上正常,Mac上无效

8、awk '{OFS=RS}NF=NF' file.txt Linux上正常,Mac上无效

列转行

样例文件如下

cat file.txt
a
b
c
d
e
f

1、cat file.txt | tr "n" ","

Linux三大利器grep、sed、awk

一. grep和正则表达式

grep

grep(global search regular expression(RE) and print out the line,全面搜索正则表达式并把行打印出来)是一种强大的文本搜索工具,它能使用正则表达式搜索文本,并把匹配的行打印出来。

选项

-d<进行动作> 当指定要查找的是目录而非文件时,必须使用这项参数,否则grep命令将回报信息并停止动作。

-i 忽略字符大小写的差别。

-l 列出文件内容符合指定的范本样式的文件名称。

-n 在显示符合范本样式的那一列之前,标示出该列的编号。

-R/-r 此参数的效果和指定“-d recurse”参数相同。

-v 反转查找。

1、-r递归查找

root@siguorui-OptiPlex-7010:/home/xhprof/trunk# grep -r XHProfRuns_Default *
examples/sample.php:$xhprof_runs = new XHProfRuns_Default();
xhprof_html/callgraph.php:$xhprof_runs_impl = new XHProfRuns_Default();
xhprof_html/typeahead.php:$xhprof_runs_impl = new XHProfRuns_Default();

2、-I的使用,显示文件名称

root@siguorui-OptiPlex-7010:~# grep -I root abc.txt 123.txt passwd 
passwd:root:x:0:0:root:/root:/bin/bash

3、-n

root@siguorui-OptiPlex-7010:~# grep -n 'root' passwd 
1:root:x:0:0:root:/root:/bin/bash

正则表达式

1、正则表达式单字符

  • 特定字符
    • grep ‘a’ passwd
  • 范围内字符
    • grep ‘[a-z]’ passwd
    • grep ‘[A-Za-z0-9]’ passwd
    • grep ‘[^0-9]’ passwd 取反,除去数字之外的字符
  • 任意字符
    • grep ‘.’ passwd

但是在grep ‘[.]’中,.只是代表点这样的字符,注意区别。如果要使用.的本意,采用.的方式

  • 以上三种组合

2、正则表达式其他符号

  • 边界字符 头尾字符
    • ^字符,头字符,放在一串字母前边,代表以此开头。grep ‘^root’ passwd
    • $符号,如false$,代表以false字符结束
    • ^$ 代表空行,grep ‘^$’ passwd
  • 元字符
    • w:匹配任何字类字符,包括下划线。相当于([A-Za-z0-9_])
    • W:大写的W,匹配任何非字类字符。相当于([^A-Za-z0-9_])
    • b 代表单词分隔。如,grep ‘bxb’ passwd,可以将单个前后分隔的x字符选出来,但不会选择单词中出现的x
  • 正则表达式字符组合
    • 重复
* : 零次或多次匹配前面的字符或子表达式。例子:grep 'se*' test.txt
+ : 一次或多次匹配前面的字符或表达式.例子:grep 'se+' test.txt.注意这里加号前面要加反斜杠
? : 零次或一次匹配前面的字符或表达式.如:grep 'se?' test.txt.注意?前面也要加反斜杠
括号的使用 :grep '(se)*' test.txt。注意括号前面要加反斜杠
指定重复次数 : grep '[0-9]{2,3}' passwd 

二.sed 行编辑器

sed是一种流编辑器,它是文本处理中非常重要的工具,能够完美的配合正则表达式使用,功能不同凡响。处理时,把当前处理的行存储在临时缓冲区中,称为“模式空间”(pattern space),接着用sed命令处理缓冲区中的内容,处理完成后,把缓冲区的内容送往屏幕。接着处理下一行,这样不断重复,直到文件末尾。文件内容并没有 改变,除非你使用重定向存储输出。Sed主要用来自动编辑一个或多个文件;简化对文件的反复操作;编写转换程序等。

命令格式

sed [options] 'command' file(s)
sed [options] -f scriptfile file(s)

options常用选项

-e<script>或--expression=<script>:以选项中的指定的script来处理输入的文本文件;

-n或--quiet或——silent:仅显示script处理后的结果;

command常用

a 在当前行下面插入文本。
i 在当前行上面插入文本。
c 把选定的行改为新的文本。
d 删除,删除选择的行。
n 读取下一个输入行,用下一个命令处理新的行而不是用第一个命令。
s 替换指定字符
p 打印模板块的行。
q 退出Sed。
r file 从file中读行。
w file 写并追加模板块到file末尾。  

1、p 打印相关的行

nl passwd|sed -n '10p' //打印第10行内容
sed -n 'p' passwd
sed -n '/root/p' passwd //正则匹配打印
nl passwd|sed -n '10,20p' //打印第10行到20行
nl passwd|sed -n '/news/,/nobody/p' //用正则来指定一个行的范围
nl passwd|sed -n '10,20!p'  //不选择10到20行,!代表取反
nl passwd|sed -n '1~2p' //间隔行,会输出1,3,5....行

注意,这里一定要加上-n选项,否则每条数据会显示同样的2行。并且无关的其他内容也会显示出来

2、a 在行后面增加内容

root@siguorui-OptiPlex-7010:~# nl passwd|sed '2a **************'
     1    root:x:0:0:root:/root:/bin/bash
     2    daemon:x:1:1:daemon:/usr/sbin:/usr/sbin/nologin
**************
     3    bin:x:2:2:bin:/bin:/usr/sbin/nologin

nl passwd|sed '1,2a **************' //在范围内的每一行后面都插入

3、i在行前面插入

root@siguorui-OptiPlex-7010:~# nl passwd|sed '1,2i **************'
**************
     1    root:x:0:0:root:/root:/bin/bash
**************
     2    daemon:x:1:1:daemon:/usr/sbin:/usr/sbin/nologin

4、c把选定的行改为新的文本

root@siguorui-OptiPlex-7010:~# nl passwd|sed '1c abcd'
abcd
     2    daemon:x:1:1:daemon:/usr/sbin:/usr/sbin/nologin

 //与a,i不同的时,如果这里是一个行的范围则是把这个范围内容替换为当前内容   
root@siguorui-OptiPlex-7010:~# nl passwd|sed '1,3c abcd'
abcd
     4    sys:x:3:3:sys:/dev:/usr/sbin/nologin

5、d删除行

root@siguorui-OptiPlex-7010:~# nl passwd | sed '/root/d'
     2    daemon:x:1:1:daemon:/usr/sbin:/usr/sbin/nologin
     3    bin:x:2:2:bin:/bin:/usr/sbin/nologin

应用案例

在文件的末尾插入2行
nl passwd | sed '$a     abcd n    linux'

    49    memcache:x:126:132:Memcached,,,:/nonexistent:/bin/false
    50    postfix:x:127:133::/var/spool/postfix:/bin/false
    51    mongodb:x:128:65534::/var/lib/mongodb:/bin/false
    abcd 
    linux


    删除文件中的空行,^$直接相连代表空行
    nl passwd | sed '/^$/d'

6、s替换命令

sed 's/false/true/' passwd 
输出:
...
sphinxsearch:x:124:131::/home/sphinxsearch:/bin/true
sshd:x:125:65534::/var/run/sshd:/usr/sbin/nologin
memcache:x:126:132:Memcached,,,:/nonexistent:/bin/true
postfix:x:127:133::/var/spool/postfix:/bin/true

sed 's/:/%/g' passwd  //加g全局替换
输出:
sphinxsearch%x%124%131%%/home/sphinxsearch%/bin/false
sshd%x%125%65534%%/var/run/sshd%/usr/sbin/nologin
memcache%x%126%132%Memcached,,,%/nonexistent%/bin/false
postfix%x%127%133%%/var/spool/postfix%/bin/false

过滤ifconfig中的ip

eno1      Link encap:以太网  硬件地址 f8:b1:56:c5:e7:44  
          inet 地址:172.19.5.175  广播:172.19.5.255  掩码:255.255.255.0
          inet6 地址: fe80::c422:e82d:ad66:7a92/64 Scope:Link
          UP BROADCAST RUNNING MULTICAST  MTU:1500  跃点数:1
          接收数据包:35171885 错误:53864 丢弃:0 过载:0 帧数:29047
          发送数据包:25049325 错误:0 丢弃:0 过载:0 载波:0
          碰撞:0 发送队列长度:1000 
          接收字节:8124495140 (8.1 GB)  发送字节:4549284803 (4.5 GB)
          中断:20 Memory:f7f00000-f7f20000 

 ifconfig eno1 | sed -n '/inet /p'|sed 's/inet.*地址://'|sed 's/广播.*$//'

 输出:
 172.19.5.175

高级操作命令

1、多个sed命令,用{}包住,‘;’隔开

删除44-48行内容,然后将false替换为true
nl passwd|sed '{44,48d;s/false/true/}'

    41    statd:x:121:65534::/var/lib/nfs:/bin/true
    42    mysql:x:1001:1001::/home/mysql:/sbin/nologin
    43    www:x:1002:1002::/home/www:/sbin/nologin
    49    memcache:x:126:132:Memcached,,,:/nonexistent:/bin/true
    50    postfix:x:127:133::/var/spool/postfix:/bin/true
    51    mongodb:x:128:65534::/var/lib/mongodb:/bin/true

2、n 读取下一个输入行

//n的用法
root@siguorui-OptiPlex-7010:~# nl passwd|sed -n '{p;n}'
     1    root:x:0:0:root:/root:/bin/bash
     3    bin:x:2:2:bin:/bin:/usr/sbin/nologin
     5    sync:x:4:65534:sync:/bin:/bin/sync
     7    man:x:6:12:man:/var/cache/man:/usr/sbin/nologin

提示: nl passwd|sed -n '{1~2p}'  前面讲到的,~也可以实现同样的效果

3、&替换固定字符串,&代表前面匹配到的字符

//姓名和后面的内容加空格隔开
root@siguorui-OptiPlex-7010:~# sed 's/^[a-z_]+/&     /' passwd
root     :x:0:0:root:/root:/bin/bash
daemon     :x:1:1:daemon:/usr/sbin:/usr/sbin/nologin
bin     :x:2:2:bin:/bin:/usr/sbin/nologin


//用户名的首字母转换为大写
//元字符u l(对首字母大小写转换) U L(对一串字符大小写转换),转换为大写小写字符

//小写u,替换用户名首字母
root@siguorui-OptiPlex-7010:~# sed 's/^[a-z_]+/u&/' passwd
Root:x:0:0:root:/root:/bin/bash
Daemon:x:1:1:daemon:/usr/sbin:/usr/sbin/nologin
Bin:x:2:2:bin:/bin:/usr/sbin/nologin

//大写U,用户名全部替换为大写
root@siguorui-OptiPlex-7010:~# sed 's/^[a-z_]+/U&/' passwd
ROOT:x:0:0:root:/root:/bin/bash
DAEMON:x:1:1:daemon:/usr/sbin:/usr/sbin/nologin
BIN:x:2:2:bin:/bin:/usr/sbin/nologin

4、()的使用

//从passwd文件中,提取出username,uid,gid.  1,2,3代表前面()匹配到字符
root@siguorui-OptiPlex-7010:~# sed 's/(^[a-z_-]+):x:([0-9]+):([0-9]+):.*$/USER:1    UID:2   GID:3/' passwd
USER:root    UID:0   GID:0
USER:daemon    UID:1   GID:1
USER:bin    UID:2   GID:2
USER:sys    UID:3   GID:3
USER:sync    UID:4   GID:65534

5、-r复制指定文件插入到匹配行。-w复制匹配行拷贝到指定文件

//123.txt文件中有3行,全是数字。abc.txt文件中有3行,全是字母
//下面命令的实现结果,读取123.txt的内容,复制到匹配的abc.txt文件的第一行,文件内容均不改变
root@siguorui-OptiPlex-7010:~# sed '1r 123.txt' abc.txt 
qwefadssa
1232323223
32343434
23333
trwrda
asdfasdf

//下面命令的实现结果,匹配abc.txt文件的第二行,写入到123.txt文件中。123.txt文件会发生变化,abc.txt文件内容不变
root@siguorui-OptiPlex-7010:~# sed '2w 123.txt' abc.txt 
qwefadssa
trwrda
asdfasdf
root@siguorui-OptiPlex-7010:~# cat 123.txt 
trwrda


//总结
sed '2w或2r 文件A' 文件B
匹配的文件都是针对文件B来说的,读或写都是针对文件A来说的

6、q找到指定结果后就提前退出

root@siguorui-OptiPlex-7010:~# nl passwd |sed '2q'
     1    root:x:0:0:root:/root:/bin/bash
     2    daemon:x:1:1:daemon:/usr/sbin:/usr/sbin/nologin
root@siguorui-OptiPlex-7010:~# nl passwd |sed '/root/q'
     1    root:x:0:0:root:/root:/bin/bash
root@siguorui-OptiPlex-7010:~# 

三、awk

AWK是一种处理文本文件的语言,是一个强大的文本分析工具。特点是处理灵活,功能强大。可实现统计、制表以及其他功能。

之所以叫AWK是因为其取了三位创始人 Alfred Aho,Peter Weinberger, 和 Brian Kernighan 的Family Name的首字符。

格式

命令行格式
awk [options] 'command' file(s)

脚本格式
awk -f awk-script-file file(s)

命令形式:

awk [-F|-f|-v] ‘BEGIN{} //{command1; command2} END{}’ file
  • [-F|-f|-v] 大参数,-F指定分隔符,-f调用脚本,-v定义变量 var=value

' ' 引用代码块

  • BEGIN 初始化代码块,在对每一行进行处理之前,初始化代码,主要是引用全局变量,设置FS分隔符
  • // 匹配代码块,可以是字符串或正则表达式
  • {} 命令代码块,包含一条或多条命令
  • ; 多条命令使用分号分隔
  • END 结尾代码块,在对每一行进行处理之后再执行的代码块,主要是进行最终计算或输出结尾摘要信息

常用内置参数

  • $0,$1,$2… 表示整个当前行
  • $1 每行第一个字段
  • NF 字段数量变量
  • NR 每行的记录号,多文件记录递增
  • FILENAME 文件名

1、常用内置参数,$1,$2….。通过分隔符指定,按顺序依次为$1,$2…。默认分隔符为空格

awk -F ':' '{print "USERNAE:"$1"t""UID:"$3}' passwd

2、NR,NF,FILENAME

awk -F ':' '{print "Line:"NR,"Col:"NF,"USER:"$1}' passwd

3、运用printf指定格式来打印

awk -F ':' '{printf("Line:%3s Col:%s User:%sn",NR,NF,$1)}' passwd

root@siguorui-OptiPlex-7010:~# awk -F ':' '{printf("Line:%3s Col:%s User:%sn",NR,NF,$1)}' passwd
Line:  1 Col:7 User:root
Line:  2 Col:7 User:daemon
Line:  3 Col:7 User:bin
Line:  4 Col:7 User:sys
...

4、使用if

awk -F ':' '{if ($3>100) printf("Line:%3s Col:%s User:%sn",NR,NF,$1)}' passwd

5、正则和命令结合使用

awk -F ':' '/root/{print $1}' passwd

root@siguorui-OptiPlex-7010:~# awk -F ':' '/root/{print $1}' passwd
root

6、使用BEGIN和END来制表

awk -F ':' 'BEGIN{print "line col user"}{print NR" |"NF" |"$1}END{print "----------------"FILENAME}' passwd

7、使用BEGIN和END来统计一个目录下文件总计大小

ls -l|awk 'BEGIN{size=0}{size+=$5}END{print " size is "size/1024/1024"M"}'

8、统计passwd中不为空的行数。$1!~,~代表匹配后面的正则,!~代表不匹配。/^$/正则匹配空行

awk -F ':' 'BEGIN{count=0}$1!~/^$/{count++}END{print " count ="count}' passwd

9、统计结果放到数组中,并打印输出

awk -F ':' 'BEGIN{count=0}{if ($3>100) name[count++]=$1}END{for(i=0;i<count;i++) print i,name[i]}' passwd 

root@siguorui-OptiPlex-7010:~# awk -F ':' 'BEGIN{count=0}{if ($3>100) name[count++]=$1}END{for(i=0;i<count;i++) print i,name[i]}' passwd 
0 nobody
1 systemd-network
2 systemd-resolve
3 systemd-bus-proxy
4 syslog

awk,分隔符编辑截取字符

输入字段分隔符:awk的内置变量FS中保存了输入字段分隔符的值。使用FS的默认值时,awk用空格或制表符来分隔字段,并且删除各字段前多余的空格或制表符。可以通过在BEGIN语句中或命令行上赋值来改变FS的值。接下来我们就要在命令行上给FS指定一个新的值。在命令行上改变FS的值需要使用-F选项,后面指定代表新分隔符的字符。

从命令行改变字段分隔符:范例中演示了如何使用-F选项在命令行中改变输入字段分隔符。

$ cat employees
Tom Jones:4424:5/12/66:543354
Mary Adams:5346:11/4/63:28765
Sally Chang:1654:7/22/54:650000
Billy Black:1683:9/23/44:336500
$ awk -F: '/Tom Jones/{print $1,$2}' employees
Tom Jones 4424

说明:-F选项用来在命令行重新设置输入字段分隔符的值。当冒号紧跟在-F选项的后面时,awk 就会在文件中查找冒号,用以分隔字段。

使用多个字段分隔符:你可以指定多个输入字段分隔符。如果有多个字符被用于字段分隔符FS,则FS对应是一个正则表达式字符串,并且被括在方括号中。下面的范例中,字段分隔符是空格、冒号或制表符。

$ awk -F'[ :t]' '{print $1,$2,$3}' employees
Tom Jones 4424
Mary Adams 5346
Sally Chang 1654
Billy Black 1683

说明:-F选项后面跟了一个位于方括号中的正则表达式,当遇到空格、冒号或制表符时,awk会把它当成字段分隔符。这个表达式两头加了引号,这样就不会被shell当成自己的元字符来解释(注意, shell使用方括号来进行文件名扩展)。

输出字段分隔符:默认的输出字段分隔符是单个空格,被保存于awk的内置变量OFS中。此前的所有例子中,我们都是用print语句把输出打印到屏幕上。因此,无论OFS如何设置,print语句中用于分隔字段的逗号,在输出时都被转换成OFS的值。如果用OFS的默认值,则$1和$2之间的逗号会被转换为单个空格,print函数打印这两个字段时会在它们之间加一个空格。

如果没有用逗号来分隔字段,则输出结果中的字段将堆在一起。另外,OFS的值可以改变。

参考资料:http://www.linuxawk.com/jiaocheng/116.html

如何在shell脚本文件中获取awk的值, 存于变量中?——来看看shell中eval的用法

遇到了, 折腾了一会儿, 所以记一下:

错误1:

#!/bin/bash
a="hello world"
b=$a | awk '{print $2}'
echo $b

错误2:

#!/bin/bash
a="hello world"
b=echo $a | awk '{print $2}'
echo $b

正确姿势:

#!/bin/bash
a="hello world"
b=$(echo $a | awk '{print $2}')
echo $b

其中$(xxx)表示执行xxx命令后的结果

如果要把多个值保存在多个变量中, 怎么搞起呢? 可以用eval, eval的作用是什么呢? 看个例子, 就明白了:

#!/bin/bash
hello=good
eval "hello=world"
echo $hello

结果是world, 实际上就是把”hello=world”中的内容理解为一个语句,而不是一个串。

来看看:

#!/bin/bash
a="hello world"
b=$(echo $a | awk '{printf("var1=%s; var2=%s;",$1,$2)}')
echo $b
echo ${var1}
echo ${var2}
eval $b
echo ${var1}
echo ${var2}

结果:

ubuntu@VM-0-15-ubuntu:~/taoge/shell$ ./a.sh 
var1=hello; var2=world;


hello
world

分析一下,$b的结果是var1=hello; var2=world;

此时, “var1=hello; var2=world;”仅仅是一个串, 而已, 而不是语句。 所以此时var1和var2都为空, 因为这两个变量根本就没有出现过。

利用eval, 可以让”var1=hello; var2=world; “变成语句, 所以var1此时的值就是hello, var2的值是world

当然, 也可以一步到位:

#!/bin/bash
a="hello world"
eval $(echo $a | awk '{printf("var1=%s; var2=%s;",$1,$2)}')
echo ${var1}
echo ${var2}

不多说。

awk 非排序和 sort uniq 排序处理文件的交叉并集合

日常工作中,经常会有文件处理。要玩的转命令,记得熟,速度快。

如题,指定列去重就是一个频繁遇到的问题。脑子里一直深深烙着 sort | uniq 这种用法,直到前两天处理一个2700万+行的大文件,才意识到老朋友玩不转了。。

对千万行的大文件执行 sort | uniq,等的花儿谢了也白扯… 显然,咱手头的文件列也不是带索引的(又不是数据库),那执行大数据排序 sort 操作当然非常慢了。 更快的方法,当然要考虑怎么不用排序去重。

熟悉编程的同学,肯定立马想到用对象数组啊,key=>value这种。因为key是唯一的(文件指定的去重列做key),这样不就是去重了么~

嗯啊,下边介绍的这种机智的命令就是应用的这个想法。 大杀器: !arr[key]++

简洁的表达,反而容易迷糊住一般人。简单说下就明白了。

  • arr即是定义一个数组
  • key即是文件的指定列
  • 运算符优先级:a++ 优先于 !

再啰嗦一句就捅破了.. 按照文件行处理的话:第一次的key,arr[key]++ 表达式返回0,加个!,表达式结果为true。此时arr[key]==1了,后续行再遇到这个相同key, !arr[key]++ 这个表达式自然就是false了。

明白这个表达式了,那命令也出来了,利用下awk。比如指定列是第一列

awk '!arr[$1]++' filename

再说下awk是’条件{do sth}'!arr[$1]++ 是条件,命令默认打印全部{print $0}

这种去重方式,显然不需要预先排序,速度自然快的飞起哈哈。不过速度是快了,牺牲了什么呢?他疯狂的吃内存,因为它要存下来整个文件列的数组。很大的文件情况下就要考虑内存承受了。

我的开发机,在搞这2700万+的文件就吃不消了,进程过一会被杀死了。执行过程中,top一下会发现这个命令内存占比蹭蹭往上涨。
但是 sort | uniq 这种方式,利用内存上会更好,不会挂掉。

因为开发机内存不够的原因,为了展示下这两个方法的数据对比。我按每900万行拆了下文件。(实际文件是两列,指定第一列去重)

split -l 9000000 userimei.log

利用分割后的文件 xaa
对比:

#!/bin/bash

time awk '{print $1}' xaa | sort | uniq > zaa.log

time awk '!arr[$1]++{print $1}' xaa > zbb.log

time 是查看命令执行时间。

1、sort | uniq 方法耗时 92秒

real    1m32.192s
user    1m27.186s
sys     0m1.529s

2、 !arr[key]++ 方法耗时 24秒

real    0m23.902s
user    0m20.660s
sys     0m1.602s

查看去重后文件行数是否一致:

wc -l zaa.log

wc -l zbb.log

都是 8942497 行,符合预期。

awk !arr[key]++ 这种用法,手痒简单写个php处理。

<?php

ini_set('memory_limit', '1024M');

$filename = 'xaa';

$file = fopen($filename, "r");

// 按照awk一行一行读取。大文件处理方式
while(!feof($file)){
    $lineArr = explode("t", fgets($file));// 本文件两列
    $key = $lineArr[0];// 指定第一列

    if(!isset($arr[$key])){
        $arr[$key] = null;
    }
}

fclose($file);

file_put_contents('zcc.log', implode("n", array_keys($arr)));

是不是真正掌握了大杀器呢?

举一反三: 比如交集、并集、差集这些集合运算呢?
a.log 和 b.log 是各自去重了的A集合和B集合(集合性质之一互异性)。

交集

1. sort | uniq 方法 -d 参数只输出重复行
    sort a.log b.log | uniq -d

2. arr[$1]++ 方法该怎么办呢?真正掌握了的话,就知道表达式等于1输出就ok了
    awk 'a[$1]++==1' a.log b.log

仍然用了前面900万行的文件a,和从中选取两行做b;实际对比:

# 方法1
real    0m51.684s

# 方法2
real    0m18.216s

并集

1. sort | uniq 方法,直接就是去重
    sort a.log b.log | uniq

2. arr[$1]++  这个就是去重而已。表达式为0 或者 非!就ok了
    awk '!a[$1]++' a.log b.log

仍然用了前面900万行的文件a,和从中选取两行做b;实际对比:

# 方法1
real    0m52.436s

# 方法2
real    0m19.080s

差集

差集需要注意顺序,是A-B or B-A

A-B 举例:

# sort | uniq 方法,-u 参数只输出出现一次的记录。一个技巧,使用两次B集合完成 A-B 差集运算
1.  sort a.log b.log b.log | uniq -u

# arr[$1] 杀器,设计这种方法花费了我好长时间。。也写出来几个,但每写一个总感觉有更快的,最后选择了下面这个方法。即便这种写法的速度也大大优越于排序的方法。
2.  awk 'NR<=FNR{a[$1]}NR>FNR{delete a[$1]}END{for(k in a) print k}' a.log b.log

A a.log 是3条数据;B b.log是300万条数据。
开始测试:

A-B: (小文件-大文件)
# 方法1
real    0m52.710s

# 方法2
real    0m1.891s

这效果杠杠滴把哈哈~

----------------------

B-A: (大文件-小文件)
# 方法1
real    0m22.575s   

# 方法2
real    0m7.930s 

----------------------

当小文件-大文件的差集时,第二种方法远远优于第一种排序方法。

同时也发现,数组这种方法,在处理上,时间更多是耗费在对数组赋值上(赋值操作是前一个文件)。

再简单介绍下差集中的 awk 命令:
NR 表示处理当前的行数,不管awk一个文件还是两个文件,NR始终都是 +1 递增的。
FNR 表示当前文件处理的行数,类似NR的是,当处理各自文件时是 +1 递增的;而区别与NR,FNR处理新文件时,是从 1 开始了。

这样语句就好解释了,当NR在第一个文件a.log时,始终赋值列到数组a里。当NR在第二个文件b.log时,始终删除a数组列。哈哈,其实这不就是差集的定义么?在 A 集合,但又不在 B 集合。

==============================================================================================================
Okay了~ 写篇博客真耗时啊… 写完是真开心!

当遇到大文件不需要排序的去重 或者 交差并集合运算时候,利用 !arr[$1]++ 起来吧~ 会给你节省时间的哈哈