CMD Shell Tips and Pitfalls: Difference between revisions

From miki
Jump to navigation Jump to search
No edit summary
(→‎Frequent Mistakes: reformating)
Line 5: Line 5:


== 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'm doing something (and something)...
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'm doing something [and something]...
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>


rem -------------------------------------------------------------------------------------------------------------------------------


rem Some Variable Expansion
*Some Variable Expansion:
** !!! Use <tt>%%</tt> for FOR statement in batch file only
** !!! Use single <tt>%</tt> for command line parameters only

<source lang="bash">
set MYVAR=My Var
set MYVAR=My Var
echo %MYVAR &rem EXPAND TO MYVAR
echo %MYVAR # expands to MYVAR
echo %%MYVAR &rem EXPAND TO %MyVar
echo %%MYVAR # expands to %MyVar
echo %MYVAR% &rem EXPAND TO My Var
echo %MYVAR% # expands to My Var
echo "%MYVAR" &rem EXPAND TO "MYVAR"
echo "%MYVAR" # expands to "MYVAR"
echo "%%MYVAR" &rem EXPAND TO "%MYVAR"
echo "%%MYVAR" # expands to "%MYVAR"
echo "%MYVAR%" &rem EXPAND TO "My Var"
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 &rem EXPAND TO TEST: TEST
echo TEST: %%i # expands to TEST: TEST
set MYVAR=%%i
set MYVAR=%%i
echo MYVAR: "%MYVAR%" &rem EXPAND TO TEST: ""
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%" &rem EXPAND TO TEST: "TEST"
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 149: 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 -------------------------------------------------------------------------------------------------------------------------------


rem WRONG: Never use "%1" or %1
* <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 "%~1"
type "%1" # WRONG
type "%~1" # CORRECT
</source>


rem -------------------------------------------------------------------------------------------------------------------------------


rem WRONG: in W2K, for /r skip SYSTEM files/directories (in 4NT, SYSTEM & HIDDEN files are skipped).
* <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 /F "usebackq" %%i in (`dir /a:-d /s /b`) do @echo %%i
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 -------------------------------------------------------------------------------------------------------------------------------


rem WRONG: FOR /F with other delims NOK if usebackq used (IT SEEMS THERE IS A BUFFER OVERFLOW IN CMD.EXE)
* <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 "usebackqtokens=1-4 delims=:~" %%i in ('%STRING%') do echo %%i-%%j-%%k-%%l
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


rem GOOD: Use FOR to enumerate items in a list (space, comma and semi-colon are used as separators)
* <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
:: Do some stuffs...
:: ...
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 244: 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>



Revision as of 14:18, 19 January 2009

External References

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

  • To bypass all pause commands, or to answer next set /P command
echo Y| batchpgm.bat
  • 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!)
  • 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 - %%~pnxi is used to remove trailing dot
for /R "%~1\" %%i in (.) do echo  "%%~pnxi"
  • Ask user input
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
)
  • 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%` 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 - !!! 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 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/disable Command Processor Extension or Delayed Extension
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
  • 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^)
)

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"