在带有bash的标准UNIX环境中,是否有一种简单的方法来运行命令以从目录中删除除最新的X文件以外的所有文件?
再给出一个具体的例子,想象一下一些cron作业每小时将一个文件(例如,日志文件或已备份的备份)写到目录中。 我想要一种方法来运行另一个cron作业,该作业将删除该目录中最旧的文件,直到少于5个文件为止。
而且要清楚一点,只有一个文件存在,永远不要删除它。
现有答案存在的问题:
-
无法处理带有嵌入式空格或换行符的文件名。
-
如果解决方案直接在无引号的命令替换(rm `...`)上调用rm,则存在意外滚动的风险。
-
无法区分文件和目录(即,如果目录恰好是最近修改的5个文件系统项之一,则您实际上将保留少于5个文件,并且将rm应用于目录将失败)。
wnoise的答案解决了这些问题,但是解决方案是特定于GNU的(而且相当复杂)。
这是一个实用的,符合POSIX的解决方案,只有一个警告:它无法处理带有嵌入式换行符的文件名-但我认为对于大多数人来说,这并不是现实问题。
作为记录,以下是为什么解析ls输出通常不是一个好主意的说明:http://mywiki.wooledge.org/ParsingLs
1
| ls -tp | grep -v '/$' | tail -n +6 | xargs -I {} rm -- {} |
上面的方法效率低下,因为xargs必须为每个文件名调用一次rm。
您平台的xargs可以让您解决此问题:
如果您具有GNU xargs,请使用-d '
',这会使xargs将每条输入行视为一个单独的参数,但会同时传递与命令行中相同数量的参数:
1 2
| ls -tp | grep -v '/$' | tail -n +6 | xargs -d '
' -r rm -- |
-r(--no-run-if-empty)确保在没有输入的情况下不调用rm。
如果您具有BSD xargs(包括OS X),则可以在第一次将换行符转换为NUL(0x0)字符后使用-0处理NUL分隔的输入。 )一次所有文件名(也将与GNU xargs一起使用):
1 2
| ls -tp | grep -v '/$' | tail -n +6 | tr '
' '\0' | xargs -0 rm -- |
说明:
-
ls -tp打印文件系统项的名称,这些文件系统项的名称按降序排列(降序排列(首先是最近修改的项))(-t),并在目录上打印尾随/以将其标记为()。
-
grep -v '/$'然后通过省略(-v)行尾带有/(/$)的行从结果列表中清除目录。
-
注意:由于指向目录的符号链接在技术上本身并不是目录,因此不会排除此类符号链接。
-
tail -n +6跳过列表中的前5个条目,实际上返回除5个最近修改的文件(如果有)以外的所有文件。
请注意,为了排除N文件,必须将N+1传递给tail -n +。
-
然后xargs -I {} rm -- {}(及其变体)在所有这些文件上的rm上调用;如果根本没有匹配项,则xargs将不执行任何操作。
-
xargs -I {} rm -- {}定义占位符{},该占位符代表整个输入行,因此,对于每个输入行,调用一次rm,但正确处理带有嵌入空格的文件名。
-
在所有情况下,--都确保不会将-开头的任何文件名都误认为rm的选项。
如果匹配文件需要单独处理或收集在shell数组中,则是原始问题的变体:
1 2 3 4 5 6 7 8 9 10 11 12
| # 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 |
删除目录中5个(或任意数量)的最新文件。
1
| (ls -t|head -n 5;ls)|sort|uniq -u|xargs rm |
此版本支持带空格的名称:
1
| (ls -t|head -n 5;ls)|sort|uniq -u|sed -e 's,.*,"&",g'|xargs rm |
thelsdj答案的简单变体:
1
| ls -tr | head -n -5 | xargs --no-run-if-empty rm |
ls -tr显示所有文件,从最旧的开始(-t最新的在先,-r反向)。
head -n -5显示除最后5行(即5个最新文件)以外的所有内容。
xargs rm为每个选定文件调用rm。
1
| 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查找,对于-z需要GNU排序,对于" 0"需要GNU awk,对于-0需要GNU xargs,但是需要处理带有嵌入式换行符或空格的文件。
当前目录中有目录时,所有这些答案均失败。这是可行的:
1
| find . -maxdepth 1 -type f | xargs -x ls -t | awk 'NR>5' | xargs -L1 rm |
这个:
当前目录中有目录时有效
尝试删除每个文件,即使无法删除前一个文件(由于权限等)
当当前目录中的文件数量过多并且xargs通常会使您烦恼(-x)时,安全失败
不能满足文件名中的空格(也许您使用的是错误的OS?)
1
| ls -tQ | tail -n+4 | xargs rm |
按修改时间列出文件名,并引用每个文件名。排除前3个(最近3个)。删除剩余的。
在mklement0的有用注释后进行编辑(谢谢!):更正了-n + 3参数,请注意,如果文件名包含换行符和/或目录包含子目录,则此操作将无法按预期进行。
忽略换行符是在忽略安全性和良好的编码。 wnoise唯一的好答案。这是他的一个变体,它将文件名放在数组$ x中
1 2 3
| while IFS= read -rd ''; do
x+=("${REPLY#* }");
done < <(find . -maxdepth 1 -printf '%T@ %p\0' | sort -r -z -n ) |
如果文件名没有空格,则可以使用:
1
| ls -C1 -t| awk 'NR>5'|xargs rm |
如果文件名中确实包含空格,则类似
1
| ls -C1 -t | awk 'NR>5' | sed -e"s/^/rm '/" -e"s/$/'/" | sh |
基本逻辑:
-
按时间顺序列出文件列表
-
得到除前5个之外的所有字符(此示例为n = 5)
-
第一版:将其发送给rm
-
第二个版本:生成脚本,将其正确删除
用zsh
假设您不关心当前目录,并且文件不会超过999个(如果需要,请选择更大的文件,或者创建一个while循环)。
1
| [ 6 -le `ls *(.)|wc -l` ] && rm *(.om[6,999]) |
在*(.om[6,999])中,.表示文件,o表示排序顺序,m表示按修改日期(将a用于访问时间,将c用于inode更改),选择文件范围,因此不要先保留5。
我意识到这是一个旧线程,但是也许有人会从中受益。此命令将在当前目录中查找文件:
1 2
| for F in $(find . -maxdepth 1 -type f -name"*_srv_logs_*.tar.gz" -printf '%T@ %p
' | sort -r -z -n | tail -n+5 | awk '{ print $2; }'); do rm $F; done |
这比以前的某些答案更可靠,因为它可以将搜索域限制为匹配表达式的文件。首先,找到符合您所需条件的文件。打印带有时间戳的文件。
1 2
| find . -maxdepth 1 -type f -name"*_srv_logs_*.tar.gz" -printf '%T@ %p
' |
接下来,按时间戳对其进行排序:
然后,从列表中删除4个最新文件:
抓住第二列(文件名,而不是时间戳):
然后将整个内容包装为for语句:
1
| for F in $(); do rm $F; done |
这可能是一个更冗长的命令,但是我能以条件文件为目标并针对它们执行更复杂的命令要好得多。
我需要一个用于busybox(路由器)的优雅解决方案,所有xargs或阵列解决方案对我来说都是无用的-那里没有这样的命令。 find和mtime不是正确的答案,因为我们谈论的是10个项目,不一定是10天。埃斯波的答案是最短,最简洁,也可能是最普遍的答案。
空格错误以及不删除任何文件时都可以通过标准方式解决:
1
| rm"$(ls -td *.tar | awk 'NR>7')" 2>&- |
更具教育意义的版本:如果我们以不同的方式使用awk,则可以完成所有操作。通常,我使用这种方法将变量从awk传递(返回)到sh。当我们阅读所有无法完成的时间时,我要有所不同:这是方法。
.tar文件示例,文件名中的空格没有问题。要测试,请将" rm"替换为" ls"。
1
| eval $(ls -td *.tar | awk 'NR>7 { print"rm "" $0"""}') |
说明:
ls -td *.tar列出按时间排序的所有.tar文件。要应用当前文件夹中的所有文件,请删除" d * .tar"部分
awk 'NR>7...跳过前7行
print"rm \"" $0"\""构造一行:rm"文件名"
eval执行它
由于我们使用的是rm,因此我不会在脚本中使用以上命令! Wiser用法是:
1
| (cd /FolderToDeleteWithin && eval $(ls -td *.tar | awk 'NR>7 { print"rm "" $0"""}')) |
在使用ls -t命令的情况下,不会对诸如touch 'foo" bar'和touch 'hello * world'这样的愚蠢示例造成任何损害。并不是说我们曾经在现实生活中使用此类名称创建文件!
边注。如果我们想以这种方式将变量传递给sh,我们只需修改打印(简单形式,不能容忍空格):
将变量VarName设置为$1的值。可以一次创建多个变量。此VarName成为普通的sh变量,之后可以在脚本或shell中正常使用。因此,要使用awk创建变量并将其返回给shell,请执行以下操作:
1
| eval $(ls -td *.tar | awk 'NR>7 { print"VarName=""$1""" }'); echo"$VarName" |
在Sed-Onliners中发现了有趣的cmd-删除最后3行-发现它是另一种为猫皮的方法的完美选择(可以),但可以这样:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16
| #!/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个最新(最新)文件外的所有文件
1
| ls -t1 | head -n $(echo $(ls -1 | wc -l) - 10 | bc) | xargs rm |
如果少于10个文件,则不会删除任何文件,您将拥有:
错误头:非法行数-0
用bash计数文件
我将其制作为bash shell脚本。用法:keep NUM DIR,其中NUM是要保留的文件数,DIR是要清理的目录。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22
| #!/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 |
1 2 3 4 5 6 7 8
| 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 |