Batch Regular Expressions
I am writing a calculator on cmd with binary operations included. I need to validate input data (remove letters and other characters that are not required for arithmetic operations)
@echo off
set data=
echo %* | findstr /R "\/\? ECHO" > nul
IF "%ERRORLEVEL%" EQU "0" goto printHelp
:main
set data= %data%%1
shift
if "%1" == "" (
echo %data% | findstr /R "^[0123456789*-+()/%!^_&|]*$" >nul 2>&1
if "%ERRORLEVEL%" EQU 0 (
echo Incorrect input data
exit /B
)
goto :result
) else (
goto :main
)
:result
set /a data="%data%"
echo %data%
exit /B
:printHelp
echo.
echo --------------------------------------------------
echo Using: calculator.bat [/?] [EXPRESSION]
echo helps you to consider arithmetic in Command Line
echo --------------------------------------------------
exit /B
My regex doesn't work. It is also not considered a binary operation. What could be the problem?
source to share
Part 1 - Why Your "Regular Expression Won't Work"
-
Your logic is wrong. FINDSTR sets ERRORLEVEL to 0 if there is a match, 1 if there is no match. Your regular expression checks that all characters are "valid", but your state treats the match as invalid input.
-
Your IF statement uses quotation marks on one side, but not on the other. You have to be consistent or it will never evaluate to TRUE.
-
Percentage literals must be doubled in script batch. Your regex has a percent literal which should be written as
%%
. -
You are using% ERRORLEVEL% in the same code block that sets the value. This cannot work because the value is expanded when the block of code is parsed - before the value is set.
The simplest alternative is to use
if errorlevel 1
that returns true if ERRORLEVEL =>.Another option is to enable delayed expansion with SETLOCAL ENABLEDELAYEDEXPANSION at the top and then use
if !errorlevel! neq 0
. But that would require the quoted!
literal in your regex to be escaped as^!
, and the^
literal escaped as^^
.My favorite option is to use conditional statements
&&
and||
instead IF.findstr ... >nul && (match found statements) || (no match statements)
In your case, you want to take action if there is no match, so you only need an operator
||
.
Part 2 - Why Your Concept Isn't a Good Idea
-
Your check is too simplistic. Simply escaping invalid characters does not prevent errors. For example,
1**2
will result in an error even if all characters are "valid". There are many other entries with "valid" characters that can lead to errors. -
SET / A can work directly with environment variables. He knows how to access the value without extending your code. This can be a powerful tool. The variable name used in the calculation can include any character that is not an operator. Therefore, it can be argued that there are no invalid characters for SET / A computations. Your elimination of "invalid" characters prevents variables from being used in calculations.
Below is a simple batch calculation program that I wrote a while ago. It loops indefinitely, asking for input and displaying the result, until you issue the quit command. It supports all the operators supported by SET / A.
It allows you to define and use variables in your expressions. The result of the most recent calculation is always stored in a named variable #
.
The calculator can display results as decimal, hexadecimal, or binary.
By default, it only displays the result of the last calculation. With each calculation, you can also specify the value of all variables.
You can enter a command instead of a mathematical calculation. All commands start with\
\
Exit \V
Toggle the list of variable variables on or off \D
Decimal mode - results are displayed as decimal \H
Hex mode - results are displayed as hexidecimal \B
Binary mode - results are displayed as binary \C X
Clear variable X \C *
Remove all variables \C X*
Clear all variables starting with X
Typing nothing will display all the current variables.
The resettable variables are undefined. Note that the variable undefined has an implicit value of 0.
Here is the code:
@echo off setlocal enableDelayedExpansion for /f "delims==" %%v in ('set') do set %%v= set __skip=#COMSPEC#PATHEXT#PROMPT#__mode#__str#__skip#__clr###__dispVars# set __mode=Dec set __dispVars=0 :top echo: set __str= set /p "__str=%__mode%> " if "!__str!"=="\" exit /b if "!__str!"=="" call :dispVar # & call :dispVars & goto :top if /i "!__str:~0,2!"=="\C" call :clearVars &goto :top if /i "!__str!"=="\H" (set __mode=Hex) ^ else if /i "!__str!"=="\D" (set __mode=Dec) ^ else if /i "!__str!"=="\B" (set __mode=Bin) ^ else if /i "!__str!"=="\V" (set /a "__dispVars=^!__dispVars") ^ else set /a #=(!__str!) call :dispVar # if !__dispVars! gtr 0 call :dispVars goto :top :clearVars for /f "delims=,; " %%v in ("!__str:~2!") do ( set __clr=%%v if "!__clr:~-1!"=="*" ( set __clr=!__clr:~0,-1! for /f "delims==" %%x in ('set !__clr!') do ( if "!__skip:#%%x#=!"=="!__skip!" set "%%x=" ) ) else set "%%v=" ) call :dispVar # call :dispVars exit /b :dispVars setlocal for /f "tokens=1,2 delims==" %%v in ('set') do if "!__skip:#%%v#=!"=="!__skip!" call :dispVar %%v exit /b :dispVar Var setlocal if !__mode!==Hex call :num2hex %1 disp if !__mode!==Bin call :num2bin %1 disp if !__mode!==Dec set /a disp=!%~1! set var=%~1 if "!var:~0,6!"=="!var!" ( set "var=!var! ----------" set "var=!var:~0,6!" ) echo %var% = !disp! exit /b :num2hex NumVal RtnVar setlocal enabledelayedexpansion set hex= set /a "dec=%~1" set "map=0123456789ABCDEF" for /l %%n in (1,1,8) do ( set /a "d=dec&15,dec>>=4" for %%d in (!d!) do set "hex=!map:~%%d,1!!hex!" ) (endlocal & rem return values set %~2=%hex% exit /b ) exit /b :num2bin NumVal RtnVar setlocal enabledelayedexpansion set bin= set /a "dec=%~1" for /l %%n in (1,1,32) do ( set /a "d=dec&1,dec>>=1" set "bin=!d!!bin!" ) (endlocal & rem return values set %~2=%bin% exit /b ) exit /b
And here are the results from a short session:
D:\test>calculate.bat Dec> 2*3 # ---- = 6 Dec> a=#+1 # ---- = 7 Dec> # ---- = 7 a ---- = 7 Dec> b=(a+=5)*2 # ---- = 24 Dec> \v # ---- = 24 a ---- = 12 b ---- = 24 Dec> c=b/3 # ---- = 8 a ---- = 12 b ---- = 24 c ---- = 8 Dec> \h # ---- = 00000008 a ---- = 0000000C b ---- = 00000018 c ---- = 00000008 Hex> \b # ---- = 00000000000000000000000000001000 a ---- = 00000000000000000000000000001100 b ---- = 00000000000000000000000000011000 c ---- = 00000000000000000000000000001000 Bin> \ D:\test>
source to share