Компьютерный форум OSzone.net  

Компьютерный форум OSzone.net (http://forum.oszone.net/index.php)
-   AutoIt (http://forum.oszone.net/forumdisplay.php?f=103)
-   -   Скрипт для разбиения файла на части (http://forum.oszone.net/showthread.php?t=149236)

Diamond 29-08-2009 09:00 1206126

Скрипт для разбиения файла на части
 
Скрипт позволяет разбивать файл на части, которые в последствии можно объеденить с помощью пакетного
файла без нарушения внутреннего форматирования (см. команда Copy /?).
После разбиения, скрипт создаёт пакетный файл "Combine all parts.bat" в том же директории куда копируются части.
P.S. Надеюсь это кому нибудь пригодится. :)
читать дальше »
Код:

#include <WinAPI.au3>
#include <GuiConstants.au3>
#include <WindowsConstants.au3>
#include <GuiStatusBar.au3>
Global $Title = "SplitFile", $Msg, $ret, $fSplitRunning
Global $Gui = GUICreate($Title, 500, 240)
GUICtrlCreateLabel("Путь к файлу:", 5, 10, 104, 22)
GUICtrlCreateLabel("Сохранить в:", 5, 50, 104, 22)
GUICtrlCreateLabel("Размер части:", 5, 90, 104, 22)
Global $Input1 = GUICtrlCreateInput("",110, 10, 300, 22, 0x0080)
    GUICtrlSetTip(-1, "Путь к файлу-источнику.")
Global $Input2 = GUICtrlCreateInput("C:\Split",110, 50, 300, 22, 0x0080)
    GUICtrlSetTip(-1, "Директорий для сохранения частей.")
Global $Input3 = GUICtrlCreateInput("", 110, 90, 300, 22, 0x2000+0x0080)
    GUICtrlSetTip($Input3, "Размер каждой части в Мегабайтах.")
Global $Button1 = GUICtrlCreateButton("Обзор", 420, 10, 70, 22)
Global $Button2 = GUICtrlCreateButton("Обзор", 420, 50, 70, 22)
Global $Button3 = GUICtrlCreateButton("Разделить", 420, 140, 70, 40)
For $i = $Input1 To $Input3
    GUICtrlSetFont($i-3, 11.5, 800, 0, "Times New Roman")
    GUICtrlSetFont($i, 11.5, 500, 0, "Arial")
Next
Global $radio1 = GUICtrlCreateRadio("Bytes", 10, 140, 50, 20)
Global $radio2 = GUICtrlCreateRadio("KBytes", 10, 160, 60, 20)
Global $radio3 = GUICtrlCreateRadio("MBytes", 10, 180, 60, 20)
    GUICtrlSetState($radio3, $GUI_CHECKED)
Global $Progress = GUICtrlCreateProgress(0, 0, -1, -1, 0x01)
Global $hProgress = GUICtrlGetHandle($Progress)
Global $aParts[4] = [1, 222, 270, -1]
Global $hStatusBar = _GUICtrlStatusBar_Create($Gui, $aParts), $fStatusBarSpoiled = False
_GUICtrlStatusBar_EmbedControl($hStatusBar, 1, $hProgress, 4)
_GUICtrlStatusBar_SetText($hStatusBar, "0%", 2)

GUIRegisterMsg($WM_COMMAND, "WM_COMMAND")
GUIRegisterMsg($WM_SYSCOMMAND, "WM_SYSCOMMAND")
GUIRegisterMsg($WM_PAINT, "WM_PAINT")
GUISetState()

While 1
    $msg = GUIGetMsg()
    Switch $msg
    Case $GUI_EVENT_CLOSE
        Exit
    Case $Button1
        GUISetState(@SW_DISABLE, $Gui)
        $ret = FileOpenDialog($Title, "", 'Все файлы (*.*)', 1, "", $Gui)
        If Not @error Then GUICtrlSetData($Input1, $ret)
        GUISetState(@SW_ENABLE, $Gui)
    Case $Button2
        GUISetState(@SW_DISABLE, $Gui)
        $ret = FileSelectFolder("Выберите пустой директорий для сохранения частей.", "", 1, "", $Gui)
        If Not @error And FileExists($ret) Then GUICtrlSetData($Input2, $ret)
        GUISetState(@SW_ENABLE, $Gui)
    Case $Button3
        If Split() = -1 Then $fSplitRunning = False
    Case $radio1
        GUICtrlSetTip($Input3, "Размер каждой части в Байтах.")
    Case $radio2
        GUICtrlSetTip($Input3, "Размер каждой части в Килобайтах.")
    Case $radio3
        GUICtrlSetTip($Input3, "Размер каждой части в Мегабайтах.")
    EndSwitch
WEnd

Func Split()
    $fSplitRunning = True ; Флаг $fSplitRunning сообщает о том что функция Split была запущена или завершена
    Local $ret, $sFile, $sDir, $iSplitSize, $aDirInfo, $iSize, $i, $PartCount, $Current, $newState, $oldState
    Local $iBytes, $tBuffer, $hFileOpen, $hFileWrite, $sExt, $sFileName, $sBat
    If @ScriptDir <> @WorkingDir Then FileChangeDir(@ScriptDir)
    $sFile = GUICtrlRead($Input1)
    $sDir = GUICtrlRead($Input2)
    For $i = $radio1 To $radio3
        If GUICtrlRead($i) = $GUI_CHECKED Then ExitLoop
    Next
    $iSplitSize = GUICtrlRead($Input3)*1024^($i-$radio1)
    If Not FileExists($sFile) Then
        _MsgBox(16, $Title, "Файл не существует.", $Gui)
        Return -1
    EndIf
    If Not FileExists($sDir) Then
        If Not DirCreate($sDir) Then
            _MsgBox(16, $Title, "Укажите путь к папке.", $Gui)
            Return -1
        EndIf
    Else
        $aDirInfo = DirGetSize($sDir, 1)
        If $aDirInfo[1]+$aDirInfo[2] > 0 Then
        $ret = _MsgBox(16, $Title, "Указанная папка не пуста.", $Gui)
        Return -1
    EndIf
    EndIf
    If DriveGetType($sDir) <> "Fixed" Then
        _MsgBox(16, $Title, "Указанный тип носителя не поддерживается.", $Gui)
        Return -1
    EndIf
    If Not $iSplitSize Then
        _MsgBox(16, $Title, "Установлено недопустимое значение размера для части.", $Gui)
        Return -1
    EndIf
    $iSize = FileGetSize($sFile)
    If $iSplitSize >= $iSize Then
        _MsgBox(16, $Title, "Указанный размер, превышает или равен размеру файла.", $Gui)
        Return - 1
    EndIf
    ;===============================================
    $PartCount = Ceiling($iSize/$iSplitSize)
    $sDir = StringRegExpReplace($sDir,"\\$","") & "\"
    $sFileName = StringRegExpReplace($sFile,"^.*\\","")
    $sExt = "." & StringRegExpReplace($sFileName, "^.*\.|^.*","")
    _GUICtrlStatusBar_SetText($hStatusBar, "Открытие файла...", 3)
    $hFileOpen = _WinAPI_CreateFile($sFile, 2, 2)
    $Current = $iSplitSize
    _GUICtrlStatusBar_SetText($hStatusBar, "Разделение...", 3)
    For $i = 1 To $PartCount
        If $i = $PartCount Then $Current = $iSize-$iSplitSize*($i-1)
        $tBuffer = DllStructCreate("byte[" & $Current & "]")
        _WinAPI_ReadFile($hFileOpen, DllStructGetPtr($tBuffer), $Current, $iBytes)
        $hFileWrite = _WinAPI_CreateFile($sDir & "part" & $i & $sExt, 1, 4)
        _WinAPI_WriteFile($hFileWrite, DllStructGetPtr($tBuffer), $Current, $iBytes)
        _WinAPI_CloseHandle($hFileWrite)
        _WinAPI_SetFilePointer($hFileOpen, 0, 1) ; смещение от текущей позиции на указанный размер($Current)
        $newState = Round(100*$i/$PartCount)
        If $newState <> $oldState Then
            GUICtrlSetData($Progress, $newState)
            _GUICtrlStatusBar_SetText($hStatusBar, $newState & "%", 2)
            $oldState = $newState
        EndIf
    Next
    _WinAPI_CloseHandle($hFileOpen)
    $sBat = StrBatCreate($sExt, $sFileName, $PartCount)
    FileWrite($sDir & "Combine all parts.bat", $sBat)
    GUICtrlSetData($Progress, $newState)
    _GUICtrlStatusBar_SetText($hStatusBar, "100%", 2)
    Sleep(400)
    GUICtrlSetData($Progress, 0)
    _GUICtrlStatusBar_SetText($hStatusBar, "0%", 2)
    _GUICtrlStatusBar_SetText($hStatusBar, "Готово", 3)
    Return -1
EndFunc

Func StrBatCreate($sExt, $sDestFile, $iCount)
    Local $sBat
    $sBat = '@echo off' & @LF & _
    'title Подготовка...' & @LF & _
    'setlocal enableextensions enabledelayedexpansion' & @LF & _
    'set strTempFolder=$temp%random%' & @LF & _
    'md ".\%strTempFolder%"' & @LF & _
    'for /l %%i in (1,1,' & $iCount & ') do (set strPartFile=Part%%i'& $sExt & @LF & _
    'if exist "!strPartFile!" (move "!strPartFile!" ".\%strTempFolder%\!strPartFile!">nul))' & @LF & _
    'title Слияние...' & @LF & _
    'copy /b ".\%strTempFolder%\Part*'& $sExt & '" "' & $sDestFile & '"' & @LF & _
    'title Пожалуйста, дождитесь завершения...' & @LF & _
    'move ".\%strTempFolder%\Part*'& $sExt & '" ".">nul' & @LF & _
    'rd /s /q ".\%strTempFolder%"'
    Return CharToOem($sBat)
EndFunc

Func WM_COMMAND($HWnd, $MsgID, $wParam, $lParam)
    Local $iFile, $CtrlID, $iSplitSize, $i, $iPartCount, $iFileSize
    $CtrlID  = BitAND($wParam, 0xFFFF)
    Switch $CtrlID
        Case $Button1 To $Button3
        If $fSplitRunning Then
            DllCall("User32.dll", "int", "MessageBeep", "uint", 16)
            Return 1
        EndIf
        Case $Input1, $Input3, $radio1 To $radio3
        $iFile = GUICtrlRead($Input1)
        If FileExists($iFile) And Not $fSplitRunning Then
            For $i = $radio1 To $radio3
                If GUICtrlRead($i) = $GUI_CHECKED Then ExitLoop
            Next
            $iSplitSize = Number(GUICtrlRead($Input3)*1024^($i-$radio1))
            $iFileSize = FileGetSize($iFile)
            $iPartCount = Ceiling($iFileSize/$iSplitSize)
            If $iPartCount < 0 Or $iFileSize <= $iSplitSize Then $iPartCount = 0
            _GUICtrlStatusBar_SetText($hStatusBar, "Кол-во частей: " & $iPartCount, 3)
        EndIf
    EndSwitch
    Return $GUI_RUNDEFMSG
EndFunc

Func WM_PAINT($HWnd, $MsgID)
    If $fStatusBarSpoiled Then ; "перерисовка" StatusBar
        _GUICtrlStatusBar_EmbedControl($hStatusBar, 1, $hProgress, 4)
        $fStatusBarSpoiled = False
    EndIf
    Return $GUI_RUNDEFMSG
EndFunc

Func WM_SYSCOMMAND($HWnd, $MsgID, $wParam, $lParam)
    Local Const $SC_RESTORE = 0xF120
    ;Устанавливаю флаг $fStatusBarSpoiled в True (для WM_PAINT это обозначит что StatusBar был неверно отрисован при разворачивании окна.)
    If $wParam = $SC_RESTORE Then $fStatusBarSpoiled = True
    Return $GUI_RUNDEFMSG
EndFunc

Func _MsgBox($MsgBoxType, $MsgBoxTitle, $MsgBoxText, $Main_GUI)
    Local $ret, $err
    GUISetState(@SW_DISABLE, $Main_GUI)
    $ret = DllCall("user32.dll", "int", "MessageBox", _
            "hwnd", $Main_GUI, _
            "str", $MsgBoxText, _
            "str", $MsgBoxTitle, _
            "int", $MsgBoxType)
    $err = @error
    GUISetState(@SW_ENABLE, $Main_GUI)
    If Not $err Then Return $ret[0]
EndFunc  ;==>_MsgBox

Func CharToOem($String)
    Local $tBuffer = DllStructCreate('char[' & StringLen($String)+1 & ']')
    DllCall('user32.dll','none','CharToOem','str',$String,'ptr',DllStructGetPtr($tBuffer))
    If @error Then Return SetError(@error, 0, 0)
    Return SetError(0, 0, DllStructGetData($tBuffer, 1))
EndFunc


FlatX007 29-08-2009 13:54 1206287

Ничё так... молодец! сам придумал ?

PS. Каскрась код в цветной (утилита Au3ToPst)

Medic84 29-08-2009 13:59 1206293

FlatX007, Если он разукрасит этот код, то у него возникнет ошибка. Слишком много символов для размещения на форуме :)
Только не вижу смысла в создании других MSgBox и FileOpenDialog Объясните зачем, а?

Diamond 29-08-2009 14:30 1206318

Цитата:

Цитата FlatX007
Ничё так... молодец! сам придумал ? »

Спасибо! :) Код мой, а идея... где-то я уже видел нечто подобное раньше.
Цитата:

