findstr: Extract value from key pair - windows

For example :
echo key=1 | findstr key
How get 1 from pair via findstr.
Please avoid ugly for, delims or something, if possible.

Related

Can I search for multiple strings in one "find" command in batch script?

I have a windows batch script that will look for a string within a file
find /i "WD6" %Inputpath%file.txt
if %errorlevel% == 0 GOTO somestuff
Currently this is what my code looks like. I've come across a new string I want to search for in the same file and do the same action if it finds it, it stored it in a variable called %acctg_cyc% can I search for both strings in one line of code? I tried this:
find /i "WD6" %acctg_cyc% %Inputpath%file.txt
if %errorlevel% == 0 GOTO somestuff
But it seems to ignore the %acctg_cyc% and only look for "WD6" in file.txt. I tried testing where %acctg_cyc% is in file.txt and when it is not and it passes both times.
Any thoughts? I know I could do this in more lines of code but I'm really trying to avoid that right now. Maybe it's just not possible.
Thank you for any help!
find isn't very powerful. It searches for one string only (even if it is two words): find "my string" file.txt looks for the string my string.
findstr has much more power, but you have to be careful how to use it:
findstr "hello world" file.txt
finds any line, that contains either hello or world or both of them.
see findstr /? for more info.
Finding both words in one line is possible with (find or findstr):
find "word1" file.txt|find "word2"
finding both words scattered over the file (find or findstr):
find "word1" file.txt && find "word2" file.txt
if %errorlevel%==0 echo file contains both words
I tried findstr with multiple /C: arguments (one for each to be searched sentence) which did the trick in my case.
So this is my solution for finding multiple sentences in one file and redirect the output:
findstr /C:"the first search" /C:" a second search " /C:"and another" sourcefile.txt > results.txt
I used this. Maybe not much orthodox, but works!
It waits until browsers dismiss
:do_while_loop
rem ECHO LOOP %result%
rem pause
tasklist /NH | find "iexplore.exe"
set result=%ERRORLEVEL%
tasklist /NH | find "firefox.exe"
set result=%result%%ERRORLEVEL%
if not "%result%"=="11" goto :do_while_loop

How can I make a batch file that will tell me which lines of a text file are NOT in another file?

What I'm trying to do is take a text file with a bunch of strings to search for, each on its own line, and search for each one of these strings in a file (check.txt). I want the output to be a text file with a list of all the strings that COULDN'T be found.
I've tried a few things so far.
for /F "tokens=*" %%A in search.txt do (
#echo on
FINDSTR %%A check.txt
IF ERRORLEVEL 1 echo %%A FAIL > fail_match.txt
)
Another attempt I made (this one was just to tell me if the whole list was good or not) was
#echo on
FINDSTR /g:search.txt check.txt > a_match.txt
IF ERRORLEVEL 1 echo bad > a_match.txt
I realize that these are incredibly basic, and I'm sure there's some easy answer that I just don't understand. I'm not a programmer; I just want to make my job a lot easier (and faster).
To clarify, my list of things to search for is in search.txt, my list of things to check them against is check.txt. Check.txt is a json file, so it's all one enormous line. I don't know if that will make a difference or not. I want a list of all lines in search.txt that are not in check.txt.
Your search scheme seems naive on two fronts:
1) JSON is not guaranteed to be a single line. A valid JASON may have any amount of whitespace, including newlines. This could cause problems if your search string logically matches across multiple lines.
2) What about substring matches? Suppose one search string is bat, and your JSON contains bath. I doubt you would want to consider that a match.
It is possible that neither of the above concerns are a problem for your case. Assuming they aren't, then there may be a fairly simple solution using FINDSTR.
You were close on your first try, except
A) - Your FOR /F IN() clause is missing parentheses
B) - You want to force each search string to be interpreted as a string literal, possibly with spaces. That requires the /C: option.
C) - You assume leading spaces are not significant in your search string ("tokens=*" strips leading spaces)
D) - You assume no search lines begin with semicolon. (The default EOF character is semicolon, and FOR /F skips all lines that begin with the EOF character)
E) - Quotes and backslashes must be escaped within a search string: \" -> \\\\\", \ -> \\, " -> \". See What are the undocumented features and limitations of the Windows FINDSTR command? for more information.
Points C) and D) may be fixed by disabling EOF and DELIMS using the following odd syntax:
for delims^=^ eof^= %%A in ...
Point E) can be addressed by defining a variable and adding escape sequences via search and replace. But this requires delayed expansion, but delayed expansion will corrupt FOR /F variables upon expansion if they contain !. So delayed expansion must be strategically toggled on and off within the loop.
Instead of using IF ERRORLEVEN n, you can use conditional command concatenation || to take action if the previous command failed.
You don't need to see the output of the FINDSTR command, so that can be redirected to NUL.
You can improve performance by redirecting just once, outside the loop.
#echo off
setlocal disableDelayedExpansion
>fail_match.txt (
for /f delims^=^ eol^= %%A in (search.txt) do (
set "search=%%A"
setlocal enableDelayedExpansion
set "search2=!search:\"=\\"!"
set "search2=!search2:\=\\!"
set "search2=!search2:"=\"!"
findstr /c:"!search2!" check.txt >nul || echo !search!
endlocal
)
)
If none of your search strings begin with ;, and no search string contains " or \, then the solution can be as simple as:
#echo off
setlocal disableDelayedExpansion
>fail_match.txt (
for /f "delims=" %%A in (search.txt) do findstr /c:"%%A" check.txt >nul || echo %%A
)
if I read your question right (output all lines of check.txt that are not in search.txt), this single line should do:
findstr /v /x /g:search.txt check.txt > nomatch.txt

