Jade Dungeon

文本处理

文本格式

文本(text)和字符串(string)和二进制(binary)是三个层面的东西。

  • 字符串是对文本的序列化,
  • 二进制是对字符串的序列化。

同样的文本在不同平台上表示成不一样的字符串 ,正如同样的字符串在不同编码下表示成不同二进制。

  • text描述的是一段自然语言文字,
  • string是为了传输或存储这个text而用来表示这一段文字的字符序列。

比如abc这个text,在主流终端传输时,用字符串可以表示`:

$ echo "abc"
abc

也可以表示为"abb\bc",即先多打一个b再打一个退格符\b删掉:

$ echo "abb\bc"
abc

你可以想想为什么http传输json的mime是application/json而不是text/json。 因为json官网http://json.org明确规定了json的换行符具体是哪个unicode字符, 因此json是对js object的string表示,而不是对js object的text表示。

同理,为什么http传输html的类型是text/html而不是application/html。 因为html是hyper text而不是string。所以http协议会将html这个text转换成string, 再转换成binary发出去,对方收到之后会将binary转为string再转为对方平台里的text。 此时换行符可能变了,trailing spaces可能删了,ending newline可能删了, 甚至tab变成了4个空格。

不过没关系,虽然string和原来不一样了,但text和原来一样。

这个问题,让我们认识到文件是一个操作系统抽象, 离开操作系统谈文件会发现这个抽象有很多问题。我们并没有一个跨操作系统的文件, 我们无论下载,上传,还是通过文件共享,其实都经过了一种通信协议, 在我们的操作系统上重建了文件,这本来就应该由通信协议完成本地化转换。

大部分遇到这个问题的人要么有意忽视了这个问题 (强行原样复制文件并在不同系统中打开,比如通过打包,传输,再解包的方式), 要么就是协议不支持,比如windows的scp。

FTP协议中区分了文本和非文本,git中支持修改或不修改换行符,都是这个原因。

见过不少同事在window下强行用b模式打开文本,再强迫自己用\r\n写行结束, 实在太辛苦。windows和unix下都通用的作法是用不加b的模式打开文本, 用\n写行结束。异种系统传递文件内容时,记得区分文本和非文本,按需重建文件。

如果要在传输过程中进行完整性校,则要记住校验的目标是校验传输的内容, 并不是校验重建的结果,文本内容用什么做换行符并不重要,不用在校验范围内。

以十六进制显示文本:xxd

$ cat README.txt
Study Notes
=====================

xxd可以生成16进制:

$ xxd README.txt
00000000: 5374 7564 7920 4e6f 7465 730a 3d3d 3d3d  Study Notes.====
00000010: 3d3d 3d3d 3d3d 3d3d 3d3d 3d3d 3d3d 3d3d  ================
00000020: 3d0a                                     =.

还可以把二六进制还原为二进制。甚至把十六进制拷贝输出成C数组:

$ xxd -i README.txt
unsigned char README_txt[] = {
  0x53, 0x74, 0x75, 0x64, 0x79, 0x20, 0x4e, 0x6f, 0x74, 0x65, 0x73, 0x0a,
  0x3d, 0x3d, 0x3d, 0x3d, 0x3d, 0x3d, 0x3d, 0x3d, 0x3d, 0x3d, 0x3d, 0x3d,
  0x3d, 0x3d, 0x3d, 0x3d, 0x3d, 0x3d, 0x3d, 0x3d, 0x3d, 0x0a
};
unsigned int README_txt_len = 34;

换行符

Windows文本文件的^M,注意这个^M的输入方式为CTRL v CTRL m

windows与dos转换:

# unix换行转windows换行:`unix2dos`
unix2dos memo.txt

# dos换行转unix换行:`dos2unix`
dos2unix memo.txt

用替换工具tr转换换行:

cat memo | tr -d '\r' > memo.txt

用替换工具sed转换换行:

sed -e ‘s/^M/\n/g’ memo.txt

字符编码

编码转换

iconf

sudo apt-get install enca
# 转换单个文件:
iconv -f GBK -t UTF-8 tt.json > tt.json

把当前目录下的文件转换编码:

sed -i "s/old/new/g" `grep old -rl ./`
# 或是:
`enca -L zh_CN -x utf-8 *`

recode

可以认为这个命令是上边iconv命令的专业版本. 这个非常灵活的并可以把整个文件都转换为不同编码格式的工具 并不是Linux标准安装的一部分.

文本编辑

拼接文本文件

cat连接多个文件

-n添加行号:

$ cat -n grocery.list
 1 apples
 2 bananas
 3 plums
 4 carrots

paste按列拼接文本

合并两个或多个文本文件,按行来进行合并。示例。如果file1的内容是:

$ cat file1
1
2
3

$ cat file2
a
b
c
d

$ cat file3
A
B
C
D
	
$ paste file1 file2 file3 > result
1    a    A
2    b    B
3    c    C
     d    D

# 默认的定界符是制表符,可以用`-d`指明定界符
$ paste -d "," file1 file2 file3 > result
1,a,A
2,b,B
3,c,C
 ,d,D

文本合并工具:join

这个命令与paste命令属于同类命令. 但是它能够完成某些特殊的目地。 这个强力工具能够以一种特殊的形式来合并两个文件, 这种特殊的形式本质上就是一个关联数据库的简单版本。

join命令只能够操作两个文件。它可以将那些具有特定标记域(通常是一个数字标签) 的行合并起来, 并且将结果输出到stdout。 被加入的文件应该事先根据标记域进行排序以便于能够正确的匹配。

文本切分

大文本切分:dd

大文本文件用dd切一段丢给vim

dd if=hugefile bs=1M count=100 skip=400 > from_400m_to_500m.txt

文本中抽取列:colrm

使用 colrm,可以从流中剪切出文本列。在第一个示例中,使用 colrm 命令从管道的每行 文本中剪切出第 4 列到行尾。然后,将同一个文件发送至colrm,以删除第4列到第5列。

使用 colrm 删除列的示例

$ cat grocery.list  | colrm 4
 app
 ban
 plu
 car
 $ cat grocery.list | colrm 4 5
 apps
 banas
 plu
 carts

按列切分文本:cut

一个从文件中提取特定域的工具. 这个命令与awk中使用的print $N命令很相似, 但是更受限. 在脚本中使用cut命令会比使用awk命令来得容易一些. 最重要的选项就是-d(字段定界符)和-f(域分隔符)选项.

cut 取的范围:

  • N-:第N个字段到结尾
  • -M:第1个字段为M
  • N-M:N到M个字段cut 取的单位
  • -b:以字节为单位
  • -c:以字符为单位
  • -f:以字段为单位(使用定界符)
# 截取文件的第2列和第4列:
cut -f2,4 filename

# 去文件除第3列的所有列:
cut -f3 --complement filename

# `-d`指定定界符:
cut -f2 -d ";" filename

cut -c1-5 file  # 打印第一到5个字符
cut -c-2  file  # 打印前2个字符

提取头部:head

把文件的头部内容打印到stdout上(默认为10行, 可以自己修改). 这个命令有一些比较有趣的选项.

提取结尾:tail

将一个文件结尾部分的内容输出到stdout中(默认为10行). 通常用来跟踪一个系统logfile的修改情况, 如果使用-f选项的话, 这个命令将会继续显示添加到文件中的行.

文本过滤

文本过滤器:nl

nl 过滤器会从 stdin 或指定文件读取行,再加上行号。 输出则会写入 stdout 并重定向到文件,或传到另一个进程中。 nl 的行为是由不同命令行选项控制的。

在默认情况下,nl 会计算行数,与cat -n的功能类似。

$nl grocery.list
 1 apples
 2 bananas
 3 plums
 4 carrots

使用-b标志指定要进行编号的行。此标志将参数作为「类型」。该类型告诉 nl 需要给哪些 行编号:

  • a:所有行都要编号
  • t:不对空行和只有空格的行进行编号
  • n:指定不编号行。
  • p:给正则表达式模式指定的行编号

例,符合正则以字母ab开始的行:

$ nl -b p^[ba] grocery.list
 1 apples
 2 bananas
 plums
 carrots

在默认情况下,nl 行号和文本之间使用制表符进行分隔。使用-s指定其他分隔符, 例如=号:

$nl -s= grocery.list
 1=apples
 2=bananas
 3=plums
 4=carrots

去除重复列:uniq

uniq 命令通常用来惟一地列出输入源(通常是文件或 stdin)中的行。要正确操作,重复 的行必须连续放置于输入中。uniq 命令的输入通常会进行排序,因此重复的行会进行合并 。与 uniq 命令搭配使用的两个常用标志是:

  • -c:输出每行出现的次数,
  • -d:用来显示重复行的一个实例。
# 消除重复行
sort unsort.txt | uniq

# 统计各行在文件中出现的次数

sort unsort.txt | uniq -c

# 找出重复行
sort unsort.txt | uniq -d

可指定每行中需要比较的重复内容:

  • -s开始位置
  • -w比较字符数

换行工具:fold

使用 fold 命令可以将行拆分为指定的宽度(默认的80列)。这个命令最初是用来对无法支持换行的定宽 输出设备进行文本格式化。-w选项标志允许使用指定行宽。

将输入按照指定宽度进行折行. 这里有一个非常有用的选项-s, 这个选项可以使用空格进行断行 (译者: 事实上只有外文才需要使用空格断行, 中文是不需要的).

换行工具:fmt

一个简单的文件格式器, 通常用在管道中, 将一个比较长的文本行输出进行"折行".

过滤反向换行:col

这个命令用来滤除标准输入的反向换行符号. 这个工具还可以将空白用等价的tab来替换. col工具最主要的应用还是从特定的文本处理工具中过滤输出, 比如groff和tbl. (译者: 主要用来将man页转化为文本.)

列格式化:column

列格式化工具. 通过在合适的位置插入tab, 这个过滤工具会将列类型的文本转化为"易于打印"的表格式进行输出.

分行与分列:pr

格式化打印过滤器. 这个命令会将文件(或stdout)分页, 将它们分成合适的小块以便于硬拷贝打印或者在屏幕上浏览. 使用这个命令的不同的参数可以完成好多任务, 比如对行和列的操作, 加入行, 设置页边, 计算行号, 添加页眉, 合并文件等等.

pr命令集合了许多命令的功能, 比如:nl, paste, fold, column, 和expand.

这个命令对fileZZZ进行了比较好的分页, 并且打印到屏幕上. 文件的缩进被设置为5, 总宽度设置为65.

pr -o 5 --width=65 fileZZZ | more 

一个非常有用的选项-d, 强制隔行打印(与sed -G效果相同).

排序:sort

文件排序, 通常用在管道中当过滤器来使用. 这个命令可以依据指定的关键字或指定的字符位置, 对文件行进行排序. 使用-m选项, 它将会合并预排序的输入文件. 想了解这个命令的全部参数请参考这个命令的info页.

字段说明:

  • -n:按数字进行排序 VS -d 按字典序进行排序
  • -r:逆序排序
  • -kN指定按第N列排序
sort -nrk 1 data.txt
sort -bd data              # 忽略像空格之类的前导空白字符

拓扑排序:tsort

拓扑排序, 读取以空格分隔的有序对, 并且依靠输入模式进行排序.

翻译工具:gettext

GNU gettext包是专门用来将程序的输出翻译或者本地化为不同国家语言的工具集. 在最开始的时候仅仅支持C语言, 现在已经支持了相当数量的其它程序语言和脚本语言.

想要查看gettext程序如何在shell脚本中使用. 请参考info页.

文本替换

文本替换工具:tr

tr 命令用来转换来自 stdin 的字符,在 stdout 中显示。tr 一般接受两个字符集合, 用第二个集合中的字符替换第一个集合中的字符。通用用法

echo 12345 | tr '0-9' '9876543210' # 加解密转换,替换对应字符
cat text   | tr '\t'  ' '           # 制表符转空格

cat file | tr -d '0-9'   # 删除所有数字

# `-c`求补集
cat file | tr -c    '0-9'   # 获取文件中所有数字
cat file | tr -d -c '0-9'   # 删除非数字数据

# `-s`压缩文本中出现的重复字符;最常用于压缩多余的空格
cat file | tr -s ' '

有许多预定义的字符类(集合)可供 tr 使用,还有其他命令可用。 这些预定义的字符类是:

  • alnum:字母数字字符
  • alpha:字母字符
  • blank:空白字符
  • cntrl:控制字符
  • digit:数字字符
  • graph:图形字符
  • lower:小写字母字符
  • print:可打印字符
  • punct:标点字符
  • space:空间字符
  • upper:大写字符
  • xdigit:16 进制字符

使用方法:tr [:class:] [:class:]

例:tr命令够将字符串中的小写字符转换成大写。

tr '[:lower:]''[:upper:]'
$ echo "Who is the standard text editor?" | tr [:lower:] [:upper:]
 WHO IS THE STANDARD TEXT EDITOR?

tr可以用来从字符串中删除指定字符。

$ echo 'ed, of course!' |tr -d aeiou
 d, f crs!

使用-s将字符串中任何指定字符转换成空格。在序列中遇到多个指定字符时,它们会转换 成一个空格。

-s选项标志的行为在不同系统中表现不同。

$ echo 'The ed utility is the standard text editor.' |tr -s astu ' '
 The ed ili y i he nd rd ex edi or.

-s选项标志可以用来取消字符串中多余的空格。

$ echo 'extra spaces – 5’ | tr -s [:blank:]
 extra spaces - 5
 $ echo ‘extra tabs – 2’ | tr -s [:blank:]
 extra tabs – 2

在基于 UNIX 和 Windows 系统之间转换文件时发生的常见问题就是行分隔符 (line delimiters)。在 UNIX 系统中,行分隔符为一个换行符,而在 Windows 系统中, 则是用两个字符(即一个回车符和一个换行符)。使用 tr 配合某种重定向,可以解决这个 格式问题。

tr 示例:消除回车符

$ tr -d '\r' < dosfile.txt > unixfile.txt

替换空格与制表符:expand 和 unexpand

expand 命令将制表符变成空格,而 unexpand 将空格变成制表符。这两个命令都接受 stdin 输入以及命令行指定文件的输入。使用-t选项可以设置一个或多个制表符停止位。

expand 和 unexpand 示例:

$ cat grocery.list|head -2|nl|nl
 1 1 apples
 2 2 bananas
 $ cat grocery.list|head -2|nl|nl|expand -t 5
 1 1 apples
 2 2 bananas
 $ cat grocery.list|head -2|nl|nl|expand -t 5,20
 1 1 apples
 2 2 bananas
 $ cat grocery.list|head -2|nl|nl|expand -t 5,20|unexpand -t 1,5
 1 1 apples
 2 2 bananas

文本比较

diff

diff 命令会对两个文件进行比较,报告两者之间的不同之处。diff 可接受多种选项标志。

  • -w选项的忽略空格,
  • -i选项标志在比较中忽略大小写区别

comm

comm 命令会对两个文件进行比较,但比较的方式与 diff 差别很大。comm 产生三列输出, 仅出现在第 1 个文件(第 1 列)的行, 仅出现在第 2 个文件(第 2 列)的行, 两个文件中都有的常见行(第 3 列)。

可使用选项标志来取消输出列。此命令可能在取消第 1 列和第 2 列时最有用, 只显示两个文件中常见的行,如下所示。

$ comm dummy_file1.dat dummy_file2.dat
 011 IBM 174.99
 011 IBM 174.99
 012 INTC 22.69
 012 INTC 22.78
 013 SAP 59.37
 014 VMW 102.92
 014 vmw 102.92
$ comm -12 dummy_file1.dat dummy_file2.dat
 013 SAP 59.37

cmp

cmp比较两个任意类型的文件并将结果输出至标准输出。如果两个文件相同, cmp默认 返回0;如果不同,将显示不同的字节数和第一处不同的位置。

比较一下这两个文件,看看命令的输出。

$ cat file1.txt
 
Hi My name is Tecmint

$ cat file2.txt
 
Hi My name is tecmint [dot] com

$ cmp file1.txt file2.txt
 
file1.txt file2.txt differ: byte 15, line 1

统计工具

文本统计:wc

wc(wordcount) 命令计算指定文件或来自 stdin 的行数、单词数(由空格分隔)和 字符数。

返回格式:

[行数] [单词数] [字符数] [文件名]
$ wc grocery.list
 4 4 29 grocery.list
$ wc -l grocery.list
 4 grocery.list
$ wc -w grocery.list
 4 grocery.list
$ wc -c grocery.list
 29 grocery.list
wc -l file // 统计行数
wc -w file // 统计单词数
wc -c file // 统计字符数