为啥我的工具输出会覆盖自身,我该如何解决?

Posted

技术标签:

【中文标题】为啥我的工具输出会覆盖自身,我该如何解决?【英文标题】:Why does my tool output overwrite itself and how do I fix it?为什么我的工具输出会覆盖自身,我该如何解决? 【发布时间】:2018-01-28 01:51:25 【问题描述】:

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

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

现在回到会导致推荐的典型问题

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

what isgoingon

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

awk 'print $2, $1' file

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

isgoingon what

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

 whatngon

或者我将输出分成两行:

isgoingon
 what

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

【问题讨论】:

感谢您提出这个问题。最有用的一个,因为它是最常见的错误!默认情况下应链接到所有awksed 问题。 这在精神上与***.com/questions/39527571/… 非常相似——我们需要多个规范吗? 【参考方案1】:

问题是您的输入文件使用 DOS 行尾 CRLF 而不是 UNIX 行尾 LF 并且您正在其上运行 UNIX 工具,因此 CR 仍然是正在操作的数据的一部分通过 UNIX 工具。 CR 通常用\r 表示,当您在文件上运行cat -vELF\n 并显示为$ 和@987654332 时,可以看作是一个控制-M (^M) @。

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

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 之前将光标返回到行首,这就是为什么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' 时要小心,因为这会删除文件中的所有 \rs,而不仅仅是每行末尾的那些。

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

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

但其他 awk 不允许这样做,因为 POSIX 只要求 awk 支持单个字符 RS,而大多数其他 awk 会悄悄地将RS='\r\n' 截断为RS='\r'。您可能需要为 gawk 添加 -v BINMODE=3 才能看到 \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,那么您将无法再将换行符中的字段内换行符作为行尾,所以如果您想这样做,我建议您将所有字段内换行符转换为某些内容否则首先,例如这会将所有字段内LFs 转换为制表符,并将所有以CRLFs 结尾的行转换为LFs:

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

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

还请注意,虽然 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$

【讨论】:

我理解您关于谨慎使用tr -d '\r' 的评论,但出于专业的好奇心:您是否曾经遇到过Windows CSV 文件,该文件在某处具有'\r' 的预期有效负载? 我写了File::Edit::Portable 来实现跨平台的无缝读写文件。 @Arminius 我有,就在昨天。那个 csv 文件当然有问题,但它有 firstname\rlastnames 和 first\nlasts。 @JamesBrown 这就是我向@EdMorton 提问的原因。我必须处理大量输入数据并在数据中找到一个单独的 \r 使我的验证例程“发出哔哔声”。我有一个案例(没有撒谎!),几年前有人使用\r 作为列,\n 作为行分隔符。 :-)【参考方案2】:

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

如果在 Fedora 系统上 dnf install dos2unix 将放置 dos2unix 工具(如果不安装的话)。

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

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

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

tr -d '\r' < infile > outfile

【讨论】:

tr -d '\r' &lt; infile &gt; outfile 表单将销毁所有应该在文件中而不是 Windows 行结尾部分的 \r。最好使用sed 's/\r$//',因为这会将替换限制在行尾。 @dawg 好点。因此提高了使用 dos2unix 的安全性。【参考方案3】:

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

因此,如果您有一个“额外”,您可以使用正则表达式找到并删除它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,您可以在 PCRE 中使用 (?&gt;\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 不支持 \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 可能是对“换行符”的最终处理方式的最佳选择。

【讨论】:

在我看来,只有当您必须对不知道行尾是什么的输入进行操作时,使用 \R 才有用,但您可以保证另一个可能的结尾行字符不能出现在输入中。我的意思是,如果我的输入文件使用\r\n 行结尾并且可以在字段中包含\v\n(我希望我可以使用Excel 生成),那么我可以有一个1 字段记录,即"foo\v\nbar"\r\n那么我将如何使用\R 来识别行?我可以将行识别为由\r\n 分隔的字符串,但不能由\R\n 分隔,因为后者将包括\v\n mid-record。 对多个 cmets 感到抱歉,我只是想不通我为什么要使用 \R,而且我绝对不明白这里发生了什么:1) od -c &lt; file 输出 " f o o \v \n b a r " \r \n 2) perl -pe 's/\r$/\n/' file | od -c 输出 " f o o \v \n b a r " \n \n 3) perl -pe 's/\R$/\n/' file | od -c1 输出 " f o o \n \n b a r " \n。正如我所期望的那样,使用\R 会弄乱\v\n 中间记录,但是为什么在正则表达式中使用\r$\r\n 变成\n\n 而使用\R$ 时只是\n?第二个\n去哪儿了? @EdMorton: 2 - 单个 \n 被 Perl 视为行分隔符/记录分隔符,即使引用。 \v 在正则表达式 s/\R$/\n/ 中被视为额外的行分隔符,因此您将获得 \n\n 以替换序列 \v\n。序列\r\n 中的\n 再次被视为行分隔符。 s/\R$/\n/\r\n 视为单行分隔符,因此您得到一个 \n。如果您想将 "foo\v\nbar"\r\n 视为单个记录,则需要 CSV 解析器或更完整的正则表达式来描述它。 @EdMorton: 3 - \R 的尝试是对 UTF-X、XML 或行尾未知的通用文本有用的“通用换行符”。您可以使用动词来控制包含的内容。 假设您已将工具设置为正确读取行,正则表达式\R$ 将删除\R 中包含的任何未包含在工具行处理中的字符。请注意,PCRE \v 字符类与\v 的 ANSI C 字符定义不同。字符类\v 等价于/[\n\cK\f\r\x85\x2028\x2029]/ 就我的口味而言,这与 BRE 和 ERE 有点不同,我觉得猜测可能是错误的,可能是行尾,但可能出现在您输入的其他地方,但我觉得这是一个坏主意假设它在某些情况下一定很有用,否则“他们”不会想出它。感谢您的解释。

以上是关于为啥我的工具输出会覆盖自身,我该如何解决?的主要内容,如果未能解决你的问题,请参考以下文章

为啥我打开 access 数据库 (.accdb) 时我的 winform 会调整大小,我该如何解决?

为啥我的导航标签会在悬停时移动?我该如何解决?我只想使用 css,

CSS3:为啥这个过渡不起作用,我该如何解决?

这是啥覆盖错误,我该如何解决? [复制]

为啥我的搜索功能没有按预期工作,我该如何解决?

为啥 Main.storyboard 中会出现内部不一致,我该如何解决?