我的问题与一些旧问题有点不同,只是要求“删除n
目录中除最新文件之外的所有文件”。
我有一个目录,其中包含不同的文件“组”,其中每组文件共享一些任意前缀,并且每组至少有一个文件。我事先不知道这些前缀,也不知道有多少组。
编辑:实际上,我对文件名有所了解,那就是它们都遵循模式prefix-some_digits-some_digits.tar.bz2
.这里唯一重要的是prefix
部分,我们可以假设每个部分中prefix
没有数字或破折号。
我想在bash
脚本中执行以下操作:
n
遍历给定的目录,识别所有现有的“组”,并且对于每组文件,仅删除该组中除最新文件之外的所有文件。如果某个组的文件少于
n
该组的文件,则对该组不执行任何操作,即不删除该组的任何文件。
在 中执行上述操作的稳健且安全的方法是什么bash
?您能逐步解释一下这些命令吗?
答案1
剧本:
#!/bin/bash
# Get Prefixes
PREFIXES=$(ls | grep -Po '^(.*)(?!HT\d{4})-(.*)-(.*).tar.bz2$' | awk -F'-' '{print $1}' | uniq)
if [ -z "$1" ]; then
echo need a number of keep files.
exit 1
else
NUMKEEP=$1
fi
for PREFIX in ${PREFIXES}; do
ALL_FILES=$(ls -t ${PREFIX}*)
if [ $(echo ${ALL_FILES} | wc -w) -lt $NUMKEEP ]; then
echo Not enough files to be kept. Quit.
continue
fi
KEEP=$(ls -t ${PREFIX}* | head -n${NUMKEEP})
for file in $ALL_FILES ; do
if [[ "$KEEP" =~ "$file" ]]; then
echo keeping $file
else
echo RM $file
fi
done
done
解释:
- 计算前缀:
- 查找
something-something-something.tar.bz2
正则表达式后面的所有文件,仅剪切第一部分到第一个破折号,并使其唯一。 - 结果是标准化列表
PREFIXES
- 查找
- 遍历所有
PREFIXES
: - 计算
ALL_FILES
与PREFIX
- 检查数量是否
ALL_FILES
小于要保留的文件数 -> 如果为真,我们可以到此为止,无需删除任何内容 - 计算
KEEP
最近的NUMKEEP
文件 - 遍历
ALL_FILES
并检查给定文件是否不在KEEP
文件列表中。如果是这样:将其删除。
运行时的示例结果:
$ ./remove-old.sh 2
keeping bar-01-01.tar.bz2
keeping bar-01-02.tar.bz2
RM bar-01-03.tar.bz2
RM bar-01-04.tar.bz2
RM bar-01-05.tar.bz2
RM bar-01-06.tar.bz2
keeping foo-01-06.tar.bz2
keeping foo-01-05.tar.bz2
RM foo-01-04.tar.bz2
RM foo-01-03.tar.bz2
RM foo-01-02.tar.bz2
$ ./remove-old.sh 8
Not enough files to be kept. Quit.
Not enough files to be kept. Quit.
答案2
根据要求,这个答案倾向于“稳健且安全”,而不是快速和肮脏。
可移植性:此答案适用于包含sh
、find
、sed
、sort
、ls
、grep
、xargs
和 的任何系统rm
。
脚本永远不应该因大目录而阻塞。不执行 shell 文件名扩展(如果文件太多,这可能会阻塞,但这是一个巨大的数字)。
此答案假设前缀不包含任何破折号 ( -
)。
请注意,根据设计,该脚本仅列出将被删除的文件。您可以通过管道传输脚本中注释掉的while
循环的输出来删除文件xargs -d '/n' rm
。这样您就可以在启用删除代码之前轻松测试脚本。
#!/bin/sh -e
NUM_TO_KEEP=$(( 0 + ${1:-64000} )) || exit 1
find . -maxdepth 1 -regex '[^-][^-]*-[0-9][0-9]*-[0-9][0-9]*.tar.bz2' |
sed 's/-.*//; s,^\./,,' |
sort -u |
while read prefix
do
ls -t | grep "^$prefix-.*-.*\.tar\.bz2$" | sed "1,$NUM_TO_KEEP d"
done # | xargs -d '\n' rm --
N参数(要保留的文件数)默认为64000(即保留所有文件)。
带注释的代码
获取命令行参数并通过加法检查整数,如果没有给出参数默认为 64000(实际上是全部):
NUM_TO_KEEP=$(( 0 + ${1:-64000} )) || exit 1
查找当前目录中与文件名格式匹配的所有文件:
find . -maxdepth 1 -regex '[^-][^-]*-[0-9][0-9]*-[0-9][0-9]*.tar.bz2' |
获取前缀:删除前缀后面的所有内容并删除开头的“./”:
sed 's/-.*//; s,^\./,,' |
对前缀进行排序并删除重复项(-u
--唯一):
sort -u |
读取每个前缀和过程:
while read prefix
do
按时间排序列出目录中的所有文件,选择当前前缀的文件,并删除我们要保留的文件之外的所有行:
ls -t | grep "^$prefix-.*-.*\.tar\.bz2$" | sed "1,$NUM_TO_KEEP d"
为了测试注释掉删除文件的代码。使用 xargs 避免命令行长度或文件名中的空格(如果有)出现任何问题。如果您希望脚本生成日志,请添加-v
到rm
例如:rm -v --
。删除#
以启用删除代码:
done # | xargs -d '\n' rm --
如果这对您有用,请接受此答案并投票。谢谢。
答案3
我假设文件按词汇顺序列出时按前缀分组在一起。这意味着不存在带有作为另一个组的后缀的前缀的组,例如,foo-1-2-3.tar.bz2
不会出现在foo-1-1.tar.bz2
和之间foo-1-2.tar.bz2
。在这种假设下,我们可以列出所有文件,当我们检测到前缀更改(或第一个文件)时,我们就有了一个新组。
#!/bin/bash
n=$1; shift # number of files to keep in each group
shopt extglob
previous_prefix=-
for x in *-+([0-9])-+([0-9]).tar.bz2; do
# Step 1: skip the file if its prefix has already been processed
this_prefix=${x%-+([0-9])-+([0-9]).tar.bz2}
if [[ "$this_prefix" == "$previous_prefix" ]]; then
continue
fi
previous_prefix=$this_prefix
# Step 2: process all the files with the current prefix
keep_latest "$n" "$this_prefix"-+([0-9])-+([0-9]).tar.bz2
done
现在我们要讨论的是确定显式列表中最旧的文件。
假设文件名不包含换行符或ls
不按字面显示的字符,这可以通过以下方式实现ls
:
keep_latest () (
n=$1; shift
if [ "$#" -le "$n" ]; then return; fi
unset IFS; set -f
set -- $(ls -t)
shift "$n"
rm -- "$@"
)
答案4
我知道这是标记的bash
,但我认为这样会更容易zsh
:
#!/usr/bin/env zsh
N=$(($1 + 1)) # calculate Nth to last
typeset -U prefixes # declare array with unique elements
prefixes=(*.tar.bz2(:s,-,/,:h)) # save prefixes in the array
for p in $prefixes # for each prefix
do
arr=(${p}*.tar.bz2) # save filenames starting with prefix in arr
if [[ ${#arr} -gt $1 ]] # if number of elements is greather than $1
then
print -rl -- ${p}*.tar.bz2(Om[1,-$N]) # print all filenames but the most recent N
fi
done
该脚本接受一个参数:n(文件的数量)
(:s,-,/,:h)
是 glob 修饰符,:s
将第一个替换-
为/
并:h
提取头部(直到最后一个斜杠的部分,在本例中也是第一个斜杠,因为只有一个)
(Om[1,-$N])
是 glob 限定符,Om
对以最旧的文件,并[1,-$N]
从第一个到第 N 个到最后一个进行选择
如果您对结果满意,请替换print -rl
为rm
以实际删除文件,例如:
#!/usr/bin/env zsh
typeset -U prefixes
prefixes=(*.tar.bz2(:s,-,/,:h))
for p in $prefixes
arr=(${p}*.tar.bz2) && [[ ${#arr} -gt $1 ]] && rm -- ${p}*.tar.bz2(Om[1,-$(($1+1))])