如何使用sed将每个换行符(\n)替换为一个空格?

1717

如何使用sed命令将换行符("\n")替换为空格("")?

我尝试了以下命令但没有成功:

sed 's#\n# #g' file
sed 's#^$# #g' file

我该怎么修复它?


46
如果只需要将一个字符替换为另一个字符,那么 tr 命令是最适合的工具。但是上面的例子展示了将换行符替换为空格,因此在这种情况下,tr 命令可以使用。但是对于后续操作可能会有限制。 - Angry 84
16
"tr"是正确的工具,因为提问者想要将每个换行符替换为一个空格,就像他的示例中展示的那样。对于"sed"来说,换行符的替换是独特而深奥的,但在"tr"中很容易完成。这是一个常见的问题。使用正则表达式进行替换不是"tr"所能做到的,而是需要用到"sed",因此它是处理不同问题的正确工具。 - Mike S
3
"tr"也可以仅删除换行符 tr -d '\n',但您可能还想删除回车以使其更通用tr -d '\012\015' - anthony
4
警告:在Linux和早期的Solaris机器(例如sol5.8)之间,“tr”在处理字符范围时表现不同。例如:tr -d 'a-z'tr -d '[a-z]'。对此,我建议您使用没有这种区别的“sed”。 - anthony
2
@MikeS 感谢您的回答。在 tr '\012' ' ' 之后加上 echo。否则文件中的最后一个换行符也会被删除。使用 tr '\012' ' ' < 文件名; echo 可以解决问题。 - Bernie Reiter
显示剩余7条评论
43个回答

2013

sed的设计初衷是用于基于行的输入。虽然它可以完成你需要的功能。


这里更好的选择是使用如下的tr命令:

tr '\n' ' ' < input_filename

或者彻底删除换行符:

tr -d '\n' < input.txt > output.txt

或者如果你有 GNU 版本(带有其长选项)

tr --delete '\n' < input.txt > output.txt

117
Sed是基于行的工具,因此它很难理解换行符。 - Alexander Gladysh
3
亚历山大: "流编辑器" 是指基于行的吗?也许,这个名字有点令人困惑。 - Léo Léopold Hertz 준영
226
sed工作在输入数据的“流”上,但它按换行符分隔的块来理解输入。它是一个Unix工具,这意味着它可以非常好地执行一项任务。这项任务就是“逐行操作文件”。让它做其他事情将很困难,并且有可能出现错误。故事的寓意是:选择正确的工具。你所提出的许多问题似乎都采取了“如何让这个工具做它从未被设计过的事情”的形式。这些问题很有趣,但如果它们出现在解决实际问题的过程中,那么你可能做错了什么。 - dmckee --- ex-moderator kitten
1
GNU sed 支持将“记录”分隔符更改为空字节,而不是换行符。 - Jacek Krysztofik
5
tr 只能用于包含一个字符的字符串。你不能将所有换行符替换为一个多字符长的字符串。 - Flimm
显示剩余3条评论

1784

使用这个解决方案需要GNU sed:

sed ':a;N;$!ba;s/\n/ /g' file
这将在循环中读取整个文件(':a;N;$!ba'),然后用空格替换换行符('s/\n/ /g')。如果需要,可以简单地追加其他替换。
解释:
  1. sed 从第一行开始读取,不包括换行符,将其放入模式空间。
  2. 通过 ':a' 创建一个标签。
  3. 通过 'N' 将一个换行符和下一行附加到模式空间中。
  4. 如果当前行不是最后一行,则跳转到已创建的标签 '$!ba'('$!' 表示不要在最后一行执行此操作。这是必需的,以避免再次执行 'N',如果没有更多的输入,则会终止脚本!)。
  5. 最后,在模式空间(即整个文件)上使用替换将每个换行符替换为一个空格。
以下是与BSD和OS X的 sed 兼容的跨平台语法(根据@Benjie comment):
sed -e ':a' -e 'N' -e '$!ba' -e 's/\n/ /g' file

正如您所见,对于这个本来很简单的问题使用sed是有问题的。查看这个答案以获取更简单、更适当的解决方案。


