如何在Unix中删除文件的最后一个字符?

91

假设我有一个任意的多行文本文件:

sometext
moretext
lastline

我怎样才能删除文件中仅有的最后一个字符(不是换行或null)而不使文本文件失效?


你为解决这个问题做了什么?-1 - Jotne
6
列出一堆删除每行末尾字符的垃圾sed和awk命令并不觉得非常有建设性。呵呵,知道我会因此受到责难。尽管如此,我仍然无法忍心把这句话留下来:“我尝试了许多sed和awk命令,但只能以多种方式剥离每行的最后一个字符”。 - MaxPRafferty
9个回答

133
一个更简单的方法(输出到标准输出,不更新输入文件):
sed '$ s/.$//' somefile
  • $ 是一个 Sed 地址,仅匹配最后一行输入,因此只会在最后一行上执行以下函数调用 (s/.$//)。

  • s/.$// 将(在这种情况下是最后)行上的最后一个字符替换为空字符串;即有效地删除行上的最后一个字符(换行符之前)。

    • 注意:如果文件以两个或更多换行符结尾,则该命令是一个空操作

. 匹配行上的任何字符,并在其后加上$将匹配锚定到行的末尾;请注意,此正则表达式中的$的使用在概念上与之前作为 Sed 地址使用的$相关,但在技术上是不同的。

使用标准输入示例(假设是Bash、Ksh或Zsh): $ sed '$ s/.$//' <<< $'line one\nline two' line one line tw
更新输入文件(如果输入文件不是符号链接,请勿使用)。
sed -i '$ s/.$//' somefile

注意:

  • 在 macOS 上,您需要使用 -i '' 而不是只使用 -i;有关与 -i 相关的陷阱的概述,请参阅 this answer 的下半部分。
  • 如果您需要处理非常大的输入文件,并且性能/磁盘使用是一个问题,并且您正在使用 GNU 工具(Linux),请参阅 ImHere's helpful answer

@curiousity,不会失败,但如果文件末尾有两个或更多个换行符,则会失败;我已经更新了答案,以明确这一点。 - undefined

83

截断

truncate -s-1 file

从同一文件的末尾删除一个字符(-1)。与>>追加到同一文件完全相同。

这种方法的问题是,如果存在换行符,则不会保留尾随换行符。

解决方案是:

if     [ -n "$(tail -c1 file)" ]    # if the file has not a trailing new line.
then
       truncate -s-1 file           # remove one char as the question request.
else
       truncate -s-2 file           # remove the last two characters
       echo "" >> file              # add the trailing new line back
fi

这个方法有效是因为tail取的是最后一个字节(而不是字符)。

即使处理大文件也几乎不需要时间。

为什么不用sed

使用类似sed '$ s/.$//' file的sed解决方案的问题在于它首先读取整个文件(处理大文件需要很长时间),然后你需要一个临时文件(与原始文件大小相同):

sed '$ s/.$//' file  > tempfile
rm file; mv tempfile file

然后将临时文件移动以替换该文件。


1
在 Mac 上,您应该首先执行 brew install truncate,然后再执行 truncate -s -1 file - dux2

4
这里是另一个使用ex的示例,我认为它不像sed解决方案那样神秘。
 printf '%s\n' '$' 's/.$//' wq | ex somefile
$移到最后一行,s删除最后一个字符,wq是众所周知(对于vi用户)的写入+退出。

2

在尝试了各种不同的策略(并避免使用sed -i或perl)之后,我发现最好的方法是:

sed '$! { P; D; }; s/.$//' somefile

1
不确定为什么你避免使用sed -i。这只是一个将数据写回文件的函数。现在你只能在监视器上获得输出。 - Jotne

2
如果目标是删除最后一行中的最后一个字符,这个awk应该可以做到:
awk '{a[NR]=$0} END {for (i=1;i<NR;i++) print a[i];sub(/.$/,"",a[NR]);print a[NR]}' file
sometext
moretext
lastlin

它将所有数据存储到数组中,然后打印出来并更改最后一行。

我尝试将>输出到同一文件,但没有成功。建议将其输出到新文件中,删除旧文件,然后根据需要更改文件名。 - MayTheSForceBeWithYou
@MayTheSForceBeWithYou Gnu Awk 4.1及更高版本具有内联编辑功能,因此请尝试使用awk -i 'your code' file - Jotne

2

仅供参考:sed将暂时删除文件。因此,如果您正在追踪该文件,则会收到“无此文件或目录”的警告,直到重新发出tail命令。


1

编辑后的答案

我创建了一个脚本,并将您的文本放在桌面上。这个测试文件保存为“old_file.txt”。

sometext
moretext
lastline

随后,我编写了一个小脚本来获取旧文件并删除最后一行中的最后一个字符
#!/bin/bash
no_of_new_line_characters=`wc  '/root/Desktop/old_file.txt'|cut -d ' ' -f2`
let "no_of_lines=no_of_new_line_characters+1"
sed -n 1,"$no_of_new_line_characters"p  '/root/Desktop/old_file.txt' > '/root/Desktop/my_new_file'
sed -n "$no_of_lines","$no_of_lines"p '/root/Desktop/old_file.txt'|sed 's/.$//g' >> '/root/Desktop/my_new_file'

打开我创建的新文件,显示的输出如下:
sometext
moretext
lastlin

我为之前的回答道歉(没有仔细阅读)


1
它要求仅删除文件的最后一个字符,而不是每行的最后一个字符。 - Robin Hsu
1
非常抱歉,我的回答没有仔细阅读。我使用了一个小脚本来解决问题。希望这可以帮到MaxPRafferty。 - repzero

0
一些 Perl 解决方案,用于比较/参考:
(echo 1a; echo 2b) | perl -e '$_=join("",<>); s/.$//; print'
(echo 1a; echo 2b) | perl -e 'while(<>){ if(eof) {s/.$//}; print }'
我发现第一个将整个文件读入内存的方法通常非常有用(对于这个特定问题来说不是那么有用)。例如,您现在可以使用跨越多行的正则表达式,将某种格式的每3行组合成1个摘要行。
对于这个问题,truncate 会更快,而 sed 版本则更短。请注意,truncate 需要在文件上操作,而不是在流上操作。通常我发现 sed 缺乏 perl 强大的功能,我更喜欢扩展的正则表达式/perl-regex 语法。但是这个问题有一个不错的 sed 解决方案。

0

sed 's/.$//' 文件名 | tee 新文件名

这应该能完成你的任务。


3
这将从每行中移除最后一个字符,而不仅仅是最后一行的最后一个字符。 - MaxPRafferty
@MaxPRafferty 我错了,我以为问题是要删除每行的最后一个字符。 - karthik339

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接