1

I have been trying to parse a mac with format "xxxx[Delimiter]xxxx[Delimiter]xxxx" where "x" is in [0-9,A-F,a-f] and "[Delimiter]" is in [:,.,-]. I have tried the following code and have referred the example with title Regex to match a variable in Batch scripting and What is a regular expression for a MAC Address?.

set MACAddr=012a.23fa.5ffc
If [%MACAddr%] EQU [] (
echo MAC Address is not set. Please set it to proceed.
) else (
echo %MACAddr%|findstr /r "^([0-9A-Fa-f]{2}[:.-]?){5}([0-9A-Fa-f]{2})$"
if errorlevel 1 (echo Mac %MACAddr% is not of same Format as xxxx.xxxx.xxxx) else ( pause )
)

also tried this

echo %MACAddr%|findstr /r "^([0-9A-Fa-f]{4})([:.-])([0-9A-Fa-f]{4})([:.-])([0-9A-Fa-f]{4})$"

But it only runs if errorlevel 1 (echo Mac %MACAddr% is not of same Format as xxxx.xxxx.xxxx).Please tell me what am i doing wrong.

aschipfl
  • 33,626
  • 12
  • 54
  • 99
Anukruti
  • 87
  • 1
  • 9
  • 3
    sorry, `findstr` has no full REGEX support. Especially no `{x}`. See `findstr /?` - and try to be not too disappointed. – Stephan Mar 14 '17 at 12:58
  • @Anu, please read the help for the command before posting questions. – Squashman Mar 14 '17 at 13:31
  • So are you trying to match: `012a.23fa.5ffc` & `F8-CA-B8-07-5A-D0` & `d4:be:d9:97:74:c2`? I have never seen mac address expressed as `012a.23fa.5ffc`. Would make more sense to be consistent: `01.2a.23.fa.5f.fc` – Squashman Mar 14 '17 at 13:48
  • @Squashman I was trying to match 012a.23fa.5ffc or 012a-23fa-5ffc or 012a:23fa:5ffc.Actually this format is followed by my router. – Anukruti Mar 15 '17 at 04:34

3 Answers3

5

findstr has a very limited subset of REGEX (see for /?)

Your request can be formulated as:

findstr /ri "^[0-9A-F][0-9A-F][0-9A-F][0-9A-F].[0-9A-F][0-9A-F][0-9A-F][0-9A-F].[0-9A-F][0-9A-F][0-9A-F][0-9A-F]$"

where . means "any char". If you want to break it down to just ;, . and - as delimiters, use [;.-] instead of .. If the delimiters may be there or may not be there, use [;.-]* instead (where * means "zero or more" - sorry, there is no ? "none or one" with findstr.)

Stephan
  • 53,940
  • 10
  • 58
  • 91
  • Hi @Stephan, that worked brilliantly!! Although its true that findstr does have a limited subset of REGEX , but this worked out ..thanks!. :) – Anukruti Mar 15 '17 at 06:57
3

As Stephan already mentioned in his answer, findstr does not have full RegEx support.

So grouping ((/)), alternatives (|), repetitions ({/}) and options (?) are not supported.

There is an additional limitation that needs to be taken into account: the number of character classes ([/]) is limited to 15, so specifying 16 or more results in an error.
And not enough, character classes are buggy, they might match unexpected characters; for instance, the range [0-9] matches the decimal figures 0 to 9, but also characters ² and ³; the range [A-Z] (without the /I option) matches also lower-case letters erroneously, like z, and also some other characters, like Á or á, depending on the current code page.

Refer to the great post by dbenham concerning all the bugs and limitations of findstr.


To work around all that, you can specify multiple search strings, like findstr /R "search_string1 search_string2" (white-space-separated list) or findstr /R /C:"search string1" /C:"search string2" (this even allows white-spaces to be part of the search strings).

I would do your task like this:

