Board logo

标题: [文件管理] 文件数量少于10则删除文件夹,若子文件夹被保留则保留所有父文件 [打印本页]

作者: namejm    时间: 2010-12-10 22:52     标题: 文件数量少于10则删除文件夹,若子文件夹被保留则保留所有父文件

  目前有这样一个需求:

  1、若某文件夹下的文件数目少于10时,删除该文件夹;
  2、若文件夹下有子文件夹,则优先检测子文件夹:若任意一层子文件夹内的文件数大于等于10,则保留该层子文件夹内的所有文件,并且该文件夹完整路径上所有父文件夹及其内部的所有文件都原封不动;除此之外,若某一层子文件夹内的文件数量少于10,则删除该子文件夹。

  **** 友情提示:最优代码见3楼 ****

  代码:
  1. @echo off
  2. set "destination="
  3. set "num=10"
  4. :: 以上两句可指定要处理的路径及文件数量
  5. setlocal enabledelayedexpansion
  6. cd.>remain.txt
  7. for /f "delims=" %%i in ('dir /ad /b /s "%destination%"^|sort /r 2^>nul') do (
  8.     set count=0
  9.     set flag=
  10.     for /f "delims=" %%j in ('dir /a-d /b "%%i" 2^>nul') do (
  11.         set /a count+=1
  12.     )
  13.     if !count! geq %num% (
  14.         set flag=yes
  15.     ) else (
  16.         findstr /ic:"%%i\\" remain.txt>nul&&set flag=yes
  17.     )
  18.     if not defined flag (
  19.         rd /q /s "%%i"
  20.     ) else (
  21.         echo "%%i\">>remain.txt
  22.     )
  23. )
  24. pause
复制代码
  思路:

  要用到的重要命令组合为:for+dir+sort+findstr,用到了临时文件。

  1、首先,用 dir /ad /b /s 列举指定目录下所有文件夹的完整路径,并用 sort /r 对路径逆序排列;罗列出来的所有路径放入for中处理。值得注意的是,逆序排列至关重要,是这个问题得以顺利解决的关键,所有后续操作都以逆序排列为基础展开;
  2、用 dir /a-d /b 检测列举出来的每一个文件夹路径,统计该路径下的文件数量;
  3、若某文件夹下的文件数量少于10,则用 rd /s 删除该文件夹;如果文件数量大于等于10,则保留该文件夹,并把该文件夹的完整路径写入临时文件备查;
  4、因为所有的操作都是从某一完整路径的最底层进行回溯,所以,上一步中的所有删除操作都是从最底层开始处理的,用 rd /s 不必担心会删掉子目录;后续的处理,都会把当前路径和临时文件中保留的路径进行对比,只要当前路径在保留路径中的某一层上,都会匹配到,从而被忽略掉。

  echo "%%i\">>remain.txt之所以要在路径后面加上反斜杠,是为了在 findstr /ic:"%%i\\" remain.txt 的时候,能正确匹配到完整路径,防止出现 findstr /ic:"c:\test"  的时候,匹配到 c:\test123\abc 这样的路径;

  当我测试这段代码的最初版本的时候,屡次无法正确执行,但我一直认为我的思路是正确的,只是代码尚有瑕疵罢了。经过反复的跟踪测试,才发现问题出现在findstr上,从而发现了findstr的一个bug:一般而言,用 findstr /c:"str" 语句的时候,我们认为字符串str内的所有特殊字符都已经被转换为普通字符了,但是在这里,当我们用 findstr /ic:"c:\test\" tmp.txt 这样的语句搜索tmp.txt中的内容时,无论tmp.txt中是否有 "c:\test\1"、"c:\test\abc" 这样的字符串,搜索都无法进行下去,表现为光标一直在cmd窗口中闪烁,无法执行下一条命令,解决的办法是在搜索字符串最后再加一条反斜杠,用"\"把"\"转义。由此观之,findstr中用 /c: 参数并不能保证作为搜索内容的字符串都被当做普通字符处理,当"\"位于搜索字符串的最后时,还需要用"\"进行转义。所以,必须使用 findstr /ic:"%%i\\" remain.txt 而不能用 findstr /ic:"%%i\" remain.txt,切记切记。(这个bug在willsort的这篇文章中有提及:[讨论]findstr的命令行分析机制:http://www.cn-dos.net/forum/viewthread.php?tid=21167

  以上代码的原型是下面这个代码:
  1. @echo off
  2. :: 删除当前目录下所有空文件夹
  3. for /f "delims=" %%i in ('dir /ad /b /s^|sort /r 2^>nul') do rd /q "%%i" 2>nul
  4. pause
