1

Тема: VBScript: работа с большими текстовыми файлами

ADOFromText.wsf

<?XML version="1.0" standalone="yes" ?>
<job id="ADOFromText">
    <?job error="True" debug="True" ?>
    <runtime>
        <description>This script XOR-filtering lines from Source file by part lines from Filter file, then save result lines in Result file</description>
        <named name="PathToSchema"   helpstring="Path to schema"             type="string" required="true" />
        <named name="SourceFile"     helpstring="Name of source file"        type="string" required="true" />
        <named name="SourcePathDiff" helpstring="Diff part of source's path" type="string" required="true" />
        <named name="FilterFile"     helpstring="Name of filter file"        type="string" required="true" />
        <named name="FilterPathDiff" helpstring="Diff part of filter's path" type="string" required="true" />
        <named name="ResultFile"     helpstring="Name of result file"        type="string" required="true" />
        
        <example>Example: ADOFromText.wsf /PathToSchema:"c:\temp" /SourceFile:"SourceFile.txt" /SourcePathDiff:"c:\Install" /FilterFile:"FilterFile.txt" /FilterPathDiff:"\\server\Installation$" /ResultFile:"ResultFile.txt"</example>
    </runtime>
    
    <reference object="ADODB.Connection" />
    
    <object id="objRecordSetResult" progid="ADODB.Recordset" />
    <object id="objFSO"             progid="Scripting.FileSystemObject" />
    
    <script language="VBScript">
        <![CDATA[
            Option Explicit
            
            Dim objNamedArgs : Set objNamedArgs = WScript.Arguments.Named
            
            If objFSO.FileExists(objFSO.BuildPath(objNamedArgs.Item("PathToSchema"), objNamedArgs.Item("ResultFile"))) Then
                objFSO.DeleteFile objFSO.BuildPath(objNamedArgs.Item("PathToSchema"), objNamedArgs.Item("ResultFile"))
            End If

            objRecordSetResult.Open "SELECT S.[FileName], S.[Path], S.[DateTimeModified], S.[Size] INTO [" & _
                objNamedArgs.Item("ResultFile") & "] " & _
                "FROM [" & objNamedArgs.Item("SourceFile") & "] AS S LEFT JOIN [" & _
                objNamedArgs.Item("FilterFile") & "] AS F ON " & _
                "S.[FileName] = F.[FileName] AND " & _
                "MID(S.[Path], " & CStr(Len(objNamedArgs.Item("SourcePathDiff")) + 1) & ") = " & _
                "MID(F.[Path], " & CStr(Len(objNamedArgs.Item("FilterPathDiff")) + 1) & ") AND " & _
                "S.[DateTimeModified] = F.[DateTimeModified] AND S.[Size] = F.[Size] " & _
                "WHERE F.[FileName] IS NULL", _
                "Provider=Microsoft.Jet.OLEDB.4.0;Data Source=" & objNamedArgs.Item("PathToSchema") & ";Extended Properties=""text;""", _
                adOpenStatic, adLockOptimistic, adCmdText
            
            Set objNamedArgs = Nothing
            
            WScript.Quit 0
        ]]>
    </script>
</job>

Schema.ini

[HomeComp.txt]
ColNameHeader=False
Format=Delimited(;)
TextDelimiter=none
CharacterSet=ANSI
Col1=FileName Text
Col2=Path Text
Col3=DateTimeModified DateTime
Col4=Size Long

[JobComp.txt]
ColNameHeader=False
Format=Delimited(;)
TextDelimiter=none
CharacterSet=ANSI
Col1=FileName Text
Col2=Path Text
Col3=DateTimeModified DateTime
Col4=Size Long

[ResultFile.txt]
ColNameHeader=False
Format=Delimited(;)
TextDelimiter=none
DateTimeFormat=mm.dd.yy hh:nn
CharacterSet=ANSI
Col1=FileName Text
Col2=Path Text
Col3=DateTimeModified DateTime
Col4=Size Long

Пример файла JobComp.txt

