删除目录中每组共享相同前缀的文件中除最近的 n 个文件之外的所有文件

删除目录中每组共享相同前缀的文件中除最近的 n 个文件之外的所有文件

我的问题与一些旧问题有点不同,只是要求“删除n目录中除最新文件之外的所有文件”。

我有一个目录,其中包含不同的文件“组”,其中每组文件共享一些任意前缀,并且每组至少有一个文件。我事先不知道这些前缀,也不知道有多少组。

编辑:实际上,我对文件名有所了解,那就是它们都遵循模式prefix-some_digits-some_digits.tar.bz2.这里唯一重要的是prefix部分,我们可以假设每个部分中prefix没有数字或破折号。

我想在bash脚本中执行以下操作:

  1. n遍历给定的目录,识别所有现有的“组”,并且对于每组文件,仅删除该组中除最新文件之外的所有文件。

  2. 如果某个组的文件少于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_FILESPREFIX
  • 检查数量是否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

根据要求,这个答案倾向于“稳健且安全”,而不是快速和肮脏。

可移植性:此答案适用于包含shfindsedsortlsgrepxargs和 的任何系统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 避免命令行长度或文件名中的空格(如果有)出现任何问题。如果您希望脚本生成日志,请添加-vrm例如: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 -rlrm以实际删除文件,例如:

#!/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))])

相关内容