介绍

介绍

我有以下路径:

/dir1/dir2/

在此路径中,我有以下目录,其中包含各种(不相关)应用程序 detrius:

follower1234  1-Dec-2018
follower3456  2-Dec-2018
follower4567  3-Dec-2018
follower7890  9-Jan-2019
follower8901 10-Jan-2019
leader8765    4-Dec-2018
bystander6789 5-Dec-2018

假设今天是 2019 年 1 月 10 日。

假设可以有任意数量的followerXXXX,leaderXXXXbystanderXXXX目录。

我想要的是删除followerXXXX除最新followerXXX目录之外的所有目录,这些目录早于两周。

现在我可以删除所有目录早于特定日期。但这不是我的问题。我正在添加两个附加参数。

在这种情况下我想删除:

follower1234  1-Dec-2018
follower3456  2-Dec-2018
follower4567  3-Dec-2018

但不是

follower7890  9-Jan-2019
follower8901 10-Jan-2019
leader8765    4-Dec-2018
bystander6789 5-Dec-2018

即我想删除文件

(a) 匹配模式

(b) 超过两周

(c) 不是与模式匹配的最新目录(即保留最后一个)

我的问题是:如何删除目录中超过 2 周的所有目录(除了与文件模式匹配的最新目录之外)?

答案1

介绍

问题已修改。

  • 我的第一个替代方案(oneliner)与新规范不匹配,但保存了足够旧的目录(超过 14 天)中的最新目录。

  • 我做了第二个替代方案(shellscript),它使用

    @ 自 1970 年 1 月 1 日 00:00 GMT 以来的秒数,带小数部分。

    seclim并减去与 14 天相对应的秒数,以获得排序目录列表​​中“秒数限制”的时间戳。

1.单线

以前的答案干净整洁,但它们不保留最新的follower目录。以下命令行将执行此操作(并且可以管理带空格的名称,但带换行符的名称会产生问题),

find . -type d -name "follower*" -printf "%T+ %p\n"|sort|head -n -1 | cut -d ' ' -f 2- | sed -e 's/^/"/' -e 's/$/"/' | xargs echo rm -r

在此目录结构上进行测试,

$ find -printf "%T+ %p\n"|sort
2019-01-10+13:11:40.6279621810 ./follower1
2019-01-10+13:11:40.6279621810 ./follower1/2/3
2019-01-10+13:11:40.6279621810 ./follower1/2/dirnam with spaces
2019-01-10+13:11:40.6279621810 ./follower1/2/name with spaces
2019-01-10+13:11:56.5968732640 ./follower1/2/file
2019-01-10+13:13:18.3975675510 ./follower2
2019-01-10+13:13:19.4016254340 ./follower3
2019-01-10+13:13:20.4056833250 ./follower4
2019-01-10+13:13:21.4097412230 ./follower5
2019-01-10+13:13:22.4137991260 ./follower6
2019-01-10+13:13:23.4138568040 ./follower7
2019-01-10+13:13:24.4219149500 ./follower8
2019-01-10+13:13:25.4259728780 ./follower9
2019-01-10+13:15:34.4094596830 ./leader1
2019-01-10+13:15:36.8336011960 .
2019-01-10+13:15:36.8336011960 ./leader2
2019-01-10+13:25:03.0751878450 ./follower1/2

像这样,

$ find . -type d -name "follower*" -printf "%T+ %p\n"|sort|head -n -1 | cut -d ' ' -f 2- | sed -e 's/^/"/' -e 's/$/"/' | xargs echo rm -r
rm -r ./follower1 ./follower2 ./follower3 ./follower4 ./follower5 ./follower6 ./follower7 ./follower8

