Shell:文本操作
主要是 awk/grep/sed
这三驾马车,加上vi这个神器,最后辅助一些小工具,包括 wc,cat,diff,join,paste,cut,uniq
。
这里 简要地整理下 Linux 用来处理数据文本的工具。具体命令详情请在 Linux 命令大全中搜索或者查阅其他相关资料。
head, tail
查看文档头尾。 -n
选项可以指定行数。
less
用来查阅文档, q
退出, space bar
翻页, g
第一行, G
最后一行, j
下, k
上, /<pattern>
往下搜索模式, ?<pattern>
往上搜索模式, n
前一个匹配字符, N
后一个匹配字符。
less
可以用于 debug,查看中间输出结果。比如:
1 | $ step1 input.txt | step2 | step3 > output.txt |
可以写为:
1 | $ step1 input.txt | less |
这样就可以一步步的查看中间结果啦~
同理还有more
命令, cat
命令也可以查看文本。
纯文本信息汇总
wc
命令默认依次输出单词数、行数、总字符数。查看行数使用 wc -l
。 如果存在空行,空行会被计数。可以使用 grep
命令实现非空行计数 grep -c "[^ \\n\\t]" some_data.bed
ls -lh
以易读形式查看文件大小。
输出文件列数:
1 | # -F指定分隔符,此处假定是table键分隔,默认空格键 |
怎么去除注释的元数据行呢?怎么计数非注释行行数呢?
可以使用 tail
结合 awk
,试试 gtf (基因组注释文件)
1 | $ head -n 6 Homo_sapiens.GRCh37.75.gtf |
可以看到注释行是 5 行,我们利用 tail
试试去掉它
1 | # 注意此处 -n后接的"+"号 |
发现还有一行没去掉
1 | $ tail -n +6 Homo_sapiens.GRCh37.75.gtf | head -n 1 |
成功搞定,然后结合前面提到的 awk
命令即可计算行数。
上面方法鲁棒性不够(人为地确定行数),一种更为通用的方法是 grep
结合 awk
命令。
1 | $ grep -v "^#" Homo_sapiens.GRCh37.75.gtf | head -n 1 |
推荐使用这种。
cut 详解
cut
可以处理列数据, -f
选项指定列,可以是一个范围(比如 2-8 ),注意不能用它给列排序。
1 | $ grep -v "^#" Homo_sapiens.GRCh37.75.gtf | head -n 10 | cut -f 3 |
-d
选项可以指定分隔符,比如 -d,
指定 ,为分隔符。
使用 column
命令来格式化输出,上次的命令结果输出明显没对齐,我们把它对齐看看:
1 | $ grep -v "^#" Homo_sapiens.GRCh37.75.gtf | head -n 10 | cut -f 3-5 | column -t |
注意,使用这个命令是为了好观察,不要把用它处理然后把结果传入文本(会导致程序处理文件效率降低,因为文本解析速度会下降)。
cut
和 column
默认以 \t
为分隔符,这里也能够用 -s
选项指定。
先把之前的 tab
分隔文件弄成逗号分隔文件,然后使用 -s
选项看看:
1 | $ grep -v "^#" Homo_sapiens.GRCh37.75.gtf | head -n 10 | cut -f 3-5 | awk '{FS="\t";OFS=",";}{print $1,$2,$3}' |
grep 简单了解
grep
处理速度非常之快,能用它尽量用它。 --color=auto
可以激活颜色(标记匹配文字),更方便查看。
-v
选项排除匹配到的, -w
进行完全匹配。这样可以防止,你想排除 abc 结果把 abc1, abcd 也排除掉了。
-B
指定输出包括匹配到的前多少行,比如 -B1
就是前一行; -A
指定输出包括匹配到的后多少行,比如 -A2
就是包括了后两行。 -C
指定输出包括匹配到的前后多少行。 grep
支持基本正则表达式, -E
指定支持扩展表达式,或者用 egrep
命令。 -c
选项对匹配的行计数; -o
选项只抽离输出匹配的部分。
1 | $ grep -E -o 'gene_id "\w+"' Homo_sapiens.GRCh37.75.gtf | head -n 5 |
发现冗余项非常多,如果我们只要唯一的呢,怎么办?
1 | $ grep -E -o 'gene_id "(\w+)"' Homo_sapiens.GRCh37.75.gtf | cut -f2 -d" "| sed 's/"//g' | sort | uniq | head -n 10 |
file 查看文件编码
1 | $ file regular_express.txt |
常用的大型数据文件一般存为 ASCII 码形式(像几大基因 bank 的数据文件),而我们自己认为创建的常为 UTF-8,所以有时候认为处理文件需要会碰到把 UTF-8 编码的字符插入到 ASCII 码文件里去了。遇到这种问题,我们可以用 hexdump -c
命令查看出错的地方(手边没有这样的文件,就不举例了)。
用 sort 对文本排序
先创建一个 bed 格式文件来试试这个命令:
1 | $ cat test.bed |
可以明显看到文本按照第一列进行了排序。 默认, sort
用空格或 tab
键作为域(列)分隔符。如果我们用其他形式的分隔符,需要用 -t
选项指定。
下面是对 bed 文件最通用的排序命令:
1 | $ sort -k1,1 -k2,2n test.bed |
基本操作 bedtools 软件都会先用这个命令对 bedtools 文件排序。 现在略加解释一下,sort
用 -k
选项指定某列的排序方式。而每次使用 -k
选项都要带上指定列的范围 (start, end)
。如果只指定一列,就为 (start,start)
了,像上面命令的 -k1,1
就是。也许你会觉得 -k2,2n
很奇怪,这里的 n
指定程序把第二列当做数值对待。如果不做设定,都是当做字符对待( shell 都是这么对待数值数据的)。所以总结其他这一行命令就是对第一列按照字符排序,第二列按照数值排序。
我们可以用 -c
选项检查一个文件是不是已经按照过某种方式排过序了。
1 | $ sort -k1,1 -k2,2n test.bed | sort -k1,1 -k2,2 -c |
上面可以清楚地看到 sort
是怎么对待文件的(一般 shell 返回 0 表示成功执行)。
1 | $ tsfds |
shell 的命令退出状态码表示了该命令执行的完成的某种情况。不同的状态码有不同的含义.。
反向排序用 -r
选项。如果你只想反转一列,可以把它加在 -k
选项后。
1 | $ sort -k1,1 -k2,2nr test.bed |
给 test.bed加一行:
1 | $ cat test.bed |
你会发现有点奇怪
1 | $ sort -k1,1 -k2,2n test.bed |
怎么 chr11 在 chr2 前面? 其实 sort
排序的方式有点像查字典。例子中,命令先比较 c
,然后比较 h
,然后比较 r
,接着比较 1,自然 11会在 2前面了。这里可以添加 V
选项修改。
1 | $ sort -k1,1V -k2,2n test.bed |
是不是觉得这样更可观一些?不过通常在处理数据时不做此处理,符合 规范的数据可以让后续处理程序效率更高。
用 uniq 寻找唯一值
创建样例文本:
1 | $ cat test.letter |
使用 uniq
看看:
1 | $ uniq test.letter |
尼玛,怎么不对。它好像只去掉了连续的同一字符。怎么办?想想我们刚学了什么命令? sort
不是刚好可以把同样的字符弄到一起去吗,然后再使用 uniq
,嘿嘿:
1 | $ sort test.letter | uniq |
well done!
加 -c
选项计数:
1 | $ sort test.letter | uniq -c |
把结果再排序:
1 | $ sort test.letter | uniq -c | sort -rn |
-d
选项只输出重复行
1 | $ cat test.letter |
使用时需要注意处理不同导致的结果差异。
Join 命令
用来连接文件。 假设现在我们有两个文件:
1 | $ cat example.bed |
我想把第二个文件说明染色体长度添加到第一个文件对应染色体的第三列。 我们首先要给文件排序(使用 join
前必须做),然后使用 join
命令。
1 | $ sort -k1,1 example.bed > example_sorted.bed |
命令基本语法是:
1 | join -1 <file_1_field> -2 <file_2_field> <file_1> <file_2> |
既然名字叫 join
,就是两者必须有共同之处,通过共同的支点将两者连为一体。 -1
和 -2
选项后接参数分别指定了这个支点,也就是连接的域(列)。比如例子中,都是两个文件的第一列。
两个文件中,第一列都共有 chr1(2)(3)。 如果不一致会出现什么情况呢?
1 | $ join -1 1 -2 1 example_sorted.bed example_length_alt.txt |
如果第二个文件没有 chr3, join
之后也没了!!
我们可以通过 -a
选项指定哪一个文件可以不遵循配对
1 | $ join -1 1 -2 1 -a 1 example_sorted.bed example_length_alt.txt |
awk 可以说是一门语言了
awk
是文本处理的一把好手,虽然它不能像 python, R 干一些高级复杂的主题工作,但是它具备完整的命令操作和编程体系。
awk
是一门语言,我不可能在学习的时候能够逻辑清晰详细地介绍给大家。主要是还通过实例来了解用法,加深认识。手册可以参考http://man.linuxde.net/awk。
首先要明白的是, awk 按行处理数据。在 shell 知识里,如果把一个文档看做一张表。那么一行就是一个记录,一列就是一个域。可以看出,awk
就是按记录处理文本的。
其次是 awk
的程序结构是: pattern {action}
pattern
可以是表达式或者正则表达式。pattern
有点像 if
语句,当它满足时就会执行相应的动作。
另一个 awk
核心是它用 $0 表示所有列,$1,$2 等等表示对应的列。我们可以很方便地用它进行操作。
1 | $ awk '{print $0}' example.bed |
print
语句就像动作一样输出你操作的结果。
1 | $ awk '{ print $2 "\t" $3}' example.bed |
了解上述几个语句的不同。
表示染色体名一般用带 chr 或者不带, chr 标志两种方式。当我们要用到这两种时,肯定要让它们能够对应起来,也就是转换。 awk
命令可以非常方便地添加 chr 标记。
下面我先把例子文件的 chr 去掉,然后加上试试。
1 | $ awk '{ print $1}' example.bed |
awk
作为一门编程语言,它支持各种操作符(运算,逻辑,判断)喔。
1 | $ awk '$3 - $2 >18' example.bed |
还有 awk
存在一些变量,像 NR
表示行号, OFS
表示输出分隔符等。
1 | $ awk 'NR >= 3 && NR <= 5' example.bed |
如果我们想把 gtf 文件转换成为 bed 格式,可以使用:
1 | $ head -n1000 Homo_sapiens.GRCh37.75.gtf | awk '!/^#/{ print $1 "\t" $4-1 "\t" $5} ' | head -n 3 |
用 sed 进行流编辑
sed
工作流:读取$\to$
执行命令$\to$
显示。默认情况,所有的命令都会一个叫做在模式空间(pattern buffer)
的缓冲区进行。因此不会改变原始输入文件的内容。
语法规则
sed
即支持在命令行中用单引号输入执行命令,也支持执行含有 sed
命令的文件。使用方式如下:
1 | sed [‐n] [‐e] 'command(s)' files |
选项
sed
后面首先需要跟参数,支持的参数有:
选项与参数:
-n
:禁止显示所有输入内容,只显示经过 sed 处理的行(常用) ;-e
:直接在命令列模式上进行 sed
的动作编辑,接要执行的一个或者多个命令 ;-f
:执行含有 sed
动作的文件;-r
:sed
的动作支持的扩展正则(默认基础正则);-i
:直接修改读取的文件内容,不输出。
作用区域
默认情况下,sed
命令会作用于文本数据的所有行。如果只想作用于某些行时,则需要使用在命令通过行号或者文本过滤的方式前指明作用区域。
行号
使用数字行号时,类似于 R 中的向量子集提取。单独数字表示某一行,逗号分割指示行范围,另外 m,+n
表示从 m
行开始向下n
行, m~n
表示从 m
行开始的每 n
行。
例如 sed ‐n '2~2 p' test.txt
输出偶数行, sed ‐n '1~2 p' test.txt
输出奇数行内容。
文本过滤
/pattern/ command
可以只在包含 pattern
的行中执行命令。如 sed ‐n '/hello/ p' test.txt
只会打印出包含hello 的行。 sed ‐n '/hello/, /world/ p' test.txt
打印两者之间的所有行。
特殊情况下也可以将文本过滤和行号结合使用, sed ‐n '/hello/,+5 p' test.txt
打印第一次出现 hello 的下面5行。
命令
p 复制
复制模式空间中的内容,如果不和 -n参数连用,每一行都会在屏幕输出两次,一行正常输出一行复制,结合 -n
参数后就可以打印需要的内容。
d 删除
没什么可以说的,支持按照行号或者匹配来删除。
i 插入
有的时候一个结果文件没有header,使用 sed
可以轻松完成。在匹配位置之前插入内容。
1 | $ sed '1i name\tlength\foldchange' test.txt |
a 追加
和插入命令的区别在于在匹配位置后一行插入内容,如果想在末尾插入一行信息时将 $
作为地址。
1 | $ sed '$a auther:zhaofei' test.txt |
c 行替换
有了行的删除插入和追加自然也就会有行替换。
1 | $ sed '$c auther:zhaofei' test.txt |
y 字符转换
sed
中的 y
命令可以实现一一映射的字符替换(注意和 s
命令的区别)。
1 | $ [address]y/inchars/outchars/ |
l 输出隐藏字符
类似与 cat -A
,但是显示隐藏字符形式不同。
w 写入新文件
增强版的 cp
,只复制自己想要的东西,也可以将一个文件按不同的筛选条件分开保存。
1 | $ sed -n -e '/a/ w a.txt' -e '1,10 w b.txt' test.txt |
r 读取文件
如果现在a文件的某个地方插入b文件,如在第三行插入
1 | $ sed '3r b.txt' a.txt |
e 执行外部命令
首先要区别于参数 -e
,这个 e
是在 ‘’ 里面的命令
1 | $ sed 'e echo "hello"' test.txt |
i 反向执行
在命令前加!反向执行命令
1 | $ sed '/hello/!d' test.txt |
n 匹配行下移一行操作
提前读取当前行的下一行内容,并且覆盖当前模式空间中的行
1 | $ seq 5 |sed '3{n;d}' |
= 打印行号
1 | $ sed '/hello/!d;=' test.txt |
s 替换
通用写法 :
1 | $ [address1[,address2]]s/pattern/replacement/[flags] |
这里 pattern
部分支持正则匹配,flags
包括 n
(替换第 n 个匹配项),g
(全局替换),p
(输出改变的行,结合-n), i
(忽略大小写匹配),w
(保存改变的行到新文件)。
如果要替换的的内容包括了 /
,第一种方式是使用 \/
进行转义,第二种方法是使用 @ | ! ^
作为分隔符。
有时候我们会对文件中的目录进行替换,可以下面的写法
1 | $ pwd |sed 's@/home/zhaofei@/home/feizhao@' |
pattern
支持各种正则表示法,例如
1 | ^ 行首 |
在进行匹配替换时,我们有时候并不想删除匹配的内容,只是希望其以另一种形式和替换内容一起出现。在 sed
中,特殊字符 &
用来存储匹配模式中的内容。
例如:
1 | $ sed 's/[[:digit:]]/number = &/ test.txt |
sed
单行命令 可以完全替代上面所有的命令。
1 | # 删除空行 |
Reference
转自 - 生信技能树: linux命令行文本操作一文就够