删除文件末尾的所有空白行或带有空格的行

删除文件末尾的所有空白行或带有空格的行

我想要删除所有空白行和带空格的行(如果存在(仅从文件底部)),然后再删除一行(也仅从文件底部)。

我有这个代码:

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

大致,

  1. 将当前行与^[ \n]*$. 匹配(即只能包含空格和换行符)
  2. 如果不匹配,则打印出来。读下一行并继续步骤 1。
  3. 如果匹配,
    • 如果我们位于文件末尾,则将其删除。
    • 如果我们不在文件末尾,附加将下一行复制到当前行,在两者之间插入一个换行符,然后使用这个新的、更长的行返回步骤 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 infileinfile:仅当管道左侧终止执行时才输出,以避免infile在被第一个命令读取之前被截断tac

awk命令分解:

  • flag {print}:如果flag设置了,它将打印该行;直到处理了值与大于的数字匹配flag的记录后才会设置,因此直到找不到值与大于的数字匹配的记录后,才会跳过该命令;NF0NF0print
  • {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

我认为

  • 删除文件末尾的空行
  • 删除文件末尾有空格的行
  • 然后删除一行

使用awktac

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

相关内容