If you are familiar with other, "real" scripting languages, you may be tempted to use regular expressions to validate variables, e.g. check if command line argument %1
is a hexadecimal number:
ECHO.%1| FINDSTR.EXE /R /I /C:"[^0-9A-F]" && ECHO "%~1" is NOT a valid hexadecimal string
Note: | We are assuming here that no 0x prefix is used for hexadecimal values |
However, there are ways to validate the string with internal commands only:
FOR /F "delims=0123456789AaBbCcDdEeFf" %%A IN ("%~1") DO ECHO "%~1" is NOT a valid hexadecimal string
or, again assuming no 0x
prefix:
SET /A "=0x%~1" || ECHO "%~1" is NOT a valid hexadecimal string
When using the code samples above in real life, you may want to redirect Standard Error to NULL.
Checking if command line argument %1
is a valid octal number, we can use similar techniques:
ECHO.%1| FINDSTR.EXE /R /C:"[^0-7]" && ECHO "%~1" is NOT a valid octal string
or with internal commands only:
FOR /F "delims=01234567" %%A IN ("%~1") DO ECHO "%~1" is NOT a valid octal string
or:
SET /A "=0%~1" || ECHO "%~1" is NOT a valid octal string
Checking if command line argument %1
is a valid binary number, we can use some, but not all of the previously mentioned techniques:
ECHO.%1| FINDSTR.EXE /R /C:"[^01]" && ECHO "%~1" is NOT a valid binary string
or with internal commands only:
FOR /F "delims=01" %%A IN ("%~1") DO ECHO "%~1" is NOT a valid binary string
Reusing some of the code for hexadecimal:
FOR /F "delims=0123456789" %%A IN ("%~1") DO ECHO "%~1" is NOT a valid decimal number
This test will fail on empty strings or stray doublequotes.
The next one may seem more reliable, but it will still fail on empty strings:
ECHO.%~1| FINDSTR.EXE /R /C:"[^0-9]" && ECHO "%~1" is NOT a valid decimal number
The next one is more reliable:
ECHO.%~1| FINDSTR.EXE /R /X /C:"[0-9][0-9]*" || ECHO "%~1" is NOT a valid decimal number
Using regular expressions:
ECHO.%1| FINDSTR.EXE /R /X /C:"[0-9][0-9]*\.[0-9][0-9]*\.[0-9][0-9]*\.[0-9][0-9]*" || ECHO "%~1" is NOT a valid IP v4 address
Unfortunately, this test will not detect that 999.999.999.999
is an invalid IP v4 address.
To make it fail-safe, we need to pass the input through several filters:
SET Test=%~1 SET Valid=0 :: First, test for exactly 4 blocks of decimal numbers ECHO.%Test%| FINDSTR.EXE /R /X /C:"[0-9][0-9]*\.[0-9][0-9]*\.[0-9][0-9]*\.[0-9][0-9]*" >NUL && SET Valid=1 :: Since 999.12000.0.1 would also have passed the first test, we'll do some extra tests IF "%Valid%"=="1" ( REM Reject strings containing numbers greater than or equal to 1000 ECHO.%Test% | FINDSTR.EXE /R /C:"[1-9][0-9][0-9][0-9]" >NUL && SET Valid=0 ) IF "%Valid%"=="1" ( REM Reject strings containing numbers in 300..999 range ECHO.%Test% | FINDSTR.EXE /R /C:"[3-9][0-9][0-9]" >NUL && SET Valid=0 ) IF "%Valid%"=="1" ( REM Reject strings containing numbers in 260..299 range ECHO.%Test% | FINDSTR.EXE /R /C:"2[6-9][0-9]" >NUL && SET Valid=0 ) IF "%Valid%"=="1" ( REM Reject strings containing numbers in 256..259 range ECHO.%Test% | FINDSTR.EXE /R /C:"25[6-9]" >NUL && SET Valid=0 ) :: Show result IF "%Valid%"=="1" ( ECHO "%Test%" is a valid IP v4 address ) ELSE ( ECHO "%Test%" is NOT a valid IP v4 address )
The filters could all be combined into a single command line using FINDSTR
's /V
switch, but I would not recommend it because it makes the code less readable for humans.
Have mercy for the collegues who may have to work with (or worse: maintain) your code.
You may want to check for and maybe remove leading zeroes, but I'll leave that to you.
With internal commands only:
SET Test=%~1 FOR %%A IN ("%Test%:.= ") DO IF %%A GTR 255 ECHO "%~1" is NOT a valid IP v4 address
Though this test will tell us that 999.999.999.999
is NOT a valid IP v4 address, it will not detect that 1.2.3
is invalid.
To make it more fail-safe we need a bit more code:
SET Test=%~1 SET Digits=0 FOR %%A IN (%Test:.= %) DO IF %%A GEQ 0 IF %%A LEQ 255 SET /A Digits += 1 IF %Digits% NEQ 4 ECHO "%~1" is NOT a valid IP v4 address
This test can still be fooled when dots are already substituted by tabs or spaces, e.g. 123.456 789 123
will pass as valid.
And so will 123.456.ABC.789.123
To make it even more fail-safe requires, again, more code:
SET Valid=1 FOR /F "tokens=1-4* delims=." %%A IN ("%~1") DO ( IF %%A LSS 0 SET Valid=0 IF %%A GTR 255 SET Valid=0 IF %%B LSS 0 SET Valid=0 IF %%B GTR 255 SET Valid=0 IF %%C LSS 0 SET Valid=0 IF %%C GTR 255 SET Valid=0 IF %%D LSS 0 SET Valid=0 IF %%D GTR 255 SET Valid=0 IF NOT "%%~E"=="" SET Valid=0 ) IF %Valid% EQU 1 ( ECHO "%~1" is a valid IP v4 address ) ELSE ( ECHO "%~1" is NOT a valid IP v4 address )
The last check, IF NOT "%%~E"==""
, makes sure there is no fifth digit.
This code is still not fail-safe, try 127.0..1
, it will pass the test even though it is invalid.
Still more code to the rescue:
SET Valid=1 FOR /F "delims=0123456789." %%A IN ("%~1") DO SET Valid=0 IF "%Valid%"=="1" ( FOR /F "tokens=1-4* delims=." %%A IN ("%~1") DO ( IF "%%~A"=="" SET Valid=0 IF %%A LSS 0 SET Valid=0 IF %%A GTR 255 SET Valid=0 IF "%%~B"=="" SET Valid=0 IF %%B LSS 0 SET Valid=0 IF %%B GTR 255 SET Valid=0 IF "%%~C"=="" SET Valid=0 IF %%C LSS 0 SET Valid=0 IF %%C GTR 255 SET Valid=0 IF "%%~D"=="" SET Valid=0 IF %%D LSS 0 SET Valid=0 IF %%D GTR 255 SET Valid=0 IF NOT "%%~E"=="" SET Valid=0 ) ) IF "%Valid%"=="1" ( ECHO "%~1" is a valid IP v4 address ) ELSE ( ECHO "%~1" is NOT a valid IP v4 address )
This seems to work so far, I haven't found any loopholes yet...
I more or less expected leading zeroes to make the test fail, e.g. 123.009.100.246
, but they don't, at least not on my Windows 10 21H2 machines.
An alternative for the code above is shown below, they are functionally equivalent, so pick your favorite one.
SET Valid=1 FOR /F "delims=0123456789." %%A IN ("%~1") DO SET Valid=0 SET Test=%~1 SET Digits=0 IF "%Valid%"=="1" ( FOR %%A IN (%Test:.= %) DO ( IF "%%~A"=="" SET Valid=0 IF %%A LSS 0 SET Valid=0 IF %%A GTR 255 SET Valid=0 SET /A Digits += 1 ) ) IF NOT "%Digits%"=="4" SET Valid=0 IF "%Valid%"=="1" ( ECHO "%~1" is a valid IP v4 address ) ELSE ( ECHO "%~1" is NOT a valid IP v4 address )
A completely alternative approach would be to use PING
to validate the address, but its return code does not differentiate between invalid addresses and addresses that could not be reached.
Its screen output could be used to determine that, but that introduces a language dependency I would rather avoid.
Validating IP v6 addresses may be a bit tougher than IP v4 addresses, see this Wikipedia page for details.
IP v6 addresses consist of 8 groups of 4 hexadecimal digits, separated by colons, optionally followed by a percent sign and an interface name (Unix) or index number.
For example: 2001:0db8:85a3:0000:0000:8a2e:0370:7334
or 2001:0db8:85a3:0000:0000:8a2e:0370:7334%9
.
The main challenge is that leading zeroes may or may not be omited, and even complete groups of all zeroes may be omited, e.g. 2001:0db8:85a3::8a2e:0370:7334
.
Whatever technique we use to validate an IP v6 address, the first step will be the removal of the interface part, i.e. the percent sign and everything following.
Keep in mind that in batch files, %9
will be interpreted immediately as the value of the ninth command line argument.
Or worse: 2001:0db8:85a3::8a2e:0370:7334%1
; if we don't remove %1
immediately, it may be replaced by 2001:0db8:85a3::8a2e:0370:7334%1
, which might lead to "endless loops".
SET Valid=1 :: Remove interface if necessary FOR /F "tokens=1* delims=%%" %%A IN ("%~1") DO SET Test=%%A :: Check for 3 or more consecutive colons ECHO.%Test% | FIND.EXE ":::" >NUL && SET Valid=0 :: Count the number of colon separated blocks SET Blocks=0 FOR %%A IN (%Test::= %) DO SET /A Blocks += 1 IF %Blocks% GTR 8 SET Valid=0 IF %Blocks% EQU 8 ( ECHO.%Test% | FIND.EXE "::" >NUL && SET Valid=0 ) IF %Blocks% LSS 8 ( ECHO.%Test% | FIND.EXE "::" >NUL || SET Valid=0 ) :: Check if each block consists of 1..4 hexadecimal digits FOR %%A IN (%Test::= %) DO ( REM Check if block is hexadecimal SET /A "Dummy=0x0%%A" || SET /A Valid=0 REM Check if number of hex digits does not exceed 4 IF 0x0%%A GTR 0xFFFF SET Valid=0 ) :: Show result IF %Valid% EQU 1 ( ECHO "%Test%" is a valid IP v6 address ) ELSE ( ECHO "%Test%" is NOT a valid IP v6 address )
Notes: | In the code above, we did not validate the optional interface name or index. |
We did use an external command, FIND , to check for consecutive colons.This could be accomplished with internal commands only, but the code would become really complex. |
A MAC address consists of 6 groups of 2-digit hexadecimal, separated by dashes or colons, or sometimes not separated at all.
With internal commands only, we need a lot of code:
SETLOCAL ENABLEDELAYEDEXPANSION SET Valid=1 SET Test=%~1 :: Check if only 1 separator type is used, be it dash or colon, but not mixed SET Groups=0 FOR %%A IN (%Test:-= %) DO SET /A Groups += 1 IF %Groups% NEQ 6 IF %Groups% NEQ 1 SET Valid=0 SET Groups=0 FOR %%A IN (%Test::= %) DO SET /A Groups += 1 IF %Groups% NEQ 6 IF %Groups% NEQ 1 SET Valid=0 :: Replace delimiters by spaces SET Test=%Test::= % SET Test=%Test:-= % :: Check if the string is sparated or not SET Groups=0 FOR %%A IN (%Test%) DO SET /A Groups += 1 IF %Groups% NEQ 6 IF %Groups% NEQ 1 SET Valid=0 :: For non-separated strings, check each 2-digit substring IF %Groups% EQU 1 ( FOR /L %%A IN (0,2,10) DO ( REM Split the entire string in 2-digit substrings SET Group=!Test:~%%A,2! REM Check if the substring is empty IF "!Group!"=="" SET Valid=0 REM Check if the substring is valid hexadecimal SET /A "Dummy = 0x0!Group!" || SET Valid=0 REM Check if the substring has exactly 2 digits IF 0x0!Group! GTR 0x0FF SET Valid=0 IF 0x1!Group! LSS 0x100 SET Valid=0 ) ) IF %Groups% EQU 6 ( FOR %%A IN (%Test%) DO ( REM Check if the group is valid hexadecimal SET /A "Dummy = 0x0%%A" || SET Valid=0 REM Check if the group has exactly 2 digits IF 0x0%%A GTR 0x0FF SET Valid=0 IF 0x1%%A LSS 0x100 SET Valid=0 ) ) :: Show result IF %Valid% EQU 1 ( ECHO "%~1" is a valid MAC address ) ELSE ( ECHO "%~1" is NOT a valid MAC address ) ENDLOCAL
ISBN-13 numbers consist of 12 decimal digits plus a 1-digit checksum:
SETLOCAL ENABLEDELAYEDEXPANSION SET Test=%~1 :: Remove dashes from input SET Test=%Test:-=% :: Read input one digit at a time, except the last one, and multiply it alternatingly by 1 or by 3 to calculate an intermediate checksum SET Check=0 FOR /L %%A IN (0,1,11) DO ( SET /A Even = %%A %% 2 IF !Even! EQU 0 ( SET /A Check += 3 * !Test:~%%A,1! ) ELSE ( SET /A Check += !Test:~%%A,1! ) ) :: Final checksum equals the number required to round the intemediate checksum to the next multiple of 10 SET /A Check = %Check% %% 10 SET /A Check = 10 - %Check% SET /A Check = %Check% %% 10 :: Check if calculated checksum and last digit match IF "%Test:~12,1%"=="%Check%" ( ECHO "%Test%" is a valid ISBN-13 number ) ELSE ( ECHO "%Test%" is NOT a valid ISBN-13 number ) ENDLOCAL
The Luhn algorithm is used to check credit card numbers (append 0 and security code), IMEI numbers and more.
The following code checks the calculated checksum, regardless of the input string length.
You may want to add a check for empty strings.
For each specific purpose (e.g. IMEI numbers), you may also want to verify the %Digits%
variable:
SETLOCAL ENABLEDELAYEDEXPANSION SET Test=%~1 :: Remove dashes, dots, colons, tabs and spaces SET Test=%Test:-=% SET Test=%Test:.=% SET Test=%Test::=% SET Test=%Test: =% SET Test=%Test: =% SET Scratch=%Test% SET Digits=0 SET Check=0 :Loop SET Digit=%Scratch:~0,1% SET Scratch=%Scratch:~1% :: Break the loop when the last digit is reached, this is the original :: checksum and will be compared later with the calculated checksum IF "%Scratch%"=="" GOTO Checksum SET /A Digits += 1 SET /A Even = %Digits% %% 2 IF %Even% EQU 0 ( SET /A Check += 2 * %Digit% REM If 2 * %Digit% is greater than 9 (digit greater than 4), take the sum of the REM separate digits (e.g. 2*6=12: 1+2=3) which means substract 9 from 2 * %Digit% IF %Digit% GTR 4 SET /A Check -= 9 ) ELSE ( SET /A Check += %Digit% ) GOTO Loop :Checksum SET /A Check = %Check% %% 10 SET /A Check = 10 - %Check% SET /A Check = %Check% %% 10 :: Display result IF "%Check%"=="%Digit%" ( ECHO "%Test%" has a valid Luhn Algorithm checksum ) ELSE ( ECHO "%Test%" does NOT have a valid Luhn Algorithm checksum ) ENDLOCAL
?
or \d
or {x,y}
)page last modified: 2022-03-16; loaded in 0.0015 seconds