Цитата Medic84
Только не вижу смысла в создании других MSgBox и FileOpenDialog Объясните зачем, а? »

FileOpenDialog - просто нравиться, MSgBox был нужен как дочерний, для того чтобы запретить доступ к основному Gui.

FlatX007 29-08-2009 14:32 1206320

Цитата:

Цитата Medic84
Только не вижу смысла в создании других MSgBox и FileOpenDialog Объясните зачем, а? »

Они не другие ... FileOpenDialog это взято из "Инклюды" WinAPI.au3
_WinAPI_GetOpenFileName() строка ~3101

Да только непонятно зачем это #include <WinAPI.au3> =)

Diamond 29-08-2009 14:42 1206328

Да, думаю действитель лучше убрать их (лишние запчасти) а про штатный MsgBox я совсем забыл что там последний параметр это hwnd окна. - Сегодня исправлю.
Цитата:

Цитата FlatX007
Да только непонятно зачем это #include <WinAPI.au3> »

Так ведь из неё используются функции для чтения и записи в файл.

Diamond 29-08-2009 16:31 1206419

Поправил кое-что, теперь пакетный файл будет создаваться в DOS кодировке, это позволит избежать проблем с кириллицей в именах файлов.

SyDr 29-08-2009 16:46 1206441

Цитата:

