Issues with spaces in FOR loop variable - batch script - windows

I'm writing a batch script that will use a WMIC command to get a list of all groups on a Windows machine, get the group info by using net localgroup <groupname>, and then write the info to an output file. Here is what I have:
for /f "skip=1" %%a in ('"wmic group get name"') do net localgroup %%a >> "%OUTPUTFILEPATH%" 2> nul && echo. >> "%OUTPUTFILEPATH%" && echo ^=^=^=^=^=^=^=^=^=^=^=^=^=^=^=^=^=^=^=^=^=^=^=^=^=^=^=^=^=^=^=^=^=^=^=^=^=^=^=^=^=^= >> "%OUTPUTFILEPATH%" && echo. >> "%OUTPUTFILEPATH%"
The issue I am having seems to be with getting quotes around the %%a variable in do net localgroup %%a because it outputs the info for groups like Administrators just fine but when it gets to a group name like Remote Desktop Users, it fails to return the info for the group.
In other words, this is what seems to be happening in the FOR loop:
net localgroup Administrators
net localgroup Remote Desktop Administrators
The first operation is successful. The second is not. Obviously, there need to be quotes around Remote Desktop Administrators in order for it to be seen as a single argument but I can't quite figure out how to get this to happen for my %%a FOR loop variable.
I have tried putting quotes around "%%a" and '%%a'. This doesn't help.
Any input would be greatly appreciated.

REM
#ECHO OFF
SETLOCAL ENABLEDELAYEDEXPANSION
SET "OUTPUTFILEPATH=u:\ofp.txt"
DEL /F /Q "%outputfilepath%"
for /f "skip=1delims=" %%a in ('wmic group get name') do (
SET "group=%%a"
CALL :loptrail
IF DEFINED group (
net localgroup "!group!" >> "%OUTPUTFILEPATH%" 2> NUL
echo.>> "%OUTPUTFILEPATH%"
echo ==========================================>> "%OUTPUTFILEPATH%"
echo.>> "%OUTPUTFILEPATH%"
)
)
GOTO :EOF
:loptrail
SET "group=%group:~0,-1%"
IF "%group:~-1%"==" " GOTO loptrail
GOTO :eof
The issue is that wmic output is unicode and %%a requires delims= else it returns the first [implicit-space]-delimited token.
Unfortunately, this means that %%a contains trailing spaces, which net appears to disapprove of.
Since batch does not allow a metavariable to be substringed, you need to put it into a common environment variable and invoke enabledelayedexpansion.
The value in group appears to be terminated by an extra NULL, so the :loptrail routine first arbitrarily removes that last character, then any remaining trailing spaces.
The very last line of wmic output will generate a forlorn empty group so the net processing proceeds only if group is non-empty.

You need to use either tokens=* as suggested by Carlos Gutiérrez or even better delims= on FOR to avoid splitting up the line into tokens based on spaces and tabs.
It is of course additionally necessary to enclose the group name in double quotes when any group name contains 1 or more spaces.
But the main problem here is that wmic outputs information in Unicode with UTF-16 Little Endian encoding which command FOR cannot parse right directly. A workaround for this issue is redirecting output of wmic into a temporary text file and using command type to get contents in ASCII/ANSI/OEM, i.e. one byte per character. See the answers on How to correct variable overwriting misbehavior when parsing output for details.
But there is one more problem here as wmic outputs the group names with trailing spaces. Those trailing spaces must be additionally removed before group name is passed as parameter to console application net enclosed in double quotes.
#echo off
setlocal EnableDelayedExpansion
set "OutputFile=%USERPROFILE%\Desktop\LocalGroupInfo.txt"
del "%OutputFile%" 2>nul
%SystemRoot%\System32\wbem\wmic.exe group get name >"%Temp%\%~n0.tmp"
for /f "skip=1 delims=" %%a in ('type "%Temp%\%~n0.tmp"') do (
set "GroupName=%%a"
call :RemoveTrailingSpaces
%SystemRoot%\System32\net.exe localgroup "!GroupName!" >>"%OutputFile%" 2>nul
if not errorlevel 1 (
echo.>>"%OutputFile%"
echo ==========================================>>"%OutputFile%"
echo.>>"%OutputFile%"
)
)
del "%Temp%\%~n0.tmp"
endlocal
:RemoveTrailingSpaces
if not "%GroupName:~-1%" == " " exit /B
set "GroupName=%GroupName:~0,-1%"
goto RemoveTrailingSpaces
For understanding the used commands and how they work, open a command prompt window, execute there the following commands, and read entirely all help pages displayed for each command very carefully.
call /?
del /?
echo /?
endlocal /?
exit /?
for /?
goto /?
if /?
net localgroup /?
set /?
setlocal /?
type /?
wmic group get /?

