【问题标题】:Removing trailing / starting newlines with sed, awk, tr, and friends使用 sed、awk、tr 和朋友删除尾随/开始的换行符
【发布时间】:2011-11-13 15:26:36
【问题描述】:

我想从文件中删除所有空行,但前提是它们位于文件的末尾/开头(也就是说,如果它们之前没有非空行,则在开头;并且如果它们后面没有非空行,则在最后。)

这是否可能在 Perl 或 Ruby 等功能齐全的脚本语言之外实现?如果可能的话,我更喜欢使用sedawk。基本上,任何轻量级且广泛可用的 UNIX-y 工具都可以,尤其是我可以快速了解更多信息的工具(因此不包括 Perl。)

【问题讨论】:

    标签: unix sed awk tr


    【解决方案1】:

    在 bash 中,使用 cat、wc、grep、sed、tail 和 head:

    # number of first line that contains non-empty character
    i=`grep -n "^[^\B*]" <your_file> | sed -e 's/:.*//' | head -1`
    # number of hte last one
    j=`grep -n "^[^\B*]" <your_file> | sed -e 's/:.*//' | tail -1`
    # overall number of lines:
    k=`cat <your_file> | wc -l`
    # how much empty lines at the end of file we have?
    m=$(($k-$j))
    # let strip last m lines!
    cat <your_file> | head -n-$m
    # now we have to strip first i lines and we are done 8-)
    cat <your_file> | tail -n+$i
    

    伙计,绝对值得学习“真正的”编程语言来避免这种丑陋!

    【讨论】:

    • 嗯, 部分使用 sed 很简单!让我玩它,并尝试用一个完整的命令回到这里。谢谢!
    • 实际上,这不适用于最后几行,因为它会在 grep 阶段删除 all 换行符,从而在最后抛出计数。 /=
    • 不:执行这些命令后,您仍然拥有原始文件。第二个命令打印所有带有行号的非空白。因此,您将拥有最后一个非空白的数量。
    • 啊!我似乎误解了grep -n的操作。是的!
    • (已接受,尽管我使用了没有任何 shell 变量的单行变体,而是使用 sed 命令表达了更多内容。)
    【解决方案2】:

    使用bash

    $ filecontent=$(<file)
    $ echo "${filecontent/$'\n'}"
    

    【讨论】:

    • 这只会从开头删除一个空行,从末尾删除一个空行。
    • @me_and:虽然您只从一开始就删除 one 空行是正确的,但这实际上 确实 删除了所有尾随换行符,因为命令替换 ($(&lt;file)) 隐式做到这一点。
    • @mklement0:嗯,确实如此。每天学习新事物!
    【解决方案3】:

    使用 awk:

    awk '{a[NR]=$0;if($0 && !s)s=NR;}
        END{e=NR;
            for(i=NR;i>1;i--) 
                if(a[i]){ e=i; break; } 
            for(i=s;i<=e;i++)
                print a[i];}' yourFile
    

    【讨论】:

    • 我想知道是否有一种方法可以减少/重构以一次性处理它? (我对 awk 不是很熟悉;我可以阅读你写的内容,但我不知道如何重构它。)
    • 基本上这是一个单行命令,唯一的动态部分是'yourFile',也就是你要处理的文件名。为什么需要减少/重构?
    • 因为它又长又复杂,即使它不需要任何换行符?几个for循环,多个语句;不必要的复杂性。 (=
    【解决方案4】:

    来自Useful one-line scripts for sed

    # Delete all leading blank lines at top of file (only).
    sed '/./,$!d' file
    
    # Delete all trailing blank lines at end of file (only).
    sed -e :a -e '/^\n*$/{$d;N;};/\n$/ba' file
    

    因此,要从文件中删除前导空行和尾随空行,您可以将上述命令组合成:

    sed -e :a -e '/./,$!d;/^\n*$/{$d;N;};/\n$/ba' file
    

    【讨论】:

    • 根据该站点的注释,尾随空白行脚本不适用于 gsed 3.02.*。这个可以工作:sed -e :a -e '/^\n*$/{$d;N;ba' -e '}'
    • 如果失败,请先尝试做dos2unix。此参考资料是一套非常有用的完整 sed 示例。
    • 这不适合大文件
    • 它不会删除 空格。要删除前导空白行或空格,请使用:sed '/\S/,$!d'
    【解决方案5】:

    这里有一个 awk 中的一次性解决方案:它在看到一个非空行之前不会开始打印,当它看到一个空行时,它会记住它直到下一个非空行

    awk '
        /[[:graph:]]/ {
            # a non-empty line
            # set the flag to begin printing lines
            p=1      
            # print the accumulated "interior" empty lines 
            for (i=1; i<=n; i++) print ""
            n=0
            # then print this line
            print
        }
        p && /^[[:space:]]*$/ {
            # a potentially "interior" empty line. remember it.
            n++
        }
    ' filename
    

    注意,由于我用于考虑空行/非空行的机制([[:graph:]]/^[[:space:]]*$/),只有空格的内部行将被截断以变成真正的空行。

    【讨论】:

    • +1 用于单通道、单实用程序解决方案,该解决方案也具有内存效率(尽管如前所述,其行为与要求的略有不同)。
    【解决方案6】:

    所以我要借用@dogbane 的部分答案,因为用于删除前导空白行的sed 行太短了...

    tac is part of coreutils,并反转文件。所以做两次:

    tac file | sed -e '/./,$!d' | tac | sed -e '/./,$!d'
    

    这当然不是最高效的,但除非您需要效率,否则我发现它比目前为止的其他任何东西都更具可读性。

    【讨论】:

    • 有一个边缘情况值得一提:如果文件没有尾随\n,最后一行将无法正确处理:尝试tac &lt;(printf 'a\nb')。可以说,这种行为是有缺陷的。也会影响tac 的OSX 等效项tail -r
    【解决方案7】:

    another answertac is part of coreutils 中所述,并反转文件。将重复执行两次的想法与the fact that command substitution will strip trailing new lines 结合起来,我们得到

    echo "$(echo "$(tac "$filename")" | tac)"
    

    不依赖于sed。您可以使用echo -n 去除剩余的尾随换行符。

    【讨论】:

    • +1 表示(相对)简单(尽管以牺牲效率为代价); OSX 版本(tac 默认不可用):echo "$(echo "$(tail -r "$filename")" | tail -r)" 我运行测试比较相对执行速度与 100 万行文件的几个答案(没有注意内存使用);更早意味着更快:OSX 10.10: sed (dogbane) tac 在 Ubuntu 上比在 OSX 上快得多。
    • 有一个边缘情况值得一提:如果文件没有尾随\n,最后一行将无法正确处理:尝试echo "$(echo "$(printf 'a\nb' | tac)" | tac)"。这是 tac(以及 OSX 上的 tail -r)的行为所固有的 - 可以说是有缺陷的 - 输入不以 \n 结尾。
    • 使用echo "$(echo "$(cat "$filename")" | tac)" | tac 修复了@mklement0 提到的边缘情况。
    【解决方案8】:

    bash 解决方案

    注意:只有文件足够小可以立即读入内存时才有用。

    [[ $(<file) =~ ^$'\n'*(.*)$ ]] && echo "${BASH_REMATCH[1]}"
    
    • $(&lt;file) 读取整个文件并修剪 尾随 换行符,因为命令替换 ($(....)) 隐式 会这样做。
    • =~ 是 bash 的 正则表达式匹配运算符=~ ^$'\n'*(.*)$ 可选地匹配任何 前导 换行符(贪婪地),并捕获后面的任何内容。请注意可能令人困惑的 $'\n',它使用 ANSI C quoting 插入文字换行符,因为不支持转义序列 \n
    • 注意这个特定的正则表达式总是匹配,所以&amp;&amp;之后的命令总是被执行。
    • 特殊数组变量BASH_REMATCH rematch 包含最近的正则表达式匹配的结果,数组元素[1] 包含(第一个也是唯一的)带括号的子表达式(捕获组)捕获的内容,它是带有任何前导的输入字符串换行符被剥离。最终结果是 ${BASH_REMATCH[1]} 包含输入文件内容,其中前导和尾随换行符都被去除了。
    • 请注意,使用echo 打印会添加一个尾随换行符。如果您想避免这种情况,请改用echo -n(或使用更便携的printf '%s')。

    【讨论】:

      【解决方案9】:

      我想介绍 gawk v4.1+

      的另一个变体
      result=($(gawk '
          BEGIN {
              lines_count         = 0;
              empty_lines_in_head = 0;
              empty_lines_in_tail = 0;
          }
          /[^[:space:]]/ {
              found_not_empty_line = 1;
              empty_lines_in_tail  = 0;
          }
          /^[[:space:]]*?$/ {
              if ( found_not_empty_line ) {
                  empty_lines_in_tail ++;
              } else {
                  empty_lines_in_head ++;
              }
          }
          {
              lines_count ++;
          }
          END {
              print (empty_lines_in_head " " empty_lines_in_tail " " lines_count);
          }
      ' "$file"))
      
      empty_lines_in_head=${result[0]}
      empty_lines_in_tail=${result[1]}
      lines_count=${result[2]}
      
      if [ $empty_lines_in_head -gt 0 ] || [ $empty_lines_in_tail -gt 0 ]; then
          echo "Removing whitespace from \"$file\""
          eval "gawk -i inplace '
              {
                  if ( NR > $empty_lines_in_head && NR <= $(($lines_count - $empty_lines_in_tail)) ) {
                      print
                  }
              }
          ' \"$file\""
      fi
      

      【讨论】:

        【解决方案10】:

        @dogbane 有一个很好的简单答案来删除前导空行。这是一个简单的 awk 命令,它只删除尾随行。将此与 @dogbane 的 sed 命令一起使用以删除前导空格和尾随空格。

        awk '{ LINES=LINES $0 "\n"; } /./ { printf "%s", LINES; LINES=""; }'
        

        这在操作上非常简单。

        • 在读取时将每一行添加到缓冲区。
        • 对于包含字符的每一行,打印缓冲区的内容,然后将其清除。

        因此,唯一被缓冲且从不显示的内容是任何尾随空格。

        我使用 printf 而不是 print 来避免自动添加换行符,因为我已经使用换行符来分隔缓冲区中的行。

        【讨论】:

          【解决方案11】:

          这是一个改编的 sed 版本,它也认为那些只有空格和制表符的行是“空的”。

          sed -e :a -e '/[^[:blank:]]/,$!d; /^[[:space:]]*$/{ $d; N; ba' -e '}'
          

          这基本上是接受的答案版本(考虑到 BryanH 评论),但第一个命令中的点 . 更改为 [^[:blank:]](任何非空白),第二个命令地址中的 \n 更改为 @ 987654325@ 允许换行、空格和制表符。

          另一个版本,不使用 POSIX 类,但您的 sed 必须支持在 […] 中插入 \t\n。 GNU sed 有,BSD sed 没有。

          sed -e :a -e '/[^\t ]/,$!d; /^[\n\t ]*$/{ $d; N; ba' -e '}'
          

          测试:

          prompt$ printf '\n \t \n\nfoo\n\nfoo\n\n \t \n\n' 
          
          
          
          foo
          
          foo
          
          
          
          prompt$ printf '\n \t \n\nfoo\n\nfoo\n\n \t \n\n' | sed -n l
          $
           \t $
          $
          foo$
          $
          foo$
          $
           \t $
          $
          prompt$ printf '\n \t \n\nfoo\n\nfoo\n\n \t \n\n' | sed -e :a -e '/[^[:blank:]]/,$!d; /^[[:space:]]*$/{ $d; N; ba' -e '}'
          foo
          
          foo
          prompt$
          

          【讨论】:

            【解决方案12】:

            对于尾随换行符(包括“白色”字符)的高效非递归版本,我开发了这个sed 脚本。

            sed -n '/^[[:space:]]*$/ !{x;/\n/{s/^\n//;p;s/.*//;};x;p;}; /^[[:space:]]*$/H'
            

            它使用保持缓冲区来存储所有空行并仅在找到非空行后打印它们。如果有人只想要换行符,那么去掉两个 [[:space:]]* 部分就足够了:

            sed -n '/^$/ !{x;/\n/{s/^\n//;p;s/.*//;};x;p;}; /^$/H'
            

            我尝试了与著名的递归脚本进行简单的性能比较

            sed -e :a -e '/^\n*$/{$d;N;};/\n$/ba'
            

            在一个 3MB 的文件上,在随机 base64 文本周围有 1MB 的随机空行。

            shuf -re 1 2 3 | tr -d "\n" | tr 123 " \t\n" | dd bs=1 count=1M > bigfile
            base64 </dev/urandom | dd bs=1 count=1M >> bigfile
            shuf -re 1 2 3 | tr -d "\n" | tr 123 " \t\n" | dd bs=1 count=1M >> bigfile
            

            流式传输脚本大约需要 0.5 秒才能完成,递归在 15 分钟后没有结束。赢:)

            为了完整起见,剥离 sed 脚本的引导线已经可以正常播放。用最适合你的。

            sed '/[^[:blank:]]/,$!d'
            sed '/./,$!d'
            

            【讨论】:

              【解决方案13】:

              这个 AWK 脚本可以解决问题:

              BEGIN {
                  ne=0;
              }
              
              /^[[:space:]]*$/ {
                  ne++;
              }
              
              /[^[:space:]]+/ {
                  for(i=0; i < ne; i++)
                      print "";
                  ne=0;
                  print
              }
              

              这个想法很简单:空行不会立即得到回应。相反,我们等到我们得到一个非空行,然后我们才首先回显之前看到的尽可能多的空行,然后才回显新的非空行。

              【讨论】:

              • 这成功地删除了尾随的空白行(包括只包含空白的行)。但是,它不会在中间空行中保留空白;这些被截断为空行。示例:$'a\n \nb' 转换为 $'a\n\nb'
              【解决方案14】:
              perl -0pe 's/^\n+|\n+(\n)$/\1/gs'
              

              【讨论】:

                【解决方案15】:

                这是一个 awk 版本,它删除了尾随的空行(空行和仅由空格组成的行)。

                内存效率高;它不会将整个文件读入内存。

                awk '/^[[:space:]]*$/ {b=b $0 "\n"; next;} {printf "%s",b; b=""; print;}'
                

                b 变量缓冲空白行;当遇到非空行时,它们会被打印出来。当遇到 EOF 时,它们不会被打印出来。这就是它的工作原理。

                如果使用 gnu awk,[[:space:]] 可以替换为 \s。 (查看gawk-specific Regexp Operators的完整列表。)

                如果您只想删除那些 的尾随行,请参阅@AndyMortimer 的回答。

                【讨论】:

                  【解决方案16】:

                  这可以通过 sed -z 选项轻松解决

                  sed -rz 's/^\n+//; s/\n+$/\n/g' file
                  Hello
                  
                  Welcome to
                  Unix and Linux
                  

                  【讨论】:

                    【解决方案17】:

                    因为我正在编写一个包含一些函数的bash 脚本,所以我发现编写这些函数很方便:

                    function strip_leading_empty_lines()
                    {
                        while read line; do
                            if [ -n "$line" ]; then
                                echo "$line"
                                break
                            fi
                        done
                        cat
                    }
                    
                    function strip_trailing_empty_lines()
                    {
                        acc=""
                        while read line; do
                            acc+="$line"$'\n'
                            if [ -n "$line" ]; then
                                echo -n "$acc"
                                acc=""
                            fi
                        done
                    }
                    
                    

                    【讨论】:

                      猜你喜欢
                      • 2016-03-19
                      • 2016-09-16
                      • 1970-01-01
                      • 1970-01-01
                      • 2011-04-01
                      • 1970-01-01
                      • 2016-07-06
                      • 2018-06-30
                      • 2013-02-10
                      相关资源
                      最近更新 更多