Цитата Diamond
Так ведь из неё используются функции для чтения и записи в файл. »

А стандартные возможности языка не подходят?


И ещё, почему бы не создавать папку, вместо того, чтобы писать, что она не сущесвтует. И почему она обязательно должна ыбть пуста?

Diamond 29-08-2009 17:08 1206458

Цитата:

Цитата SyDr
А стандартные возможности языка не подходят? »

А там в любом случае есть необходимость в использовании функции _WinAPI_SetFilePointer, а штатной замены насколько я знаю для неё нет.
Цитата:

Цитата SyDr
И ещё, почему бы не создавать папку, вместо того, чтобы писать, что она не сущесвтует. »

Спасибо. Хороший совет. :)
Цитата:

Цитата SyDr
И почему она обязательно должна ыбть пуста? »

Туда может копироваться большое количество частей, я думаю, так будет удобней в ней ориентироваться. Кроме того я не могу гарантировать корректную работу пакетного файла поскольку в нём используются подставные символы. В общем, чем меньше в папке посторонних файлов тем надёжнее.

SyDr 29-08-2009 18:34 1206520

Цитата:

Цитата Diamond
А там в любом случае есть необходимость в использовании функции _WinAPI_SetFilePointer, а штатной замены насколько я знаю для неё нет. »