133
您可以通过单独执行命令(而不是用分号隔开)来在跨平台(如Mac OS X)上运行此命令:sed -e ':a' -e 'N' -e '$!ba' -e 's/\n/ /g' - Benjie
5
似乎没有移除最后一个 \n? - Pierre-Gilles Levallois
4
这是一个令人印象深刻的回答。我也觉得很讽刺,Linux 工具被认为是“专做一件事情”的,但似乎大多数 Linux 工具做很多事情,却做得不好。 - Andy Ray
4
使用命令 echo "Hello\nWorld" | sed -e ':a' -e 'N' -e '$!ba' -e 's/\n/ /g' 将返回 "Hello World"。但是,如果使用命令 echo "Hello World" | sed -e ':a' -e 'N' -e '$!ba' -e 's/\n/ /g',我会收到一个空字符串。我使用的操作系统是 MacOS Big Sur。 - Tobias Bergkvist
1
@AndyRay:真的吗? - Thor
显示剩余5条评论

611

快速回答

sed ':a;N;$!ba;s/\n/ /g' file
  1. :a 创建一个名为'a'的标签
  2. N 将下一行追加到模式空间中
  3. $! 如果不是最后一行,ba 跳转到标签'a'
  4. s 替换操作/\n/ 匹配新行字符/ / 替换为空格/g 全局匹配(尽可能多次)

sed会循环执行步骤1到3,直到达到最后一行,获取全部符合模式空间的内容,然后替换所有的\n字符


替代方案

所有的替代方案,与sed不同,无需等到处理最后一行再开始处理

使用bash,速度较慢

while read line; do printf "%s" "$line "; done < file

使用Perl,具有类似于sed的速度

perl -p -e 's/\n/ /' file

使用 trsed 更快,能够只用一个字符替换内容。

tr '\n' ' ' < file

使用paste命令,类似于tr命令的速度,可以只用一个字符来替换。

paste -s -d ' ' file

使用 awk,类似于 tr 的速度

awk 1 ORS=' ' file

除了像"echo $(< file)"这样的另一种选择是慢,仅适用于小文件,并且需要处理整个文件才能开始进程。


sed FAQ 5.10的详细回答

5.10.为什么我不能使用 \n 转义序列来匹配或删除换行符?为什么我不能使用 \n 匹配两行或更多行?

\n 永远不会匹配行末的换行符,因为换行符总是在将行放入模式空间之前剥离掉。要将两个或多个行放入模式空间,请使用“N”命令或类似命令(例如“H;...;g;”)。

Sed 的工作方式如下:sed 逐行读取,截断终止符换行符,将剩余内容放入模式空间中,sed 脚本可以对其进行操作或更改,并在打印模式空间时向 stdout (或文件) 添加换行符。如果使用 'd' 或 'D' 完全或部分删除模式空间,则在此类情况下不添加换行符。因此,像以下脚本这样的脚本:

  sed 's/\n//' file       # to delete newlines from each line             
  sed 's/\n/foo\n/' file  # to add a word to the end of each line         

永远都不会起作用,因为尾随的换行符在将行放入模式空间之前就被去除了。要执行上述任务,请改用以下其中一种脚本:

  tr -d '\n' < file              # use tr to delete newlines              
  sed ':a;N;$!ba;s/\n//g' file   # GNU sed to delete newlines             
  sed 's/$/ foo/' file           # add "foo" to end of each line          

由于除了GNU sed之外的其他版本sed存在模式缓冲区大小限制,因此Unix中的'tr'实用程序更好用。
如果文件的最后一行包含换行符,则GNU sed将将该换行符添加到输出中但删除所有其他换行符,而tr将删除所有换行符。

要匹配两行或更多行的块,有3种基本选择:
(1) 使用'N'命令将下一行添加到模式空间中;
(2) 至少使用两次'H'命令将当前行附加到Hold space中,然后使用x、g或G从hold space中检索行;或者 (3) 使用地址范围 (请参见上面的第3.3节) 匹配两个指定地址之间的行。

选择 (1) 和 (2) 将在模式空间中插入\n,从而可以按需要进行处理 ('s/ABC\nXYZ/alphabet/g')。在第4.13节 ("如何删除一个特定连续行的块?") 中出现了使用'N'删除一组行的示例。可以通过将删除命令更改为其他内容(例如'p'(打印),'i'(插入),'c'(更改),'a'(追加)或's'(替换))来修改此示例。

选择 (3) 不会将\n放入模式空间中,但它确实匹配连续行的块,因此您可能甚至不需要\n就可以找到要查找的内容。由于GNU sed版本3.02.80现在支持此语法:

  sed '/start/,+4d'  # to delete "start" plus the next 4 lines,           

