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。从最终输出内容的十六进制来看,两者一样,但是对于终端显示,两者行为不同。
解决显式效果的办法也简单,重定向到文件,再输出来,或者直接重定向到more
、cat
等命令(是上面重定向到文件的一种特殊情况),或者通过tr
命令删除\r
,都可以。如下所示:
]# grep e$'\r'$ Tag | more
Trelease
]# grep e$'\r'$ Tag | cat
Trelease
]# grep e$'\r'$ Tag | tr -d '\r'
Trelease