Код:

3.3.1.0 (20th May, 2009) (Beta)
- Added #135: FileSetPos(), FileGetPos() functions for moving the file pointer around.

В бетах уже есть :)

Iska 30-08-2009 06:33 1206825

Diamond, есть одно замечание: желательно упомянуть, что слияние отдельных частей созданным пакетным файлом желательно производить на разделе с файловой системой NTFS, поскольку именно там записи о файлах в MFT хранятся в упорядоченном виде, именно в таком порядке они будут возвращаться по FindFirst/NextFile и обрабатываться командой «copy …».

На файловых системах FATxx (FAT16/FAT32; насчёт FAT12, полагаю, озадачиваться сим бессмысленно даже теоретически :), на exFat не проверялось) обработка будет идти в порядке создания записей файлов в каталоге, что может привести к неверным результатам слияния на разделах с этими файловыми системами.

Вероятность этого исчезающе мала, но, тем не менее, существует.

Diamond 30-08-2009 13:53 1206997

Iska, У меня на FAT32 объединяются без проблем, на NTFS проверить не могу(диск полностью "забит")

Изначально создаваемый пакетный файл не использовал подставные символы т.е. каждое имя части указывалось точно(part1+part2+... и т.д.), но в этом случае может возникнуть ошибка из-за ограничения на длину строки в пакетном файле. Ещё у меня возникала мысль использовать цикл "For" (в пакетном файле) чтобы обойти это ограничение но я отказался от этой идеи.

