sed命令和tr命令的坑

  • linux很多常用的命令,如果不彻底研究一下,只是baidu或者google出来直接用,会有很多坑。今天遇到一个坑,仔细研究了一下,在这记个笔记。

需求

把log中含有某关键字的所有行输出到另外txt中。

解决办法

  • 根据关键字抽取一行比较简单,暂时忽略。
# cat service.log |sed -n '/AAA/p' > result.txt
  • 困难点是过滤有换行符的一行log

对策

tr,sed

tr用法

为了实验,先创建了文本例子。
其中sql输出部分是用换行和tab来调整格式的(也就是用tab来错位)。

[test@CentOS7 tmp]$ cat service.log 
1 AAAAA
2 BBBBB
3 CCCCC  SELECT
    FROM
    WHERE
4 DDDDD UPDATE
    WHERE

5 EEEEE
6 FFFFF
7 XXXXXX SELECT COUNT(1) FROM TABLE WHERE ;;;;
容易看出空格/tab的截图模式

按照使用其他文本编辑器(我常用EmEditor)的习惯,习惯性地想到把[\n\t]替换成[\t]或者[空格],这样sql部分的换行就会被提升至一行了。
实验对象准备好了,开始敲命令实验吧。

[test@CentOS7 tmp]$ $ cat service.log | tr "\n\t" "\t" > result.txt
[test@CentOS7 tmp]$ cat result.txt 
1 AAAAA 2 BBBBB 3 CCCCC  SELECT     FROM        WHERE   4 DDDDD UPDATE  WHERE       5 EEEEE 6 FFFFF 7 XXXXXX SELECT COUNT(1) FROM TABLE WHERE ;;;;  [test@CentOS7 tmp]$ 
  • 注意点:因为tr命令最后输出不带换行符,所以[test@CentOS7]也跟在了文本后面显示了,回车即可解决显示问题。

cat出结果中空格和tab看不到,拷贝到文本编辑器里看一下。


result

截图中点代表空格,线代表tab。

我的期望是有换行且第二行开头是tab的行作为对象(普通换行不受影响),[\n\t]作为一个整体替换成[\t]。
但是看输出的结果,是按照顺序一对一地把[\n\t](前字符串)里的元素逐个替换成[\t](后字符串)。

这个用法失败,tr的坑算是知道怎么回事了。

sed的用法

  • sed简介:
    sed是一种流编辑器,它是文本处理中非常中的工具,能够完美的配合正则表达式使用,功能不同凡响。处理时,把当前处理的行存储在临时缓冲区中,称为“模式空间”(pattern space),接着用sed命令处理缓冲区中的内容,处理完成后,把缓冲区的内容送往屏幕。接着处理下一行,这样不断重复,直到文件末尾。文件内容并没有 改变,除非你使用重定向存储输出。Sed主要用来自动编辑一个或多个文件;简化对文件的反复操作;编写转换程序等。
    加粗的是重点,逐行处理,避免读入大size文件,导致内存被占满的风险。
  • 最简单使用方式--替换一行中的某部分
    格式:sed 's/要替换的字符串/新的字符串/g' (要替换的字符串可以用正则表达式)

还是继续使用文本编辑器的思路,把[\n\t]作为一个整体替换成[\t]。
实验结果如下:

[test@CentOS7 tmp]$ cat service.log | sed 's/\n\t/\t/g' > result.txt 
[test@CentOS7 tmp]$ cat result.txt 
1 AAAAA
2 BBBBB
3 CCCCC  SELECT
    FROM
    WHERE
4 DDDDD UPDATE
    WHERE

5 EEEEE
6 FFFFF
7 XXXXXX SELECT COUNT(1) FROM TABLE WHERE ;;;;
[test@CentOS7 tmp]$ 

什么变化都没有。
google之后发现,sed命令竟然对\n无效。
理由是sed命令的作用域只是当前行(简介粗体)。如果要跨行的话,需要加一堆别的参数。

使用如下解决方案:

sed ':a;N;$!ba;s/\n/ /g'

这将在一个循环里读取整个文件,在内存中拼成一行,然后将换行符替换成一个空格,输出到屏幕或者重定向到别的文件中。

  • 各个说明
    1:通过 [:a]创建一个标记,类似C语言中的goto(也就是说后面代码中如果有a,就跳转到上一个a执行的地方)。当然[a]只是个名字,可以用别的字母或单词。
    2:通过[N]追加当前行和下一行到模式区域。[N]是sed命令专有参数,不能修改。
    3:如果处于最后一行前,跳转到之前的标记处。
    [$!ba]的[$!]的意思是不在最后一行做ba(b代表无条件跳转到上一次a执行的行)操作。
    4:最后置换操作把模式区域(就是已经读到内存中整理成一行的整个文件)的每一个换行符换成一个空格。

实验一下

[test@CentOS7 tmp]$ cat service.log | sed ':a;N;$!ba;s/\n\t/\t/g' > result.txt 
[test@CentOS7 tmp]$ cat result.txt 
1 AAAAA
2 BBBBB
3 CCCCC  SELECT    FROM    WHERE
4 DDDDD UPDATE    WHERE

5 EEEEE
6 FFFFF
7 XXXXXX SELECT COUNT(1) FROM TABLE WHERE ;;;;
[test@CentOS7 tmp]$ 

效果出来了。
接下来就是根据关键字提取对应的行了。

好像哪里不对???

上面的方法实际上是把整个文件都记录进内存,当文件很大的时候,这有把内存全都吃掉的危险。

所以,换个方式。
思路:既然知道了 [:a]标记,有循环处理的功能了,不妨试试。

  • 查看操作对象文件内容
[test@CentOS7 tmp]$ cat service.log  
1 AAAAA
2 BBBBB
3 CCCCC  SELECT
    FROM
    WHERE
4 DDDDD UPDATE
    WHERE

5 EEEEE
5 EEEEE
6 FFFFF
7 XXXXXX SELECT COUNT(1) FROM TABLE WHERE ;;;;
  • 把替换操作放到循环里面(上面例子是放到循环外,最后处理)
[test@CentOS7 tmp]$ cat service.log | sed ':a;N;s/\n\t/\t/g;ba'
1 AAAAA
2 BBBBB
3 CCCCC  SELECT FROM    WHERE
4 DDDDD UPDATE  WHERE

5 EEEEE
5 EEEEE
6 FFFFF
7 XXXXXX SELECT COUNT(1) FROM TABLE WHERE ;;;;

结果成功!!

内容扩展

关于sed命令中[b]的用法,跟[t]容易弄混。作为参照,我把ta执行的结果也贴出来。这个实际上是失败的例子,不过却能很好帮助理解区别。

[test@CentOS7 tmp]$ cat service.log | sed ':a;N;s/\n\t/\t/g;ta'
1 AAAAA
2 BBBBB
3 CCCCC  SELECT FROM    WHERE
4 DDDDD UPDATE
    WHERE

5 EEEEE
5 EEEEE
6 FFFFF
7 XXXXXX SELECT COUNT(1) FROM TABLE WHERE ;;;;

具体说明,这里省略。详细可以参考以下例子。
http://man.linuxde.net/sed :说明和例子详细齐全。只是有很多参数的描述太难理解。
关于b、t的区别,可以参考下面两个链接。
http://blog.chinaunix.net/uid-639516-id-2692525.html
http://www.bubuko.com/infodetail-1655818.html

你可能感兴趣的:(sed命令和tr命令的坑)