除了传统的'/从这里开始/到那里结束/{...}'范围地址之外,完全可以避免使用\n。


13
"tr" 是一个很棒的想法,而您的全面覆盖使回答质量非常高。 - New Alexandria
3
使用标准实用程序 paste,并使用其他所有工具,给出 +1。 - Totor
1
@elgalu 试试这个:http://unix.stackexchange.com/questions/4527/program-that-passes-stdin-to-stdout-with-color-codes-stripped - hdorio
4
这个回答最好的部分是,“长回答”详细解释了该命令如何工作以及为什么会这样。 - pdwalker
5
这可能是我在StackExchange上读到的成千上万个答案中最有帮助的一个。我需要跨越多行匹配多个字符。以前的sed示例都没有涵盖多行,而tr无法处理多个字符匹配。Perl看起来不错,但并不像我期望的那样工作。如果可以的话,我会多次投票支持这个答案。 - mightypile
显示剩余7条评论

258

GNU sed有一个选项-z, 用于处理以null字符分隔的记录(行)。您可以直接调用:

sed -z 's/\n/ /g'

6
即使输入包含空值,它们仍将被保留(作为记录分隔符)。 - Toby Speight
8
如果没有空值,这样做会不会加载整个输入?在这种情况下,处理几个GB的文件可能会导致崩溃。 - Ruslan
6
@Ruslan,是的,它会加载整个输入。对于多GB的文件,这种解决方案并不是一个好主意。 - JJoao
22
这是真的最佳答案。其他表达方式太过复杂,难以记忆。@JJoao,你可以使用“-u”或“--unbuffered”来操作。根据“man”手册所述:“从输入文件中加载最少量的数据,并更频繁地刷新输出缓冲区”。 - not2qubit
2
很少被使用的 y 命令也可以完成这个工作 sed -z 'y/\n/ /' file - potong
显示剩余6条评论

251
一个更短的awk替代方案:
awk 1 ORS=' '

解释

一个 awk 程序由规则组成,这些规则包含条件代码块,即:

condition { code-block }
如果省略了代码块,则使用默认值:{ print $0 }。 因此,1被解释为真条件,print $0将针对每行执行。
在读取输入时,awk基于RS(记录分隔符)将其拆分为记录,默认情况下为换行符,因此awk将默认按行解析输入。 拆分还涉及从输入记录中剥离RS
现在,当打印记录时,将附加ORS(输出记录分隔符),默认值再次为换行符。 因此,通过将ORS更改为空格,所有换行符都将更改为空格。

6
我很喜欢这个简单的解决方案,它比其他方案更易读。 - Fedir RYKHTIK
11
如果更易理解的话,这可以有效地写成:awk 'BEGIN { ORS=" " } { print $0 } END { print "\n" }' file.txt(添加一个结束换行符只是为了说明开始/结束);数字“1”代表true(处理该行),而print(打印该行)。 这个表达式也可以添加条件,例如,只处理与模式匹配的行:awk 'BEGIN { ORS=" " } /pattern/ { print $0 } END { print "\n" }' - michael
2
您可以更简单地实现它:code awk 'ORS=" "' file.txt code - Udi
1
为什么在结尾处使用 ORS=' '?它是程序的一部分还是被解释为选项?我本以为你需要将其放在 BEGIN 块中或作为程序之前的选项,例如 awk -v ORS=' ' 1。我无法确定 awk 如何解析 awk 1 ORS=' ' - Jonah
2
@Jonah:这是设置变量的另一种方式,例如请参阅GNU awk手册 - Thor
显示剩余3条评论

104

Perl版本按照您的期望工作。

perl -i -p -e 's/\n//' file

正如评论中指出的那样,值得注意的是这个会直接在原文件上进行编辑。-i.bak 将会在替换前给你一个原始文件的备份,以防你的正则表达式没有你想象的那么聪明。