Iska 30-08-2009 21:48 1207444

Цитата:

Цитата Diamond
Изначально создаваемый пакетный файл не использовал подставные символы т.е. каждое имя части указывалось точно(part1+part2+... и т.д.), но в этом случае может возникнуть ошибка из-за ограничения на длину строки в пакетном файле. Ещё у меня возникала мысль использовать цикл "For" (в пакетном файле) чтобы обойти это ограничение но я отказался от этой идеи.

Один-в-один :), коллега. С тем исключением, что тот самый пример из справки («copy /b *.exe combin.exe ») я обошёл своим вниманием по вышеупомянутой причине.

Вы отказались, а я сделал как раз с «FOR /L», и результат оказался весьма удручающ (на больших объёмах/количествах частей время росло в геометрической прогрессии, хотя, конечно, можно было и тут несколько сократить время, используя слияние попарно, но я этого не стал делать).

Цитата:

Iska, У меня на FAT32 объединяются без проблем, на NTFS…
Вот пример, на котором будет отчётливо видна существующая разница в порядке слияния между FAT и NTFS (для этого копируемые файлы в примере создаются в порядке, отличном от нарастающего):

читать дальше »
Код:

@echo off

md ".\Test"
cd ".\Test"

echo 03>03.txt
echo 02>02.txt
echo 01>01.txt

copy *.txt result.dat
type result.dat

del result.dat
del *.txt

cd ".."
rd ".\Test"

Такое может возникнуть, например, при переносе частей на флэшке с машины на машину в раздел FAT в случайном (не нарастающем) порядке.

В принципе, я думаю, что можно (и нужно) побороться и с FAT, например, предварительно сделав перенос отдельных частей в правильном порядке во временную папку (хотя бы тем же «FOR /L»), а затем уже безболезненно выполняя «copy /b *.parts …». Во всяком случае, я у себя именно так попробую и сделать, что-то наподобие:

Код:

@echo off
setlocal enableextensions enabledelayedexpansion

md ".\Test"
cd ".\Test"

echo 03>03.txt
echo 02>02.txt
echo 01>01.txt


set strTempFolder=$temp%random%
md ".\%strTempFolder%"

for /l %%i in (1,1,99) do (
    set number=00%%i
    set strPartFile=!number:~-2!.txt

    if exist "!strPartFile!" (
        move "!strPartFile!" ".\%strTempFolder%\!strPartFile!">nul
    )
)

copy /b ".\%strTempFolder%\*.txt" "result2.dat"
move ".\%strTempFolder%\*.txt" ".">nul
rd /s /q ".\%strTempFolder%"
echo [result2.dat] & type "result2.dat"

или, например, получая гарантированно отсортированный список файлов-частей посредством «dir /b /o:n ??.txt», или попросту задавая хранение списка частей в самом пакетном файле при его создании, и лишь потом делая шаманство «move в папку-combine-move назад».

