用sed,awk,tr和朋友删除尾随/开始的换行符

我想从文件中删除所有的空行,但只有当它们在文件的结尾/开始时(也就是说,如果在它们之前没有非空行,则在开头;如果有在他们之后没有非空行,最后。)

这可能是一个function齐全的脚本语言,如Perl或Ruby? 如果可能的话,我宁愿用sedawk来完成。 基本上,任何轻量级和广泛使用的UNIX-y工具都可以,尤其是我可以快速了解更多(Perl,因此不包括在内)。

有用的单行脚本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 

所以我要借用@ dogbane的部分回答,因为删除前导空行的sed行很短…

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

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

这当然不是最有效率的,但是除非你需要效率,否则我发现它比其他所有东西都更具可读性。

这里是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:]]*$/ ),只有空格的内部行将被截断变得真正的空虚。

使用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 

正如另一个答案中提到的, tac是coreutils的一部分 ,并反转一个文件。 将两次做这个想法和命令替换将剥去拖尾新行的事实相结合,我们就可以得到

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

这不依赖于sed 。 你可以使用echo -n去除剩下的换行符。

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

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

这基本上是接受的答案版本(考虑BryanH评论),但点. 在第一个命令中被改为[^[:blank:]] (任何东西都不为空),而第二个命令地址中的\n更改为[[:space:]]以允许换行符,空格标签。

另一个版本,不使用POSIX类,但你的sed必须支持在\t […]内插入\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 -nl $ \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$ 

使用bash

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

在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 

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

一个bash解决方案

注意:只有在文件足够小以便一次读入内存时才有用。

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

我想介绍一下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 

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

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

这在操作上非常简单。

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

所以唯一被缓冲而且从不显示的是任何尾随的空白。

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

对于一个高效的非递归版本的尾随换行条(包括“白色”字符),我已经开发了这个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' 

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

 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'