有一个简单的方法,在一个非常标准的UNIX环境中使用bash运行命令来删除目录中除最新的X文件之外的所有文件吗?
为了给出一个具体的例子,想象一下一些cron作业每小时写一个文件(比如一个日志文件或一个tar-up up备份)到一个目录.我想要一种方法来运行另一个cron作业,它将删除该目录中最旧的文件,直到少于5个.
而且要清楚,只有一个文件存在,它永远不应该被删除.
现有答案存在的问题:
无法处理带有嵌入空格或换行符的文件名.
对于rm
直接在不带引号的命令substitution(rm `...`
)上调用的解决方案,会增加意外通配的风险.
无法区分文件和目录(即,如果目录恰好是最近修改的5个文件系统项目之一,那么您实际上将保留少于 5个文件,并且应用于rm
目录将失败).
wnoise的答案解决了这些问题,但解决方案是GNU特定的(并且非常复杂).
这是一个实用的,符合POSIX标准的解决方案,只有一个警告:它无法处理带有嵌入式换行符的文件名- 但我不认为这是大多数人的现实问题.
为了记录,这里解释为什么解析ls
输出通常不是一个好主意:http://mywiki.wooledge.org/ParsingLs
ls -tp | grep -v '/$' | tail -n +6 | xargs -I {} rm -- {}
上面的效率很低,因为xargs
必须rm
为每个文件名调用一次.
您的平台xargs
可能允许您解决此问题:
如果你有GNU xargs
,使用-d '\n'
,这使得xargs
考虑每个输入线路分离的说法,但经过许多参数作为将适合在命令行上一次:
ls -tp | grep -v '/$' | tail -n +6 | xargs -d '\n' -r rm --
-r
(--no-run-if-empty
)确保rm
在没有输入的情况下不调用.
如果你有BSD xargs
(包括OS X),你可以使用-0
处理NUL
-分隔输入,经过第一平移换行至NUL
(0x0
)字符,这也传递(典型值)的所有文件名.在一次(也将与GNU工作xargs
):
ls -tp | grep -v '/$' | tail -n +6 | tr '\n' '\0' | xargs -0 rm --
说明:
ls -tp
打印文件系统项目的名称,按照最近修改的顺序排序,按降序排列(最近修改的项目首先)(-t
),目录打印有尾部/
标记为(-p
).
grep -v '/$'
然后通过省略(-v
)具有尾随/
(/$
)的行来从结果列表中清除目录.
警告:由于指向目录的符号链接在技术上本身不是目录,因此不会排除此类符号链接.
tail -n +6
跳过列表中的前5个条目,实际上返回除了最近修改的5个文件之外的所有文件(如果有的话).
请注意,为了排除N
文件,N+1
必须传递给tail -n +
.
xargs -I {} rm -- {}
(及其变体)然后调用rm
所有这些文件; 如果根本没有比赛,xargs
将不会做任何事情.
xargs -I {} rm -- {}
定义占位符{}
,表示每个输入行作为一个整体,因此rm
然后为每个输入行调用一次,但具有正确处理嵌入空格的文件名.
--
在任何情况下确保了发生在开始任何文件名-
是不误选项通过rm
.
甲变化上的原始问题,在情况下,匹配的文件需要被处理单独或收集在壳阵列:
# 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=$'\n' read -d '' -ra files < <(ls -tp | grep -v '/$' | tail -n +6) printf '%s\n' "${files[@]}" # print array elements
删除目录中除最新文件的5个(或任何数量)之外的所有文件.
rm `ls -t | awk 'NR>5'`
(ls -t|head -n 5;ls)|sort|uniq -u|xargs rm
此版本支持带空格的名称:
(ls -t|head -n 5;ls)|sort|uniq -u|sed -e 's,.*,"&",g'|xargs rm
更简单的thelsdj答案:
ls -tr | head -n -5 | xargs --no-run-if-empty rm
ls -tr显示所有文件,最早的文件(-t最新的第一个,-r反向).
head -n -5显示除最后5行之外的所有行(即5个最新文件).
xargs rm为每个选定的文件调用rm.
find . -maxdepth 1 -type f -printf '%T@ %p\0' | sort -r -z -n | awk 'BEGIN { RS="\0"; ORS="\0"; FS="" } NR > 5 { sub("^[0-9]*(.[0-9]*)? ", ""); print }' | xargs -0 rm -f
需要GNU查找-printf,GNU排序为-z,GNU awk表示"\ 0",GNU xargs表示-0,但处理带有嵌入换行符或空格的文件.
当前目录中有目录时,所有这些答案都会失败.这是有效的:
find . -maxdepth 1 -type f | xargs -x ls -t | awk 'NR>5' | xargs -L1 rm
这个:
当前目录中有目录时有效
尝试删除每个文件,即使前一个文件无法删除(由于权限等)
失败时的文件在当前目录数量过多安全,xargs
通常会去你的过(的-x
)
不适合文件名中的空格(也许你使用的是错误的操作系统?)
ls -tQ | tail -n+4 | xargs rm
按修改时间列出文件名,引用每个文件名.排除前3位(最近3位).删除剩余的.
在mklement0的有用评论之后编辑(谢谢!):更正了-n + 3参数,并注意如果文件名包含换行符和/或目录包含子目录,这将无法按预期工作.
忽略换行符会忽略安全性和良好的编码.wnoise有唯一的好答案.这是他的一个变体,它将文件名放在数组$ x中
while IFS= read -rd ''; do x+=("${REPLY#* }"); done < <(find . -maxdepth 1 -printf '%T@ %p\0' | sort -r -z -n )