So, I have probably a simple question but I cannot seem to find an easy answer.
Issue: I have a file that contains a set of lines such as:
%windir%\file.exe
%windir%\file2.dll
and so forth...
What I am trying to do is echo the actual file path to a second file such that the resulting output would be something like:
C:\Windows\file.exe
C:\Windows\file2.dll
and so forth...
The actual source file could have other variables such as %programfiles% but all of them have a resulting actual path.
I am currently using a for /f loop but when I echo the variable, I just get the environment variable returned rather than the actual path to the file.
Is there a solution out there for batch scripting?
The actual script is below. Note I am all for making this more efficient as time to get the information is important.
#echo off
setlocal enabledelayedexpansion
reg.exe query "HKLM\System\CurrentControlSet\Services" >> registry_hklm_installed_services_tmp.txt 2>nul
reg.exe query "HKLM\System\ControlSet001\Services" >> registry_hklm_installed_services_tmp.txt 2>nul
reg.exe query "HKLM\System\ControlSet002\Services" >> %temp_outpath%\registry_hklm_installed_services_tmp.txt 2>nul
reg.exe query "HKLM\System\ControlSet002\Services" >> registry_hklm_installed_services_tmp.txt 2>nul
for /f "delims=?" %%a in (registry_hklm_installed_services_tmp.txt) do (
set regkey=%%a
call :getvalue
)
goto :parsereg
:getvalue
reg.exe query "!regkey!\Parameters" /v ServiceDll > nul 2>&1 && goto regkeyexist
goto :eof
:regkeyexist
for /f "tokens=2*" %%b in ('reg.exe query "!regkey!\Parameters" /v ServiceDll') do set ImagePath=%%c
call :regag
goto :eof
:regag
echo !ImagePath! >> registry_hklm_installed_services_tmp2.txt
goto :eof
:parsereg
for /f "delims=?" %%a in (registry_hklm_installed_services_tmp2.txt) do echo %%a >> registry_hklm_installed_services_tmp3.txt
You can use the for /f command to cycle through the lines in the file like you are doing, and pass the line from the file to a subroutine inside the batch file, which will resolve it while it is being passed. Give the following:
Test.txt
%windir%\test.txt
%programfiles%\Test2.txt
This batch file will resolve the environment variables:
#echo off
setlocal ENABLEDELAYEDEXPANSION
for /f "delims=" %%i in (Test.txt) do call :Expand "%%i"
endlocal
goto TheEnd
:Expand
set _var=%1
echo !_var:"=!
:TheEnd
This is how the output looks when you run it:
c:\>Test.bat
C:\Windows\test.txt
C:\Program Files (x86)\Test2.txt
You can redirect the result to a new text file like this:
Test.bat > NewFile.txt
Or you can modify the original Test.bat to output the modified filename under Expand instead of echoing it to the console. It is important to include the quotes around %%i ("%%i") or spaces in the resolved paths will break into multiple variables when calling Expand (e.g., %1, %2, %3, etc.). The !_var:"=! removes the quotes.
This will also expand the variables.
#echo off
for /f "delims=" %%a in (Test.txt) do call echo %%a
pause
Test.txt
%windir%\test.txt
%programfiles%\Test2.txt
#ECHO OFF
SETLOCAL
FOR /f "delims=" %%a IN (q22726616.txt) DO (
FOR /f "delims=" %%b IN ('echo %%a') DO (
ECHO %%b
)
)
GOTO :EOF
I used a file named q22726616.txt containing your data for my testing.
[fixed following response - %%b line]
Related
#Echo off&SetLocal EnableExtensions EnableDelayedExpansion
cd "C:\Documents and Settings\John\Desktop\New\Interest\f2"
Pushd "C:\Documents and Settings\John\Desktop\New\Interest\f2"
Set Line#=26
Set /A LOfs=24 -1, Len=34 - LOfs
For %%A in (*.txt) do For /F "tokens=1* delims=:" %%B in (
'Findstr /N ".*" "%%A" ^|Findstr "^%Line#%:"'
) do if %errorlevel% == 0 Set "Line=%%C"&Ren "%%~fA" "!Line:~%LOfs%,%Len%! - %%A!""
Popd
In the above I am trying to change the filename of files in a directory with text in it at a certain position.
If line 26 is blank do nothing and do not change filename.
I have gone wrong somewhere and am going round in circles.
Can anyone help?
Thank you.
You don't state how your script fails, but I can see some potential problems. I also see possible simplifications.
You certainly don't need both CD and PUSHD
I got rid of the numeric variables and included the number literals in the actual code. You can revert back to variables if you want.
You don't need the outer FOR loop. FINDSTR can search multiple files when using wildcards in the file name, and then it will include the filename, followed by : in the output. So if you add the /N option, output will have the form filename:line#:text. You can then adjust the 2nd FINDSTR to return only the correct line numbers.
It is not enough to ignore blank lines. Your rename only works if there is at least one valid file name character after the 23rd character. Filenames cannot include :, *, ?, /, \, <, >, or |. (I may have missed some). I adjusted the FOR /F delims and the FINDSTR search to compensate.
FOR variable expansion like %%A will corrupt values if they contain ! and delayed expansion is enabled. ! is a valid character in file names. So the delayed expansion must be toggled on and off within the loop.
I believe the following will do what you want. The code below will simply echo the rename commands. Remove the ECHO before the ren once it gives the correct results.
#echo off
setlocal disableDelayedExpansion
pushd "C:\Documents and Settings\John\Desktop\New\Interest\f2"
for /f "tokens=1,3 delims=:*?\/<>|" %%A in (
'findstr /n "^" "*.txt" ^| findstr "^[^:]*:26:.......................[^:*?\\/<>|]"'
) do (
set "old=%%A"
set "line=%%B"
setlocal enableDelayedExpansion
ECHO ren "!old!" "!line:~23,11! - !old!"
endlocal
)
popd
An slightly different method to Daves:
#Echo Off
Set "SrcDir=%UserProfile%\Desktop\New\Interest\f2"
Set "Mask=*.txt"
Set "Line#=26"
Set "LOfs=23"
Set "Len=11"
If /I Not "%CD%"=="%SrcDir%" Pushd "%SrcDir%"2>Nul&&(Set _=T)||Exit/B
For /F "Tokens=1-2* Delims=:" %%A In ('FindStr/N "^" "%Mask%" 2^>Nul'
) Do If "%%B"=="%Line#%" If Not "%%~C"=="" (Set "Line=%%C"
SetLocal EnableDelayedExpansion
If Not "!Line:~%LOfs%,%Len%!"=="" (
If Not Exist "!Line:~%LOfs%,%Len%! - %%A" (
Ren "%%A" "!Line:~%LOfs%,%Len%! - %%A"))
EndLocal)
If "_"=="T" PopD
This method don't require findstr.exe nor toggle setlocal/endlocal, so it should run faster. Also, it avoids to re-process any already renamed file changing the plain for %%A by a for /F combined with dir command.
#Echo off
SetLocal EnableDelayedExpansion
cd "C:\Documents and Settings\John\Desktop\New\Interest\f2"
Set /A Line#=26, LOfs=24 -1, Len=34 - LOfs
For /F "delims=" %%A in ('dir /A-D /B *.txt') do (
rem Read the desired line from this file
(for /L %%i in (1,1,%Line#%) do set "Line=" & set /P "Line=") < "%%A"
if defined Line ECHO Ren "%%~fA" "!Line:~%LOfs%,%Len%! - %%A"
)
Note also that when this Batch file ends the current directory is automatically recovered to the current one when setlocal command was executed, so pushd/popd commands are not needed either.
I want to write a script to prompt user for file path and list all files found. The file path can contain wildcards. Something similar to this. But the batch script version of it. For example:
C:\Somewhere\user*\app\version-*.*\start.exe
The files might be located like this:
C:\Somewhere\user345\app\version-1.0\start.exe
C:\Somewhere\user898\app\version-1.2\start.exe
C:\Somewhere\user898\app\version-1.3\start.exe
I tried to use FOR and it turns out to be so much harder than expected because FOR does not support wildcards in the middle of a path.
Is there a way to list these files? (Maybe without using for?)
I think this recursive solution works pretty well; you may name it WCDIR.bat:
#echo off
setlocal
if "%~1" neq "" set "next=%~1" & goto next
echo Show files selected by several wild-cards
echo/
echo WCDIR wildcardPath
echo/
echo Each folder in the path may contain wild-cards
echo the last part must be a file wild-card
goto :EOF
:next
for /F "tokens=1* delims=\" %%a in ("%next%") do set "this=%%a" & set "next=%%b"
if defined next (
for /D %%a in ("%this::=:\%") do (
setlocal
cd /D "%%~a" 2>NUL
if not errorlevel 1 call :next
endlocal
)
) else (
for /F "delims=" %%a in ('dir /B /A:-D "%this%" 2^>NUL') do echo %%~Fa
)
exit /B
EDIT: I fixed a small bug in the last for /F command.
For example, the output of WCDIR.bat C:\Windows\Sys*\find*.exe command in my Windows 8.1 64-bits computer is:
C:\Windows\System32\find.exe
C:\Windows\System32\findstr.exe
C:\Windows\SysWOW64\find.exe
C:\Windows\SysWOW64\findstr.exe
You can try with the command Where /?
The WHERE command is roughly equivalent to the UNIX 'which' command. By default, the search is done in the current directory and in the PATH.
#echo off
Where /R "%programfiles%" *winrar.exe
pause
#echo off
:: Example d'input
set UserInput=*drive*
:: building the Pattern
set cmd=%Userinput%.exe
:: storage Where.exe command in a macro, the execution will be faster
set whereCmd=where.exe /r c:\windows\ %cmd%
:: execution of macro and output formatting
for /f %%a in ('%whereCmd%') do echo %%~nxa --^> %%a
pause
This is my first posting so if the format is not as it supposed to be please excuse me for this. (Suggestions for
improvement are welcome.)
I am trying to create a batchfile that will read last lines from logfiles and copy them to a new file.
Until now I have found here a way to read the last line.
Code would be something like:
for /f %%i in ('find /v /c "" ^< someFile.txt') do set /a lines=%%i
set /a startLine=%lines% - 1
more /e +%startLine% someFile.txt > lastLines.txt
The above code works for one file at a time. What I need is to read the last line from all files in a known list and add this line to a new .csv file.
I have been using the following code for getting the 4th entry in the logfiles but it returns every line of every logfile:
for /f %%x in (%list%) do for /f "delims=.txt, tokens=4" %%i in (%%x.txt) do echo %%x, %%i >> output.csv
What I would need is a sort of combination of both but I don't know how to combine them and make the complete last line be copied to the .csv file.
===
#Magoo:
Thanx for your reaction.
In every logfile can be 1 to >100 lines with comma separated information. Something like:
"LOGON,6-1-2015,12:43:39,USERNAME,HOSTNAME,,,,192.168.209.242,00:21:5A:2E:64:5E"
The last code with the 4th entry was used to get a list of all accounts that had logged in to the computers. This code gave me a very large list of all logon/logoff events on all computerlogs I checked in %list%.
In %list$ I had all the names of logfiles I wanted to be checked. This returned all lines.
For a new batchfile I need only the last logon/logoff entry and I want the whole last line.
So I have a .txt file with the hostnames of all computers I need to examine.
This .txt file will be read line by line via the variable %list%.
From every logfile I need only the last line copied to an output file.
===
I just tried the solution offered by JosefZ. Unfortunately this does not work for me yet. No lastlines are copied to the resultfile. In the code I removed the extra entry for possible lastlines for there are no empty lines in the logs, I also added an entry for the hostname I want to be available in the result. JosefZ had the filename there:
#ECHO OFF >NUL
#SETLOCAL enableextensions disabledelayedexpansion
type nul>output.csv
set "list=_listing.txt"
for /F "tokens=*" %%x in ('type "%list%"') do (
set "host=%%~x"
for /F "tokens=*" %%G in ('type "%%~x"') do set "lastline=%%G"
call :lline
)
:endlocal
#ENDLOCAL
goto :eof
:lline
set "filename=.\logs\%filename:&=^&%.txt"
echo %host%,%lastline%>>output.csv
goto :eof
The resultfile shows only the hostnames. I'll puzzle some more with this but all tips are welcome!
===
Got it!!!
#ECHO OFF >NUL
#SETLOCAL enableextensions disabledelayedexpansion
type nul>output.csv
set "list=_listing.txt"
for /F "tokens=*" %%x in ('type "%list%"') do (
set filename= :: *empty previous filename*
set lastline= :: *empty previous lastline*
set "host=%%~x"
set "filename=.\logs\%host%.txt" :: *creating the filename from path+hostname+extention*
for /F "tokens=*" %%G in ('type "%filename%"') do set "lastline=%%G"
call :lline
)
:endlocal
#ENDLOCAL
goto :eof
:lline
echo %host%,%lastline%>>output.csv
goto :eof
Your approach with line numbering could fail if a file has more trailing empty lines. Fortunately for /F loop ignores (does not iterate) empty lines; let's put to use this feature: in the script used next practices:
disabledelayedexpansion to allow ! in file names
set "list=_listing.txt" where the _listing.txt contains list of file names (full path and extension .txt including), one file name on one line: got by dir /b /s *.txt>_listing.txt
type nul>files\output.csv to empty the output file (optional)
set "lastline=!!!file empty!!!" to initialize variable %lastline%; could be set "lastline=" as well
call :lline to process variables %filename% and %lastline%
set "filename=%filename:&=^&%" to allow & in file names
The script is as follows:
#ECHO OFF >NUL
#SETLOCAL enableextensions disabledelayedexpansion
type nul>files\output.csv
set "list=_listing.txt"
for /F "tokens=*" %%x in ('type "%list%"') do (
set "filename=%%~x"
set "lastline=!!!file empty!!!"
rem the whole line
for /F "tokens=*" %%G in ('type "%%~x"') do set "lastline=%%G"
rem the fourth token only
rem for /F "tokens=4" %%G in ('type "%%~x"') do set "lastline=%%G"
call :lline
)
:endlocal
#ENDLOCAL
goto :eof
:lline
set "filename=%filename:&=^&%"
echo %filename% %lastline%
rem >>files\output.csv
goto :eof
Sample _listing.txt file:
d:\bat\files\1exclam!ation.txt
d:\bat\files\2exc!lam!ation.txt
d:\bat\files\11per%cent.txt
d:\bat\files\12per%cent%.txt
d:\bat\files\17per%Gcent.txt
d:\bat\files\18per%%Gcent.txt
d:\bat\files\21ampers&nd.txt
d:\bat\files\22ampers&&nd.txt
Output:
d:\bat>lastlines
d:\bat\files\1exclam!ation.txt 0 15.01.2015 1:52:28.48 -15072 20465
d:\bat\files\2exc!lam!ation.txt 6 15.01.2015 1:52:28.50 3250 16741
d:\bat\files\11per%cent.txt -8 15.01.2015 1:52:28.50 -3692 27910
d:\bat\files\12per%cent%.txt !!!file empty!!!
d:\bat\files\17per%Gcent.txt 0 15.01.2015 1:52:28.56 14508 12374
d:\bat\files\18per%%Gcent.txt 1 15.01.2015 1:52:28.56 30540 26959
d:\bat\files\21ampers&nd.txt 15.01.2015 1:22:50.18
d:\bat\files\22ampers&&nd.txt 15.01.2015 1:22:50.18
Honestly, all that ballast is for (possibly) trailing empty lines in files and for (possibly) ! and & in file names only; all could be done with
for /f %%x in (%list%) do for /f "skip=%startLine% tokens=4" %%i in (%%x) do echo %%x, %%i >> output.csv
You should use a simple FOR to iterate a list of values, not FOR /F.
Something like the following should work:
#echo off
setlocal enableDelayedExpansion
>>output.csv (
for %%F in (
"file1.log"
"file2.log"
"file3.log"
etc.
) do (
for /f %%A in ('find /v /c "" <%%F') do set /a skip=%%A-1
more +!skip! %%F
)
)
The quotes around the file names are there in case you get a name with spaces.
You could use your LIST variable if it looks something like
set LIST="file1.log" "file2.log" "file3.log" etc.
#echo off
setlocal enableDelayedExpansion
set LIST="file1.log" "file2.log" "file3.log" etc.
>>output.csv (
for %%F in (%LIST%) do (
for /f %%A in ('find /v /c "" <%%F') do set /a skip=%%A-1
more +!skip! %%F
)
)
If any of your file names contain the ! character, then you must toggle delayed expansion ON and OFF within your loop. Otherwise the delayed expansion will corrupt the names when %%F is expanded.
#echo off
setlocal disableDelayedExpansion
set LIST="file1.log" "file2.log" "file3.log" etc.
>>output.csv (
for %%F in (%LIST%) do (
for /f %%A in ('find /v /c "" <%%F') do set /a skip=%%A-1
setlocal enableDelayedExpansion
more +!skip! %%F
endlocal
)
)
I am having a problem with Windows path expansion using a batch file where the variable contains a string from the Windows registry. See script below. I have tried multiple methods but I must be missing something simple.
#echo off
setlocal enabledelayedexpansion
for /f "tokens=2*" %%a in ('reg.exe query "HKEY_LOCAL_MACHINE\SYSTEM\CurrentControlSet\Services" /t REG_EXPAND_SZ /f ServiceDll /s ^| grep.exe -ia "REG_EXPAND_SZ" ') do (
set registry_value2=%%b
call :regmerge
)
goto :endofscript
:regmerge
echo !registry_value2!
goto :eof
:endofscript
endlocal
Replace
set registry_value2=%%b
with
call set "registry_value2=%%b"
and remove the call to regmerge. The call will force the parser to do a second pass and replace the environment variables references.
Here is a possible solution with the use of findstr and storing the intermediate result in a temp file.
#echo off
rem tempfile
set tf=regfind.tmp
rem reg query output pipe into findstr and redirect to temnpfile
reg.exe query "HKEY_LOCAL_MACHINE\SYSTEM\CurrentControlSet\Services" /t REG_EXPAND_SZ /v ServiceDll /s | findstr /C:SystemRoot > %tf%
rem iterate over each line in temp file and call merge
for /F "tokens=3*" %%a in (%tf%) do call :merge %%a
rem delete tempfile
del /q %tf%
goto :eof
:merge
rem SystemRoot is exapanded now...
echo %1
goto :eof
I'm trying to work up a batch file to combine two pieces of text on one line. The first is the filename; the second is the first line of text beginning with "To: ". I have been struggling for hours and this is as far as I've gotten:
#echo off
IF EXIST fullnames.txt DEL fullnames.txt
FOR /F %%g IN ('dir /b *.eml') DO (
SET filename=%~f1
SET toline=FINDSTR /B /C "To: "
ECHO %FILENAME%%TOLINE% >> fullnames.txt
)
and it doesn't work. I am getting errors or incorrect results almost regardless of what I put down for the filename line; haven't even begun to test the toline part. Any suggestions?
You already used FOR /F to capture the output of the DIR command. Capturing the output of FINDSTR is no different.
However, it is more efficient to use a simple FOR in place of the FOR /F with the DIR command.
You used %~f1 when I think you intended %%~fg.
You cannot expand a variable set within parentheses using %var%, you must use !var! delayed expansion instead. Type SET /? from the command line for more information - read the section starting with "Finally, support for delayed environment variable expansion has been
added..."
However, in your case, you can easily avoid using delayed expansion (not that it is a problem).
Instead of deleting any existing "fullnames.txt" and then appending output to it, it is more efficient to enclose the entire construct within parentheses and redirect all output to the file using the over-write mode.
#echo off
(
for %%F in (*.eml) do (
for "delims=" %%A in ('findstr /b /c:"To: " "%%F"') do echo %%F %%A
)
) >fullnames.txt
But the above solution, simple as it is, is much more complicated than it needs to be.
FINDSTR can process multiple files specified with wildcards, and it will prefix each matching line with the filename followed by a colon.
You can get your results simply from the command line without even using a batch file (or you could put this in a batch file):
findstr /b /c:"To: " *.eml >fullnames.txt
Edit
If you are concerned that a file might contain multiple lines starting with "To: ", and you only want to use the first line, then it is back to using a batch file:
#echo off
setlocal enableDelayedExpansion
set "prevFile="
(
for /f "tokens=1* delims=:" %%A in ('findstr /b /c:"To: " *.eml') do (
if "%%A" neq "!prevFile!" echo %%A: %%B
set "prevFile=%%A"
)
) >fullnames.txt
The above solution could fail if a filename contains !. Also, a path could be used with *.eml as long as the path does not contain a drive letter. Both the drive and ! issues can be resolved with additional modifications.
#echo off
setlocal EnableDelayedExpansion
if exist fullnames.txt del fullnames.txt
for %%f in (*.eml) do (
set toline=
for /F "delims=" %%l in ('findstr /B /C:"To: " "%%f"') do (
if not defined toline set "toline=%%l"
)
echo %%f!toline! >> fullnames.txt
)
EDIT: Simpler method added
The set toline= command delete 'toline' variable before each file is processed, so just the first "To: " matching line found is assigned to it and later shown using Delayed Expansion. However, this process may be achieved in a simpler way that doesn't require Delayed Expansion, as dbenham suggested:
#echo off
if exist fullnames.txt del fullnames.txt
for %%f in (*.eml) do (
set firstFind=
for /F "delims=" %%l in ('findstr /B /C:"To: " "%%f"') do (
if not defined firstFind set firstFind=now & echo %%f%%l >> fullnames.txt
)
)
You can't assign and use environment variables inside a for loop. Use delayed variable expansion or call a subroutine.
Delayed would look something like this
setlocal EnableDelayedExpansion
#echo off
IF EXIST fullnames.txt DEL fullnames.txt
FOR /F %%g IN ('dir /b *.eml') DO (
SET filename=%~f1
SET toline=FINDSTR /B /C "To: "
ECHO !FILENAME!!TOLINE! >> fullnames.txt
)
However, that doesn't look like it would work correctly anyway. I would do it like this
FOR /F %%g IN ('dir /b *.eml') DO call :process %%g
goto :eof
:process
SET filename=%~f1
SET toline=FINDSTR /B /C "To: "
ECHO %FILENAME%%TOLINE% >> fullnames.txt