ERRORLEVEL in if statement not works correctly [closed] - if-statement

Closed. This question is not reproducible or was caused by typos. It is not currently accepting answers.
This question was caused by a typo or a problem that can no longer be reproduced. While similar questions may be on-topic here, this one was resolved in a way less likely to help future readers.
Closed 1 year ago.
Improve this question
In this batch file ERRORLEVEL is displayed correctly (First option returns 1 and the second returns 2):
#echo off
choice /C YN /M "Yes or No"
echo The actual errorlevel is: %ERRORLEVEL%
pause
exit
But when i try with if statements something happens:
#echo off
choice /C YN /M "Yes or No"
if (%ERRORLEVEL% == 1) (echo You chose "Yes")
if (%ERRORLEVEL% == 2) (echo You chose "No")
pause
exit
Here no message is displayed... Any help? Am i doing something wrong?

Remove parenthesis:
if %ERRORLEVEL% == 1 echo You chose "Yes"
if %ERRORLEVEL% == 2 echo You chose "No"
Explanation
Unlike langauages like C, Javascript or Python, parenthesis in batch scripts denote blocks of code like { and }
So putting them around == checks is not needed and should not be used. If you want more complex expressions, you would instead probably have to split it into several if commands as outlined in this article about if
EDIT:
As noticed by Stephan :
In this case the mistake is caused by command processor
understanding ( and ) as a part of comparison strings,
rather than as special characters, and so interpreting
your statement as comparison between strings (%ERRORLEVEL% and 1)
so string "(1" is compared to "1)" - they do not match
so expression is false, but error is not generated since this is
technically valid syntax (even though it does not do what you wanted)
You could write "%ERRORLEVEL%" == "1" to disambiguate your intentions,
but really parenthesis should just not be used here
Although parenthesis around echo should work in this particular case on modern Windows...
they are not necessary when only one command is executed, and are usually excluded in such cases in batch scripts
I recommend to put them on their own lines (except that the opening parenthesis needs to be on the same line as if or for) to make sure they are not understood as a part of syntax of the command you are executing:
if %ERRORLEVEL% == 1 (
echo You chose "Yes"
)
Here is a more thorough explanation of parenthesis in batch scripts.
Additional note on exit:
You do not need exit at the end of the script unless you want the whole batch execution to be aborted.
This is relevant if one batch script is called from another, or in a subroutine.
If you want to end execution of your *.bat file or subroutine use goto :eof (note the colon) - this is similar to placing a label at the very end of the script and jumping to it with goto.
However, exit /b allows to exit a script and set ERRORLEVEL code to a specified value. This only returns from running subroutine or batch-script and not terminates cmd.exe as plain exit does.
More info on exit can be found here

Related

Using a nested IF NOT within a batch file [closed]