Try:
for /f "tokens=*" %%a in ('"wmic group get name"') do ....

Related

Batch file that searches cmdline output for keyword and displays the previous line(x5)

I have a batch file that runs a command in the command line and searches the output for a keyword. It then outputs the line previous to the line with the keyword.
For example: Search for KEYWORD
PREVIOUS LINE
KEYWORD
result: PREVIOUS LINE
The code is as follows
set Output="pnputil -e"
FOR /F "eol=. tokens=*" %%a IN ( '%Output%' ) DO (
set line=%%a
set ourline=!line:KEYWORD=!
if not !line!==!ourline! (
SET Output=!prev_line!
)
SET prev_line=%%a
)
echo Installed OEM file found as: !Output!
I want to replicate this but the line I want is 5 lines previous to the keyword. The output I want is followed by 4 lines of random info with only one constant keyword 5 lines after it that I can search.
There could be used the driver date and version to identify the oem*.inf file of an installed driver. The batch file below searches for a driver with the driver date 06/12/2020 and the driver version 16.51.39.165.
#echo off
setlocal EnableExtensions DisableDelayedExpansion
set "FileName="
for /F "tokens=1,2* delims=: " %%I in ('%SystemRoot%\System32\pnputil.exe -e 2^>nul') do (
if "%%I %%J" == "Published name" (
set "FileName=%%K"
) else if "%%I %%J" == "Driver date" (
set "DriverDateVersion=%%K"
setlocal EnableDelayedExpansion
if not "!DriverDateVersion:06/12/2020 16.51.39.165=!" == "!DriverDateVersion!" echo Installed OEM file found as: !FileName!
endlocal
)
)
endlocal
But it is of course also possible to remember in the loop the last four lines and check every line for the string to find like Hardware Compatibility as done by the batch file below.
#echo off
setlocal EnableExtensions EnableDelayedExpansion
set "Line1=?"
set "Line2=?"
set "Line3=?"
set "Line4=?"
for /F delims^=^ eol^= %%I in ('%SystemRoot%\System32\pnputil.exe -e 2^>nul') do (
set "Line1=!Line2!"
set "Line2=!Line3!"
set "Line3=!Line4!"
set "Line4=!CurrentLine!"
set "CurrentLine=%%I"
if not "!CurrentLine:Hardware Compatibility=!" == "!CurrentLine!" echo(!Line1!
)
endlocal
Note: Lines with one or more ! are not corrected processed by this code because of permanently enabled delayed environment variable expansion which results in interpreting an exclamation mark in the string assigned to loop variable I as beginning/end of a delayed expanded environment variable reference.
There are lots of other possible solutions depending on the data to process.
For understanding the used commands and how they work, open a command prompt window, execute there the following commands, and read entirely all help pages displayed for each command very carefully.
echo /?
endlocal /?
for /?
if /?
pnputil -? or pnputil /?
set /?
setlocal /?
You may easily store the 5 lines previous to the searched one in a very simple way:
#echo off
setlocal EnableDelayedExpansion
for /F "eol=. delims=" %%a in ('pnputil -e') do (
set "i=5"
for /L %%n in (4,-1,1) do set "line!i!=!line%%n!" & set "i=%%n"
set "line1=%%a"
if not "!line1!" == "!line1:KEYWORD=!" set "Output=!line5!"
)
echo Result: %Output%
You may change the number of stored lines by changing just 2 lines...

Batch renaming files incrementally with special characters

I am trying to rename the episodes in a directory in an incremental way, but there are exclamation marks in some of the episodes. It will skip those files. I tried doing delayed expansion, but it didn't work.
#echo off
setlocal DisableDelayedExpansion
set /a num=0
for %%a in (*.mkv) do (
set filename=%%a
setlocal EnableDelayedExpansion
ren "!filename!" "Soul Eater Episode 0!num!.mkv"
set /a num=!num!+1
)
pause
endlocal
Try this:
#echo off
setlocal EnableDelayedExpansion
set /a num=0
for %%a in (*.mkv) do (
set filename=%%a
ren "!filename!" "Soul Eater Episode 0!num!.mkv"
set /a num=!num!+1
)
endlocal
pause
The following batch file code could be used to rename the files containing one or more ! in file name.
#echo off
setlocal EnableExtensions DisableDelayedExpansion
set "num=0"
for /F "eol=| delims=" %%I in ('dir *.mkv /A-D /B /ON 2^>nul ^| %SystemRoot%\System32\findstr.exe /B /I /L /V /C:"Soul Eater Episode"') do (
set "filename=%%I"
setlocal EnableDelayedExpansion
ren "!filename!" "Soul Eater Episode 0!num!.mkv"
endlocal
set /A num+=1
)
pause
endlocal
There is no need to use an arithmetic expression to define the environment variable num with the value 0.
It is very advisable on running renames on a list of file names in a directory using a wildcard pattern like *.mkv to get first the list of file names loaded into memory of Windows command processor and then rename one file after the other as done by this code using a for /F loop. Otherwise the result of the file renames is unpredictable as depending on file system (NTFS, FAT32, exFAT) and current names of the files matched by the wildcard pattern.
The additional FINDSTR is used to filter out all file names beginning already case-insensitive with the string Soul Eater Episode although the batch file would most likely fail to rename some files if there are already files with a file name matched by Soul Eater Episode 0*.mkv in the current directory on execution of the batch file.
Read the Microsoft documentation about Using command redirection operators for an explanation of 2>nul and |. The redirection operators > and | must be escaped with caret character ^ on FOR command line to be interpreted as literal characters when Windows command interpreter processes this command line before executing command FOR which executes the embedded command line with dir and findstr with using a separate command process started in background with %ComSpec% /c and the specified command line appended as additional arguments.
The file name is first assigned as output by DIR filtered by FINDSTR to the environment variable filename with delayed expansion disabled as otherwise the double processing of this command line on enabled delayed expansion would result in interpreting ! in file name assigned to loop variable I as beginning/end of a delayed expanded environment variable reference.
Then delayed expansion is enabled to be able to do the rename with referencing the environment variable num using delayed expansion and of course also the file name assigned to environment variable filename.
Next delayed expansion is disabled again before an arithmetic expression is used using the preferred syntax to increment the value of an environment variable by one which always works independent on disabled or enabled delayed expansion.
For understanding the used commands and how they work, open a command prompt window, execute there the following commands, and read entirely all help pages displayed for each command very carefully.
dir /?
echo /?
endlocal /?
findstr /?
for /?
pause /?
ren /?
set /?
setlocal /?
Please read this answer with details on what happens in background on every execution of SETLOCAL and ENDLOCAL.

Combining two batch files / "do"ing more than one command per "do"

Essentially what I want is to get the the user that's currently using a computer. I got the below code from this community which works well...,
::Batch 1
#echo off
for /f %%a in (output1.txt) do WMIC /NODE:%%a computersystem GET name, username
do echo %%r >> output2.txt
..., except that my computer names have hyphens in them and cannot be used.
To circumvent that, I run the below code (also got this here, thanks again!) to retrieve the IPs first:
::Batch 2
#echo off
for /f %%a in (hosts.txt) do call :process %%a
goto :eof
:process
set hostname=%1
for /f "tokens=4 delims=: " %%r in ('ping -n 1 %hostname%^|find /i "Statistics"') do echo %%r >> output2.txt
I then feed this information to the first batch file above to get the hostnames.
So essentially I place my hostnames on a txt file named Hosts.txt, run batch 2, then run batch 1.
I've tried many days to combine the both, but cant seem to figure it out.
As #aschipfl suggested. Use FOR /? to see information about using the FOR command.
for /f %%a in (output1.txt) do (
WMIC /NODE:%%a computersystem GET name, username
ECHO right here
)

Quoting a long filenamed command in a for loop in a batch file

Linked:
Best free resource for learning advanced batch-file usage?
Dealing with quotes in Windows batch scripts
This appears to be one of those maddening quoting issues. In this example program:
#echo off
set wmicpath=%windir%\System32\wbem\wmic.exe
for /f "usebackq" %%a in (`%wmicpath% COMPUTERSYSTEM GET SystemType ^| findstr /I "x64"`) do (
echo %%a
)
The program runs just fine. Unless you try to quote the wmicpath. Imagine if you will that it contains a long path name. Then you should quote it. But I cannot quite get it to work. This fails:
for /f "usebackq" %%a in (`"%wmicpath%" COMPUTERSYSTEM GET SystemType ^| findstr /I "x64"`) do (
but this works!:
for /f "usebackq" %%a in (`"%wmicpath%" COMPUTERSYSTEM GET SystemType ^| findstr /I x64`) do (
as does this:
for /f "usebackq" %%a in (`"%wmicpath%" COMPUTERSYSTEM GET SystemType`) do (
There's something really odd about matching quotes in a for command. You can quote a command as long as you don't start quoting elsewhere...
Is it possible? I tried escaping at various points but I'm not sure about the escaping rules when quotes are involved...
Edit: I think this link might be the issue (ie: it's a bug): Pipe in for loop breaks double quoted variables
#echo off
setlocal enableextensions disabledelayedexpansion
set "wmicpath=%windir%\System32\wbem\wmic.exe"
for /f "usebackq delims=" %%a in (`
^""%wmicpath%" COMPUTERSYSTEM GET SystemType ^| findstr /I "x64"^"
`) do (
echo %%a
)
If you look at the start and end of the inner command, you will see two additional ^" (a escaped double quote). Your problem is that the for command is spawning a separate instance of cmd to handle the inner command, and this separate instance is removing the initial and final double quotes.
Why escaped quotes? To avoid this additional quotes being paired with the double quotes in the command that could lead to some other parsing problems.
You can run cmd /? to obtain the help page (sorry, i have a spanish locale so i will not include the output here). You will see a section about the /C and /K usage explaining quote removal behaviour.
First of all I would change the command, WMIC allows you to use a query language LIKE operator which would in this case remove the need to pipe anything.
#Echo Off
Set "WMIC=%SystemRoot%\System32\Wbem\wmic.exe"
For /F "UseBackQ Skip=1" %%a In (
`""%WMIC%" ComputerSystem Where "SystemType Like 'x64%%'" Get SystemType"`
) Do For %%b In (%%a) Do Echo=%%b
Timeout -1
Then I may even change the format of the command such that I don't use back quotes.
#Echo Off
Set "WMIC=%SystemRoot%\System32\Wbem\wmic.exe"
For /F "Skip=1" %%a In (
'""%WMIC%" ComputerSystem Where (SystemType Like "x64%%") Get SystemType"'
) Do For %%b In (%%a) Do Echo=%%b
Timeout -1
Whilst this doesn't directly answer the question in the subject title, it does allow for your particular command to work correctly.
However neither are necessary to your particular command example, because you do not need the for loop to echo that output:
#Echo Off
Set "WMIC=%SystemRoot%\System32\Wbem\wmic.exe"
"%WMIC%" ComputerSystem Get SystemType|Find /I "x64"
Timeout -1
Change Find to FindStr if you feel the need.
>x64.txt ECHO x64
for /f "usebackq" %%a in (`"%wmicpath%" COMPUTERSYSTEM GET SystemType ^| findstr /I /g:x64.txt`) do (
might be a work-around, depending on your actual application and preferences.

How do you loop through each line in a text file using a windows batch file?

I would like to know how to loop through each line in a text file using a Windows batch file and process each line of text in succession.
I needed to process the entire line as a whole. Here is what I found to work.
for /F "tokens=*" %%A in (myfile.txt) do [process] %%A
The tokens keyword with an asterisk (*) will pull all text for the entire line. If you don't put in the asterisk it will only pull the first word on the line. I assume it has to do with spaces.
For Command on TechNet
If there are spaces in your file path, you need to use usebackq. For example.
for /F "usebackq tokens=*" %%A in ("my file.txt") do [process] %%A
From the Windows command line reference:
To parse a file, ignoring commented lines, type:
for /F "eol=; tokens=2,3* delims=," %i in (myfile.txt) do #echo %i %j %k
This command parses each line in Myfile.txt, ignoring lines that begin with a semicolon and passing the second and third token from each line to the FOR body (tokens are delimited by commas or spaces). The body of the FOR statement references %i to get the second token, %j to get the third token, and %k to get all of the remaining tokens.
If the file names that you supply contain spaces, use quotation marks around the text (for example, "File Name"). To use quotation marks, you must use usebackq. Otherwise, the quotation marks are interpreted as defining a literal string to parse.
By the way, you can find the command-line help file on most Windows systems at:
"C:\WINDOWS\Help\ntcmds.chm"
In a Batch File you MUST use %% instead of % : (Type help for)
for /F "tokens=1,2,3" %%i in (myfile.txt) do call :process %%i %%j %%k
goto thenextstep
:process
set VAR1=%1
set VAR2=%2
set VAR3=%3
COMMANDS TO PROCESS INFORMATION
goto :EOF
What this does:
The "do call :process %%i %%j %%k" at the end of the for command passes the information acquired in the for command from myfile.txt to the "process" 'subroutine'.
When you're using the for command in a batch program, you need to use double % signs for the variables.
The following lines pass those variables from the for command to the process 'sub routine' and allow you to process this information.
set VAR1=%1
set VAR2=%2
set VAR3=%3
I have some pretty advanced uses of this exact setup that I would be willing to share if further examples are needed. Add in your EOL or Delims as needed of course.
Improving the first "FOR /F.." answer:
What I had to do was to call execute every script listed in MyList.txt, so it worked for me:
for /F "tokens=*" %A in (MyList.txt) do CALL %A ARG1
--OR, if you wish to do it over the multiple line:
for /F "tokens=*" %A in (MuList.txt) do (
ECHO Processing %A....
CALL %A ARG1
)
Edit: The example given above is for executing FOR loop from command-prompt; from a batch-script, an extra % needs to be added, as shown below:
---START of MyScript.bat---
#echo off
for /F "tokens=*" %%A in ( MyList.TXT) do (
ECHO Processing %%A....
CALL %%A ARG1
)
#echo on
;---END of MyScript.bat---
#MrKraus's answer is instructive. Further, let me add that if you want to load a file located in the same directory as the batch file, prefix the file name with %~dp0. Here is an example:
cd /d %~dp0
for /F "tokens=*" %%A in (myfile.txt) do [process] %%A
NB:: If your file name or directory (e.g. myfile.txt in the above example) has a space (e.g. 'my file.txt' or 'c:\Program Files'), use:
for /F "tokens=*" %%A in ('type "my file.txt"') do [process] %%A
, with the type keyword calling the type program, which displays the contents of a text file. If you don't want to suffer the overhead of calling the type command you should change the directory to the text file's directory. Note that type is still required for file names with spaces.
I hope this helps someone!
The accepted answer is good, but has two limitations.
It drops empty lines and lines beginning with ;
To read lines of any content, you need the delayed expansion toggling technic.
#echo off
SETLOCAL DisableDelayedExpansion
FOR /F "usebackq delims=" %%a in (`"findstr /n ^^ text.txt"`) do (
set "var=%%a"
SETLOCAL EnableDelayedExpansion
set "var=!var:*:=!"
echo(!var!
ENDLOCAL
)
Findstr is used to prefix each line with the line number and a colon, so empty lines aren't empty anymore.
DelayedExpansion needs to be disabled, when accessing the %%a parameter, else exclamation marks ! and carets ^ will be lost, as they have special meanings in that mode.
But to remove the line number from the line, the delayed expansion needs to be enabled.
set "var=!var:*:=!" removes all up to the first colon (using delims=: would remove also all colons at the beginning of a line, not only the one from findstr).
The endlocal disables the delayed expansion again for the next line.
The only limitation is now the line length limit of ~8191, but there seems no way to overcome this.
Or, you may exclude the options in quotes:
FOR /F %%i IN (myfile.txt) DO ECHO %%i
Here's a bat file I wrote to execute all SQL scripts in a folder:
REM ******************************************************************
REM Runs all *.sql scripts sorted by filename in the current folder.
REM To use integrated auth change -U <user> -P <password> to -E
REM ******************************************************************
dir /B /O:n *.sql > RunSqlScripts.tmp
for /F %%A in (RunSqlScripts.tmp) do osql -S (local) -d DEFAULT_DATABASE_NAME -U USERNAME_GOES_HERE -P PASSWORD_GOES_HERE -i %%A
del RunSqlScripts.tmp
If you have an NT-family Windows (one with cmd.exe as the shell), try the FOR /F command.
The accepted anwser using cmd.exe and
for /F "tokens=*" %F in (file.txt) do whatever "%F" ...
works only for "normal" files. It fails miserably with huge files.
For big files, you may need to use Powershell and something like this:
[IO.File]::ReadLines("file.txt") | ForEach-Object { whatever "$_" }
or if you have enough memory:
foreach($line in [System.IO.File]::ReadLines("file.txt")) { whatever "$line" }
This worked for me with a 250 MB file containing over 2 million lines, where the for /F ... command got stuck after a few thousand lines.
For the differences between foreach and ForEach-Object, see Getting to Know ForEach and ForEach-Object.
(credits: Read file line by line in PowerShell )
Modded examples here to list our Rails apps on Heroku - thanks!
cmd /C "heroku list > heroku_apps.txt"
find /v "=" heroku_apps.txt | find /v ".TXT" | findstr /r /v /c:"^$" > heroku_apps_list.txt
for /F "tokens=1" %%i in (heroku_apps_list.txt) do heroku run bundle show rails --app %%i
Full code here.
To print all lines in text file from command line (with delayedExpansion):
set input="path/to/file.txt"
for /f "tokens=* delims=[" %i in ('type "%input%" ^| find /v /n ""') do (
set a=%i
set a=!a:*]=]!
echo:!a:~1!)
Works with leading whitespace, blank lines, whitespace lines.
Tested on Win 10 CMD

Resources