Mat*_*ard 138 unix bash scripting
有一个简单的方法,在一个非常标准的UNIX环境中使用bash运行命令来删除目录中除最新的X文件之外的所有文件吗?
为了给出一个具体的例子,想象一下一些cron作业每小时写一个文件(比如一个日志文件或一个tar-up up备份)到一个目录.我想要一种方法来运行另一个cron作业,它将删除该目录中最旧的文件,直到少于5个.
而且要清楚,只有一个文件存在,它永远不应该被删除.
mkl*_*nt0 98
现有答案存在的问题:
rm直接在不带引号的命令substitution(rm `...`)上调用的解决方案,会增加意外通配的风险.rm目录将失败).wnoise的答案解决了这些问题,但解决方案是GNU特定的(并且非常复杂).
这是一个实用的,符合POSIX标准的解决方案,只有一个警告:它无法处理带有嵌入式换行符的文件名- 但我不认为这是大多数人的现实问题.
为了记录,这里解释为什么解析ls输出通常不是一个好主意:http://mywiki.wooledge.org/ParsingLs
ls -tp | grep -v '/$' | tail -n +6 | xargs -I {} rm -- {}
Run Code Online (Sandbox Code Playgroud)
上面的效率很低,因为xargs必须rm为每个文件名调用一次.
您的平台xargs可能允许您解决此问题:
如果你有GNU xargs,使用-d '\n',这使得xargs考虑每个输入线路分离的说法,但经过许多参数作为将适合在命令行上一次:
ls -tp | grep -v '/$' | tail -n +6 | xargs -d '\n' -r rm --
Run Code Online (Sandbox Code Playgroud)
-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 --
Run Code Online (Sandbox Code Playgroud)
说明:
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
Run Code Online (Sandbox Code Playgroud)
Esp*_*spo 95
删除目录中除最新文件的5个(或任何数量)之外的所有文件.
rm `ls -t | awk 'NR>5'`
Run Code Online (Sandbox Code Playgroud)
the*_*sdj 85
(ls -t|head -n 5;ls)|sort|uniq -u|xargs rm
Run Code Online (Sandbox Code Playgroud)
此版本支持带空格的名称:
(ls -t|head -n 5;ls)|sort|uniq -u|sed -e 's,.*,"&",g'|xargs rm
Run Code Online (Sandbox Code Playgroud)
Fab*_*ien 59
更简单的thelsdj答案:
ls -tr | head -n -5 | xargs --no-run-if-empty rm
Run Code Online (Sandbox Code Playgroud)
ls -tr显示所有文件,最早的文件(-t最新的第一个,-r反向).
head -n -5显示除最后5行之外的所有行(即5个最新文件).
xargs rm为每个选定的文件调用rm.
wno*_*ise 16
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
Run Code Online (Sandbox Code Playgroud)
需要GNU查找-printf,GNU排序为-z,GNU awk表示"\ 0",GNU xargs表示-0,但处理带有嵌入换行符或空格的文件.
小智 13
当前目录中有目录时,所有这些答案都会失败.这是有效的:
find . -maxdepth 1 -type f | xargs -x ls -t | awk 'NR>5' | xargs -L1 rm
Run Code Online (Sandbox Code Playgroud)
这个:
当前目录中有目录时有效
尝试删除每个文件,即使前一个文件无法删除(由于权限等)
失败时的文件在当前目录数量过多安全,xargs通常会去你的过(的-x)
不适合文件名中的空格(也许你使用的是错误的操作系统?)
Mar*_*ark 12
ls -tQ | tail -n+4 | xargs rm
Run Code Online (Sandbox Code Playgroud)
按修改时间列出文件名,引用每个文件名.排除前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 )
Run Code Online (Sandbox Code Playgroud)
对于 Linux(GNU 工具),一种有效且可靠的方法可以将n最新文件保留在当前目录中,同时删除其余文件:
n=5\n\nfind . -maxdepth 1 -type f -printf \'%T@ %p\\0\' |\nsort -z -nrt \' \' -k1,1 |\nsed -z -e "1,${n}d" -e \'s/[^ ]* //\' |\nxargs -0r rm -f --\nRun Code Online (Sandbox Code Playgroud)\n对于 BSD,find没有谓词-printf,stat不能输出 NULL 字节,并且sed+awk不能处理NULL- 分隔的记录。
这是一个不支持路径中换行符的解决方案,但可以通过过滤掉换行符来防止换行符:
\n#!/bin/bash\nn=5\n\nfind . -maxdepth 1 -type f ! -path $\'*\\n*\' -exec stat -f \'%.9Fm %N\' {} + |\nsort -nrt \' \' -k1,1 |\nawk -v n="$n" -F\'^[^ ]* \' \'NR > n {printf "%s%c", $2, 0}\' |\nxargs -0 rm -f --\nRun Code Online (Sandbox Code Playgroud)\n注意:我之所以使用它是bash因为$\'\\n\'符号。因为sh您可以定义一个包含文字换行符的变量并使用它。
POSIX解决方案(灵感来自 @mklement0答案)。
\n这个为 POSIX 添加了正确的转义xargs,但是当文件或目录的名称中包含换行符时,它仍然会中断;如果您想解决这个问题,那么除了清除或重命名这些文件之外别无选择。
n=5\n\nls -tp . |\ngrep -v \'/$\' |\nhead -n +"$((n+1))" |\nsed -e \'s/"/"\\\\""/g\' -e \'s/.*/"&"/\' |\nxargs rm --\nRun Code Online (Sandbox Code Playgroud)\n备注:实际上您可以将其替换grep | head | sed为awk -v n="$n" \'/[^/]$/ && --n < 0 {gsub(/"/, "\\"\\\\\\\\\\"\\""); print "\\"" $0 "\\""}\'
针对UNIX 和 Linux的解决方案(灵感来自 AIX/HP-UX/SunOS/BSD/Linux ls -b):
某些平台不提供find -printf、 、stat、 也不支持使用/ / / /NUL分隔的记录。这就是为什么 using可能是解决该问题的最便携的方法,因为它默认在几乎每个操作系统中都可用。statsortawksedxargsperl
我本可以把整个事情写下来,perl但我没有。我只用它来替换stat和编码-解码-转义文件名。核心逻辑与之前的方案相同,都是通过POSIX工具实现的。
注意: perl \ 的默认stat分辨率为秒,但从这里开始,您可以通过模块的功能perl-5.8.9获得亚秒分辨率(当操作系统和文件系统都支持它时)。这就是我在这里使用的;如果您不提供它,那么您可以从命令行中删除它。statTime::HiResperl\xe2\x80\x91MTime::HiRes=stat
n=5\n\nfind . \'(\' -name \'.\' -o -prune \')\' -type f -exec \\\nperl -MTime::HiRes=stat -le \'\n foreach (@ARGV) {\n @st = stat($_);\n if ( @st > 0 ) {\n s/([\\\\\\n])/sprintf( "\\\\%03o", ord($1) )/ge;\n print sprintf( "%.9f %s", $st[9], $_ );\n }\n else { print STDERR "stat: $_: $!"; }\n }\n\' {} + |\n\nsort -nrt \' \' -k1,1 |\n\nsed -e "1,${n}d" -e \'s/[^ ]* //\' |\n\nperl -l -ne \'\n s/\\\\([0-7]{3})/chr(oct($1))/ge;\n s/(["\\n])/"\\\\$1"/g;\n print "\\"$_\\""; \n\' |\n\nxargs -E \'\' sh -c \'[ "$#" -gt 0 ] && rm -f -- "$@"\' sh\nRun Code Online (Sandbox Code Playgroud)\n说明:
\n对于找到的每个文件,第一个perl获取修改时间并将其与编码的文件名一起输出(每个newline和backslash字符分别替换为文字\\012和\\134)。
现在每个流都time filename保证是单行的,因此 POSIXsort可以sed安全地使用该流。
第二个perl解码文件名并将其转义为 POSIX xargs。
最后,xargs呼吁rm删除这些文件。该sh命令是一个技巧,可以防止在没有要删除的文件时xargs运行。rm
我意识到这是一个旧线程,但也许有人会从中受益。该命令将在当前目录中查找文件:
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
Run Code Online (Sandbox Code Playgroud)
这比之前的一些答案更强大一点,因为它允许将搜索域限制为匹配表达式的文件。首先,找到符合您想要的任何条件的文件。打印这些文件,旁边带有时间戳。
find . -maxdepth 1 -type f -name "*_srv_logs_*.tar.gz" -printf '%T@ %p\n'
Run Code Online (Sandbox Code Playgroud)
接下来,按时间戳对它们进行排序:
sort -r -z -n
Run Code Online (Sandbox Code Playgroud)
然后,从列表中删除 4 个最新文件:
tail -n+5
Run Code Online (Sandbox Code Playgroud)
获取第二列(文件名,而不是时间戳):
awk '{ print $2; }'
Run Code Online (Sandbox Code Playgroud)
然后将整个事情包装成一个 for 语句:
for F in $(); do rm $F; done
Run Code Online (Sandbox Code Playgroud)
这可能是一个更详细的命令,但我的运气要好得多,能够定位条件文件并针对它们执行更复杂的命令。