Войти

Показать полную графическую версию : Скрипт для разбиения файла на части


Diamond
29-08-2009, 09:00
Скрипт позволяет разбивать файл на части, которые в последствии можно объеденить с помощью пакетного
файла без нарушения внутреннего форматирования (см. команда 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
Ничё так... молодец! сам придумал ?

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

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

Diamond
29-08-2009, 14:30
Ничё так... молодец! сам придумал ? »
Спасибо! :) Код мой, а идея... где-то я уже видел нечто подобное раньше.
Только не вижу смысла в создании других MSgBox и FileOpenDialog Объясните зачем, а? »
FileOpenDialog - просто нравиться, MSgBox был нужен как дочерний, для того чтобы запретить доступ к основному Gui.

FlatX007
29-08-2009, 14:32
Только не вижу смысла в создании других MSgBox и FileOpenDialog Объясните зачем, а? »

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

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

Diamond
29-08-2009, 14:42
Да, думаю действитель лучше убрать их (лишние запчасти) а про штатный MsgBox я совсем забыл что там последний параметр это hwnd окна. - Сегодня исправлю.
Да только непонятно зачем это #include <WinAPI.au3> »
Так ведь из неё используются функции для чтения и записи в файл.

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

SyDr
29-08-2009, 16:46
Так ведь из неё используются функции для чтения и записи в файл. »
А стандартные возможности языка не подходят?


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

Diamond
29-08-2009, 17:08
А стандартные возможности языка не подходят? »
А там в любом случае есть необходимость в использовании функции _WinAPI_SetFilePointer, а штатной замены насколько я знаю для неё нет.
И ещё, почему бы не создавать папку, вместо того, чтобы писать, что она не сущесвтует. »
Спасибо. Хороший совет. :)
И почему она обязательно должна ыбть пуста? »
Туда может копироваться большое количество частей, я думаю, так будет удобней в ней ориентироваться. Кроме того я не могу гарантировать корректную работу пакетного файла поскольку в нём используются подставные символы. В общем, чем меньше в папке посторонних файлов тем надёжнее.

SyDr
29-08-2009, 18:34
А там в любом случае есть необходимость в использовании функции _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
Diamond, есть одно замечание: желательно упомянуть, что слияние отдельных частей созданным пакетным файлом желательно производить на разделе с файловой системой NTFS, поскольку именно там записи о файлах в MFT хранятся в упорядоченном виде, именно в таком порядке они будут возвращаться по FindFirst/NextFile и обрабатываться командой «copy …».

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

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

Diamond
30-08-2009, 13:53
Iska, У меня на FAT32 объединяются без проблем, на NTFS проверить не могу(диск полностью "забит")

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

Iska
30-08-2009, 21:48
Изначально создаваемый пакетный файл не использовал подставные символы т.е. каждое имя части указывалось точно(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
Iska, Спасибо за подробное разъяснение ошибки слияния!
Отдельное спасибо за рабочий пример, имхо, главная привлекательность этой идеи, как раз и заключается в использовании пакетного файла.
+5

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

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

AZJIO
14-05-2013, 19:28
STARSsoft,

FileOperations.au3 (http://www.autoitscript.com/forum/topic/133224-filesearch-foldersearch/?fromsearch=1)

#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
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




© OSzone.net 2001-2012