Arrastre y suelte el archivo por lotes para varios archivos?

Escribí un archivo por lotes para usar PngCrush para optimizar una imagen .png cuando la arrastro y la coloco en el archivo por lotes.

En la sección siguiente, escribí sobre lo que pensé que sería una buena actualización del archivo por lotes.

Mi pregunta es: ¿ es posible crear un archivo por lotes como lo hice en la publicación, pero capaz de optimizar varias imágenes a la vez? Arrastre y suelte varios archivos .png en él? (y la salida será algo así como new.png, new (1) .png, new (2) .png, etc …

Sí, por supuesto, esto es posible. Al arrastrar varios archivos en un archivo por lotes, obtiene la lista de archivos eliminados como una lista separada por espacios. Puede verificar esto con el siguiente lote simple:

@echo %* @pause 

Ahora, tú tienes dos opciones:

  1. PngCrush ya puede manejar múltiples nombres de archivo dados a él en la línea de comando. En este caso, todo lo que tendrías que hacer sería pasar %* a PngCrush en lugar de solo %1 (como probablemente lo hagas ahora):

     @pngcrush %* 

    %* contiene todos los argumentos en el archivo por lotes, por lo que esta es una forma conveniente de pasar todos los argumentos a otro progtwig. Sin embargo, hay que tener cuidado con los archivos nombrados como opciones de PngCrush. Los geeks de UNIX sabrán ese problema 🙂

    Sin embargo, después de leer su publicación describiendo su técnica, esto no funcionará correctamente mientras está escribiendo el archivo comprimido en new.png . Una mala idea si está manejando varios archivos a la vez, ya que solo puede haber un new.png :-). Pero simplemente probé que PngCrush maneja varios archivos muy bien, así que si no te importa una actualización en el lugar de los archivos, entonces poner

     @pngcrush -reduce -brute %* 

    en su lote hará el trabajo (siguiendo su artículo original).

  2. PngCrush no manejará varios archivos o si desea escribir cada imagen en un nuevo archivo después de la compresión. En este caso, sigue con su rutina de “un archivo a la vez” pero realiza un bucle sobre los argumentos de entrada. En este caso, es más fácil crear un pequeño bucle y shift los argumentos cada vez que procesa uno:

     @echo off if [%1]==[] goto :eof :loop pngcrush -reduce -brute %1 "%~dpn1_new%~x1" shift if not [%1]==[] goto loop 

    Lo que estamos haciendo aquí es simple: primero omitimos todo el lote si se ejecuta sin argumentos, luego definimos una etiqueta para saltar a: loop . En el interior, simplemente ejecutamos PngCrush en el primer argumento, dando al archivo comprimido un nuevo nombre. Es posible que desee leer la syntax de disección de ruta que utilicé aquí en la help call . Básicamente lo que hago aquí es nombrar el archivo exactamente como antes; Solo coloco “_new” al final del nombre del archivo (antes de la extensión). %~dpn1 expande para conducir, ruta y nombre de archivo (sin extensión), mientras que %~x1 expande a la extensión, incluido el punto.

    ETA: Eep, acabo de leer el resultado deseado con new.png, new (1) .png, etc. En este caso, no necesitamos ninguna disección de ruta de fantasía, pero tenemos otros problemas que preocuparnos.

    La forma más fácil sería simplemente iniciar un contador en 0 antes de procesar el primer archivo y boostlo cada vez que procesemos otro:

     @echo off if [%1]==[] goto :eof set n=0 :loop if %n%==0 ( pngcrush -reduce -brute %1 new.png ) else ( pngcrush -reduce -brute %1 new^(%n%^).png ) shift set /a n+=1 if not [%1]==[] goto loop 

    %n% es nuestro contador aquí y manejamos el caso donde n es 0 escribiendo el resultado en new.png , en lugar de new(0).png .

    Este enfoque tiene problemas, sin embargo. Si ya hay archivos llamados new.png o new(x).png , probablemente los golpees. No está bien. Entonces, tenemos que hacer algo diferente y verificar si realmente podemos usar los nombres de los archivos:

     rem check for new.png if exist new.png (set n=1) else (set n=0 & goto loop) rem check for numbered new(x).png :checkloop if not exist new^(%n%^).png goto loop set /a n+=1 goto checkloop 

    El rest del progtwig permanece igual, incluido el ciclo normal. Pero ahora comenzamos con el primer nombre de archivo no utilizado y evitamos sobrescribir los archivos que ya están allí.

Siéntase libre de adaptarse según sea necesario.

Arrastrar y soltar de forma segura no es tan sencillo con el lote.

Tratar con %1 , shift o %* podría fallar, porque el explorador no es muy inteligente, mientras que al citar los nombres de los archivos, solo se citan los nombres de los archivos con espacios.
Pero los archivos como Cool&stuff.png no son citados por el explorador por lo que se obtiene una línea de cmd como

 pngCr.bat Cool&stuff.png 

