grep匹配回车符的问题

对于不识别CRLF格式文本文件的grep命令(比如Linux和Cygwin下面的grep)来说,回车符(carriage return)\r 并不是有特殊含义的字符,而是普通字符,所以如果要匹配回车符,只需要找到一个能够输入回车符这个字符的方式即可,而bash$'\r' 就能满足要求。下面举例说明。

CVS 在windows的CVS目录下Tag文件是是DOS格式文件文件,把这个文件拷贝到Linux,查看内容如下:

]# cat Tag
Trelease
]# cat Tag | od -c
0000000   T   r   e   l   e   a   s   e  \r  \n
0000012

现在要匹配行尾的e ,为了对比,我们还建立一个Tag文件的副本TagUnix,使用dos2unix 命令将它转成Unix风格的文本文件,如下所示:

]# cat TagUnix | od -c
0000000   T   r   e   l   e   a   s   e  \n
0000011

使用e$ 作为模式,结果如下

]# grep e$ Tag
]# grep e$ TagUnix
Trelease

这是说明因为Tag的行尾并不是e ,而是e\r 。那么使用e$'\r'$ 作为模式如何?结果如下:

]# grep e$'\r'$ Tag

]#

得到一个空行输出。这是为什么?这说明,实际上有一行匹配,但是由于这一行的内容包含\r,而它对于终端是有特殊含义的,所以终端就在输出\r的时候,将光标回到行首,于是乎之前输出的内容就看不到,就只能看到一个空行(这似乎说明,Linux下面兼容CRLF格式的不是终端,而是输出文本文件的命令如more, cat等等)。
关于输出的差别,补充一个例子(在centos是如此,在cygwin下面也是如此):

]#  grep '3306' my.ini | od -c
0000000   p   o   r   t   =   3   3   0   6  \r  \n   p   o   r   t   =
0000020   3   3   0   6  \r  \n
0000026
]# grep '3306'$'\r''$' my.ini | od -c
0000000   p   o   r   t   =   3   3   0   6  \r  \n   p   o   r   t   =
0000020   3   3   0   6  \r  \n
0000026
]#  grep '3306' my.ini
port=3306
port=3306
]# grep '3306'$'\r''$' my.ini

]#

其中my.ini是windows下mysql数据库的配置文件,是以CRLF结尾的。上面的例子中,输出结果通过od命令查看是一样的,但是显示到终端后,显示效果不一样。这种情况一时让我有点糊涂,我只能猜测如下:当EOF是CRLF的时候,grep命令实际上是能够识别出来的,如果是正常字符串那么只有除CRLF的部分参与匹配,得到匹配行的内容是3306,然后输出这行内容的时候,再输出一个EOF,即CRLF(假设终端处理一起的CRLF时,实际上只输出一个换行)。当匹配模式中包含\r的时候,grep将这个文件识别为EOF是LF,那么得到匹配内容3306$'\r'这这一行,输出这行内容(此时这行内容包含CR,而CR单独出现,没有与后面的LF连在一起,终端解释为回车),然后再输出一个EOF,即LF。从最终输出内容的十六进制来看,两者一样,但是对于终端显示,两者行为不同。

解决显式效果的办法也简单,重定向到文件,再输出来,或者直接重定向到morecat等命令(是上面重定向到文件的一种特殊情况),或者通过tr命令删除\r,都可以。如下所示:

]# grep e$'\r'$ Tag | more
Trelease
]# grep e$'\r'$ Tag | cat
Trelease
]# grep e$'\r'$ Tag | tr -d '\r'
Trelease
(0)

相关推荐