为什么我的工具输出会自行覆盖,我该如何解决?

Why does my tool output overwrite itself and how do I fix it?

此问题的目的是为答案为 "you have DOS line endings" 的日常问题提供答案,因此我们可以简单地将它们作为该问题的副本关闭,而无需重复相同的答案 令人作呕.

注意:这不是任何现有问题的重复。此问答的目的不仅是提供 "run this tool" 答案,而且还解释了问题,这样我们就可以在这里指出任何有相关问题的人,他们也会找到明确的解释,说明为什么他们会被指到这里作为工具运行所以解决了他们的问题。我花了几个小时阅读所有现有的问答,它们都缺乏对问题的解释、可用于解决问题的替代工具、and/or pros/cons/caveats 可能的解决方案。此外,他们中的一些人接受了非常危险且永远不应使用的答案。

现在回到会在此处引荐的典型问题

我有一个包含 1 行的文件:

what isgoingon

当我使用这个 awk 脚本颠倒字段顺序打印它时:

awk '{print , }' file

而不是看到我期望的输出:

isgoingon what

我得到了应该在行尾的字段出现在行的开头,覆盖了行开头的一些文本:

 whatngon

或者我将输出拆分为 2 行:

isgoingon
 what

可能是什么问题,我该如何解决?

问题是您的输入文件使用 CRLF 的 DOS 行结尾,而不是仅 LF 的 UNIX 行结尾,并且您正在 运行 在其上安装 UNIX 工具,因此CR 仍然是 UNIX 工具正在操作的数据的一部分。 CR 通常用 \r 表示,当您在文件上 运行 cat -vELF\n 并显示为 $cat -vE.

所以你的输入文件不只是:

what isgoingon

实际上是:

what isgoingon\r\n

如您所见 cat -v:

$ 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

所以当你 运行 像 awk 这样的 UNIX 工具(它把 \n 当作行尾)在文件上时,\n 被读取行消耗了,但这将 2 个字段保留为:

<what> <isgoingon\r>

注意第二个字段末尾的 \r\r 表示 Carriage Return 字面意思是 return 将光标移至行首的指令,因此当您执行以下操作时:

print , 

awk 将打印 isgoingon,然后 return 光标在打印 what 之前到达行首,这就是 what 似乎覆盖开头的原因isgoingon.

要解决此问题,请执行以下任一操作:

dos2unix file
sed 's/\r$//' file
awk '{sub(/\r$/,"")}1' file
perl -pe 's/\r$//' file

显然 dos2unix 在某些 UNIX 变体(例如 Ubuntu)中又称为 frodos

如果您决定使用通常建议的 tr -d '\r',请小心,因为这会删除文件中的 all \r,而不仅仅是那些每行的结尾。

请注意,GNU awk 允许您通过简单地适当设置 RS 来解析具有 DOS 行结尾的文件:

gawk -v RS='\r\n' '...' file

但其他 awks 不允许这样做,因为 POSIX 只需要 awks 支持单个字符 RS,而大多数其他 awks 会悄悄地 t运行cate RS='\r\n'RS='\r'.您可能需要为 gawk 添加 -v BINMODE=3 才能看到 \rs,尽管底层 C 原语会在某些平台上剥离它们,例如cygwin.

需要注意的一件事是,由 Windows 工具(如 Excel)创建的 CSV 将使用 CRLF 作为行尾,但可以在其中嵌入 LF CSV 的特定字段,例如:

"field1","field2.1
field2.2","field3"

真的是:

"field1","field2.1\nfield2.2","field3"\r\n

所以如果你只是将 \r\ns 转换为 \ns 那么你就不能再将换行符中的字段中的换行符作为行尾,所以如果你想这样做我建议转换所有的场内换行到别的东西,例如这会将所有字段内 LFs 转换为制表符,并将所有以 CRLFs 结尾的行转换为 LFs:

gawk -v RS='\r\n' '{gsub(/\n/,"\t")}1' file

在没有 GNU awk 的情况下做类似的练习,但对于其他 awk,它涉及合并阅读时不以 CR 结尾的行。

另请注意,虽然 CR 是 [[:space:]] POSIX 字符 class 的一部分,但它不是作为分隔字段包含的白色 space 字符之一使用默认" "的FS,其白色space字符只有制表符、空白和换行符。如果您的输入在 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}'

$

这是因为尾部字段分隔符白色 space 在具有 LF 行结尾的行的 beginning/end 处被忽略,但是 \r 如果前面的字符是白色,则最后一个字段以 CRLF 行结尾space:

$ printf 'x y \r\n' | awk '{print $NF}' | cat -Ev
^M$

运行dos2unix。虽然您可以使用自己编写的代码来操纵行尾,但 Linux / Unix 世界中存在一些实用程序,它们已经为您完成了这项工作。

如果在 Fedora 系统上 dnf install dos2unix 将安装 dos2unix 工具(不应该安装)。

有一个类似的 dos2unix deb 包可用于基于 Debian 的系统。

从编程的角度来看,转换很简单。在文件中搜索序列 \r\n 的所有字符并将其替换为 \n.

这意味着有数十种方法可以使用几乎所有可以想象到的工具将 DOS 转换为 Unix。一种简单的方法是使用命令 tr,您只需将 \r 替换为空即可!

tr -d '\r' < infile > outfile

对于行结尾未知的文件,您可以使用 \R shorthand character class in PCRE。对于 Unicode 或其他平台,还有更多的行尾要考虑。 \R 形式是 Unicode 联盟推荐的 class 字符,用于表示通用换行符的所有形式。

因此,如果您有一个 'extra',您可以使用正则表达式找到并删除它 s/\R$/\n/ 会将任何行尾组合规范化为 \n。或者,您可以使用 s/\R/\n/g 来捕获 'line ending' 的任何概念并标准化为 \n 字符。

给定:

$ printf "what\risgoingon\r\n" > file
$ od -c file
0000000    w   h   a   t  \r   i   s   g   o   i   n   g   o   n  \r  \n
0000020

Perl 和 Ruby 以及大多数 PCRE 实现 \R 结合字符串结束断言 $(多行模式下的行结束):

$ perl -pe 's/\R$/\n/' file | od -c
0000000    w   h   a   t  \r   i   s   g   o   i   n   g   o   n  \n    
0000017
$ ruby -pe '$_.sub!(/\R$/,"\n")' file | od -c
0000000    w   h   a   t  \r   i   s   g   o   i   n   g   o   n  \n    
0000017

(注意两个词之间的 \r 是正确的)

如果您没有 \R,您可以在 PCRE 中使用 (?>\r\n|\v) 的等价物。

使用直接 POSIX 工具,你最好的选择可能是 awk 像这样:

$ awk '{sub(/\r$/,"")} 1' file | od -c
0000000    w   h   a   t  \r   i   s   g   o   i   n   g   o   n  \n    
0000017

有点用的东西(但知道你的局限性):

tr 删除所有 \r,即使在另一个上下文中使用也是如此(当然很少使用 \r,并且 XML 处理需要 \r被删除,所以 tr 是一个很好的解决方案):

$ tr -d "\r" < file | od -c
0000000    w   h   a   t   i   s   g   o   i   n   g   o   n  \n        
0000016

GNU sed 有效,但 POSIX sed 无效,因为 POSIX.[=38 不支持 \r\x0D =]

仅限 GNU sed:

$ sed 's/\x0D//' file | od -c   # also sed 's/\r//'
0000000    w   h   a   t  \r   i   s   g   o   i   n   g   o   n  \n    
0000017

Unicode Regular Expression Guide 可能是对 "newline" 的最终处理的最佳选择。