Por lo tanto, en %1 solo está Cool incluso en %* solo es Cool , pero después de que termine el lote, cmd.exe intenta ejecutar stuff.png (y fallará).

Para manejar esto, puede acceder a los parámetros con !cmdcmdline! en lugar de %1 .. %n , y para eludir un posible error al final de la ejecución, una simple exit podría ayudar.

 @echo off setlocal ENABLEDELAYEDEXPANSION rem Take the cmd-line, remove all until the first parameter set "params=!cmdcmdline:~0,-1!" set "params=!params:*" =!" set count=0 rem Split the parameters on spaces but respect the quotes for %%G IN (!params!) do ( set /a count+=1 set "item_!count!=%%~G" rem echo !count! %%~G ) rem list the parameters for /L %%n in (1,1,!count!) DO ( echo %%n #!item_%%n!# ) pause REM ** The exit is important, so the cmd.ex doesn't try to execute commands after ampersands exit 

Por cierto. hay un límite de línea para operaciones de arrastrar y colocar de ~ 2048 caracteres, a pesar del límite de línea de lote “estándar” de ~ 8192 caracteres.
En cuanto a cada archivo, se pasa la ruta completa, este límite se puede alcanzar con pocos archivos.

 FOR %%A IN (%*) DO ( REM Now your batch file handles %%A instead of %1 REM No need to use SHIFT anymore. ECHO %%A ) 

Y para diferenciar entre archivos y carpetas descartadas, puede usar esto:

 FOR %%I IN (%*) DO ( ECHO.%%~aI | FIND "d" >NUL IF ERRORLEVEL 1 ( REM Processing Dropped Files CALL :_jobF "%%~fI" ) ELSE ( REM Processing Dropped Folders CALL :_jobD "%%~fI" ) ) 

Esta es una respuesta muy tardía. En realidad, no estaba al tanto de esta vieja pregunta y preparé una respuesta para esta similar donde hubo una discusión sobre el manejo de nombres de archivo con caracteres especiales porque el explorador solo cita nombres de archivos que contienen espacio (s). Luego, en los comentarios sobre esa pregunta, vi una referencia a este hilo, después de eso y, para mi sorpresa, me di cuenta de que jeb ya había cubierto y explicado este asunto muy bien, lo que se espera de él.

