【发布时间】:2011-11-13 15:26:36
【问题描述】:
我想从文件中删除所有空行,但前提是它们位于文件的末尾/开头(也就是说,如果它们之前没有非空行,则在开头;并且如果它们后面没有非空行,则在最后。)
这是否可能在 Perl 或 Ruby 等功能齐全的脚本语言之外实现?如果可能的话,我更喜欢使用sed 或awk。基本上,任何轻量级且广泛可用的 UNIX-y 工具都可以,尤其是我可以快速了解更多信息的工具(因此不包括 Perl。)
【问题讨论】:
我想从文件中删除所有空行,但前提是它们位于文件的末尾/开头(也就是说,如果它们之前没有非空行,则在开头;并且如果它们后面没有非空行,则在最后。)
这是否可能在 Perl 或 Ruby 等功能齐全的脚本语言之外实现?如果可能的话,我更喜欢使用sed 或awk。基本上,任何轻量级且广泛可用的 UNIX-y 工具都可以,尤其是我可以快速了解更多信息的工具(因此不包括 Perl。)
【问题讨论】:
在 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
伙计,绝对值得学习“真正的”编程语言来避免这种丑陋!
【讨论】:
grep -n的操作。是的!
sed 命令表达了更多内容。)
使用bash
$ filecontent=$(<file)
$ echo "${filecontent/$'\n'}"
【讨论】:
$(<file)) 隐式做到这一点。
使用 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
【讨论】:
来自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
【讨论】:
sed -e :a -e '/^\n*$/{$d;N;ba' -e '}'
sed '/\S/,$!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:]]*$/),只有空格的内部行将被截断以变成真正的空行。
【讨论】:
所以我要借用@dogbane 的部分答案,因为用于删除前导空白行的sed 行太短了...
tac is part of coreutils,并反转文件。所以做两次:
tac file | sed -e '/./,$!d' | tac | sed -e '/./,$!d'
这当然不是最高效的,但除非您需要效率,否则我发现它比目前为止的其他任何东西都更具可读性。
【讨论】:
\n,最后一行将无法正确处理:尝试tac <(printf 'a\nb')。可以说,这种行为是有缺陷的。也会影响tac 的OSX 等效项tail -r。
如another answer、tac is part of coreutils 中所述,并反转文件。将重复执行两次的想法与the fact that command substitution will strip trailing new lines 结合起来,我们得到
echo "$(echo "$(tac "$filename")" | tac)"
不依赖于sed。您可以使用echo -n 去除剩余的尾随换行符。
【讨论】:
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 提到的边缘情况。
bash 解决方案。
注意:只有文件足够小可以立即读入内存时才有用。
[[ $(<file) =~ ^$'\n'*(.*)$ ]] && echo "${BASH_REMATCH[1]}"
$(<file) 读取整个文件并修剪 尾随 换行符,因为命令替换 ($(....)) 隐式 会这样做。=~ 是 bash 的 正则表达式匹配运算符,=~ ^$'\n'*(.*)$ 可选地匹配任何 前导 换行符(贪婪地),并捕获后面的任何内容。请注意可能令人困惑的 $'\n',它使用 ANSI C quoting 插入文字换行符,因为不支持转义序列 \n。&&之后的命令总是被执行。BASH_REMATCH 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 -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$
【讨论】:
对于尾随换行符(包括“白色”字符)的高效非递归版本,我开发了这个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'
【讨论】:
这个 AWK 脚本可以解决问题:
BEGIN {
ne=0;
}
/^[[:space:]]*$/ {
ne++;
}
/[^[:space:]]+/ {
for(i=0; i < ne; i++)
print "";
ne=0;
print
}
这个想法很简单:空行不会立即得到回应。相反,我们等到我们得到一个非空行,然后我们才首先回显之前看到的尽可能多的空行,然后才回显新的非空行。
【讨论】:
$'a\n \nb' 转换为 $'a\n\nb'。
perl -0pe 's/^\n+|\n+(\n)$/\1/gs'
【讨论】:
这是一个 awk 版本,它删除了尾随的空行(空行和仅由空格组成的行)。
内存效率高;它不会将整个文件读入内存。
awk '/^[[:space:]]*$/ {b=b $0 "\n"; next;} {printf "%s",b; b=""; print;}'
b 变量缓冲空白行;当遇到非空行时,它们会被打印出来。当遇到 EOF 时,它们不会被打印出来。这就是它的工作原理。
如果使用 gnu awk,[[:space:]] 可以替换为 \s。 (查看gawk-specific Regexp Operators的完整列表。)
如果您只想删除那些 空 的尾随行,请参阅@AndyMortimer 的回答。
【讨论】:
这可以通过 sed -z 选项轻松解决
sed -rz 's/^\n+//; s/\n+$/\n/g' file
Hello
Welcome to
Unix and Linux
【讨论】:
因为我正在编写一个包含一些函数的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
}
【讨论】: