Operator IF doesnt check condition of values in cmd - if-statement

Hi everyone I writing little .bat file and seemed for I was ended but I got error. Operator IF does not checking Permission value and Free space conditions of values and are not compering. All the time I get "false" even when the free space on disc D is less then 25Gb (26,843,545,600 bits)
My code looks such:
#echo off
#setlocal enableextensions enabledelayedexpansion
set permissiblevalue=26,843,545,600
set permissiblevalue=%permissiblevalue:,=%
for /f "tokens=3" %%a in ('dir d:\') do (
set bytesfree=%%a
)
set bytesfree=%bytesfree:,=%
if %permissiblevalue% leq %bytesfree% (
The disk D is checking.....
) else (
echo msgbox "Lacks of free space on disk D. Klick OK for delete files!!!" > %tmp%\tmp.vbs
wscript %tmp%\tmp.vbs
del %tmp%\tmp.vbs
%SystemRoot%\explorer.exe "d:\"
)
exit
Please explain to me Where is the mistake

If the two arguments to if are both pure numeric strings, cmd will convert them to integers and compare them as integers. BUT cmd is limited to 2^31 and mechanically processes the string character-by-character, left-to-right, multiplying by 10 and adding the next value, so any value greater than 2147483647 will be processed incorrectly (but won't generate en error).
Consequently, if you are using large numbers, you need to force cmd to interpret them as strings. This is easily done by "quoting each string".
BUT a string comparison is performed character-by-character, so you need to leading-0-fill each so that they are the same length.
To do this :
set "zeroes=000000000000000000000000000000000000000000000000000000000"
set "var=%zeroes%%var%"
set "var=%var:~-20%"
Which prepends a series of 0 characters to the current value of var, then sets var to the last 20 characters of the result.
You can then safely use if "%var%" leq "%someothervar%"
Tip : Use set "var=value" for setting string values - this avoids problems caused by trailing spaces. Don't assign a terminal \, Space or " - build pathnames from the elements - counterintuitively, it is likely to make the process easier.

Related

How can I use FINDSTR to place a serial number from a txt file in a variable?

I have a script that outputs a serials.txt file like this:
Serial Number
88PRDQ1
Serial Number
CZSQZV1
I would like to capture the service tag in a variable for future manipulation in the script.
My code seems to fail entirely and I can't find much in the way of what I am looking for with FINDSTR.
for /f "delims=" %%S in (serials.txt) do (findstr /r "^......[^ ]")
Since the output can be 6-7 long and any letter or number, I am struggling to get it to behave properly. It runs forever at the moment and I am unsure how to output the result to a variable and act on it if I somehow managed to get this line correct.
The [^ ] is meant to exclude spaces but accept anything else so as to not print the line that says "Serial Number" in the event of a 6 char serial.
What am I missing?
findstr /v /c:"Serial Number" serials.txt|findstr "."
First finds all line not containing the string "Serial Number", then filters any line containing at least one character to suppress empty lines.

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 /?.

Batch: getting numbers between x and y from string "x123y"

I need to extract the numbers between su11b_ and .agm using batch script.
In the example below the result should be 733.
su11b_ is always the same, the numbers here will never change
733 can change, ranging from 1 - eternity (and will thus have a variable length)
Here's what I tried (and it doesn't work at all):
set "str=d:\agrcc\agrtest\server logging\su11b_733.agm"
set /A "number=str"
I'm thinking perhaps a regex thingy could extract the digits between su11b_ and .agm but I don't know how to do this in batch scripting?
You actually don't need regex if you're sure the numbers are always between su11b_ and .agm. You can just delete everything before su11b_ and delete the .agm part afterwards:
set "var=d:\agrcc\agrtest\server logging\su11b_733.agm"
set number=%var:*su11b_=%
set number=%number:.agm=%
echo %number%
This link has some more info about replacing substrings in variables in batch

RegEx in Batch File

Hey I'm trying to create a function that parses a string passed via a browser protocol. It's a "callto://" protocol and it is in this format: "callto://5551234567/" with the persons phone number inside there. I need to extract the number and pass it to another program that dials the number. The syntax for that other program is like this: "CallClerk.exe dial=5551234567=".
I'm a beginner to batch however, and can't figure out exactly what to do. Here's my current code:
#echo off
set var=%1
set number=theirphone
FindStr /R "callto://(..........)/" %var% > %number%
start C:\Program Files (x86)\CallClerk\CallClerk.exe dial=%number%=
Exit /B
Thanks for the help!
#echo off
FOR /f "tokens=2 delims=/" %%i IN ('echo %~1') DO start "" "C:\Program Files (x86)\CallClerk\CallClerk.exe" dial=%%i=
Exit /B
should work for you (untested) - assuming your input parameter is callto://5551234567/
Note the use of quoting - the .exe needs to be quoted since it contains a space in the path. The extra pair of quotes in the window-name. If you like, you could replace that pair with "Calling %%i". This parameter is optional, but inserting it ensures that START doesn't get confused between window-title, executable-name and parameter-to-executable.
This works to extract numbers from a string.
It uses two for loops, the first one gathers all the non-numeric characters and they are used as delimiters in the second for loop to gather the numerics and dial the number.
Strings of variable lengths can be handled, as long as all numbers are used in the desired telephone number.
If you want to keep the + as a valid telephone character then include it in the first for command in the delims with the numbers.
#echo off
set "var=callto://5551234567/"
for /f "delims=0123456789" %%a in ("%var%") do set "delims=%%a"
for /f "delims=%delims%" %%a in ("%var%") do (
start "" "C:\Program Files (x86)\CallClerk\CallClerk.exe" dial=%%a=
)
You should be able to use a regex along the lines of (?<=callto:\/\/)[\d]+(?=\/) to grab the number itself. This uses a positive look ahead and look behind to make sure you are matching at least one number that is preceded by the callto:// and followed by a /.
If you left it as something like callto:\/\/[\d]+\/, then it is matching the entire string and will return back with the callto text included. If you are intending to pass just the numbers along to the next part of you code, extract them using the look ahead to guarantee the before and after conditions are met.
I did a quick test using the strings you used in your example. You can see the regex in action here.

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.