Entonces, sin más explicaciones, aportaré mi solución con el foco principal para cubrir más casos especiales en los nombres de archivo con estos caracteres ,;!^ Y también para proporcionar un mecanismo para adivinar si el archivo de proceso por lotes es lanzado directamente por el explorador o no, entonces la lógica de la moda antigua para manejar argumentos de archivo por lotes se podría usar en todos los casos.

 @echo off setlocal DisableDelayedExpansion if "%~1" EQU "/DontCheckDrapDrop" ( shift ) else ( call :IsDragDrop && ( call "%~f0" /DontCheckDrapDrop %%@*%% exit ) ) :: Process batch file arguments as you normally do setlocal EnableDelayedExpansion echo cmdcmdline=!cmdcmdline! endlocal echo, echo %%*=%* echo, if defined @* echo @*=%@*% echo, echo %%1="%~1" echo %%2="%~2" echo %%3="%~3" echo %%4="%~4" echo %%5="%~5" echo %%6="%~6" echo %%7="%~7" echo %%8="%~8" echo %%9="%~9" pause exit /b :: IsDragDrop routine :: Checks if the batch file is directly lanched through Windows Explorer :: then Processes batch file arguments which are passed by Drag'n'Drop, :: rebuilds a safe variant of the arguments list suitable to be passed and processed :: in a batch script and returns the processed args in the environment variable :: that is specified by the caller or uses @* as default variable if non is specified. :: ErrorLevel: 0 - If launched through explorer. 1 - Otherwise (Will not parse arguments) :IsDragDrop [retVar=@*] setlocal set "Esc=" set "ParentDelayIsOff=!" setlocal DisableDelayedExpansion if "%~1"=="" (set "ret=@*") else set "ret=%~1" set "Args=" set "qsub=?" :: Used for emphasis purposes set "SPACE= " setlocal EnableDelayedExpansion set "cmdline=!cmdcmdline!" set ^"ExplorerCheck=!cmdline:%SystemRoot%\system32\cmd.exe /c ^""%~f0"=!^" if "!cmdline!"=="!ExplorerCheck!" ( set ^"ExplorerCheck=!cmdline:"%SystemRoot%\system32\cmd.exe" /c ^""%~f0"=!^" if "!cmdline!"=="!ExplorerCheck!" exit /b 1 ) set "ExplorerCheck=" set ^"cmdline=!cmdline:*"%~f0"=!^" set "cmdline=!cmdline:~0,-1!" if defined cmdline ( if not defined ParentDelayIsOff ( if "!cmdline!" NEQ "!cmdline:*!=!" set "Esc=1" ) set ^"cmdline=!cmdline:"=%qsub%!" ) ( endlocal & set "Esc=%Esc%" for /F "tokens=*" %%A in ("%SPACE% %cmdline%") do ( set "cmdline=%%A" ) ) if not defined cmdline endlocal & endlocal & set "%ret%=" & exit /b 0 :IsDragDrop.ParseArgs if "%cmdline:~0,1%"=="%qsub%" (set "dlm=%qsub%") else set "dlm= " :: Using '%%?' as FOR /F variable to not mess with the file names that contain '%' for /F "delims=%dlm%" %%? in ("%cmdline%") do ( set ^"Args=%Args% "%%?"^" setlocal EnableDelayedExpansion set "cmdline=!cmdline:*%dlm: =%%%?%dlm: =%=!" ) ( endlocal for /F "tokens=*" %%A in ("%SPACE% %cmdline%") do ( set "cmdline=%%A" ) ) if defined cmdline goto :IsDragDrop.ParseArgs if defined Esc ( set ^"Args=%Args:^=^^%^" ) if defined Esc ( set ^"Args=%Args:!=^!%^" ) ( endlocal & endlocal set ^"%ret%=%Args%^" exit /b 0 ) 

OUTPUT con archivos de muestra arrastrados y soltados en el archivo por lotes:

 cmdcmdline=C:\Windows\system32\cmd.exe /c ""Q:\DragDrop\DragDrop.cmd" Q:\DragDrop\ab.txt "Q:\DragDrop\c d.txt" Q:\DragDrop\!ab!c.txt "Q:\DragDrop\a b.txt" Q:\DragDrop\a!b.txt Q:\DragDrop\a&b.txt Q:\DragDrop\a(b&^)).txt Q:\DragDrop\a,b;c!d&e^f!!.txt Q:\DragDrop\a;b.txt" %*=/DontCheckDrapDrop "Q:\DragDrop\ab.txt" "Q:\DragDrop\c d.txt" "Q:\DragDrop\!ab!c.txt" "Q:\DragDrop\a b.txt" "Q:\DragDrop\a!b.txt" "Q:\DragDrop\a&b.txt" "Q:\DragDrop\a(b&^)).txt" "Q:\DragDrop\a,b;c!d&e^f!!.txt" "Q:\DragDrop\a;b.txt" @*= "Q:\DragDrop\ab.txt" "Q:\DragDrop\c d.txt" "Q:\DragDrop\!ab!c.txt" "Q:\DragDrop\a b.txt" "Q:\DragDrop\a!b.txt" "Q:\DragDrop\a&b.txt" "Q:\DragDrop\a(b&^)).txt" "Q:\DragDrop\a,b;c!d&e^f!!.txt" "Q:\DragDrop\a;b.txt" %1="Q:\DragDrop\ab.txt" %2="Q:\DragDrop\c d.txt" %3="Q:\DragDrop\!ab!c.txt" %4="Q:\DragDrop\a b.txt" %5="Q:\DragDrop\a!b.txt" %6="Q:\DragDrop\a&b.txt" %7="Q:\DragDrop\a(b&^)).txt" %8="Q:\DragDrop\a,b;c!d&e^f!!.txt" %9="Q:\DragDrop\a;b.txt" 

En :IsDragDrop rutina :IsDragDrop Intenté especialmente minimizar las suposiciones sobre el formato de línea de comandos y el espaciado entre los argumentos. La detección (suposición) para el inicio del explorador se basa en esta línea de comando firma %SystemRoot%\system32\cmd.exe /c ""FullPathToBatchFile" Arguments"

Por lo tanto, es muy posible engañar al código para que piense que se ha lanzado haciendo doble clic desde el explorador o arrastrando y soltando, y eso no es un problema, y ​​el archivo por lotes funcionará normalmente.

Pero con esta firma particular no es posible lanzar intencionalmente el archivo por lotes de esta manera: %SystemRoot%\system32\cmd.exe /c ""FullPathToBatchFile" Arguments & SomeOtherCommand" y esperamos que SomeOtherCommand se ejecute, en su lugar se fusionará en los argumentos del archivo por lotes.

No necesita un script por lotes para optimizar varios PNG, todo lo que necesita es el comodín:

 pngcrush -d "crushed" *.png 

Eso atrapará todos los PNG en el directorio actual y los moverá a un subdirector llamado “aplastado”. Agregaría la bandera de -brute para afeitar algunos bytes más.

 pngcrush -d "crushed" -brute *.png 

Estoy publicando esto porque no parece estar bien documentado o es ampliamente conocido, y porque a la larga puede ser más fácil que escribir y mantener un archivo por lotes arrastrado y soltado.