45

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

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

4

17 回答 17

64

来自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
于 2011-09-09T09:52:40.697 回答
16

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

tac是 coreutils 的一部分,并反转文件。所以做两次:

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

它当然不是最有效的,但除非您需要效率,否则我发现它比目前为止的其他所有内容都更具可读性。

于 2014-05-27T16:27:19.403 回答
6

这是 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:]]and /^[[:space:]]*$/)的机制,只有空格的内部行将被截断以变成真正的空行。

于 2011-09-09T14:42:31.670 回答
6

正如另一个答案中提到的,tac它是 coreutils 的一部分,并且可以反转文件。将执行两次的想法与命令替换将删除尾随新行的事实相结合,我们得到

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

这不取决于sed. 您可以使用echo -n删除剩余的尾随换行符。

于 2014-07-07T12:35:58.013 回答
4

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

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

它基本上是公认的答案版本(考虑到 BryanH 评论),但.第一个命令中的点已更改为[^[:blank:]](任何非空白),并且\n第二个命令地址内部已更改[[:space:]]为允许换行符、空格和制表符。

另一个版本,不使用 POSIX 类,但您的 sed 必须支持 inserting\t\ninside […]。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$
于 2015-03-05T14:58:01.763 回答
2

使用 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
于 2011-09-09T09:42:36.230 回答
2

对于尾随换行符条(包括“白色”字符)的有效非递归版本,我开发了这个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'
于 2017-06-30T16:12:41.640 回答
2

这可以使用 sed-z选项轻松解决

sed -rz 's/^\n+//; s/\n+$/\n/g' file
Hello

Welcome to
Unix and Linux
于 2020-07-30T17:50:33.147 回答
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

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

于 2011-09-09T09:36:22.773 回答
1

使用bash

$ filecontent=$(<file)
$ echo "${filecontent/$'\n'}"
于 2011-09-09T09:38:21.067 回答
1

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

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

这在操作上非常简单。

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

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

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

于 2015-01-30T09:00:57.850 回答
1

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

BEGIN {
    ne=0;
}

/^[[:space:]]*$/ {
    ne++;
}

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

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

于 2018-11-03T08:57:31.367 回答
1

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

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

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

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

如果使用 gnu awk,[[:space:]]可以替换为\s. (请参阅gawk 特定的正则表达式运算符的完整列表。)

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

于 2020-04-30T05:53:28.420 回答
0

一个bash解决方案

注意:仅当文件小到可以一次读入内存时才有用。

[[ $(<file) =~ ^$'\n'*(.*)$ ]] && echo "${BASH_REMATCH[1]}"
  • $(<file)读取整个文件并修剪尾随的换行符,因为命令替换 ( $(....))隐式执行此操作。
  • =~是 bash 的正则表达式匹配运算符,并且=~ ^$'\n'*(.*)$可以选择匹配任何前导换行符(贪婪地),并捕获后面的任何内容。请注意潜在的混淆,它使用ANSI C 引用$'\n'插入文字换行符,因为不支持转义序列。\n
  • 请注意,此特定正则表达式始终匹配,因此始终&&执行之后的命令。
  • 特殊数组变量BASH_REMATCHrematch 包含最近的正则表达式匹配的结果,数组元素[1]包含(第一个也是唯一的)带括号的子表达式(捕获组)捕获的内容,它是删除任何前导换行符的输入字符串。最终效果是 ${BASH_REMATCH[1]}包含输入文件内容,其中前导换行符和尾随换行符都被剥离。
  • 请注意,打印echo添加了一个尾随换行符。如果您想避免这种情况,请echo -n改用(或使用更便携的printf '%s')。
于 2014-07-07T13:30:05.853 回答
0

我想介绍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
于 2014-11-02T18:07:01.670 回答
0
perl -0pe 's/^\n+|\n+(\n)$/\1/gs'
于 2019-09-05T21:40:35.253 回答
0

因为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
}

于 2021-06-22T11:24:08.383 回答