ak-cmd.txt (cp866) Тонкие особенности .bat/.cmd сценариев cmd.exe (Windows XP, Server 2003) ■ cmd.exe ищет метки goto и call сканируя файл от текущей строки до конца, а затем, если метка не найдена, от начала до текущей строки. Это, к сожалению, делает неэффективными goto-циклы (переходы назад) и исключает возможность добиться быстрой работы независимо от объёма файла (чтобы в самый конец можно было бы помещать длинный Help и т.п.). С другой стороны, поиск меток - достаточно быстр, и даже 75 мегабайт текста сканируются за секунду-две на одном ядре Core i5/i7 3200 МГц ■ Если внутри подпрограммы, вызванной через call :Subr , встречается goto Label, где Label - несуществующая метка, то после вывода сообщения на STDERR о том, что метка не найдена, автоматически происходит возврат из подпрограммы. Это состояние может быть распознано по %ERRORLEVEL% (1 вместо 0) или по if errorlevel 1 ... ■ Если call :Subr 3>file не может открыть файл на запись (из-за блокировки, например), то :Subr не вызывается. Однако это состояние ошибки распознать невозможно, т.к. ERRORLEVEL не устанавливается. Поэтому доступность file надо проверять перед выполнением call :Subr ■ call :Subr возвращает 1 если метка Subr не найдена. Это позволяет создавать очень быстрые селекторы по строке-переменной, учитывая, что в метках разрешены почти любые символы, а длина строк-меток может быть очень большой (похоже, распознавание не ограничено никаким специальным лимитом, и действуют общие ограничения на длины строк и переменных) ■ Особенности передачи параметров без пробела отделяющего имя программы от аргументов 1) call batfile/args - не возвращается из call 2) batfile;args - первый аргумент %1 не содержит символа ';', однако %* его содержит ■ cmd.exe не разрешает конструкции типа if "!VAR:"=!"=="" ... из-за нечётного числа кавычек и разбора операторов прежде подстановки !...! Можно, однако, использовать if "%VAR:"=%"=="" ... т.к. замена %переменных% выполняется прежде разбора операторов ■ Безопасная форма удаления кавычек set "WHAT=%WHAT:"=%" допустима несмотря на нечётное число кавычек в строке, т.к. замена %переменных% выполняется прежде разбора синтаксиса ■ Можно ли задать кавычку как разделитель лексем в опциях FOR ? - Да. set LINE=prefix "string",%% 200 Не работает ни использование delayed переменных, ни экранирование через ^ set QCHR=" FOR /F "tokens=1,* delims=!QCHR!" %%x in ("!LINE!") DO echo 1:{%%x} 2:{%%y} set QCHR="tokens=1,2* delims=," FOR /F !QCHR! %%x in ("!LINE!") DO echo 1:{%%x} 2:{%%y} FOR /F "tokens=1,* delims=^"" %%x in ("!LINE!") DO echo 1:{%%x} 2:{%%y} Решение! можно, оказывается, вовсе не закавычивать tokens и delims, а вместо этого экранировать ^ все символы-разделители: FOR /F tokens^=1^,*^ delims^=^" %%x in ("!LINE!") DO echo 1:{%%x} 2:{%%y} ■ Разделённая^ строка не может начаться в контексте открытых кавычек echo "ABC^ echo DEF здесь вторая строка понимается как новая команда ■ При задании разделённой^ строки первый символ S следующей строки рассматривается как ^S, поэтому надо избегать в этой позиции управляющих символов - кавычки строки, круглой скобки группы и т.п. Экранированный пробел в первой позиции тоже может привести к ошибке, если идёт в начале новой команды, после & - поэтому & надо ставить не в конце предшествующей строки, а в начале следующей echo A^ &(if 1==1 echo B)^ &(if 2==2 (echo C) else (echo D))^ &echo E при этом в первой строке буква A выводится с пробелом на конце ■ В режиме EnableDelayedExpansion можно вводить восклицательный знак через Set /P WHAT=<"inpfile" Однако, перенаправление echo !VAR!|Set /P WHAT=_ использовать невозможно из-за автономности контекста правой части - после возврата оттуда все переменные теряются, и также нельзя вызывать call :Sub ■ В цикле FOR /F "tokens=1,* delims=," %%a in ("!%~1! ,!WHAT!") DO echo 1{%%a} 2{%%b} при пустом параметре %1 переменная !WHAT! не подставляется, а вместо этого берётся строка WHAT - проблема в последовательной обработке символов ! из-за которой ! ,! некорректно рассматривается как переменная Решение: set WTT=!%~1! ■ До возврата из call (или .cmd файла) ENDLOCAL не может закрывать контексты существовавшие прежде вызова. Поэтому call :Sub или call another.cmd нельзя использовать для закрытия контекстов. Однако, контекст может быть закрыт после простого вызова-перехода another.cmd (т.е. без команды call) ■ При возврате из call (или .cmd файла) все созданные контексты закрываются автоматически (если не были явно закрыты через ENDLOCAL). Поэтому call :Sub или call another.cmd нельзя использовать для открытия контекстов. ■ Пустые строки игнорируются при разборе на лексемы for /F "delims=" %%t in ("") DO echo {%%t}& goto :EOF ■ Пустые лексемы не распознаются for /F "tokens=1,* delims=," %%a in (",two") DO echo 1:{%%a} 2:{%%b} ■ Присваивание локальных переменных циклов осуществляется так же, как и delayed-переменных, не вызывает сложностей со спецсимволами, и не требует экранирования типа set "X=STRING" set "AUDOPT=160000&|<>^) -2pass -hev2" for /F "tokens=1,*" %%a in ("%AUDOPT%") DO set WHAT=%%a& set AUDOPT=%%b echo 1:{"%WHAT%"} 2:{"%AUDOPT%"} ■ echo ... echo on echo off не меняют ERRORLEVEL ■ exit /b без параметра не меняет предшествующее состояние ERRORLEVEL ■ SETLOCAL изменяет ERRORLEVEL, ENDLOCAL - не изменяет ■ Если после имени метки идёт пробел, то дальше можно помещать комментарии ■ В режиме EnableDelayedExpansion циклы по файловой маске выдают значения в переменных цикла так, что теряются ! и ^ в имени файла ■ В call вызове одного сценария из другого передавать символ % можно лишь учетверяя его! Удвоения недостаточно, оно приводит к попытке подстановки. ■ Одиночные символы % содержащиеся в %VAR% подставляются корректно ■ Плюсы и минусы EnableDelayedExpansion в сравнении с DisableDelayedExpansion + ECHO/TITLE !unquoted_filepath! с любыми символами в отличие от SET "var=string" экранирование всей команды здесь невозможно + Безопасное сравнение if "!VAR!"=="string" | для if "%VAR%"=="string" недопустимы кавычки, и их сперва надо заменять + Возможность переустанавливать и считывать !VAR! переменную в цикле без вызова подпрограммы (где допустимо считывать через %VAR%) + Возможность переустанавливать и считывать !VAR! переменную в пределах одной сложной командной строки + Возможно использование аргументов-ссылок в виде имён переменных, когда операция доступа к содержимому выражается как !%1! или !%NAME%! (аналоги %%1% и %%NAME%% синтаксически невозможны) - SET "var=text / %var2%" уничтожает символы ! и ^ - SET var=%%i уничтожает символы ! и ^ (однако, при собственно установке %%переменных циклов, ! и ^ не теряются и могут быть получены после переключения в DisableDelayedExpansion) @echo off set WHAT="T:\dirpath\file^name!.ext" SETLOCAL EnableDelayedExpansion REM set WHAT="T:\dirpath\file^^name^!.ext" for %%i in (!WHAT!) DO set WTT=%%i& echo _!WTT!_ == %%i for %%i in (!WHAT!) DO set WTT=%%i& SETLOCAL DisableDelayedExpansion& call :Print %%i& ENDLOCAL for %%i in (!WHAT!) DO SETLOCAL DisableDelayedExpansion& set WTT=%%i& call :Print %%i& ENDLOCAL SETLOCAL DisableDelayedExpansion for %%i in (%WHAT%) DO SETLOCAL EnableDelayedExpansion& set WTT=%%i& echo _!WTT!_ == %%i& ENDLOCAL for %%i in (%WHAT%) DO set WTT=%%i& SETLOCAL EnableDelayedExpansion& echo _!WTT!_ == %%i& ENDLOCAL goto :EOF :Print echo _%WTT%_ == %1 goto :EOF ■ Чтобы без потерь символов ! ^ возвратить строку из суб-режима SETLOCAL DisableDelayedExpansion ... ENDLOCAL в режим EnableDelayedExpansion Для строки в кавычках: set WHAT=%WHAT:^=^^% set WHAT=%WHAT:!=^!% ENDLOCAL& set WHAT=%WHAT% Для строки без кавычек: set "WHAT=%WHAT:^=^^%" set "WHAT=%WHAT:!=^!%" ENDLOCAL& set "WHAT=%WHAT%" Для строки общего вида - ? ■ Можно возвращать строку без потерь символов ! ^ % из суб-режима EnableDelayedExpansion в объемлющий режим DisableDelayedExpansion ENDLOCAL& set WHAT=%WHAT% ENDLOCAL& set "WHAT=%WHAT%" if defined WHAT for /F "delims=" %%x in ("!WHAT!") DO ENDLOCAL& set WHAT=%%x ■ Контекст переменных цикла %%i никак не связан с SETLOCAL-контекстом, что позволяет осуществлять 1) вынос переменной из контекста по ENDLOCAL 2) переход из EnableDelayedExpansion в SETLOCAL DisableDelayedExpansion чтобы прочитать переменную цикла без потерь символов ^ ! ■ exit /b N и if errorlevel N ... допускают отрицательные значения (32 бита) ■ !CMDCMDLINE! содержит имя сценария, запущенного из cmd.exe, тогда как вложенные вызовы сценариев не учитываются C:\WINXP\system32\cmd.exe /C ""C:\TEMP\XDIR\a&ud^ c%vt!\#NONAME1.CMD" arguments..." ■ Форма if cond op1& op2... предпочтительнее, нежели if cond ( op1 op2 ) поскольку в первом случае допустимо использование %1 без кавычек и пробелов но со скобками. Во втором случае следует использовать что-то вроде "%~1" ■ Запуск с незакрытым кавычкой аргументом script.cmd "... выдаёт ошибку: The syntax of the command is incorrect. ■ Выход по goto из цикла работает очень своеобразно - оставшиеся циклы прокручиваются вхолостую, что визуально видно, если включить echo on for /L %%n in (1,1,80) do goto Out :Out Задержка становится ощутимой при больших счётчиках (~10000) и/или при большом размере содержимого цикла Для коротких циклов предпочтительнее форма for %%n in (1 2 3 4 5) do goto Out выход из такого цикла осуществляется сразу ■ Переменная цикла %%i может содержать незакавыченные скобки и это не создаёт трудностей в цикле, тело которого задано в скобках ■ Если из цикла вызвать call :Sub, то внутри :Sub переменные цикла не будут доступны ■ Существует некая непонятная ошибка с if defined VAR set ...[что-то с использованием VAR]... ■ Если в подпрограмме после pushd TMPDIR были открыты SETLOCAL блоки, то надо закрыть их перед popd, иначе они сами могут автоматически закрыться при goto :EOF / exit и поменять директорию на TMPDIR, т.е. на указанную в pushd ■ pushd .. popd блок НЕ закрывается авторматически по exit /b ■ переменные %1 %2 подставляются до исполнения командной строки set WHAT=%1 if "!WHAT:~0,2!"=="**" ( shift /1 set WHAT=%1 ) REM переменная WHAT осталась с тем же значением ■ SETLOCAL..ENDLOCAL не сохраняет ERRORLEVEL ■ set SAVERR=%ERRORLEVEL% ... set ERRORLEVEL=%SAVERR% устанавливает %ERRORLEVEL% как независимую переменную, которая не связана с проверками if [not] errorlevel N ... ■ Странный bug, связанный с поиском меток, начинающихся с символа `:` @echo off pause& goto Lbl REM nnnnnnnnnnnnnnnnnnnnnnnnnnnnnn aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa:bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbccccccccccccdddddddddx :Lbl echo DONE The system cannot find the batch label specified - dddddddddx сбой происходит если символ `:` в REM-строке проставлен в 513 или 514 позиции ■ FOR /F "..." %%L in ("fpath") не читает содержимое заблокированного файла, но можно копировать fpath во временный файл и работать с копией ■ Чтобы строка с FOR была устойчивой к модификациям .cmd файла во время его исполнения, она должна иметь вид (for %%F in (a b c d) DO (echo %%F& pause)) & goto ForDone :ForDone ■ Для файла с именем в переменной V %%~xV при наличии у файла расширения содержит точку ".ext", а при отсутствии расширения устанавливается в пустую строку "", без точки ■ Очистка всех переменных cmd.exe начинающихся на T_ set T_ALBUM=Album Titlo set T_TITLE=mikuru for /F "tokens=1,* delims==" %%i in ('set T_') DO set %%i= ■ Как ни странно, цикл проверок работает быстрее, нежели однократная замена в длинной строке с переключением SETLOCAL for %%E in (aac m4a ogg mpc mp2 mp3 wma spx flac fla alac wv tta ape tak ofr ofs wav mka ac3 avs avi ogm mp4 3gp flv mkv webm ts m2ts mpg mpeg tags) DO if "%%E"=="%EXT%" goto KnownExt exit /b 0 :KnownExt SETLOCAL EnableDelayedExpansion set "WTT=:aac:m4a:ogg:mpc:mp2:mp3:wma:spx:flac:fla:alac:wv:tta:ape:tak:ofr:ofs:wav:mka:ac3:avs:avi:ogm:mp4:3gp:flv:mkv:webm:ts:m2ts:mpg:mpeg:tags:" if "!WTT::%EXT%:=!"=="!WTT!" ENDLOCAL& exit /b 0 ENDLOCAL :KnownExt ■ FOR %%F in (*) DO ... не видит скрытые файлы (с атрибутом HIDDEN) Но если значки шаблонов * и ? отсутствуют, то цикл выполняется однократно для этого конкретного имени и доступ к характеристикам файла через %%~...F есть даже если установлен атрибут HIDDEN ■ Если при считывании файла по строкам длина строки >= 8192 символа, то эта строка целиком пропускается, после чего считывается следующая строка for /F "tokens=1,* delims==" %%i in ('type ogg-tag.lst') DO echo %%i=%%j ■ Для передачи информации изнутри SETLOCAL ... ENDLOCAL может использоваться shift [/N] - состояние %аргументов не восстанавливается по ENDLOCAL ■ Операции, подобные set /A VAR=VAR+1 , работают внутри циклов, без ограничений, связанных с подстановкой %VAR% прежде выполнения цикла, т.е. не требуя включения режима EnableDelayedExpansion. Это, к сожалению, не относится к проверкам if %%N GTR %NUM% ... ■ Цикл FOR /L c нулевым шагом выполняется бесконечное число раз если конечный предел >= начального и ни разу - если меньше. FOR /L %%N in (1,0,1) DO echo [%%N] Из такого цикла нельзя выйти при помощи goto ■ (а) Скрытие сообщения об ошибке, что не найден файл FPATH в случае если этот файл заблокирован - посредством перенаправления вывода всей команды FOR; и (б) возможность различения состояния, когда цикл не выполнялся ни разу (FOR /F "usebackq delims=" %%L IN ("%FPATH%") DO echo [%%L]& cd>nul) 2>nul || echo "%FPATH%" is Locked, Dir or EmptyLine_File cd>nul (FOR /L %%N in (1,1,-1) DO echo [%%N]) || echo NONE ERRORLEVEL Устанавливается при невыполнении FOR и только если указана || альтернатива = 6 если файл %FPATH% не существует или заблокирован, или это директория = 1 если пустой (0 байт или все строки пустые) Сохраняет предшествующее значение если нет || альтернативы (в т.ч. если есть && cmnd); чтобы различать невыполнение цикла надо в конце каждого цикла сбрасывать errorlevel в нулевое значение при помощи простой команды вроде cd >nul ■ (FOR ...) && cmnd выполняет cmnd в зависимости от предшествующего ERRORLEVEL, вне связи с тем, выполнялся цикл хотя бы один раз, или нет ■ Цикл FOR /F по содержимому файла не останавливается на коде EOF (26) ■ В режиме EnableDelayedExpansion !! даёт пустую строку, благодаря чему подпрограмма, получающая имя переменной, может проверять одновременно и наличие аргумента-имени, и непустое значение собственно переменной: if not "!%~1!"=="" ... ■ SET /A при ошибке `Missing Operand` или `Divide by zero error` сохраняет прежнее значение переменной SET /A WHAT= SET /A WHAT=23/0 ■ Не находит файл filename.ext FOR %%F (filename?ext) DO echo [%%~fF] Находит файл filename.ext FOR %%F (filename.ext?) DO echo [%%~fF] ■ Если длинное имя всегда можно получить по короткому, то можно передавать без потерь любые имена файлов в короткой форме как параметры call, а затем восстанавливать длинное имя с помощью %~f1 ■ Windows XP, недокументированная опция /al, выводит все directory junctions dir /al /s ■ Путь c:\dir\subdir\filename.ext\.. , как ни странно, является допустимым и в MS-DOS 7.10, и в Windows XP - как в cmd.exe, так и в command.com /c batchfile Такой путь ссылается на директорию c:\dir\subdir\ c:\dir\subdir\file1\..\file2 ссылается на file2 Эту особенность можно использовать в формах cd %0\.. %0\..\program.exe if exist %0\..\subdir\*.* ... в .bat файлах, совместимых с MS-DOS, для того, чтобы узнать директорию .bat файла, если она была явно указана при его запуске. Если же она не была указана при запуске (.bat файл был найден по PATH), то маршрут будет браться от текущей рабочей директории. ■ В MS-DOS 7.10 максимально допустимая длина строки .bat файла достаточно велика - гораздо больше максимальных 127 символов, передаваемых программам ■ WINXP\System32\command.com /E:1536 /c somefile.bat - Есть доступ к переменным среды WinXP (%SystemRoot%, %OS% и проч.) - %PATH% урезается (!) до 123 символов. Его надо изменить в WINXP\system32\autoexec.nt на более короткий, исключив пути для WINXP - Маршрутные переменные даются в форме коротких имён. Это видно по %APPDATA%, %COMMONPROGRAMFILES%, %ALLUSERSPROFILE% - COMSPEC указывает на command.com, а не на cmd.exe - Появляется переменная BLASTER=A220 I5 D1 P330 T3 она устанавливается в WINXP\system32\autoexec.nt - Нет переменной %windir% - но её можно установить в autoexec.nt - Если не задать явно большой размер буфера переменных опцией /E:NNN, то будет ошибка `Out of Environment Space`. Размер, похоже, определяется как минимальное от размера набора переменных, которые command наследует (причём в autoexec.nt подобной ошибки не бывает) и некоей константы по умолчанию, то ли прописанной в command.com, то ли как-то некорректно передаваемой из ntvdm - но в любом случае, совершенно недостаточной. Хуже всего, что параметр /E:nnnn не наследуется от предшествующей копии command.com - При запуске DOS Navigator.pif, а из него somefile.bat (выполняется, опять же, через command /c) - та же ошибка - При запуске просто command, а уже оттуда вручную somefile.bat не возникает ошибка `Out of Environment Space`. Но если оттуда запустить command /c или /k ... то эта ошибка появится - НЕ помогает SHELL=%SystemRoot%\system32\command.com /E:1536 /P - НЕ помогает удаление (большей части) наследуемых переменных в AUTOEXEC.NT - НЕ помогает установка размера Environment в .pif файле ■ При запуске DOS-программы сперва ищется файл _имя_программы.pif - в текущей директории программы, в %SystemRoot%, в текущей рабочей директории, затем на всех путях %PATH% Если такой файл не найден, то используется %windir%\_default.pif Из .pif файла под NT берутся маршруты к config.nt и autoexec.nt ■ Файлы .pif унаследованы с Win3.x/9x и не все их параметры действуют в NTVDM. Ни на что не влияют: выбор шрифта; "Close on Exit"; размер памяти DPMI (под WS2003 SP2 всегда доступно 32 МБ; впрочем, утилита NTDPMIX.EXE снимает ограничение). Установка размера XMS действует; выбор XMS: None не отключает XMS полностью, но уменьшает размер до 1024 КБ. EMS память можно полностью отключить, увеличив область UMB на 64k. EMS память может пропадать/появляться при вкл./выкл. Option ROM через BIOS, т.к. NTVDM плохо определяет доступные диапазоны адресов в UMB. Ручным вводом размеров в .pif файле не получается поднять размер XMS выше 16 МБ, но можно поднять выше этого предела размер EMS, до 32 МБ Background: Always Suspend по назначению не действует, но (?) сказывается на частоте отрисовки windowed консоли при вводе символов в командной строке command.com Idle Sensitivity: если поставить Low, то Volkov Commander на фоне в ожидании грузит целиком 1 ядро CPU; в любом другом положении ползунка в ожидании загрузка падает до 0. NDN более чувствителен к ползунку, от ~12% в крайне правом или среднем положении до ~4% в крайне левом. ■ Своя копия ntvdm.exe ассоциированная с 32-битным (консольным?) процессом Windows создаётся при первом запуске из него DOS-программы. Если процесс ntvdm.exe вручную уничтожить, новая копия будет инициализирована заново при запуске DOS-программы. Запуск ntvdm.exe определяется ключом реестра HKEY_LOCAL_MACHINE\SYSTEM\CurrentControlSet\Control\WOW\cmdline ■ Виртуализация MS-DOS в Windows XP медленно работает в окне, особенно, если оно гораздо больше 80x25 (переключение DOS-задачи в 80xN форсируется только при выполнении программой каких-то определённых обращений к I/O или к BIOS). В полноэкранном текстовом режиме всё гораздо быстрее. ■ MS-DOS приложению Windows NT не даёт открывать файлы на NTFS > 4ГБ. Кроме того, некоторые программы считают длину файлов > 2ГБ отрицательной. ■ Длина значимой части имени метки в MS-DOS 7.10 и Windows XP NTVDM MS-DOS 5.50 - всго лишь 8 символов. В метках могут быть ? $ # @ _ но не символы * : . которые допустимы для cmd.exe ■ Windows Server 2003 R2 Enterprise cmd.exe (17.02.2007 06:03:32) не работает в Windows XP SP3 (14.04.2008) - не находит импортируемую функцию KERLNEL32.DLL::NeedCurrentDirectoryForExePathW ■ Старые версии cmd.exe от Windows NT 3.51, 4.0 и Windows 2000 вполне возможно запускать в Windows XP в целях проверки совместимости. ■ Встроенные переменные, распознаваемые разными версиями cmd.exe echo [%CMDEXTVERSION%] [%CD%] [%DATE%] [%TIME%] [%RANDOM%] [%ERRORLEVEL%] [%CMDCMDLINE%] для Windows NT 3.10.497 @ 1993 истинно C:\SUBDIR> if exist . .. нет C:> if exist . .. для Windows NT 3.51 @ 26.05.1995 13:57:02 [] [] [] [] [] [] [] работают & | && || (...) команды устанавливают errorlevel нет if cmdextversion 1 ... - ошибка с прерыванием исполнения нет аргументов SETLOCAL EnableExtensions - игнорируются втихую недопустим символ = в строках присваивания set VAR=... - Syntax Error, присваивание не выполняется, исполнение продолжается; так же работает и MS COMMAND.COM любой версии нет %VAR:~I,N% - просто пустая строка нет %~dp1 и т.п. операторов файловых маршрутов нет set /A нет if defined VAR ... нет if /I ... нет goto :EOF но можно поставить метку с таким именем в конце истинно if exist . .. ...... *\. *\.. нет color, exit /b bug-feature ren/copy *.7z *.zip даёт fb2bin-1.5.7z => fb2bin-1.zip для Windows NT 4.0 @ 26.07.1996 16:41:12 ... SP6 @ 18.11.1999 11:04:00 [] [] [] [] [] [0] [cmd.exe /c Q:\test.bat] условие верно if cmdextversion 1 ... условие неверно if cmdextversion 2 ... нет SETLOCAL EnableDelayedExpansion нет exit /b нет if ... else ... нет если I < 0 в результате %VAR:~I,N% - пустая строка нет %VAR:abc=defg% - подставляется просто %VAR%, без замены errorlevel 9153 деление на ноль в set /A нет %~1, однако %~dpnx1 работает не работает %~sfF и %~$PATH:F если имя переменной цикла совпадает с одним из модификаторов dpnxfs (работает I вместо F) bug-feature ren/copy *.7z *.zip даёт fb2bin-1.5.7z => fb2bin-1.zip bug color 00 не выставляет errorlevel 1 (вопреки color /?) для Windows 2000 @ 04.05.2001 12:05:02 ... SP3 @ 30.10.2001 04:57:00 [2] [Q:\] [Сб 05.01.2013] [21:58:49,87] [16571] [0] [cmd.exe /c Q:\test.bat] русское "Сб" выдаётся исходя из текущей chcp (866) условие верно if cmdextversion 2 ... errorlevel 9169 деление на ноль в set /A неверн.значение целочисленная константа в set /A не вмещается в 32 бита bug color 00 не выставляет errorlevel 1 (вопреки color /?) - способ отличать NT5.1+ от NT5.0- если предварительно выставить состояние errorlevel 0 командой (call ) для Windows XP SP3 @ 14.04.2008 15:00:00 / WS2003 [2] [Q:\] [05.01.2013] [22:01:30,95] [17097] [0] [cmd.exe /c Q:\test.bat] errorlevel 9168 целочисленная константа в set /A не вмещается в 32 бита, хотя переполнение + - * / % не устанавливает код ошибки - способ отличать NT5.1+ от NT5.0-, но его минус в том, что задействуется имя переменной ■ Если доступны файлы name.cmd, name.bat и name.exe, то выбор при запуске по имени, без указания расширения, зависит от порядка перечисления расширений в PATHEXT (проверено в WS2003). Этот порядок приоритетнее, нежели порядок перечисления директорий в PATH. Установить более высокий приоритет для .CMD и .BAT: set PATHEXT=.CMD;.BAT;.COM;.EXE;.VBS;.VBE;.JS;.JSE;.WSF;.WSH ■ Команда dir ... в cmd.exe (WinXP) возвращает 0 или 1 в зависимости от того, найдены файлы или нет (можно использовать для проверки наличия файла с определённым атрибутом), а command.com (DOS 7.10) всегда возвращает 0 ■ Перенаправление stdout и stderr в один общий файл: program.exe 1>outfile 2>&1 Не будет работать, если порядок изменить: program.exe 2>&1 1>outfile - в файл пойдёт лишь stdout, а stderr пойдёт на stdout ДО перенаправления ■ Если Win32 .exe программа завершилась по Unhandled Exception, то %ERRORLEVEL% устанавливается в 0xC000001E в случае Illegal Opcode, 0xC0000094 в случае Divide by zero, 0xC0000005 при срабатывании защиты NX/DEP и т.п. Эти величины рассматриваются как отрицательные, поэтому их надо проверять так: if not errorlevel 0 ... или, считая, что такие ошибки ниже 0xC000xxxx if not errorlevel -1073676289 ... ■ Если в ответ на вопрос Terminate batch job (Y/N)? снова нажать CtrlC или CtrlBreak, то такой ответ рассматривается как N и работа продолжается ■ Если .bat/.cmd сценарий завершён с помощью CtrlC или CtrlBreak (с ответом Y на Terminate batch job (Y/N)?), то cmd.exe возвращает код 255 ■ Если Win32 .exe программа завершена с помощью CtrlC или CtrlBreak, то в вызывавшем .cmd файле задаётся вопрос вопрос Terminate batch job (Y/N)? и при ответе N возвращается код 0xC000_013A = -1073741510 ■ Если Win32 .exe программа снята вручную при помощи Task Manager, или через taskkill /F /IM progname.exe, то код завершения = 1 ■ choice.exe от WS2003 при нажатии CtrlC или CtrlBreak возвращает 0 ■ Вложенные вызовы call ___.cmd невозможно прерывать отдельно от вызвавших. Если запустить вложенный вызов как cmd.exe /c ___.cmd, то отвечать на вопрос Terminate batch job (Y/N)? придётся дважды: при первом Y произойдёт выход из вложенного cmd.exe, а при втором N %ERRORLEVEL% будет равен 255 ■ При прерывании команды pause с помощью CtrlC, как и при её завершении обычным нажатием клавиши, сохраняется предшествующее значение %ERRORLEVEL% ■ %~x1 в применении к файловому шаблону типа *filename_substr* при наличии таких файлов выдаёт расширение реально существующего файла ■ В цикле FOR аргументы %1 %2 и т.д. подставляются перед выполнением, а не динамически, во время выполнения цикла. Т.е., они подобны переменным типа %VAR%, а не динамически подставляемым переменным %%v или !VAR! ■ cd .. возвращает 0 при невозможности подняться из корня диска (WS2003), т.е. никак не сигнализирует об ошибке ■ Незакрытые кавычками символы , ; = считаются разделителями аргументов в командной строке, аналогичными пробелу myfile.cmd arg1,arg2;arg3=arg4 arg5 ■ Какой способ сравнения параметров командной строки использовать? if "%1"=="" и if ""=="%1" влекут ошибку на "git pull" (из-за пробела) if ""=="%1" (...) else ... не влечёт ошибку при нечётном числе кавычек в аргументе, но в этом случае не срабатывает и else (если оно есть), что может приводить к логически некорректной работе. Для command.com из Win98SE кавычки - рядовой символ. Сравнение "строк с пробелами" не работает if ""=="a string" echo COMPARED! Вывод: простейший вариант с кавычками точно не следует использовать! if %1_==_ и if _==%1_ влекут ошибку на ^& ^| ^< ^> " (нечётное число кавычек без пробелов), но хорошо, что такие ошибки сразу прекращают выполнение командного файла. Влекут лишнюю обработку ^ символов (^^ превращается в пустоту) if %1_==_ также влечёт ошибку на " "" (нечётное число кавычек с пробелами), и потому безопаснее/предпочтительнее, нежели if _==_%1 ... Более эстетичные варианты, но на 1 символ длиннее: if '%1'=='' if [%1]==[] if "%~1"=="" Правильно обрабатывает ^& ^| ^< ^> При нечётном числе кавычек или влечёт ошибку или, что плохо, некорректно трактует конструкцию if Не различает "" от несуществующего параметра Вывод: не лучший вариант, к тому же несовместимый с DOS .bat файлами. Лучше выглядят варианты с переменной, которые идут далее. set "WHAT=%~1" if not defined WHAT ... Правильно обрабатывает ^& ^| ^< ^> Правильно устанавливает WHAT при нечётном числе кавычек, в т.ч. с пробелами, хотя дальше не получится использовать такую переменную в форме "%WHAT%" Не различает "" от несуществующего параметра set WHAT=%1 if not defined WHAT ... Влечёт ошибку на ^| ^< ^> Влечёт лишнюю обработку ^ символов (^^ превращается в пустоту) Устанавливает WHAT как пустую на ^& Правильно устанавливает WHAT при нечётном числе кавычек, в т.ч. с пробелами Обработка переменных с нечётным числом кавычек возможна в форме !WHAT! в режиме SETLOCAL EnableDelayedExpansion. Другие варианты - замена кавычек для некоторой операции, например, на апостроф %WHAT:"='% или полное удаление всех кавычек из переменной SET "WHAT=%WHAT:"=%" (полезно для имён файлов) ■ "%~f0" выдаёт неправильный результат при вызове B.CMD из A.CMD при условии, что A.CMD и B.CMD находятся в C:\CMDDIR, который включён в PATH, при том, что A.CMD вызывается из Q:\WORKDIR "%~0" в A.CMD: "C:\CMDDIR\A.CMD" "%~0" в B.CMD: "Q:\WORKDIR\B.CMD" ■ cmd.exe от "неродной" OS при ENDLOCAL, явном или неявном, не выполняет восстановление переменных, хотя восстанавливает текущую директорию и состояние Enable/Disable Extensions/DelayedExpansion. Нижеследующий код должен выдавать два одинаковых результата [1], но выдаёт разные [1] [2] при запуске под WS2003 cmd.exe от NT4, W2000, XP, а также при запуске под Win98SE+KernelEx cmd.exe от W2000 c патчем. @echo off set WHAT=1 echo [%WHAT%] [%CD%] ! call :PROC echo [%WHAT%] [%CD%] ! exit /b :PROC SETLOCAL EnableDelayedExpansion set WHAT=2!!! CD .. rem ENDLOCAL rem goto :EOF exit /b В том числе это касается наиболее важных неявных ENDLOCAL для .cmd файла в целом, вследствие чего переменные среды подвергаются изменениям точно так же, как и в случае .bat файлов для COMMAND.COM Как выясняется, этой ошибки нет в случае cmd.exe из SP4 Windows 2000 5.00.2195.6656 (MD5 4c6529a9b2976145e49f9fa035f79921), хотя есть в SP3! Этим принципиальным недостатком не страдает под Windows 98SE (и всеми прочими 9x?) Win95Cmd.exe из старых Microsoft SDK http://cygutils.fruitbat.org/consize/index.html ...который, однако страдает тем, что команда type выводит UTF16-текст (только на консоль, в файл пишутся однобайтовые символы). И Windows 2000 cmd.exe, и Win95cmd.exe, кроме того, выдают, будучи запущенными из-под Windows 98, ошибку на `copy *.rar ..`, хотя без шаблонов * ? copy работает, а команда ren работает и с шаблонами. ■ goto :EOF предпочтительнее exit /b (если без параметра), т.к. exit /b влечёт Syntax Error под cmd.exe от Windows NT 4.x ■ Чтобы использовать !VAR! из командной строки нужно запускать её через cmd /v /c ... ибо SETLOCAL EnableDelayedExpansion из командной строки не включает режим распознавания ! как управляющего символа ■ Код возврата (ERRORLEVEL), возвращаемый командой cmd /c batchfile 1) Под Windows NT 4 - всегда возвращает 0 если batchfile запустился (1 если не найден) 2) Под Windows 2000/XP - exit N возвращает N, всё остальное, включая exit /b N возвращает 0 3) Под WS2003 - exit N и exit /b N возвращают N, в остальных случаях - 0, т.е. теряется ERRORLEVEL на момент завершения файла, в т.ч. при выходе через exit /b (без кода возврата) и через goto :EOF ■ start.exe в cmd файлах понимается как команда start для файла ".exe" Имя программы "start.exe" (из Windows 9x, полезной на NT тем, что может запускать bat/cmd файлы с закрытием окна), должно быть в кавычках. ■ start batchfile закрывает окно в случае, если batchfile выходит по exit (но не exit /b). Бесполезная возможность: выход простым exit, в общем случае - вещь недопустимая, т.к. прерывает работу родительских bat-файлов ■ У команды REM приоритет над скобками (), поэтому конструкция if 1==1 (echo ==YES== & rem Comment) else echo ==NO== ... отнесёт часть else к комментарию и будет искать закрывающую скобку в последующих строках ■ В COMMAND.COM (Windows 9x) переменные %var% не расширяются в области имени файла перенаправления for %%N in (1 2 3 4 5) DO echo REGEDIT4 >$HIST%%N$$.REG создаёт не 5 файлов, а один - "$HIST%N$$.REG" ■ В COMMAND.COM (Windows 9x) результат выполнения команд MD, CD и т.п. не может быть проверен через if errorlevel 1 goto Error ■ В COMMAND.COM (Windows 9x) if exist ... проверяет только существование файлов, но не директорий ■ Создание пустого файла; если файл уже существует, то содержимое теряется: 1) универсальный способ для command.com или cmd.exe: PROMPT >fpath 2) только в command.com работает REM >fpath 3) только в cmd.exe работает COPY nul fpath >nul ■ Проверка версии командного процессора и версии ОС 1) Отличить cmd.exe NT 3.51+ от NT 3.10 и DOS/Win9x/NTVDM command.com if exist . [...] условие всегда истинно под cmd.exe и всегда ложно под command.com 2) Отличить новый command.com (MS-DOS 7.x/8.0), с поддержкой имён в кавычках и с пробелами (LFN) от старого if exist "%COMSPEC%" [...] Примечание: к "старым" относятся FreeDOS command.com и NTVDM command.com, который был взят от MS-DOS 5.0 (несмотря даже на то, что под NTVDM в XP/2003 поддерживаются LFN - длинные файловые имена) 3) Отличить cmd.exe NT 4.0+ от NT 3.51 и любых command.com REM set ERRORLEVEL= if exist . if not ""=="%ERRORLEVEL%" [...] Примечание: добавлять условие if exist . приходится из-за command.com от FreeDOS и Caldera DR-DOS, где тоже поддерживается переменная %ERRORLEVEL% 4) Отличить cmd.exe 2000/XP/Vista/7/8+ от NT 3.51/4.x и любых command.com REM set CMDEXTVERSION= if not ""=="%CMDEXTVERSION%" [...] 5) Отличить cmd.exe Windows 7+ от Windows Vista/2003/... и любых command.com REM set HIGHESTNUMANODENUMBER= if not ""=="%HIGHESTNUMANODENUMBER%" [...] 6) Отличить запуск под Windows NT от Windows 9x if not ""=="%SystemRoot%" [...] или if "Windows_NT"=="%OS%" [...] Примечание: проверяется система, а командный процессор может быть любым, включая самые старые версии command.com 7) Отличить запуск под полноценной установкой Windows XP и выше от более ранних или от урезанных (в частности, MiniXP @HBCD и WinPE 7/8/10 @2k10) if not ""=="%SESSIONNAME%" [...] 8) Отличить запуск под Windows Vista и выше от более ранних ОС if not ""=="%LOCALAPPDATA%" [...] ■ Команда call обладает некорректной особенностью, состоящей в том, что call exefile.exe /? call :Subroutine A B /? C D ... работают как call /? то есть выводит справку по использованию call ■ При запуске 64-bit приложения из 32-bit WS2003 выводится сообщение об ошибке `The image file _program.exe is valid, but is for a machine type other than the current machine.` и устанавливается errorlevel 216 ■ Чтение строк файла/STDIN в переменную, проверка подстроки, отображение строк - с корректной обработкой символов ! % ^ " == < > | & ( ) SETLOCAL EnableExtensions DisableDelayedExpansion FOR /F "delims=" %%L in ('type #myfile') DO ( set LINE=%%L SETLOCAL EnableDelayedExpansion echo !LINE! if not "!LINE:Search Text=!"=="!LINE!" echo ^^ --- FOUND --- ENDLOCAL ) ■ Если из цикла FOR вызывается подпрограмма, то переменные цикла в ней не будут доступны. for %%L IN (1,3,1) DO call :Sub EXIT /B 0 :Sub echo [%%L] [%L] Exit/b Передача параметра call :Sub %%L плоха тем, что интерпретирует символы % Предпочтительнее доступ через статическую переменную: set ARG_L=%%L ■ Выход из сценария или из подпрограммы через через goto :EOF не выполняет поиска метки :EOF (в отличие от, скажем, goto :Done) и, по идее, столь же быстр, как и exit /b ■ При передаче параметров в подпрограммы происходит удвоение символа ^ в закавыченных строках, из-за чего искажаются имена файлов, если они содержат этот символ. Метод противодействия: SETLOCAL DisableDelayedExpansion set WHAT=%* echo %WHAT:^^=^% ■ Ошибка деления на ноль в команде SET /A не изменяет значение переменной и возвращает errorlevel 9169. Эту особенность можно использовать для проверки значений переменных внутри цикла в режиме DisableDelayedExpansion @echo off set VAR=1 FOR /L %%N IN (1,1,10) DO ( set /A VAR=VAR*2 set /A "1/(VAR-8)" 2>nul if errorlevel 9169 echo === VAR is 8 === ) ■ Когда в команде set /A не указывается имя переменной, то результат выводится на stdout при вводе из командной строки или когда set /A является объектом захвата в цикле вида for /F %%F in ('set /A "0x100 | 0100"') do echo %%f будучи же обычной командой bat-файла set /A без имени переменной пытается произвести вычисление, устанавливает код ошибки, но ничего не выводит. ■ Команда TYPE _filename, если не находит _filename в текущей директории, пытается смотреть маршруты из переменной окружения DPATH. Поддерживается не только установка через команду set, но и недокументированная команда DPATH: set DPATH=C:\TEMP\dir1;C:/DIR2 DPATH C:\TEMP\dir1;C:/DIR2 Если имя файла задано с маршрутом, например ".\myfile.txt", то поиск по маршрутам DPATH не выполняется. ■ Использование call для форсирования операций подстановки переменных %var% @echo off set index=1 set var%index%=42 set var42=[mydata] set var ECHO ---- ECHO without call: %%var%index%%% call ECHO call once: %%var%index%%% call call ECHO call twice: %%%%var%%var%index%%%%%%% ■ call можно использовать для динамической подстановки переменных %VAR% что открывает доступ к части возможностей режима EnableDelayedExpansion @echo off set VAR=[ FOR /L %%N in (1,1,5) DO ( call set "VAR=%%VAR%%%%N " ) echo "%VAR%" Однако невозможны конструкции call if ... или call for ... Также следует проявлять осторожность с именами переменных объемлющих циклов, т.к. если существует %%V, то %%VAR%% не будет подставлена правильно ■ cmd.exe даже старых версий (в т.ч. NT 3.51) правильно работает с .bat/.cmd файлами, где перевод строки - односимвольный (\n вместо обычного \r\n) ■ При вызове одного .cmd файла из другого предшествующий %ERRORLEVEL% не сбрасывается, и может быть прочитан в вызываемом .cmd ■ Команда start при успешном выполнении не меняет ERRORLEVEL, а при невозможности найти объект запуска устанавливает ERRORLEVEL 9059 ■ Список файлов дерева директорий в utf8: chcp 65001 & CMD /C dir /a /s /b >\dirlist.lst & chcp 866 Если вызывать dir без CMD /C, то вместо utf8 список будет в cp866 Если не поставить возврат к однобайтовой кодировке в той же строке, то выполнение .cmd файла завершается. ■ Команды rd (rmdir) и del (erase) не устанавливают код завершения/ошибки. Сохраняется предшествующий %ERRORLEVEL% Однако, системный код ошибки Windows устанавливается после конструкции rd SomeDir || rem (2 - не найдено, 5 - нет доступа, 145 - директория не пуста) ■ Необходимы скобки в конструкции вида conditional_command || (echo COMMAND1 & echo COMMAND2) если требуется, чтобы обе COMMAND1 и COMMAND2 либо выполнялись, либо не выполнялись. При отсутствии скобок COMMAND2 будет выполняться всегда ■ В отличие от проверки вида if [not] errorlevel 1 ... операторы && и || проверяют равенство или неравенство %ERRORLEVEL% нулю для команд, которые устанавливают ERRORLEVEL, и успешность выполнения команд, которые ERRORLEVEL не устанавливают. Таким образом, команды, прерванные нажатием CtrlBreak (с отрицательным ERRORLEVEL, но проверяется не он) считаются неудачными, что существенно, например, для распаковки чего-либо архиватором. ■ _Set_ERRORLEVEL_Zero || op2 || op3 && op4 || op5 && op6 && op7 ... & opAlways пропускают все операции op2, op3, ... и выполняют opAlways. Однако _Set_ERRORLEVEL_NonZero && op2 ... подобным свойством уже не обладает. Принцип, видимо, в том, что после невыполненной правой части команды || ищется безусловная команда &, а после невыполненной правой части && ищется либо ||, либо безусловная команда & ■ Конструкция ;@echo off и любые другие команды, работают как если бы ; не было. Это позволяет делать .cmd и .bat файлы, которые выглядят как комментарии для интерпретаторов, где строчные комментарии начинаются с ; ■ Вместо echo %VAR% (если %VAR% может оказаться пустой) следует использовать echo:%VAR% - и тогда не будет выведена строка "ECHO is on." ■ На NTFS-разделе код завершения compact /U или compact /C можно использовать для проверки заблокирован ли файл (например, исполняющимся .exe или .dll) ■ [WS2003 cmd.exe] В PATH можно добавлять маршруты с пробелами, без кавычек. ■ Копирование файлов, если их не существует: echo n| copy SRCDIR\* DSTDIR\ ■ Закрывающая скобка ) в начале строки, распознанная как отдельная лексема, игнорируется если закрывать нечего. Таким способом можно переходить на метки, поставленные внутри циклов, так что команды цикла будут выполняться вне контекста циклов. ) Можно использовать для комментариев ■ Можно опускать имя переменной и разделяющие пробелы в set/P=Press ENTER... ■ Вывод текста без перевода строки: echo|set/P"= Launched... " ■ Для запуска "start.exe" (от Windows 9x) под WinNT имя надо заключать в кавычки, иначе оно будет воспринято как встроенная команда start с аргументом .exe ■ Определение разрядности ОС по реестру (есть на WinNT4, ?нет на Win9x) set IsX64= FOR /f "tokens=3" %%X in ('reg.exe query HKLM\Hardware\Description\System\CentralProcessor\0 /v Identifier') DO if not '%%X'=='x86' set IsX64=Yes ■ В DOS7/Win9x command.com по-разному обрабатывает шаблоны файловых имён В FOR(...) выдаются короткие имена и не шаблоны вида *substr*.ext раскрываются как *.ext В IF EXIST / DIR шаблоны допускают текст после * ■ В параметрах call :Subr невозможно передать символ | вне закавыченной строки Не помогает экранирование ^| , использование delayed переменной !VAR! ■ COMMAND.COM от 5/5-ntvdm/7 не поддерживают goto на имена меток начинающихся с допустимых для CMD.EXE символов . * / [ ] goto .Label :.Label Символы ! # $ ( ) - @ ? допустимы везде Символ = игнорируется в начале метки :=Label и в goto =Label (метка считается за Label) - верно для CMD.EXE и для COMMAND.COM 5/7/8/IBM/DR, но не FreeDOS Символы = , недопустимы даже в CMD.EXE Символы + недопустим в CMD.EXE в начале метки Символ & допустим в COMMAND.COM, но не в CMD.EXE COMMAND.COM от FreeDOS и DR-DOS допускают символы, допустимые для CMD.EXE, а также + в начале ■ Проверка занятости буквы диска: if exist Q:\. echo EXISTS! Проверки Q:\* или Q:\*.* на пустом диске не покажут его присутствие ■ (call) устанавливает ERRORLEVEL 1, а (call ) - ERRORLEVEL 0 prompt устанавливает ERRORLEVEL 0 при первом обращении, а при последующих - ERRORLEVEL 1; неожиданное поведение! Так работают под WS2003 все cmd.exe от NT 3.51/4.0 SP6/5.0 SP4/5.2 SP2 Однако под WinPE 10 эти же версии cmd.exe всегда возвращают 0 ! ■ Проверка блокированности файла на запись. Если файл не блокирован, то никакие его даты (M,C,A,P) не меняются. 2>nul (>>test.txt call ) && (echo File is not locked) || (echo File is locked) Файл создаётся, если не существует, поэтому обычно уместна предварительная проверка if exist test.txt ... Кроме того, если на файле установлен атрибут Read Only, то файл будет казаться заблокированным независимо от того, заблокирован ли он на самом деле. Проверяя отсутствие блокировки .exe и .dll можно практически удостоверяться, что программа в данный момент не запущена. ■ Блокирование файла до нажатия клавиши. Опять же, даты файла не меняются (>&2 pause) >> test.txt ■ В set /A оператор присваивания = имеет те же права, что и другие операторы, и вместе с оператором-разделителем операций делаются допустимыми такие вещи: set /A X=10, Y=20 set /A X=10+(Y=16,30)+Y set /A NUM=%NUMBER_OF_PROCESSORS%,NUM=NUM/2+1 ■ BUG? Команда call :Proc ... вообще не выполняется, если аргумент содержит незакавыченный символ & или | call :Proc A^&B call :Proc A^^^&B set "WHAT=A&B" & call :Proc !WHAT! ■ COMMAND.COM Microsoft/IBM/DR-DOS не понимает односимвольные (Unix-ные) переводы строк \n - только стандартные \r\n. Исключением является FreeDOS. ■ COMMAND.COM (дажe от DOS 7.x) не понимает команд наподобие for %%N in (1 2 3) do echo Number %%N >%%N.txt Перенаправление трактуется как относящееся не к отдельной команде в цикле, а ко всему циклу сразу, в контексте, где не определена переменная цикла ■ Для COMMAND.COM строки с комментариями лучше начинать с :: а не REM, иначе символы > < | будут рассматриваться как перенаправление I/O. Точно так же не должно быть этих символов в условных командах, рассчитанных на WinNT cmd.exe - если таковые неизбежны, их следует обходить с помощью goto ■ start /B /W работает даже в [WIN95]CMD.EXE под Win95 и Win98 ■ x64 версия CMD.EXE использует не 64-битную, а 32-битную арифметику в set /A ... ■ Задержка с точностью до миллисекунд в рамках стандартных утилит системы (начиная с Windows 95) ping -n 1 -w 1000 127.255.255.255 Все другие "гарантированно недоступные" адреса могут давать ответ без ожидания. 127.255.255.255 тоже может давать ответ (ошибку) без ожидания, но для этого придётся остановить драйвер tcpip.sys (таково, однако, состояние на HBCD 15.x MiniXP пока не запущена "Настройка сети"). ■ Для вывода пустой строки надо использовать не echo. а echo, или echo; echo. Если существует файл с названием "echo", то cmd.exe /c "echo." выдаёт 'echo.' is not recognized as an internal or external command, operable program or batch file. echo:abc Может совпасть с именем abc альтернативного потока данных NTFS у файла с именем echo. Даже echo: проверяет альтернативный поток данных с пустым именем! echo/abc Проверяет директорию с именем "echo" (но выводит корректно) echo(abc Сбивает учёт парных скобок редактором echo+abc Проверяет существование файла "echo+abc" во всех каталогах PATH, несмотря на то, что не запускает его. echo=abc echo;abc echo,abc под cmd.exe не проверяют файл с именем echo* Под разными версиями command.com они работают корректно, кроме echo= под IBM PC-DOS 7 (аналог MS-DOS 6.x), где выводится `echo is ON/OFF` Альтернативные символы работают и в COMMAND.COM разных версий DOS. echo, echo; echo= Не реагируют на файлы с такими же именами, однако рассматриваются как имена в командной строке FAR1/FAR2, что неудобно. Поиск в *.bat,*.cmd: \b(@?echo)[=,;:\/=\.] \b(@?echo)[=,;:\/=\.](?![&>])(?!\r?\n) ■ На сетевых дисках (VBox Shared Folders) почему-то невозможно определить существование директории (а не файла!) добавив слэш в конец маршрута if exist Q:\rar.exe\ echo TRUE if the regular file exists надо использовать форму if exist Q:\rar.exe\* echo FALSE if the regular file exists которая определяет наличие и пустых директорий, и с файлами (всё это касается cmd.exe, но не command.com) ■ CMD.EXE внутренне работает в UTF-16, поэтому способен создавать файлы с производными именами за пределами набора символов OEM+Ansi независимо от выбранного chcp for %F in (*.txt) DO echo [%F] >"%~nF.name" Параметры исполняемым .exe передаются в UTF-16, и от программы требуется лишь чтобы она читала командную строку через GetCommandLineW(), а не ...A() ■ При запуске консольной программы через start "MyTitle" something.exe индивидуальные настройки консоли берутся и пишутся в HKCU\Console\MyTitle ■ start "/max" /max ... обеспечивает совместимость встроенной команды start в Windows NT cmd.exe и внешней стандартной утилиты start.exe в Windows 98. "/max" может варьироваться по длине от "/ma" до "/maximized", но версия без кавычек должна быть строго /max (встроенный start иначе не понимает) ■ В cmd.exe есть команды DPATH (аналогично PATH) и KEYS (on/off). Обе являют наследие cmd.exe от OS/2 ■ C:\FAR1>start /D C:\FAR2 far.exe запускает C:\FAR2\far.exe только если не существует C:\FAR1\far.exe - а иначе запускается именно последний! Чтобы такого не происходило, надо задавать полный маршрут запускаемого .exe C:\FAR1>start /D C:\FAR2 C:\FAR2\far.exe ■ Создание отдельного процесса командой start cmd /c %0 _arg1 "_arg2" заключает в себе "подводный камень", связанный с неопределённостью наличия кавычек в %0 (FAR3 добавляет их там, где FAR1 и FAR2 не добавляют). Работает: cmd /c Q:\test.cmd arg1 "arg2" Работает: cmd /c "Q:\test.cmd" arg1 НЕ работает: cmd /c "Q:\test.cmd" arg1 "arg2" Есть, конечно, формат cmd /s /c ""Q:\test.cmd" arg1 "arg2"" (ключ /s не поддерживается в старых версиях cmd.exe, например NT4, но можно обойтись и без него, если кавычек гарантированно много) - однако тут, с точки зрения запускающего интерпретатора, test.cmd и arg2 перестают быть строками, экранирующими спец. символы НЕ работает: cmd /c ""Q:\test&.cmd" arg1 """ Работает: cmd "/c ""Q:\test&.cmd" arg1 """ (обманываем вызывающий интерпретатор, причём "/c понимает и NT4 cmd.exe) Универсально: cmd /c ; %0 _args -- символ ; игнорируется, но делает невозможным удаление охватывающих кавычек. Не только cmd.exe, но и command.com (MS-DOS, PC-DOS, FreeDOS) понимают запуск команд через /c ; или /c; cmd /c %~sf0 _args -- начальный символ никогда не будет кавычкой, работает только если на диске поддерживаются короткие имена. cmd "/c "..." -- после этой команды в строке .cmd файла не должно быть др. команд (например, отделённых символом &), иначе хвост будет передаваться новой копии cmd и выполняться как часть её командной строки. ■ Неочевидное поведение (BUG?) из-за @ глушения вывода (WS2003, WinPE 10x64) Не печатается ANY CASE, т.е. не выполняется безусловный код cd. || @echo FAILED & echo ANY CASE @cd. || @echo FAILED & echo ANY CASE Печатается cd. || echo FAILED & echo ANY CASE cd. || echo FAILED & @echo ANY CASE @cd.|| echo FAILED & echo ANY CASE Такое впечатление, что @...\n неявно означает @(...)\n Поиск подозрительных мест \|\| *@[^\r\n]+& \|\| *@(?!\([^\r\n()]+\)$)[^\r\n]+& ■ "Inline" версия команды call имеет доступ к переменным цикла. Следующие две команды эквивалентны, но в первой %%~I подставляется прежде call, а во второй подстановка происходит внутри call ... for %%I in (aa bb cc) DO if exist "%%~I" call set WHAT=%%WHAT%% "%%~I" for %%I in (aa bb cc) DO if exist "%%~I" call set WHAT=%%WHAT%% "%%%%~I" ■ Переход на другой .cmd файл (без команды call) очень странно работает из циклов. Цикл всё равно проходится весь, однако goto не работают (включая goto :EOF), и всё, что после цикла - не работает. Возможные варианты: 1) выйти из цикла при помощи goto, затем безвозвратный переход на .cmd 2) вызов .cmd из цикла через call - ведь возврат всё равно происходит и никакой экономии нет! 3) по-настоящему прервать цикл можно с помощью exit или exit /b ■ Псевдо-цикл for %%_ in (_) do ... режиме echo on, в заглушённом с помощью префикса @ коде обладает эффектом включения эхо-вывода для команды ... в той же строке: @if exist archive.7z for %%_ in (_) do 7z x archive.7z ||@pause - будет видно, к какой команде относится потенциальная ошибка архиватора, и паузу необязательно сопровождать пояснением. ■ Символ с кодом 26, означающий, по идее, конец текстового файла, cmd.exe считает альтернативным символом перевода строки в .cmd и .bat файлах. А вот для COMMAND.COM это действительно конец команд в .bat файле. ■ Количество значащих символов в метках и в команде goto для cmd.exe очень велико, тогда как для COMMAND.COM их лишь 8 ■ cmd /c UnexistantProgram.exe почему-то возвращает 1, а не 9009 Но возврат значений > 255 возможен, например: rslt cmd /c call rslt.cmd call:SetErr 1234 rslt cmd /c "UnexistantProgram.exe & if errorlevel 9009 call rslt call:SetErr 9009" rslt cmd /v/c "UnexistantProgram.exe & if !ErrorLevel!==9009 exit 9009" Причём возвращает 0, если первый вызов не call rslt ..., а просто rslt ... ■ Можно избежать необходимости ставить дополнительную метку и goto на неё, если использовать переходы внутрь формально многострочной команды goto Suspendi goto Resumi :Suspendi set WHAT=suspendprocess& break^ :Resumi ^ set WHAT=resumeprocess echo nircmdc %WHAT% iexplore.exe ■ Символы - и ! нестандартно интерпретируются при порядковом сравнении строк if "a-c" GTR "ac" echo TRUE if "a-c" GTR "ad" (call) else echo FALSE if "a!" LSS "a" echo TRUE if "a!z" LSS "a" echo TRUE ■ Удаление начальных пробелов в строке for /F "tokens=* delims= " %i in ('cmd /c "echo Dungeons and Dragons "') do echo [%i] [%j] ■ По умолчанию delims= в циклах FOR назначено на пробельные символы, включая табуляцию, что хорошо, т.к. табуляция - визуально "неочевидный" символ. ■ Команда cmd.exe start в WinXP не запускает .lnk файлы, а в WS2003 - запускает. Файлы .scf запускаются через start и там, и там. ■ Конструкция if=defined==ERRORLEVEL echo [New cmd.exe with Extensions] синтаксически совместима со старыми cmd.exe (и даже с COMMAND.COM), а также с режимом запрета расширений cmd.exe /E:OFF ... Все знаки = в режиме расширений понимаются cmd.exe 5.0+ как пробелы, тогда как в старом режиме или старыми cmd.exe (включая 4.0, который понимает defined...) == трактуется как операция сравнения, и условие ложно. И даже если создана вручную переменная ERRORLEVEL, старый cmd.exe не перейдёт к исполнению кода, для него не предназначенного. ■ cmd.exe от WinNT 3.51 пытается анализировать синтаксис всей командной строки, включая часть, которая закрыта ложным условием, и не должна исполняться им. При этом, синтаксис %~n1 ... %~dp1 ему непонятен и он видит переменную с именем "~n1 ... " (между двумя знаками %). Это позволяет экранировать части кода от ущерных попыток проверять синтаксис, избегая ошибки с прекращением работы в случае выполнения старым cmd.exe Грамотное использование команды break "%~0" позволяет экранировать остаток строки от попыток проверки синтаксиса со стороны WinNT 3.51 cmd.exe ■ goto :EOF 2>nul или goto 2>nul :EOF завершают исполнение и возвращают код 1 под cmd.exe от NT 3.10, 3.51, 4.0. Для 3.10 и 3.51 это goto на несуществующую метку, а 4.0 считает корректным только синтаксис 2>nul goto :EOF (иначе тоже видит несуществующую метку!). cmd.exe от Windows 2000 и выше допускают любое положение оператора перенаправления и ошибку не выдадут. ■ Внутри скобки FOR допустима многострочность: @for %%I in (a b c ) DO @echo [%%I] Только cmd.exe, в COMMAND.COM не поддерживается ■ Проверка режима SETLOCAL EnableDelayedExpansion if '!!'=='' ... ■ COMMAND.COM 5.x FreeDOS DR-DOS не понимают символ локального echo off @ во вложенных командах @for %%I in (1 2 3) do @echo [%%I] @if 1==1 @echo [ALETHEIA] ■ if !VAR! GTR -1 (echo YES) else (echo NO) Если переменная VAR не определена, то 1) в отличие от %VAR% не будет ошибки синтаксиса, 2) значение переменной будет считаться равным 0. ■ Операция call ... без вызова подпрограммы может выполняться на порядки медленнее, нежели сама по себе простая операция ... (такая, как set VAR=...) Рассмотрим вычисление длины строки, см. https://ss64.org/viewtopic.php?t=17 Два способа - StrLen и StrLenOpt. Второй способ - без расхода памяти на копию строки и без операций отсечения частей строки, только извлечением одиночных символов по индексу - мог бы, по идее, работать быстрее. Однако из-за наличия call set "c=..." работает почти в 8 раз медленнее. :StrLen InStrVarName [OutLenVarName] SETLOCAL EnableDelayedExpansion set s=#!%~1! set len=0 for %%N in (4096 2048 1024 512 256 128 64 32 16 8 4 2 1) do if "!s:~%%N,1!" NEQ "" set /A len+=%%N& set s=!s:~%%N! ENDLOCAL& if "%~2" NEQ "" (set "%~2=%len%") else echo StrLen: %len% goto :EOF :StrLenOpt InStrVarName [OutLenVarName] SETLOCAL EnableDelayedExpansion set len=-1 if defined %~1 for %%N in (4096 2048 1024 512 256 128 64 32 16 8 4 2 1) do set /A len+=%%N& call set "c=%%%~1:~!len!,1%%"& if not defined c set /A len-=%%N set /A len+=1 ENDLOCAL& if "%~2"=="" (echo StrLen: %len%) else set "%~2=%len%" goto :EOF ■ cd /D и cd /D "" не считаются ошибочными, устанавливают ERRORLEVEL 0 ■ После включения режима EnableDelayedExpansion надо проявлять осторожность в использовании подстановок вида %Var% поскольку выдаваемые ими символы ! будут потеряны или интерпретированы как маркеры имени !VarDelayed! переменных. Однако, в режиме echo on, при использовании !WHAT! вместо %WHAT% командная строка отобразится с именем переменной !WHAT! (без подстановки содержимого), что обычно нежелательно. ■ Конструкции вида echo, IRFAN\i_view32 ... где работает: %%~$PATH:1 %%~$PATH:I {wrapper}.exe by Dm.Koteroff/MOD, т.е. вызов Win32 API CreateProcess(NULL, cmdline, ...) FAR1,FAR2,FAR3 префикс whereis:... FAR1,FAR2 запуск (из ком.строки, из FARMENU.INI) ... работает, но только для .exe, а не для .cmd/.bat: FAR3 запуск (из ком.строки, из FARMENU.INI) -- для .cmd/.bat приходится делать pushd / whereis:target / target / popd runas /user:%USERNAME% запуск hstart [/NOCONSOLE] запуск runwaitw.exe запуск conx /HideRun[Wait] запуск ... где НЕ работает: cmd запуск (cmd /c, из ком. строки, из .bat/.cmd) cmd /c start запуск Win9x "start.exe" запуск hstart /SHELL запуск hstart /[NON]ELEVATED запуск cmdow /RUN запуск FAR1,FAR2,FAR3 префикс goto:... HKLM\SOFTWARE\Microsoft\Windows\CurrentVersion\App Paths\ -- при отсутствии маршрута простой поиск на PATH там есть ■ UNC-маршруты (сетевые диски; Shared Folders основной системы в VirtualBox) .. где работают При запуске файла с расширением из списка %PATHEXT% находятся UNC маршруты в %PATH% При поиске оператором вида %~$PATH:1 или %~$PATH:I находятся UNC маршруты start /D \\UNCPath app.exe pushd \\UNCPath&& start /w app.exe & popd -- создаётся временный subst-диск Z: ... где НЕ работают cmd.exe при запуске из \\UNCPath меняет директорию на %SystemRoot% cd /D ... FAR1,FAR2,FAR3: run:nul<|UNCPath|app.exe pushd \\UNCPath && (start app.exe & popd) -- теряется рабочая директория, возможны сбои ■ Плюсы и минусы {wrapper}.cmd VS {wrapper}.exe - Форма вызова из .bat/.cmd вместо wrapper ... должна измениться на call wrapper ..., а такая замена означает, что, например, символы % не будут передаваться корректно без их удваивания - Если вызов оригинала прописан в форме app.exe, то придётся править .lnk .cmd и т.п. (менять на app.cmd, call app.cmd или cmd.exe /c app.cmd). Иногда это даже невозможно + Возможность дополнительной обработки аргументов (в конкретных случаях, где это надо) + Возможность сменить каталог на каталог цели; ведь wrapper.exe не знает каталог цели прежде вызова CreateProcess(NULL, cmdline, ...) - При запуске с рабочего UNC-маршрута, запускается в %windir%, т.е. относительные пути к файлам работать не будут - Если цель выполняется в той же консоли, при нажатии CtrlC или CtrlBreak выводит запрос "Terminate batch job (Y/N)?" - В Win9x натурально не поддерживается (нужен win95cmd.exe или KernelEx + правленый cmd.exe) - В Win3.11+win32s не поддерживается (из command.com / .bat нельзя запускать Win32 приложения); wrapper.exe там работает + Возможность выводить сообщение об ошибке в консоль + call wrapper.cmd немного быстрее, чем wrapper.exe [6:10] - wrapper.exe немного быстрее, чем %ComSpec% /c wrapper.cmd [10:13] ■ Создание переменной, которая при %раскрытии% даёт символ перевода строки Работает и с 2-символьными переводами строк \r\n и с 1-символьными \n @echo off set _NL_=^^^ ^ rem Don't delete 2 blank lines above echo abc%_NL_%def ■ if A EQU B ... выполняет усечение перед сравнением, вследствие чего -2147483648 EQU любому (недопустимому) отрицательному числу, которое меньше. 2147483647 EQU любому (недопустимому) положительному числу, которое больше. cmd.exe не умеет работать с числами за пределами 32 бит. cmd.exe отличает число вне диапазона 32 бит от произвольной строки. if -2147483648 NEQ %1 работает как if not -2147483648 EQU %1 ■ Символы @ и ; можно использовать в операторе if string==%1 ; commands чтобы не было синтаксических ошибок или непредвиденного поведения, если %1 - пустая строка. @ перед командами запрещает вывод на консоль ; перед командами игнорируется