【问题标题】:How can I remove double line breaks with sed?如何使用 sed 删除双换行符?
【发布时间】:2015-02-15 02:36:04
【问题描述】:

我试过了:

sed -i 's/\n+/\n/' file

但它不起作用。

我仍然想要单行换行符。

输入:

abc

def


ghi




jkl

期望的输出:

abc

def

ghi

jkl

【问题讨论】:

  • 解决方案是否严格需要通过sed
  • 所以你想要的输出仍然包含\n\n,但不包含\n\n\n
  • 某些版本的cat-s--squeeze-blank 用于用单个空行替换多个空行的序列...

标签: linux bash sed


【解决方案1】:

在这里找到That's What I Sed(比this solution 慢)。

sed '/^$/N;/\n$/D' file

sed 脚​​本可以这样解读:

如果下一行为空,则删除当前行。

并且可以翻译成如下伪代码(对于已经熟悉sed的读者,buffer指的是模式空间):

 1 | # sed '/^$/N;/\n$/D' file
 2 | while not end of file :
 3 |   buffer = next line
 4 |   # /^$/N
 5 |   if buffer is empty :                        # /^$/
 6 |     buffer += "\n" + next line                # N
 7 |   end if
 8 |   # /\n$/D
 9 |   if buffer ends with "\n" :                  # /\n$/
10 |     delete first line in buffer and go to 5   # D
11 |   end if
12 |   print buffer
13 | end while

在正则表达式/^$/ 中,^$ 符号分别表示“缓冲区的开始”和“缓冲区的结束”。它们指的是缓冲区的边缘,而不是缓冲区的内容。

D 命令执行以下任务:如果缓冲区包含换行符,则删除缓冲区的文本直到第一个换行符,并重新启动程序循环(返回第 1 行)而不处理其余命令,不打印缓冲区,也不读取新的输入行。

最后,请记住sed 在处理该行之前会删除尾随换行符,并请记住print 命令会添加回尾随换行符。所以,在上面的代码中,如果要处理的下一行是Hello World!\n,那么next line隐含地引用Hello World!

更多详情请访问https://www.gnu.org/software/sed/manual/sed.html

您现在已准备好将算法应用于以下文件:

a\n
b\n
\n
\n
\n
c\n

现在让我们看看为什么this solution 更快。