IM000001.JPG;C:\Install\My Archivies\ФОТКИ\28-05-06 (Н.Я.День рожденье);28.05.2006 17:17;1437280
IM000002.JPG;C:\Install\My Archivies\ФОТКИ\28-05-06 (Н.Я.День рожденье);28.05.2006 17:26;1453600
IM000003.JPG;C:\Install\My Archivies\ФОТКИ\28-05-06 (Н.Я.День рожденье);28.05.2006 17:27;1576192
IM000006.JPG;C:\Install\My Archivies\ФОТКИ\28-05-06 (Н.Я.День рожденье);28.05.2006 17:30;1483488
IM000007.JPG;C:\Install\My Archivies\ФОТКИ\28-05-06 (Н.Я.День рожденье);28.05.2006 17:34;1335840
IM000008.JPG;C:\Install\My Archivies\ФОТКИ\28-05-06 (Н.Я.День рожденье);28.05.2006 17:37;1516512
IM000009.JPG;C:\Install\My Archivies\ФОТКИ\28-05-06 (Н.Я.День рожденье);28.05.2006 17:38;1672512
IM000010.JPG;C:\Install\My Archivies\ФОТКИ\28-05-06 (Н.Я.День рожденье);28.05.2006 17:38;1521632
IM000011.JPG;C:\Install\My Archivies\ФОТКИ\28-05-06 (Н.Я.День рожденье);28.05.2006 17:39;1367072

Пример файла HomeComp.txt

IM000001.JPG;D:\Other\ФОТКИ\28-05-06 (Н.Я.День рожденье);28.05.2006 17:17;1437280
IM000002.JPG;D:\Other\ФОТКИ\28-05-06 (Н.Я.День рожденье);28.05.2006 17:26;1453600
IM000005.JPG;D:\Other\ФОТКИ\28-05-06 (Н.Я.День рожденье);28.05.2006 17:29;1606784
IM000006.JPG;D:\Other\ФОТКИ\28-05-06 (Н.Я.День рожденье);28.05.2006 17:30;1483488
IM000010.JPG;D:\Other\ФОТКИ\28-05-06 (Н.Я.День рожденье);28.05.2006 17:38;1521632
IM000011.JPG;D:\Other\ФОТКИ\28-05-06 (Н.Я.День рожденье);28.05.2006 17:39;1367072

Для примера: все указанные файлы находятся в [c:\0001]. Тогда вызов ADOFromText.wsf осуществляется командой:

cscript.exe //nologo "c:\0001\ADOFromText.wsf" /PathToSchema:"c:\0001" /SourceFile:"JobComp.txt" /SourcePathDiff:"C:\Install\My Archivies" /FilterFile:"HomeComp.txt" /FilterPathDiff:"D:\Other" /ResultFile:"ResultFile.txt"

Результирующий файл ResultFile.txt

IM000003.JPG;C:\Install\My Archivies\ФОТКИ\28-05-06 (Н.Я.День рожденье);05.28.06 17:27;1576192
IM000007.JPG;C:\Install\My Archivies\ФОТКИ\28-05-06 (Н.Я.День рожденье);05.28.06 17:34;1335840
IM000008.JPG;C:\Install\My Archivies\ФОТКИ\28-05-06 (Н.Я.День рожденье);05.28.06 17:37;1516512
IM000009.JPG;C:\Install\My Archivies\ФОТКИ\28-05-06 (Н.Я.День рожденье);05.28.06 17:38;1672512

Примечания:

Собственно, вся функциональность лежит в использовании запроса

RecordSet.Open "SELECT <поля> INTO "ResultFile" FROM "SourceFile" LEFT JOIN "FilterFile" ON <Связь полей>, <Соединение>,…

Мы извлекаем все записи из "SourceFile", связанного с "FilterFile" (LEFT JOIN) по указанному условию <Связь полей>. Нас интересуют равенство полей [FileName], [DateTimeModified] и [Size] (S.[FileName] = F.[FileName], …). Из полей [Path] при сравнении мы исключаем несовпадающие начальные части, указанные в параметрах скрипта (MID(S.[Path], " & CStr(Len(objNamedArgs.Item("SourcePathDiff")) + 1) & ")=…). Также, из полученного набора мы исключаем записи, в которых значения полей из правой таблицы — пустые (для примера взято поле [File]: WHERE F.[FileName] IS NULL). И, наконец, помещаем полученный набор записей в результирующую таблицу "ResultFile" (INTO [" & objNamedArgs.Item("ResultFile") & "]).

