问题在于您的输入文件使用了DOS的换行符
CRLF
而不是UNIX的换行符
LF
,而您正在运行一个UNIX工具对其进行操作,因此
CR
仍然是UNIX工具操作的一部分数据。
CR
通常用
\r
表示,在运行
cat -vE
命令时,可以看到它显示为控制字符
^M
,而
LF
则是
\n
,在
cat -vE
命令中显示为
$
。
因此,您的输入文件实际上不只是:
what isgoingon
其实是这样的:
what isgoingon\r\n
如你所见,通过
cat -vE
命令:
$ cat -vE file
what isgoingon^M$
和 od -c
:
$ od -c file
0000000 w h a t i s g o i n g o n \r \n
0000020
所以当你在UNIX工具(比如awk)上运行一个文件时,它会将
\n
视为行结束符。读取行的过程中,
\n
会被消耗掉,但是这会导致2个字段的存在。
<what> <isgoingon\r>
注意第二个字段末尾的
\r
。
\r
表示
回车符,字面上是将光标返回到行的起始位置的指令。所以当你执行以下操作时:
print $2, $1
awk会将其打印到终端,终端会打印出
isgoingon
并将光标返回到行的起始位置,然后打印一个空格,接着打印
what
,这就是为什么
what
似乎覆盖了
isgoingon
的起始部分。
解决方法如下:
dos2unix file
sed 's/\r$//' file
awk '{sub(/\r$/,"")}1' file
perl -pe 's/\r$//' file
显然,在某些UNIX变体(例如Ubuntu),
dos2unix
也被称为
fromdos
。
如果你决定使用
tr -d '\r'
,要小心,因为这会删除文件中
所有的
\r
,而不仅仅是每行末尾的
\r
。(更多细节见下文。)
注释
使用awk处理DOS换行符
GNU awk可以通过适当设置
RS
来解析具有DOS换行符的文件:
gawk -v RS='\r\n' '...' file
但其他的awk不允许这样做,因为POSIX只要求awk支持单个字符的RS,而大多数其他的awk会将
RS='\r\n'
静默截断为
RS='\r'
。你可能需要添加
-v BINMODE=3
来让gawk能够看到
\r
,因为底层的C原语会在某些平台上去除它们,例如cygwin。
包含换行符的CSV数据
需要注意的一件事是,由Windows工具如Excel创建的CSV文件会使用CRLF
作为行尾,但可以在CSV的特定字段中嵌入LF
,例如:
"field1","field2.1
field2.2","field3"
真的很:
"field1","field2.1\nfield2.2","field3"\r\n
所以,如果你只是将\r\n转换为\n,那么你就无法再区分字段内的换行符和行尾的换行符了。所以,如果你想要做到这一点,我建议先将所有字段内的换行符转换为其他字符,例如,将所有字段内的LF转换为制表符,并将所有行尾的CRLF转换为LF:
gawk -v RS='\r\n' '{gsub(/\n/,"\t")}1' file
不使用GNU awk进行类似操作留作练习,但使用其他awk时,需要在读取时将不以CR
结尾的行合并。
Awk的默认FS
还要注意的是,尽管CR是[[:space:]]
POSIX字符类的一部分,但它不是在使用默认FS " "
时作为分隔字段的空白字符之一,其空白字符只有制表符、空格和换行符。如果输入中可以在CRLF之前有空格,这可能会导致混淆的结果:
$ printf 'x y \n'
x y
$ printf 'x y \n' | awk '{print $NF}'
y
$
$ printf 'x y \r\n'
x y
$ printf 'x y \r\n' | awk '{print $NF}'
$
这是因为在以LF换行符结尾的行的开头/结尾处忽略了尾随字段分隔符的空白,但如果它之前的字符是空白,则
\r
是以CRLF换行符结尾的行上的最后一个字段。
$ printf 'x y \r\n' | awk '{print $NF}' | cat -Ev
^M$
awk
和sed
问题。 - kvantour