1@echo off 2 3:: 4:: Some basic tests 5:: 6 7echo ------------ Testing FOR loop ------------ 8echo --- Multiple lines 9for %%i in (A 10B 11C) do echo %%i 12 13echo --- Lines and spaces 14for %%i in (D 15 E 16 F) do echo %%i 17 18echo --- Multiple lines and commas 19for %%i in (G, 20H, 21I 22) do echo %%i 23 24echo --- Multiple lines and %%I 25:: The FOR-variable is case-sensitive 26for %%i in (J 27 K 28 L) do echo %%I 29 30echo --- Multiple lines and %%j 31for %%i in (M, 32N, 33O 34) do echo %%j 35 36echo --- FOR /F token parsing 37:: This test requires extensions being enabled 38setlocal enableextensions 39 40set TEST_STRING="_ ` a b c d e f g h i j k l m n o p q r s t u v w x y z { | } ~ ? @ [ \ ] _ ` a b c d e f g h i j k l m n o p q r s t u v w x y z { | } ~ ? @ [ \ ]" 41set "ECHO_STRING=?=%%? @=%%@ A=%%A B=%%B C=%%C D=%%D E=%%E F=%%F G=%%G H=%%H I=%%I J=%%J K=%%K L=%%L M=%%M N=%%N O=%%O P=%%P Q=%%Q R=%%R S=%%S T=%%T U=%%U V=%%V W=%%W X=%%X Y=%%Y Z=%%Z [=%%[ \=%%\ ]=%%] ^^=%%^^ _=%%_ `=%%` a=%%a b=%%b c=%%c d=%%d e=%%e f=%%f g=%%g h=%%h i=%%i j=%%j k=%%k l=%%l m=%%m n=%%n o=%%o p=%%p q=%%q r=%%r s=%%s t=%%t u=%%u v=%%v w=%%w x=%%x y=%%y z=%%z {=%%{ ^|=%%^| }=%%} ^~=%%^~" 42 43echo. 44 45:: Bug 1: Ranges that are not specified in increasing order are ignored. 46:: Token numbers strictly greater than 31 are just ignored, and if they 47:: appear in a range, the whole range is ignored. 48 49for /f "tokens=30-32" %%? in (%TEST_STRING%) do echo %ECHO_STRING% 50echo. 51for /f "tokens=5-1" %%? in (%TEST_STRING%) do echo %ECHO_STRING% 52echo. 53 54:: Bug 2: Ranges that partially overlap: too many variables are being allocated, 55:: while only a subset is actually used. This leads to the extra variables returning 56:: empty strings. 57 58for /f "tokens=1-31,31,31" %%? in (%TEST_STRING%) do echo %ECHO_STRING% 59echo. 60for /f "tokens=1-31,1-31" %%? in (%TEST_STRING%) do echo %ECHO_STRING% 61echo. 62for /f "tokens=1-5,3,5,6" %%? in (%TEST_STRING%) do echo %ECHO_STRING% 63echo. 64for /f "tokens=1-31,* tokens=1-31 tokens=1-20,*" %%? in (%TEST_STRING%) do echo %ECHO_STRING% 65echo. 66 67:: For comparison, this works: 68for /f "tokens=1-5,6" %%? in (%TEST_STRING%) do echo %ECHO_STRING% 69echo. 70for /f "tokens=1-5,6-10" %%? in (%TEST_STRING%) do echo %ECHO_STRING% 71echo. 72 73endlocal 74 75 76echo ---------- Testing AND operator ---------- 77:: Test for TRUE condition - Should be displayed 78ver | find "Ver" > NUL && echo TRUE AND condition 79 80:: Test for FALSE condition - Should not display 81ver | find "1234" > NUL && echo FALSE AND condition 82 83echo ---------- Testing OR operator ----------- 84:: Test for TRUE condition - Should not display 85ver | find "Ver" > NUL || echo TRUE OR condition 86 87:: Test for FALSE condition - Should be displayed 88ver | find "1234" > NUL || echo FALSE OR condition 89 90 91 92:: 93:: Testing CMD exit codes and errorlevels. 94:: 95:: Observations: 96:: - OR operator || converts the LHS error code to ERRORLEVEL only on failure; 97:: - Pipe operator | converts the last error code to ERRORLEVEL. 98:: 99:: See https://stackoverflow.com/a/34987886/13530036 100:: and https://stackoverflow.com/a/34937706/13530036 101:: for more details. 102:: 103setlocal enableextensions 104 105echo ---------- Testing CMD exit codes and errorlevels ---------- 106 107:: Tests for CMD returned exit code. 108 109echo --- CMD /C Direct EXIT call 110 111call :setError 0 112cmd /c "exit 42" 113call :checkErrorLevel 42 114 115call :setError 111 116cmd /c "exit 42" 117call :checkErrorLevel 42 118 119echo --- CMD /C Direct EXIT /B call 120 121call :setError 0 122cmd /c "exit /b 42" 123call :checkErrorLevel 42 124 125call :setError 111 126cmd /c "exit /b 42" 127call :checkErrorLevel 42 128 129:: Non-existing ccommand, or command that only changes 130:: the returned code (but NOT the ERRORLEVEL) and EXIT. 131 132echo --- CMD /C Non-existing command 133 134:: EXIT alone does not change the ERRORLEVEL 135call :setError 0 136cmd /c "nonexisting & exit" 137call :checkErrorLevel 9009 138 139call :setError 111 140cmd /c "nonexisting & exit" 141call :checkErrorLevel 9009 142 143call :setError 0 144cmd /c "nonexisting & exit /b" 145call :checkErrorLevel 9009 146 147call :setError 111 148cmd /c "nonexisting & exit /b" 149call :checkErrorLevel 9009 150 151echo --- CMD /C RMDIR (no ERRORLEVEL set) 152 153call :setError 0 154cmd /c "rmdir nonexisting & exit" 155call :checkErrorLevel 0 156 157call :setError 111 158cmd /c "rmdir nonexisting & exit" 159call :checkErrorLevel 0 160 161call :setError 0 162cmd /c "rmdir nonexisting & exit /b" 163call :checkErrorLevel 0 164 165call :setError 111 166cmd /c "rmdir nonexisting & exit /b" 167call :checkErrorLevel 0 168 169:: Failing command (sets ERRORLEVEL to 1) and EXIT 170echo --- CMD /C DIR (sets ERRORLEVEL) - With failure 171 172:: EXIT alone does not change the ERRORLEVEL 173call :setError 0 174cmd /c "dir nonexisting>NUL & exit" 175call :checkErrorLevel 1 176 177call :setError 111 178cmd /c "dir nonexisting>NUL & exit" 179call :checkErrorLevel 1 180 181call :setError 0 182cmd /c "dir nonexisting>NUL & exit /b" 183call :checkErrorLevel 1 184 185call :setError 111 186cmd /c "dir nonexisting>NUL & exit /b" 187call :checkErrorLevel 1 188 189:: Here EXIT changes the ERRORLEVEL 190call :setError 0 191cmd /c "dir nonexisting>NUL & exit 42" 192call :checkErrorLevel 42 193 194call :setError 111 195cmd /c "dir nonexisting>NUL & exit 42" 196call :checkErrorLevel 42 197 198call :setError 0 199cmd /c "dir nonexisting>NUL & exit /b 42" 200call :checkErrorLevel 42 201 202call :setError 111 203cmd /c "dir nonexisting>NUL & exit /b 42" 204call :checkErrorLevel 42 205 206:: Succeeding command (sets ERRORLEVEL to 0) and EXIT 207echo --- CMD /C DIR (sets ERRORLEVEL) - With success 208 209call :setError 0 210cmd /c "dir>NUL & exit" 211call :checkErrorLevel 0 212 213call :setError 111 214cmd /c "dir>NUL & exit" 215call :checkErrorLevel 0 216 217call :setError 0 218cmd /c "dir>NUL & exit 42" 219call :checkErrorLevel 42 220 221call :setError 111 222cmd /c "dir>NUL & exit 42" 223call :checkErrorLevel 42 224 225call :setError 0 226cmd /c "dir>NUL & exit /b 42" 227call :checkErrorLevel 42 228 229call :setError 111 230cmd /c "dir>NUL & exit /b 42" 231call :checkErrorLevel 42 232 233 234:: Same sorts of tests, but now from within an external batch file: 235:: Tests for CALL command returned exit code. 236 237:: Use an auxiliary CMD file 238mkdir foobar && cd foobar 239 240:: Non-existing ccommand, or command that only changes 241:: the returned code (but NOT the ERRORLEVEL) and EXIT. 242 243echo --- CALL Batch Non-existing command 244 245:: EXIT alone does not change the ERRORLEVEL 246echo nonexisting ^& exit /b> tmp.cmd 247call :setError 0 248call tmp.cmd 249call :checkErrorLevel 9009 250 251echo nonexisting ^& exit /b> tmp.cmd 252call :setError 111 253call tmp.cmd 254call :checkErrorLevel 9009 255 256:: These tests show that || converts the returned error code 257:: from RMDIR on failure, and converts it to an ERRORLEVEL 258:: (first two tests: no ||, thus no ERRORLEVEL set; 259:: last two tests: ||used and ERRORLEVEL is set). 260:: 261 262echo --- CALL Batch RMDIR (no ERRORLEVEL set) 263 264:: This test shows that if a batch returns error code 0 from CALL, 265:: then CALL will keep the existing ERRORLEVEL (here, 111)... 266echo rmdir nonexisting> tmp.cmd 267echo exit /b>> tmp.cmd 268call :setError 0 269call tmp.cmd 270call :checkErrorLevel 0 271 272echo rmdir nonexisting> tmp.cmd 273echo exit /b>> tmp.cmd 274call :setError 111 275call tmp.cmd 276call :checkErrorLevel 111 277 278echo --- CALL Batch RMDIR with ^|^| (sets ERRORLEVEL) 279 280:: ... but if a non-zero error code is returned from CALL, 281:: then CALL uses it as the new ERRORLEVEL. 282echo rmdir nonexisting ^|^| rem> tmp.cmd 283echo exit /b>> tmp.cmd 284call :setError 0 285call tmp.cmd 286call :checkErrorLevel 2 287:: This gives the same effect, since the last command's error code 288:: is returned and transformed by CALL into an ERRORLEVEL: 289echo rmdir nonexisting> tmp.cmd 290call :setError 0 291call tmp.cmd 292call :checkErrorLevel 2 293 294echo rmdir nonexisting ^|^| rem> tmp.cmd 295echo exit /b>> tmp.cmd 296call :setError 111 297call tmp.cmd 298call :checkErrorLevel 2 299:: This gives the same effect, since the last command's error code 300:: is returned and transformed by CALL into an ERRORLEVEL: 301echo rmdir nonexisting> tmp.cmd 302call :setError 111 303call tmp.cmd 304call :checkErrorLevel 2 305 306 307:: Failing command (sets ERRORLEVEL to 1) and EXIT 308echo --- CALL Batch DIR (sets ERRORLEVEL) - With failure 309 310echo dir nonexisting^>NUL> tmp.cmd 311call :setError 0 312call tmp.cmd 313call :checkErrorLevel 1 314 315echo dir nonexisting^>NUL> tmp.cmd 316call :setError 111 317call tmp.cmd 318call :checkErrorLevel 1 319 320echo dir nonexisting^>NUL ^& goto :eof> tmp.cmd 321call :setError 0 322call tmp.cmd 323call :checkErrorLevel 1 324 325echo dir nonexisting^>NUL ^& goto :eof> tmp.cmd 326call :setError 111 327call tmp.cmd 328call :checkErrorLevel 1 329 330echo dir nonexisting^>NUL ^& exit /b> tmp.cmd 331call :setError 0 332call tmp.cmd 333call :checkErrorLevel 1 334 335echo dir nonexisting^>NUL ^& exit /b> tmp.cmd 336call :setError 111 337call tmp.cmd 338call :checkErrorLevel 1 339 340echo dir nonexisting^>NUL ^& exit /b 42 > tmp.cmd 341call :setError 0 342call tmp.cmd 343call :checkErrorLevel 42 344 345echo dir nonexisting^>NUL ^& exit /b 42 > tmp.cmd 346call :setError 111 347call tmp.cmd 348call :checkErrorLevel 42 349 350:: Succeeding command (sets ERRORLEVEL to 0) and EXIT 351echo --- CALL Batch DIR (sets ERRORLEVEL) - With success 352 353echo dir^>NUL> tmp.cmd 354call :setError 0 355call tmp.cmd 356call :checkErrorLevel 0 357 358echo dir^>NUL> tmp.cmd 359call :setError 111 360call tmp.cmd 361call :checkErrorLevel 0 362 363echo dir^>NUL ^& goto :eof> tmp.cmd 364call :setError 0 365call tmp.cmd 366call :checkErrorLevel 0 367 368echo dir^>NUL ^& goto :eof> tmp.cmd 369call :setError 111 370call tmp.cmd 371call :checkErrorLevel 0 372 373echo dir^>NUL ^& exit /b> tmp.cmd 374call :setError 0 375call tmp.cmd 376call :checkErrorLevel 0 377 378echo dir^>NUL ^& exit /b> tmp.cmd 379call :setError 111 380call tmp.cmd 381call :checkErrorLevel 0 382 383echo dir^>NUL ^& exit /b 42 > tmp.cmd 384call :setError 0 385call tmp.cmd 386call :checkErrorLevel 42 387 388echo dir^>NUL ^& exit /b 42 > tmp.cmd 389call :setError 111 390call tmp.cmd 391call :checkErrorLevel 42 392 393 394:: Cleanup 395del tmp.cmd 396cd .. & rmdir /s/q foobar 397 398 399:: 400:: ERRORLEVEL tests for special commands. 401:: 402:: For some commands, the errorlevel is set differently, 403:: whether or not they are run within a .BAT, a .CMD, or 404:: directly from the command-line. 405:: 406:: These commands are: 407:: APPEND/DPATH, ASSOC, FTYPE, PATH, PROMPT, SET. 408:: 409:: See https://ss64.com/nt/errorlevel.html for more details. 410:: 411 412echo ---------- Testing ERRORLEVEL in .BAT and .CMD ---------- 413 414:: Use an auxiliary CMD file 415mkdir foobar && cd foobar 416 417echo --- In .BAT file 418call :outputErrLvlTestToBatch tmp.bat 419cmd /c tmp.bat 420del tmp.bat 421 422echo --- In .CMD file 423call :outputErrLvlTestToBatch tmp.cmd 424cmd /c tmp.cmd 425del tmp.cmd 426 427:: Cleanup 428cd .. & rmdir /s/q foobar 429 430:: Go to the next tests below. 431goto :continue 432 433:: ERRORLEVEL test helper function 434:outputErrLvlTestToBatch (filename) 435 436echo @echo off> %1 437echo setlocal enableextensions>> %1 438 439:: Reset the errorlevel 440echo call :zeroErrLvl>> %1 441 442:: dpath 443:: echo %errorlevel% 444:: dpath xxx 445:: echo %errorlevel% 446:: dpath 447:: echo %errorlevel% 448 449echo assoc^>NUL>> %1 450echo echo %%errorlevel%%>> %1 451echo assoc .nonexisting^>NUL>> %1 452echo echo %%errorlevel%%>> %1 453echo assoc .nonexisting^=^>NUL>> %1 454echo echo %%errorlevel%%>> %1 455 456:: ftype 457:: echo %errorlevel% 458:: ftype xxx 459:: echo %errorlevel% 460:: ftype 461:: echo %errorlevel% 462 463echo path^>NUL>> %1 464echo echo %%errorlevel%%>> %1 465echo path^;^>NUL>> %1 466echo echo %%errorlevel%%>> %1 467 468echo prompt^>NUL>> %1 469echo echo %%errorlevel%%>> %1 470echo prompt ^$p^$g^>NUL>> %1 471echo echo %%errorlevel%%>> %1 472echo prompt foobar^>NUL>> %1 473echo echo %%errorlevel%%>> %1 474 475echo set^>NUL>> %1 476echo echo %%errorlevel%%>> %1 477echo set nonexisting^>NUL>> %1 478echo echo %%errorlevel%%>> %1 479echo set nonexisting^=^>NUL>> %1 480echo echo %%errorlevel%%>> %1 481echo set nonexisting^=trololol^>NUL>> %1 482echo echo %%errorlevel%%>> %1 483echo set nonexisting^=^>NUL>> %1 484echo echo %%errorlevel%%>> %1 485 486echo goto :eof>> %1 487 488:: Zero ERRORLEVEL 489echo :zeroErrLvl>> %1 490echo exit /B 0 >> %1 491 492goto :eof 493 494 495:: 496:: Next suite of tests. 497:: 498:continue 499 500 501:: Testing different ERRORLEVELs from the SET command. 502:: See https://ss64.com/nt/set.html for more details. 503 504echo ---------- Testing SET /A ERRORLEVELs ---------- 505 506echo --- Success 507call :setError 0 508set /a "total=1+1" 509call :checkErrorLevel 0 510echo %errorlevel% 511echo %total% 512 513echo --- Unbalanced parentheses 514call :setError 0 515set /a "total=(2+1" 516call :checkErrorLevel 1073750988 517echo %errorlevel% 518echo %total% 519 520echo --- Missing operand 521call :setError 0 522set /a "total=5*" 523call :checkErrorLevel 1073750989 524echo %errorlevel% 525echo %total% 526 527echo --- Syntax error 528call :setError 0 529set /a "total=7$3" 530call :checkErrorLevel 1073750990 531echo %errorlevel% 532echo %total% 533 534echo --- Invalid number 535call :setError 0 536set /a "total=0xdeadbeeg" 537call :checkErrorLevel 1073750991 538echo %errorlevel% 539echo %total% 540 541echo --- Number larger than 32-bits 542call :setError 0 543set /a "total=999999999999999999999999" 544call :checkErrorLevel 1073750992 545echo %errorlevel% 546echo %total% 547 548echo --- Division by zero 549call :setError 0 550set /a "total=1/0" 551call :checkErrorLevel 1073750993 552echo %errorlevel% 553echo %total% 554 555 556 557:: 558:: Finished! 559:: 560echo --------- Finished -------------- 561goto :EOF 562 563:checkErrorLevel 564if %errorlevel% neq %1 (echo Unexpected errorlevel %errorlevel%, expected %1) else echo OK 565goto :eof 566 567:: Subroutine to set errorlevel and return 568:: in windows nt 4.0, this always sets errorlevel 1, since /b isn't supported 569:setError 570exit /B %1 571:: This line runs under cmd in windows NT 4, but not in more modern versions. 572