28
请至少提到,没有后缀的-i选项不会进行备份。使用-i.bak可以避免一些容易犯的错误(例如忘记输入-p选项而导致文件被清空)。 - Telemachus
7
@Telemachus: 这是一个公正的观点,但也可以有不同的争论。我没有提到它的主要原因是 OP 问题中的 sed 示例没有备份,所以在这里似乎是多余的。另一个原因是我从未真正使用过备份功能(实际上我发现自动备份很烦人),所以我总是忘记它的存在。第三个原因是它会让我的命令行变长四个字符。不管好坏(可能更差),我是一位强迫症的极简主义者;我只喜欢简洁明了。我知道你不同意我的看法。我会尽力记住在将来警告备份。 - ire_and_curses
7
@Ire_and_curses: 实际上,你刚刚为忽略我提出了一个非常好的论点。也就是说,你有选择的理由,无论我是否同意这些选择,我都会尊重它们。我不确定完全为什么,但我最近一直在对这件特定的事情发泄(Perl中的“-i”标志没有后缀)。我相信很快我就会找到其他事情来着迷。 :) - Telemachus
我从这个答案和评论中了解到,不带后缀的 -i 会将内容写入文件。我之前从其他地方复制了一段代码,使用的是 source > destination,但如果源文件和目标文件相同,那么目标文件会变成空白。因此,像这个答案中提到的 -i 可以帮助避免这种情况。非常感谢。 - vee
这个解决方案需要使用-0777标志,以便Perl一次性处理整个文件,即perl -0777 -i -p -e 's/\n//' file - caw
显示剩余2条评论

52

谁需要sed?这里是bash的方式:

cat test.txt |  while read line; do echo -n "$line "; done

4
赞同,我通常使用排名最高的答案,但当通过/dev/urandom进行管道传输时,sed不会打印直到EOF,^C不是EOF。 这个解决方案每次看到换行符都会打印。 正是我所需要的! 谢谢! - Vasiliy Sharapov
10
@Tony 因为反引号已经过时,而且猫也是多余的 ;-)使用:echo $(<days.txt) - seumasmac
13
不使用cat命令,通过以下语句可以将 test.txt 中的每一行输出并以空格分隔:while read line; do echo -n "$line "; done < test.txt。如果子 shell 是个问题,这可能会很有用。 - Carlo Cannas
7
echo $(<file)会将所有空格压缩成一个空格,不仅限于换行符:这超出了原帖的要求。 - glenn jackman
1
如果您没有设置IFS,我认为这也会删除制表符和连续的空格? - wds
显示剩余9条评论

31

为了使用awk将所有换行符替换为空格,而无需将整个文件读入内存:

awk '{printf "%s ", $0}' inputfile
如果你想要一个最后的换行符:
awk '{printf "%s ", $0} END {printf "\n"}' inputfile

您可以使用除空格以外的字符:

awk '{printf "%s|", $0} END {printf "\n"}' inputfile

2
"END{ print ""}" 是一个更短的替代方案,用于添加尾随换行符。 - user8017719

28
tr '\n' ' ' 

是该命令。

简单易用。


21
如果您不想添加空格,可以简单地使用“tr -d'\n'”命令。 - spuder

19

三件事。

  1. tr (or cat, etc.) is absolutely not needed. (GNU) sed and (GNU) awk, when combined, can do 99.9% of any text processing you need.

  2. stream != line based. ed is a line-based editor. sed is not. See sed lecture for more information on the difference. Most people confuse sed to be line-based because it is, by default, not very greedy in its pattern matching for SIMPLE matches - for instance, when doing pattern searching and replacing by one or two characters, it by default only replaces on the first match it finds (unless specified otherwise by the global command). There would not even be a global command if it were line-based rather than STREAM-based, because it would evaluate only lines at a time. Try running ed; you'll notice the difference. ed is pretty useful if you want to iterate over specific lines (such as in a for-loop), but most of the times you'll just want sed.

  3. That being said,

    sed -e '{:q;N;s/\n/ /g;t q}' file
    

    works just fine in GNU sed version 4.2.1. The above command will replace all newlines with spaces. It's ugly and a bit cumbersome to type in, but it works just fine. The {}'s can be left out, as they're only included for sanity reasons.


5
作为一个只懂得基本操作的 sed 的人来说,我必须得说,关键不在于你能够用 sed 做些什么,而是它的易用性和易理解性。由于我很难使用 sed,所以当有简单命令可以替代时,我更愿意采用简单的方式。 - Nate
使用 t q 作为条件跳转,可以在不读取整个文件到内存的情况下,适用于类似s/\n / /模式(连接以空格开头的所有行)的操作。在转换多兆字节文件时非常方便。 - textshell
你链接的文章并不反映你所说的内容。 - hek2mgl
3
这几乎比大输入的优秀答案慢了800倍。这是因为在越来越大的输入上运行替换每一行。 - Thor

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接