如何在没有外部软件的情况下使用批处理脚本将二进制文件拆分为一定大小的块?

How can I split a binary file into chunks with certain size with batch script without external software?

想要将文件拆分成块的原因有很多 - 主要是为了网络传输(例如电子邮件附件),但我确信有些情况可能需要我无法想象的事情。 那么如何将一个文件拆分成可以轻松 assembled 回到原始文件(包括非 windows 系统)的块?

有哪些可能性:

  1. MAKECAB - 内置 Windows 归档器 - 它可以压缩文件和 拆分它,但会很难 assemble 非 Windows 上的文件 机.
  2. WSH/Jscript/VBscript - 二进制文件可以很容易地用 ADODB 处理 Streams.And 根据我的说法,JScript 更可取。
  3. .NET/JScript.net/VB/C# - 因为 .NET 带有命令行编译器 它也可以用于此目的。 .NET 二进制流可能 在这种情况下,它可以提供最好的。
  4. CERTUTIL - 因为它可以将二进制文件转换为 HEX 并返回它 可以使用批处理文件处理 HEX 数据并拆分文件 成块。

可能也可以使用 GZipStreams,因为它们允许 读取字节(并提供便携式压缩!)。如果有人成功了(或 提供任何其他方法):-)

所有脚本的语法都是相同的 - 要拆分的文件和以字节为单位的大小。

1) MAKECAB - 主要限制是在 Unix/Mac machines.For unix 上的使用最终 cabextract 或 7zip 可以使用,但是我不确定它是否可以处理拆分的 CAB 文件。即使在 windows 上,EXPAND 命令也无法做到这一点 并且应该使用 EXTRAC32(命令在帮助消息中给出)(或 Shell.Application

;@echo off

;;;;; rem start of the batch part  ;;;;;
;; 
;; 
;; rem the first parameter is the file you want to split  the second is the size in bytes.
;; rem size is not guaranteed but will be not overflown 
;; 
; if "%~2" equ "" (
; call :helpmessage
; exit /b 1 
;)
;if not exist "%~dpnx1" (
; call :helpmessage
; exit /b 2
;)
;if exist  "%~dpnx1\" (
; call :helpmessage
; exit /b 3
;)
; rem remove leading zeroes
; cmd /c exit /b %~2
; set /a size=%errorlevel%
; if %size% equ 0 (
; echo size must be greater than 0
; exit /b 4
;)
; rem MaxDiskSize must be multiple of 512 and closest possible to desired size.
;if %~2 LSS 512 set diskSize=512 else (
; set /a part=%~2%%512
; set /a diskSize=%~2-part
;)
;makecab /d the_file="%~1" /d diskSize=%diskSize% /d the_size="%~2" /F "%~dpfnxs0"
;exit /b %errorlevel%
;:helpmessage
; echo no existing file has been passed
; echo usage [split a file to cab parts with given size]:
; echo %~nx0 file size
; echo(
; echo size must be greater than 0
; echo (
; echo for extraction use :
; echo extrac32 /a /e file.ext_part1.cab /l .
; exit /b 0

;;
;;;; rem end of the batch part ;;;;;

;;;; directives part ;;;;;
;;
.New Cabinet
.set GenerateInf=OFF
.Set Cabinet=on
.Set Compress=on
.Set MaxDiskSize=%diskSize%;
.Set MaxCabinetSize=%the_size%
.set CabinetFileCountThreshold=1
.set CompressedFileExtensionChar=_
.Set CabinetNameTemplate=%the_file%_part*.cab
.set DestinationDir=.
.Set DiskDirectoryTemplate=; 

.set RptFileName=nul
.set UniqueFiles=ON
;.set DoNotCopyFiles=ON
;.set MaxDiskFileCount=1
.set MaxErrors=1
.set GenerateInf=OFF
%the_file% /inf=no
;;
;;;; end of directives part ;;;;;

--所有其他方法都是直接拆分,文件可以 assembled 以正确的顺序将它们附加到彼此 --

2) JScript - 必须以 .bat 扩展名保存的混合文件

     @if (@x)==(@y) @end /***** jscript comment ******
         @echo off
         cscript //E:JScript //nologo "%~f0" "%~nx0" %* 
         exit /b %errorlevel%

     @if (@x)==(@y) @end ******  end comment *********/

     //https://github.com/npocmaka/batch.scripts/blob/master/hybrids/jscript/zipjs.bat

    var FileSystemObj = new ActiveXObject("Scripting.FileSystemObject");
    var AdoDBObj = new ActiveXObject("ADODB.Stream");

    var ARGS = WScript.Arguments;
    var scriptName=ARGS.Item(0);

    if (ARGS.length <3) {
        WScript.Echo("Wrong arguments");
        WScript.Echo("usage:");
        WScript.Echo(scriptName +"file_to_split size_in_bytes");
        WScript.Quit(1);
    }
    var file=ARGS.Item(1);
    var max_size=parseInt(ARGS.Item(2));

    function getSize(file){
        return FileSystemObj.getFile(file).size;
    }

    function isExist(file){
        return FileSystemObj.FileExists(file);
    }

    function writeFile(fileName,data ){
        AdoDBObj.Type = 1;       
        AdoDBObj.Open();
        AdoDBObj.Position=0;
        AdoDBObj.Write(data);
        AdoDBObj.SaveToFile(fileName,2);
        AdoDBObj.Close();   
    }

    function readFile(fileName,size,position){
        AdoDBObj.Type = 1; 
        AdoDBObj.Open();
        AdoDBObj.LoadFromFile(fileName);
        AdoDBObj.Position=position;
        fileBytes=AdoDBObj.Read(size);
        AdoDBObj.Close();
        return fileBytes;


    }

    function chunker(file,size){
        var part=0;
        var position=0;
        var buffer=readFile(file,size,0);
        file_size=getSize(file);
        while (buffer !== null ) {
            part++;
            WScript.Echo("Creating: "+file+".part."+part);
            writeFile(file+".part."+part,buffer);
            if (size*part <= file_size) {
                position=size*part;
            } else {
                position=file_size;
            }
            buffer=readFile(file,size,position);
        }
    }

    if (!isExist(file)){
        WScript.Echo(file+" does not exist");
        WScript.Quit(2);
    }

    if(max_size<=0){
        WScript.Echo("Size must be bigger than 0.")
        WScript.Quit(3);
    }

    chunker(file,max_size);

3) JScript.net - 自编译的混合体,必须以 .bat 扩展名保存。

