17

这个问题的目的是为回答是“你有 DOS 行结尾”的日常问题提供一个答案,这样我们就可以简单地将它们作为这个问题的副本关闭,而不会重复相同的答案令人作呕

注意:这不是任何现有问题的重复。此问答的目的不仅是提供“运行此工具”的答案,而且是为了解释这个问题,以便我们可以在这里指出任何有相关问题的人,他们也会清楚地解释为什么他们被指向这里作为工具运行所以解决他们的问题。我花了几个小时阅读所有现有的问答,他们都缺乏对问题的解释、可用于解决问题的替代工具和/或可能解决方案的优点/缺点/警告。他们中的一些人也接受了那些非常危险且永远不应该使用的答案。

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

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

what isgoingon

当我使用这个 awk 脚本打印它来反转字段的顺序时:

awk '{print $2, $1}' file

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

isgoingon what

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

 whatngon

或者我将输出分成两行:

isgoingon
 what

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

4

3 回答 3

23

问题是您的输入文件使用 DOS 行结尾CRLF而不是 UNIX 行结尾 justLF并且您正在其上运行 UNIX 工具,因此 UNIX 工具CR正在操作的数据的剩余部分。当您在文件上运行时CR,通常表示为\r并且可以将其视为 control-M ( ^M),而is和显示为.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这实际上是将光标返回到行首的指令,所以当你这样做时:

print $2, $1

awk 将打印isgoingon,然后在打印前将光标返回到行首,这what就是为什么.whatisgoingon

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

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

显然dos2unixfrodos某些 UNIX 变体(例如 Ubuntu)中也是如此。

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

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

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

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

需要注意的一件事是,由 Excel 等 Windows 工具创建的 CSV 将CRLF用作行尾,但可以将LFs 嵌入到 CSV 的特定字段中,例如:

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

是真的:

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

因此,如果您只是将\r\ns 转换为\ns,那么您将无法再将换行符中的字段内换行符作为行结尾,所以如果您想这样做,我建议您首先将所有字段内换行符转换为其他内容,例如,这将转换所有内部换行符-fieldLFs到制表符并将所有行尾CRLFs 转换为LFs:

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

在没有 GNU awk 的情况下做类似的练习,但使用其他 awk 时,它涉及组合CR在读取时不以结尾的行。

另请注意,尽管 CR 是[[:space:]]POSIX 字符类的一部分,但在使用默认 FS of 时,它不是作为分隔字段包含的空白字符之一" ",其空白字符只有制表符、空白符和换行符。如果您的输入在 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 行结尾的行的开头/结尾处忽略尾随字段分隔符空格,但如果之前的字符是空格,则它是具有 CRLF 行结尾的行上的最后一个字段\r

$ printf 'x y \r\n' | awk '{print $NF}' | cat -Ev
^M$
于 2017-08-19T14:12:53.760 回答
4

您可以将PCRE中的\R 速记字符类用于行尾未知的文件。对于 Unicode 或其他平台,还有更多行尾需要考虑。该表单是 Unicode 联盟推荐的字符类,用于表示通用换行符的所有形式。\R

因此,如果你有一个“额外”,你可以找到并删除它,正则表达式s/\R$/\n/会将行尾的任何组合标准化为\n. 或者,您可以使用s/\R/\n/g来捕获“行尾”的任何概念并将其标准化为\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你可以使用(?>\r\n|\v)PCRE 中的等价物。

使用直接的 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

GNUsed工作,但不是 POSIX,因为sedPOSIX不支持。\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正则表达式指南可能是对“换行符”的最终处理方式的最佳选择。

于 2017-08-19T16:44:10.460 回答
2

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

如果在 Fedora 系统dnf install dos2unix上将把该dos2unix工具放在适当的位置(如果它没有安装的话)。

有一个类似的dos2unixdeb 软件包可用于基于 Debian 的系统。

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

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

tr -d '\r' < infile > outfile
于 2017-08-19T14:26:59.853 回答