Is it possible to use TWO spaces as a single Delimiter in CMD?

I don't think this is possible, but I'd like to be able to do this, or possibly use an alternative method...
I have a batch file;
for /f "usebackq tokens=*" %%a in (`wmic process get description, commandline`) do (
*Some Code*
)
I need to be able to take the two answers from each line, and use them individually (basically, use the description to check if a process is running, then after I've killed the process and done some file clean-up work, reload the original process including any command line parameters.
One example of the output for a process I may need to end/re-open might be;
"C:\some folder\some other folder\some_application" -cmd_parameter process_name.exe
Note that the descrption is clearly defined by multiple spaces..
So is there a way of saying
for /f "tokens=* delims= " <--(The delims is TWO spaces, not space OR space)
Another way that may be better could be to replcae all instances of multiple spaces with a special character (i.e. one that is never used in a proces or path), and then use that as my delimeter... Though I don't know if that is even possible..
I'm also open to any alternative methods, as long as I can get the process name (to check against a pre-defined list of processes, and the full path to the exe, plus any command line paramteres given.
Thanks all
In direct answer to you question: No, you cannot specify 2 spaces as a delimiter. You can use SET search and replace to change 2 spaces into some unique character, but determining a unique character that will never appear in your description or command line is easier said then done.
A better alternative is to change the output format of WMIC to LIST - one value per line in the form of propertyName=Value. Each propery value can be stored in a variable, and then when the last property for a process is recorded you can take action using the variable values. WMIC output uses Unicode, and that results in a CarriageReturn character being appended to the end of each variable assignment. The CarriageReturn must be stripped to get the correct results.
#echo off
setlocal
for /f "tokens=1* delims==" %%A in ('"wmic process get description, commandline /format:list"') do (
if "%%A"=="CommandLine" (
set "cmd=%%B"
) else if "%%A"=="Description" (
set "desc=%%B"
setlocal enableDelayedExpansion
set "desc=!desc:~0,-1!"
set "cmd=!cmd:~0,-1!"
echo(
echo Do whatever you need to do with the description and command line.
echo description=!desc!
echo command line=!cmd!
endlocal
)
)
There are a few things you need to be careful of.
1) You could have multiple processes for the same image name. If you kill a process via the image name (description), then you will delete all of them. If you also restart it it based on the command line, then it will be killed again when the next process with the same name is killed. It is probably better to kill the process via the process ID.
2) If you know the image name (description) of the process, then you can restrict your output using the WMIC WHERE clause.
3) The command line reported by WMIC is not always reliable. The process is able to modify the value that is reported as the command line.
Here is a solution that retrieves the process ID and command line for a specific description.
EDIT - I fixed the code below
#echo off
setlocal
for /f "tokens=1* delims==" %%A in ('"wmic process where description='MyApp.exe' get processId, commandline /format:list"') do (
if "%%A"=="CommandLine" (
set "cmd=%%B"
) else if "%%A"=="ProcessId" (
set "id=%%B"
setlocal enableDelayedExpansion
set "id=!id:~0,-1!"
set "cmd=!cmd:~0,-1!"
echo(
echo Do whatever you need to do with the process id and command line.
echo process Id=!id!
echo command line=!cmd!
endlocal
)
)
Note - the WMIC WHERE clause uses SQL syntax. It can be made complex using AND and OR conditions, and it supports the LIKE operator using % and _ as wildcards. I believe the entire expression needs to be enclosed in double quotes when it becomes complex.
Foreword
I'd just like to add this for future readers, because I had this problem, solved it myself and I think it'll be useful just to show how to do this simply. Firstly, dbenham is absolutely correct in his answer that "No, you cannot specify 2 spaces as a delimiter.". Since you can't do it directly using the batch for loop, you can simply make your own that does the job. Again dbenham is correct in saying
"You can use SET search and replace to change 2 spaces into some unique character"
And thats somewhat similar to what I did (with some differences) but for completeness sake I think its good to have it on record. The thing is, simply setting all occurences of double spaces to some other character doesn't always solve the problem. Sometimes we have more than two spaces and what we really want is to delimit strings by more than one space. The problem I was trying to solve here is more like this (from the OP) Ricky Payne
"Another way that may be better could be to replcae all instances of multiple spaces with a special character (i.e. one that is never used
in a proces or path), and then use that as my delimeter... Though I
don't know if that is even possible.."
The answer to that is that It IS possible, and not hard at all. All you need is to be able to
A. loop over each character of the string
B. differentiate single from double (or more) spaces
C. turn a flag on when you encounter a double space
D. turn the double (or more) spaces into a special character or sequence of characters that you can delimit by.
the code
To do Exactly this, I coded this for my own use (edited for clarity):
FOR /F "tokens=* delims=*" %%G IN ('<command with one line output>') DO (SET
"LineString=%%G")
SET /A "tempindex=0"
:LineStringFOR
SET "currchar=!LineString:~%tempindex%,1!"
IF "!currchar!"=="" (goto :LineStringFOREND)
SET /A "tempindex=!tempindex!+1"
SET /A "BeforeSpacePosition=!tempindex!"
SET /A "AfterSpacePosition=!tempindex!+1"
IF NOT "!LineString:~%BeforeSpacePosition%,2!"==" " (goto :LineStringFOR)
:LineStringSUBFOR
IF "!LineString:~%BeforeSpacePosition%,2!"==" " (
SET LineString=!LineString:~0,%BeforeSpacePosition%!!LineString:~%AfterSpacePosition%!
GOTO :LineStringSUBFOR
) ELSE (
SET LineString=!LineString:~0,%BeforeSpacePosition%!;!LineString:~%AfterSpacePosition%!
GOTO :LineStringSUBFOREND
)
:LineStringSUBFOREND
GOTO :LineStringFOR
:LineStringFOREND
ECHO Final Result is "!LineString!"
So if your input (output of the command in the FOR or you can change that FOR loop to take in a string) was:
"a b c a b c"
The output should be in this format:
"a;b;c;a b c"
I have tested this on my own code. However, for my answer here I removed all of my comments and changed some variable names for clarity. If this code doesn't work after putting in your commands feel free to let me know and I'll update it but it SHOULD be working. Formatting on here might prevent a direct copy paste.
Just to show whats actually going on
The program flow is basically like this:
FOR each character
:TOP
grab the next character
set a variable to the current index
set another variable to the next index
IF this or the next character are not spaces, goto the TOP
:Check for 2 spaces again
IF this and the next character are both spaces then
get the string up to (but not including) the current index AS A
get the string after the current index AS B
set the string to A+B
goto Check for 2 spaces again
ELSE we have turned the double or more space into one space
get the string up to (but not including) the current index AS A
get the string after the current index AS B
set the string to A + <char sequence of choice for delimiting> + B
goto TOP to grab the next character
After all characters are looped over
RETURN the string here (or echo it out like I did)
Extra
dbenham says in his answer on this type of method that:
"You can use SET search and replace to change 2 spaces into some
unique character, but determining a unique character that will never
appear in your description or command line is easier said then done."
While this may have been true in the past, my yeilded that (at least for my method correct me if I'm wrong for other cases) you can in fact use a delimiter that definitely WON'T appear in your input. this is accomplished by using multicharacter delimiters. This doesn't allow you to use the standard FOR loop, however you can quite easily do this manually. This is described much more in depth here:
"delims=#+#" - more then 1 character as delimiter
Great thread!
This got me thinking and I came up with a slightly sideways solution that may work well for someone as it did for me.
As the original questions was for the WMIC command, and the output can be CSV format, why not just circumvent the space handling by using the /format:csv switch and setting a comma as the delimiter, and incorporating 'usebackq'?
Of course, this might not work if the data itself from WMIC has commas but waqs perfect in my instance where I wanted only the BootOptionOnWatchDog status
would look something like this:
FOR /F "usebackq skip=1 tokens=1-31 delims=," %a IN (`%windir%\system32\wbem\wmic computersystem list /format:csv`) DO echo %f
which returns:
BootOptionOnWatchDog
Normal boot
I ended using 'skip=2' which would return "Normal Boot"
btw, dont post here often hence posting as a guest, but thought it prudent to put this here as it was this post that helped me come to the answer above.
cheers
-steve (NZ)

How to find the number of occurrences of a string in file using windows command line?

I have a huge files with e-mail addresses and I would like to count how many of them are in this file. How can I do that using Windows' command line ?
I have tried this but it just prints the matching lines. (btw : all e-mails are contained in one line)
findstr /c:"#" mail.txt
Using what you have, you could pipe the results through a find. I've seen something like this used from time to time.
findstr /c:"#" mail.txt | find /c /v "GarbageStringDefNotInYourResults"
So you are counting the lines resulting from your findstr command that do not have the garbage string in it. Kind of a hack, but it could work for you. Alternatively, just use the find /c on the string you do care about being there. Lastly, you mentioned one address per line, so in this case the above works, but multiple addresses per line and this breaks.
Why not simply using this (this determines the number of lines containing (at least) an # char.):
find /C "#" "mail.txt"
Example output:
---------- MAIL.TXT: 96
To avoid the file name in the output, change it to this:
find /C "#" < "mail.txt"
Example output:
96
To capture the resulting number and store it in a variable, use this (change %N to %%N in a batch file):
set "NUM=0"
for /F %N in ('find /C "#" ^< "mail.txt"') do set "NUM=%N"
echo %NUM%
Using grep for Windows
Very simple solution:
grep -o "#" mail.txt | grep -c .
Remember a dot at end of line!
Here is little bit more understandable way:
grep -o "#" mail.txt | grep -c "#"
First grep selects only "#" strings and put each on new line.
Second grep counts lines (or lines with #).
The grep utility can be easy installed from grep-for Windows page. It is very small and safe text filter. The grep is one of most usefull Unix/Linux commands and I use it in both Linux and Windows daily.
The Windows findstr is good, but does not have such features as grep.
Installation of the grep in Windows will be one of the best decision if you like CLI or batch scripts.
Download and Installation
Download latest version from the project page https://sourceforge.net/projects/grep-for-windows/. Direct link to file is https://sourceforge.net/projects/grep-for-windows/files/grep-3.5_win32.zip/download.
Unzip the ZIP archive. A file is inside.
Put the grep.exe file to the C:\Windows directory or another place from the system path list got using command echo %PATH%.
That is all.
Test if grep is working:
Open command line window (cmd)
Run the command grep --help
Uninstallation
Delete the grep.exe file from folder where you have placed it.
May be it's a little bit late, but the following script worked for me (the source file contained quote characters, this is why I used 'usebackq' parameter).
The caret sign(^) acts as escape character in windows batch scripting language.
#setlocal enableextensions enabledelayedexpansion
SET TOTAL=0
FOR /F "usebackq tokens=*" %%I IN (file.txt) do (
SET LN=%%I
FOR %%J IN ("!LN!") do (
FOR /F %%K IN ('ECHO %%J ^| FIND /I /C "searchPhrase"') DO (
#SET /A TOTAL=!TOTAL!+%%K
)
)
)
ECHO Number of occurences is !TOTAL!
I found this on the net. See if it works:
findstr /R /N "^.*certainString.*$" file.txt | find /c "#"
I would install the unix tools on your system (handy in any case :-), then it's really simple - look e.g. here:
Count the number of occurrences of a string using sed?
(Using awk:
awk '$1 ~ /title/ {++c} END {print c}' FS=: myFile.txt
).
You can get the Windows unix tools here:
http://unxutils.sourceforge.net/
OK - way late to the table, but... it seems many respondents missed the original spec that all email addresses occur on 1 line. This means unless you introduce a CRLF with each occurrence of the # symbol, your suggestions to use variants of FINDSTR /c will not help.
Among the Unix tools for DOS is the very powerful SED.exe. Google it. It rocks RegEx. Here's a suggestion:
find "#" datafile.txt | find "#" | sed "s/#/#\n/g" | find /n "#" | SED "s/\[\(.*\)\].*/Set \/a NumFound=\1/">CountChars.bat
Explanation: (assuming the file with the data is named "Datafile.txt")
1) The 1st FIND includes 3 lines of header info, which throws of a line-count approach, so pipe the results to a 2nd (identical) find to strip off unwanted header info.
2) Pipe the above results to SED, which will search for each "#" character and replace it with itself+ "\n" (which is a "new line" aka a CRLF) which gets each "#" on its own line in the output stream...
3) When you pipe the above output from SED into the FIND /n command, you'll be adding line numbers to the beginning of each line. Now, all you have to do is isolate the numeric portion of each line and preface it with "SET /a" to convert each line into a batch statement that (increasingly with each line) sets the variable equal to that line's number.
4) isolate each line's numeric part and preface the isolated number per the above via:
| SED "s/\[\(.*\)\].*/Set \/a NumFound=\1/"
In the above snippet, you're piping the previous commands's output to SED, which uses this syntax "s/WhatToLookFor/WhatToReplaceItWith/", to do these steps:
a) look for a "[" (which must be "escaped" by prefacing it with "\")
b) begin saving (or "tokenizing") what follows, up to the closing "]"
--> in other words it ignores the brackets but stores the number
--> the ".*" that follows the bracket wildcards whatever follows the "]"
c) the stuff between the \( and the \) is "tokenized", which means it can be referred-to later, in the "WhatToReplaceItWith" section. The first stuff that's tokenized is referred to via "\1" then second as "\2", etc.
So... we're ignoring the [ and the ] and we're saving the number that lies between the brackets and IGNORING all the wild-carded remainder of each line... thus we're replacing the line with the literal string:
Set /a NumFound= + the saved, or "tokenized" number, i.e.
...the first line will read: Set /a NumFound=1
...& the next line reads: Set /a NumFound=2 etc. etc.
Thus, if you have 1,283 email addresses, your results will have 1,283 lines.
The last one executed = the one that matters.
If you use the ">" character to redirect all of the above output to a batch file, i.e.:
> CountChars.bat
...then just call that batch file & you'll have a DOS environment variable named "NumFound" with your answer.
This is how I do it, using an AND condition with FINDSTR (to count number of errors in a log file):
SET COUNT=0
FOR /F "tokens=4*" %%a IN ('TYPE "soapui.log" ^| FINDSTR.exe /I /R^
/C:"Assertion" ^| FINDSTR.exe /I /R /C:"has status VALID"') DO (
:: counts number of lines containing both "Assertion" and "has status VALID"
SET /A COUNT+=1
)
SET /A PASSNUM=%COUNT%
NOTE: This counts "number of lines containing string match" rather than "number of total occurrences in file".
Use this:
type file.txt | find /i "#" /c

