如何使用batch file压缩(/ zip)和解压缩(/ unzip)文件和文件夹而不使用任何外部工具?

我知道类似的问题在这里被问到很多,但是我对答案(甚至是问题)并不完全满意。

主要目标是兼容性 – 它应该适用于尽可能广泛的Windows机器(包括XP,Vista,Win2003 – 共同拥有20%左右的Windows共享)和生产的文件应该可以在Unix / Mac机上使用(如此标准归档/压缩格式是最好的)。

有什么select:

  1. 创build一个实现一些zipalgorithm的批处理。 显然这是可能的 – 但只有单个文件,并使用CERTUTIL进行二进制处理(有些机器默认情况下没有CERTUTIL,不能在WinXP Home Edition上安装)
  2. 通过WSH使用shell.application。根据我的最佳select。它允许压缩整个目录,并可在每个Windows计算机上使用
  3. Makecab – 尽pipe它的压缩不是那么便携,它可以在每台Windows机器上使用。一些像7zip这样的外部程序能够提取.CAB内容,但是在Unix / Mac上需要使用文件时,它不会很方便。一个文件是非常简单的,保留目录结构需要更多的努力。
  4. 使用.NET Framework – 不太好的select.Form .NET 2.0有GZipStream,但它只允许压缩单个文件。 .NET 4.5具有Zipfunction,但在Vista和XP上不支持.NET。在XP和Win2003上,.NET并没有默认安装,但由于.NET 2.0的可能性高达4.0, 。
  5. Powershell – 因为它依赖于.NET它具有相同的function。它不是默认安装在XP,2003和Vista上,所以我会跳过它。

这里是答案:

1.使用“纯”批处理脚本压缩/解压缩文件。

这可能要归功于Frank Westlake的ZIP.CMD和UNZIP.CMD (需要pipe理员权限,并且需要FSUTILCERTUTIL )。对于Win2003和WinXP,需要安装CERTUTIL 2003 Admin Tool Pack 。 因为ZIP.CMD语法是反向的,所以要小心:

 ZIP.CMD destination.zip source.file 

而且它只能压缩单个文件。

2.使用Shell.Application

我花了一些时间来创build一个单一的jscript /批处理混合脚本的常见用法,拉链/解压缩文件和目录(加上更多的function)。 这是一个链接 (它变得太大,不能发布答案)。 可以直接使用.bat扩展名,不会创build任何临时文件。 我希望这个帮助信息足以描述如何使用它。

一些例子:

 // unzip content of a zip to given folder.content of the zip will be not preserved (-keep no).Destination will be not overwritten (-force no) call zipjs.bat unzip -source C:\myDir\myZip.zip -destination C:\MyDir -keep no -force no // lists content of a zip file and full paths will be printed (-flat yes) call zipjs.bat list -source C:\myZip.zip\inZipDir -flat yes // lists content of a zip file and the content will be list as a tree (-flat no) call zipjs.bat list -source C:\myZip.zip -flat no // prints uncompressed size in bytes zipjs.bat getSize -source C:\myZip.zip // zips content of folder without the folder itself call zipjs.bat zipDirItems -source C:\myDir\ -destination C:\MyZip.zip -keep yes -force no // zips file or a folder (with the folder itslelf) call zipjs.bat zipItem -source C:\myDir\myFile.txt -destination C:\MyZip.zip -keep yes -force no // unzips only part of the zip with given path inside call zipjs.bat unZipItem -source C:\myDir\myZip.zip\InzipDir\InzipFile -destination C:\OtherDir -keep no -force yes call zipjs.bat unZipItem -source C:\myDir\myZip.zip\InzipDir -destination C:\OtherDir // adds content to a zip file call zipjs.bat addToZip -source C:\some_file -destination C:\myDir\myZip.zip\InzipDir -keep no call zipjs.bat addToZip -source C:\some_file -destination C:\myDir\myZip.zip 