@if (@X)==(@Y) @end /* JScript comment
@echo off
setlocal

for /f "tokens=* delims=" %%v in ('dir /b /s /a:-d  /o:-n "%SystemRoot%\Microsoft.NET\Framework\*jsc.exe"') do (
   set "jsc=%%v"
)

::if not exist "%~n0.exe" (
    "%jsc%" /nologo /out:"%~n0.exe" "%~dpsfnx0"
::)

 %~n0.exe  %*

endlocal & exit /b %errorlevel%

*/

import System;
import System.IO;

var arguments:String[] = Environment.GetCommandLineArgs();

if (arguments.length<3){
    Console.WriteLine("Wrong arguments");
    Console.WriteLine("usage:");
    Console.WriteLine(arguments[0]+"file_to_split size_in_bytes");
    Environment.Exit(1);
}

var file=arguments[1];
var max_size=parseInt(arguments[2]);

if (max_size<=0){
    Console.WriteLine("size must be bigger than zero");
    Environment.Exit(2);
}

if (!File.Exists(file)){
    Console.WriteLine("file"+file+" does not exist");
    Environment.Exit(3);
}

function writeData(file,data:byte[]){
    Console.WriteLine(data.Length);
    var writer = new BinaryWriter(File.Open(file, FileMode.Create));
    writer.Write(data);
    writer.Close();
}

function  chunker(inputFile, chunkSize){

    var part=0;
    var reader= new BinaryReader(File.Open(inputFile, FileMode.Open));
    var data:byte[]=reader.ReadBytes(chunkSize);

    while(reader.BaseStream.Position !== reader.BaseStream.Length) {
        part++;
        Console.WriteLine("Processing part " + part);
        writeData(inputFile+".part."+part,data);
        data=reader.ReadBytes(chunkSize);

    }
    if (data.Length !== 0) {
        part++;
        Console.WriteLine("Processing part " + part)
        writeData(inputFile+".part."+part,data);    
    }
    reader.Close();
}

