How to output multiple lines from a FINDSTR to a variable - batch-file

It's no surprise that the official documentation doesn't really help in the matter of understanding how does the command process the result of a command instead of a filelist neither why is it even called 'FOR'. Yes I already know Stack Overflow is full of similar question but apparently, since batch scripts are influenced by so many
"breaking" factors that, even as a non-batch experienced programmer, it is difficult not to get lost in the thousands exceptions and do-nots which may affect the result.
My objective, aside from learning from the best answer possible, is to formulate a generic enough question to represent the matter which is probably the most common task including the FINDSTR command:
THE QUESTION:
How do I get the output of a FINDSTR in a way that allows me to compute every result line one at the time
possibly INSIDE the loop?
The most 'generic' (batch bs-proof if you know what I mean) example I can make is the following:
Let's say this is secret_file.txt
some not interesting line
A very interesting line = "secret1";
some not interesting line
A very interesting line = "secret2";
some not interesting line
A very interesting line = "secret3";
some not interesting line
Now with the findstr command I can output every "secret" line like this:
findstr /R /C:"secret.\"" secret_file.txt
A very interesting line = "secret1";
A very interesting line = "secret2";
A very interesting line = "secret3";
But this result is just useless without further parsing right? I could have used ctrl-F over any text reader/editor
for this matter, anyway, let's say I now want to output every line ONE AT THE TIME so that I can compute it, for
example, saving every secret to a variable then using that variable somehow
(it doesn't really matter how, we can just echo it to keep things simple).
Now, everybody agrees on the fact that for this kind of task, a FOR loop is needed.
Quoting https://ss64.com/nt/for.html on the syntax, my script.bat should looks like this:
#echo off
FOR /F %%A IN ('findstr /R /C:"secret.\"" secret_file.txt') DO ECHO Batch script language is completely fine, good job Microsoft!
This just doesn't even give any output, can someone explain me why? My hypothesis was that the output from the findstr command
is in a non-compatible format with the FOR command, not like I could check or something since the source is closed and the
documentation doesn't even bother defining the word String.
I'm ready to provide any details and even edit the question to be more visible to the wanna be Microsoft-forsaken batch scripters out there.

Using "tokens=*" to strip off leading spaces this batch uses a counter to create a (pseudo) array secret[]
:: Q:\Test\2018\12\04\SO_53614102.cmd
#Echo off
set Cnt=0
FOR /F "tokens=*" %%A IN (
'findstr /R /C:"secret.\"" secret_file.txt'
) DO (
set /a Cnt+=1
call Set Secret[%%Cnt%%]=%%A
)
Set Secret[
Sample output:
> SO_53614102.cmd
Secret[1]=A very intersting line = "secret1";
Secret[2]=A very intersting line = "secret2";
Secret[3]=A very intersting line = "secret3";
As variables in a (code block) are expanded at parse time,
delayed expansion is requiered (here through a call and doubled %%)

Related

Why is my for loop repeating the last operation? [duplicate]

This question already has answers here:
Where does GOTO :EOF return to?
(4 answers)
Closed 8 months ago.
I have a batch file which is performing a series of functions for each item in a file. It's running correctly, however, for some reason, it's performing the operation for the last line in the file twice. Can anyone help me determine why? This is the first for loop I've made on my own, so I'm sure I've made some mistakes.
for /F "tokens=*" %%A in (nations.txt) do (
set "nationname=%%A"
call :ageofdiscovery
)
Point of clarification, what I am trying to do is call each and every line of "nations.txt" one at a time, storing them as a variable, then performing an elaborate series of operations using that variable, before moving on to the next line, and running through the whole of "nations.txt". The idea is to allow the script to work for an arbitrary number of loops, so as to make the script more flexible (It's a text generator, creating histories for fantasy kingdoms).
If there is no problem with the for loop, could someone explain why it's repeating the last output? I have an exit command after the loop, so it shouldn't be executing the script again, and it also has the same random generated output for the repeated last line.
EDIT: As requested, the current contents of nations.txt is:
Nation1
Nation2
Nation3
Nation4
As for the batch script itself, it's 2,134 lines long (and runs perfectly fine with a hard-coded version of the nation selection system. I'm retrofitting code here). I'm unsure of what, or where, any problems could be occurring. I also know that people here do not want me to share the entire script. I will do as requested in relation to the script itself.
The best guess for your observed phenomenon is that your function is defined directly after your loop (you don't show that important detail).
for /F "tokens=*" %%A in (nations.txt) do (
set "nationname=%%A"
call :ageofdiscovery
)
:ageofdiscovery
echo %nationname%
In that case, the function code will be executed after the loop is finished, because batch-files don't have a concept of functions, they only know labels.
It can be easily fixed by an exit /b or goto :eof
for /F "tokens=*" %%A in (nations.txt) do (
set "nationname=%%A"
call :ageofdiscovery
)
exit /b
:ageofdiscovery
echo %nationname%

What is wrong with this batch processing of the contents of a text file

This is how I have always remembered processing the CONTENTS of a file line-by-line in a batch file (backed up by web search):
for /F "delims=" %%N in ("del files.txt") do del "%%N"
It now deletes "del files.txt" and #echo %%N gives "del files.txt"
Does it have something to do with the name needing to be quoted because of the space? WTF am I missing?
("string") is used to parse strings. ("filename") can be used with usebackq. It seems you confused them in your search. So, you can write:
for /f "usebackq delims=" %%N IN ("del files.txt") do whatever you want.
Please don't accuse me of not reading help.
for /? (first thing I did) yields:
FOR /F ["options"] %variable IN (file-set) DO command [command-parameters]
FOR /F ["options"] %variable IN ('string') DO command [command-parameters] MAKE NOTE THAT 'string' IS IN SINGLE QUOTES!!!
FOR /F ["options"] %variable IN (command) DO command [command-parameters]
because I just happened to lose the "if usebackq..." line because it just happened to get lost coinciding withe the "press any key..." line in my command prompt window because it just happened to be sized unfortunately (and still doesn't show file-set in standard quotes).
I was asking why quotes wouldn't be used in (file-set) when they are required for spaces and used in (file-set) without /F (i.e. for \%f in ("c\:path w spc\*.txt") do...)
I have used that exact syntax in other working batch files. though I went and checked an in each case, %1 or an environment var were in () which apparently settled how quotes were addressed.
#1 google search result: ss64.com/nt/for_f.html which in my frustration I misread the usebackq in reverse and tried ` because it went on to say
"The backquote character ` is just below the ESC key on most
keyboards"
, indicating to me that I was supposed to use that (once or twice) as quotes as seems rational to me if I'm giving direction to use that character.
I am providing this clarification in case it helps others experiencing my frustration.
Those who have "graded me down" for what I believe is legitimate confusion by doing so also prevent me from asking a different question in a totally unrelated area and I would appreciate if they would re-evaluate. I have certainly learned my lesson about unfortunate series' of events.
I understand the reasons that underlie the policy but am very frustrated that 1 time in the years I have been a member being criticized, just for poor luck IMHO, for a question should penalize me apparently 4 or 5 days.
(It's also confusing since since the message says
It's been 2 days since you asked your last question. We ask that you wait 2 days before asking again. Use this time to revisit your previous questions, editing to address any issues that folks have pointed out in comments.
Does that mean 2 more days or the two I have waited?)
So I guess if people want to pound on me for this protest, how many more days can it cost me?
And a further Thanks! to double-beep for the useful answer even if it wasn't a complete explanation.
Well, back to the penalty box...

Batch file how to delete end text in a variable string in a for loop

I am writing some batch code to simplify a process I have of downloading some files, renaming them, and then copying them to replace the old ones. I'm running into an issue where I have a FOR loop read in a list of files from a directory, then try to modify the filenames.
The filenames all have FLY in the name, and I want to remove all text after FLY. I can't use tokens because the filenames are inconsistent in length, have multiple spaces, and wouldn't have a set number of tokens. I can't use substring because there is not a set number of characters after FLY.
I've tried using the examples at SS64 and also read numerous threads on here but nothing really seems to match my situation.
Here's the code snippet, appreciate if someone can tell me where I'm going wrong:
SETLOCAL ENABLEDELAYEDEXPANSION
FOR /F "TOKENS=*" %%A IN ('DIR /B ^"%~DP0VFR^"') DO (
SET FILENAME=%%A
SET REMOVETEXT=!FILENAME:*FLY=!
SET NEWFILENAME=!FILENAME:!REMOVETEXT!=!
ECHO !FILENAME! will be renamed !NEWFILENAME!
)
When I insert echos to see what's going on everything works as expected up until the last SET, where somehow the ending result is !NEWFILENAME! is blank.
Hmm. My results were different from yours.
The " in your dir do not need to be escaped.
The problem with your set statement is that it's interpreted as
SET NEWFILENAME=!FILENAME:! + REMOVETEXT + !=!
and since FILENAME: and = are not existing variables, each will be replaced by nothing yielding "REMOVETEXT", not blank as you claim.
The solution is to use a two-stage evaluation of newname
call SET NEWFILENAME=%%FILENAME:!REMOVETEXT!=%%
which is resolved as
SET NEWFILENAME=%FILENAME:current_value_of_REMOVETEXT=%
in a sub-shell.
After cold brew it occurred to me that I might be going about this all wrong and making it more complicated than it needs to be... I decided to try directly renaming the files with wildcards and that actually worked. Didn't even need the FOR loop.
REN "%~DP0VFR\*FLY*" *FLY
No idea why the first (and overly convoluted) solution I tried didn't work, but this does with a lot less code!

Using FINDSTR to search for a pattern and assign it to a variable

I have the following entry in the file Build.aip. I need to write a batch file that searches for "PackageFileName and prints the value assigned for that in the file. In this case, I need to print MyPackageName on the console:
<ROW BuildKey="DefaultBuild" BuildName="DefaultBuild" BuildOrder="1" BuildType="0" PackageFolder="C:\Build\Build.aip" PackageFileName="MyPackageName" Languages="en" InstallationType="4">
May you please give me some examples how I can do that? I seen in some forums that this can be done using FINDSTR.
Thanks in advance.
findstr "PackageFileName" Build.aip
If you want to make it case insensitive, add the /i argument.
For more details, type findstr /?
Updated for an example to use the for statement
FOR /F "token=2" %i in (`FINDSTR "PackageFileName" Build.aip`) do SET var=%i
A couple things to keep in mind here:
This version is based on Windows 8.1; it may work differently in different versions of Windows.
The token=2 is an example assuming that the word you are looking for is the second word in the line
If PackageFileName appears more than once in Build.aip, this code will break.
The findstring command is surrounded by back-ticks, not single quotes.
I haven't tested it; the SET may not actually survive the for loop. So test!
If you use it in a batch file, you must double all the % signs.

How to get a the directory path from a variable in a cmd batch file

Within my batch file I have a variable that contains a file path:
SET VAR1=C:\Folder1\Folder2\File.txt
I would like to extract on the directory structure and retreive:
C:\Folder1\Folder2\
I have read threads like this where I need to use %~dp0 where 0 I believe is passed as a parameter. I have tried %~dpVAR1 but that doesn't work. How can I get the output I'm looking for, but with a variable containing the file path?
Also, to make matters difficult, I have to perform all of this within an IF condition which means that once the variable is declared, I will need to refer to it with ! instead of % (I have declared setlocal enableextensions enabledelayedexpansion at the beginning of my script to allow for this).
Any help is much appreciated!
Thanks!
Andrew
You are attempting to use parameter expansion syntax on an environment variable - that cannot work. But it is relatively easy to do what you want.
Using a CALL (relatively slow):
(...
call :getPath "!var!" var
...
)
exit /b
:getPath
set "%2=%~dp1"
exit /b
Using FOR, assuming the variable does not contain any wildcards (fast)
(...
for %%F in ("!var!") do set "var=%%~dpF"
...
)
Using FOR, if the variable may contain wildcards (also fast)
(...
for /f "delims=" %%F in ("!var!") do set "var=%%~dpF"
...
)
Note 1: If the variable does not contain the full path, then all the solutions will attempt to resolve the name into an absolute path and will return the full absolute path. For example, if var contains foobar\test.txt, then the solutions will include the full path to the current directory, even if the file is not found. Something like c:\pathToCurrentDirectory\foobar\.
Note 2: All solutions above will remove all quotes from the path.
Note 3: A path could include the ! character, which will cause problems when expanding %~dp1 or %%~dpF because you have delayed expansion enabled. The delayed expansion will corrupt both ^ and ! if the value contains !. There is a solution that involves protecting both ! and ^. Here is a demonstration applied to the last solution above. The protection requires normal expansion, and since you are within a code block, it requires at least one CALL. It could be done without a subroutine, but it is easier with a subroutine. The subroutine assumes the variable is named var.
(...
call :getPath
...
)
exit /b
:getPath
set "var=!var:"=!"
set "var=!var:^=^^^^!"
set "var=%var:!=^^^!%" !
for /f "delims=" %%F in ("!var!") do set "var=%%~dpF" !
exit /b
I do believe (once again) many questions are on the same topic (string constraints, or splitting strings).
Instead of giving you the whole code, I'm going to give you a template and explain why %~dpVAR! didn't work.
Firstly, why %~dpVAR! did't work.
Before I get into modifiers, let's discuss parameters. You may know that batch files can parse parameters to each other. These parameters can be called by using a single percent sign (%) in front of the numbers 0-9. As far as I'm aware (someone might have made a way for more to be parsed), only 9 parameters can be parsed. You may think that is wrong (there's 10 parameters right?). Parameters 1-9 are parsed to the batch file (or function within one), %0 is the file path of the batch file (or function name). If you look, %~dp0 shares some (not really) resemblance to %0. This will be discussed below.
Secondly, the term %~dp0 has modifiers in it. Modifiers are things that modify variables (only in the case of parameters and those declared in for loops, you know the ones with double percent signs like %%i) and parameters. The modifier d expands the parameter to a drive letter only while p expands the parameter to a path only. You may think that these would contradict themselves, but parameters can be combined to create extremely wacky formats.
So, as you can see, you attempt at replacing 0 with your variable name failed because it's not specified for those sort of things.
Now, on to the template.
You can constrain variables (and put them into other variables) like this:
set variable=!variable:~offset,amount!
Don't worry if that seems confusing, I'm about to explain the components.
Firstly, notice that there is no /a switch. This is because this is not a mathematical function (don't really know why I added this). So, before I explain it, here's an example of what it would do to a variable name numbers that has the value of 0123456789.
set numbers=!numbers:~5,1!
By using that line of code, numbers would now equal 5. This is because it is recreating the variable with a smaller version of the original value (gee this is hard to explain). As you can see, there is a 5 where offset was on the template above. This is because it is skipping the first 5 characters and setting the variable as the next amount, or 1 character (I really hope you're getting this).
So basically, it sets a variable as a shorter value of a different (or the same) variable determined by the offset and the amount of characters to contain in it.
I really hope this helps because I probably wouldn't understand a word of this.
Can someone redirect this poor guy to a link explaining this better (I tried, ok!)?
Complete example of extracting paths from variable:
#echo off
set /p Fullpath="Specify full path: "
call :getPath %Fullpath% filename folder
echo %filename%
echo %folder%
pause
exit /b
:getPath
set "%2=%~nx1"
set "%3=%~dp1"
exit /b
Would this work:
SET VAR1=C:\Folder1\Folder2\File.txt
echo %var1%
Where Echo is the name of your exe.
%CD% may work as well: Echo %CD%

Resources