Closed. This question is not reproducible or was caused by typos. It is not currently accepting answers.
This question was caused by a typo or a problem that can no longer be reproduced. While similar questions may be on-topic here, this one was resolved in a way less likely to help future readers.
Closed yesterday.
Improve this question
I am writing a batch script, and I need to use nested IF NOT in order to mimic 'AND AND' logic.
When trying to run it, I get an error:
"!ext!" was unexpected at this time.
I am not pasting the whole script since the issue is clearly in the nested IF NOT piece. (It works if I remove that). Here is the relevant part:
if not exist "%dst%!relPath!" (
echo file "%dst%!relPath!" does NOT EXIST and it will be created
rem Check if the file extension is png jpg or mp4
if /I "!ext!" == ".png" (
echo It worked!!
)
if /I "!ext!" == ".jpg" (
echo It worked2!!
)
if /I "!ext!" == ".mp4" (
echo It worked3!!
)
if not /I "!ext!" == ".png" if not /I "!ext!" == ".jpg" if not /I "!ext!" == ".mp4" (
echo It worked4!!
)
)
The first 3 ifs are okay, but the 4th if statement, the nested one, is giving me that error, and I can´t see where the issue could be, (unless IF NOT is not supported as nested.

Which regex method is best for validating user input? (for /f with delims vs. echo %var%|Findstr /ri)

I would like to validate a user's input and limit the input to alphanumeric characters only (underscores may be allowed as well), but i'm not sure which method is best for this.
I've seen various examples on SA and the first one that raises some questions for me is the following one:
:input
set "in="
set /p "in=Please enter your username: "
ECHO(%in%|FINDSTR /ri "^[0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZ][0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZ]*$" >nul || (
goto input
)
I see a second case that's identical to the first one (with as expection, the leading ^ and ending *$).
Why is the extra case and ^ *$ needed when the following also works?:
:input
set "in="
set /p "in=Please enter your username: "
ECHO(%in%|FINDSTR /ri "[0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZ]" >nul || (
goto input
)
Finally, The FOR /F loop method i've noticed on here as well:
for /f "delims=1234567890ABCDEFGHIJKLMNOPQRSTUVWXYZ" %%a in ("%in%") do goto :input
Is there any (dis)advantage in using this over the beforementioned FINDSTR regex one?
For safely validating user input, both methods are reliable, but you must improve them:
findstr method
At first, let us focus on the search string like ^[...][...]*$ (where ... stands for a character class, meaning a set of characters): A character class [...] matches any one character from set ...; * means repetition, so matching zero or more occurrences, hence [...]* matches zero or more occurrences of characters from set ...; therefore, [...][...]* matches one or more occurrences of characters from set .... The leading ^ anchors the match to the beginning of the line, the trailing $ anchors it to the end; therefore, when both anchors are specified, the entire line must match the search string.
Concerning character classes [...]: According to the thread What are the undocumented features and limitations of the Windows FINDSTR command?, classes are buggy; for instance, the class [A-Z] matches small letters b to z, and [a-z] matches capital letters A to Y (this does of course not matter in case a case-insensitive search is done, so when /I is given); the class [0-9] may match ² or ³, depending on the current code page; [A-Z] and [a-z] may match special letters like Á or á, for example, also depending on current code page. Hence to safely match certain characters only, do not use ranges, but specify each character individually, like [0123456789], [ABCDEFGHIJKLMNOPQRSTUVWXYZ] or [abcdefghijklmnopqrstuvwxyz].
All this leads us to the following findstr command line:
findstr /R /I "^[0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZ][0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZ]*$"
Nevertheless, the whole approach with the piped echo might still fail, because special characters like ", &, ^, %, !, (, ), <, >, | could lead to syntax errors or other unintended behaviour. To avoid that, we need to establish delayed expansion, so the special characters become hidden from the command parser. However, since pipes (|) initialise new cmd instances for either side (which inherit the current environment), we need to ensure to do the actual variable expansion in the left child cmd instance rather than in the parent one, like this:
:INPUT
set "IN="
set /P IN="Please enter your username: "
cmd /V /C echo(^^!IN^^!| findstr /R /I "^[0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZ][0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZ]*$" > nul || goto :INPUT
The extra explicit cmd instance is needed to enable delayed expansion (/V), because the instances initiated by the pipe have delayed expansion disabled.
The doubled escaping of the exclamation marks ^^! is only needed in case delayed expansion is also enabled in the parent cmd instance; if not, single escaping ^! was sufficient, but doubled escaping does not harm.
for /F method
This approach makes life easier, because there is no pipe involved and so, you do not have to deal with multiple cmd instances, but there is still room for improvement. Again, special characters may cause trouble, so delayed expansion needs to be enabled.
The for /F loop ignores empty lines and such beginning with the default eol character, the semicolon ;. To disable the eol option, simply define one of the delimiter characters, so eol becomes hidden behind delims. Empty lines are not iterated, so the goto command in your approach would never execute in case of empty user input. Therefore, we must capture empty user input explicitly, using an if statement. Now all this leads to the following code:
setlocal EnableDelayedExpansion
:INPUT
set "IN="
set /P IN="Please enter your username: "
if not defined IN goto :INPUT
for /F "delims=0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZ eol=0" %%Z in ("!IN!") do goto :INPUT
endlocal
This approach detects capital letters only; to include small letters as well, you have to add them to the delims option: delims=0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz.
Note that variable IN is no longer available beyond endlocal, but this should be the very last comand of your script anyway.
To detect whether or not a for /F loop iterated or not, there is an undocumented feature, which we can make use of: for /F returns a non-zero exit code if it does not iterate, hence conditional execution operators && or || can be used; so, when the user input is empty, the loop does not iterate, then ||; for this to work, the for /F loop must be enclosed within parentheses:
setlocal EnableDelayedExpansion
:INPUT
set "IN="
set /P IN="Please enter your username: "
if not defined IN goto :INPUT
(for /F "delims=0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZ eol=0" %%Z in ("!IN!") do rem/) && goto :INPUT
endlocal
First, you have to reference environment variable in with using delayed expansion to avoid an exit of batch file execution because of a syntax error when the user enters a string with critical characters like ><|&". Always take into account that a variable specified with %variable% is expanded before execution of the command line which can easily break batch execution on user input variable strings.
Second, it is strongly recommended to immediately verify if the user has input anything at all after the prompt, i.e. use if not defined in goto input after the prompt command line.
Third, I think the FOR method is better because of being faster.
FINDSTR is not an internal command of cmd.exe like FOR. So when specifying FINDSTR in batch file without path and without file extension Windows command interpreter must first search for this executable and hopefully really finds %SystemRoot%\System32\findstr.exe via PATHEXT and PATH.
Next with an anti-virus process running in background the execution of findstr.exe triggers the scanning process of anti-virus process which results in a delay of execution.
The execution of an application like FINDSTR by Windows command interpreter takes always a bit longer as the execution of an internal command of cmd.exe even with no anti-virus scan process running. So the FOR loop approach is most likely (not verified by me) faster than the FINDSTR approach.
On using FINDSTR the regular expression characters ^ and *$ are needed because the regular expression search string [0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZ] results in a positive match if the processed line contains anywhere at least 1 digit or letter. So it is not checked if the line (= string of variable) consists of only digits and letters. The shorter character class definitions [0-9A-Z] with depending on option /I or [0-9A-Za-z] can't be used in this case as explained by aschipfl in his comment below.
With ^ is specified that the searched string must be found at beginning of a line, with * that 0 or more digits or letters must be found, and with $ that the searched string must be found at end of line. Or in other words the entire line (user input) not being completely empty as checked before must completely consist of only digits and letters for a positive match.
For every internal or external command help on command can be get by running the command from within a command prompt window with /? as parameter. Try it out with opening a command prompt window and run findstr /? and for /? and set /?.

Windows Batch - check if string starts with ... in a loop

this grabs the output from a remote branch list with git::
for /f "delims=\" %r in ('git branch -r') do (
::then in here I want to get rid of the origin/HEAD -> line of the output
::and do a few git ops on the other lines, which are the names of branches
)
anyhow, I'm finding this frustrating as apparently batch doesn't have regex
here's the code I'm using to do this in bash
for remote in `git branch -r | grep -v '\->'`;
do echo $remote; #...git stuff
done;
the grep removes the "origin/HEAD -> origin/master" line of the output of git branch -r
So I'm hoping to ask how to implement the 'contains' verb
for /f "delims=\" %r in ('git branch -r') do (
if not %r contains HEAD (
::...git stuff
)
)
on a loop variable
this stackoverflow question appears to answer a similar question, although in my attempts to implement as such, I became confused by % symbols and no permutation of them yielded function
EDIT FOR FUTURE READERS: there is some regex with findstr /r piped onto git branch -r
for /f "delims=\" %%r in ('git branch -r^|findstr "HEAD"') do (
echo ...git stuff %%r
)
should give you a start.
Note: %%r, not %r within a batch file - %r would work directly from the prompt.
Your delims=\ filter will produce that portion up to the first \ of any line from git branch -r which contains HEAD - sorry, I don't talk bash-ish; you'd need to say precisely what the HEAD string you want to locate is.
Use "delims=" fo the entire line - omitting the delims option will set delimiters to the default set (space, comma, semicolon, etc.)
Don't use ::-comments within a block (parenthesised statement-sequence) as it's actually a broken label and cmd doesn't appeciate labels within a block. Use REM comments here instead.
The resultant strings output from the findstr (which acts on a brain-dead verion of regex) will be processed through to the echo (or whatever statement you may substitute here) - if there are none, the for will appear to be skipped.
Quite what your target string would be for findstr I can't tell. From the prompt, findstr /? may reveal. You may also be able to use find (find /?) - but if you are using cygwin the *nix version of find overrides windows-native.
I don't know what the git branch output looks like, but with a test case of
test 1
HEAD test \-> 2
test 3
test 4
the following prints all the text lines except the one containing \->
#setlocal enableextensions enabledelayedexpansion
#echo off
for /f "tokens=*" %%r in (d:\test2.txt) do (
set str1=%%r
if "!str1:\->=!"=="!str1!" (
echo %%r
)
)
The if test is fundamentally doing this test: string1.replace("HEAD", "") == string1.
Your loop variable needs to be %r if used directly in the command prompt, but %%r if in a batch file.
The string replacement is a part of environment variables, not loop variables, so it needs to be put into a holding string (str1) to work with. If you have the command extensions enabled ( enableextensions ).
And because environment variable setting operations happen when the script is read, you need to override that with enabledelayedexpansion and using !str1! instead of %str1%, otherwise the value of str1 won't change from one loop to the next.
(PS. Use PowerShell instead. Get-Content D:\test2.txt | Select-String "\->" -NotMatch ).

Writing .txt files from lines 1 to i

I am very close to my answer, but I cant seem to find it.
I am using the Findstr function in a batch file to narrow now an entire directory to just one file.
cd ...
findstr /s /m "Desktop" *class.asasm >results1.txt
findstr /m /f:results1.txt "Production" *class.asasm >results2.txt
findstr /n /f:results2.txt "Capabilities" *class.asasm >results3.txt
TASK 1: I need to figure out a way to make findstr search backwards for a fourth string from the line number the third line was found on
TASK 2: I need to write lines 1-the one we arrive at of the file in results2.txt
the insert a .txt file. Then write the rest of the original lines.
I am writing an application in VB.Net with Visual Studios and I am having a difficult time figuring out how to complete this process. I am currently having better luch with having the application run batch files that are written within the application.
The correct solution is to find a tool that does this properly. batch/CMD does not.
Here's a script that tells you the line numbers of the 3rd and 4th match. It's probably not exactly what you want, but it is a demonstration of how one can effectively work with line numbers.
#ECHO OFF
SETLOCAL ENABLEDELAYEDEXPANSION
SET FILE=TestFile.txt
SET _LINENO=1
SET _MATCHNO=0
SET _THIRDLINENUM=
SET _FOURTHLINENUM=
FOR /F %%l IN (%FILE%) DO (
ECHO %%l | FINDSTR "Target" %_TMP% >NUL
IF NOT ERRORLEVEL 1 (
SET /A _MATCHNO=!_MATCHNO!+1
IF !_MATCHNO!==3 SET _THIRDLINENUM=!_LINENO!
IF !_MATCHNO!==4 SET _FOURTHLINENUM=!_LINENO!
)
SET /A _LINENO=!_LINENO!+1
)
#ECHO %_THIRDLINENUM% : %_FOURTHLINENUM%
Here's what's in TestFile.txt
abcdefg
bcdefgh
Target 1
cdefghi
defghij
fghijkl
Target 2
ghijklm
hijklmn
ijklmno
jklmnop
klmnopq
lmnopqr
mnopqrs
Target 3
nopqrst
Target 4
opqrstu
pqrstuv
qrstuvw
rstuvwx
stuvwxy
tuvwxyz
If you insist on using batch/CMD (and I sometimes do when nothing else is available), and you need to get the text on line #n (otherwise, head and tail would do just fine), you could produce a similar loop but replace the code from FINDSTR down to the end of the IF statement with something that compares _LINENO with some other variable, ECHO'ing the line if it is between the two values. I don't know if IF supports logical operators, so you may have to nest the IF statements, like
IF !_LINENO! GEQ %START_LINE% IF !_LINENO! LEQ %END_LINE% #ECHO %%l
assuming you need this (from your first comment):
I still have not found a way to search starting at line xx rather than 1 or to search in reverse order
you can try this (from the command line):
for /r %i in ("file pattern") do #more "%~i" +starting_line |findstr "search string"
for /r = recursively (if you mean really reverse, please explain)
"file pattern" = files to find, eg. "*class.asasm"
starting_line = search starting line, eg. 7 (more +6)
"search string" = your search pattern, eg. "Desktop"
OR search "Desktop Production Capabilities"
AND search |findstr "Desktop"|findstr "Production"|findstr "Capabilities"

Piped Variable Into FINDSTR w/ Regular Expressions and Escaped Double Quotes

I am trying to understand a batch file that was sent to me in order to work around a bug in a third party program while they resolve the issue. Basically they are running a findstr regular expression command in order to determine whether or not the string matches. If it does, then the special characters that should not be stripped out are being added back in manually before it is passed off to the original commandline program.
As best I can tell though, what has been provided does not work or I do not understand it. I am pasting the relevant section of code below.
#echo off
setlocal
set username=%1
shift
echo %username% | findstr /r "^\"[0-9][0-9]*\"" >nul
if not errorlevel 1 (set username=";%username:~0,9%=%username:~10,4%?")
echo %username%
The three pieces I really have questions about are as follows:
I believe the unescaped interpretation of the regular express above is ^"[0-9][0-9]*" which I think means that the string must begin with a numeric character and then must consist of zero or more additional numeric-only characters in order for a match to be found. Well, FINDSTR seems to be doing something weird with the escaped quotes and I cannot get it to match anything I have tried. If I remove the \" around [0-9][0-9]* then I can get it to work, but it does not properly reject non-numeric characters such as an input string of 123456789O1234 (there is a letter O instead of a zero in that sample string).
What is the point of the >nul
Wouldn't it be better to check for an errorlevel equal to 0 instead of "not errorlevel 1" since it could possibly return an error level of 2?
Anyway, the following code works, but it is not as precise as I would like. I am just looking to understand why the quotes in the regex string are not working. Perhaps this is a limitation of FINDSTR, but I have not came across anything definitive yet.
#echo off
setlocal
set username=%1
shift
echo %username% | findstr /r "^[0-9][0-9]*" >nul
if not errorlevel 1 (set username=";%username:~0,9%=%username:~10,4%?")
echo %username%
I can workaround the problem by repeating the class 14 times since that is the number of characters in my situation (more than 15 classes will cause it to crash - scroll to the bottom). I am still curious as to how this could be achieved more simply, and of course the remaining 2 questions.
EDIT / WORKING SOLUTION
#echo off
setlocal enableDelayedExpansion
set username=%~1
shift
echo !username!|findstr /r /c:"^[0-9][0-9]*$" >nul
if not errorlevel 1 (set username=";!username:~0,9!=!username:~10,4!?")
echo !username!
NOTES:
When I first ran it after modifying my existing code to more cloesly resemble dbenham's, enableDelayedExpansion gave an error as did the quotes around setting the username (see below). I can't replicate what I did wrong, but it is all working now (this is in case someone else comes across the same issue).
I had tried the $ for the EOL marker (which is the key to forcing it match numeric content only), but I think that the other problems were getting in the way which made me think it was not the solution. Also, to ensure the $ works don't miss this part of dbenham's answer "...you must also make sure there are no spaces between your echoed value and the pipe symbol."
In short it pretty much seems that trying to put double quotes inside a regex for findstr is wrong syntax/does not work/etc... unless you are actually looking to match " in the string/files you are parsing through. See dbenham's answer for clarity here. As he noted, you can use %~1 to strip the quotes from the argument instead of adding it to your regex (and programmatically add them back in if needed).
Error Message
C:>sample.bat 123456789
'enableDelayedExpansion' is not recognized as an internal or external command,
operable program or batch file.
'"' is not recognized as an internal or external command,
operable program or batch file.
!username!
Reference Links:
Undocumented features and limitations of the Windows FINDSTR command
Case sesntive anomalies with findstr (not handling case properly in some circumstances)
http://ss64.com/nt/findstr.html
http://www.robvanderwoude.com/findstr.php
http://www.microsoft.com/resources/documentation/windows/xp/all/proddocs/en-us/findstr.mspx
Answering your questions in reverse order:
3) if not errorlevel 1 is probably the same as if %errorlevel%==0 because IF ERRORLEVEL 1 means if ERRORLEVEL is greater than or equal to 1. So putting a NOT in front means if ERRORLEVEL is less than 1. I believe FINDSTR never returns a negative ERRORLEVEL, so the syntax should be OK.
2) The >nul redirects the stdout output of FINDSTR to the nul device, meaning it disables the output. Normally any matching line would be printed. You are only interested in the return code - you don't want to see the output.
1) The original regex will match any input string that starts with a quote, followed by at least one digit, followed by another quote. It ignores any characters that may appear after the 2nd quote.
So the following strings (quotes included) will match:
"0"
"01234"
"0"a
"01234"a
The following strings will not match:
0
01234
""
"0a"
The original code has problems if the number of digits in the matching string reaches a certain length because the ending quote gets stripped causing the closing ) to be quoted and so the rest of the script fails.
I don't understand your requirements so I don't know how to fix the code.
It sounds like you don't want to match strings that have non digits. That means you need to include the end of line marker $ at the end of the regex. But you must also make sure there are no spaces between your echoed value and the pipe symbol.
I believe you probably don't want quotes in your value, (or else you should programatically add them at the very end). You can use %~1 to strip any enclosing quotes from the supplied argument.
If you are looking to check if argument 1 consists of nothing but numeric digits, then you can use:
setlocal enableDelayedExpansion
set "username=%~1"
echo !username!|findstr /r "^[0-9][0-9]*$" >nul
I used delayed expansion because you have no control over what characters are in %1, and if it contains special characters like & or | it will cause problems if you use normal expansion. The syntax I have given is not bullet proof, but it handles most "normal" situations.
It is not necessary in your case, but I prefer to use the /c option, just in case your search string contains spaces. So the above could be written as
echo !username!|findstr /r /c:"^[0-9][0-9]*$" >nul
It seems odd to me that both the original and your modified code simply pass through the username if it does not match your regex. Maybe that is your intent, maybe not.