# 文本处理
# 查看文本
有多种命令可以查看文件的内容。
- 有的文件是采用二进制格式存储的,不能直接阅读。
- 有的文件是采用 ASCII 码等编码格式存储的,称为文本文件,可以直接阅读。
- 如果查看文本文件时出现乱码,说明解码时使用的编码格式不对。
本文中,大部分命令的输入参数都声明为 [file] 。如果不指定文件参数 file ,或者指定 - 作为 file ,该命令就会读取 stdin 作为输入。例如:ps | wc -l
# cat
$ cat [file] # 显示文件的内容
-n # 同时显示行号
-b # 空白行不记行号
-E # 在每行的末尾显示 $
-T # 将 Tab 符显示成 ^I
-v # 显示不会 print 的特殊字符
- 不适合查看内容较多的文件,否则会显示好几页终端。
- 例:
cat f1 > f2 # 拷贝文件 f1 的内容到文件 f2 中(相当于只 cp 内容)
# tac
$ tac [file] # 从最后一行开始倒序显示文件的内容(与 cat 命令相反)
# head
$ head [file] # 显示文件开头的 10 行
-n # 只显示 n 行
-c n # 只显示 n 个字节,可以使用单位 K、M、G、T 等
# tail
$ tail [file] # 显示文件末尾的 10 行
-n # 只显示 n 行
-c n # 只显示 n 个字节
-f n # 跟踪显示文件的末尾,当末尾增加内容时就显示出来(这会阻塞前台)
-q # 查看多个文件时,默认会分别显示文件名,启用该选项则不显示文件名
# more
$ more <file> # 打开阅读器,显示文件的全部内容(只能向前翻页)
# less
$ less <file> # 打开阅读器,显示文件的全部内容(可以前后翻页、查找)
-N # 显示行号
- 该阅读器的控制方法与 vim 相似,只是要按 q 键退出。
# 文本排版
# column
$ column [file]...
-t # 将每行文本分割成多个字段,从而将输入的多行文本转换成一个列表(会自动上下对齐)
-s " " # 指定输入文本的字段分隔符(默认是连续的空白字符)
-o " " # 指定输出列表的字段分隔符(默认是至少两个空格)
- 例:
[root@CentOS ~]# echo $PATH /usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin [root@CentOS ~]# echo $PATH | column -t -s : /usr/local/sbin /usr/local/bin /usr/sbin /usr/bin /sbin /bin [root@CentOS ~]# echo $PATH | column -t -s : -o ', ' /usr/local/sbin, /usr/local/bin, /usr/sbin, /usr/bin, /sbin, /bin
# sort
$ sort [file]... # 将每行文本排序之后输出
-k 1 # 按每行的第 1 个字段进行排序
-t " " # 指定每行的字段分隔符
-r # 反序排列
- 例:
[root@CentOS ~]# ls -al | sort -k 9 total 216 dr-xr-x---. 22 root root 4096 Dec 6 15:54 . dr-xr-xr-x. 19 root root 4096 Dec 9 10:00 .. -rw------- 1 root root 41827 Dec 9 09:59 .bash_history -rw-r--r--. 1 root root 18 Dec 29 2013 .bash_logout -rw-r--r--. 1 root root 176 Dec 29 2013 .bash_profile
# jq
$ jq <filter> [file...] # 读取文本,按 JSON 格式解析,然后输出
--indent 2 # 输出时,每层缩进的空格数
--compact-output # 将输出的 JSON 对象压缩成一行。否则默认会输出多行,自动缩进
- 安装:
yum install jq
- 例:
jq . test.json # filter 为 . ,这会输出整个 JSON 对象 cat test.json | jq .links[0] # 只输出 filter 指定的元素 cat test.json | jq ,.name,.status # filter 可以指定多个元素 cat test.json | jq [.name, .status] # 输出为 array 类型 cat test.json | jq '{name: .name, status: .status}' # 输出为 object 类型 cat test.json | jq '"name=\(.name)"' # 输出为 string 类型,可以用 \(x) 的格式插入值 cat test.json | jq 'del(.name, .status)' # 删除一些字段
- 还可用 Python 进行格式化:
python -m json.tool <file>
# yq
$ yq <expression> [file...]
-I <int> # --indent ,设置输出时的缩进,默认为 2
-i # --inplace ,将输出保存到源文件中。此时不支持读取 stdin 作为输入
- yq 命令的用法与 jsq 类似,但支持解析 YAML、JSON、XML 文本格式。
- 安装:
wget https://github.com/mikefarah/yq/releases/download/v4.19.1/yq_linux_amd64 -O /usr/bin/yq chmod +x /usr/bin/yq
- 例:
yq '.a[0].b' test.yaml # 输出指定字段 cat test.yaml | yq - # 读取 stdin yq -i '.a="hello"' test.yaml # 替换一个字段的值 yq '.a[]' test.yaml -s '.name' # 将 list 元素分别保存到不同文件中,文件名引用其中的 .name 字段
- yq 命令不支持通过 $ 读取 shell 环境变量。而要使用以下语法:
yq -i ".a=env(PWD)" test.yaml # 引用 shell 环境变量,自动保存为字符串、数值、数组等数据类型 yq -i ".a=strenv(PWD)" test.yaml # 引用 shell 环境变量,总是保存为字符串类型
- 如果目标文件中包含多个用
---
分隔的 YAML 文档,则会被 yq 分别处理。
# 统计分析
# wc
$ wc [file]...
-l # 统计行数
-c # 统计字节数
-m # 统计字符数
# uniq
$ uniq [file]... # 去掉文本中重复出现的行,再显示
-c # 统计每行重复出现的次数,显示在每行左侧
-d # 仅显示重复出现的行
-u # --unique ,仅显示没有重复的行
-i # --ignore-case ,忽略大小写
- uniq 识别重复的行时,这些行必须相邻,因此通常先用 sort 将文本排序,再用 uniq 去重。如下:
[root@CentOS ~]# cat f1 # 查看文件的原内容 Hello World Hello Hello World Hello [root@CentOS ~]# uniq f1 # 此时重复的行没有相邻,因此 uniq 不能识别 Hello World Hello Hello World Hello [root@CentOS ~]# cat f1 | sort # 将文本排序 Hello Hello Hello World Hello World [root@CentOS ~]# cat f1 | sort | uniq # 将文本排序后去重 Hello Hello World [root@CentOS ~]# cat f1 | sort | uniq -c # 统计每行重复出现的次数 2 Hello 2 Hello World
# diff
$ diff
<file1> <file2> # 逐行比较两个文本文件的内容差异(如果没有差异,则显示为空)
-q # 安静模式(如果有差异,则不显示具体差异,只显示一行提示)
-y # 在屏幕左右侧分别显示两个文件的内容,类似 GUI
-b # 忽略空格(多个连续空格视作一个空格)
-B # 忽略空行
-i # 忽略大小写的差异
-w # 忽略所有空白字符
<dir1> <dir2> # 比较两个目录。找出哪些文件名只存在单个目录中;找出哪些文件名存在两个目录中,但是内容不同
-q # 安静模式
-r # 递归比较(默认不会比较子目录)
- 安装:
yum install diffutils
- 例:
cat f2 | diff f1 - # 通过 stdin 传入一个文件进行比较
diff 的显示结果表示了从文件 1 到文件 2 的变化。[root@CentOS ~]# diff -y f1 f2 A | B | C | D D > E > F > G [root@CentOS ~]# diff f1 f2 1,3c1,3 < A < B < C --- > > > 4a5,7 > E > F > G
- 例如,
1,3c1,3
表示从文件 1 的第 1~3 行到文件 2 的第 1~3 行有变化。 a
表示 add ,c
表示 change ,d
表示 delete 。---
是两个文件内容的分隔线。
- 例如,
# hexdump
$ hexdump <file>... # 将文件内容显示成十六进制值(两个字节为一组)
-c # 显示成 ASCII 码
-C # 显示成十六进制值 + ASCII 码(每个字节为一组)
-n 4 # 最多显示 4 个字节
-s 4 # 从第 4 个字节开始显示
例:
[root@CentOS ~]# echo 'Hello World! How are you?' > f1 [root@CentOS ~]# hexdump f1 0000000 6548 6c6c 206f 6f57 6c72 2164 4820 776f # 第一排包含从地址 0000000 到 000000F 处的 16 个字节 0000010 6120 6572 7920 756f 0a3f 000001a # 最后一排表示该文件的总字节数 [root@CentOS ~]# hexdump -C f1 00000000 48 65 6c 6c 6f 20 57 6f 72 6c 64 21 20 48 6f 77 |Hello World! How| 00000010 20 61 72 65 20 79 6f 75 3f 0a | are you?.| 000001a [root@CentOS ~]# hexdump -n 1 f1 0000000 0048 0000001 [root@CentOS ~]# hexdump -n 2 f1 0000000 6548 0000002
- 上例中,地址 0000000、0000001 处的字节内容分别为 48、65 ,对应的 ASCII 字符分别为 H、e 。
- 显示 . 代表不能解码成 ASCII 码中的可显示字符。
xxd 命令与之类似:
[root@CentOS ~]# echo Hello | xxd 0000000: 4865 6c6c 6f0a Hello.
# 筛选文本
# grep
$ grep <pattern> [file]... # 筛选出文件中匹配 pattern 的每行文本
[-e pattern]... # --regexp ,指定基本语法的正则表达式(这是默认类型)
[-E pattern]... # --extended-regexp ,指定扩展语法的正则表达式
[-P pattern]... # --perl-regexp ,指定 Perl 语法的正则表达式
-a # 将二进制文件也当作 text 格式筛选,否则默认不能处理二进制文件
-i # --ignore-case ,忽略大小写
-r # --recursive ,递归检索指定目录下的所有文件
-c # --count ,只显示匹配的总行数
-H # --with-filename ,输出每个匹配结果时显示文件名
-h # --no-filename ,输出每个匹配结果时不显示文件名。默认情况下,如果只读取一个文件或 stdin ,则启用 -h 选项,否则启用 -H 选项
-o # --only-matching ,只显示每行中与 pattern 匹配的子字符串,而不是整行
-q # --quit ,不显示匹配结果。此时只能根据命令返回码,判断匹配是否成功
-n # --line-number ,增加显示每行的行号
-v # 反向匹配
-A <int> # --after-context ,增加显示在匹配的行之后的 n 行
-B <int> # --before-context ,增加显示在匹配的行之前的 n 行
-C <int> # --context ,增加显示在匹配的行之后和之前的 n 行
-o -P '.{0,10}pattern.{0,10}' # 只显示在与 pattern 匹配的子字符串的前后 10 个字符
-l # 如果文件内容匹配,则只显示文件名
-L # 只显示不匹配的文件名
- 如果 grep 的匹配结果不为空,则返回码为 0 ,表示匹配成功(只要有一行文本匹配成功即可)。否则为非 0 ,表示匹配失败。
- 例:
grep -n root /etc/passwd grep -v '^$' /etc/passwd # 去掉空行 grep README.md -e hello -e world # 可以指定多个 pattern ,只要文本匹配任一 pattern 就会被筛选出来 grep -P '[\p{Han}]' # 匹配汉字 grep -cr DEBUG . | grep -v :0 # 递归检索指定目录下的所有文件 find . -name *.py | xargs grep -c DEBUG # 先找出一组文件,再筛选文本
# cut
$ cut [file]...
-f 1-4 # 显示每行的第 1 至 4 个字段
-f 1 -d "," # 显示每行的第 1 个字段,以 , 作为字段分隔符
-b "1 2 3" # 显示每行的第几个字符
# awk
$ awk [option] [expression] [file]...
-F , # 设置字段分隔符(默认是一个或多个空格、Tab)
- awk 可编写复杂的表达式,是一种脚本语言。
- awk 表达式要用单引号包住,避免转义。
- 正则表达式 pattern 要用 / / 包住。
- awk 表达式可以简写,例如
$1=="root" {print $3}'
的完整写法是:if ( $1 == "root" ) { print($3) # 函数的括号可以省略 }
- awk 表达式示例:
# 筛选显示的内容 '{print $3}' # 显示每行的第 3 个字段 '{print NR, $(NF-1)}' # 显示每行的行号(从 1 开始编号)、倒数第二个字段 '$3="hello"; print' # 设置第 3 个字段的值,然后打印全部内容 'NR>1 && NR<=10' # 显示行号在指定范围的行 '/^\S+/ {print $2,$3}' # 要求当前行与 pattern 正则匹配,才显示 '$1=="root" {print $3}' # 要求第一个字段等于指定字符串,才显示 '/Hello/,/^\s\s/' # 查找两个 pattern 匹配的行,显示它们之间的所有行。其中的逗号 , 是 awk 语法的分隔符 # 引用 shell 变量 -v a=$A -v b=$B '{print $a}' # 进行运算 '{sum+=$1} END {print sum / 1024}' # 计算第一个字段的总和
[root@CentOS ~]# echo "1.76e-06"| awk '{printf "%f", $0 }' # 控制输出格式为小数,默认小数点后保留 6 位 0.000002 [root@CentOS ~]# echo "1.76e-04"| awk '{printf "%.2f", $0 }' # 控制小数点后保留 2 位 0.00
- awk 的内置变量:
- $0 :当前行的全部字段。
- $1 :当前行的第一个字段,以此类推。
- 如果 $n 超过当前行的最大字段数,则获取的值为空。
- NF :当前行的最大字段数。
- NR :当前行数。
- awk 命令不支持直接修改文件,可采用以下方式:
awk '{$3=$2; print}' f1 > f2 rm -f f1 mv f2 f1