如果你经常和数据打交道,那么你肯定会经常需要对列进行操作。在 Linux 中,对纯文本文件的列操作有两个十分有用的命令:cut
和 paste
。其中 cut
主要用于从纯文本文件中取出某些列,paste
则可以用于按列合并。
cut
命令
假设有这样一个测试文件 cut.txt
:
1 | 1|2|3|4|5|6|7|8|九|0 |
我们将用这个测试文件来做一些实验。
cut
基础与字节模式
前面说过,cut
命令的本职工作就是取出某些列。实际上,更准确地说法,是 cut
命令逐行地处理输入,并从中取出某些列。这里说的「列」有三种模式:
1 | -b # 以字节作为标准取出列 |
首先我们看看字节模式。比如我们可以取出每一行的第三个字节中的内容。我们知道,英文字符都是以 ASCII 编码用一个字符保存的。这样,我们预期会输出 6 个 2。我们来看下实际的输出。
1 | $ cut -b 3 cut.txt |
完美,完全符合预期!
我们来看一下 cut
命令的样式
1 | cut -[b,c,f] <columns> <filename> |
在刚才的例子中,我们选择了字节模式(-b
),并指定了第三列。值得一提的是,cut
命令的列指定风格非常的灵活。
1 | 3 # 第三列 |
字节模式在某些情况会遇到问题。比如,遇到非 ASCII 编码的字符时(特别是多字节字符),就会遇到问题。我们试着看看用 -b
模式输出第 17 列会怎样。
1 | $ cut -b 17 cut.txt |
实际上,-b
模式的第 17 列,会输出「九」的第一个字节。具体输出的内容取决于系统使用的编码。如果我们想输出字符「九」就需要使用字符模式了。
字符模式与域模式
-c
是字符模式。为了输出一列汉字「九」,我们可以这样
1 | $ cut -c 17 cut.txt |
除了解析列的方式不一样之外,-c
和 -b
完全一样。
类似的,还有域模式。与字节模式以及字符模式最大的不同是,域模式可以指定单个字符作为分隔符,逐行地将文件分成若干列。比如,这里我们可以用 |
作为分隔符,输出第三列至第五列以及第九列。注意,在列模式下,分隔符也会按需输出。
1 | $ cut -d '|' -f 3-5,9 cut.txt |
补集
cut
命令还支持 --complement
参数,意思是取补集。比如在我们刚才的例子中,取补集就意味着取出第一列、第二列、第六列至第八列以及第十列。
1 | $ cut -d '|' -f 3-5,9 --complement cut.txt |
使用 --complement
参数,我们可以很容易地从纯文本中删除某一列。比如我们想删除第四列
1 | $ cut -d '|' -f 4 --complement cut.txt |
轻而易举~
一点黑魔法:处理连续空格分割的情况
cut
在处理连续空格分割列的时候,结果就会变得一团糟。不过,好在我们有 tr
命令。使用 -s
参数,可以逐行地将连续的字符 unique 成单独的一个字符。
1 | $ who |
这样,我们就能轻易地获得各个用户的登录时间了
1 | $ who | tr -s ' ' | cut -d ' ' -f 1,3,4 |
paste
命令
基本用法
相比 cut
命令,paste
命令的用法就简单粗暴许多了。
假设我们有三个文件
1 | $ cat paste1.txt | $ cat paste2.txt | $ cat paste3.txt |
现在我们用 paste
试试看
1 | $ paste paste1.txt paste2.txt |
不难发现,paste
命令支持输入多个文件,并按顺序将他们用制表符粘在一起。如果你想用其他的分隔符将他们粘在一起,也可以像 cut
命令那样使用 -d
参数指定。
1 | $ paste -d '|' paste2.txt paste1.txt paste3.txt |
一点黑魔法:避免使用临时文件
如果我们需要将几个程序的即时输出(标准输出)按列粘在一起的话,可能不得不将这些输出先写入临时文件当中,然后再调用 paste
命令。不过,也有不用这样麻烦的办法——使用 Bash Process Substituation 来解决这个问题。
简单来说,就是使用 <(command)
来「伪装成一个文件」的样子,作为 paste
命令的输入。比如
1 | $ paste -d '|' <(cat paste2.txt) <(cat paste1.txt) <(cat paste3.txt) |