复制代码
  这段代码首先逆序排列文件夹路径,保证了从最底层文件夹开始处理,充分利用了当文件夹下有文件时,单独的 rd 不能直接删除文件夹的特性,并充分考虑到了错误信息的屏蔽,没有一条多余的语句,干脆利落,堪称经典。(代码出处: 一个删除当前目录下及其子目录中的空文件夹的批处理:http://bbs.wuyou.com/viewthread.php?tid=88780

[ 本帖最后由 namejm 于 2010-12-11 23:15 编辑 ]
作者: broly    时间: 2010-12-11 13:54

此题目引用四眼兄弟的话说,介于难与易之间。
先来看看,有时间再做一做。
P.S:有一个叫场哥的猥琐家伙,貌似也跟我说过类似的题目
作者: namejm    时间: 2010-12-11 23:14

  Broly提供了一种更加高效更加环保的思路,但是使用了大量的变量名,从而使得处理海量路径时会因为变量数目过大而无法进行下去,经过与本人代码的结合,得出了以下代码:
  1. @echo off
  2. set "destination="
  3. set "num=10"
  4. setlocal enabledelayedexpansion
  5. for /f "delims=" %%i in ('dir /ad /b /s^|sort /r 2^>nul') do (
  6.     rd /q "%%i" 2>nul
  7.     set count=0
  8.     set flag=
  9.     for /f "delims=" %%j in ('dir /a-d /b "%%i" 2^>nul') do (
  10.         set /a count+=1
  11.     )
  12.     if !count! lss %num% (
  13.         set flag=
  14.         for /f "delims=" %%j in ('dir /ad /b "%%i" 2^>nul') do set flag=remain
  15.         if not defined flag rd /q /s "%%i" 2>nul
  16.     )
  17. )
  18. pause
复制代码

  与本人顶楼的代码相比,这段代码完全抛弃了临时文件,并且不再使用findstr,从而使得效率大为提升。

  本段代码的思路为:

  1、逆序排列所有文件夹的完整路径;为了减少后面对文件夹的检测次数,在本步中首先对所有的空文件夹进行了删除处理;
  2、逆序检测所有的文件夹路径,把所有包含文件个数少于10的文件夹罗列出来备用;
  3、继续检测这些备用的文件夹路径,如果某文件夹下第一层包含有子文件夹,则说明这个子文件夹下的文件数量大于等于10,则保留之;若该文件夹下没有子文件夹,则用 rd /s 命令把文件夹和其中的文件一起删除掉;(加粗字体是关键,也是最难理解的地方,请多思考几次就能明白)

  因为所有的处理都是按照文件夹完整路径的逆序来进行,所以保证了所有的处理都是从最底层子目录开始,所以,在第3步的时候,完全可以用 rd /s 来删除文件夹及其下的文件,而不用担心误伤。
作者: 随风    时间: 2011-3-25 09:07

本帖最后由 随风 于 2011-3-25 09:08 编辑

3# namejm
将for内部的两个 dir 换成 for  在面对海量文件夹的时候效率上应该会有质的飞跃  ^_^
作者: CrLf    时间: 2011-3-25 14:30

本帖最后由 zm900612 于 2011-3-25 14:34 编辑

学以致用:
  1. @echo off 2>nul 3>nul
  2. for /f "delims=" %%a in ('dir /ad /b /s "%destination%"^|sort /r 2^>nul') do (
  3.    for /f "skip=10 delims=" %%b in ('if not exist "%%a\*\" dir /a-d "%%a\"') do rd /s /q %%a
  4. )
复制代码

作者: caruko    时间: 2011-3-26 17:30

不知道有多海量,如果足够存路径名,也可以这样
  1. rem 得到每个路径下文件数
  2. for /r %%i in (*) do (
  3.      set /a #%~dpi+=1
  4. )
  5. rem 遍历路径,如果路径<10,搜索子路径,如果子路径下没有>=10的路径存在则删除,否者保留。
  6. for /f "tokens=1,2 delims==" %%a in ('set #') do (
  7.           if %%b lss 10 (
  8.                    set "flag="
  9.                    for /f "tokens=1,2 delims==" %%x in ('set #%%a') do (
  10.                               if %%y geq 10 set "flag=@"
  11.                    )
  12.                    if not defined flag rd /q "%%a"
  13.           )
  14. )
复制代码

作者: caruko    时间: 2011-3-26 17:49

dir /s /b |sort 的速度很慢。
for /r 的速度大于 for /f ('dir /s /b') 特别是海量时。
因为FOR必须等到 DIR | SORT 执行完毕,才从管道里读取输出,再一行一行执行FOR。
而FOR /R 是一边搜索文件,一边输出。

或许文件个数很多,但如果路径,也就是文件夹的个数不是特别多,那么用大量变量还是可以做到。


其实最快的方法不是我那个,应该是一边搜索,一边检测文件夹层次。
搜索到一个路径时,set %~dpi+=1,如果下一次的%~DPI不是上一次的子目录,那么就可以根据%~dpi的值确定是否删除了。
如果是,就继续记录路径。

如果文件夹的层次不超过32,而且for /r 搜索的规律性,可以用构建一个:函数
每次层次深入就setlocal 一次,每次检测到路径不是上一次的子目录,就endlocal一次,然后根据返回值确定是否删除该级目录。




欢迎光临 批处理之家 (http://bathome.net./) Powered by Discuz! 7.2