[英]Delete all but the most recent X files in bash
有没有一种简单的方法,在带有 bash 的非常标准的 UNIX 环境中,运行一条命令从目录中删除除最新的 X 文件以外的所有文件?
举一个更具体的例子,假设某个 cron 作业每小时将一个文件(例如,一个日志文件或一个压缩的备份)写到一个目录中。 我想要一种运行另一个 cron 作业的方法,该作业将删除该目录中最旧的文件,直到少于 5 个为止。
需要说明的是,只有一个文件存在,永远不应该删除它。
现有答案的问题:
rm `...`
) 上调用rm
的解决方案,会增加意外通配的风险。rm
应用于目录将失败)。wnoise 的回答解决了这些问题,但解决方案是特定于GNU 的(并且非常复杂)。
这是一个务实的、符合 POSIX 的解决方案,只有一个警告:它不能处理带有嵌入换行符的文件名 - 但我不认为这对大多数人来说是一个现实世界的问题。
作为记录,这里解释了为什么解析ls
输出通常不是一个好主意: http : //mywiki.wooledge.org/ParsingLs
ls -tp | grep -v '/$' | tail -n +6 | xargs -I {} rm -- {}
注意:该命令在当前目录下运行; 要明确定位目录,请使用子shell ( (...)
): (cd /path/to && 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
(包括在macOS 上),您可以在首先将换行符转换为NUL
( 0x0
) 字符后使用-0
处理NUL
分隔的输入,这也(通常)一次传递所有文件名(也适用于GNU xargs
):
ls -tp | grep -v '/$' | tail -n +6 | tr '\n' '\0' | xargs -0 rm --
解释:
ls -tp
打印文件系统项的名称,按最近修改的时间排序,按降序排列(最近修改的项目在前)( -t
),目录打印有尾随/
以标记它们( -p
)。
ls -tp
总是只输出文件/目录名,而不是完整路径,这就需要上面提到的子shell方法来定位当前目录以外的目录( (cd /path/to && ls -tp ...)
)。 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
-printf 需要 GNU find,-z 需要 GNU sort,"\\0" 需要 GNU awk,-0 需要 GNU xargs,但处理带有嵌入换行符或空格的文件。
当当前目录中有目录时,所有这些答案都会失败。 这是有效的方法:
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 )
如果文件名没有空格,这将起作用:
ls -C1 -t| awk 'NR>5'|xargs rm
如果文件名确实有空格,例如
ls -C1 -t | awk 'NR>5' | sed -e "s/^/rm '/" -e "s/$/'/" | sh
基本逻辑:
我意识到这是一个旧线程,但也许有人会从中受益。 此命令将在当前目录中查找文件:
for F in $(find . -maxdepth 1 -type f -name "*_srv_logs_*.tar.gz" -printf '%T@ %p\n' | sort -r -z -n | tail -n+5 | awk '{ print $2; }'); do rm $F; done
这比之前的一些答案更强大,因为它允许将搜索域限制为匹配表达式的文件。 首先,找到与您想要的任何条件匹配的文件。 打印带有时间戳的文件。
find . -maxdepth 1 -type f -name "*_srv_logs_*.tar.gz" -printf '%T@ %p\n'
接下来,按时间戳对它们进行排序:
sort -r -z -n
然后,从列表中删除 4 个最近的文件:
tail -n+5
获取第二列(文件名,而不是时间戳):
awk '{ print $2; }'
然后将整个事情包装成一个 for 语句:
for F in $(); do rm $F; done
这可能是一个更冗长的命令,但我有更好的运气能够定位条件文件并针对它们执行更复杂的命令。
用 zsh
假设您不关心当前目录,并且您的文件不会超过 999 个(如果需要,请选择更大的数字,或者创建一个 while 循环)。
[ 6 -le `ls *(.)|wc -l` ] && rm *(.om[6,999])
在*(.om[6,999])
, .
表示文件, o
表示排序顺序, m
表示按修改日期(输入a
表示访问时间或c
表示 inode 更改), [6,999]
选择文件范围,因此不首先 rm 5。
在 Sed-Onliners 中发现了有趣的 cmd - 删除最后 3 行 - 发现它非常适合另一种给猫剥皮的方法(好吧不是)但想法:
#!/bin/bash
# sed cmd chng #2 to value file wish to retain
cd /opt/depot
ls -1 MyMintFiles*.zip > BigList
sed -n -e :a -e '1,2!{P;N;D;};N;ba' BigList > DeList
for i in `cat DeList`
do
echo "Deleted $i"
rm -f $i
#echo "File(s) gonzo "
#read junk
done
exit 0
删除除 10 个最新(最近)文件之外的所有文件
ls -t1 | head -n $(echo $(ls -1 | wc -l) - 10 | bc) | xargs rm
如果少于 10 个文件没有文件被删除,你将有:错误头:非法行数 -- 0
我需要一个优雅的busybox(路由器)解决方案,所有xargs或数组解决方案对我来说都没用——那里没有这样的命令。 find 和 mtime 不是正确的答案,因为我们谈论的是 10 个项目,不一定是 10 天。 埃斯波的回答是最短、最干净的,也可能是最普遍的。
空格错误和没有文件被删除时都可以通过标准方式简单地解决:
rm "$(ls -td *.tar | awk 'NR>7')" 2>&-
更具教育意义的版本:如果我们以不同的方式使用 awk,我们可以做到这一切。 通常,我使用此方法将变量从 awk 传递(返回)到 sh。 由于我们一直在阅读无法完成的内容,因此我不同意:这是方法。
.tar 文件的示例,文件名中的空格没有问题。 要进行测试,请将“rm”替换为“ls”。
eval $(ls -td *.tar | awk 'NR>7 { print "rm \"" $0 "\""}')
解释:
ls -td *.tar
列出所有按时间排序的 .tar 文件。 要应用于当前文件夹中的所有文件,请删除“d *.tar”部分
awk 'NR>7...
跳过前 7 行
print "rm \\"" $0 "\\""
构造一行:rm "file name"
eval
执行它
由于我们使用的是rm
,我不会在脚本中使用上述命令! 更明智的用法是:
(cd /FolderToDeleteWithin && eval $(ls -td *.tar | awk 'NR>7 { print "rm \"" $0 "\""}'))
在使用ls -t
命令的情况下,不会对诸如touch 'foo " bar'
和touch 'hello * world'
类的愚蠢示例造成任何伤害。并不是说我们在现实生活中创建过具有此类名称的文件!
边注。 如果我们想以这种方式将变量传递给 sh,我们只需修改打印(简单形式,不允许空格):
print "VarName="$1
将变量VarName
设置为$1
的值。 可以一次性创建多个变量。 这个VarName
成为一个普通的 sh 变量,之后可以在脚本或 shell 中正常使用。 因此,要使用 awk 创建变量并将它们返回给 shell:
eval $(ls -td *.tar | awk 'NR>7 { print "VarName=\""$1"\"" }'); echo "$VarName"
使用一些参数调整@mklement0 的出色答案,而无需导航到包含要删除的文件的文件夹...
TARGET_FOLDER="/my/folder/path"
FILES_KEEP=5
ls -tp "$TARGET_FOLDER"**/* | grep -v '/$' | tail -n +$((FILES_KEEP+1)) | xargs -d '\n' -r rm --
[参考文献:https://stackoverflow.com/a/3572628/3223785]
谢谢!
leaveCount=5
fileCount=$(ls -1 *.log | wc -l)
tailCount=$((fileCount - leaveCount))
# avoid negative tail argument
[[ $tailCount < 0 ]] && tailCount=0
ls -t *.log | tail -$tailCount | xargs rm -f
我把它变成了一个 bash shell 脚本。 用法: keep NUM DIR
其中 NUM 是要保留的文件数,DIR 是要清理的目录。
#!/bin/bash
# Keep last N files by date.
# Usage: keep NUMBER DIRECTORY
echo ""
if [ $# -lt 2 ]; then
echo "Usage: $0 NUMFILES DIR"
echo "Keep last N newest files."
exit 1
fi
if [ ! -e $2 ]; then
echo "ERROR: directory '$1' does not exist"
exit 1
fi
if [ ! -d $2 ]; then
echo "ERROR: '$1' is not a directory"
exit 1
fi
pushd $2 > /dev/null
ls -tp | grep -v '/' | tail -n +"$1" | xargs -I {} rm -- {}
popd > /dev/null
echo "Done. Kept $1 most recent files in $2."
ls $2|wc -l
对于 Linux(GNU 工具),在删除 rest 的同时将n
最新文件保留在当前目录中的可靠方法:
n=5
find . -mindepth 1 -maxdepth 1 -type f -printf '%T@\t%p\0' |
sort -znr -k1,1 |
sed -z -e "1,${n}d" -e 's/^[^\t]*\t//' |
xargs -0r rm -f
对于 BSD(我找不到一种有效且健壮的方法,所以我发布了这个不处理路径中的换行符的方法):
n=5
t=$(printf '\t')
find . -mindepth 1 -maxdepth 1 -type f -exec stat -f '%m%t%N' {} + |
sort -nr -k1,1 |
sed -e "1,${n}d; s/^[^$t]*$t//" -e 's/[^[:alnum:]]/\\&/g' |
xargs rm -f
如果要指定路径,请修改@Fabien 答案的版本。 如果您在其他地方运行脚本,这很有用。
ls -tr /path/foo/ | head -n -5 | xargs -I% --no-run-if-empty rm /path/foo/%
声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.