echo %MACAddr%| (findstr /I /X /R ^
    /C:"[0-9A-F][0-9A-F][0-9A-F][0-9A-F]\.[0-9A-F][0-9A-F][0-9A-F][0-9A-F]\.[0-9A-F][0-9A-F][0-9A-F][0-9A-F]" ^
    /C:"[0-9A-F][0-9A-F][0-9A-F][0-9A-F]:[0-9A-F][0-9A-F][0-9A-F][0-9A-F]:[0-9A-F][0-9A-F][0-9A-F][0-9A-F]" ^
    /C:"[0-9A-F][0-9A-F][0-9A-F][0-9A-F]-[0-9A-F][0-9A-F][0-9A-F][0-9A-F]-[0-9A-F][0-9A-F][0-9A-F][0-9A-F]" ^
    /C:"[0-9A-F][0-9A-F]\.[0-9A-F][0-9A-F]\.[0-9A-F][0-9A-F]\.[0-9A-F][0-9A-F]\.[0-9A-F][0-9A-F]\.[0-9A-F][0-9A-F]" ^
    /C:"[0-9A-F][0-9A-F]:[0-9A-F][0-9A-F]:[0-9A-F][0-9A-F]:[0-9A-F][0-9A-F]:[0-9A-F][0-9A-F]:[0-9A-F][0-9A-F]" ^
    /C:"[0-9A-F][0-9A-F]-[0-9A-F][0-9A-F]-[0-9A-F][0-9A-F]-[0-9A-F][0-9A-F]-[0-9A-F][0-9A-F]-[0-9A-F][0-9A-F]" ^

)

You can also use the syntax without /C, but it is terrible to read:

echo %MACAddr%| findstr /I /X /R "[0-9A-F][0-9A-F][0-9A-F][0-9A-F]\.[0-9A-F][0-9A-F][0-9A-F][0-9A-F]\.[0-9A-F][0-9A-F][0-9A-F][0-9A-F] [0-9A-F][0-9A-F][0-9A-F][0-9A-F]:[0-9A-F][0-9A-F][0-9A-F][0-9A-F]:[0-9A-F][0-9A-F][0-9A-F][0-9A-F] [0-9A-F][0-9A-F][0-9A-F][0-9A-F]-[0-9A-F][0-9A-F][0-9A-F][0-9A-F]-[0-9A-F][0-9A-F][0-9A-F][0-9A-F] [0-9A-F][0-9A-F]\.[0-9A-F][0-9A-F]\.[0-9A-F][0-9A-F]\.[0-9A-F][0-9A-F]\.[0-9A-F][0-9A-F]\.[0-9A-F][0-9A-F] [0-9A-F][0-9A-F]:[0-9A-F][0-9A-F]:[0-9A-F][0-9A-F]:[0-9A-F][0-9A-F]:[0-9A-F][0-9A-F]:[0-9A-F][0-9A-F] [0-9A-F][0-9A-F]-[0-9A-F][0-9A-F]-[0-9A-F][0-9A-F]-[0-9A-F][0-9A-F]-[0-9A-F][0-9A-F]-[0-9A-F][0-9A-F]"

You can put the search strings into a text file (say MACAddrRegEx.txt) and use this syntax:

echo %MACAddr%| findstr /I /X /R /G:"MACAddrRegEx.txt"

And the content of MACAddrRegEx.txt is:

[0-9A-F][0-9A-F][0-9A-F][0-9A-F]\.[0-9A-F][0-9A-F][0-9A-F][0-9A-F]\.[0-9A-F][0-9A-F][0-9A-F][0-9A-F]
[0-9A-F][0-9A-F][0-9A-F][0-9A-F]:[0-9A-F][0-9A-F][0-9A-F][0-9A-F]:[0-9A-F][0-9A-F][0-9A-F][0-9A-F]
[0-9A-F][0-9A-F][0-9A-F][0-9A-F]-[0-9A-F][0-9A-F][0-9A-F][0-9A-F]-[0-9A-F][0-9A-F][0-9A-F][0-9A-F]
[0-9A-F][0-9A-F]\.[0-9A-F][0-9A-F]\.[0-9A-F][0-9A-F]\.[0-9A-F][0-9A-F]\.[0-9A-F][0-9A-F]\.[0-9A-F][0-9A-F]
[0-9A-F][0-9A-F]:[0-9A-F][0-9A-F]:[0-9A-F][0-9A-F]:[0-9A-F][0-9A-F]:[0-9A-F][0-9A-F]:[0-9A-F][0-9A-F]
[0-9A-F][0-9A-F]-[0-9A-F][0-9A-F]-[0-9A-F][0-9A-F]-[0-9A-F][0-9A-F]-[0-9A-F][0-9A-F]-[0-9A-F][0-9A-F]

Remove the former three search strings if you do not want to accept strings with hexadecimal digits in groups of four; remove latter three ones if you do not want to accept strings in groups of two.

