Перетащите пакетный файл для нескольких файлов?

Я написал командный файл, чтобы использовать PngCrush для оптимизации изображения.png, когда я перетаскиваю его в командный файл.

В следующем разделе я написал о том, что я считаю хорошим обновлением командного файла.

Мой вопрос: возможно ли создать командный файл, как я делал в посте, но способен оптимизировать несколько изображений одновременно? Перетащите несколько файлов.png на него? (и вывод должен быть что-то вроде new.png, new(1).png, new(2).png и т. д.)

5 ответов

Решение

Да, конечно, это возможно. При перетаскивании нескольких файлов в пакетный файл вы получаете список удаленных файлов в виде списка через пробел. Вы можете проверить это с помощью простой следующей партии:

@echo %*
@pause

Теперь у вас есть два варианта:

  1. PngCrush уже может обрабатывать несколько имен файлов, заданных ему в командной строке. В этом случае все, что вам нужно сделать, это пройти %* в PngCrush вместо просто %1 (как вы, вероятно, делаете сейчас):

    @pngcrush %*
    

    %* содержит все аргументы в пакетный файл, так что это удобный способ передать все аргументы в другую программу. Осторожнее с файлами, названными как опции PngCrush. Вундеркинды UNIX будут знать эту проблему:-)

    Однако после прочтения вашего поста, описывающего вашу технику, это не будет работать должным образом, так как вы записываете сжатый файл в new.png, Плохая идея, если вы обрабатываете несколько файлов одновременно, так как может быть только один new.png:-). Но я только что попробовал, что PngCrush хорошо обрабатывает несколько файлов, поэтому, если вы не возражаете против обновления файлов на месте, тогда поставьте

    @pngcrush -reduce -brute %*
    

    в вашу партию выполнит работу (следуя вашей оригинальной статье).

  2. PngCrush не будет обрабатывать несколько файлов или вы хотите записать каждое изображение в новый файл после сжатия. В этом случае вы придерживаетесь своей процедуры "один файл за раз", но зацикливаетесь на входных аргументах. В этом случае проще всего построить небольшой цикл и shift аргументы каждый раз, когда вы обрабатываете один:

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

    То, что мы делаем здесь, очень просто: сначала мы пропускаем весь пакет, если он запускается без аргументов, а затем определяем метку, к которой нужно перейти: loop, Внутри мы просто запускаем PngCrush для первого аргумента, давая сжатому файлу новое имя. Вы можете прочитать о синтаксисе рассечения путей, который я использовал здесь help call, В основном то, что я делаю здесь, это имя файла в точности как раньше; Я просто прикрепляю "_new" к концу имени файла (до расширения). %~dpn1 расширяется на диск, путь и имя файла (без расширения), а %~x1 расширяется до расширения, включая точку.

    ETA: Eep, я просто прочитал ваш желаемый результат с помощью new.png, new(1).png и т. Д. В этом случае нам не нужны какие-либо причудливые расчеты путей, но у нас есть другие проблемы, о которых нужно заботиться.

    Самый простой способ, вероятно, состоит в том, чтобы просто запустить счетчик с 0 до того, как мы обработаем первый файл, и увеличивать его каждый раз, когда обрабатываем другой:

    @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% наш счетчик здесь, и мы обрабатываем случай, когда n 0, записав результат в new.png, вместо new(0).png,

    Однако у этого подхода есть проблемы. Если уже есть файлы с именем new.png или же new(x).png тогда вы, вероятно, забьете их. Не хорошо. Поэтому мы должны сделать что-то другое и проверить, действительно ли мы можем использовать имена файлов:

    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
    

    Остальная часть программы остается прежней, включая обычный цикл. Но теперь мы начинаем с первого неиспользуемого имени файла и избегаем перезаписи уже существующих файлов.

Не стесняйтесь адаптироваться по мере необходимости.

Делать Drag & Drop безопасным способом, не так просто с пакетной обработкой.

Имея дело с %1, shift или же %* может произойти сбой, потому что проводник не очень умен, при цитировании имен файлов приводятся только имена файлов с пробелами.
Но файлы вроде Cool&stuff.png не цитируются исследователем, поэтому вы получаете cmdline, как

pngCr.bat Cool&stuff.png

Так в %1 только Cool даже в %* только Cool, но после завершения пакета cmd.exe пытается выполнить stuff.png (и не получится).

Чтобы справиться с этим, вы можете получить доступ к параметрам с !cmdcmdline! вместо %1.. %nи обойти потенциальную ошибку в конце выполнения, простой exit мог бы помочь.

@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

Btw. есть предел строки для операций перетаскивания ~2048 символов, несмотря на "стандартный" предел строки партии ~8192 символов.
Поскольку для каждого файла указан полный путь, этот предел может быть достигнут с помощью нескольких файлов.

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

И чтобы различать удаленные файлы и папки, вы можете использовать это:

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"
    )
)

Это очень поздний ответ. На самом деле я не знал об этом старом вопросе и подготовил ответ на этот похожий вопрос, где обсуждалась обработка имен файлов специальными символами, потому что проводник цитирует только те имена файлов, которые содержат пробелы. Затем в комментариях к этому вопросу я увидел ссылку на эту ветку, после этого, а не на свою уверенную в себе информацию, я понял, что jeb уже раскрыл и очень хорошо объяснил этот вопрос, что от него ожидается.

Поэтому без каких-либо дополнительных объяснений я внесу свое решение с основным акцентом, чтобы охватить более особые случаи в именах файлов с этим ,;!^ символы, а также для обеспечения механизма, позволяющего угадать, запускается ли пакетный файл напрямую проводником или нет, поэтому во всех случаях можно использовать старую логику для обработки аргументов пакетного файла.

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

Выходной файл с примерами файлов, перетаскиваемых на командный файл:

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"

В :IsDragDrop Я специально пытался минимизировать предположения о формате командной строки и интервале между аргументами. Обнаружение (предположение) для запуска проводника основано на этой сигнатуре командной строки %SystemRoot%\system32\cmd.exe /c ""FullPathToBatchFile" Arguments"

Таким образом, можно обмануть код, думая, что он запущен двойным щелчком из проводника или перетаскиванием, и это не проблема, и командный файл будет работать нормально.

Но с этой конкретной подписью невозможно запустить пакетный файл преднамеренно следующим образом: %SystemRoot%\system32\cmd.exe /c ""FullPathToBatchFile" Arguments & SomeOtherCommand" и ожидать, что SomeOtherCommand для выполнения, вместо этого он будет объединен с аргументами командного файла.

Вам не нужен пакетный скрипт для оптимизации нескольких PNG, все, что вам нужно, это подстановочный знак:

pngcrush -d "crushed" *.png

Это будет pngcrush все PNG в текущем каталоге и переместит их в суб-каталог с именем "дробленый". Я бы добавил флаг -brute, чтобы сбить еще несколько байтов.

pngcrush -d "crushed" -brute *.png

Я публикую это, потому что это не очень хорошо документировано или широко известно, и потому что это может быть проще для вас в долгосрочной перспективе, чем написание и ведение файла перетаскивания.

Другие вопросы по тегам