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

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

提问人:Ed Morton 提问时间:8/19/2017 最后编辑:wjandreaEd Morton 更新时间:9/13/2023 访问量:2547

问:

这个问题的目的是成为一个规范,涵盖各种问题,其答案归结为“你有DOS行尾被输入到Unix工具中”。任何有相关问题的人都应该找到一个明确的解释,说明为什么他们被指向这里,以及可以解决他们问题的工具,以及可能解决方案的利弊/警告。关于这个主题的一些现有问题已经接受了答案,这些答案只说“运行这个工具”,几乎没有解释,或者只是非常危险,永远不应该使用。

现在来讨论一个典型的问题,该问题将导致此处的推荐:


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

what isgoingon

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

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

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

isgoingon what

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

 whatngon

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

isgoingon
 what

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

UNIX AWK sed DOS2UNIX

评论

2赞 kvantour 9/7/2018
感谢您提出这个问题。最有用的一个,因为它是最常见的错误!默认情况下,应链接到 all 和 questions。awksed
1赞 tripleee 8/3/2019
这在精神上与 stackoverflow.com/questions/39527571/ 非常相似...... - 我们需要多个规范吗?

答:

28赞 Ed Morton 8/19/2017 #1

问题在于您的输入文件使用的是 DOS 行尾,而不是 的 UNIX 行尾 ,并且您正在其上运行 UNIX 工具,因此 UNIX 工具正在操作的剩余部分数据。 在文件上运行时,通常用 和 表示为 control-M (),而 is 和 显示为 with 。CRLFLFCRCR\r^Mcat -vELF\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

因此,当您在文件上运行像 awk(被视为行尾)这样的 UNIX 工具时,读取行的行为会消耗 ,但这会将 2 个字段保留为:\n\n

<what> <isgoingon\r>

请注意第二个字段末尾的 。 表示回车,从字面上看,这是将光标返回到行首的指令。因此,当您这样做时:\r\r

print $2, $1

awk 会将其打印到终端,终端将打印并将光标返回到行的开头,然后再打印一个空格,后跟 ,这就是为什么 似乎会覆盖 的开头。isgoingonwhatwhatisgoingon

溶液

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

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

显然,在某些 UNIX 变体(例如 Ubuntu)中又名。dos2unixfromdos

如果您决定使用通常建议的使用,请小心,因为这将删除文件中的所有 s,而不仅仅是每行末尾的 s。(更多细节见下文。tr -d '\r'\r

笔记

使用 awk 处理 DOS 行尾

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

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

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

包含换行符的 CSV 数据

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

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

真的是:

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

因此,如果您只是将 s 转换为 s,那么您就无法再将字段中的换行符与换行符区分开来,因此,如果您想这样做,我建议您先将所有字段内换行符转换为其他内容,例如,这会将所有字段内转换为制表符并将所有行尾 s 转换为 s:\r\n\nLFsCRLFLF

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

在没有 GNU awk 的情况下做类似的操作,但使用其他 awk,它涉及组合在读取时不会结束的行。CR

Awk 的默认 FS

另请注意,尽管 CR 是 POSIX 字符类的一部分,但在使用默认 FS 时,它不是作为分隔字段包含的空格字符之一,其空格字符仅为 tab、blank 和换行符。如果您的输入在 CRLF 之前有空格,这可能会导致令人困惑的结果:[[:space:]]" "

$ 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$

评论

2赞 Arminius 8/19/2017
我理解你关于小心谨慎的评论,但出于专业的好奇心:你有没有遇到过一个 Windows CSV 文件,它的预期有效载荷是某处?tr -d '\r''\r'
0赞 stevieb 8/19/2017
我编写了File::Edit::P ortable,使跨平台的读取和写入文件无缝。
0赞 James Brown 8/19/2017
@Arminius,就在昨天。那个 csv 文件当然有问题,但它有 s 和 s。firstname\rlastnamefirst\nlast
2赞 Arminius 8/19/2017
@JamesBrown这就是我提出问题的原因@EdMorton。我必须处理大量输入数据,并且在数据中找到单独的数据会使我的验证例程“哔哔”作响。我有一个案例(没有说谎!),几年前有人用作列和线分隔符。:-)\r\r\n
2赞 Edwin Buck 8/19/2017 #2

运行 dos2unix。虽然你可以用你自己编写的代码来操作行尾,但 Linux / Unix 世界中已经存在一些实用程序,它们已经为你做到了这一点。

如果在 Fedora 系统上将把工具放到位(如果不应该安装)。dnf install dos2unixdos2unix

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

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

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

tr -d '\r' < infile > outfile

评论

2赞 dawg 8/19/2017
该表单将销毁所有本应包含在文件中的内容,而不是 Windows 行结尾的一部分。最好这样做,因为这会将替换限制在行尾。tr -d '\r' < infile > outfile\rsed 's/\r$//'
2赞 Edwin Buck 8/20/2017
@dawg 好点子。因此,提高了使用 dos2unix 的安全性。
4赞 dawg 8/20/2017 #3

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

因此,如果您有一个“额外”,您可以找到并使用正则表达式删除它,它将规范化行尾的任意组合为 .或者,您可以使用捕获任何“行尾”的概念并标准化为字符。s/\R$/\n/\ns/\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

如果没有,可以使用 in PCRE 中的等效项。\R(?>\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即使在其他上下文中使用也会全部删除(当然很少使用 OF,并且 XML 处理需要删除它,因此是一个很好的解决方案):\r\r\rtr

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

GNU可以工作,但POSIX不能工作,因为POSIX不支持。sedsed\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 正则表达式指南可能是确定“换行符”的最佳选择。

评论

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