Sofollower9被排除,因为它是最新的follower目录(名称不以follower(开头leader1且不在游戏中的目录)leader22

现在我们添加时间标准,-mtime +14并进行另一次“试运行”以检查它是否正常工作,当我们将目录更改为存在真实follower目录的位置时,

find . -type d -name "follower*" -mtime +14 -printf "%T+ %p\n"|sort|head -n -1 | cut -d ' ' -f 2- | sed -e 's/^/"/' -e 's/$/"/' | xargs echo rm -r

最后我们删除echo并拥有一个可以执行我们想要的操作的命令行,

find . -type d -name "follower*" -mtime +14 -printf "%T+ %p\n"|sort|head -n -1 | cut -d ' ' -f 2- | sed -e 's/^/"/' -e 's/$/"/' | xargs rm -r

  • find当前目录中名称以 开头且follower自 14 天前以来未修改过的目录。
  • 打印和分类后head -n -1将排除最新的follower目录
  • 去掉时间戳,并在每个目录名的头尾添加双引号。
  • 最后,结果通过管道xargs作为参数传递,rm -r以便删除我们想要删除的目录。

2. Shell脚本

我做了第二个替代方案(shellscript),它使用

@      seconds since Jan. 1, 1970, 00:00 GMT, with fractional part.

它还有两个选择,

  • -n试运行
  • -v冗长的

  • 我根据OP的要求修改了shellscript:输入模式作为参数在单引号内,例如“follower*”。

  • 我建议使用 shellscript 的名称是prune-dirs因为它现在更通用(不再只是prune-followers为了修剪目录follower*)。

建议您第一次使用这两个选项运行 shellscript,以便“查看”您将执行的操作,当看起来正确时,删除-n以使 shellscript 删除足够旧的目录以进行删除。因此,让我们调用它prune-dirs并使其可执行。

#!/bin/bash

# date        sign     comment
# 2019-01-11  sudodus  version 1.1
# 2019-01-11  sudodus  enter the pattern as a parameter
# 2019-01-11  sudodus  add usage
# 2019-01-14  sudodus  version 1.2
# 2019-01-14  sudodus  check if any parameter to the command to be performed

# Usage

usage () {
 echo "Remove directories found via the pattern (older than 'datint')

 Usage:    $0 [options] <pattern>
Examples: $0 'follower*'
          $0 -v -n 'follower*'  # 'verbose' and 'dry run'
The 'single quotes' around the pattern are important to avoid that the shell expands
the wild card (for example the star, '*') before it reaches the shellscript"
 exit
}

# Manage options and parameters

verbose=false
dryrun=false
for i in in "$@"
do
 if [ "$1" == "-v" ]
 then
  verbose=true
  shift
 elif [ "$1" == "-n" ]
 then
  dryrun=true
  shift
 fi
done
if [ $# -eq 1 ]
then
 pattern="$1"
else
 usage
fi

# Command to be performed on the selected directories

cmd () {
 echo rm -r "$@"
}

# Pattern to search for and limit between directories to remove and keep

#pattern='follower*'
datint=14  # days

tmpdir=$(mktemp -d)
tmpfil1="$tmpdir"/fil1
tmpfil2="$tmpdir"/fil2

secint=$((60*60*24*datint))
seclim=$(date '+%s')
seclim=$((seclim - secint))
printf "%s limit-in-seconds\n" $seclim > "$tmpfil1"

if $verbose
then
 echo "----------------- excluding newest match:"
 find . -type d -name "$pattern" -printf "%T@ %p\n" | sort |tail -n1 | cut -d ' ' -f 2- | sed -e 's/^/"/' -e 's/$/"/'
fi

# exclude the newest match with 'head -n -1'

find . -type d -name "$pattern" -printf "%T@ %p\n" | sort |head -n -1 >> "$tmpfil1"

# put 'limit-in-seconds' in the correct place in the sorted list and remove the timestamps

sort "$tmpfil1" | cut -d ' ' -f 2- | sed -e 's/^/"/' -e 's/$/"/' > "$tmpfil2"

if $verbose
then
 echo "----------------- listing matches with 'limit-in-seconds' in the sorted list:"
 cat "$tmpfil2"
 echo "-----------------"
fi

# create 'remove task' for the directories older than 'limit-in-seconds'

params=
while read filnam
do
 if [ "${filnam/limit-in-seconds}" != "$filnam" ]
 then
  break
 else
  params="$params $filnam"
 fi
done < "$tmpfil2"
cmd $params > "$tmpfil1"
cat  "$tmpfil1"

if ! $dryrun && ! test -z "$params"
then
 bash "$tmpfil1"
fi
rm -r $tmpdir
  • follower将当前目录更改为包含子目录的目录
  • 创建文件prune-dirs
  • 使其可执行
  • 并使用两个选项运行-v -n

    cd directory-with-subdirectories-to-be-pruned/
    nano prune-dirs  # copy and paste into the editor and save the file
    chmod +x prune-dirs
    ./prune-dirs -v -n
    

测试

prune-dirs在具有以下子目录的目录中进行了测试,如下所示find

$ find . -type d -printf "%T+ %p\n"|sort
2018-12-01+02:03:04.0000000000 ./follower1234
2018-12-02+03:04:05.0000000000 ./follower3456
2018-12-03+04:05:06.0000000000 ./follower4567
2018-12-04+05:06:07.0000000000 ./leader8765
2018-12-05+06:07:08.0000000000 ./bystander6789
2018-12-06+07:08:09.0000000000 ./follower with spaces old
2019-01-09+10:11:12.0000000000 ./follower7890
2019-01-10+11:12:13.0000000000 ./follower8901
2019-01-10+13:15:34.4094596830 ./leader1
2019-01-10+13:15:36.8336011960 ./leader2
2019-01-10+14:08:36.2606738580 ./2
2019-01-10+14:08:36.2606738580 ./2/follower with spaces
2019-01-10+17:33:01.7615641290 ./follower with spaces new
2019-01-10+19:47:19.6519169270 .

用法

$ ./prune-dirs
Remove directories found via the pattern (older than 'datint')

 Usage:    ./prune-dirs [options] <pattern>
Examples: ./prune-dirs 'follower*'
          ./prune-dirs -v -n 'follower*'  # 'verbose' and 'dry run'
The 'single quotes' around the pattern are important to avoid that the shell expands
the wild card (for example the star, '*') before it reaches the shellscript

运行-v -n(详细的试运行)

$ ./prune-dirs -v -n 'follower*'
----------------- excluding newest match:
"./follower with spaces new"
----------------- listing matches with 'limit-in-seconds' in the sorted list:
"./follower1234"
"./follower3456"
"./follower4567"
"./follower with spaces old"
"limit-in-seconds"
"./follower7890"
"./follower8901"
"./2/follower with spaces"
-----------------
rm -r "./follower1234" "./follower3456" "./follower4567" "./follower with spaces old"

具有更通用模式的详细演练

$ LANG=C ./prune-dirs -v -n '*er*'
----------------- excluding newest match:
"./follower with spaces new"
----------------- listing matches with 'limit-in-seconds' in the sorted list:
"./follower1234"
"./follower3456"
"./follower4567"
"./leader8765"
"./bystander6789"
"./follower with spaces old"
"limit-in-seconds"
"./follower7890"
"./follower8901"
"./leader1"
"./leader2"
"./2/follower with spaces"
-----------------
rm -r "./follower1234" "./follower3456" "./follower4567" "./leader8765" "./bystander6789" "./follower with spaces old"

不带任何选项运行(删除目录的真实案例)

$ ./prune-dirs 'follower*'
rm -r "./follower1234" "./follower3456" "./follower4567" "./follower with spaces old"

运行-v“再试一次”

$ LANG=C ./prune-dirs -v 'follower*'
----------------- excluding newest match:
"./follower with spaces new"
----------------- listing matches with 'limit-in-seconds' in the sorted list:
"limit-in-seconds"
"./follower7890"
"./follower8901"
"./2/follower with spaces"
-----------------
rm -r

shellscript 没有列出“上方”“秒数限制”目录,并且没有为rm -r命令行列出文件,因此工作已经完成(这是正确的结果)。但是,如果几天后再次运行 shellscript,可能会在“秒数限制”的“上方”找到一些新目录并被删除。

答案2

补充罗文的答案。您可以通过目录路径更改点

find . -type d -name follower* -mtime +14 -exec rm -rf {} +;

答案3

zsh

(){ n=$#; } follower<->(/)       # count the number of follower<n> dirs

to_remove=(follower<->(/m+13om)) # assumes the dir list is not changed
                                 # since the previous command

(($#to_remove < n)) || to_remove[1]=() # keep the youngest if they're
                                       # all over 2 weeks old



echo rm -rf $to_remove

echo(高兴时删除)

  • <->任何十进制数字序列(<1-20>无界的缩写形式)。
  • (){code} args:匿名函数,此处将其参数数量存储在 中$n
  • (/omm+13): 全局限定符
  • /:仅选择文件类型目录(相当于find-type d
  • m+13:全天年龄严格大于 13 天的文件,即 14 天或更早的文件(相当于finds -mtime +13)。
  • om:按修改时间排序(ls -t较新的文件优先)

请注意,依赖目录修改时间是危险的。当在目录中添加、删除或重命名文件(或编辑目录touch)时,目录会被修改。由于这些目录已编号,您可能希望改为依赖该编号,因此替换omnOn( numerically Order inverse (capital O) by name)。

要将模式包含在变量中,请替换follower<->$~pattern和 setpattern='follower<->'或任何其他值。

答案4

几个解决方案:

1.基于GNU find

#!/bin/bash

# The pattern has to be available to subshells
export patt="$1"

find . -maxdepth 1 -type d -name "${patt}*" -mtime +14 \
  -exec sh -c '[ "$(find . -maxdepth 1 -type d -name "${patt}*" -print0 |
    sort -z -V |
    tail -z -n 1 |
    tr -d "\0")" != "$1" ]' sh {} \; \
  -exec sh -c 'echo rm -r "$1"' sh {} \;

该脚本的调用方式如下:

./script name_pattern

按原样,它会给你一个试运行。echo在最后一个操作中删除-exec以使其真正删除目录。

它会:

  • 查找当前目录中超过 14 天之前修改过的所有目录(但请参阅下面的注释),并且名称以;-mtime值开头${patt}对于每个:
  • 确保(第一个-exec)找到的目录不是最后一个与名称模式匹配的目录,按升序排序版本order ( -V) (例如,follower100放在 后面follower2);如果测试( [)失败,find则跳至下一个循环,不执行后续操作;
  • 删除找到的目录(第二个-exec)。

在这里,我假设按名称按字典顺序对目录进行排序与​​按修改日期对目录进行排序之间是等效的。如果您的最新的目录是根据其名称定义的。
相反,如果您的最新的目录是具有最近修改时间的目录,我们必须将-exec ...上面代码中的第一个目录替换为以下目录:

  -exec sh -c '[ "$(find . -maxdepth 1 -type d -name "${patt}*" -printf "%T@\n" |
    sed "s/\..*$//" |
    sort -n |
    tail -n 1)" != "$(stat -c "%Y" "$1")" ]' sh {} \; \

在内部,find我们找到与名称模式匹配的所有目录,打印自纪元以来的修改时间列表(以秒为单位),去掉小数部分,排序,取出最后一个并检查它是否不等于当前的修改时间外在的结果find

请注意,使用此过滤器时,如果所有匹配的目录都超过 14 天并且具有完全相同的修改时间,则不会删除任何目录。


笔记:

-maxdepth 1并不严格要求将搜索限制为当前目录 ( ) 的内容。

您可能想告诉sort如何排序,例如export LC_ALL=C在脚本的开头添加(请参阅这个回答“‘LC_ALL=C’有什么作用?”关于排序时可能遇到的问题,具体取决于您的本地化设置)。

请注意,使用-mtime +14, 会跳过 14 到 15 天前修改的文件,即使它们的修改时间在技术上早于 14*24 小时现在man find详情请参阅;具体请参阅 的描述-atime n)。

即使名称包含空格、换行符、不常见和不可打印的字符,它也能工作。

兼容性:另一方面是它不可移植:这里使用的一些功能,特别是、 and 、find命令、选项和选项(我可能忘记了更多),在 POSIX 中没有指定。-maxdepth-print0-printfstat-Vsort-zsorttail

2.基于shell特性

#!/bin/sh

patt="$1"                 # The name pattern
test -z "$patt" && exit   # Caution: pattern must not be empty

days=14     # How old has to be a directory to get deleted, in days?
last=       # The youngest directory

dirs=( "$patt"* )     # Array of files matched by name (note, here we
                      # have everything that matches, not just dirs)
now="$(date +'%s')"   # Now in seconds since Epoch

threshold="$(( "$now" - ( "$days" * 24 * 60 *60 ) ))"
                      # Dirs older than this date (in seconds since
                      # Epoch) are candidates for deletion

# We find the youngest directory in the array
#
for i in "${!dirs[@]}"; do
  if  [ -z "$last" ] ||
    ( [ -d "${dirs[$i]}" ] &&
      [ "$(stat -c '%Y' -- "${dirs[$i]}")" -gt "$(stat -c '%Y' -- "$last")" ] ); then
    last="${dirs[$i]}"
  fi
done

# We delete all the directories in the array that are
# not the youngest one AND are older that the thrashold
#
for i in "${!dirs[@]}"; do
  if  [ -d "${dirs[$i]}" ] &&
      [ "${dirs[$i]}" != "$last" ] &&
      [ "$(stat -c '%Y' -- "${dirs[$i]}")" -lt "$threshold" ]; then
    echo rm -rf -- "${dirs[$i]}"
  fi
done

该脚本也应该被调用为

./script name_pattern

同样,它会给你一个空运行,直到你echo从 中删除echo rm -rf -- "${dirs[$i]}"

它会:

  • 使用当前目录中与名称模式匹配的所有文件的名称填充数组;
  • 确定数组中最新的目录;
  • 删除数组中 1) 超过 14 天且 2) 不是最新目录的所有目录。

笔记:

它将针对 14 天之前的目录现在(不同于find)。因此,这两个解决方案并不严格等效。
此外,如果所有匹配的目录都早于阈值并且具有相同的修改时间,它将删除除其中一个之外的所有目录 - 随机选择。

带有不常见字符的名称是可以的,包括换行符和不可打印的字符。

兼容性:即使这个解决方案也依赖于一些非 POSIX 功能:即stat格式%s date。一只手数组, 显然...

相关内容