问题描述
有没有一种简单的方法,在一个非常标准的 UNIX 环境中使用 bash,运行一个命令来从目录中删除除最新的 X 文件之外的所有文件?
Is there a simple way, in a pretty standard UNIX environment with bash, to run a command to delete all but the most recent X files from a directory?
再举一个具体的例子,想象一下某个 cron 作业每小时将一个文件(例如,一个日志文件或一个 tar 备份)写到一个目录中.我想要一种方法来运行另一个 cron 作业,该作业将删除该目录中最旧的文件,直到少于 5 个.
To give a bit more of a concrete example, imagine some cron job writing out a file (say, a log file or a tar-ed up backup) to a directory every hour. I'd like a way to have another cron job running which would remove the oldest files in that directory until there are less than, say, 5.
需要说明的是,只有一个文件存在,永远不应删除.
And just to be clear, there's only one file present, it should never be deleted.
推荐答案
现有答案的问题:
- 无法处理带有嵌入空格或换行符的文件名.
- 对于直接在未加引号的命令替换 (
rm `...`
) 上调用rm
的解决方案,会增加意外通配的风险.莉>
- inability to handle filenames with embedded spaces or newlines.
- in the case of solutions that invoke
rm
directly on an unquoted command substitution (rm `...`
), there's an added risk of unintended globbing.
wnoise 的答案 解决了这些问题,但解决方案是 GNU 特定的(并且相当复杂).
wnoise's answer addresses these issues, but the solution is GNU-specific (and quite complex).
这是一个务实的、POSIX 兼容的解决方案,它只有一个警告:它不能处理带有嵌入换行符的文件名 - 但我不不认为这是大多数人的现实问题.
Here's a pragmatic, POSIX-compliant solution that comes with only one caveat: it cannot handle filenames with embedded newlines - but I don't consider that a real-world concern for most people.
ls -tp | grep -v '/$' | tail -n +6 | xargs -I {} rm -- {}
以上是低效,因为
xargs
必须为每个文件名分别调用rm
.
但是,您平台的特定xargs
实现可能允许您解决此问题:The above is inefficient, because
xargs
has to invokerm
separately for each filename.
However, your platform's specificxargs
implementation may allow you to solve this problem:与 GNU
xargs
一起使用的解决方案是使用-d ''
,这使得xargs
将每个输入行视为一个单独的参数,但同时传递尽可能多的参数,以一次:A solution that works with GNU
xargs
is to use-d ''
, which makesxargs
consider each input line a separate argument, yet passes as many arguments as will fit on a command line at once:ls -tp | grep -v '/$' | tail -n +6 | xargs -d ' ' -r rm --
一个适用于两者 GNU
xargs
和 BSD的解决方案>xargs
(包括在 macOS 上) - 虽然技术上仍然不 POSIX 兼容 - 是使用-0
处理NUL
分隔的输入,首先将换行符转换为NUL
(0x0
) 字符后,它还传递(通常)所有文件名一次:A solution that works with both GNU
xargs
and BSDxargs
(including on macOS) - though technically still not POSIX-compliant - is to use-0
to handleNUL
-separated input, after first translating newlines toNUL
(0x0
) chars., which also passes (typically) all filenames at once:ls -tp | grep -v '/$' | tail -n +6 | tr ' ' ' ' | xargs -0 rm --
说明:
ls -tp
打印文件系统项的名称,按最近修改的时间排序,降序排列(最近修改的项在前)(-t
), 目录打印有尾随/
以标记它们 (-p
).
ls -tp
prints the names of filesystem items sorted by how recently they were modified , in descending order (most recently modified items first) (-t
), with directories printed with a trailing/
to mark them as such (-p
).
- 注意:事实上,
ls -tp
总是只输出文件/目录名称,而不是完整路径,这就需要上面提到的子shell方法来定位目录不同于当前的 ((cd/path/to && ls -tp ...)
).
- Note: It is the fact that
ls -tp
always outputs file / directory names only, not full paths, that necessitates the subshell approach mentioned above for targeting a directory other than the current one ((cd /path/to && ls -tp ...)
).
grep -v '/$'
然后通过省略具有尾随的 (
(-v
) 行,从结果列表中清除目录//$
).grep -v '/$'
then weeds out directories from the resulting listing, by omitting (-v
) lines that have a trailing/
(/$
).- 警告:由于指向目录的符号链接在技术上本身不是目录,因此不会排除此类符号链接.莉>
- Caveat: Since a symlink that points to a directory is technically not itself a directory, such symlinks will not be excluded.
tail -n +6
跳过列表中的前 5 个条目,实际上返回所有但最近的 5 个条目修改过的文件,如果有的话.
请注意,为了排除N
个文件,必须将N+1
传递给tail -n +
.tail -n +6
skips the first 5 entries in the listing, in effect returning all but the 5 most recently modified files, if any.
Note that in order to excludeN
files,N+1
must be passed totail -n +
.xargs -I {} rm -- {}
(及其变体)然后在所有这些文件上调用rm
;如果根本没有匹配项,xargs
不会做任何事情.xargs -I {} rm -- {}
(and its variations) then invokes onrm
on all these files; if there are no matches at all,xargs
won't do anything.xargs -I {} rm -- {}
定义占位符{}
代表每个输入行作为一个整体,所以rm
然后为每个输入行调用一次,但正确处理带有嵌入空格的文件名.--
在所有情况下确保任何以-
开头的文件名不会被rm 误认为 options
.
xargs -I {} rm -- {}
defines placeholder{}
that represents each input line as a whole, sorm
is then invoked once for each input line, but with filenames with embedded spaces handled correctly.--
in all cases ensures that any filenames that happen to start with-
aren't mistaken for options byrm
.
原始问题的变体,以防匹配的文件需要单独处理或收集在一个shell数组中:
A variation on the original problem, in case the matching files need to be processed individually or collected in a shell array:
# One by one, in a shell loop (POSIX-compliant): ls -tp | grep -v '/$' | tail -n +6 | while IFS= read -r f; do echo "$f"; done # One by one, but using a Bash process substitution (<(...), # so that the variables inside the `while` loop remain in scope: while IFS= read -r f; do echo "$f"; done < <(ls -tp | grep -v '/$' | tail -n +6) # Collecting the matches in a Bash *array*: IFS=$' ' read -d '' -ra files < <(ls -tp | grep -v '/$' | tail -n +6) printf '%s ' "${files[@]}" # print array elements
这篇关于删除 bash 中除最新的 X 文件之外的所有文件的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持!
- in the case of solutions that invoke
- 对于直接在未加引号的命令替换 (