1

I am looking for a way in a Windows batch file to find if a file contains the word Shutdown a given number of times.

I have tried this using FINDSTR:

 FINDSTR /r .*Shutdown.*{5} file.txt

This doesn't seem to work, but if I remove the {5} it executes successfully.

Also I would eventually like the 5 to be a variable, presumably this is possible with some sort of dynamic command?

Thanks

Arj
  • 1,981
  • 4
  • 25
  • 45

3 Answers3

3

It is possible to use FINDSTR to detect if "Shutdown" appears 5 or more times anywhere within a file. All the information you need is contained within What are the undocumented features and limitations of the Windows FINDSTR command?, but it is more than a bit tricky and not at all obvious.

A regex search can match multiple instances of Shutdown across multiple lines, as long as all intervening characters, including carriage return and linefeed characters, are matched by an appropriate character class range. I cannot post the characters required here, so I will use symbolic notation within angle brackets.

The following regex expression will match any byte character except 0xFF (decimal 255). It consists of a character class expression with two ranges and a gap in the middle for 0xFF. The gap is critical because FINDSTR will fail (and possibly hang) if 0xFF is included:

[<0x01>-<space><tab>-<0xEA>]

You might think the expression should be [<0x01>-<0xFE>], but that does not work because FINDSTR does not collate characters by the numeric code value.

So to look for 5 or more instances of Shutdown anywhere within the file, you would need the following regex search:

Shutdown[<0x01>-<space><tab>-<0xEA>]*Shutdown[<0x01>-<space><tab>-<0xEA>]*Shutdown[<0x01>-<space><tab>-<0xEA>]*Shutdown[<0x01>-<space><tab>-<0xEA>]*Shutdown

The 0xEA (decimal 234) character is an extended ASCII character, and extended ASCII cannot be included on the command line of a FINDSTR search. So the search string must be put in an external file and the /G:file option must be used.

Here is a complete batch script that takes the minimum number of Shutdown instances to search for as the 1st argument, and the name of the file to search as the 2nd argument. Again I use symbolic notation within angle brackets in place of the actual characters needed.

