Answers:
现有答案存在的问题:
rm
直接在无引号的命令替换(rm `...`
)上调用,则存在意外滚动的风险。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
(包括Mac系统),你可以使用-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
ls
不在当前目录中,则文件的路径将包含“ /”,这意味着grep -v '/'
不匹配任何内容。我相信grep -v '/$'
您只想排除目录。
grep
命令在概念上更加清晰。但是请注意,您所描述的问题不会只用一个目录路径就可以解决。例如,ls -p /private/var
仍然只打印纯文件名。只有传递了多个文件参数(通常通过glob),您才会在输出中看到实际的路径。例如,ls -p /private/var/*
(除非您也包含,否则您还将看到匹配子目录的内容-d
)。
删除目录中除5个(或任意数量)的最新文件外的所有文件。
rm `ls -t | awk 'NR>5'`
ls -t
为ls -td *.bz2
ls -t | awk 'NR>1'
(我只想要最新的)。谢谢!
ls -t | awk 'NR>5' | xargs rm -f
如果您更喜欢管道,并且如果没有要删除的内容,则需要抑制该错误。
touch 'hello * world'
,这将绝对删除当前目录中的所有内容。
(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
(ls -t|head -n 5;ls)
是一个命令组。它将5个最新文件打印两次。sort
将相同的线放在一起。uniq -u
删除重复项,以便保留除5个最新文件以外的所有文件。xargs rm
呼吁rm
他们每个人。
--no-run-if-empty
到xargs
在(ls -t|head -n 5;ls)|sort|uniq -u|xargs --no-run-if-empty rm
请更新答案。
touch 'foo " bar'
将抛出命令的其余部分。
xargs -d $'\n'
比将引号插入您的内容更安全,尽管NUL分隔输入流(这需要使用其他方法ls
才能真正做到正确)是理想的选择。
thelsdj答案的更简单变体:
ls -tr | head -n -5 | xargs --no-run-if-empty rm
ls -tr显示所有文件,最旧的优先(-t最新的优先,-r反向)。
head -n -5显示除最后5行(即最新的5个文件)以外的所有内容。
xargs rm为每个选定文件调用rm。
-1
是输出到管道时的默认值,因此此处不是必需的。这具有更大的问题,与xargs
解析带有空格,引号和&c的名称时的默认行为有关。
--no-run-if-empty
在我的shell中无法识别。我在Windows上使用Cmder。
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,但是需要处理带有嵌入式换行符或空格的文件。
awk
逻辑的复杂性(或就此而言的必要性)感到惊讶。我是否在OP的问题中遗漏了一些必要的要求?
while read -r -d ' '; IFS= -r -d ''; do ...
循环-第一个读取终止于该空间,而第二个读取继续进行到NUL。
sed -z -e 's/[^ ]* //; 1,5d'
是最清晰的。(或sed -n -z -e 's/[^ ]* //; 6,$p'
。)
当前目录中有目录时,所有这些答案均失败。这是可行的:
find . -maxdepth 1 -type f | xargs -x ls -t | awk 'NR>5' | xargs -L1 rm
这个:
当前目录中有目录时有效
尝试删除每个文件,即使无法删除前一个文件(由于权限等)
当当前目录中的文件数量过多时,安全失败,xargs
通常会使您烦恼(-x
)
不能满足文件名中的空格(也许您使用的是错误的OS?)
find
返回的文件名超过单个命令行中可以传递的文件名,该ls -t
怎么办?(提示:您会获得的多次运行ls -t
,每个运行仅是单独排序,而不是具有全局正确的排序顺序;因此,当使用足够大的目录运行时,此答案将被严重破坏)。
ls -tQ | tail -n+4 | xargs rm
按修改时间列出文件名,并引用每个文件名。排除前3个(最近3个)。删除剩余的。
在mklement0的有用注释后进行编辑(谢谢!):更正了-n + 3参数,请注意,如果文件名包含换行符和/或目录包含子目录,则此操作将无法正常工作。
-Q
我的机器上似乎不存在该选项。
-Q
。是的,-Q
是GNU扩展(这是POSIX ls
规范)。一个小警告(实际上很少有问题):-Q
将文件名中嵌入的换行符编码为文字\n
,rm
无法识别。为了排除第3,该xargs
参数必须+4
。最后,警告也适用于大多数其他答案:如果当前目录中没有子目录,则命令将仅按预期运行。
--no-run-if-empty
选项调用xargs :ls -tQ | tail -n+4 | xargs --no-run-if-empty rm
如果文件名没有空格,则可以使用:
ls -C1 -t| awk 'NR>5'|xargs rm
如果文件名中确实有空格,则类似
ls -C1 -t | awk 'NR>5' | sed -e "s/^/rm '/" -e "s/$/'/" | sh
基本逻辑:
while read
处理空间的窍门: ls -C1 -t | awk 'NR>5' | while read d ; do rm -rvf "$d" ; done
while IFS= read -r d
会更好一些- -r
防止反斜杠文字被占用read
,并IFS=
防止尾部空白自动修剪。
touch $'hello \'$(rm -rf ~)\' world'
:文件名中的文字引号将与您添加的文字引号相抵消sed
,从而导致文件名中的代码被执行。
| sh
具有外壳注入漏洞的表单)。
用zsh
假设您不关心当前目录,并且文件总数不超过999个(如果需要,请选择更大的文件,或者创建一个while循环)。
[ 6 -le `ls *(.)|wc -l` ] && rm *(.om[6,999])
在中*(.om[6,999])
,.
均值文件,o
均值排序顺序,m
均值按修改日期(a
用于访问时间或c
用于inode更改),[6,999]
选择文件范围,因此不先管理5。
om
)正常工作(我尝试过的任何排序均未显示效果-对OSX 10.11.2均无影响(使用zsh 5.0.8和5.1.1进行了尝试) ,也不在Ubuntu 14.04(zsh 5.0.2)上)-我缺少什么?至于范围端点:无需对其进行硬编码,只需使用-1
以引用最后一个条目,从而包括所有剩余文件:[6,-1]
。
我意识到这是一个旧线程,但是也许有人会从中受益。此命令将在当前目录中查找文件:
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
这可能是一个更冗长的命令,但是我能以条件文件为目标并针对它们执行更复杂的命令要好得多。
在Sed-Onliners中发现了有趣的cmd-删除最后三行-查找它是另一种为猫皮化的方法的完美选择(可以),但是请注意:
#!/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
我需要一个用于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“文件名”
eval
执行它
由于我们正在使用rm
,因此我不会在脚本中使用以上命令!Wiser用法是:
(cd /FolderToDeleteWithin && eval $(ls -td *.tar | awk 'NR>7 { print "rm \"" $0 "\""}'))
在使用ls -t
command 的情况下,不会对以下愚蠢的示例造成任何伤害: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"
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
xargs
没有-0
或至少-d $'\n'
是不可靠的;观察文件名中带有空格或引号字符的文件的行为。
我将其制作为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