如何从目录中删除所有文件的最后一行?


17

我的目录中有许多文本文件,我想删除目录中每个文件的最后一行。

我该怎么做?


6
你尝试了什么?unix.stackexchange.com/help/how-to-ask:“分享您的研究成果对每个人都有帮助。告诉我们您所发现的内容以及为什么它不能满足您的需求。这表明您已经花了一些时间来尝试帮助自己,它使我们免于重复明显的答案,最重要的是,它可以帮助您获得更具体和相关的答案!”
帕特里克

为什么有人不小心将其应用于/ etc的想法会产生非常特殊的
恐惧

Answers:


4

如果您有权访问vim,则可以使用:

for file in ./*
do
  if [ -f "${file}" ]
  then
    vim -c '$d' -c "wq" "${file}"
  fi
done

16

如果您有GNU,则可以使用这个不错的oneliner sed

 sed -i '$ d' ./*

它将删除当前目录中每个未隐藏文件的最后一行。-iGNU的Switch sed表示就地操作,并'$ d'命令sed删除最后一行($意味着last和ddelete)。


3
如果该文件夹包含除常规文件以外的任何内容,例如另一个文件夹,这将引发错误(并且不执行任何操作)
Najib Idrissi

1
@StefanR您正在使用的-i是GNUism,所以这没有什么意义,但是如果我未能指出某些旧版本的sed不允许您在$和之间放置任何空格,那么我将失去旧的胡须d。通常,在模式和命令之间)。
zwol

1
@zwol如我所写,这将导致错误,而不是警告,并且一旦到达该文件,sed将放弃(至少使用我拥有的sed版本)。下一个文件将不被处理。丢弃错误消息将是一个糟糕的主意,因为您甚至都不知道它发生了!使用zsh,您可以使用*(.)glob来存储常规文件,而我不了解其他shell。
纳吉·伊德里斯

@NajibIdrissi嗯,你是对的。这让我感到惊讶;我本来希望它抱怨目录,但随后转到命令行的下一个文件。实际上,我想我将其报告为错误。
zwol

@don_crissti我也有GNU sed v4.3 ...我不知道要告诉你什么,我只是再次测试。gist.github.com/nidrissi/66fad6be334234f5dbb41c539d84d61e
Najib Idrissi

11

如果目录包含常规文件或文件名中带有空格/换行符的文件,则其他所有答案均存在问题。无论如何,这都是可行的:

find "$dir" -type f -exec sed -i '$d' '{}' '+'
  • find "$dir" -type f:在目录中找到文件 $dir
    • -type f 是常规文件;
    • -exec 在找到的每个文件上执行命令
    • sed -i:在适当位置编辑文件;
    • '$d':删除(d)最后($)行。
    • '+':告诉find继续添加参数sed(由于@zwol,比分别为每个文件运行命令效率更高)。

如果您不想进入子目录,则可以将参数添加-maxdepth 1到中find


1
嗯,但不像其他答案这个下降到子目录。(此外,使用当前版本find可以更高效地编写find $dir -type f -exec sed -i '$d' '{}' '+'。)
zwol

@zwol谢谢,我已将其添加到答案中。
纳吉·伊德里斯

-print0在完整命令中不存在,为什么要添加到解释中?
Ruslan

1
另外,-depth 0它不起作用(findutils 4.4.2),应该改为-maxdepth 1
Ruslan

@Ruslan我有一个使用xargs的第一个版本,但后来想起了-exec
纳吉·伊德里斯

9

使用GNU sed -i '$d'意味着读取完整文件并在没有最后一行的情况下对其进行复制,而仅将文件截断到位(至少对于大文件而言)会更有效率。

使用GNU truncate,您可以执行以下操作:

for file in ./*; do
  [ -f "$file" ] &&
    length=$(tail -n 1 "$file" | wc -c) &&
    [ "$length" -gt 0 ] &&
    truncate -s "-$length" "$file"
done

如果文件相对较小,则效率可能会降低,因为每个文件运行多个命令。

请注意,对于在最后一个换行符之后(在最后一行之后)包含额外字节的文件,或者换句话说,如果文件的最后一行没有分隔符,那么根据tail实现的不同,文件tail -n 1将仅返回那些额外的字节(例如GNU tail),或最后一行(正确定界)以及那些额外的字节。


你需要一个|wc -ctail电话吗?(或a ${#length}
杰夫·谢勒

@JeffSchaller。哎呀。实际上是wc -c。${#length}因为它计算字符数而不是字节数,所以将不起作用,并且$(...)将删除尾部的换行符,因此${#...}即使所有字符都是单字节的,也将被一掉。
斯特凡Chazelas

6

一种更可移植的方法:

for f in ./*
do
test -f "$f" && ed -s "$f" <<\IN
d
w
q
IN
done

我不认为这需要任何解释......除了可能在这种情况下d是一样的$d,因为ed默认情况下选择的最后一行。
这不会递归搜索,也不会处理隐藏文件(又名dotfiles)。
如果您也想编辑它们,请参阅如何将*与目录中的隐藏文件进行匹配


真好!如果更改[[]]为,[]则它将完全符合POSIX。([[ ... ]]是Bashism。)
Wildcard

@Wildcard-谢谢,更改了(尽管[[不是bashism
don_crissti

我应该说是非POSIX主义。:)
通配符

3

递归地从当前目录开始的所有文件(包括点文件)的POSIX兼容单行代码:

find . -type f -exec sh -c 'for f; do printf "\$d\nx\n" | ex "$f"; done' sh {} +

.txt仅对于文件(非递归):

find . -path '*/*/*' -prune -o -type f -name '*.txt' -exec sh -c 'for f; do printf "\$d\nx\n" | ex "$f"; done' sh {} +

另请参阅:

By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.