The aforementioned bug concerning upper- and lower-case-matching against letter ranges does not apply here since the /I option is specified, which defines to do case-insensitive searches. However, the other range-related bugs are ignored here as otherwise, the search strings became extensively long (note that [0-9] needed to be changed to [0123456789] in order to match decimal digits only; same for letters) and so the length limit was going to be exceeded.

Community
  • 1
  • 1
aschipfl
  • 33,626
  • 12
  • 54
  • 99
  • 2
    Shouldn't all these classes `[0-9A-Z]` better be `[0-9A-F]` ? –  Mar 14 '17 at 19:31
  • 1
    Yes, of course, @LotPings, thank you! I seem to have confused myself with the `[A-Z]` example at the first section... :-/ – aschipfl Mar 14 '17 at 19:36
1

You know how when you get a problem like this, you just have to keep going until you get a solution?

@ECHO Off
SETLOCAL ENABLEDELAYEDEXPANSION 

:: This simply tests various addresses

FOR %%D IN (":" "." "-" "#") DO FOR %%T IN (
.012a.23fa.5ffc
012a.23fa.5ffc.
012a.23fa.5ffc
012a.23fa
012a.23fa.5ffc.0123
01ka.23fa.5ffc
012a.23ga.5ffc
012a.23fa.5pfc
012a1.23fa.5ffc
012a.263fa.5ffc
012a.23fa.58ffc
1.2.3
03ed.2.f
) DO (
 set "MACAddr=%%T"
 SET "macaddr=!macaddr:.=%%~D!"
 CALL :testmacaddr
)
SET "macaddr="
CALL :testmacaddr

GOTO :eof

:testmacaddr
SET "invalid="
SET "valid="
If NOT DEFINED MACAddr SET "invalid=MACADDR not set"&GOTO report
FOR %%d IN (":" "." "-") DO (
 IF "%macaddr:~0,1%"=="%%~d" SET "invalid=Initial"
 IF "%macaddr:~-1%"=="%%~d" SET "invalid=Terminal"
 SET "control="tokens=1-4delims=%%~d""
 CALL :tokenise
)
:report
IF NOT DEFINED valid IF NOT DEFINED invalid SET "invalid=Incorrect octet count"
:: pad 'macaddr' with a goodly number of spaces,
:: show the first 20 character of the result and the 'invalid' value

SET "macaddr=%macaddr%                          "
ECHO %macaddr:~0,20% : %invalid%
GOTO :EOF

:tokenise
FOR /f %control% %%p IN ("%macaddr%") DO (
 IF "%%s"=="" IF "%%r" neq "" (
  SET /a octets=1
  CALL :validate %%p %%q %%r
 )
)

GOTO :eof

:validate
ECHO %1|FINDSTR /r /x /i "[0-9a-f]">NUL
IF NOT ERRORLEVEL 1 GOTO valok
ECHO %1|FINDSTR /r /x /i "[0-9a-f][0-9a-f]">NUL
IF NOT ERRORLEVEL 1 GOTO valok
ECHO %1|FINDSTR /r /x /i "[0-9a-f][0-9a-f][0-9a-f]">NUL
IF NOT ERRORLEVEL 1 GOTO valok
ECHO %1|FINDSTR /r /x /i "[0-9a-f][0-9a-f][0-9a-f][0-9a-f]">NUL
IF ERRORLEVEL 1 SET "invalid=Octet %octets%"&GOTO :eof
:valok
shift
SET /a octets+=1
IF %octets% lss 4 GOTO validate
SET "valid=Y"

GOTO :eof

So - the first part simply generates a series of "possible" entries for testing. The routine :testmacaddr with macaddr assigned to a test string actually does the work and the part after label :report reports results - if invalid is defined, then the supplied macaddr is invalid for the reason in %invalid%

The for %%d loop checks for initial or terminal delimiters and establishes the control string for the inner loop in the :tokenise routine.

The :tokenise routine examines the supplied address for the first 4 tokens using the current delimiter, assigning the octets to %%p..%%s. Since we want exactly 3, then %%s must be empty and %%r not-empty to require further validation using :validate

:validate examines each octet for an /x exact match /i case-insensitive to the /r regular expression 1,2,3 or 4 hex-digits. If the octet fails all of these tests, invalid is set to the failing octet number. If all octets pass then valid is set to Y

So, if there are no failing conditions and the :validate routine was passed once, then the value is valid.

Magoo
  • 77,302
  • 8
  • 62
  • 84
  • @Mangoo Thanks. I haven't tried this out yet but this seems more generic than previous answer so I will try this out surely. :) – Anukruti Mar 15 '17 at 04:38