@echo off
set count=%1
set file=%2
setlocal enableDelayedExpansion
set "search="
for /l %%N in (1 1 %count%) do set "search=!search!Shutdown[<0x01>-<space><tab>-<0xEA>]*"
set "search=!search:~0,-9!"
echo(!search!>search.txt
findstr /rg:search.txt %file% >nul&&echo FOUND||echo NOT found

The maximum supported count is limited by the maximum regex string length. For XP the max regex length is 127 bytes, equating to a count of 7. On Vista and Windows 7 the max regex length is 254 bytes, which should support a count of 15. But my testing on Windows 7 only supported a count up to 12. Additional tests reveal the max length is affected by how many string literals and character classes appear, as well as the relative placement of each. But I haven't been able to figure out an exact formula.


If you don't want to use an external file, then the following regex expression is almost as good. It matches any characters except for the following extended ASCII hex codes: 0xE0, 0xE2, 0xE3, 0xE4, 0xE5, 0xE7, 0xE8, 0xE9, 0xEA, 0xEB, 0xED, 0xEE, 0xFF.

[<0x01>-<space><tab>-Z]

The full regex search would be:

Shutdown[<0x01>-<space><tab>-Z]*Shutdown[<0x01>-<space><tab>-Z]*Shutdown[<0x01>-<space><tab>-Z]*Shutdown[<0x01>-<space><tab>-Z]*Shutdown

And here is the complete batch script:

@echo off
setlocal enableDelayedExpansion
set count=%1
set file=%2
set "search="
for /l %%N in (1 1 %count%) do set "search=!search!Shutdown[<0x01>-<space><tab>-Z]*"
set "search=!search:~0,-9!"
findstr /rc:"!search!" %file% >nul&&echo FOUND||echo NOT found
Community
  • 1
  • 1
dbenham
  • 127,446
  • 28
  • 251
  • 390
  • EDIT - Trimmed unnecessary expression from end of search string to maximize the supported count value. Also corrected the count limitation discussion. Also added explanation of odd sequencing in character class range expression. – dbenham May 19 '13 at 14:41
  • Incredible, what a detailed answer. Thanks – Arj May 20 '13 at 11:19
  • In my case I get an error saying the search expression is too long, but no doubt the code example wil be very useful anyway. Thanks – Arj May 20 '13 at 13:11
2
.*Shutdown.*Shutdown.*Shutdown.*Shutdown.*Shutdown.*

would seem to be the only way for FINDSTR

srsly.


Test batch:

@ECHO OFF
SETLOCAL
FOR /f "delims=" %%i IN (f5shutdown.txt) DO (
 ECHO %%i|FINDSTR /r .*Shutdown.*Shutdown.*Shutdown.*Shutdown.*Shutdown.* >NUL
 IF ERRORLEVEL 1 ECHO FAILED IN "%%i"
 IF NOT ERRORLEVEL 1 ECHO FOUND  IN "%%i"
)

GOTO :eof

Test textfile in f5shutdown.txt

Shutdown
Shutdown Shutdown
Shutdown Shutdown Shutdown Shutdown
Shutdown Shutdown Shutdown Shutdown Shutdown
Shutdown already !Shutdown Shutdown Shutdown Shutdown now
You should now Shutdown Shutdown Shutdown Shutdown Shutdown
ShutdownShutdownShutdownShutdownShutdown
OK, don't shutdown then...

Result:

FAILED IN "Shutdown"
FAILED IN "Shutdown Shutdown"
FAILED IN "Shutdown Shutdown Shutdown Shutdown"
FOUND  IN "Shutdown Shutdown Shutdown Shutdown Shutdown"
FOUND  IN "Shutdown already !Shutdown Shutdown Shutdown Shutdown now"
FOUND  IN "You should now Shutdown Shutdown Shutdown Shutdown Shutdown"
FOUND  IN "ShutdownShutdownShutdownShutdownShutdown"
FAILED IN "OK, don't shutdown then..."

Works for me (W7)


Ah - perhaps you meant "occurs on 5 (or whatever) lines or "occurs 5 times in the file"...

@ECHO OFF
SETLOCAL
FOR /f "delims=" %%i IN (f5shutdown.txt) DO (
 ECHO %%i|FINDSTR /r .*Shutdown.*Shutdown.*Shutdown.*Shutdown.*Shutdown.* >NUL
 IF ERRORLEVEL 1 ECHO FAILED IN "%%i"
 IF NOT ERRORLEVEL 1 ECHO FOUND  IN "%%i"
)

:: For 'shutdown' on n different lines: (assuming case-insensitive)
FOR /f %%i IN ('FIND /i /c "shutdown" ^<f5shutdown.txt') DO SET shutdowns=%%i
ECHO Second method: %shutdowns% lines found with "Shutdown"

:: For 'shutdown' occurrences (assuming case-insensitive)
SET /a shutdowns=0
FOR /f "delims=" %%i IN (f5shutdown.txt) DO (
 SET line=%%i
 CALL :findem
)
ECHO Third method found "Shutdown" %shutdowns% times
GOTO :eof

:findem
SET line=%line:"=%
SET after=%line:*shutdown=%
IF NOT DEFINED after SET /a shutdowns+=1&GOTO :EOF 
if NOT "%line%"=="%after%" SET "line=%after%"&SET /a shutdowns+=1&GOTO findem
GOTO :eof

(I added an extra non-shutdown-containing line for the test. Found 8 lines and 28 occurrences)

Magoo
  • 77,302
  • 8
  • 62
  • 84
  • OMG RLY? Ok, so if I wanted a variable number of `.*Shutdown.*`'s, I would need to loop `around [and around]{x}` ? – Arj May 17 '13 at 13:18
  • I've just tried your code and it doesn't seem to return the correct result.. it doesn't find the string – Arj May 17 '13 at 13:24
  • Thanks for the update, yes I did mean '5 times in the file' - I'll give it a go! – Arj May 17 '13 at 14:32
0

This should detect when shutdown is on only 5 lines in the file.txt

@echo off
for /f %%a in ('find /i /c "shutdown" ^< "file.txt" ') do if %%a EQU 5 echo found it 5 times
foxidrive
  • 40,353
  • 10
  • 53
  • 68
  • This will detect if a file contains at least 5 lines with SHUTDOWN, but it will not properly count the number of SHUTDOWN if some appear on the same line. That may or may not be a problem for the OP. – dbenham May 18 '13 at 00:04
  • In my specific case it would be fine, but in general the ideal solution would give you a total count, or perhaps an option to include/exclude multiple instances on the same line. – Arj May 20 '13 at 11:21