This question already has an answer here:
Variables are not behaving as expected
(1 answer)
Closed 3 years ago.
I have a list of host names separated by new lines in a text file. I want to run netstat on each of those lines and write the output of all these commands to a text file. I know the netstat command will work on each line so thats not an issue.
Here is what I have so far in my .bat:
FOR /F "tokens=*" %%A in (fqdn.txt) do (
FOR /F "tokens=* USEBACKQ" %%F IN (`netstat %%A`) DO (
SET var=%%F
)
echo %var% >> test.txt
echo delim >> test.txt
)
All that happens is the netstat help is posted to the command line over and over and the text file fills up with:
ECHO is on.
delim
ECHO is on.
delim
ECHO is on.
delim
Thanks in advance for the help :)
You need delayedexpansion because you are setting and using a variable inside of a code block:
#echo off
setlocal enabledelayedexpansion
for /f "tokens=*" %%A in (fqdn.txt) do (
for /f "tokens=* USEBACKQ" %%F IN (`netstat %%A`) DO (
SET var=%%F
)
echo !var! >> test.txt
echo delim >> test.txt
)
To get more detail on delayedexpansion run from cmd set /? and setlocal /?
That being said, you also do not need delayedexpansion:
#echo off
for /f "tokens=*" %%A in (fqdn.txt) do (
for /f "tokens=* USEBACKQ" %%F IN (`netstat %%A`) DO (
echo %%F >> test.txt
)
echo delim >> test.txt
)
As I did not see the actual input file, it is also possible to eliminate the second for loop should you want the entire output from the netstat command.
#echo off
(for /f "tokens=*" %%i in (fqdn.txt) do (
netstat %%i
echo delim
)
)>>test.txt
have my script splitted into two batch files as i dont know how to combine it:
short so you get an idea:
1.bat : calls 2.bat with args, sorts its output and makes lines unique
FOR /F ... (2.bat %1 ^| sort) DO (...)
the do part compares lines to each other and excludes identical lines using a linebuffer
2.bat : prints strings as a result of an FOR loop
FOR ... DO ECHO
in bash this might look like this: (piping is easy)
(command) | while read line ; do echo $line ; done | sort | uniq
i really dont know how to process the final output of a FOR loop in windows batch to use its result with another loop (without using temp files for result buffering)
This is the whole code with jeb's solution applied:
#echo off
setlocal EnableDelayedExpansion
REM *** This "goto" to a label, if there is one embedded in %~0 -
FOR /F "delims=: tokens=3" %%L in ("%~0") do goto :%%L
REM *** The second part calls this batch file, but embedd a label to be called
FOR /F "usebackq" %%A IN (`echo dummy ^| call %~d0\:main:\..\%~pnx0 %1 ^| SORT`) DO (
IF NOT "%%A"=="!buff!" SET str=!str!%%A
SET buff=%%A
)
ECHO %str:.dll=;%
endlocal
GOTO :eof
REM *** This function will be called in the pipe, but runs in batch context
:main
CD %1
:sub
FOR %%A IN (*.dll *.exe) DO dumpbin /DEPENDENTS %%A | FINDSTR /I "DLL" | FINDSTR /V "Dump :"
FOR /D %%B IN (*) DO (
CD %%B
CALL :sub
CD..
)
GOTO :eof
it returns a single line with runtime dependencies of all files inside a folder [arg1] and all its subfolders : the .dll part of the output string is stripped to suit my needs
ADVAPI32;COMCTL32;COMDLG32;GDI32;KERNEL32;ole32;SHELL32;USER32;WININET;
here is the counterpart on osx using pev tools:
#!/bin/sh
find "${1}" -type f \( -iname "*.dll" -o -iname "*.exe" \) | while read pe ; do
readpe -i "${pe}" | grep -i '.dll' | awk '{print $2}'
done | sort | uniq | sed 's/.[dD][lL][lL]//g;' | tr '\n' ';' ## have the BSD sed release and can't use I with sed
The first step is simple
command | (
for /f "delims=" %%L in ('more') DO #(
echo # %%L
)
) | sort
But it's get nasty when you really want to make more than a simple echo.
The next code will fail at the IF 1 NEQ 2
echo dummy | (
for /f "delims=" %%L in ('more') DO #(
echo # %%L
if 1 NEQ 2 echo Not Equal
)
)
This has different (complex) reasons, you can read at Why does delayed expansion fail when inside a piped block of code? and What happens with IF command when I pipe to it?
To be able to use the loop without problems you can use a call-function-wrapper.
#echo off
REM *** This "goto" to a label, if there is one embedded in %~0 -
FOR /F "delims=: tokens=3" %%L in ("%~0") do goto :%%L
REM *** The second part calls this batch file, but embedd a label to be called
echo dummy | call %~d0\:myFunc:\..\%~pnx0 | sort
exit /b
REM *** This function will be called in the pipe, but runs in batch context
:myFunc
for /f "delims=" %%L in ('more') DO #(
echo # %%L
if 1 NEQ 2 echo Not Equal
)
exit /b
How the self-calling works:
call %~d0\:myFunc:\..\%~pnx0
This expands to the full path of the batch, assuming the batch is C:\temp\myFile.bat then the result will be:
call C:\:myFunc:\..\temp\myFile.bat
This is strange but still a valid path, because :myFunc: will not be evaluated as the \..\ part will remove the previous part.
So the batch file calls itself.
The trick is, that :myFunc: is still part of the %0 variable.
FOR /F "delims=: tokens=3" %%L in ("%~0") do goto :%%L
This part splits the %0 by : into
"C", "\", "myFunc" and "\..\temp\myFile.bat"
And the use the third token (myFunc) for the goto %%L.
The advantage of this technic is, it doesn't break existing batch files and their parameter handling.
You could put the function also into %1 but then it's hard to guess if the batch shall use %1 as a goto destination or use it as a normal parameter.
Is it possible to output the result of a command to a variable when using pipes?
Example batch file script:
#echo off
cls
echo Script Started
setlocal enableextensions
::prove that functionality works without pipes
call:OutputCommandToVariable "set computername" demo
echo.%demo%
::prove that the command itself works
call:IsServiceStartAutoDebug "MyComputerName" "wuauserv"
::now try it for real
call:IsServiceStartAuto "MyComputerName" "wuauserv"
::done
goto:end
:IsServiceStartAuto
call:OutputCommandToVariable "sc \\%~1 qc "%~2" | find "START_TYPE" | find "2"" IsServiceStartAuto
echo.%IsServiceStartAuto%
#goto:eof
:OutputCommandToVariable
set "%2="
for /f "delims=" %%a in ('%~1') do #set "%2=%%a"
::for /f "usebackq tokens=*" %%a in (`%~1`) do #set "%2=%%a"
#goto:eof
:IsServiceStartAutoDebug
sc \\%~1 qc "%~2" | find "START_TYPE" | find "2"
#goto:eof
:end
Echo Completed
::pause
Script Output:
Script Started
COMPUTERNAME=MyComputerName
START_TYPE : 2 AUTO_START
| was unexpected at this time.
Desired Output:
Script Started
COMPUTERNAME=MyComputerName
START_TYPE : 2 AUTO_START
START_TYPE : 2 AUTO_START
Completed
:OutputCommandToVariable
SET val=%1
SET val=%val:|=^|%
set "%2="
for /f "delims=" %%a in (%val%) do set "%2=%%a"
::for /f "usebackq tokens=*" %%a in (`%~1`) do #set "%2=%%a"
goto:eof
I'd try these changes, but I can guarantee nothing since whatever the sc is doing does nothing apparent on my machine.
The issue is that whereas the string is correctly applied, you need to escape the pipe character within the single-quotes contained between the parentheses in the Court Of King Caractacus.
I did try adding the caret to escape the pipe in the quoted argument to OutputCommandToVariable - but it intriguingly gained interest and appeared as a double-caret - possibly batch trying to escape the caret rather than the pipe...
The code below will write the computer name and ip address to file, but I would like it to also write the name of the computers it cannot ping with a fail next to it. I have no idea how I would modify the batch file to do this.
#echo off
Echo Pinging list...
set ComputerList=list.txt
Echo Computername,IP Address>Final.csv
setlocal enabledelayedexpansion
for /f "usebackq tokens=*" %%A in ("%ComputerList%") do (
for /f "tokens=3" %%B in ('ping -n 1 -l 1 %%A ^|findstr Reply') do (
set IPadd=%%B
echo %%A,!IPadd:~0, -1!>>Results.csv
))
pause
You could use errorlevel set by findstr to substitute return string(s) if 'Reply' is not found:
('ping -n 1 -l 1 %%A ^|findstr Reply ^|^| echo Not found Failed:')
where || (escaped here because of for context with ^) means execute only if previous command failed.
As a side note, you should be aware that ping messages are system language dependent (they are translated to language of OS) so 'Reply' as success indicator works only for English versions.
This may not be directly what you are looking for, but I had a similar task: run ping and report success or failure. I'll leave extracting the IP address to you - seeing as you have already done it.
The problem with ping is that it returns success upon name resolution, whether packets get lost or host is unreachable (will report 0% Loss) is irrelevant.
FOR %%a IN (
google.com
a.b.c.d
) DO #FOR /F "delims=" %%p IN (
'PING -w 100 -n 1 %%a ^| findstr ^"Reply Request fail name^"'
) DO #(
ECHO "%%p" | FINDSTR TTL >2 && echo %%a, success, %%p || echo %%a, failed, %%p
) >> Results.csv
Logic: Ping once, filter only lines with the one of the words listed. If TTL exists in resulting line (output to STDERR or NUL to avoid output pollution) echo success, else echo failed.
I'm on English Windows, words will have to be adjusted for other languages.
EDIT:
FOR %%a IN (
google.com
a.b.c.d
) DO #FOR /F "delims=" %%p IN ('PING -n 1 %%a ^| findstr TTL ^|^| echo Failed') DO #(
ECHO "%%p" | FINDSTR TTL >2 && (for /f "tokens=3" %%b IN ("%%p") do #echo %%a, %%b) || echo %%a, failed, %%p
)
Less dependant on language, works only for IPv4, added IP extraction.
Filter ping output for TTL, set output to "Failed" if TTL not found.
If output string contains TTL, extract IP and echo host and IP, else echo host name and output string.
I want to count the no of lines in a text file and then the value has to be stored into a environment variable. The command to count the no of lines is
findstr /R /N "^" file.txt | find /C ":"
I refered the question How to store the result of a command expression in a variable using bat scripts?
Then I tried,
set cmd="findstr /R /N "^" file.txt | find /C ":" "
I am getting the error message,
FIND: Parameter format not correct
How could i get rid of this error.
There is a much simpler way than all of these other methods.
find /v /c "" filename.ext
Holdover from the legacy MS-DOS days, apparently. More info here: https://devblogs.microsoft.com/oldnewthing/20110825-00/?p=9803
Example use:
adb shell pm list packages | find /v /c ""
If your android device is connected to your PC and you have the android SDK on your path, this prints out the number of apps installed on your device.
You could use the FOR /F loop, to assign the output to a variable.
I use the cmd-variable, so it's not neccessary to escape the pipe or other characters in the cmd-string, as the delayed expansion passes the string "unchanged" to the FOR-Loop.
#echo off
cls
setlocal EnableDelayedExpansion
set "cmd=findstr /R /N "^^" file.txt | find /C ":""
for /f %%a in ('!cmd!') do set number=%%a
echo %number%
Inspired by the previous posts,
a shorter way of doing so:
CMD.exe
C:\>FINDSTR /R /N "^.*$" file.txt | FIND /C ":"
The number of lines
Try it. It works in my console.
EDITED:
(the "$" sign removed)
FINDSTR /R /N "^.*" file.txt | FIND /C ":"
$ reduces the number by 1 because it is accepting the first row as Field name and then counting the number of rows.
Try this:
#Echo off
Set _File=file.txt
Set /a _Lines=0
For /f %%j in ('Find "" /v /c ^< %_File%') Do Set /a _Lines=%%j
Echo %_File% has %_Lines% lines.
It eliminates the extra FindStr and doesn't need expansion.
- edited to use ChrisJJ's redirect suggestion. Removal of the TYPE command makes it three times faster.
#Tony: You can even get rid of the type %file% command.
for /f "tokens=2 delims=:" %%a in ('find /c /v "" %_file%') do set /a _Lines=%%a
For long files this should be even quicker.
I usually use something more like this
for /f %%a in (%_file%) do (set /a Lines+=1)
for /f "usebackq" %A in (`TYPE c:\temp\file.txt ^| find /v /c "" `) do set numlines=%A
in a batch file, use %%A instead of %A
The perfect solution is:
FOR /F %%i IN ('TYPE "Text file.txt" ^| FIND /C /V ""') DO SET Lines=%%i
I found this solution to work best for creating a log file that maintains itself:
setlocal enabledelayedexpansion
SET /A maxlines= 10
set "cmd=findstr /R /N "^^" "filename.txt" | find /C ":""
for /f %%a in ('!cmd!') do set linecount=%%a
GOTO NEXT
:NEXT
FOR /F %%A IN ("filename.txt") DO (
IF %linecount% GEQ %maxlines% GOTO ExitLoop
echo %clientname% %Date% %Time% >> "filename.txt")
EXIT
:ExitLoop
echo %clientname% %Date% %Time% > "filename.txt"
EXIT
Environmental variables included are %clientname% the computername of the remote client %Date% is the current date and %Time% the current time. :NEXT is called after getting the number of lines in the file. If the file line count is greater than the %maxlines% variable it goes to the :EXITLOOP where it overwrites the file, creating a new one with the first line of information. if it is less than the %maxlines% variable it simply adds the line to the current file.
You don't need to use find.
#echo off
set /a counter=0
for /f %%a in (filename) do set /a counter+=1
echo Number of lines: %counter%
This iterates all lines in the file and increases the counter variable by 1 for each line.
The :countLines subroutine below accepts two parameters: a variable name; and a filename. The number of lines in the file are counted, the result is stored in the variable, and the result is passed back to the main program.
The code has the following features:
Reads files with Windows or Unix line endings.
Handles Unicode as well as ANSI/ASCII text files.
Copes with extremely long lines.
Isn’t fazed by the null character.
Raises an error on reading an empty file.
Counts beyond the Batch max int limit of (31^2)-1.
#echo off & setLocal enableExtensions disableDelayedExpansion
call :countLines noOfLines "%~1" || (
>&2 echo(file "%~nx1" is empty & goto end
) %= cond exec =%
echo(file "%~nx1" has %noOfLines% line(s)
:end - exit program with appropriate errorLevel
endLocal & goto :EOF
:countLines result= "%file%"
:: counts the number of lines in a file
setLocal disableDelayedExpansion
(set "lc=0" & call)
for /f "delims=:" %%N in ('
cmd /d /a /c type "%~2" ^^^& ^<nul set /p "=#" ^| (^
2^>nul findStr /n "^" ^&^& echo(^) ^| ^
findStr /blv 1: ^| 2^>nul findStr /lnxc:" "
') do (set "lc=%%N" & call;) %= for /f =%
endlocal & set "%1=%lc%"
exit /b %errorLevel% %= countLines =%
I know it looks hideous, but it covers most edge-cases and is surprisingly fast.
Just:
c:\>(for /r %f in (*.java) do #type %f ) | find /c /v ""
Font: https://superuser.com/questions/959036/what-is-the-windows-equivalent-of-wc-l
One nice surprise is for one who has git bash on his windows: just plain old linux wc -l <filename> will works for you there
In the below code, the variable name are SalaryCount and TaxCount
#ECHO OFF
echo Process started, please wait...
for /f %%C in ('Find /V /C "" ^< "D:\Trial\Salary.txt"') do set SalaryCount=%%C
echo Salary,%SalaryCount%
for /f %%C in ('Find /V /C "" ^< "D:\Trial\Tax.txt"') do set TaxCount=%%C
echo Tax,%TaxCount%
Now if you need to output these values to a csv file, you could use the below code.
#ECHO OFF
cd "D:\CSVOutputPath\"
echo Process started, please wait...
echo FILENAME,FILECOUNT> SUMMARY.csv
for /f %%C in ('Find /V /C "" ^< "D:\Trial\Salary.txt"') do set Count=%%C
echo Salary,%Count%>> SUMMARY.csv
for /f %%C in ('Find /V /C "" ^< "D:\Trial\Tax.txt"') do set Count=%%C
echo Tax,%Count%>> SUMMARY.csv
The > will overwrite the existing content of the file and the >> will append the new data to existing data. The CSV will be generated in D:\CSVOutputPath
You can pipe the output of type into find inside the in(…) clause of a for /f loop:
for /f %%A in ('
type "%~dpf1" ^| find /c /v ""
') do set "lineCount=%%A"
But the pipe starts a subshell, which slows things down.
Or, you could redirect input from the file into find like so:
for /f %%A in ('
find /c /v "" ^< "%~dpf1"
') do set "lineCount=%%A"
But this approach will give you an answer 1 less than the actual number of lines if the file ends with one or more blank lines, as teased out by the late foxidrive in counting lines in a file.
And then again, you could always try:
find /c /v "" example.txt
The trouble is, the output from the above command looks like this:
---------- EXAMPLE.TXT: 511
You could split the string on the colon to get the count, but there might be more than one colon if the filename had a full path.
Here’s my take on that problem:
for /f "delims=" %%A in ('
find /c /v "" "%~1"
') do for %%B in (%%A) do set "lineCount=%%B"
This will always store the count in the variable.
Just one last little problem… find treats null characters as newlines. So if sneaky nulls crept into your text file, or if you want to count the lines in a Unicode file, this answer isn’t for you.
You can also try
set n=0 & for /f "tokens=*" %a in (text.txt) do set/a n=!n!+1
echo !n!
You can also mark with a wildcard symbol * to facilitate group files to count.
Z:\SQLData>find /c /v "" FR_OP133_OCCURENCES_COUNT_PER_DOCUMENTS_*.txt
Result
---------- FR_OP133_OCCURENCES_COUNT_PER_DOCUMENTS_AVIFRS01_V1.TXT: 2041
---------- FR_OP133_OCCURENCES_COUNT_PER_DOCUMENTS_AVIOST00_V1.TXT: 315938
---------- FR_OP133_OCCURENCES_COUNT_PER_DOCUMENTS_AVIFRS00_V1.TXT: 0
---------- FR_OP133_OCCURENCES_COUNT_PER_DOCUMENTS_CNTPTF00_V1.TXT: 277