我想要删除所有空白行和带空格的行(如果存在(仅从文件底部)),然后再删除一行(也仅从文件底部)。
我有这个代码:
while [[ "$last_line" =~ $ ]] || [[ "$last_line" =~ ^[[:space:]]+$ ]]
do
sed -i -e '${/$/d}' "./file.txt"
done
sed -i -e '${/$/d}' "./file.txt"
由于某种原因,循环不会停止,并且会删除文件中的所有内容。这是怎么回事?
答案1
如果像这样修复,你的脚本应该可以工作:
while
last_line=$(tail -1 "./file.txt")
[[ "$last_line" =~ ^$ ]] || [[ "$last_line" =~ ^[[:space:]]+$ ]]
do
sed -i '$d' "./file.txt"
done
你的脚本有两个主要问题:(1)你从来没有更新过$last_line
,所以循环的保护总是会评估同一件事;(2)你的[[ "$last_line" =~ $ ]]
测试匹配任何行,因为任何行都有结束。(这就是你的脚本完全清空文件的原因。)你可能想匹配^$
,它只匹配空行。此外,我简化了sed
删除循环主体中最后一行的命令(简单地$d
完成这项工作)。
但是,这个脚本不必要地复杂。sed
就是为了这种事情!这行代码将执行与上述脚本相同的操作:
sed -i ':a;/^[ \n]*$/{$d;N;ba}' ./file.txt
大致,
- 将当前行与
^[ \n]*$
. 匹配(即只能包含空格和换行符) - 如果不匹配,则打印出来。读下一行并继续步骤 1。
- 如果匹配,
- 如果我们位于文件末尾,则将其删除。
- 如果我们不在文件末尾,附加将下一行复制到当前行,在两者之间插入一个换行符,然后使用这个新的、更长的行返回步骤 1。
互联网上有很多很棒的sed
教程。例如,我可以推荐这个。学习愉快!:-)
更新:当然,如果您还想在截断尾部空白行后删除文件的最后一行(非空白行),您可以sed -i '$d' ./file.txt
在脚本或上述单行代码后使用另一行。我故意不想在单行代码中包含这一点,sed
因为我认为删除尾部空白行是一段可重复使用的代码,其他人可能会感兴趣;但删除最后一行非空白行确实特定于您的用例,而且一旦您删除了尾部空白行,删除就很简单了。
答案2
通过以相反的顺序处理文件的行,这项任务可以更轻松地完成。
tac infile | awk 'flag {print} {if(NF) flag=1}' | tac | sponge infile
正如 Malte Skoruppa 和 zwets 在评论中指出的那样,Ubuntu 并没有moreutils
预装包含的软件包sponge
;另一种解决方案是在 herestring 中使用命令替换来读取输入文件,这样,由于命令替换首先被处理,因此文件可以安全地被第二个命令截断tac
:
<<<"$(< infile)" tac | awk 'flag {print} {if(NF) flag=1}' | tac > infile
tac infile
: ... 与之相反cat infile
(!):打印文件时stdout
反转行的顺序;awk [...]
:处理文件;tac
: ... 与之相反cat
(!):打印文件时stdout
反转行的顺序;sponge infile
infile
:仅当管道左侧终止执行时才输出,以避免infile
在被第一个命令读取之前被截断tac
;
awk
命令分解:
flag {print}
:如果flag
设置了,它将打印该行;直到处理了值与大于的数字匹配flag
的记录后才会设置,因此直到找不到值与大于的数字匹配的记录后,才会跳过该命令;NF
0
NF
0
print
{if(NF) flag=1}
:如果在flag
仍未设置 的情况下,处理了 的一个记录,其NF
值与 的数字匹配0
,则不会打印该记录并将其flag
设置为1
,因此第一个NF
值与 的数字匹配的记录0
将不会被打印;
在测试文件上进行测试(请注意,第 4 行和第 7 行包含 5 个空格,而第 5 行和第 8 行是空的):
user@debian ~ % cat infile
line1
line2
line3
line6
user@debian ~ % tac infile | awk 'flag {print} {if(NF) flag=1}' | tac
line1
line2
line3
user@debian ~ %
删除了第 7 行和第 8 行,因为它们都位于文件末尾,仅包含空格(第 7 行)或不包含任何内容(第 8 行);删除了第 6 行,因为它是第一个以相反顺序读取文件行且至少包含 1 个字段(因此不为空或仅包含空格)的行
答案3
编辑
所以最初,我忽略了 OP 只想删除最后一个空白行这一点,而我原来的解决方案也是这样做的。尽管如此,以下版本只删除最后一个空白行,并且如果它是空白的。
awk -v numlines=$(wc -l file2|cut -f1 -d' ') 'NR < numlines; END {if (NF) print }' file2
代码的作用相当简单 - 获取行数,并打印每一行,直到最后一行。在最后一行,我们检查该行是否包含任何字段;如果有任何文本,NF 计算结果为整数 (true),从而打印最后一行,如果没有文本或只有空格 - NF 计算结果为零 (false),不打印任何其他内容。
至于删除一行,head -n -1
就可以了。
下面是一个小演示。尾随换行符用$
提示指定*$
*$ cat -A file2
212$
1231$
$
324234$
213$
$
*$ awk -v numlines=$(wc -l file2|cut -f1 -d' ') 'NR < numlines ; END {if ( NF ) print }' file2 | head -n -1
212
1231
324234
原来的
awk
解决方案。
awk 'NF' file1 > /tmp/tmpfile && cat /tmp/tmpfile > file1
这里我们使用字段数变量作为打印测试。对于空白行,字段数为零,因此不会打印评估为 false 的空白行。现在,除非您的awk
版本支持内联编辑(gnu awk or gawk
我认为是),否则您必须将输出重定向到临时输出并返回到原始文件cat
,就像我在这里所做的那样
主题的变化是使用正则表达式来测试行是否包含某些特定数据,如数字或字母数字字符,例如
awk '$0~/[[:digit:]]||[[:alpha:]]/ ' file1 > /tmp/tmpfile && cat /tmp/tmpfile > file1
答案4
我认为
- 删除文件末尾的空行
- 删除文件末尾有空格的行
- 和然后删除一行
使用awk
和tac
tac foo | awk '! non_empty && ! /^$/ && ! /[ \t]/ {non_empty = 1} non_empty {skip++} skip > 1 {print}' | tac
示例下方有更多变化……
例子
% cat -n foo
1 line1
2 line2
3 line3
4
5
6 line6
7 line 7
8
% tac foo | awk '! non_empty && ! /^$/ && ! /[ \t]/ {non_empty = 1} non_empty {skip++} skip > 1 {print}' | tac > bar
% cat -n bar
1 line1
2 line2
3 line3
4
5
仅删除文件末尾的空白行
tac foo | awk '! non_empty && ! /^$ {non_empty = 1} non_empty {print}' | tac
删除末尾的空白行和一行
tac foo | awk '! non_empty && ! /^$/ && {non_empty = 1} non_empty {skip++} skip > 1 {print}' | tac