grep
grep [options] regex [file...]
regex 是指一个正则表达式
- -i : 忽略大小写。不会区分大小写字符。也可用–ignore-case 来指定。
- -v : 不匹配。通常,grep 程序会打印包含匹配项的文本行。这个选项导致 grep 程序只会打印不包含匹配项的文本行。也可用–invert-match 来指定。
- -c : 打印匹配的数量(或者是不匹配的数目,若指定了-v 选项),而不是文本行本身。 也可用–count 选项来指定。
- -l : 打印包含匹配项的文件名,而不是文本行本身,也可用–files-with-matches 选项来指定。
- -L : 相似于-l 选项,但是只是打印不包含匹配项的文件名。也可用–files-without-match 来指定。
- -n : 在每个匹配行之前打印出其位于文件中的相应行号。也可用–line-number 选项来指定。
- -h : 应用于多文件搜索,不输出文件名。也可用–no-filename 选项来指定。
正则表达式regex:
- . 匹配任意字符,例如 .zip 匹配四个字符,任意一个字符和zip,注意 zip 三个字符不能被匹配,因为 . 一个任意字符必须有。即一个任意字符带上zip三个字符,一共四个字符才行
- [bg]zip,匹配 bzip 或者 gzip;[^bg]zip,匹配不是 bzip 或 gzip,[^bg] 也必须占一个字符,zip 不能被匹配因为前面没有一个非 bg 的字符,所以一共匹配四个字符。一个否定的字符集仍然在给定位置要求一个字符, 但是这个字符必须不是否定字符集的成员
BRE 可辨认的元字符:
^ $ [ ] . *
ERE 添加了以下元字符
( ) { } + ? |
在 BRE 中 字符“(”,“)”,“{”,和 “}”用反斜杠转义后,然而在 ERE 中,在任意元字符之前加上反斜杠会导致其被看作是一个文本字符。
grep 正则表达式默认使用 BRE,要想使用扩展正则表达式,可以用 egrep 命令。也可以给 grep 命令加个 -E 的选项。
为了把 alternation 和其它正则表达式元素结合起来,我们可以使用()来分离 alternation。
# 匹配以 bz 或 gz 或 zip 开头的字符串行
grep -Eh '^(bz|gz|zip)' dirlist*.txt
# 匹配以 以bz开头 或包含 gz 或 包含zip 字符串行
grep -Eh '^bz|gz|zip' dirlist*txt
sort
排序,可按照文本行中的某个列字段排序,sort 命令把空白字符(空格和制表符)当做是列的分割符,也可指定按数值排序,或者指定按某个列字段的一部分进行排序,还可通过 -t 选项指定分隔符。
具体选项:
- -b :(–ignore-leading-blanks)默认情况下,对整行进行排序,从每行的第一个字符开始。这个选项导致 sort 程序忽略 每行开头的空格,从第一个非空白字符开始排序。
- -f : (–ignore-case)让排序不区分大小写。
- -n : (–numeric-sort)基于字符串的数值来排序。使用此选项允许根据数字值执行排序,而不是字母值。
- -r : (–reverse)按相反顺序排序。结果按照降序排列,而不是升序。
- -k : (–key=field1[,field2])对从 field1到 field2之间的字符排序,而不是整个文本行。看下面的讨论。
- -m : (–merge)把每个参数看作是一个预先排好序的文件。把多个文件合并成一个排好序的文件,而没有执行额外的排序。
- -o : (–output=file)把排好序的输出结果发送到文件,而不是标准输出。
- -t : (–field-separator=char)定义域分隔字符。默认情况下,域由空格或制表符分隔。
#以 : 为分隔符 按第7个字段排序
sort -t ':' -k 7 /etc/passwd
#按第三个字段的第7个字符到第三个字段结尾的字符的数字进行逆向排序 忽略开头的空格
sort -k3.7nbr ./distros.txt
uniq
与 sort 程序相比,这个 uniq 程序是个轻量级程序。uniq 执行一个看似琐碎的行为。当给定一个 排好序的文件(包括标准输出),uniq 会删除任意重复行,并且把结果发送到标准输出。 它常常和 sort 程序一块使用,来清理重复的输出。
选项如下:
- -c : 输出所有的重复行,并且每行开头显示重复的次数。
- -d : 只输出重复行,而不是特有的文本行。
- -f n : 忽略每行开头的 n 个字段,字段之间由空格分隔,正如 sort 程序中的空格分隔符;然而, 不同于 sort 程序,uniq 没有选项来设置备用的字段分隔符。
- -i : 在比较文本行的时候忽略大小写。
- -s n : 跳过(忽略)每行开头的 n 个字符。
- -u : 只输出独有的文本行。这是默认的。
对于一个文件 foo.txt 里面有如下行:
cat foo.txt
a
b
c
d
a
b
c
d
#执行 uniq foo.txt ,将原样输出
#执行 sort foo.txt | uniq 将排序后去重输出
这是因为 uniq 只会删除相邻的重复行。
cut
这个 cut 程序被用来从文本行中抽取文本,并把其输出到标准输出。它能够接受多个文件参数或者 标准输入。
选项如下:
- -c : 从文本行中抽取由 char_list 定义的文本。这个列表可能由一个或多个逗号 分隔开的数值区间组成。
- -f : 从文本行中抽取一个或多个由 field_list 定义的字段。这个列表可能 包括一个或多个字段,或由逗号分隔开的字段区间.
- -d : 当指定-f 选项之后,使用 delim_char 做为字段分隔符。默认情况下, 字段之间必须由单个 tab 字符分隔开。
- –complement : 抽取整个文本行,除了那些由-c 和/或-f 选项指定的文本。
正如我们所看到的,cut 程序抽取文本的方式相当不灵活。cut 命令最好用来从其它程序产生的文件中 抽取文本,而不是从人们直接输入的文本中抽取。
从 /etc/passwd 中抽取用户名:
/etc/passwd 中以 : 做为分割
cut -d ":" -f 1 /etc/passwd
sed
sed 命令格式:
sed [选项] 命令 文件(可以一个也可以多个)
sed [options] 'command' file(s)
选项
- -e
替换操作:s命令
替换文本中的字符串:将 front 替换成 back,结果输出 back
echo 'front' | sed 's/front/back/'
-n选项和p命令一起使用表示只打印那些发生替换的行:
sed -n 's/test/TEST/p' file
直接编辑文件选项-i,会匹配file文件中每一行中的所有book替换为books
sed -i 's/book/books/g' file
sed 替换标记:
- g 表示行内全面替换。
- p 表示打印行。
- w 表示把行写入一个文件。
- x 表示互换模板块中的文本和缓冲区中的文本。
- y 表示把一个字符翻译为另外的字符(但是不用于正则表达式)
- \1 子串匹配标记
- & 已匹配字符串标记
sed 命令
- s/regexp/replacement/ 只要找到一个 regexp 匹配项,就替换为 replacement 的内容。 replacement 可能包括特殊字符 &,其等价于由 regexp 匹配的文本。另外, replacement 可能包含序列 \1到 \9,其是 regexp 中相对应的子表达式的内容。更多信息,查看 下面 back references 部分的讨论。在 replacement 末尾的斜杠之后,可以指定一个 可选的标志,来修改 s 命令的行为。
cat distros.txt
SUSE 10.2 12/07/2006
Fedora 10 11/25/2008
SUSE 11.0 06/19/2008
Ubuntu 8.04 04/24/2008
Fedora 8 11/08/2007
# 打印 [1, 4] 行
执行命令:sed -n '1,4p' distros.txt
SUSE 10.2 12/07/2006
Fedora 10 11/25/2008
SUSE 11.0 06/19/2008
Ubuntu 8.04 04/24/2008
# 打印被 SUSE 匹配到的行
执行命令:sed -n '/SUSE/p' distros.txt
SUSE 10.2 12/07/2006
SUSE 11.0 06/19/2008
# 打印未被 SUSE 匹配到的行。
执行命令:sed -n '/SUSE/!p' distros.txt
Fedora 10 11/25/2008
Ubuntu 8.04 04/24/2008
sed ‘s/regex/replacement/’ file
#search
11/25/2008
#regex
([0-9]{2})/([0-9]{2})/([0-9]{4})$
#replacement
\3-\1-\2
# sed 命令的 regex 使用 BRE 所以要想用 ERE 里面的元字符(( ) { })需要使用 \ 转义
sed 's/\([0-9]\{2\}\)\/\([0-9]\{2\}\)\/\([0-9]\{4\}\)/\3-\1-\2/' distros.txt
这个功能叫做 逆参照 ,像这样工作:如果序列 \n 出现在 replacement 中 ,这里 n 是指从 1 到 9 的数字,则这个序列指的是在前面正则表达式中相对应的子表达式。为了 创建这个子表达式,我们简单地把它们用圆括号括起来,
Linux 去除回车(将回车替换成空格):
sed ':a;N;$!ba;s/\n/ /g'
sed 应用举例
关键 options 介绍:
n
: 安静模式。一般sed用法中,所有来自STDIN的数据都会被输出到屏幕上,使用-n只有被sed处理的行才会列出来。如果不使用-n,使用sed打印时,会把输入流和处理的信息都打印一遍a
:append,追加文本i
:insert,插入文本d
:delete,删除文本s
:模式匹配替换p
:打印文本
- 在指定行插入或追加:
a, i
- 在test.txt第一行前插入:
sed "1 i This is a test file" test.txt
- 在test.txt最后一行追加:
sed "$ a This is the end of file" test.txt
- 在test.txt第一行前插入:
- 删除:
d
- 删除test.txt第二行:
sed "2d" test.txt
- 删除test.txt符合正则表达式/fish的行:
sed "/fish/d" test.txt
- 删除test.txt第二行:
- 修改文本:
s
- 将text.txt中love替换为like:
sed "s/love/like/g" test.txt
(/g表示全局匹配)
- 将text.txt中love替换为like:
- 打印文本:
p
- 输出test.txt的第5-7行:
sed -n "5,7p" test.txt
(-n的作用就显示出来了,可以去除-n查看效果)
- 输出test.txt的第5-7行:
printf
格式字符串可能包含文字文本(如“我格式化了这个字符串:” “I formatted the string:”),转义序列(例如\n,换行符)和以%字符开头的序列,这被称为转换规范。
命令格式:
printf format-string [arguments...]
# 注意在 awk 中的使用
awk '{printf "%s\t%s", $1, $2}' # arguements 用逗号隔开
常用的数据类型:
- d 将数字格式化为带符号的十进制整数
- f 格式化并输出浮点数
- o 将整数格式化为八进制数
- s 将字符串格式化
- x 将整数格式化为十六进制数,必要时使用小写a-f
- X 与 x 相同,但变为大写
- % 打印 % 符号 (比如,指定 “%%”)
awk
awk '条件 {操作}' 文件
awk '$9 == 500 {print $9, $7}' access.log
awk 的内建变量:
- $0 当前记录(这个变量中存放着整个行的内容)
- $1~n 当前记录的第n个字段,字段间由FS分隔
- FS 输入字段分隔符 默认是空格或Tab
- NF 当前记录中的字段个数,就是有多少列
- NR 已经读出的记录数,就是行号,从1开始,如果有多个文件话,这个值也是不断累加中。
- FNR 当前记录数,与NR不同的是,这个值会是各个文件自己的行号
- RS 输入的记录分隔符, 默认为换行符
- OFS 输出字段分隔符, 默认也是空格
- ORS 输出的记录分隔符,默认为换行符
- FILENAME 当前输入文件的名字
指定 :为分隔符
# 以下两句效果一样
awk 'BEGIN{FS=":"} {print $1, $3, $6}' /etc/passwd
awk -F ":" '{print $1, $3, $6}' /etc/passwd
# 如果要指定多种分割符,例如指定 : ; 作为分割符
awk -F '[:;]'
# 以 \t 作为输出分割符
awk -F ':' '{print $1, $3, $6}' OFS='\t' /etc/passwd
比较运算符:
==, !=, >, <, >=, <=, ~
下面看字符串匹配的例子:
# 第一行或者第 6 列匹配到 FIN 的
awk '$6 ~ /FIN/ || NR ==1 {print $1, $3, $6}' OFS='\t' netstat.txt
# awk 也可以像 grep 一样去匹配
awk '/LISTEN/' netstat.txt
# 可以用 FIN|TIME 来匹配 FIN 或者 TIME :
awk '$6 ~ /FIN|TIME/ || NR == 1 {print NR, $4, $5, $6}' OFS="\t" netstat.txt
再复杂一点,带上 if else 语句
awk 'NR!=1{if($6 ~ /TIME|ESTABLISHED/) print > "1.txt"; else if($6 ~ /LISTEN/) print > "2.txt"; else print > "3.txt"}' netstat.txt
awk 的 BEGIN 和END关键字
- BEGIN {这里面放的是执行前的语句}
- {这里面放的是处理每一行时要执行的语句}
- END {这里面放的是处理完所有的执行后要执行的语句}
awk 常用内建变量
- $n 当前记录的第n个字段,字段间由FS分隔
- $0 完整的输入记录
- FILENAME 当前文件名
- NF 一条记录的字段的数目
- NR 已经读出的记录数,就是行号,从1开始
- OFS 输出记录分隔符(输出换行符),输出时用指定的符号代替换行符
- ORS 输出记录分隔符(默认值是一个换行符)
- 记录分隔符(默认是一个换行符)
- FS 字段分隔符(默认是任何空格)
**注意:**一行称为一条记录,一列称为一个字段
例子
1、行列转换问题
cat file.txt
1 2 3 4 5
1 2 3 4 5
1 2 3 4 5
awk '{for(i=1;i<=NF;i++)arr[NR,i]=$i}END{for(col=1;col<=NF;col++)for(row=1;row<=NR;row++)printf row==NR?arr[row,col] RS:arr[row,col] FS}' file.txt
1 1 1
2 2 2
3 3 3
4 4 4
5 5 5
解析:NF 为每一行的字段数,相当于有多少列cols,NR 为读到的行数,读取结束后,NR 就等于总行数rows。因为 awk 读取的时候是一行一行读的,读一行就会执行一下后面的操作,所以每读一行就会执行一次 for 循环,NF 为读到的该行的列数,NR 为当前所读的行号,读一行就遍历该行,将其每列放入数组。END 后面的操作是在读取结束之后进行的,遍历打印二维数组
LeetCode 194. Medium 用到该方法
2、Nginx access.log 日志相关操作
问题描述:
1、取出 access.log 中状态码为 500 的 url 要求去重后取出前 100 条(access.log 中字段以空格分割,第9列为状态吗,第 7 列为url)
awk '($9=="500") {print $7}' | sort -u | head -n 1000
2、统计接口的访问次数,根据 access.log 日志 统计各个 URL 的访问次数,去除静态资源(css、js、html、png、jpg、jpeg、gif),以及接口后的 parameter(?问号后面的内容),并按照访问次数逆序排序
awk '$7 !~ /\.(css|js|html|png|jpg|jpeg|gif)/ {print $7}' access.log| sed 's/\?.*//' | sort |uniq -c | sort -k1,1nr
3、打印 file.txt 中第 10 行的内容
LeetCode 195. Tenth Line (Medium)
- 解法一 awk:
awk "NR==10" file.txt
- 解法二 sed:
sed -n "10p" file.txt