为什么我的工具输出会覆盖自己以及如何修复它?

时间:2017-08-19 14:08:02

标签: unix awk sed dos2unix

这个问题的目的是为每日问题提供答案,答案是"你有DOS行结尾"所以我们可以简单地将它们作为这个副本重复关闭而不重复相同的答案 ad nauseam

注意:这不是任何现有问题的重复。本Q& A的目的不仅仅是提供一个"运行此工具"回答但也要解释这个问题,我们可以在这里指出任何有相关问题的人,他们会找到一个明确的解释,为什么他们被指向这里,以及运行工具,以解决他们的问题。我花了几个小时阅读所有现有的Q& A,他们都缺乏对问题的解释,可用于解决问题的替代工具,和/或可能解决方案的优缺点/警告。他们中的一些人也接受了一些非常危险的答案,不应该使用。

现在回到典型问题,这将导致推荐:

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

what isgoingon

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

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

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

isgoingon what

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

 whatngon

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

isgoingon
 what

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

3 个答案:

答案 0 :(得分:11)

问题是您的输入文件使用CRLF的DOS行结尾而不是LF的UNIX行结尾,并且您正在运行UNIX工具,因此CR仍然是UNIX工具正在操作的数据。 CR通常由\r表示,当您在^M为{{cat -vE时对文件运行LF时,可以将其视为控件-M(\n) 1}}并显示为$ 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之前将光标返回到行的开头,这就是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',请务必小心,因为这会删除文件中的所有 \r,而不仅仅是每行末尾的

请注意,只需正确设置RS,GNU awk就可以解析具有DOS行结尾的文件:

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

但是其他awks不允许这样做,因为POSIX只需要awks来支持单个字符RS,而大多数其他awk会悄悄地将RS='\r\n'截断为RS='\r'。您可能需要为gawk添加-v BINMODE=3,甚至可以查看\r,因为底层C基元将在某些平台上剥离它们,例如cygwin的。

需要注意的一件事是,像Excel这样的Windows工具创建的CSV将使用CRLF作为行结尾,但可以将LF嵌入CSV的特定字段中,例如:< / p>

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

真的是:

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

因此,如果您只是将\r\n转换为\n s,那么您就无法再将换行中的换行符作为行结尾,因此如果您想这样做,我建议您将所有内容转换为现场线路首先输入其他东西,例如这会将所有字段内LFs转换为标签,并将所有结尾CRLF转换为LF s:

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

在没有GNU awk的情况下做类似的练习但作为一个练习但是与其他awks一样,它涉及组合在CR中不会在阅读时结束的行。

答案 1 :(得分:3)

您可以使用shorthand character class中的\R PCRE来查找包含未知行结尾的文件。使用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中使用等效的(?>\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,因此{{ 1}}是一个很好的解决方案):

tr

GNU $ tr -d "\r" < file | od -c 0000000 w h a t i s g o i n g o n \n 0000016 有效,但不是POSIX sed,因为POSIX不支持sed\r

仅限GNU:

\x0D

Unicode Regular Expression Guide可能是对“换行符”进行最终处理的最佳选择。

答案 2 :(得分:2)

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

如果在Fedora系统上.scroll-content { overflow-y: auto !important; } 将放置dnf install dos2unix工具(如果没有安装)。

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

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

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

\r