Как правило, стоит заключать строки в кавычки, как и положено в CSV, хотя это и необязательно. В примере я не использовал их и для выходного файла (Schema.ini:TextDelimiter=none).

Формат дата/время желательно представлять с секундами В примере я не использовал секунды и для выходного файла (Schema.ini:DateTimeFormat=mm.dd.yy hh:nn) [в скобках замечу, что мне не удалось использовать указание такого формата для входных файлов, приводило к ошибке].

Вместо

If objFSO.FileExists(objFSO.BuildPath(objNamedArgs.Item("PathToSchema"), objNamedArgs.Item("ResultFile"))) Then
    objFSO.DeleteFile objFSO.BuildPath(objNamedArgs.Item("PathToSchema"), objNamedArgs.Item("ResultFile"))
End If

можно использовать "DROP TABLE [" & objNamedArgs.Item("ResultFile") & "]", однако это приводит и к удалению из схемы информации о соответствующей таблице , а она у нас не совсем стандартная (-кавычки, -секунды), так что, для простоты, пользуем простое удаление результирующего файла [также отмечу, что операция DELETE для Text Driver не поддерживается в принципе].

Часть параметров из Schema.ini можно указывать в строке подключения, как свойства:

Provider=Microsoft.Jet.OLEDB.4.0;Data Source=c:\txtFilesFolder\;Extended Properties="text;HDR=Yes;FMT=Delimited"

Полного описания я, к сожалению, не нашёл.

Much ADO About Text Files

Автор статьи - alexii.

Предложения в русском языке начинаются с большой буквы и заканчиваются точкой.
В названии ветки всегда должен быть указан язык программирования или среда исполнения скрипта, если это возможно.

2

Re: VBScript: работа с большими текстовыми файлами

Ещё один пример, попроще. Я взял для примера большой файл лога 1С v7.7 (1cv7.mlg). Расширение файла надо переименовывать в "txt".
C:\Temp\Schema.ini

[1cv7.txt]
ColNameHeader=False
Format=Delimited(;)
TextDelimiter=none
CharacterSet=ANSI
Col1=Date Text
Col2=Time Text
Col3=User Text
Col4=Mode Text
Col5=Field5 Text
Col6=Field6 Text
Col7=Field7 Text
Col8=Field8 Text
Col9=Field9 Text
Col10=Field10 Text

Собственно пример:

Set objRec = CreateObject("ADODB.Recordset")
strFile = "1cv7.txt"
strQuery = "SELECT S.[Date], S.[Time], S.[Mode] FROM [" & strFile & "] AS S" 'WHERE S.[User] = 'kos'"
strIni = "C:\Temp" 'Schema.ini
strConn = "Provider=Microsoft.Jet.OLEDB.4.0;Data Source=" & strIni & ";Extended Properties=""text;"""
Const adOpenStatic = 3
Const adLockOptimistic = 3
Const adCmdText = 1

objRec.Open strQuery, strConn, adOpenStatic, adLockOptimistic, adCmdText
'While Not objRec.EOF
'    strRes = vbNullString
'    For i=0 To objRec.Fields.Count-1
'        strRes = strRes & CStr(objRec.Fields(i).Value) & vbTab
'    Next
'    WScript.Echo Trim(strRes)
'    objRec.MoveNext
'Wend
WScript.Echo objRec.RecordCount
objRec.Close
Set objRec = Nothing

Файл лога имел размер 630 915 078 байт, все записи (строки) без условий выбираются примерно за 80 сек. (5 223 755 строк). С условиями (WHERE): 164 117 строк примерно за 70 сек., 2 228 строк примерно за 44 сек., ноль строк примерно за те же 44 сек.
WinXP SP2, 512Мб памяти, Celeron D 3.0GHz.

Замечание от fps: для небольших объемов (<100 МБ) ReadAll() и затем, при надобности, разбор регекспами получается быстрее и проще в реализации. Пример подобной обработки здесь. Но с гигабайтами так уже не поработаешь.

Предложения в русском языке начинаются с большой буквы и заканчиваются точкой.
В названии ветки всегда должен быть указан язык программирования или среда исполнения скрипта, если это возможно.