Batch FOR loop processing drops equal and comma characters - loops

Any batch experts able to explain the following behavior?
I am trying to have a variable which contains the permissions required to be used by a net share command. The variable's name is %GRANT%
I have found that I cannot simply enter "net share ShareName=D:\Path %GRANT%"
However, it does appear that the following will work possibly:
"FOR /F "delims=" %%A in ('echo net share ShareName="D:\Path" %GRANT%') do (%%A)
Here is the issue. The FOR loop described above is NOT working. But it is not working because "it just wont' work." It is not working, because the FOR loop is dropping the comma and equal characters out of the command.
Please see the attached screenshot. You can see that %GRANT% does indeed contain a comma (1.). You can also see that the "IN" command contains an equal sign as should be present in net share. however, when I echo the resulting command in order to troubleshoot what's going on, I see that the FOR loop processing is dropping both the equal and comma characters out of the result (2.).
Can anyone explain this and is there something I can do to make it leave those characters in?

#ECHO OFF
SETLOCAL
SET "grant=/grant:Everyone^,FULL^=testing"
FOR /F "delims=" %%A in ('echo net share ShareName="D:\Path" %GRANT%') do (ECHO %%A)
SET "grant=/grant:Everyone,FULL=testing"
FOR /F "delims=" %%A in ("net share ShareName="D:\Path" %GRANT%") do (ECHO %%A)
GOTO :EOF
Here's two slightly different ways to do what you appear to want to do.
The problem is that commas, semicolons, tabs and often = are seen as separators. Usng the caret or "quoting the string" overcomes the problem.

Related

Batch loopvariable manipulation

I have a small problem with a .bat file that I have to build to manipulate a specific .csv.
I want the .bat to read the line of the file, and then check for the first three letters of that line. At the end there should be n-files where file xxx.csv contains the lines of the input.csv with xxx as the first three letters of line.
First things first, I don't even know if it is possible to do it this job in a batch-file, because the file has >85000 lines and may even get much bigger. So if it is impossible you can directly tell me that.
for /f "delims=" %%a in (input.CSV) DO (
echo %%~a:~0,3
pause
)
I want to "output" the first three letters of %%a.
It would be great if you could help me.
Phil
Substring substitution only works with environment variables (%var%), but not with metavariables (%%a) (as Mofi already commented). And because you are setting and using a variable within the same command block, you need delayed expansion:
setlocal enabledelayedexpansion
for /f "delims=" %%a in (input.CSV) DO (
set "var=%%~a"
echo !var:~0,3!
pause
)
(there are methods without delayed expansion, but they make use of call, which slows things down)

disabling eol, setting delims to linefeed or newline to enable tokenization by line numbers