sed 脚本/^$/{:a;N;s/\n$//;ta} 可以这样解读:

如果当前行匹配/^$/,则执行{:a;N;s/\n$//;ta}

由于^$ 之间没有任何内容,我们可以这样改写:

如果当前行为空,则执行{:a;N;s/\n$//;ta}

表示sed对每个空行执行以下命令:

Step Command Description
1 :a Declare a label named "a".
2 N Append the next line preceded by a newline (\n) to the current line.
3 s/\n$// Substitute (s) any trailing newline (/\n$/) with nothing (//).
4 ta Return to label "a" (to step 1) if a substitution was performed (at step 3), otherwise print the result and move on to the next line.

非空行按原样打印。知道了这一切,我们可以用下面的伪代码来描述整个过程:

 1 | # sed '/^$/{:a;N;s/\n$//;ta}' file
 2 | while not end of file :
 3 |   buffer = next line
 4 |   # /^$/{:a;N;s/\n$//;ta}
 5 |   if buffer is empty :               # /^$/
 6 |     :a                               # :a
 7 |     buffer += "\n" + next line       # N
 8 |     if buffer ends with "\n" :       # /\n$/
 9 |       remove last "\n" from buffer   # s/\n$//
10 |       go to :a (at 6)                # ta
11 |     end if
12 |   end if
13 |   print buffer
14 | end while

如您所见,两个sed 脚本非常相似。事实上,s/\n$//;ta/\n$/D 几乎相同。但是,第二个脚本跳过了第 5 步,因此它可能比第一个脚本更快。让我们用大约 10Mb 的空行来计算两个脚本的时间:

$ yes '' | head -10000000 > file
$ /usr/bin/time -f%U sed '/^$/N;/\n$/D' file > /dev/null
3.61
$ /usr/bin/time -f%U sed '/^$/{:a;N;s/\n$//;ta}' file > /dev/null
2.37

第二个脚本获胜。

【讨论】:

    【解决方案2】:

    我不会为此使用sed,而是使用cat-s 标志。 正如手册所述:

    -s, --squeeze-blank    suppress repeated empty output lines
    

    因此,获得所需输出所需的只是:

    cat -s file
    

    【讨论】:

      【解决方案3】:

      这可能对你有用(GNU sed):

      sed '/^$/{:a;N;s/\n$//;ta}' file
      

      这用一个空行替换多个空行。

      但是,如果您想在每个非空行之后放置一个空行,那么:

      sed '/^$/d;G' file 
      

      删除所有空行,只将一个空行附加到非空行。

      【讨论】:

      • 这很完美:sed '/^$/{:a;N;s/\n$//;ta}' 文件
      • 想解释或链接到 doc 文件?那些大括号是序列化数据吗? — 没关系,它们是特定于 sed 的命令。请参阅 sed 手册。 grymoire.com/Unix/Sed.html
      【解决方案4】:

      仅使用 sed 即可为您提供所需的内容:

      sed '/^$/d' txt | sed -e $'s/$/\\\n/'
      

      第一个 sed 命令删除所有空行,表示为“^$”。

      第二个 sed 命令在每一行的末尾插入一个换行符。

      【讨论】:

      • OP 要求用单个空行替换双空行,而不是每个非空行之间的单个空行。
      • 不,这可能是问题文本所暗示的,但他提供的示例清楚地表明他希望所有系列的空行都变成单个空行。也就是说,提问者对我的解决方案的回应表明,在某些情况下(不清楚何时),他不希望在非空行之间有一个空行。
      【解决方案5】:

      使用 awk(gnu 或 BSD)您可以:

      awk -v RS= -v ORS='\n\n' '1' file
      abc
      
      def
      
      ghi
      
      jkl
      

      也使用perl:

      perl -pe '$/=""; s/(\n)+/$1$1/' file
      abc
      
      def
      
      ghi
      
      jkl
      

      【讨论】:

      • 该 perl 解决方案会将整个文件加载到内存中,这不适用于非常大的文件。
      【解决方案6】:
      perl -00 -pe 1 filename
      

      将输入文件拆分为由 2 个或更多换行符分隔的“段落”,然后打印由单个空行分隔的段落:

      perl -00 -pe 1 <<END
      abc
      
      def
      
      
      ghi
      
      
      
      
      jkl
      END
      
      abc
      
      def
      
      ghi
      
      jkl
      

      【讨论】:

        【解决方案7】:

        为什么不去掉所有的空行,然后在每行之后添加一个空行呢?对于您指定的输入文件tmp

        sed '/^$/d' tmp|sed '0~1 a\ '
        abc
        
        def
        
        ghi
        
        jkl
        

        如果空格(空格和制表符)对您来说算作“空白”行,请改用sed '/^\s*$/d' tmp|sed '0~1 a\ '

        请注意,这些解决方案确实会在末尾留下一个尾随空行,因为我不确定这是否需要。轻松移除。

        【讨论】:

        • 有些行应该放在一起,比如mno mno pqr
        • @SamRoberts 所以你是说某些行之间不应该有空行?如何知道哪些应该和不应该?
        【解决方案8】:

        Sed 不太擅长以编程方式检查多行的任务。这是我能得到的最接近的:

        $ sed '/^$/{n;/^$/d}' file
        abc
        
        def
        
        ghi
        
        
        jkl
        

        这样的逻辑:如果找到空行,再看下一行。如果下一行也是空白,则删除下一行。

        这最终并没有吞噬所有的行,因为它假设有一个有意的额外对并将两个\n\ns 减少到两个\ns。


        在基本的awk中做到这一点:

        $ awk 'NF > 0 {blank=0} NF == 0 {blank++} blank < 2' file
        abc
        
        def
        
        ghi
        
        jkl
        

        这使用了一个名为 blank 的变量,当字段数 (NF) 不为零时该变量为零,并且在它们为零时递增(空白行)。 awk 的默认动作,打印,是在连续空行数少于两行时执行的。

        【讨论】:

        • 但是ghi之后还有2个以上EOL
        • @anubhava:正确。正如我所指出的,这是sed 所能做的最好的事情。这解决了“双换行符”,但不是问题中给出的伪代码正则表达式。 awk 是更优雅的解决方案。
        猜你喜欢
        • 1970-01-01
        • 2011-05-11
        • 2018-01-21
        • 2016-03-19
        • 1970-01-01
        • 2018-12-10
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        相关资源
        最近更新 更多