¿Cómo puedo comprimir (/ comprimir) y descomprimir (/ descomprimir) archivos y carpetas con archivos por lotes sin usar herramientas externas?

Sé que se hicieron muchas preguntas similares aquí, pero no estoy completamente satisfecho con las respuestas (e incluso con las preguntas).

El objective principal es la compatibilidad: debe ser aplicable a la gama más amplia posible de máquinas Windows (incluidas XP, Vista y Win2003, que en conjunto aún conservan el 20% de las ventanas compartidas) y los archivos producidos deben poder utilizarse en máquinas Unix / Mac (estándar formatos de archivo / compresión son preferibles).

Cuáles son las opciones:

  1. Creando un lote que implementa algún algoritmo zip. Aparentemente esto es posible, pero solo con archivos individuales y usando CERTUTIL para procesamiento binario (algunas máquinas no tienen CERTUTIL de forma predeterminada y no se pueden instalar en WinXP Home Edition)
  2. Usar shell.application a través de WSH. La mejor opción según yo. Permite comprimir directorios completos y se puede usar en todas las máquinas de Windows.
  3. Makecab – a pesar de su compresión no es tan portátil, está disponible en todas las máquinas de Windows. Algunos progtwigs externos como 7zip son capaces de extraer contenido .CAB, pero no será tan conveniente cuando los archivos deben ser utilizados en Unix / Mac. Y mientras se comprime un solo archivo es bastante sencillo, preservar la estructura del directorio requiere un poco más de esfuerzo.
  4. Usando .NET Framework – no es tan buena opción. Form .NET 2.0 existe GZipStream, pero permite la compresión solo de archivos individuales. .NET 4.5 tiene capacidades Zip pero no es compatible con Vista y XP. Y aún más: .NET no está instalado por defecto en XP y Win2003, pero como es muy probable que tenga .NET 2.0 de hasta 4.0, es una opción considerable. .
  5. Powershell: dado que depende de .NET, tiene las mismas capacidades. No está instalado por defecto en XP, 2003 y Vista, así que lo omitiré.

Y aquí está la respuesta (s):

1.Uso de script por lotes “puro” para comprimir / descomprimir archivo.

Es posible gracias a ZIP.CMD y UNZIP.CMD de Frank Westlake (necesita permisos de administrador y requiere FSUTIL y CERTUTIL ). Para Win2003 y WinXP requerirá 2003 Admin Tool Pack que instalará CERTUTIL . Tenga cuidado ya que la syntax ZIP.CMD es al revés:

 ZIP.CMD destination.zip source.file 

Y puede comprimir solo archivos individuales.

2. Uso de Shell.Application

He dedicado un tiempo a crear un único script híbrido jscript / batch para uso común que comprime / descomprime archivos y directorios (más algunas características más). Aquí hay un enlace (se volvió demasiado grande para publicar en la respuesta). Puede usarse directamente con su extensión .bat y no crea ningún archivo temporal. Espero que el mensaje de ayuda sea lo suficientemente descriptivo de cómo se puede usar.

Algunos ejemplos:

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

Algunos problemas conocidos durante la compresión:

  • si no hay suficiente espacio en la unidad del sistema (normalmente C 🙂 la secuencia de comandos podría producir varios errores, la mayoría de las veces el script se detiene. Esto se debe a Shell. La aplicación utiliza activamente la carpeta % TEMP% para comprimir / descomprimir los datos.
  • Las carpetas y archivos que contienen símbolos Unicode en sus nombres no pueden ser manejados por Shell. Objeto de aplicación.
  • El tamaño máximo soportado de los archivos comprimidos producidos es de alrededor de 8gb en Vista y superior y alrededor de 2gb en XP / 2003

El script detecta si aparece un mensaje de error y detiene la ejecución e informa por las posibles razones. En este momento no tengo manera de detectar el texto dentro de la ventana emergente y dar la razón exacta del error.

3. Makecab .

Comprimir un archivo es fácil: makecab file.txt "file.cab" . Finalmente MaxCabinetSize podría boostse. La compresión de una carpeta requiere el uso de una directiva DestinationDir (con rutas relativas) para cada (sub) directorio y los archivos dentro de. Aquí hay un script:

 ;@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 ;;;;; 

Para descompresión EXPAND cabfile -F:* . se puede utilizar.Para la extracción en Unix cabextract o 7zip se puede utilizar.

4. .NET y GZipStream

Preferí un Jscript.net ya que permite una hibridación ordenada con .bat (sin salida tóxica y sin archivos temporales) .Jscript no permite pasar una referencia de objeto a una función, así que la única forma que encontré para hacerlo funcionar es mediante leer / escribir archivos byte por byte (así que supongo que no es la manera más rápida, ¿cómo se puede hacer la lectura / escritura en búfer?) De nuevo solo se puede usar con archivos individuales.

 @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); } 

soluciones increíbles!

La solución makecab tiene algunos problemas, así que aquí hay una versión fija que resuelve el problema cuando se usan directorios con espacios en blanco.

 ;@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 [entrada] carpeta o archivo: paquete | .cab o. ?? _: desempaquetar | ninguno: empacar una subcarpeta de files
También agregará una entrada CAB para hacer clic con el botón derecho en el menú Enviar a para un fácil manejo
Como este hace ambas tareas a la perfección, debería preferirse al feo makecab, ¿por qué usar script híbrido si escribes al archivo temporal de todos modos?

 @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