Foreword
I'm using batch to do this
despite the many 'related' questions on this topic, this one is different/not a duplicate because I want to tokenize lines rather than just 'read line by line'
What I'm doing
I want an easy way to specify what lines I want to parse and I thought the best way to do that would be to use the tokens option of the FOR loop. The problem is that tokens by default are split up by the eol setting (defaults to newline) and by the delims setting (defaults to space). This is great for most use cases but I want to tokenize each line. This will enable me to do alot of what I want to do easily and cleanly.
How I've tried it
Anyways, I've figured out that you can disable the eol character by doing eol^= in the options of FOR. The problem Is that I can't find the actual character I need to specify into delims= to set the delimiter equal to the newline, linefeed, or anything else that only denotes a new line. I don't want to simply process line by line I want to tokenize each line. This is important because questions like this:
New line as a delimeter of FOR loop
and this:
What delimiter to use in FOR loop for reading lines?
Don't apply. Additionally I found this but the answers again didn't actually suit my needs.
https://www.dostips.com/forum/viewtopic.php?t=6471
The reason they don't apply is because those are asking about reading the file line by line. I'm asking about tokenizing each line. This is different because reading line by line can be accomplished by disabling delims and eol, or by setting delims to the first character it finds/setting it to empty (doing "delims="). This ISN'T what I want because I WANT to have delims enabled and I want to have IT split each line INSTEAD of eol splitting each line.
Why would you want to do that?
some backstory, I was going to use the skip command but the manpage for the forloop says that the skip option only skips up to the line specified and doesn't allow you to say; skip 3 lines, read one line and then skip some more lines, or skip by line numbers. I could just use one forloop to extract one line and just have more forloops or do something complicated with counters and nested for loops but it'd be much easier if I can simply tokenize each line.
So here is what I want
Effectively what I want is:
FOR /F "tokens=1,3 delims=<linefeed go here> eol^=" %%A IN ('command
that prints out multiple lines') DO (echo %%A)
which would echo out the first line and third line of the command output like so:
<command output line 1>
<command output line 3>
(if ya'll have a good example of a simple command that prints out at least 3 lines I'd be willing to edit this to more directly display what I mean but I think ya'll get the idea).
So my question is basically this:
A: Is it possible to accomplish tokenization of lines like this (i.e. specifying line numbers to read by token numbers)
B: If A is true then what is the actual linefeed character I need to put in delims? Everywhere I've searched, people seem to say that its not suppsoed to be done that way, but since they're asking a slightly different question, that doesn't apply here. Can I use the ASCII number for it? Is it possible to set it to linefeed with eol disabled?
I've seen some people use:
set $lf=^
delims^=^%$lf%%$lf%^
on the DOStips forum and I Don't quite understand whats going on there. Are they setting linefeed to a different character? It also looks like theyre trying to both use and disable delims at the same time which makes no sense to me.
Extra: If I'm disabling eol wrong or something else would interfere with my current approach please tell me and if you would kindly point me to a manpage or something I'd gladly inform myself so I don't take up your time.
Why is this important?
Because it makes it much easier to read files and grab only the lines you want from command outputs rather than have to play defense, tokenize lines+spaces you don't even want and only grab the ones you do. Doing it this way allows me to just directly say (I want only these lines) and I don't even need a goto or anything weird to break out of the forloop once I'm done grabbing everything I need.
A perfet example is to consider the below lines of text, say I want to grab only the e and i from this 'file'.
a b c
d e f
g h i
j k l
To do this regularly, I'd need to skip the first line, start tokenizing, grab the second token, grab the 6th token and break out using a goto. I don't want to have to count token by token and I don't want to have to use a goto to break out of the loop when I'm done. I just want to say 'grab the 2nd and third lines and treat them slightly differently'. No gotos, no counting tokens, no mess
Update I tried the dostips suggestion
BTW this is just trying to grab all lines of the portopening settings on my local machine (I'm testing a colleauge's batch script)
echo Portopenings check
set $lf=^
FOR /F "tokens=* delims^=^%$lf%%$lf%^ eol^=" %%A IN ('netsh firewall show
portopening') DO (echo %%A)
But for some reason it didn't throw any errors, nor did it output anything. I expected it to output some lines containing my portopening settings. Running the command in the forloop without the delims and eol options works fine f.e. this:
FOR /F "tokens=*" %%A IN ('netsh firewall show portopening') DO (echo %%A)
Update 2
Found this monster from
How can you echo a newline in batch files?
set NLM=^
set NL=^^^%NLM%%NLM%^%NLM%%NLM%
echo There should be a newline%NL%inserted here.
which actually works as intended (be sure to preserve the spacing for some reason messing that up causes the above to not work and instead print There should be a newline^^^^inserted here). The only problem is I can't seem to actually get it to work inside a FOR loop. I keep trying:
FOR /F "tokens=* delims=%NL% eol^=" %%A IN ('netsh firewall show
portopening') DO (echo %%A)
with variations but nothing seems to work at all. It just says eol^=" was not expected and If I remove the "" it says syntax incorrect. I know I need the quotes, I'm pretty sure the eol^= syntax is correct so I don't think its directly related to those things. I think something weird is happening with delims causing bad errors that don't reflect the actual problem.
Update 3, The rabbit hole
Note that you need the NL or NLM definitions from above to try to run these (they don't work though)
I've tried:
for /F "tokens^=1,2 delims^= eol^=^^!NLM^^!" %%i in ('netsh firewall show
portopening') do (echo %%i)
for /F ^"tokens^=1,2 delims^=!NLM! eol^=^" %%i in ('netsh firewall show
portopening') do (echo %%i)
for /F "tokens=1,2 delims^=!NLM! eol=" %%i in ('netsh firewall show
portopening') do (echo %%i)
for /F "tokens=1,2 delims^=!NLM!" %%i in ('netsh firewall show
portopening') do (echo %%i)
for /F "tokens=1,2 delims= eol=" %%i in ('netsh firewall show
portopening') do (echo %%i)
for /F "delims=!NLM! eol=" %%i in ('netsh firewall show
portopening') do (echo %%i)
for /F "delims^=!NLM! eol=" %%i in ('netsh firewall show
portopening') do (echo %%i)
for /F "delims^=!NLM! eol^=" %%i in ('netsh firewall show
portopening') do (echo %%i)
for /F ^"delims^=!NLM! eol^=^" %%i in ('netsh firewall show
portopening') do (echo %%i)
for /F ^"delims^=^!NLM^! eol^=^" %%i in ('netsh firewall show
portopening') do (echo %%i)
and a bunch of other ways, I've tried all of the above using %NLM% as well
and I've tried using the !NL! as well as %NL% for all of these. I've tried omiting options, recombining options, reordering options, escaping, not escaping, and all the other FUN combinations. Most result in syntax errors, some print the whole output with tokens=* and some print some stuff that just makes no sense (weird untokenized column based output that has splits that make no sense) but it doesn't seem to ever print only certain lines by token. Additioanlly the man page says the eol default is ; and that it is for detemrining which lines are comments rather than ending a line. All I want is to just have the delimiter be a newline and have everything else NOT DO ANYTHING WEIRD. I just want to token by each line of output or have some other easy way to grab only specific lines. the Skip option is practically useless unless I only want to grab one line (they REALLY should've expanded that functionality). I just can't wrap my head around the output: TO ME eol=<whatever> should JUST WORK. I've even tried setting it to Q and # and - just to try and NOT HAVE IT SPLIT LINES but for some reason the command line hates eol^= and says thats horrible syntax. Even stranger if i use delims and eol but not tokens I can omit "" but if I use tokens it will NEVER work without quotes. Even worse I can't find a definitive source on how the heck to actually escape everything properly to accomplish my needs. All I know is that eol^= is """"SUPPOSED"""" to 'disable' eol. I have NO IDEA how that works, if it works, or anything but after trying the above I think 90% of the answers on this topic for other questions must just be completely wrong. Even stranger, I can use !NL! and %NL% in echo statements and it works fine. Trying to use it for delims just doesn't work. Trying to use raw ^ characters or escaped ^ charactrs doesn't work either. I don't even know if the carat IS the linefeed/newline character, I just want that character to be delims so that EACH TOKEN IS A LINE. Mybe delims and tokens are unrelated but I THOUGHT they were related. I thought tokens were defined by delims because delims is BY DEFAULT a space. Feel free to educate me, I'm going to grab lunch now before I explode.
Mmmm... A couple points related to this question.
The important point first: there is no way that a for /F command first read all file lines and store they in a class of "buffer", and then proceed to tokenise the buffer based on LF character; for /F command just does not work this way.
Please, carefully read this phrase written by yourself: "Additioanlly the man page says the eol default is ; and that it is for detemrining which lines are comments rather than ending a line". eol option define the character that cause to ignore lines when it comes at beginning of the line. Period.
Now an alternative:
set "lines=1 3"
FOR /F "tokens=1* delims=:" %%A IN ('command prints lines ^| findstr /N "^"') DO (
FOR /F "tokens=1*" %%X in ("!lines!") do (
IF "%%A" EQU "%%X" (
echo %%B
set "lines=%%Y"
)
)
)
Working code based on your example:
#echo off
setlocal EnableDelayedExpansion
set "lines=2 3"
set "selected="
FOR /F "tokens=1* delims=:" %%A IN ('type test.txt ^| findstr /N "^"') DO (
FOR /F "tokens=1*" %%X in ("!lines!") do (
IF "%%A" EQU "%%X" (
set "selected=!selected! %%B"
set "lines=%%Y"
)
)
)
for /F "tokens=2,6" %%A in ("%selected%") do (
echo Token 2: "%%A"
echo Token 6: "%%B"
)
test.txt:
a b c
d e f
g h i
j k l
Output:
Token 2: "e"
Token 6: "i"

Batch script to grab lines with findstr without filepath

I've got a log file that monitors a large system including requests and acknowledgements. My objective is to be able to:
1. Loop through the script and get the lines where requests & their acknowledgements happen
2. Pull the entire lines of importance as strings and store them as variables for string modifying to output somewhere else.
Here's what I have so far:
#ECHO off
setlocal
setlocal enabledelayedexpansion
setlocal enableextensions
:: Lets get today's date, formatted the way the ABCD File is named
for /f "tokens=1-5 delims=/ " %%d in ("%date%") do set targetDate=%%f-%%d-%%e
:: Now we set the targetFile name
SET ABCDLogsFile=C:\Users\me\Documents\monitoring_file_for_jim\ABCDFIX*%targetDate%.log
::****Scrapped original approach*****
set "ackFoundCount=0"
set "reqFoundCount=0"
::Get lines with acks
for /f delims^=^ eol^= %%a in ('findstr /c:"\<ACK\>" "%ABCDLogsFile%"') do (
set /a "ackFoundCount+=1"
setlocal enabledelayedexpansion
for %%N in (!ackFoundCount!) do (
endlocal
set "ackFound%%N=%%a"
)
)
::Get lines with requests
for /f delims^=^ eol^= %%b in ('findstr /c:"ReqSingle" "%ABCDLogsFile%"') do (
set /a "reqFoundCount+=1"
setlocal enabledelayedexpansion
for %%N in (!reqFoundCount!) do (
endlocal
set "reqFound%%N=%%b"
)
)
setlocal enabledelayedexpansion
for /l %%N in (1,1,2 %reqFoundCount%) do echo REQ %%N FOUND= !reqFound%%N!
pause
for /l %%N in (1,1,2 %ackFoundCount%) do echo ACK %%N FOUND= !ackfound%%N!
endlocal
EDIT 2 dbenham
The roundabout way I was trying to accomplish this before was totally unnecessary.
Thanks to the questions and answer here:
'findstr' with multiple search results (BATCH)
I've got my script working similarly. However, I'm curious if its possible to get findstr output without the filepath at the beginning. I only need to substring out the timestamp in the log, which would always be the first 12 characters of each line (without the filepath). My output currently is prefixed with the path, and while I could get the path where the log would eventually be in production, it would be safer to try and do it another way. At the time that this script would eventually be run, there would only be 1 or 2 reqs and acks each, that is why I store all which are found. It's not necessary but I think it would be reassuring to see two if there are two. Here is what the output looks like for acks and reqs alike:
C:\Users\me\Documents\monitoring_file_for_jim\ABCDFIX 2015-04-01.log:2015-03-26 07:00:11,028 INFO etc...
I'm thinking that if I could strip the filepath off the start, then all I'd need to do to get just the timestamps of the events would be
for /l %%N in (1,1,1 %reqFoundCount%) do echo Req %%N occurred at: !reqFound%%N:~0,12! >> MorningAckChecks.txt
for /l %%N in (1,1,1 %ackFoundCount%) do echo ACK %%N occurred at: !ackfound%%N:~0,12! >> MorningAckChecks.txt
I suspect you could not get SKIP to work because you you were iterating the delimited list of line numbers with a FOR statement, which means the number is in a FOR variable. Problem is, you cannot include FOR variables or (delayed expansion) when specifying a SKIP value, or any other FOR option. The batch parser evaluates the FOR options before FOR variables are expanded, so it couldn't possibly work. Only normal expansion can be used when including a variable as part of FOR options.
But I don't understand why you think you need the line numbers at all. FINDSTR is already able to parse out the lines you want. Simply use FOR /F to iterate each matching line. For each line, define a variable containing the line content, and then use substring operations to parse out your desired values.
But I can offer an alternative that I think could make your life much easier. JREPL.BAT is a sophisticated regular expression text processor that could identify the lines and parse out and transform your desired values, all in one pass. JREPL.BAT is a hybrid JScript/batch script that runs natively on any Windows machine from XP onward.
If I knew what your input looked like, and what your desired output is, then I could probably knock up a simple solution using JREPL.BAT. Or you could read the extensive built in documentation and figure it out for yourself.
Documentation is accessed from the command line via jrepl /?. You might want to pipe the output through MORE so you get one screen of help at a time. But I never do because my command line console is configured with a large output buffer, so I can simply scroll up to see past output.
EDIT - In response to comment and updated question
Here are the relevant snippets of your code that are causing the problem.
SET ABCDLogsFile=C:\Users\me\Documents\monitoring_file_for_jim\ABCDFIX*%targetDate%.log
findstr /c:"\<ACK\>" "%ABCDLogsFile%"
findstr /c:"ReqSingle" "%ABCDLogsFile%
The issue is your ABCDLogsFile definition includes a wildcard, which causes FINDSTR to prefix each matching line with the full path to the file name where the match occurred.
I have a simple solution for you - Just change the definition of ABCDLogsFile as follows:
SET "ABCDLogsFile=C:\Users\me\Documents\monitoring_file_for_jim\ABCDFIX<%targetDate%.log"
Explanation
My solution relies on two undocumented features
1) Undocumented file mask wildcards.
< - Very similar to *
> - Very similar to ?
These symbols are normally used for redirection, so they must be either quoted or escaped if you want to use them as file mask wildcards.
We discuss the undocumented feature at DosTips - Dir undocumented wildcards. Sprinkled throughout the thread (and a link) are some example use cases.
I document my understanding of exactly how the non-standard wildcards work at http://www.dostips.com/forum/viewtopic.php?p=39420#p39420
2) FINDSTR works with the non-standard wildcards
FINDSTR will prefix each matching line with the file name (and possibly path) if any of the following conditions occur
The /M option is used
The /F option is used
Multiple input files are explicitly listed on the command line
Multiple input files are implied via a file mask with at least one * or ? wildcard on the command line
Your are getting the file path prefix because of the last trigger - the * in your file mask.
But you can use < instead to get the same result, except the non-standard wildcards do not trigger the file prefix in the output.
Problem solved :-)
I talk about this FINDSTR feature at http://www.dostips.com/forum/viewtopic.php?p=39464#p39464.
Some day I hope to update my What are the undocumented features and limitations of the Windows FINDSTR command? post with this tasty little tidbit.
This post has become a bit cluttered. It would be very helpful if you posted the lines of input that correspond to the output you are getting. If you can't do that then add this statement before your FOR. I am sure you will find that testReqSkip is blank.
echo.testReqSkip=%testReqSkip%

Get registry key value and change accordingly from multiple PCs

First thank you for this great site! I've learned lots of batch scripting from here, but finally got stuck. I was tasked to write a script that will go out and check a specific registry keyword and change the ones that are not correct, on all PCs on the network.
#echo off
SetLocal EnableDelayedExpansion
FOR /F %%a in (C:\batchFiles\computers.txt) DO (
FOR /F "tokens=3" %%b in (reg query "\\%%a\HKLM\SOFTWARE\some\any" /v "Forms Path") do set "var=%%b"
if "%var%" == "\\server\folder\forms\path"
echo %%a was correct
pause
if "%var%" NEQ "\\server\folder\forms\path"
echo %%a was not correct
pause
)
My boss tasked me with this not to long ago and its a little above my head, so i'm trying to learn on the fly. I tried with %errorlevel% and couldn't get it to do what I wanted either.
I had all of my PC names listed in C:\batchFiles\computers.txt. The REG_SZ key from "Forms Path" is a folder located on a network drive. Right now it says that the syntax is incorrect.
If you can understand what i'm trying to do, and have a better suggestion, I'm all ears! Oh and I'd like to output ALL of the results to a text file so I know which PCs were changed, which ones had it correct, and which ones the script couldn't reach.
Thank you so much for your time!
You enabled delayed environment variable expansion, but do not use it. %var% must be written as !var! to make use of delayed expansion as required here.
The syntax used on both if conditions is also not correct.
The registry query output by reg.exe on my computer running Windows XP is:
! REG.EXE VERSION 3.0
HKEY_LOCAL_MACHINE\SOFTWARE\some\any
Forms Path REG_SZ \\server\folder\forms\path
There is first a blank line, next a line with version of reg.exe, one more blank line, a line with registry key and finally on fifth line the data of interest. Therefore I used in the batch code below skip=4 to speed it up. However, the inner loop would produce the right result also without skip=4 and therefore parsing all 5 lines.
Important is the last line. The inner loop separates by spaces. As the name of the registry value contains also a space character, the first two tokens are for Forms and Path. And the third token is REG_SZ.
The rest of the line after the spaces after REG_SZ is of real interest, but could contain also a space character. So I used in batch code below not tokens=4, but instead tokens=3* and ignored %b which holds REG_SZ. Instead %c is assigned to environment variable var resulting in getting really entire string value even if the string contains 1 or more spaces.
And the environment variable var is deleted before a new query on next computer is executed in case of a computer does not contain the registry value at all. The error message written by reg.exe to stderr is redirected to device nul for this case. The value of var would be unchanged from previous computer if not deleted before running the next query.
#echo off
setlocal EnableDelayedExpansion
for /F %%a in (C:\batchFiles\computers.txt) do (
set var=
for /F "skip=4 tokens=3*" %%b in ('%SystemRoot%\System32\reg.exe query "\\%%a\HKLM\SOFTWARE\some\any" /v "Forms Path" 2^>nul') do set "var=%%c"
if "!var!" == "\\server\folder\forms\path" (
echo %%a has correct value.
) else if "!var!" == "" (
echo %%a does not have the value at all.
) else (
echo %%a has wrong value.
)
pause
)
endlocal

BATCH Iterate FOR /F variables

I have simple files that look like:
Submit.log
Submitting job(s)..
2 job(s) submitted to cluster 528.
Submitting job(s)..
2 job(s) submitted to cluster 529.
I need then to output only the last number from the second line (the cluster), in this case the desired output would be:
528
529
I tried using the script below but I dont know why it is not working. I need to do it using batch, it is a restriction of our system.
#ECHO OFF
FOR /F "tokens=6" %%g IN (Submit.log) DO (
set Job=%%g
echo %Job:.=%
)
An elegant solution would definitively be a plus, but if someone could help me out with a simple solution I would appreciate.
Here is a one liner that works. The FINDSTR search can easily be adapted to make the search very precise.
#echo off
for /f "tokens=6 delims=. " %%N in ('findstr /c:"submitted to cluster" "submit.log"') do echo %%N
The paxdiablo solution can be simplified as well so that it does not need to modify the value therefor it doesn't need delayed expansion. There is no need to capture all the tokens, just the ones you want to use. The delimiter is set to look for dot and space (the default was space and tab). As paxdiablo said, more tokens can be tested to make the search more precise, but this is not as convenient as FINDSTR.
#echo off
for /f "tokens=5,6 delims=. " %%A in (submit.log) do if %%A==cluster echo %%B
The following script will do the job:
#setlocal enableextensions enabledelayedexpansion
#echo off
for /f "tokens=1-6" %%a IN (submit.log) do (
if %%c==submitted (
set job=%%f
echo !job:.=!
)
)
endlocal
And, since I'm basically the modest type, I'll stop short of contending that it's both elegant and simple :-)
It basically only looks at those lines where the third word is submitted (you can also use additional rules if this proves problematic) and then modifies and outputs the sixth word.
Note the use of delayed expansion (using ! instead of %) That's needed to ensure the variables aren't evaluated before the loop even begins.

Resources