chunker(file,max_size);

4) CERTUTIL - 更像是一个实验性的东西 - 它更慢,因为缓冲区被限制为字符串可以有 8,1** 个字符的最大长度并且是文本处理:

@echo off

setlocal enableExtensions
rem :-----------------------------------------
rem : check if should prompt the help message
rem :-----------------------------------------
if "%~2" equ "" goto :help
for %%H in (/h -h /help -help) do (
    if /I "%~1" equ "%%H" goto :help
)
if not exist "%~1" echo file does not exist & exit /b 1


rem :-----------------------------------------
rem : validate input
rem :-----------------------------------------
set /a size=%~2
if not defined size echo something wrong with size parameter & exit /b 2
if %size%0 LSS 00 echo not a valid number passed as a parameter & exit /b 3

rem : -- two hex symbols and an empty space are 1 byte
rem : -- so the sum of all hex symbols
rem : -- per part should be doubled
set /a len=%size%*2
set "file=%~dfn1"

for %%F in ("%file%") do set fn=%%~nxF

rem : -- clear temp data
del /F /Q "%temp%\file" >nul 2>&1
del /F /Q  "%temp%\fn.p.*" >nul 2>&1
certutil -encodehex -f "%file%" "%temp%\file" >nul
set "part=1"

setlocal enableDelayedExpansion
        set "hex_str="
        set hex_len=0
        break>%temp%\fn.p.!part!

rem : -- reads the hex encoded file
rem : -- and make it on a parts that will
rem : -- decoded with certutil

