vbscript如何按修改日期对子文件夹中的文件进行排序(并使用文件的绝对路径打印)

时间:2012-04-18 10:41:50

标签: sorting vbscript batch-file directory

我需要创建一个vbs,在具有子文件夹的文件夹中按修改日期对可输出数量的文件(仅文件)进行排序,并使用绝对路径打印文件,如下所示:

vbs:

Dim MAX
Dim Folder
MAX = 100
Folder = "C:\Test"
vbscript functions to group all files of all subfolders, and sort them by MOD date... ok
vbscript funciont to make a text file output (This i can't do it by myself)
end

文本文件输出(100个最新文件):

c:\newest 1st file.txt
c:\subfolder1\newest 2nd file.txt
c:\subfolder7\newest 3rd file.txt
c:\subfolder2\newest 4 file.txt
c:\subfolder8\newest 5 file.txt
c:\subfolder4\newest 6 file.txt
c:\subfolder2\newest 7 file.txt
c:\newest 8 file.txt
c:\subfolder3\newest 9 file.txt
etc...

如果解决方案可以通过Batch完成,我真的没有其他问题,我同意,但我已经尝试过了:

Dir /S /TC /O-D

唯一的问题是不要向我展示绝对路径......

编辑:哦,当然我已经尝试过了:

Dir / B / S / TC / O-D

但是/ B参数意味着我之前说过的命令差异很大......

我的意思是:

Dir / S / TC / O-D

命令组(一起)所有子目录中的所有文件,并按日期对它们进行排序。 (GOOD!)

Dir / B / S / TC / O-D

命令按文件夹处理文件夹并对每个文件进行排序并显示。 (BAD!)

所以,如果我需要排序neswest只有100个文件,如果我使用带有“/ B”参数的Batch dir命令,我得到这个:

输出:

(Position 1) c:\subfolder1\Newest 1st file of this folder.txt
(Position 2) c:\subfolder1\Newest 2nd fil eof this folder.txt
(Position 3) c:\subfolder1\Old file of this folder.txt
(Position 3) c:\subfolder1\Older file of this folder.txt
(Position 4) c:\subfolder1\Oldest file of this folder.txt
(Position 5) c:\subfolder2\Newest 1st file of this folder.txt
(Position 6) c:\subfolder2\Newest 2nd file of this folder.txt
(Position 7) c:\subfolder2\Old file.txt
etc ...

所以请不要告诉我关于使用带/ B参数的dir,我知道它很好:(。

再次感谢

4 个答案:

答案 0 :(得分:2)

这是一个仅使用本机命令的纯批处理解决方案 - 它实际上运行良好: - )

我不确定在WMIC命令中是否可能需要转义其他字符,因为我对WMIC没有太多经验。但除此之外,我认为这是相当防弹的。

::treeNewestFiles FileCount [RootFolder]
::
::  Searches the directory tree rooted at RootFolder and prints
::  the most recently modified files. The number of files printed
::  is limited to a maximum of FileCount. If RootFolder is not
::  specified then the root is the current directory.
::
@echo off
setlocal disableDelayedExpansion

::define base temp file name
set "tempFile=%temp%\fileDates%random%.txt"

::Loop through all folders rooted at %2 (current directory if not specified), and use
::WMIC to list last modified timestamp and full path of each file in each folder.
::The last modified timestamp is in UTC ISO 8601 format, so it sorts properly.
(
  for /r %2 %%F in (.) do (
    set "folder=%%~pnxF"
    set "drive=%%~dF"
    setlocal enableDelayedExpansion
    2>nul wmic datafile where (path='!folder:\^=\\!\\' and drive='%%~dF'^) get lastmodified, name
    endlocal
  )
)>"%tempFile%"

::Convert unicode to ansii
type "%tempFile%" >"%tempFile%2"

::Preserve only data rows
findstr "^[0-9]" "%tempFile%2" >"%tempFile%3"

::Sort the results in descending order
sort /r "%tempFile%3" >"%tempFile%4"

::Print first %1 files in results
set n=0
for /f "usebackq tokens=1*" %%A in ("%tempFile%4") do (
  echo %%B
  set /a "n+=1, 1/(%1-n)" 2>nul || goto finish
)

:finish
del "%tempFile%*"

新的更快版本

我的原始代码为每个目录重新调用WMIC。 WMIC每次调用初始化需要大量时间。通过构建命令脚本并仅调用一次WMIC,我将执行时间减少了45%。性能增益量是树中目录数量的函数。目录数越多,这个新版本就越有帮助。我确信通过转换到VBS可以获得更多的性能提升,但我认为这不值得付出努力。我相信这个过程现在已经非常优化了。

::treeNewestFiles FileCount [RootFolder]
::
::  Searches the directory tree rooted at RootFolder and prints
::  the most recently modified files. The number of files printed
::  is limited to a maximum of FileCount. If RootFolder is not
::  specified then the root is the current directory.
::
@echo off
setlocal disableDelayedExpansion

::define temp folder for temp files
set "tempFolder=%temp%\fildates%random%"
md "%tempFolder%"

::define base path\name for temp files
set "tempFile=%tempFolder%\tempFile.txt"

::Loop through all folders rooted at %2 (current directory if not specified),
::and build a script of WMIC commands that will list last modified timestamps
::and full path of files for each folder. The last modified tamestamp will
::be in ISO 8601 format, so it sorts properly.
(
  echo /append:"%tempFile%1"
  for /r %2 %%F in (.) do (
    set "folder=%%~pnxF"
    set "drive=%%~dF"
    setlocal enableDelayedExpansion
    echo datafile where (path='!folder:\^=\\!\\' and drive='%%~dF'^) get lastmodified, name
    endlocal
  )
  echo quit
)>"%tempFile%"

::Execute the WMIC script
::WMIC creates a temporary file in current directory,
::so change directory 1st so it doesn't interfere with results.
pushd "%tempFolder%"
cmd /c ^<"%tempFile%" wmic ^>nul 2^>nul

::Convert unicode to ansii
type "%tempFile%1" >"%tempFile%2"

::Preserve only data rows
findstr "^[0-9]" "%tempFile%2" >"%tempFile%3"

::Sort the results in descending order
sort /r "%tempFile%3" >"%tempFile%4"

::Print first %1 files in results
set n=0
for /f "usebackq tokens=1*" %%A in ("%tempFile%4") do (
  echo %%B
  set /a "n+=1, 1/(%1-n)" 2>nul || goto finish
)

:finish
popd
rd /q /s "%tempFolder%"

答案 1 :(得分:2)

我遵循上面KH1的建议:“你需要将所有文件路径和名称与DateModified一起加载到一个数组中,对数组进行排序,然后遍历数组并输出文件路径和名称”,但是在批处理文件中。下面的程序使用该文件的YYYYMMDDHHMM修改时间戳作为数组的索引。这样,Batch SET命令就会使数组保持自动排序。参数与上面的dbenham程序相同:FileCount和可选的RootFolder。

@echo off
setlocal EnableDelayedExpansion

rem Get order of FileTimeStamp elements independent of regional settings
for /F "skip=1 tokens=2-4 delims=(-)" %%a in ('date^<NUL') do (
   set timeStampOrder=%%a %%b %%c ho mi ap
)

rem For each file in the folder given by %2 (default current one)
for /R %2 %%F in (*.*) do (
   rem Extract FileTimeStamp data (yy mm dd ho mi ap)
   for /F "tokens=1-6" %%a in ("%timeStampOrder%") do (
      for /F "tokens=1-6 delims=/-.: " %%i in ("%%~tF") do (
         set %%a=%%i
         set %%b=%%j
         set %%c=%%k
         set %%d=%%l
         set %%e=%%m
         set %%f=%%n
      )
   )
   rem Adjust hour if needed
   if !ap! equ p set /A "ho=10!ho! %% 100 + 12
   rem Create the array element with proper index
   set "file[!yy!!mm!!dd!!ho!!mi!]=%%~fF"
)

rem At this point the array is automatically sorted

rem Show the first %1 array elements
set n=0
for /F "tokens=2 delims==" %%a in ('set file[') do (
   echo %%a
   set /A n+=1
   if !n! equ %1 goto finish
)

:finish

答案 2 :(得分:1)

让您入门的三步演示:

Option Explicit

' ADO Constants needed in this demo
Const adDBTimeStamp      =        135 ' 00000087
Const adVarWChar         =        202 ' 000000CA
Const adClipString       =          2 ' 00000002

' Globals
Dim goFS   : Set goFS = CreateObject("Scripting.FileSystemObject")
Dim gsSDir : gsSDir   = "..."

' Dispatch using comments or re-order
WScript.Quit demoTraversal()
WScript.Quit demoDirWalker()
WScript.Quit demoAdoDirWalker()

' Step00: Understanding recursive traversal
Function demoTraversal()
  walkDir00 goFS.GetFolder(gsSDir)
End Function ' demoTraversal

' Minimal recursive traversal: do something for each file in folder and
' then call same Sub for each subfolder
Sub walkDir00(oDir)
  Dim oElm
  For Each oElm In oDir.Files
      WScript.Echo oElm.DateLastModified, oElm.Path
  Next
  For Each oElm In oDir.SubFolders
      walkDir00 oElm
  Next
End Sub ' walkDir00

' Step01: Recursive traversal with Class
' Use an object to abstract the 'something' action(s) and to augment
' state (count)
Function demoDirWalker()
  Dim oDirWalker : Set oDirWalker = New cDirWalker01.init()
  walkDir01 goFS.GetFolder(gsSDir), oDirWalker
  WScript.Echo oDirWalker.Count, "files seen"
End Function ' demoTraversal

Class cDirWalker01
  Private m_nCount
  Public Function init()
    Set init    = Me
    m_nCount    = 0
  End Function ' init
  Public Sub processFile(oFile)
    ' add bool expression or function to filter
    WScript.Echo oFile.DateLastModified, oFile.Path
    m_nCount = m_nCount + 1
  End Sub ' processFile
  Public Property Get Count()
    Count = m_nCount
  End Property ' Count
End Class ' cDirWalker01

Sub walkDir01(oDir, oDirWalker)
  Dim oElm
  For Each oElm In oDir.Files
      oDirWalker.processFile oElm
  Next
  For Each oElm In oDir.SubFolders
    ' add bool expression or DirWalker.method to filter
      walkDir01 oElm, oDirWalker
  Next
End Sub ' walkDir00

' Step02: Solution (POC)
Function demoAdoDirWalker()
  Dim oDirWalker : Set oDirWalker = New cAdoDirWalker.init()
  walkDir01 goFS.GetFolder(gsSDir), oDirWalker
  oDirWalker.sort "sPath ASC, dtLM ASC"
  WScript.Echo oDirWalker.getResultString()
  oDirWalker.sort "dtLM DESC, sPath ASC"
  WScript.Echo oDirWalker.getResultString()
End Function ' demoAdoDirWalker

Class cAdoDirWalker
  Private m_oRS
  Public Function init()
    Set init  = Me
    Set m_oRS = CreateObject("ADODB.Recordset")
    m_oRS.Fields.Append "dtLM" , adDBTimeStamp
    m_oRS.Fields.Append "sPath", adVarWChar, 255
    m_oRS.Open
  End Function ' init
  Public Sub processFile(oFile)
    m_oRS.AddNew
    m_oRS.Fields("sPath").Value = oFile.Path
    m_oRS.Fields("dtLM" ).Value = oFile.DateLastModified
    m_oRS.Update
  End Sub ' add
  Public Sub sort(sWhat)
    m_oRS.sort = sWhat
  End Sub ' sort
  Public Function GetResultString()
    m_oRS.MoveFirst
    GetResultString = m_oRS.GetString(adClipString, , " | ", vbCrLf, "NULL")
  End Function ' GetResultString
End Class ' cAdoDirWalker

主要思想是使用Disconnected ADO Recordset来存储和排序文件夹树中的文件集合。

答案 3 :(得分:1)

您可以使用for命令和〜语法(请参阅for /?)来执行此操作:

(for /r %A in (*) do @echo %~tA %A ) | sort /r

括号的使用允许单个重定向整个forsort。没有括号,每个echo都会被重定向到单个sort,因此不会进行排序。

编辑:正如Ekkehard.Horner所指出的,上述代码仅适用于以yyyy-mm-dd格式打印日期的区域。在以mm / dd / yyyy格式打印日期的区域中,您可以使用以下批处理文件:

@Echo Off
setlocal enabledelayedexpansion

if "%1"=="list" goto :list
%0 list | sort /r

endlocal
goto :EOF

:list
for /r %%A in (*) do (
  set t=%%~tA
  echo !t:~6,4!-!t:~0,2!-!t:~3,2! %%A
)
goto :EOF

我没有设法在批处理文件中使用括号重复这个技巧,因此脚本使用参数调用自身,使其打印文件列表,然后对输出进行排序。使用%variable~:start-length%语法(请参阅set /?)和延迟变量扩展,将日期转换为yyyy-mm-dd格式。它不像dbenham的解决方案那样具有防弹功能,但它确实有效。