sorting - programa - vbscript cómo ordenar los archivos en subcarpetas por fecha de modificación(e imprimirlo con la ruta absoluta del archivo)
programa para organizar archivos y carpetas (4)
Una demostración de tres pasos para que comiences:
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
La idea principal es usar un conjunto de registros ADO desconectado para almacenar y ordenar la colección de archivos en un árbol de carpetas.
Necesito crear un vbs para ordenar una cantidad fija de archivos (solo los archivos) por la fecha modificada en una carpeta con subcarpetas, e imprimir el archivo con la ruta absoluta, como esta:
El 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
El resultado del archivo de texto (100 archivos más nuevos):
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...
Realmente no importa si la solución se puede hacer con Batch, estoy de acuerdo, pero he intentado esto:
Dir /S /TC /O-D
Y el único problema es que no me muestren el camino absoluto ...
EDITAR : Ah, y por supuesto que he intentado:
Dir / B / S / TC / O-D
Pero el parámetro / B implica una GRAN diferencia en el comando que dije antes ...
Quiero decir:
Dir / S / TC / O-D
El comando agrupa (juntos) todos los archivos en todos los subdirectorios y los ordena por fecha. (¡BUENO!)
Dir / B / S / TC / O-D
El comando va procesando carpeta por carpeta y ordenando cada archivo, y mostrándolo. (¡MALO!)
Entonces, si necesito ordenar neswest solo 100 archivos, y si uso el comando Batch dir con el parámetro "/ B", obtengo esto:
Salida:
(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 ...
Así que por favor no me digas nada sobre usar dir con el parámetro / B, lo sé bien :(.
gracias de nuevo
Aquí hay una solución de lote puro que usa solo comandos nativos, y en realidad funciona bien :-)
No estoy seguro de si podría haber caracteres adicionales que deban escaparse en el comando WMIC, ya que no tengo mucha experiencia con WMIC. Pero de lo contrario, creo que es bastante a prueba de balas.
::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%*"
Nueva versión más rápida
Mi código original realizó una nueva llamada a WMIC para cada directorio. A WMIC le lleva una cantidad significativa de tiempo inicializarse con cada llamada. Reduje el tiempo de ejecución en un 45% construyendo un script de comandos y llamando a WMIC solo una vez. La cantidad de ganancia de rendimiento es una función de la cantidad de directorios en el árbol. Cuanto mayor sea el número de directorios, más ayudará esta nueva versión. Estoy seguro de que aún se obtendrán más mejoras de rendimiento al convertir a VBS, pero no creo que valga la pena el esfuerzo. Creo que este proceso ahora está bastante optimizado.
::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%"
Puedes hacerlo con comandos y ~ sintaxis (ver for /?
):
(for /r %A in (*) do @echo %~tA %A ) | sort /r
El uso de corchetes permite una sola redirección od entero for
sort
. Sin saltos, cada echo
se redirigiría a una sort
individual, por lo que no se realizaría ninguna clasificación.
EDITAR: Como señaló Ekkehard.Horner, el código anterior funcionará solo en regiones donde las fechas se imprimen en formato aaaa-mm-dd. En una región donde las fechas se imprimen en formato mm / dd / aaaa, puede usar el siguiente archivo por lotes:
@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
No logré repetir el truco con corchetes dentro de un archivo por lotes, por lo que el script se llama a sí mismo con un parámetro que hace que imprima la lista de archivos y luego ordena la salida. Las fechas se convierten al formato aaaa-mm-dd utilizando %variable~:start-length%
sintaxis (ver set /?
) Y la expansión de variables retrasadas. No es tan a prueba de balas como la solución de dbenham, pero funciona.
Sigo el consejo de KH1 anterior: "Tendrá que cargar toda la ruta y los nombres del archivo junto con DateModified en una matriz, ordenar la matriz, y luego recorrer el conjunto y generar la ruta y el nombre del archivo", pero en un Archivo por lotes. El siguiente programa usa como índice de la matriz la marca de tiempo modificada YYYYMMDDHHMM del archivo. De esta manera, la matriz se mantiene automáticamente ordenada por el comando Batch SET. Los parámetros son los mismos que los del programa dbenham anterior: FileCount y RootFolder opcional.
@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