rem :-- the delimitier is <tab> wich separates
rem :-- line number from the rest of the information
rem :-- in the hex file
rem :---------------------------- v <tab>
for /f "usebackq tokens=2 delims=   " %%A in ("%temp%\file") do (
        set "line=%%A"
        rem : -- there's a double space in the middle of the line
        rem :-- so here the line is get
        set hex_str=!hex_str!!line:~0,48!
        rem echo hex_str !hex_str!
        rem :-- empty spaces are cleared
        set hex_str=!hex_str: =!
        rem echo hex_str !hex_str! 
        rem :-- the length of the hex line 32
        set /a hex_len=hex_len+32

        rem : -- len/size is reached
        rem : -- and the content is printed to a hex file
        if !hex_len! GEQ !len! (
            echo  !hex_len! GEQ !len!
                set /a rest=hex_len-len
                for %%A in (!rest!) do (
                        (echo(!hex_str:~0,-%%A!)>>%temp%\fn.p.!part!
                        rem : -- the rest of the content of the line is saved
                        set hex_str=!hex_str:~-%%A!
                        set /a hex_len=rest
                        echo !hex_len!
                )
                certutil -decodehex -f %temp%\fn.p.!part! %fn%.part.!part! >nul
                echo -- !part!th part created --
                rem :-- preprarin next hex file
                set /a part=part+1
                break>%temp%\fn.p.!part!
                rem :-- reinitilization of the len/size of the file part
                set /a len=%size%*2
        )
        rem : -- a buffer that not allows to
        rem : -- to enter too long commmands
        rem : -- used to reduce disk operations
        if !hex_len! GEQ 7800 (
                (echo(!hex_str!)>>%temp%\fn.p.!part!
                set "hex_str="
                set hex_len=0
                rem :-- the size that need to be reached is reduces
                rem :-- as there's alredy part of the part of the file
                rem :-- added to the hex file
        set /a len=!len!-7800
                if !len! LSS 0 set len=0

        )

)
rem : -- adding the rest of the file
echo !hex_str!>>%temp%\fn.p.!part!
certutil -decodehex -f %temp%\fn.p.!part! %fn%.part.!part! >nul
echo -- !part!th part created --

rem : -- clear created temp data
rem del /F /Q  %temp%\fn.p.* >nul 2>&1
rem del /F /Q  %temp%\file >nul 2>&1
endlocal
endlocal

goto :eof
rem :-----------------------------------------
rem : help message
rem :-----------------------------------------

:help
echo\
echo Splits a file on parts by given size in bytes in 'pure' batch.
echo\
echo\
echo    %0 file size
echo\
echo\

这里有一个脚本可以 assemble 使用后三种方法分割文件:

@echo off
if "%~1" EQU "" echo parameter not entered & exit /b 1
set "parts=%~1.part"

setlocal enableDelayedExpansion
set numb=0
for /f "delims=." %%P in ('dir /b %parts%*') do (
    set /a numb=numb+1
)
rem echo !numb!


setlocal enableDelayedExpansion
set "string=%~1.part.1"
for /l %%n in (2;1;!numb!) do (
    set "string=!string!+!parts!.%%n"
)
rem echo !string!
copy /y /b !string! %~1%~x1
endlocal

下面的示例将拆分一个文件,生成多个输出文件,所有文件都小于提供的 maxChunkSize。要重新组装,您可以使用 copy /b.

SplitFile.cs
(用c:\Windows\Microsoft.NET\Framework\v2.0.50727\csc /out:splitFile.exe SplitFile.cs编译)

using System;
using System.IO;

namespace SplitFile
{
    class Program
    {
        static void Main(string[] args)
        {
            long maxChunkSize;
            if (args.Length != 3 || !long.TryParse(args[2], out maxChunkSize) || maxChunkSize <= 81920)
            {
                Console.WriteLine("Usage: splitfile.exe inputFile outputprefix maxchunksize");
                Console.WriteLine(" inputfile:     File to split");
                Console.WriteLine(" outputprefix:  Prefix to use for the output name");
                Console.WriteLine("                Ex: out -> { out0001.bin, out0002.bin }");
                Console.WriteLine(" maxchunksize:  Maximum number of bytes in each file");
                Console.WriteLine("                Note: this is the maximum size, not an exact size");
                Console.WriteLine("                Note: chunk size cannot be smaller than 81920 bytes");
                return;
            }

            string inputFilePath = args[0];
            string outputFilePathFormat = string.Format("{0}{{0:0000}}.bin", args[1]);

            using (Stream fsInput = File.Open(inputFilePath, FileMode.Open, FileAccess.Read, FileShare.Read))
            {
                byte[] buffer = new byte[81920 /* default from System.Stream */];
                int cOutFileNo = 0;
                Stream destination = getOutputFile(ref cOutFileNo, outputFilePathFormat);
                try
                {
                    int read;
                    while ((read = fsInput.Read(buffer, 0, buffer.Length)) != 0)
                    {
                        if (destination.Length + read > maxChunkSize)
                        {
                            destination.Dispose();
                            destination = getOutputFile(ref cOutFileNo, outputFilePathFormat);
                        }

                        destination.Write(buffer, 0, read);
                    }
                }
                finally
                {
                    destination.Dispose();
                }
            }
        }

        private static Stream getOutputFile(ref int cOutFileNo, string outFileFormat)
        {
            string filename = string.Format(outFileFormat, cOutFileNo);
            cOutFileNo++;

            return File.Open(filename, FileMode.CreateNew, FileAccess.Write, FileShare.None);
        }
    }
}

使用示例:

C:\drop>splitFile.exe ubuntu-rescue-remix-12-04.iso Ubuntu_Split_ 10485760
C:\drop>dir
01/29/2015  17:21       244,570,112 ubuntu-rescue-remix-12-04.iso
01/30/2015  15:27        10,485,760 Ubuntu_Split_0000.bin
01/30/2015  15:27        10,485,760 Ubuntu_Split_0001.bin
01/30/2015  15:27        10,485,760 Ubuntu_Split_0002.bin
01/30/2015  15:27        10,485,760 Ubuntu_Split_0003.bin
01/30/2015  15:27        10,485,760 Ubuntu_Split_0004.bin
01/30/2015  15:27        10,485,760 Ubuntu_Split_0005.bin
01/30/2015  15:27        10,485,760 Ubuntu_Split_0006.bin
01/30/2015  15:27        10,485,760 Ubuntu_Split_0007.bin
01/30/2015  15:27        10,485,760 Ubuntu_Split_0008.bin
01/30/2015  15:27        10,485,760 Ubuntu_Split_0009.bin
01/30/2015  15:27        10,485,760 Ubuntu_Split_0010.bin
01/30/2015  15:27        10,485,760 Ubuntu_Split_0011.bin
01/30/2015  15:27        10,485,760 Ubuntu_Split_0012.bin
01/30/2015  15:27        10,485,760 Ubuntu_Split_0013.bin
01/30/2015  15:27        10,485,760 Ubuntu_Split_0014.bin
01/30/2015  15:27        10,485,760 Ubuntu_Split_0015.bin
01/30/2015  15:27        10,485,760 Ubuntu_Split_0016.bin
01/30/2015  15:27        10,485,760 Ubuntu_Split_0017.bin
01/30/2015  15:27        10,485,760 Ubuntu_Split_0018.bin
01/30/2015  15:27        10,485,760 Ubuntu_Split_0019.bin
01/30/2015  15:27        10,485,760 Ubuntu_Split_0020.bin
01/30/2015  15:27        10,485,760 Ubuntu_Split_0021.bin
01/30/2015  15:27        10,485,760 Ubuntu_Split_0022.bin
01/30/2015  15:27         3,397,632 Ubuntu_Split_0023.bin

C:\drop>copy /b Ubuntu_Split_* Ubuntu_recombined.iso
Ubuntu_Split_0000.bin
Ubuntu_Split_0001.bin
Ubuntu_Split_0002.bin
Ubuntu_Split_0003.bin
Ubuntu_Split_0004.bin
Ubuntu_Split_0005.bin
Ubuntu_Split_0006.bin
Ubuntu_Split_0007.bin
Ubuntu_Split_0008.bin
Ubuntu_Split_0009.bin
Ubuntu_Split_0010.bin
Ubuntu_Split_0011.bin
Ubuntu_Split_0012.bin
Ubuntu_Split_0013.bin
Ubuntu_Split_0014.bin
Ubuntu_Split_0015.bin
Ubuntu_Split_0016.bin
Ubuntu_Split_0017.bin
Ubuntu_Split_0018.bin
Ubuntu_Split_0019.bin
Ubuntu_Split_0020.bin
Ubuntu_Split_0021.bin
Ubuntu_Split_0022.bin
Ubuntu_Split_0023.bin
           1 file(s) copied.

C:\drop>dir Ubuntu*.iso
01/29/2015  17:21       244,570,112 ubuntu-rescue-remix-12-04.iso
01/30/2015  15:27       244,570,112 Ubuntu_recombined.iso

C:\drop>fciv -sha1 ubuntu-rescue-remix-12-04.iso
//
// File Checksum Integrity Verifier version 2.05.
//
02403c37cbdb3e03e00f5176807a793ef63d877c ubuntu-rescue-remix-12-04.iso

C:\drop>fciv -sha1 Ubuntu_recombined.iso
//
// File Checksum Integrity Verifier version 2.05.
//
02403c37cbdb3e03e00f5176807a793ef63d877c ubuntu-rescue-remix-12-04.iso

C:\drop>

前段时间我为此编写了一个名为 BinToBat.bat 的 Batch-JScript 混合脚本。这是它的帮助屏幕:

Create an installer Batch program for data files of any type

BINTOBAT [/T:.ext1.ext2...] [/L:lineSize] [/F[:fileSize]] filename ...

  /T:.ext1.ext2    Specify the extensions of text type files that will not be
                   encoded as hexadecimal digits, but preserved as text.
  /L:lineSize      Specify the size of output lines (default: 78).
  /F[:fileSize]    /F switch specify to generate a Full installer file.
                   The optional fileSize specify the maximum output file size.

BinToBat encode the given data files as hexadecimal digits (or preserve they
as compressed text) and insert they into InstallFiles.bat program; when this
program run, it generates the original data files.

You may rename the InstallFiles.bat program as you wish, but preserving the
"Install" prefix is suggested.

You may use wild-cards in the filename list.

If the /F switch is not given, a Partial installer is created:
- You may insert a short description for each file.
- You may insert divisions in the file listing via a dash in the parameters.
- The installer allows to select which files will be downloaded and ask
  before overwrite existent files.

If the /F switch is given, a Full installer is created:
- The installer always download all files.
- You may specify commands that will be executed after the files were copied.
- You may specify the maximum size of the output file via /F:fileFize, so in
  this case the output file will be divided in parts with a numeric postfix.

  If you use /F switch you can NOT rename the InstallFiles??.bat files; the
  first one is the installer and the rest just contain data.

您可以从 this site 下载 BinToBat.bat 程序。

对于原始字节块,powershell 可以拆分文件:

set size=<split size>
set file=<file.in>

for %j in (%file%) do (
set /a chunks=%~zj/%size% >nul

for /l %i in (0,1,!chunks!) do (
set /a tail=%~zj-%i*%size% >nul
powershell gc %file% -Encoding byte -Tail !tail! ^| sc %file%_%i -Encoding byte
if %i lss !chunks! FSUTIL file seteof %file%_%i %size% >nul
)
)

然后可以通过 copy

加入这些

某些系统可能需要准备 运行 FSUTIL 并启用延迟扩展。

另一种方法提取文件的较小部分,但makecab实用程序以自己的格式对块进行编码,因此它们不能被视为原始文件字节,类似于平面二进制文件,例如。通过 copy.

与一些人在某些答案中所说的不同,拆分文件的大小不需要是 512 的倍数。但是,这些块可以通过 extrac32.

首先制作一个 ddf(文本)文件或仅通过 /d 在命令行上提供选项:

.Set CabinetNameTemplate=test_*.cab; <-- Enter chunk name format
.Set MaxDiskSize=900000; <-- Enter file split/chunk size (doesn't have to be multiple of 512)
.Set ClusterSize=1000
.Set Cabinet=on;
.Set Compress=off; <-- Optional: set compression on to save disk space
.set CompressionType=LZX;
.set CompressionMemory=21
.Set DiskDirectoryTemplate=;
file.in <-- Enter the file name to be split

然后:

makecab /f ddf.txt

要取回原始文件,请确保所有块都在同一目录中,然后依次调用第一个文件:

extrac32 test_1.cab file.out

MakeCAB 引入了文件夹的概念来指代一组连续的压缩字节。
“MakeCAB 将产品或应用程序中的所有文件进行压缩,将字节放置为一个连续的字节流,压缩整个流,根据需要将其切碎到文件夹中,然后用文件夹填充一个或多个柜子。 “

第三种方法 通过 CMD 和 certutil 分割文件:

set file="x.7z"             &REM compressed to generate CRLF pairs
set max=70000000            &REM certutil has max file limit around 74MB

REM Findstr line limit 8k
REM Workaround: wrap in 7z archive to generate CRLF pairs

for %i in (%file%) do (
set /a num=%~zi/%max% >nul      &REM No. of chunks
set /a last=%~zi%%max% >nul     &REM size of last chunk
if %last%==0 set /a num=num-1       &REM ove zero byte chunk
set size=%~zi
)

ren %file% %file%.0

for /l %i in (1 1 %num%) do (
set /a s1=%i*%max% >nul
set /a s2="(%i+1)*%max%" >nul
set /a prev=%i-1 >nul

echo Writing %file%.%i
type %file%.!prev! | (
  (for /l %j in (1 1 %max%) do pause)>nul& findstr "^"> %file%.%i)

FSUTIL file seteof %file%.!prev! %max% >nul
)
if not %last%==0 FSUTIL file seteof %file%.%num% %last% >nul
echo Done.

备注

  1. 块可以通过 copy /b
  2. 连接
  3. 可以通过填充块编号使文件扩展名更整洁
  4. 可以循环拆分整个目录

请参阅下面的示例输出:

29/04/2022  22:14        71,000,000 ATOMIC Blonde.mp4.014
29/04/2022  23:33        71,000,000 ATOMIC Blonde.mp4.015
30/04/2022  00:56        71,000,000 ATOMIC Blonde.mp4.016
30/04/2022  02:16        71,000,000 ATOMIC Blonde.mp4.017
30/04/2022  02:16        71,000,000 ATOMIC Blonde.mp4.018
30/04/2022  03:05        37,601,635 The Young Messiah.mp4.000
30/04/2022  04:25        71,000,000 The Young Messiah.mp4.001
30/04/2022  05:45        71,000,000 The Young Messiah.mp4.002
30/04/2022  07:05        71,000,000 The Young Messiah.mp4.003
30/04/2022  08:25        71,000,000 The Young Messiah.mp4.004
30/04/2022  09:59        71,000,000 The Young Messiah.mp4.005
30/04/2022  11:23        71,000,000 The Young Messiah.mp4.006
30/04/2022  12:47        71,000,000 The Young Messiah.mp4.007

在 Win 10 上测试。