为什么我的工具输出会自行覆盖,我该如何解决?
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 -vE
而 LF
是 \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
才能看到 \r
s,尽管底层 C 原语会在某些平台上剥离它们,例如cygwin.
需要注意的一件事是,由 Windows 工具(如 Excel)创建的 CSV 将使用 CRLF
作为行尾,但可以在其中嵌入 LF
CSV 的特定字段,例如:
"field1","field2.1
field2.2","field3"
真的是:
"field1","field2.1\nfield2.2","field3"\r\n
所以如果你只是将 \r\n
s 转换为 \n
s 那么你就不能再将换行符中的字段中的换行符作为行尾,所以如果你想这样做我建议转换所有的场内换行到别的东西,例如这会将所有字段内 LFs
转换为制表符,并将所有以 CRLF
s 结尾的行转换为 LF
s:
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" 的最终处理的最佳选择。
此问题的目的是为答案为 "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 -vE
而 LF
是 \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
才能看到 \r
s,尽管底层 C 原语会在某些平台上剥离它们,例如cygwin.
需要注意的一件事是,由 Windows 工具(如 Excel)创建的 CSV 将使用 CRLF
作为行尾,但可以在其中嵌入 LF
CSV 的特定字段,例如:
"field1","field2.1
field2.2","field3"
真的是:
"field1","field2.1\nfield2.2","field3"\r\n
所以如果你只是将 \r\n
s 转换为 \n
s 那么你就不能再将换行符中的字段中的换行符作为行尾,所以如果你想这样做我建议转换所有的场内换行到别的东西,例如这会将所有字段内 LFs
转换为制表符,并将所有以 CRLF
s 结尾的行转换为 LF
s:
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" 的最终处理的最佳选择。