В любом случае — отдельное спасибо: как бы то ни было, а Вы своим скриптом помогли мне осознать своё искреннее заблуждение в этом вопросе (что делать «copy /b *.parts …» всё-таки можно, выполнив предварительно некоторые телодвижения).

Diamond 31-08-2009 09:29 1207714

Iska, Спасибо за подробное разъяснение ошибки слияния!
Отдельное спасибо за рабочий пример, имхо, главная привлекательность этой идеи, как раз и заключается в использовании пакетного файла.
+5

eacl 03-09-2012 17:08 1982246

Люди подскажите а как таким же с помобом с помощью bat разбить файл на несколько маленьких, после определенного слова например конец?
Очень нужно

STARSsoft 14-05-2013 10:25 2149754

Есть большой HTML файл. Одного текста только почти на 30 мегабайт. Разные части текста в файле разделены тегами-комментариями, например <!-- Post --> тут сам текст <!-- # Post -->.
Нужно каждую часть между этими тегами сохранить в отдельный файл. Имя файла не имеет значения, можно даже просто цифрами по порядку.
Сидеть и копипастить уже заморился, а таких файлов несколько.

AZJIO 14-05-2013 19:28 2150130

STARSsoft,

FileOperations.au3

Код:

#include <FileOperations.au3>

$sPath = @ScriptDir & '\index.htm'
$sText = FileRead($sPath)
$aSplit = _StringSplitRegExp($sText, '<!-- Post -->.*?<!-- # Post -->')
For $i = 1 To $aSplit[0]
    $sPathNew = _FO_GetCopyName($sPath, 1)
    $hFile = FileOpen($sPathNew, 2)
    FileWrite($hFile, $aSplit[$i])
    FileClose($hFile)
Next

; http://www.autoitscript.com/forum/topic/139260-autoit-snippets/?p=1065198
; http://www.autoitscript.com/forum/topic/139260-autoit-snippets/page__st__140#entry1036931
; http://www.autoitscript.com/forum/topic/65662-stringsplitregexp/
; Автор ..........: AZJIO

Func _StringSplitRegExp($sString, $sPattern, $flag = 0, $sIncludeMatch = 0, $iCount = 0)
    Local $sSplit, $sDelim, $sReplace, $Toggle, $iPos = 1
    If IsBinary($sString) Then
        $sString = Hex($sString)
        $sDelim = Chr(1)
        Local $aError[2] = [1, $sString]
    Else
        Local
$aError[2] = [1, $sString]
        For $i = 1 To 30
            $Toggle = Not $Toggle
            If $Toggle Then ; 1, 30, 3, 28    ...  27, 4, 29, 2
                $sDelim &= Chr($i)
            Else
                $sDelim &= Chr(32 - $i)
            EndIf
            $iPos = StringInStr($sString, $sDelim, 1, 1, $iPos) ; смещение позволяет найти разделитель за 1 проход
            If Not $iPos Then ExitLoop ; если вхождение не найдено, то разделитель сформирован
        Next
        If
$iPos Then Return SetError(1, 0, $aError)
    EndIf
    Switch
$sIncludeMatch
        Case 0
            $sReplace = $sDelim
        Case 1
            $sReplace = "$0" & $sDelim
        Case 2
            $sReplace = $sDelim & "$0"
    EndSwitch
    $sSplit = StringRegExpReplace($sString, $sPattern, $sReplace, $iCount)
    If @error Then Return SetError(2, @extended, $aError)
    If Not @extended Then Return SetError(3, 0, $aError)
    If $flag Then $flag = 2
    Return StringSplit($sSplit, $sDelim, 1 + $flag)
EndFunc  ;==>_StringSplitRegExp


AZJIO 17-05-2013 01:42 2151346

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

Код:

#include <FileOperations.au3>
$sPath = @ScriptDir & '\index.htm'
$vText = FileRead($sPath)
$vText=StringRegExp($vText, '<!-- Post -->(.*?)<!-- # Post -->', 3)
For $i = 0 To UBound($vText)-1
    $sPathNew = _FO_GetCopyName($sPath, 1)
    $hFile = FileOpen($sPathNew, 2)
    FileWrite($hFile, $vText[$i])
    FileClose($hFile)
Next



Время: 12:25.

Время: 12:25.
© OSzone.net 2001-