Regular expressions in findstr

I'm doing a little string validation with findstr and its /r flag to allow for regular expressions. In particular I'd like to validate integers.
The regex
^[0-9][0-9]*$
worked fine for non-negative numbers but since I now support negative numbers as well I tried
^([1-9][0-9]*|0|-[1-9][0-9]*)$
for either positive or negative integers or zero.
The regex works fine theoretically. I tested it in PowerShell and it matches what I want. However, with
findstr /r /c:"^([1-9][0-9]*|0|-[1-9][0-9]*)$"
it doesn't.
While I know that findstr doesn't have the most advanced regex support (even below Notepad++ which is probably quite an achievement), I would have expected such simple expressions to work.
Any ideas what I'm doing wrong here?
This works for me:
findstr /r "^[1-9][0-9]*$ ^-[1-9][0-9]*$ ^0$"
If you don't use the /c option, the <Strings> argument is treated as a space-separated list of search strings, which makes the space a sort of crude replacement for the | construct. (As long as your regexes don't contain spaces, that is.)
Argh, I should have read the documentation better. findstr apparently doesn't support alternations (|).
So I'm probably back to multiple invocations or replacing the whole thing with a custom parser eventually.
This is what I do for now:
set ERROR=1
rem Test for zero
echo %1|findstr /r /c:"^0$">nul 2>&1
if not errorlevel 1 set ERROR=
rem Test for positive numbers
echo %1|findstr /r /c:"^[1-9][0-9]*$">nul 2>&1
if not errorlevel 1 set ERROR=
rem Test for negative numbers
echo %1|findstr /r /c:"^-[1-9][0-9]*$">nul 2>&1
if not errorlevel 1 set ERROR=
Or if you can, download grep for windows.. Many more features than findstr provides.
A simpler regex that achieves the same thing is possible, just add an optional minus to the start of your original expression:
^-?[0-9][0-9]*$
Support for regex in findstr is quite limited. I suggest using Notepad++. The find in files option supports Perl Compatible Regular Expressions; results showing filename, line number and matching text can be easily copied to a text file.

Resources