CMD Shell Tips and Pitfalls: Difference between revisions
(→Hints and Tips: Reformating) |
|||
(8 intermediate revisions by the same user not shown) | |||
Line 1: | Line 1: | ||
== External References == |
|||
* Excellent site on http://www.ss64.com/nt/ |
|||
** Also check pages on syntax http://www.ss64.com/nt/syntax.html |
|||
* On redirection http://www.robvanderwoude.com/index.html |
|||
== Frequent Mistakes == |
== Frequent Mistakes == |
||
<source lang="winbatch"> |
|||
rem =============================================================================================================================== |
|||
rem WIN2000 BATCH FILES FREQUENT MISTAKES |
|||
rem =============================================================================================================================== |
|||
* <font color="red">'''WRONG'''</font> - Use <tt>=</tt> for string comparison. |
|||
goto :EOF& rem to avoid execution of this example file... |
|||
* <font color="green">'''CORRECT'''</font> - Use <tt>==</tt> for string comparison. |
|||
* <font color="green">'''BETTER'''</font> - <tt>EQU</tt> is less confusing and less exposed to mistake. |
|||
<source lang="bash"> |
|||
rem ------------------------------------------------------------------------------------------------------------------------------- |
|||
if "0" = "0" echo Hello # WRONG |
|||
if "0" == "0" echo Hello # CORRECT |
|||
if "0" EQU "0" echo Hello # BETTER |
|||
</source> |
|||
rem WRONG |
|||
if "0" = "0" ( |
|||
echo Hello |
|||
) |
|||
* <font color="red">'''WRONG'''</font> - Closing bracket will close the IF statement! |
|||
rem GOOD |
|||
* <font color="green">'''CORRECT'''</font> - Use <tt>[]</tt> instead |
|||
if "0" == "0" ( |
|||
* <font color="green">'''BETTER'''</font> - Escape parenthesis with <tt>^</tt> instead |
|||
echo Hello |
|||
) |
|||
<source lang="bash"> |
|||
rem BETTER - 'EQU' is less confusing and exposed to mistake |
|||
if "0" EQU "0" ( |
if "0" EQU "0" ( |
||
rem do something |
|||
echo Hello |
|||
echo I am doing something (and something)... # WRONG |
|||
) |
) |
||
rem ------------------------------------------------------------------------------------------------------------------------------- |
|||
rem WRONG - Closing bracket will close the IF statement! |
|||
if "0" EQU "0" ( |
if "0" EQU "0" ( |
||
rem do something |
rem do something |
||
echo I |
echo I am doing something [and something]... # CORRECT |
||
) |
) |
||
rem GOOD - Use [] instead |
|||
if "0" EQU "0" ( |
if "0" EQU "0" ( |
||
rem do something |
rem do something |
||
echo I |
echo I am doing something ^(and something^)... # BETTER |
||
) |
) |
||
</source> |
|||
* <font color="red">'''WRONG'''</font> - Closing bracket will close the '''IF''' statement '''even in variable expansion!''' |
|||
rem ------------------------------------------------------------------------------------------------------------------------------- |
|||
* <font color="green">'''CORRECT'''</font> - Always quote variables with <tt>"..."</tt> |
|||
* <font color="green">'''CORRECT'''</font> - Quotes " not necessary with <tt>%%</tt> variable |
|||
<source lang="bash"> |
|||
rem WRONG - Closing bracket will close the IF statement EVEN IN VARIABLE EXPANSION! |
|||
set MYVAR=Closing) |
set MYVAR=Closing) |
||
if "0" EQU "0" ( |
if "0" EQU "0" ( |
||
rem do something |
rem do something |
||
echo My beautiful var %MYVAR%... |
echo My beautiful var %MYVAR%... # WRONG |
||
) |
) |
||
rem GOOD - Englobe bracket with quotes " " |
|||
set MYVAR=Closing) |
set MYVAR=Closing) |
||
if "0" EQU "0" ( |
if "0" EQU "0" ( |
||
rem do something |
rem do something |
||
echo My beautiful var "%MYVAR%"... |
echo My beautiful var "%MYVAR%"... # CORRECT |
||
) |
) |
||
rem GOOD - Quotes " not necessary with double % variable |
|||
for /F %%i in ("bracket)") do ( |
for /F %%i in ("bracket)") do ( |
||
echo My Closing bracket: %%i |
echo My Closing bracket: %%i # CORRECT |
||
) |
) |
||
</source> |
|||
*Some Variable Expansion: |
|||
rem ------------------------------------------------------------------------------------------------------------------------------- |
|||
** !!! Use <tt>%%</tt> for FOR statement in batch file only |
|||
** !!! Use single <tt>%</tt> for command line parameters only |
|||
<source lang="bash"> |
|||
rem Some Variable Expansion |
|||
set MYVAR=My Var |
set MYVAR=My Var |
||
echo %MYVAR |
echo %MYVAR # expands to MYVAR |
||
echo %%MYVAR |
echo %%MYVAR # expands to %MyVar |
||
echo %MYVAR% |
echo %MYVAR% # expands to My Var |
||
echo "%MYVAR" |
echo "%MYVAR" # expands to "MYVAR" |
||
echo "%%MYVAR" |
echo "%%MYVAR" # expands to "%MYVAR" |
||
echo "%MYVAR%" |
echo "%MYVAR%" # expands to "My Var" |
||
</source> |
|||
rem Use %% for FOR statement in batch file only |
|||
rem Use single % for command line parameters only |
|||
* <font color="red">'''WRONG'''</font> - <tt>%VAR%</tt> are expanded when read not when executed (early expansion)! |
|||
rem ------------------------------------------------------------------------------------------------------------------------------- |
|||
* <font color="green">'''CORRECT'''</font> - Use a subroutine ! |
|||
* <font color="green">'''BETTER'''</font> - Use '''Variable Delayed Expansion''' and syntax <tt>!VAR!</tt>(enabled with <tt>cmd.exe /V</tt> or <tt>setlocal enabledelayedexpansion</tt>)! |
|||
<source lang="bash"> |
|||
rem WRONG: %VAR% are expanded when read not when executed (early expansion)! |
|||
setlocal ENABLEDELAYEDEXPANSION |
|||
set MYVAR= |
set MYVAR= |
||
for /F %%i in ("TEST") do ( |
for /F %%i in ("TEST") do ( |
||
echo TEST: %%i |
echo TEST: %%i # expands to TEST: TEST |
||
set MYVAR=%%i |
set MYVAR=%%i |
||
echo MYVAR: "%MYVAR%" |
echo MYVAR: "%MYVAR%" # WRONG - expands to TEST: "" |
||
call :myset %%i # CORRECT - expands to TEST: "TEST" - call subroutine |
|||
echo MYVAR: "!MYVAR!" # BETTER - expands to TEST: "TEST" - delayed expansion |
|||
) |
) |
||
goto :EOF |
|||
rem GOOD: Use subroutine! |
|||
set MYVAR= |
|||
for /F %%i in ("TEST") do ( |
|||
echo TEST: %%i &rem EXPAND TO TEST: TEST |
|||
call :myset %%i |
|||
) |
|||
goto :EOF |
|||
:myset |
:myset |
||
set MYVAR=%1 |
set MYVAR=%1 |
||
echo MYVAR: "%MYVAR%" |
echo MYVAR: "%MYVAR%" # CORRECT - within sub, expands to TEST: "TEST" |
||
goto :EOF |
goto :EOF |
||
</source> |
|||
rem GOOD: Delay use of MYVAR! |
|||
set MYVAR= |
|||
for /F %%i in ("TEST") do ( |
|||
echo TEST: %%i &rem EXPAND TO TEST: TEST |
|||
set MYVAR=%%i |
|||
) |
|||
echo MYVAR: "%MYVAR%" &rem EXPAND TO TEST: "TEST" |
|||
* <font color="red">'''WRONG'''</font> - Possible syntax error if <tt>MYVAR</tt> is empty in <tt>IF ... EQU</tt> expression |
|||
rem GOOD: Use Delayed Expansion (activated by CMD /V)! |
|||
* <font color="green">'''CORRECT'''</font> - Always quote variables (we'll never repeat this enough) ! |
|||
set MYVAR= |
|||
for /F %%i in ("TEST") do ( |
|||
echo TEST: %%i &rem EXPAND TO TEST: TEST |
|||
set MYVAR=%%i |
|||
echo MYVAR: "!MYVAR!" &rem EXPAND TO TEST: "TEST" |
|||
) |
|||
<source lang="bash"> |
|||
rem ------------------------------------------------------------------------------------------------------------------------------- |
|||
rem WRONG: Possible syntax error if MYVAR is empty in IF ... EQU expression |
|||
set MYVAR= |
set MYVAR= |
||
if MYVAR EQU YES echo failed |
if MYVAR EQU YES echo failed # WRONG |
||
if "MYVAR" EQU "YES" echo failed # CORRECT |
|||
</source> |
|||
rem GOOD: Always surround var with quote |
|||
set MYVAR= |
|||
if "MYVAR" EQU "YES" echo failed |
|||
* <font color="red">'''WRONG'''</font> - <tt>SET</tt> command includes '''trailing blanks''' |
|||
rem ------------------------------------------------------------------------------------------------------------------------------- |
|||
* <font color="green">'''CORRECT'''</font> - Remove all trailing blanks in <tt>SET</tt> command |
|||
* <font color="green">'''BETTER'''</font> - Configure your favorite editor to always remove trailing blanks on every line on save |
|||
<source lang="bash"> |
|||
rem WRONG: SET command includes TRAILING BLANKS |
|||
# WRONG - set with trailing blanks |
|||
set MYDIR=C:\TEMP\ |
|||
# CORRECT - set without trailing blanks |
|||
set MYDIR=C:\TEMP\ |
set MYDIR=C:\TEMP\ |
||
</source> |
|||
rem GOOD: SET command doesn't include TRAILING BLANKS |
|||
set MYDIR=C:\TEMP\ |
|||
* <font color="red">'''WRONG'''</font> - '''trailing blanks''' inside path makes <tt>%~n1</tt> to fail ('''!!! very frequent when using an env_var with trailing blanks !!!''') |
|||
rem ------------------------------------------------------------------------------------------------------------------------------- |
|||
* <font color="green">'''CORRECT'''</font> - remove '''trailing blanks''' when using <tt>%~n1</tt> |
|||
''EDIT: This bug seems fixed in Windows XP now.'' |
|||
<source lang="bash"> |
|||
rem WRONG: trailing blanks inside path makes %~n1 to fail (!!! VERY FREQUENT WHEN USING AN ENV_VAR WITH TRAILING BLANKS !!!) |
|||
call :strip "C:\TEMP \MYFILE.ext" |
call :strip "C:\TEMP \MYFILE.ext" # WRONG... will output 'FILE' |
||
rem |
|||
rem ... will output 'FILE' |
|||
call :strip "C:\TEMP\MYFILE.ext" # CORRECT... will output 'MYFILE' |
|||
rem GOOD: remove TRAILING BLANKS when using %~n1 |
|||
call :strip "C:\TEMP\MYFILE.ext" |
|||
rem ... will output 'MYFILE' |
|||
goto :EOF |
goto :EOF |
||
Line 144: | Line 134: | ||
echo %~n1 |
echo %~n1 |
||
goto :EOF |
goto :EOF |
||
</source> |
|||
* <font color="red">'''WRONG'''</font> - <tt>%*</tt> not modified by <tt>SHIFT</tt> |
|||
rem ------------------------------------------------------------------------------------------------------------------------------- |
|||
* <font color="green">'''CORRECT'''</font> - Use a loop instead with <tt>%~1</tt> |
|||
<source lang="bash"> |
|||
rem WRONG: %* not modified by SHIFT |
|||
echo %* |
echo %* |
||
SHIFT |
SHIFT |
||
echo %* # WRONG... will output the same string |
|||
echo %* |
|||
rem ... will output the same string |
|||
rem GOOD: Use a loop instead |
|||
if defined FILELIST set FILELIST= |
|||
:getFileList |
:getFileList |
||
if "%~1" EQU "" goto :noMoreParam |
if "%~1" EQU "" goto :noMoreParam |
||
echo "%~1" # CORRECT... Loop with %~1 |
|||
set FILELIST=%FILELIST% "%~1" |
|||
SHIFT |
SHIFT |
||
goto :getFileList |
goto :getFileList |
||
:noMoreParam |
:noMoreParam |
||
</source> |
|||
rem ------------------------------------------------------------------------------------------------------------------------------- |
|||
* <font color="red">'''WRONG'''</font> - Never use <tt>"%1"</tt> or <tt>%1</tt> |
|||
* <font color="green">'''CORRECT'''</font> - Always use <tt>"%~1"</tt> (always correctly quoted) |
|||
type "%1" |
|||
<source lang="bash"> |
|||
rem GOOD: Always use "%~1" |
|||
type "% |
type "%1" # WRONG |
||
type "%~1" # CORRECT |
|||
</source> |
|||
rem ------------------------------------------------------------------------------------------------------------------------------- |
|||
* <font color="red">'''WRONG'''</font> - in W2K, <tt>FOR /R</tt> skips SYSTEM files/directories (in 4NT, SYSTEM & HIDDEN files are skipped). |
|||
* <font color="green">'''CORRECT'''</font> - Use DIR instead (here we list all files that is not a directory...) |
|||
for /r "." %%i in (*.*) do @echo %%i |
|||
* <font color="green">'''CORRECT'''</font> - Use DIR instead (here we list all directories) |
|||
<source lang="bash"> |
|||
rem GOOD: Use DIR instead (here we list all files that is not a directory...) |
|||
for / |
for /r "." %%i in (*.*) do @echo %%i # WRONG - Skips SYSTEM files/directories |
||
for /F "usebackq" %%i in (`dir /a:-d /s /b`) do @echo %%i # CORRECT (all files that is not a directory) |
|||
for /F "usebackq" %%i in (`dir /a:d /s /b`) do @echo %%i # CORRECT (all directories) |
|||
</source> |
|||
rem GOOD: Use DIR instead (here we list all directories) |
|||
for /F "usebackq" %%i in (`dir /a:d /s /b`) do @echo %%i |
|||
* <font color="red">'''WRONG'''</font> - <tt>cmd.exe</tt> remove surrounding quotes, except in some very specific cases (see <tt>CMD /?</tt>). |
|||
rem ------------------------------------------------------------------------------------------------------------------------------- |
|||
* <font color="green">'''CORRECT'''</font> - Always surround with quotes and always use flag <tt>cmd.exe /S</tt> to force quote removal. |
|||
<source lang="bash"> |
|||
rem WRONG: CMD remove surrounding quotes, except in some very specific cases (see CMD /?). |
|||
rem |
|||
rem Hence first quote in %MYBAT% and last quote in %MYPARAM% will be removed. |
|||
set MYBAT="C:\Program Files\MyBat\MyBat.bat" |
set MYBAT="C:\Program Files\MyBat\MyBat.bat" |
||
set MYPARAM="parameter 1" "parameter 2" |
set MYPARAM="parameter 1" "parameter 2" |
||
cmd /V:F /C %MYBAT% %MYPARAM% |
cmd /V:F /C %MYBAT% %MYPARAM% # WRONG - quotes will be removed from parameters |
||
rem GOOD: Always surround with quotes and always use flag /S to force quote removal |
|||
set MYBAT="C:\Program Files\MyBat\MyBat.bat" |
set MYBAT="C:\Program Files\MyBat\MyBat.bat" |
||
set MYPARAM="parameter 1" "parameter 2" |
set MYPARAM="parameter 1" "parameter 2" |
||
cmd /V:F /S /C "%MYBAT% %MYPARAM%" |
cmd /V:F /S /C "%MYBAT% %MYPARAM%" # CORRECT - First quote in %MYBAT% and last quote in %MYPARAM% will be removed. |
||
</source> |
|||
rem ------------------------------------------------------------------------------------------------------------------------------- |
|||
* <font color="red">'''WRONG'''</font> - <tt>FOR /F</tt> with other delims NOK if <tt>usebackq</tt> used (it seems there is a buffer overflow in cmd.exe) |
|||
* <font color="green">'''CORRECT'''</font> - <tt>FOR /F</tt> with other delims is ok if <tt>usebackq</tt> not used |
|||
''EDIT: This bug seems fixed in Windows XP now.'' |
|||
<source lang="bash"> |
|||
set STRING=MaskOSLib Maker~1:dir:1 |
set STRING=MaskOSLib Maker~1:dir:1 |
||
for /F " |
for /F "usebackq tokens=1-4 delims=:~" %%i in ('%STRING%') do echo %%i-%%j-%%k-%%l # WRONG |
||
goto :EOF |
goto :EOF |
||
rem GOOD: FOR /F with other delims is ok if usebackq not used |
|||
set STRING="MaskOSLib Maker~1:dir:1" |
set STRING="MaskOSLib Maker~1:dir:1" |
||
for /F "tokens=1-4 delims=:~" %%i in (%STRING%) do echo %%i-%%j-%%k-%%l |
for /F "tokens=1-4 delims=:~" %%i in (%STRING%) do echo %%i-%%j-%%k-%%l # CORRECT |
||
goto :EOF |
goto :EOF |
||
</source> |
|||
rem WRONG: FOR /F to enumerate items in a list |
|||
set STRING=value1 value2 value3 |
|||
for /F %%i in ("%STRING%") do echo %%i |
|||
goto :EOF |
|||
* <font color="red">'''WRONG'''</font> - Use <tt>FOR /F</tt> to enumerate items in a list |
|||
* <font color="green">'''CORRECT'''</font> - Use <tt>FOR</tt> to enumerate items in a list (space, comma and semi-colon are used as separators) |
|||
<source lang="bash"> |
|||
set STRING=value1 value2 value3 |
set STRING=value1 value2 value3 |
||
for %%i in (%STRING%) do echo %%i |
for /F %%i in ("%STRING%") do echo %%i # WRONG |
||
for %%i in (%STRING%) do echo %%i # CORRECT |
|||
goto :EOF |
goto :EOF |
||
</source> |
|||
* <font color="red">'''WRONG'''</font> - returning an ERRORLEVEL to VBS with an ending goto :EOF (or whatever) |
|||
rem ------------------------------------------------------------------------------------------------------------------------------- |
|||
* <font color="green">'''CORRECT'''</font> - Set ERRORLEVEL as the very last operation executed |
|||
rem WRONG: RETURNING AN ERRORLEVEL TO VBS WITH AN ENDING GOTO :EOF (or whatever) : ERRORLEVEL IS SEEN AS = 0 BY VBS SCRIPT |
|||
<source lang="bash"> |
|||
:: following set ERRORLEVEL to 1, and then exit |
:: following set ERRORLEVEL to 1, and then exit |
||
VERIFY OTHER 2 |
VERIFY OTHER 2>/NUL |
||
goto :EOF # WRONG - Calling script will see an ERRORLEVEL 0 |
|||
goto :EOF |
|||
::----------------------------- |
|||
rem GOOD: DO VERIFY OTHER 2 AS THE VERY LAST OPERATION. TO AVOID HIDDEN ENDLOCAL THAT WOULD BE DONE AFTER, CLOSE ALL LOCALS |
|||
setlocal |
setlocal |
||
:: |
:: ... |
||
if ... goto :FINISHFAIL |
|||
:: We must fail for some condition |
|||
if %COND% equ "YES" goto :FINISHFAIL |
|||
:: Do some stuffs and finish... |
:: Do some stuffs and finish... |
||
endlocal |
endlocal |
||
Line 239: | Line 234: | ||
:: close locals and set ERRORLEVEL |
:: close locals and set ERRORLEVEL |
||
endlocal |
endlocal |
||
VERIFY OTHER 2>/NUL # CORRECT - Calling script will see an ERRORLEVEL > 0 |
|||
VERIFY OTHER 2>NUL |
|||
</source> |
</source> |
||
Line 245: | Line 240: | ||
=== Miscellaneous === |
=== Miscellaneous === |
||
==== Bypass pause ==== |
|||
* To bypass all pause commands, or to answer next <tt>set /P</tt> command |
|||
To bypass all pause commands, or to answer next <tt>set /P</tt> command |
|||
<source lang="winbatch"> |
<source lang="winbatch"> |
||
Line 251: | Line 247: | ||
</source> |
</source> |
||
==== Enumerate list ==== |
|||
Enumerate elements in a list |
|||
<source lang="winbatch"> |
<source lang="winbatch"> |
||
Line 259: | Line 257: | ||
</source> |
</source> |
||
==== Alternate rem ==== |
|||
<source lang="winbatch"> |
<source lang="winbatch"> |
||
Line 265: | Line 263: | ||
</source> |
</source> |
||
==== Inlined rem ==== |
|||
<source lang="winbatch"> |
<source lang="winbatch"> |
||
Line 271: | Line 269: | ||
</source> |
</source> |
||
==== Persistent errorlevel ==== |
|||
* Make errorlevel persistent - env. var override ERRORLEVEL if defined |
|||
Make errorlevel persistent - env. var override ERRORLEVEL if defined |
|||
<source lang="winbatch"> |
<source lang="winbatch"> |
||
Line 278: | Line 277: | ||
</source> |
</source> |
||
==== Change dir and drive ==== |
|||
<source lang="winbatch"> |
<source lang="winbatch"> |
||
Line 284: | Line 283: | ||
</source> |
</source> |
||
==== Parsing command result ==== |
|||
<source lang="winbatch"> |
<source lang="winbatch"> |
||
Line 290: | Line 289: | ||
</source> |
</source> |
||
==== Echo an empty line ==== |
|||
<source lang="winbatch"> |
<source lang="winbatch"> |
||
Line 296: | Line 295: | ||
</source> |
</source> |
||
==== Scan directory and subdir ==== |
|||
Scan directory and subdir - <tt>%%~pnxi</tt> is used to remove trailing dot |
|||
<source lang="winbatch"> |
<source lang="winbatch"> |
||
Line 302: | Line 303: | ||
</source> |
</source> |
||
==== Ask user input ==== |
|||
Ask user input (note that these scripts required ''Delayed Expansion'' to be enabled) |
|||
<source lang="winbatch"> |
<source lang="winbatch"> |
||
setlocal enabledelayedexpansion |
|||
set /P CONFIRM=... Empty files found [might crash GNU/Indent]... Delete [Y/N/Abort]? |
set /P CONFIRM=... Empty files found [might crash GNU/Indent]... Delete [Y/N/Abort]? |
||
if /I " |
if /I "!CONFIRM:~0,1!" EQU "Y" ( |
||
echo user says YES |
echo user says YES |
||
) else ( |
) else ( |
||
Line 313: | Line 317: | ||
</source> |
</source> |
||
Ask user input with default value |
|||
* Set errorlevel in subroutine |
|||
<source lang="winbatch"> |
|||
setlocal enabledelayedexpansion |
|||
set USER=Y |
|||
set /P USER=Do you want to DISABLE it (Y/n^) |
|||
if /I "!USER:~0,1!" EQU "y" ( |
|||
echo user said YES |
|||
) else ( |
|||
echo user said NO |
|||
) |
|||
</source> |
|||
==== Set errorlevel in subroutine ==== |
|||
<source lang="winbatch"> |
<source lang="winbatch"> |
||
Line 326: | Line 343: | ||
</source> |
</source> |
||
==== Remove surrounding quotes in argument ==== |
|||
<source lang="winbatch"> |
<source lang="winbatch"> |
||
Line 334: | Line 351: | ||
</source> |
</source> |
||
==== Use <tt>`echo %ENV_VAR%`</tt> in FOR loops ==== |
|||
Use <tt>`echo %ENV_VAR%`</tt> instead of <tt>'%ENV_VAR%'</tt> in FOR loops |
|||
<source lang="winbatch"> |
<source lang="winbatch"> |
||
Line 344: | Line 363: | ||
</source> |
</source> |
||
==== Enumerate items in a list separated by a blank ==== |
|||
Enumerate items in a list separated by a blank - '''!!! The list may not contain joker like <tt>*</tt> or <tt>&</tt> !!!''' |
|||
<source lang="winbatch"> |
<source lang="winbatch"> |
||
Line 351: | Line 371: | ||
</source> |
</source> |
||
==== Enumerate items in a list separated by another token ==== |
|||
Enumerate items in a list separated by an another token (list can be quite long) |
|||
<source lang="winbatch"> |
<source lang="winbatch"> |
||
Line 365: | Line 386: | ||
</source> |
</source> |
||
'''Yet another solution''' To enumerate items in a list separated by an another token (list can be quite long) |
|||
<source lang="winbatch"> |
<source lang="winbatch"> |
||
Line 378: | Line 399: | ||
</source> |
</source> |
||
==== Enable Command Processor Extensions / Delayed Expansion ==== |
|||
<source lang="winbatch"> |
<source lang="winbatch"> |
||
Line 388: | Line 409: | ||
</source> |
</source> |
||
==== Know whether a name refers to an existing file or to an existing dir ==== |
|||
<source lang="winbatch"> |
<source lang="winbatch"> |
||
Line 403: | Line 424: | ||
</source> |
</source> |
||
==== String length subroutine ==== |
|||
* An optimized sub-routine that returns the length of a string (requires ''Delayed Expansion'' to be enabled) |
|||
An optimized sub-routine that returns the length of a string (requires ''Delayed Expansion'' to be enabled) |
|||
<source lang="winbatch"> |
<source lang="winbatch"> |
||
Line 423: | Line 445: | ||
) |
) |
||
goto :nextchar |
goto :nextchar |
||
</source> |
|||
==== Escaping characters ==== |
|||
Use <tt>^)</tt> to escape parenthesis in a command block |
|||
<source lang="winbatch"> |
|||
for %%i in (*.*) do ( |
|||
echo first one: %%i (hello^) |
|||
echo second one: %%i (hello again^) |
|||
) |
|||
</source> |
|||
==== Test whether a task or service is running ==== |
|||
Here an example to test whether a given task or process is currently running: |
|||
<source lang="winbatch"> |
|||
rem Test whether a service is running.... |
|||
net start | find /i "automatic updates" > \nul |
|||
if ERRORLEVEL 1 ( |
|||
echo No, it is not running |
|||
) |
|||
rem Test whether a task is running.... |
|||
tasklist | find /i "task" >\nul 2>\nul |
|||
if ERRORLEVEL 1 ( |
|||
echo No, it is not running |
|||
) |
|||
rem Test whether a task is running (more reliable).... |
|||
tasklist /FI "IMAGENAME eq task" 2>\nul | find /i "task" >\nul 2>\nul |
|||
if ERRORLEVEL 1 ( |
|||
echo No, it is not running |
|||
) |
|||
</source> |
|||
==== Parsing <tt>tasklist</tt> output ==== |
|||
It seems difficult to parse the output generated by the command <tt>tasklist</tt>. For instance the following command doesn't work as foreseen. |
|||
<source lang="winbatch"> |
|||
> tasklist /svc | find /i "exe" |
|||
</source> |
|||
We can circumvent this using a for loop. Type in a batch file: |
|||
<source lang="winbatch"> |
|||
@echo off |
|||
setlocal enabledelayedexpansion |
|||
for /F "usebackq tokens=1,2,3" %%i in (`tasklist /svc`) do ( |
|||
if /I "%%k" EQU "wudfsvc" set UDF_PID=%%j |
|||
) |
|||
echo PID is %UDF_PID% |
|||
goto :EOF |
|||
</source> |
</source> |
||
Latest revision as of 13:29, 3 September 2009
External References
- Excellent site on http://www.ss64.com/nt/
- Also check pages on syntax http://www.ss64.com/nt/syntax.html
- On redirection http://www.robvanderwoude.com/index.html
Frequent Mistakes
- WRONG - Use = for string comparison.
- CORRECT - Use == for string comparison.
- BETTER - EQU is less confusing and less exposed to mistake.
if "0" = "0" echo Hello # WRONG
if "0" == "0" echo Hello # CORRECT
if "0" EQU "0" echo Hello # BETTER
- WRONG - Closing bracket will close the IF statement!
- CORRECT - Use [] instead
- BETTER - Escape parenthesis with ^ instead
if "0" EQU "0" (
rem do something
echo I am doing something (and something)... # WRONG
)
if "0" EQU "0" (
rem do something
echo I am doing something [and something]... # CORRECT
)
if "0" EQU "0" (
rem do something
echo I am doing something ^(and something^)... # BETTER
)
- WRONG - Closing bracket will close the IF statement even in variable expansion!
- CORRECT - Always quote variables with "..."
- CORRECT - Quotes " not necessary with %% variable
set MYVAR=Closing)
if "0" EQU "0" (
rem do something
echo My beautiful var %MYVAR%... # WRONG
)
set MYVAR=Closing)
if "0" EQU "0" (
rem do something
echo My beautiful var "%MYVAR%"... # CORRECT
)
for /F %%i in ("bracket)") do (
echo My Closing bracket: %%i # CORRECT
)
- Some Variable Expansion:
- !!! Use %% for FOR statement in batch file only
- !!! Use single % for command line parameters only
set MYVAR=My Var
echo %MYVAR # expands to MYVAR
echo %%MYVAR # expands to %MyVar
echo %MYVAR% # expands to My Var
echo "%MYVAR" # expands to "MYVAR"
echo "%%MYVAR" # expands to "%MYVAR"
echo "%MYVAR%" # expands to "My Var"
- WRONG - %VAR% are expanded when read not when executed (early expansion)!
- CORRECT - Use a subroutine !
- BETTER - Use Variable Delayed Expansion and syntax !VAR!(enabled with cmd.exe /V or setlocal enabledelayedexpansion)!
setlocal ENABLEDELAYEDEXPANSION
set MYVAR=
for /F %%i in ("TEST") do (
echo TEST: %%i # expands to TEST: TEST
set MYVAR=%%i
echo MYVAR: "%MYVAR%" # WRONG - expands to TEST: ""
call :myset %%i # CORRECT - expands to TEST: "TEST" - call subroutine
echo MYVAR: "!MYVAR!" # BETTER - expands to TEST: "TEST" - delayed expansion
)
goto :EOF
:myset
set MYVAR=%1
echo MYVAR: "%MYVAR%" # CORRECT - within sub, expands to TEST: "TEST"
goto :EOF
- WRONG - Possible syntax error if MYVAR is empty in IF ... EQU expression
- CORRECT - Always quote variables (we'll never repeat this enough) !
set MYVAR=
if MYVAR EQU YES echo failed # WRONG
if "MYVAR" EQU "YES" echo failed # CORRECT
- WRONG - SET command includes trailing blanks
- CORRECT - Remove all trailing blanks in SET command
- BETTER - Configure your favorite editor to always remove trailing blanks on every line on save
# WRONG - set with trailing blanks
set MYDIR=C:\TEMP\
# CORRECT - set without trailing blanks
set MYDIR=C:\TEMP\
- WRONG - trailing blanks inside path makes %~n1 to fail (!!! very frequent when using an env_var with trailing blanks !!!)
- CORRECT - remove trailing blanks when using %~n1
EDIT: This bug seems fixed in Windows XP now.
call :strip "C:\TEMP \MYFILE.ext" # WRONG... will output 'FILE'
rem
call :strip "C:\TEMP\MYFILE.ext" # CORRECT... will output 'MYFILE'
goto :EOF
:strip
echo %~n1
goto :EOF
- WRONG - %* not modified by SHIFT
- CORRECT - Use a loop instead with %~1
echo %*
SHIFT
echo %* # WRONG... will output the same string
:getFileList
if "%~1" EQU "" goto :noMoreParam
echo "%~1" # CORRECT... Loop with %~1
SHIFT
goto :getFileList
:noMoreParam
- WRONG - Never use "%1" or %1
- CORRECT - Always use "%~1" (always correctly quoted)
type "%1" # WRONG
type "%~1" # CORRECT
- WRONG - in W2K, FOR /R skips SYSTEM files/directories (in 4NT, SYSTEM & HIDDEN files are skipped).
- CORRECT - Use DIR instead (here we list all files that is not a directory...)
- CORRECT - Use DIR instead (here we list all directories)
for /r "." %%i in (*.*) do @echo %%i # WRONG - Skips SYSTEM files/directories
for /F "usebackq" %%i in (`dir /a:-d /s /b`) do @echo %%i # CORRECT (all files that is not a directory)
for /F "usebackq" %%i in (`dir /a:d /s /b`) do @echo %%i # CORRECT (all directories)
- WRONG - cmd.exe remove surrounding quotes, except in some very specific cases (see CMD /?).
- CORRECT - Always surround with quotes and always use flag cmd.exe /S to force quote removal.
rem
set MYBAT="C:\Program Files\MyBat\MyBat.bat"
set MYPARAM="parameter 1" "parameter 2"
cmd /V:F /C %MYBAT% %MYPARAM% # WRONG - quotes will be removed from parameters
set MYBAT="C:\Program Files\MyBat\MyBat.bat"
set MYPARAM="parameter 1" "parameter 2"
cmd /V:F /S /C "%MYBAT% %MYPARAM%" # CORRECT - First quote in %MYBAT% and last quote in %MYPARAM% will be removed.
- WRONG - FOR /F with other delims NOK if usebackq used (it seems there is a buffer overflow in cmd.exe)
- CORRECT - FOR /F with other delims is ok if usebackq not used
EDIT: This bug seems fixed in Windows XP now.
set STRING=MaskOSLib Maker~1:dir:1
for /F "usebackq tokens=1-4 delims=:~" %%i in ('%STRING%') do echo %%i-%%j-%%k-%%l # WRONG
goto :EOF
set STRING="MaskOSLib Maker~1:dir:1"
for /F "tokens=1-4 delims=:~" %%i in (%STRING%) do echo %%i-%%j-%%k-%%l # CORRECT
goto :EOF
- WRONG - Use FOR /F to enumerate items in a list
- CORRECT - Use FOR to enumerate items in a list (space, comma and semi-colon are used as separators)
set STRING=value1 value2 value3
for /F %%i in ("%STRING%") do echo %%i # WRONG
for %%i in (%STRING%) do echo %%i # CORRECT
goto :EOF
- WRONG - returning an ERRORLEVEL to VBS with an ending goto :EOF (or whatever)
- CORRECT - Set ERRORLEVEL as the very last operation executed
:: following set ERRORLEVEL to 1, and then exit
VERIFY OTHER 2>/NUL
goto :EOF # WRONG - Calling script will see an ERRORLEVEL 0
::-----------------------------
setlocal
:: ...
if ... goto :FINISHFAIL
:: Do some stuffs and finish...
endlocal
goto :EOF
:FINISHFAIL
:: close locals and set ERRORLEVEL
endlocal
VERIFY OTHER 2>/NUL # CORRECT - Calling script will see an ERRORLEVEL > 0
Hints and Tips
Miscellaneous
Bypass pause
To bypass all pause commands, or to answer next set /P command
echo Y| batchpgm.bat
Enumerate list
Enumerate elements in a list
set SOURCEFILEEXT=.c .h
set TEXTFILEEXT=%SOURCEFILEEXT% .txt .ddf
for %i in (%TEXTFILEEXT%) do echo %i
Alternate rem
:: This is also a remark
Inlined rem
echo Hello world &rem ampersand must be used for inlined rem (all space on the left are part of echo command!)
Persistent errorlevel
Make errorlevel persistent - env. var override ERRORLEVEL if defined
if ERRORLEVEL 1 set ERRORLEVEL=%ERRORLEVEL%
echo %ERRORLEVEL%
Change dir and drive
cd /D "%TEMP%"
Parsing command result
for /F "usebackq tokens=*" %%i in (`isText /sv %1`) do echo ... invalid ASCII: "%%i"
Echo an empty line
echo.
Scan directory and subdir
Scan directory and subdir - %%~pnxi is used to remove trailing dot
for /R "%~1\" %%i in (.) do echo "%%~pnxi"
Ask user input
Ask user input (note that these scripts required Delayed Expansion to be enabled)
setlocal enabledelayedexpansion
set /P CONFIRM=... Empty files found [might crash GNU/Indent]... Delete [Y/N/Abort]?
if /I "!CONFIRM:~0,1!" EQU "Y" (
echo user says YES
) else (
echo user says NO
)
Ask user input with default value
setlocal enabledelayedexpansion
set USER=Y
set /P USER=Do you want to DISABLE it (Y/n^)
if /I "!USER:~0,1!" EQU "y" (
echo user said YES
) else (
echo user said NO
)
Set errorlevel in subroutine
Call :MySub "1"
goto :Continue
:MySub
if "%1" NEQ "1" exit /b 1
goto :EOF
:Continue
Remove surrounding quotes in argument
echo with or without quote: %1
echo always without quote: %~1
echo always with quote: "%~1"
Use `echo %ENV_VAR%` in FOR loops
Use `echo %ENV_VAR%` instead of '%ENV_VAR%' in FOR loops
rem Assume ENV_VAR is set to a very long list of items delimited by ;
rem The following fails when ENV_VAR is too long
for /F "usebackq delims=;" %%i in ('%ENV_VAR%') do echo %%i
rem The following works when ENV_VAR is too long
for /F "usebackq delims=;" %%i in (`echo %ENV_VAR%`) do echo %%i
Enumerate items in a list separated by a blank
Enumerate items in a list separated by a blank - !!! The list may not contain joker like * or & !!!
set TEXTFILEEXT=.txt .ddf .c .h
for %%i in (%TEXTFILEEXT%) do echo *%%i
Enumerate items in a list separated by another token
Enumerate items in a list separated by an another token (list can be quite long)
set ENV_VAR="item1";"item2";"item3"
for /F "usebackq delims=;" %%i in (`echo %ENV_VAR%`) do call :enumerate %%i
rem ... For loop is only to translate ; into blanks
goto :EOF
:enumerate
if "%~1" equ "" goto :EOF
echo "%~1"
SHIFT
goto :enumerate
Yet another solution To enumerate items in a list separated by an another token (list can be quite long)
set ENV_VAR="item1";"item2";"item3"; my item
call :enumerate %ENV_VAR:;= %
goto :EOF
:enumerate
if "%~1" equ "" goto :EOF
echo "%~1"
SHIFT
goto :enumerate
Enable Command Processor Extensions / Delayed Expansion
SETLOCAL ENABLEEXTENSIONS
SETLOCAL DISABLEEXTENSIONS
SETLOCAL ENABLEDELAYEDEXPANSION
SETLOCAL DISABLEDELAYEDEXPANSION
rem ... setlocal set errorlevel to 0 if successful - can be used to detect if extensions are supported
Know whether a name refers to an existing file or to an existing dir
if exist "%~1" goto :isFile
pushd "%~1"
if errorlevel 1 goto :doesntexist
popd
:isDir
rem %1 is a directory
:isFile
rem %1 is a file
:doesntexist
rem %1 does not exist
String length subroutine
An optimized sub-routine that returns the length of a string (requires Delayed Expansion to be enabled)
::------ GetLength ----------------------------------------------------------------------------------------------------------------
:: %1 = string whose length must be returned
:: RESULT = length
:GetLength
set _STR=_%~1
set RESULT=0
set _STEP=128
:nextchar
if "%_STEP%" EQU "0" goto :EOF
set /A _I=RESULT + _STEP
if "!_STR:~%_I%!" EQU "" (
set /A "_STEP>>=1"
) ELSE (
set /A RESULT=RESULT + _STEP
if "%_STEP%" NEQ "128" set /A "_STEP>>=1"
)
goto :nextchar
Escaping characters
Use ^) to escape parenthesis in a command block
for %%i in (*.*) do (
echo first one: %%i (hello^)
echo second one: %%i (hello again^)
)
Test whether a task or service is running
Here an example to test whether a given task or process is currently running:
rem Test whether a service is running....
net start | find /i "automatic updates" > \nul
if ERRORLEVEL 1 (
echo No, it is not running
)
rem Test whether a task is running....
tasklist | find /i "task" >\nul 2>\nul
if ERRORLEVEL 1 (
echo No, it is not running
)
rem Test whether a task is running (more reliable)....
tasklist /FI "IMAGENAME eq task" 2>\nul | find /i "task" >\nul 2>\nul
if ERRORLEVEL 1 (
echo No, it is not running
)
Parsing tasklist output
It seems difficult to parse the output generated by the command tasklist. For instance the following command doesn't work as foreseen.
> tasklist /svc | find /i "exe"
We can circumvent this using a for loop. Type in a batch file:
@echo off
setlocal enabledelayedexpansion
for /F "usebackq tokens=1,2,3" %%i in (`tasklist /svc`) do (
if /I "%%k" EQU "wudfsvc" set UDF_PID=%%j
)
echo PID is %UDF_PID%
goto :EOF
Redirections
Info from http://www.robvanderwoude.com/index.html
command > file Write standard output of command to file
command 1> file Write standard output of command to file (same as previous)
command 2> file Write standard error of command to file (OS/2 and NT)
command > file 2>&1 Write both standard output and standard error of command to file (OS/2 and NT)
command >> file Append standard output of command to file
command 1>> file Append standard output of command to file (same as previous)
command 2>> file Append standard error of command to file (OS/2 and NT)
command >> file 2>&1 Append both standard output and standard error of command to file (OS/2 and NT)
commandA ¦ commandB Redirect standard output of commandA to standard input of commandB
command < file Command gets standard input from file
command 2>&1 Command's standard error is redirected to standard output (OS/2 and NT)
command 1>&2 Command's standard output is redirected to standard error (OS/2 and NT)
Location of 2>&1 is critical. It must be placed at the end of the line, or right before the next pipe.
To redirect both standard output and standard error to a file, use 2>&1 at the end:
echo Everything from stdout and stderr to a single file > file 2>&1
Redirection can be placed at the beginning of the line to increase readability, but beware of side effects:
ECHO Directory of all files on C: >> LOG1.LOG
DIR C:\ /S >> LOG1.LOG
rem is the same as the more readable version:
>> LOG1.LOG ECHO Directory of all files on C:
>> LOG1.LOG DIR C:\ /S
rem But the following
VER ¦ TIME > LOG1.LOG
rem is not the same as the line below (where it is VER that is redirected to LOG1.LOG !!!)
> LOG1.LOG VER ¦ TIME
Each redirection device exists in every directory on every drive so redirection to a device like NUL, AUX, LPTn, COMn, PRN COST 1 file handle per device per directory where the redirection is done !!! To avoid this, avoid redirect to NUL, but redirect to \NUL or better %TEMP%\NUL. Also use PRINT instead of redirecting to LPTn.
echo A way to trash output>NUL
echo This is a better way >\NUL
Using SED in a CMD batch file
Main problem of using SED is escaping the quote " in sed command.
- In general, use \" to escape "
sed "s!\"Micro!\"Macro!g;" "temp.txt" ::Change '"Micro' into '"Macro'
- ... but it doesn't work anymore if query contains \"[^\"]*
sed "s!\"[^\"]*soft!hard!g;" "temp.txt" ::DOESN'T WORK
- Solution: Don't quote the sed command (Ok if no space in it)!
sed s!\"[^\"]*soft!hard!g; "temp.txt" ::WORKS !
- Solution (cont'd): if there is space, escape them using \x20 or \t.
- Note that [^\"]*\" works even if quoted:
sed "s![^\"]*\"Micro!Macro!g;" "temp.txt" ::WORKS !
- Escaping " and redirecting output
- \" is not recognised by cmd.exe, but considered as a backslash followed by an opening/closing quote
- → so redirection will work only if not enclosed in quotes according to cmd.exe:
sed "s![^\"]*\"Micro!Macro!g;" "temp.txt" > "result.txt" ::WORKS ! (even number of \")
sed "s![^\"]*\"Micro!\"Macro!g;" "temp.txt" > "result.txt" ::DOESN'T WORK ! (odd number of \")
- Solution: Use a sub-routine:
call :DOIT > "result.txt"
rem ...
:DOIT
sed "s![^\"]*\"Micro!\"Macro!g;" "temp.txt" > "result.txt"