Batch files: How to read a file? - file

How you can read a file (text or binary) from a batch file? There is a way to read it in a binary mode or text mode?

Under NT-style cmd.exe, you can loop through the lines of a text file with
FOR /F %%i IN (file.txt) DO #echo %%i
Type "help for" on the command prompt for more information. (don't know if that works in whatever "DOS" you are using)

The FOR-LOOP generally works, but there are some issues.
The FOR doesn't accept empty lines and lines with more than ~8190 are problematic.
The expansion works only reliable, if the delayed expansion is disabled.
Detection of CR/LF versus single LF seems also a little bit complicated.
Also NUL characters are problematic, as a FOR-Loop immediatly cancels the reading.
Direct binary reading seems therefore nearly impossible.
The problem with empty lines can be solved with a trick. Prefix each line with a line number, using the findstr command, and after reading, remove the prefix.
#echo off
SETLOCAL DisableDelayedExpansion
FOR /F "usebackq delims=" %%a in (`"findstr /n ^^ t.txt"`) do (
set "var=%%a"
SETLOCAL EnableDelayedExpansion
set "var=!var:*:=!"
echo(!var!
ENDLOCAL
)
Toggling between enable and disabled delayed expansion is neccessary for the safe working with strings, like ! or ^^^xy!z.
That's because the line set "var=%%a" is only safe with DisabledDelayedExpansion, else exclamation marks are removed and the carets are used as (secondary) escape characters and they are removed too.
But using the variable var is only safe with EnabledDelayedExpansion, as even a call %%var%% will fail with content like "&"&.
EDIT: Added set/p variant
There is a second way of reading a file with set /p, the only disadvantages are that it is limited to ~1024 characters per line and it removes control characters at the line end.
But the advantage is, you didn't need the delayed toggling and it's easier to store values in variables
#echo off
setlocal EnableDelayedExpansion
set "file=%~1"
for /f "delims=" %%n in ('find /c /v "" %file%') do set "len=%%n"
set "len=!len:*: =!"
<%file% (
for /l %%l in (1 1 !len!) do (
set "line="
set /p "line="
echo(!line!
)
)
For reading it "binary" into a hex-representation
You could look at SO: converting a binary file to HEX representation using batch file

You can use the for command:
FOR /F "eol=; tokens=2,3* delims=, " %i in (myfile.txt) do #echo %i %j %k
Type
for /?
at the command prompt. Also, you can parse ini files!

One very easy way to do it is use the following command:
set /p mytextfile=< %pathtotextfile%\textfile.txt
echo %mytextfile%
This will only display the first line of text in a text file. The other way you can do it is use the following command:
type %pathtotextfile%\textfile.txt
This will put all the data in the text file on the screen. Hope this helps!

settings.ini
name="John"
lastName="Doe"
script.bat
#echo off
for /f "tokens=1,2 delims==" %%a in (settings.ini) do (
if %%a==name set %%a=%%b
if %%a==lastName set %%a=%%b
)
echo %name% %lastName%

Well theres a lot of different ways but if you only want to DISPLAY the text and not STORE it anywhere then you just use: findstr /v "randomtextthatnoonewilluse" filename.txt

Corrected code :
setlocal enabledelayedexpansion
for /f "usebackq eol= tokens=* delims= " %%a in (`findstr /n ^^^^ "name with spaces.txt"`) do (
set line=%%a
set "line=!line:*:=!"
echo(!line!
)
endlocal
pause

Related

Reading large file from CMD and cutting information

I'm writing batch script on Windows, with it's help I would like to sort out information from many files to smaller files.
I got ~3000 long lines in log files, from whom I need get few things, basically there are name and value (example ",INC_LIMI=050,ISO_LIMI=050,INC_MLIM=074,"), and everything is separated with "," symbol. My question how you can read long string line and just read values like:
String LineString[]
LineString = Line.split(,)
String s = "INC_MLIM"
For elem in LineString
if elem.exist(s)
NewLine.append(elem)
and latter on just save to new file.
EDIT:
There is service.log file which contains multiple lines with same variable names, but I don't need all of them so the thing I'm trying to do is
From line :
",INC_MLIM=074,ISO_MLIM=074,LOC_LI_P=050,LOC_LI_L=050,TRI_LI_P=074,TRI_LI_L=074,"
Transform to new line structure with less variables and separate with tabs instead of comma. New line should look something like this:
"INC_MLIM=074 ISO_MLIM=074 LOC_LI_L=050 TRI_LI_L=074"
You don't state which values you want. I'll arbitrarily assume you want INC_LIMI and INC_MLIM.
Like most any text file manipulation, this is a pain to do with pure batch. But it is possible.
I'm assuming your lines are all <8192 characters long. If you have lines that are longer than that, then a pure batch solution is not possible, and you should skip right down to the bottom of this answer for a JREPL solution
Batch does not have a convenient split function that allows splitting at a specific user defined character. The FOR command almost works, but it also splits at ;, =, <tab>, and <space>. So it is not a good choice.
With the correct arcane syntax, you can use variable expansion find/replace to substitute a newline (0x0A) for every comma. This will generate one name=value pair per line, which is very convenient for letting FINDSTR filter out the values that you want.
Here is a solution that relies on a temporary table. This iterates all *.log files, and for each one, it creates output in *.log.new.
#echo off
setlocal enableDelayedExpansion
(set LF=^
%= This creates a newline 0x0A character =%
)
for %%N in ("!LF!") do for %%F in (*.log) do (
(
for /f "usebackq delims=" %%A in ("%%F") do (
set "ln=%%A"
echo(!ln:,=%%~N!
)
)>"%%F.temp"
findstr /b "INC_LIMI= INC_MLIM=" "%%F.temp" >"%%F.new"
del "%%F.temp"
)
type *.log.new
exit /b
Note that the above can fail if your log files contain !. This could be solved by toggling delayed expansion on and off as needed.
Some people don't like to use temp files. In this case, getting rid of the temp file introduces even more arcane batch constructs. But it does eliminate the ! delayed expansion issue, and the code is shorter. This version can also be significantly slower if the source files are very large.
#echo off
setlocal disableDelayedExpansion
(set LF=^
%= This creates a newline 0x0A character =%
)
for %%F in (*.log) do (
for /f "usebackq delims=" %%A in ("%%F") do (
set "ln=%%A"
cmd /v:on /c "for %%N in ("!LF!") do #echo(!ln:,=%%~N!"|findstr /b "INC_LIMI= INC_MLIM="
)
) >"%%F.new"
type *.log.new
exit /b
It is also possible to solve this without using FINDSTR. But this solution assumes the same name never appears more than once on any given line, and all found names have a value:
#echo off
setlocal disableDelayedExpansion
for %%F in (*.log) do (
for /f "usebackq delims=" %%A in ("%%F") do (
set "ln=,%%A"
for %%N in (INC_LIMI INC_MLIM) do call :findName %%N
)
) >"%%F.new"
type *.log.new
exit /b
:findName Name
setlocal enableDelayedExpansion
set "test=!ln!"
:loop
set "test2=!test:*,%1=!"
if "!test2!" equ "!test!" return
if not defined test2 return
if "!test2:~0,1!" neq "=" set "test=,!test2:*,=!" & goto :loop
for /f "delims=," %%V in ("!test2:~1!") do (
endlocal
echo(%1=%%V
)
exit /b
Here is a variation that handles empty values, but can break if a value contains quotes or poison characters:
#echo off
setlocal disableDelayedExpansion
for %%F in (*.log) do (
for /f "usebackq delims=" %%A in ("%%F") do (
set "ln=,%%A"
for %%N in (INC_LIMI INC_MLIM) do call :findName %%N
)
) >"%%F.new"
type *.log.new
exit /b
:findName Name
setlocal enableDelayedExpansion
set "test=!ln!"
:loop
set "test2=!test:*,%1=!"
if "!test2!" equ "!test!" return
if not defined test2 return
if "!test2:~0,1!" neq "=" set "test=,!test2:*,=!" & goto :loop
set "test2=%1!test2!
endlocal&echo(%test2:,=&rem %
exit /b
But I wouldn't use any of the above. In fact, I would never restrict myself to pure batch because text file manipulation is so darn inefficient and inscrutable.
Instead, I would use JREPL.BAT - a regular expression command line text processing utility. JREPL.BAT is pure script (hybrid JScript/batch) that runs natively on any Windows machine from XP onward - no 3rd party exe file required.
With JREPL, the solution is as simple as
#echo off
for %%F in (*.log) do call jrepl "(?:^|,)((?:INC_LIMI|INC_MLIM)=[^,]*)" "$txt=$1" /jmatchq /f "%%F" /o "%%F.new"
type *.log.new
Not only is the code nice and clean, it is much faster than any pure batch solution.

Batch: Reading in a line from a file that has a colon and exclamation

I'm trying to read a line from a file that contains a colon and exclamation point. When I echo that line, it only shows everything AFTER the colon. I need to keep the enabledelayedexpansion for more advanced code I will be doing in the do loop. But right now I just want to echo properly.
The file should say something like this:
! 12345 APX:
6.32
The code I tried was:
setlocal EnableDelayedExpansion
cd C:\Users\jwagh\Desktop\
for /f "usebackq delims=" %%a in (test.txt) DO (
setlocal DisableDelayedExpansion
echo %%a
set line=%%a
set example=%line:~0,-1%
echo %example%
)
Probably disabling delayedexpansion is the only practicable solution, but depending on your data, setting delims to ! would set %%a to the entire line, minus the initial ! (assuming obviously only one !-sequence of known length and it always leads the line)
OR
for /f "usebackq delims=!" %%a in (%FamilyFile%) DO echo ^^!%%a
The only bullet-proof solution is to disable delayed expansion during expansion of %%a, and to enable it only when it is actually needed. For example, something like this:
setlocal DisableDelayedExpansion
for /F "usebackq delims=" %%a in (%File%) do (
set "Item=%%a"
setlocal EnableDelayedExpansion
echo(!Item:,-1!
endlocal
)
Since localised environments created by setlocal/endlocal blocks are limited in terms of nesting, you need to put also endlocal within a loop. Regard that after endlocal, all environment changes since the latest setlocal get lost.
But now for something completely different (related to revision 3 of the question):
I assume %File% holds a quoted file path/name, because of the usebackq option.
Let me recommend not to include the quotes in the variable value, using the following syntax:
set "File=D:\Data\file.ext" & rem // (particularly note the position of the opening quote)
Reference this post to learn why this is better.

preserve empty lines in a text file while using batch for /f

I have been trying to find and replace a string in a text file using batch script. I came across this answer which almost solved my issue, but the empty lines were not preserved in the output file. I have tried this answer too but lines start with line numbers []... [17]
Any help to to extend this answer to preserve empty lines in the output file would be appreciated.
Thanks
setlocal enableextensions disabledelayedexpansion
set "search=<Tool>"
set "replace=XYZ"
set "textFile=C:\abc.txt"
for /f "delims=" %%i in ('type "%textFile%" ^| find /v /n "" ^& break ^> "%textFile%"') do (
set "line=%%i"
setlocal enabledelayedexpansion
set "line=!line:%search%=%replace%!"
>>"%textFile%" echo(!line!
endlocal
)
The output looks like:
You did not study the answer at your 2nd link hard enough - it has a solution that works perfectly well.
I prefer a variant of that technique that uses *]= replacement instead of substring:
#echo off
setlocal enableextensions disabledelayedexpansion
set "search=<Tool>"
set "replace=XYZ"
set "textFile=C:\abc.txt"
for /f "delims=" %%i in ('type "%textFile%" ^| find /v /n "" ^& break ^> "%textFile%"') do (
set "line=%%i"
setlocal enabledelayedexpansion
set "line=!line:*]=!"
if defined line set "line=!line:%search%=%replace%!"
>>"%textFile%" echo(!line!
endlocal
)
But the code is not optimized - the append redirection slows things down because the output file must be opened and the file pointer positioned to the end-of-file for each iteration of the loop. It is much faster to redirect to a temporary file once, outside of the loop, and then use MOVE to replace the original with the temp.
I also prefer to use FINDSTR instead of FIND - it handles long lines better, and does not need a pipe or redirection.
#echo off
setlocal enableextensions disabledelayedexpansion
set "search=<Tool>"
set "replace=XYZ"
set "textFile=C:\abc.txt"
>"%textFile%.new" (
for /f "delims=" %%i in ('findstr /n "^" "%textFile%"') do (
set "line=%%i"
setlocal enabledelayedexpansion
set "line=!line:*:=!"
if defined line set "line=!line:%search%=%replace%!"
echo(!line!
endlocal
)
)
move /y "%textFile%.new" "%textFile%" >nul
Truth be told, I never use pure batch to modify text files anymore. There are too many edge cases that take a lot of arcane code to work around. There are still many potential issues with the above code. For example:
The search string cannot contain =
The search string cannot begin with * or !
The replace string cannot contain !
The replacement can fail if the search and/or replace contain both " as well as poison characters like &, | etc.
I use the JREPL.BAT regular expression find/replace utility instead. It is faster, more robust, and much more powerful. It is pure script (hybrid batch/JScript) that runs natively on any Windows machine from XP onward, without any need for 3rd party exe files.
For example, the following simple command does your literal find/replace very quickly.
call jrepl "<Tool>" "XYZ" /l /f "C:\abc.txt" /o -

I am writing a .bat program to find and replace text in a file without changing its position

I am writing a .bat program that will find and replace text in a file. The problem that I am having is that it is removing blank lines and left justifying the other lines. I need the blank lines to remain and the new text to remain in the same location. Here is what I have wrote, and also the result. Can anybody please help.
program:
#ECHO OFF
cls
cd\
c:
setLocal EnableDelayedExpansion
For /f "tokens=* delims= " %%a in (samplefile.tx) do (
Set str=%%a
set str=!str:day=night!
set str=!str:winter=summer!
echo !str!>>samplefile2.txt)
ENDLOCAL
cls
exit
samle File:
this line is the first line in my file that I am using as an example.This is made up text
the cat in the hat
day
winter
below is the result:
this line is the first line in my file that I am using as an example.This is made up text
the cat in the hat
night
summer
I need the lines, spaces and new text to remain in the same position while making the text replacement. Please help
Your use of "tokens=* delims= " will trim leading spaces. Instead, use "delims=" to preserve leading spaces.
FOR /F always skips empty lines. The trick is to insert something before each line. Typically FIND or FINDSTR is used to insert the line number at the front of each line.
You can use !var:*:=! to delete the the line number prefix from FINDSTR.
Use echo(!str! to prevent ECHO is off message when line is empty
It is more efficient (faster) to redirect only once.
#echo off
setlocal enableDelayedExpansion
>samplefile2.txt (
for /f "delims=" %%A in ('findstr /n "^" samplefile.txt') do (
set "str=%%A"
set "str=!str:*:=!"
set "str=!str:day=night!"
set "str=!str:winter=summer!"
echo(!str!
)
)
This still has a potential problem. It will corrupt lines that contain ! when %%A is expanded because of the delayed expansion. The trick is to toggle delayed expansion on and off within the loop.
#echo off
setlocal disableDelayedExpansion
>samplefile2.txt (
for /f "delims=" %%A in ('findstr /n "^" samplefile.txt') do (
set "str=%%A"
setlocal enableDelayedExpansion
set "str=!str:*:=!"
set "str=!str:day=night!"
set "str=!str:winter=summer!"
echo(!str!
endlocal
)
)
Or you could forget custom batch entirely and get a much simpler and faster solution using my JREPL.BAT utility that performs regular expression search and replace on text. There are options to specify multiple literal search/replace pairs.
jrepl "day winter" "night summer" /t " " /l /i /f sampleFile.txt /o sampleFile2.txt
I used the /I option to make the search case insensitive. But you can drop that option to make it case sensitive if you prefer. That cannot be done easily using pure batch.
#ECHO Off
SETLOCAL
(
FOR /f "tokens=1*delims=]" %%a IN ('find /n /v "" q27459813.txt') DO (
SET "line=%%b"
IF DEFINED line (CALL :subs) ELSE (ECHO()
)
)>newfile.txt
GOTO :EOF
:subs
SET "line=%line:day=night%"
SET "line=%line:winter=summer%"
ECHO(%line%
GOTO :eof
Thi should work for you. I used a file named q27459813.txt containing your data for my testing.
Produces newfile.txt
Will not work correctly if the datafile lines start ].
Revised to allow leading ]
#ECHO Off
SETLOCAL
(
FOR /f "delims=" %%a IN ('type q27459813.txt^|find /n /v "" ') DO (
SET "line=%%a"
CALL :subs
)
)>newfile.txt
GOTO :EOF
:subs
SET "line=%line:*]=%"
IF NOT DEFINED line ECHO(&GOTO :EOF
SET "line=%line:day=night%"
SET "line=%line:winter=summer%"
ECHO(%line%
GOTO :eof

Multiple conditions in For loop batch?

I'd like to print each line of 2 separate txt files alternately using a for loop in a batch file, I tried using an AND but was given: "AND was unexpected at this time" in cmd.exe when I ran my batch. Any ideas?
FOR /F "tokens=*" %%F in (!logPath!) AND for /f "tokens=*" %%H in (%%refLogPath) DO (
REM print each line of log file and refLog file sequentially
echo %%F
echo %%H
REM set logLine=%%F
REM check 'each line' of log file against ENG-REF.log
)
There isn't a keyword like AND, normally you couldn't solve this with two FOR loops.
But there is an alternative way to read a file with set /p.
setlocal EnableDelayedExpansion
<file2.txt (
FOR /F "delims=" %%A in (file1.txt) DO (
set /p lineFromFile2=
echo file1=%%A, file2=!lineFromFile2!
)
)
I believe this is as robust as a batch solution can get.
It handles blank lines in both files
It can read up to approximately 8k bytes on each line
The number of lines in the files does not have to match
A line can begin with any character (avoiding a FOR /F EOL issue)
A line can contain ! without getting corrupted (avoiding a problem of expanding a FOR
variable while delayed expansion is enabled)
Lines can be either Unix or Windows style.
Control characters will not be stripped from end of line
But this solution will get progressively slower as it reads a large file because it must rescan the 2nd file from the beginning for every line.
#echo off
setlocal disableDelayedExpansion
set "file1=file1.txt"
set "file2=file2.txt"
for /f %%N in ('find /c /v "" ^<"%file2%"') do set file2Cnt=%%N
findstr /n "^" "%file1%" >"%file1%.tmp"
findstr /n "^" "%file2%" >"%file2%.tmp"
set "skip=0"
set "skipStr="
for /f "usebackq delims=" %%A in ("%file1%.tmp") do (
set "ln1=%%A"
call :readFile2
set /a "skip+=1"
)
if %file2Cnt% gtr %skip% (
for /f "usebackq skip=%skip% delims=" %%B in ("%file2%.tmp") do (
set "ln2=%%B"
setlocal enableDelayedExpansion
set "ln2=!ln2:*:=!"
(echo()
(echo(!ln2!)
)
)
del "%file1%.tmp" 2>nul
del "%file2%.tmp" 2>nul
exit /b
:readFile2
if %skip% gtr 0 set "skipStr=skip=%skip% "
if %file2Cnt% gtr %skip% (
for /f "usebackq %skipStr%delims=" %%B in ("%file2%.tmp") do (
set "ln2=%%B"
goto :break
)
) else set "ln2="
:break
setlocal enableDelayedExpansion
set "ln1=!ln1:*:=!"
if defined ln2 set "ln2=!ln2:*:=!"
(echo(!ln1!)
(echo(!ln2!)
exit /b
Much better to use jeb's approach if that solution's limitations are not a concern with your files. It currently has the following limitations that could be removed with fairly minor modifications:
Files must have same number of lines
Files must not have blank lines
File1 must not contain ! character
No line in File1 can start with ;
In addition it has the following limitations when reading File2 that are inherent to the SET /P limitations
Lines must be Windows style, ending in carriageReturn lineFeed
Lines cannot exceed 1021 characters (bytes) excluding the line terminators
Control characters will be stripped off the end of each line
An even better solution would be to use something other than batch. There are many possibilities: VBS, JScript, PowerShell, perl ... the list goes on and on.

Resources