Rob van der Woude's Scripting Pages

Validate Variables

Check if a string matches the required format

 

 

Hexadecimal

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.

 

Octal

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

 

Binary

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

 

Decimal

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

 

IP v4 Address

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.

 

IP v6 Address

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.

 

MAC Address

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

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

 

Luhn Algorithm

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

 


page last modified: 2022-03-16