压缩过程中的一些已知问题:

  • 如果系统驱动器上没有足够的空间(通常是C :),那么脚本可能会产生各种错误,通常是脚本暂停。这是由于Shell.Application主动使用%TEMP%文件夹来压缩/解压缩数据。
  • 在其名称中包含unicode符号的文件夹和文件不能由Shell.Application对象处理。
  • 在Vista和更高版本中,生成的zip文件的最大支持大小约为8GB,在XP / 2003中约为2GB

脚本检测是否popup错误消息,并停止执行,并通知可能的原因。目前,我无法检测popup窗口中的文本,并给出失败的确切原因。

3. Makecab

压缩文件很容易 – makecab file.txt "file.cab" 。 最终MaxCabinetSize可能会增加。 压缩文件夹需要为每个(子)目录及其中的文件使用DestinationDir指令(具有相对path)。 这是一个脚本:

 ;@echo off ;;;;; rem start of the batch part ;;;;; ; ;for %%a in (/h /help -h -help) do ( ; if /I "%~1" equ "%%~a" if "%~2" equ "" ( ; echo compressing directory to cab file ; echo Usage: ; echo( ; echo %~nx0 "directory" "cabfile" ; echo( ; echo to uncompress use: ; echo EXPAND cabfile -F:* . ; echo( ; echo Example: ; echo( ; echo %~nx0 "c:\directory\logs" "logs" ; exit /b 0 ; ) ; ) ; ; if "%~2" EQU "" ( ; echo invalid arguments.For help use: ; echo %~nx0 /h ; exit /b 1 ;) ; ; set "dir_to_cab=%~f1" ; ; set "path_to_dir=%~pn1" ; set "dir_name=%~n1" ; set "drive_of_dir=%~d1" ; set "cab_file=%~2" ; ; if not exist %dir_to_cab%\ ( ; echo no valid directory passed ; exit /b 1 ;) ; ;break>"%tmp%\makecab.dir.ddf" ; ;setlocal enableDelayedExpansion ;for /d /r "%dir_to_cab%" %%a in (*) do ( ; ; set "_dir=%%~pna" ; set "destdir=%dir_name%!_dir:%path_to_dir%=!" ; (echo(.Set DestinationDir=!destdir!>>"%tmp%\makecab.dir.ddf") ; for %%# in ("%%a\*") do ( ; (echo("%%~f#" /inf=no>>"%tmp%\makecab.dir.ddf") ; ) ;) ;(echo(.Set DestinationDir=!dir_name!>>"%tmp%\makecab.dir.ddf") ; for %%# in ("%~f1\*") do ( ; ; (echo("%%~f#" /inf=no>>"%tmp%\makecab.dir.ddf") ; ) ;makecab /F "%~f0" /f "%tmp%\makecab.dir.ddf" /d DiskDirectory1=%cd% /d CabinetNameTemplate=%cab_file%.cab ;rem del /q /f "%tmp%\makecab.dir.ddf" ;exit /b %errorlevel% ;; ;;;; rem end of the batch part ;;;;; ;;;; directives part ;;;;; ;; .New Cabinet .set GenerateInf=OFF .Set Cabinet=ON .Set Compress=ON .Set UniqueFiles=ON .Set MaxDiskSize=1215751680; .set RptFileName=nul .set InfFileName=nul .set MaxErrors=1 ;; ;;;; end of directives part ;;;;; 

用于解压EXPAND cabfile -F:* . 可以使用。用于提取Unix的cabextract或7zip都可以使用。

4. .NET和GZipStream

我更喜欢Jscript.net,因为它允许与.bat(没有有毒的输出,没有临时文件)整齐的杂交.Jscript不允许将对象的引用传递给一个函数,所以我发现使它的工作的唯一方法是读/写文件逐字节(所以我想这不是最快的方式 – 如何缓冲读/写可以做?)再次只能用于单个文件。

 @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.Collections.Generic; import System.IO; import System.IO.Compression; function CompressFile(source,destination){ var sourceFile=File.OpenRead(source); var destinationFile=File.Create(destination); var output = new GZipStream(destinationFile,CompressionMode.Compress); Console.WriteLine("Compressing {0} to {1}.", sourceFile.Name,destinationFile.Name, false); var byteR = sourceFile.ReadByte(); while(byteR !=- 1){ output.WriteByte(byteR); byteR = sourceFile.ReadByte(); } sourceFile.Close(); output.Flush(); output.Close(); destinationFile.Close(); } function UncompressFile(source,destination){ var sourceFile=File.OpenRead(source); var destinationFile=File.Create(destination); var input = new GZipStream(sourceFile, CompressionMode.Decompress, false); Console.WriteLine("Decompressing {0} to {1}.", sourceFile.Name, destinationFile.Name); var byteR=input.ReadByte(); while(byteR !== -1){ destinationFile.WriteByte(byteR); byteR=input.ReadByte(); } destinationFile.Close(); input.Close(); } var arguments:String[] = Environment.GetCommandLineArgs(); function printHelp(){ Console.WriteLine("Compress and uncompress gzip files:"); Console.WriteLine("Compress:"); Console.WriteLine(arguments[0]+" -c source destination"); Console.WriteLine("Uncompress:"); Console.WriteLine(arguments[0]+" -u source destination"); } if (arguments.length!=4){ Console.WriteLine("Wrong arguments"); printHelp(); Environment.Exit(1); } switch (arguments[1]){ case "-c": CompressFile(arguments[2],arguments[3]); break; case "-u": UncompressFile(arguments[2],arguments[3]); break; default: Console.WriteLine("Wrong arguments"); printHelp(); Environment.Exit(1); } 

惊人的解决

makecab解决scheme有一些问题,所以这里是一个固定的版本,解决了使用空格的目录时的问题。

 ;@echo off ;;;;; rem start of the batch part ;;;;; ; ;for %%a in (/h /help -h -help) do ( ; if /I "%~1" equ "%%~a" if "%~2" equ "" ( ; echo compressing directory to cab file ; echo Usage: ; echo( ; echo %~nx0 "directory" "cabfile" ; echo( ; echo to uncompress use: ; echo EXPAND cabfile -F:* . ; echo( ; echo Example: ; echo( ; echo %~nx0 "c:\directory\logs" "logs" ; exit /b 0 ; ) ; ) ; ; if "%~2" EQU "" ( ; echo invalid arguments.For help use: ; echo %~nx0 /h ; exit /b 1 ;) ; ; set "dir_to_cab=%~f1" ; ; set "path_to_dir=%~pn1" ; set "dir_name=%~n1" ; set "drive_of_dir=%~d1" ; set "cab_file=%~2" ; ; if not exist "%dir_to_cab%\" ( ; echo no valid directory passed ; exit /b 1 ;) ; ;break>"%tmp%\makecab.dir.ddf" ; ;setlocal enableDelayedExpansion ;for /d /r "%dir_to_cab%" %%a in (*) do ( ; ; set "_dir=%%~pna" ; set "destdir=%dir_name%!_dir:%path_to_dir%=!" ; (echo(.Set DestinationDir=!destdir!>>"%tmp%\makecab.dir.ddf") ; for %%# in ("%%a\*") do ( ; (echo("%%~f#" /inf=no>>"%tmp%\makecab.dir.ddf") ; ) ;) ;(echo(.Set DestinationDir=!dir_name!>>"%tmp%\makecab.dir.ddf") ; for %%# in ("%~f1\*") do ( ; ; (echo("%%~f#" /inf=no>>"%tmp%\makecab.dir.ddf") ; ) ;makecab /F "%~f0" /f "%tmp%\makecab.dir.ddf" /d DiskDirectory1="%cd%" /d CabinetNameTemplate=%cab_file%.cab ;rem del /q /f "%tmp%\makecab.dir.ddf" ;exit /b %errorlevel% ;; ;;;; rem end of the batch part ;;;;; ;;;; directives part ;;;;; ;; .New Cabinet .set GenerateInf=OFF .Set Cabinet=ON .Set Compress=ON .Set UniqueFiles=ON .Set MaxDiskSize=1215751680; .set RptFileName=nul .set InfFileName=nul .set MaxErrors=1 ;; ;;;; end of directives part ;;;;; 

CAB.bat [input]文件夹或文件:pack | .cab或。?? _:unpack | none:打包filesfiles
还将添加一个CAB条目,右键单击SendTo菜单以便于处理
既然这个任务无缝地完成了这两项任务,那么应该比丑陋的makecab更好 – 为什么使用混合脚本,如果你写入临时文件呢?

 @echo off &echo. &set "ext=%~x1" &title CAB [%1] &rem input file or folder / 'files' folder / unpacks .cab .??_ if "_%1"=="_" if not exist "%~dp0files" echo CAB: No input and no 'files' directory to pack &goto :Exit "do nothing" if "_%1"=="_" if exist "%~dp0files" call :CabDir "%~dp0files" &goto :Exit "input = none, use 'files' directory -pack" for /f "tokens=1 delims=r-" %%I in ("%~a1") do if "_%%I"=="_d" call :CabDir "%~f1" &goto :Exit "input = dir -pack" if not "_%~x1"=="_.cab" if not "_%ext:~-1%"=="__" call :CabFile "%~f1" &goto :Exit "input = file -pack" call :CabExtract "%~f1" &goto :Exit "input = .cab or .??_ -unpack" :Exit AveYo: script will add a CAB entry to right-click -- SendTo menu if not exist "%APPDATA%\Microsoft\Windows\SendTo\CAB.bat" copy /y "%~f0" "%APPDATA%\Microsoft\Windows\SendTo\CAB.bat" >nul 2>nul ping -n 6 localhost >nul &title cmd.exe &exit /b :CabExtract %1:[.cab or .xx_] echo %1 &pushd "%~dp1" &mkdir "%~n1" >nul 2>nul &expand -R "%~1" -F:* "%~n1" &popd &goto :eof :CabFile %1:[filename] echo %1 &pushd "%~dp1" &makecab /D CompressionType=LZX /D CompressionLevel=7 /D CompressionMemory=21 "%~nx1" "%~n1.cab" &goto :eof :CabDir %1:[directory] dir /a:-D/b/s "%~1" set "ddf="%temp%\ddf"" echo/.New Cabinet>%ddf% echo/.set Cabinet=ON>>%ddf% echo/.set CabinetFileCountThreshold=0;>>%ddf% echo/.set Compress=ON>>%ddf% echo/.set CompressionType=LZX>>%ddf% echo/.set CompressionLevel=7;>>%ddf% echo/.set CompressionMemory=21;>>%ddf% echo/.set FolderFileCountThreshold=0;>>%ddf% echo/.set FolderSizeThreshold=0;>>%ddf% echo/.set GenerateInf=OFF>>%ddf% echo/.set InfFileName=nul>>%ddf% echo/.set MaxCabinetSize=0;>>%ddf% echo/.set MaxDiskFileCount=0;>>%ddf% echo/.set MaxDiskSize=0;>>%ddf% echo/.set MaxErrors=1;>>%ddf% echo/.set RptFileName=nul>>%ddf% echo/.set UniqueFiles=ON>>%ddf% setlocal enabledelayedexpansion pushd "%~dp1" for /f "tokens=* delims=" %%D in ('dir /a:-D/b/s "%~1"') do ( set "DestinationDir=%%~dpD" &set "DestinationDir=!DestinationDir:%~1=!" &set "DestinationDir=!DestinationDir:~0,-1!" echo/.Set DestinationDir=!DestinationDir!;>>%ddf% echo/"%%~fD" /inf=no;>>%ddf% ) makecab /F %ddf% /D DiskDirectory1="" /D CabinetNameTemplate=%~nx1.cab &endlocal &popd &del /q /f %ddf% &goto :eof