I am not sure if this is easily possible. I can easily parse any line that contains the same information. If I wanted to find all the lines that contain billing info in test.txt I could do findstr /c:"Billing" test.txt
But what if I wanted the line after the findstr result. In this example I would like to try and find the user notes but they are on the line below.
Edit:
The random strings are exactly that. They contain no similar characters. Also the actual file contains hundreds of lines not only the four I have included as an example.
Billing Info 1234
PayPal Manage Payment
User Notes:
ABC RANDOM STRING
Billing Info 5678
PayPal Manage Payment
User Notes:
DEF RANDOM STRING
Billing Info qwerty
PayPal Manage Payment
User Notes:
XYZ RANDOM STRING
I thought something could be done with a for loop and the skip flag.
for /f "skip=1 tokens=1*" %a in ('findstr /c:"User" test.txt') do (echo %a %b)
But I think skip skips lines at the start of file rather than at each result.
Apologies if this is painfully obvious, thank you for any help or advice.
Rory
FINDSTR cannot be used here as it always processes a text file line by line. It is not possible to search for a line below another line or instruct FINDSTR to output additional lines below or above a line containing a found string.
The following batch file code may work:
#echo off
setlocal EnableExtensions DisableDelayedExpansion
if not exist "test.txt" exit /B 1
set "UserNotes="
for /F usebackq^ delims^=^ eol^= %%# in ("test.txt") do for /F tokens^=1^,2^ eol^= %%I in ("%%#") do (
if "%%I %%J" == "Billing Info" (
set "UserNotes="
) else if "%%I %%J" == "User Notes:" (
set "UserNotes=1"
) else if defined UserNotes (
echo(%%#
)
)
endlocal
The outer FOR /F loop processes the text file with ignoring only empty lines. Each non-empty line is assigned as is to the specified loop variable # because of the definition of an empty list of delimiters and no end of line character. The syntax without usage of surrounding " must be used here as this is the only possibility to specify no delimiter and no end of line character.
The inner FOR /F loop processes the current line by splitting the line up into substrings (tokens) with using normal space and horizontal tab as string delimiters. The end of line character is again defined with no character as the user note to output could start also with a semicolon after zero or more leading spaces/tabs which should not be ignored by the inner FOR /F loop. Just the first two space/tab separated strings are of interest and are assigned therefore to the loop variables I and J.
The first IF condition is true on current line starting with the two words which indicate that a new data block begins which means the user notes section of previous data block ends. The environment variable UserNotes is deleted in this case.
The second IF condition is true on current line starting with the two words which indicate that next the user notes follow. For that reason the environment variable UserNotes is defined with a value. The value itself does not matter.
The third IF condition is true only if the other two IF conditions are false and the environment variable UserNotes is defined at the moment. In this case a non-empty line with user notes is currently assigned by the outer FOR /F to the loop variable # which is output now (or further processed).
There is used echo(%%# instead of echo %%# to output correct also a user note line which contains only spaces/tabs and nothing else (blank but not empty line). The usage of echo %%# would result in this case in output of current state of the command echo mode. The usage of ( as delimiter between the command ECHO and the non-empty line to output prevents the output of the state of the command echo mode on blank line to output.
The following batch file can be used if all user notes should be written into a text file like UserNotes.txt in current directory.
#echo off
setlocal EnableExtensions DisableDelayedExpansion
if not exist "test.txt" exit /B 1
set "UserNotes="
(for /F usebackq^ delims^=^ eol^= %%# in ("test.txt") do for /F tokens^=1^,2^ eol^= %%I in ("%%#") do (
if "%%I %%J" == "Billing Info" (
set "UserNotes="
) else if "%%I %%J" == "User Notes:" (
set "UserNotes=1"
) else if defined UserNotes (
echo(%%#
)
))>"UserNotes.txt"
if exist "UserNotes.txt" for %%I in ("UserNotes.txt") do if %%~zI == 0 del "UserNotes.txt"
endlocal
The file UserNotes.txt is deleted on being created before, but nothing is output into this file.
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.
del /?
echo /?
endlocal /?
exit /?
findstr /?
for /?
if /?
set /?
setlocal /?
Related
I've been trying to figure out how to replace an entire line in a text file that contains a certain string using a Batch Script. I've found this solution provided by another user on Stack Overflow, which does the job, however, it just stops iterating through the text file at some random point and in turn, the output file is left with a bunch of lines untransferred from the original file. I've looked character by character, and line by line of the script to figure out what each part exactly does, and can not seem to spot what is causing this bug.
The code provided, thanks to Ryan Bemrose on this question
copy nul output.txt
for /f "tokens=1* delims=:" %%a in ('findstr /n "^" file.txt') do call :do_line "%%b"
goto :eof
:do_line
set line=%1
if {%line:String =%}=={%line%} (
echo.%~1 >> output.txt
goto :eof
)
echo string >> output.txt
The lines it is stopping at always either contain < or > or both and lines with | will either cause it to stop, or sometimes it will delete the line and continue.
To do this robustly, Delayed expansion is necessary to prevent "poison" characters such as < > & | etc being interpreted as command tokens.
Note however that delayed expansion should not be enabled until after the variable containing the line value is defined so as to preserve any ! characters that may be present.
The following will robustly handle all Ascii printable characters *1, and preserve empty lines present in the source file:
#Echo off
Set "InFile=%~dp0input.txt"
Set "OutFile=%~dp0output.txt"
Set "Search=String "
Set "Replace="
>"%OutFile%" (
for /F "delims=" %%G in ('%SystemRoot%\System32\findstr.exe /N "^" "%InFile%"') do (
Set "line=%%G"
call :SearchReplace
)
)
Type "%OutFile%" | More
goto :eof
:SearchReplace
Setlocal EnableDelayedExpansion
Set "Line=!Line:*:=!"
If not defined Line (
(Echo()
Endlocal & goto :eof
)
(Echo(!Line:%Search%=%Replace%!)
Endlocal & goto :eof
*1 Note - Due to how substring modification operates, You cannot replace Search strings that:
contain the = Operator
Begin with ~
Yesterday a friend of mine give me this code what extracts a line from a file.
I don't know how it really works, and I need to port it to bash.
#echo off
setlocal EnableDelayedExpansion
set list="List_of_pages.txt"
:: Count lines and generate number
for /f "usebackq" %%c in (`find /V /C "" ^< %list%`) do set lines=%%c
set /a chosen = 0
:AA
set /a chosen = %chosen% +1
set /a skiplines= %chosen% -1
if chosen equ lines goto eof
:: gets the line
set skip=
if %skiplines% gtr 0 set skip=skip=%skiplines%
for /f "usebackq %skip% delims=" %%c in (%list%) do set "current=%%c" & goto continue
:continue
echo %current%
Now, what does for /f "usebackq %skip% delims=" %%c in (%list%) do set "current=%%c" & goto continue mean? What i really don't know what is is that %skip%
Any help though of how that line could be in bash?
Please, Note these basics of batch... which will keep you running to easily convert batch scripts to bash.
All the strings enclosed within % Symbols are Variables. (E.g. %Variable%)
Set is a command used to assign a value to a variable. (or to initialize a variable)
For is a Loop command - and, you must know the work of loop.
The Sub Commands of For command (loop) - UseBackQ, Skip, Delims.
UseBackQ: tells the CMD that, inside FOR LOOP parenthesis, there will be string - if that atring is enclosed by "" (double quotes) then it is a filename (not a simple string), and modifies the usage of ` (ticks) too.
Skip: Tells CMD to skip First N Number of lines from a Text file (It will not read those lines)
Delims: tells from where you want to chop a string (line of file) - e.g. Delims=, will seperate lines into pieces from all commas.
My Analysis:
Here, I think - what this code will do is to take a file, and Return one line of that text file as selected by user.
It is reading a file List_of_pages.txt in line 5. And, it should be Set /p Skip= #Line 12. (Don't count empty lines). And, Returning The selected line #line 14.
Hope, I helped! :)
In Windows batch, I have a for loop like so:
for /l %%a in (0,1,337) do (
for /F "tokens=*" %%b IN ("tile%%a.jpg") DO set size=%%~zb
if !size! GTR 0 (
echo Size is greater than 0
) ELSE (
)
)
I know this code doesn't make much sense right now, but I'm going to develop it further. I just want to know how to subtract 1 from %%a in the ELSE statement. Basically I want to be able to "redo" a loop number when the IF isn't true, if that makes sense. Thanks.
You can't modify the value of a loop variable. You can only modify the value of an environment variable.
But why using for /L %%a in (0,1,337) do at all?
Better would be for example:
#echo off
for %%A in (tile*.jpg) do (
if %%~zA == 0 (
echo File size of %%A is 0 bytes.
) else (
echo File size of %%A is greater than 0.
)
)
This loop processes simply all tile*.jpg in current directory.
But this loop can't be used if files with 0 bytes are deleted in current directory. Processing the list of tile*.jpg files in current directory and change the files list in the same loop is no good idea because simply not working. The solution is using command DIR to get first the list of all files matching the file name pattern and next process the output of DIR line by line using FOR.
#echo off
setlocal EnableExtensions DisableDelayedExpansion
for /F "delims=" %%A in ('dir /A-D /B /OS tile*.jpg 2^>nul') do (
if %%~zA == 0 (
echo File size of %%A is 0 bytes.
) else (
echo First file with more than 0 bytes is: %%A
goto ExitLoop
)
)
:ExitLoop
endlocal
The command DIR is executed to output the list of files matching the pattern tile*.jpg with ignoring directories which by chance would be matched also by this wildcard pattern because of option /A-D in bare format (only file name) because option /B in order sorted by file size because of option /OS from smallest to largest file.
2^>nul redirects the error message output by command DIR to handle STDERR on not finding any file matching the wildcard pattern to device NUL to suppress this error message. The redirection operator > must be escaped here with caret character ^ to be interpreted as literal character on parsing the FOR command line and interpreted as redirection operator on execution of DIR command line by FOR.
The loop is immediately exited once a file with more than 0 bytes is found as all further files have surely also more than 0 bytes.
One more loop can be used after label ExitLoop which should be renamed to something more suitable in this case for example to renumber the remaining files using command REN when first loop deletes files with 0 bytes.
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 /?
for /?
goto /?
if /?
setlocal /?
See also the Microsoft article Using command redirection operators for an explanation of 2>nul.
You cannot modify the loop variable %%a. Only the loop itself can modify it.
If you want to calculate a new value you can do:
set /A NEW_VALUE=%%a-1
echo %NEW_VALUE% (prove that NewValue is now 1 smaller than %%a)
You cannot modify a for variable reference like %%a, but you can store its value into a standard environment variable (like index) and modify this. For this to work you need to enable and use delayed expansion, because the variable is modified and read within the same block of code, namely the loop body, so read it like !index!; using normal expansion like %index% returned the value present before the loop has even started:
#echo off
setlocal EnableDelayedExpansion
for /L %%a in (0,1,337) do (
set /A "index=%%a-1"
echo %%a - 1 = !index!
)
endlocal
A nice alternative that avoids need of delayed expansion is to use an embedded for /F loop that gets the output of the subtraction and iterates once only per iteration of the surrounding for /L loop, like this:
#echo off
for /L %%a in (0,1,337) do (
for /F %%b in ('set /A "%%a-1"') do (
echo %%a - 1 = %%b
)
)
This works because the for /F loop executes the set /A command in cmd context, in which it returns the resulting value -- in contrast to the aforementioned approach, where set /A is executed in batch-file context, in which it does not output anything.
I have created command script for reading %N% lines from file. The problem is I can't delete " from anywhere in all text streams when I work with file's text. " deletion is very needed because if file's text line have substring like "text" and text have special chars or even worse, script code, then the script crashes or works not proper way (including script control capturing by programmer who specially composed the text).
If I can't delete " from the text stream(s), then I just want to identify, that the file (or it's first %N% lines, including empty lines) contains at least one " char.
Any thoughts are appreciated, including any file preprocessing. But main aim is script speed.
for /f "skip=2 delims=" %%a in ('find /v /n "" "file" 2^>nul') do set "v=%%a"&call :v&if not errorlevel 1 goto FURTHER1
goto FURTHER2
:v
for /f "delims=[]" %%a in ("%v%") do set "line%%a=%v:*]=%"&if %%a lss %N% (exit /b 1) else exit /b 0
#ECHO Off
SETLOCAL
SET "sourcedir=U:\sourcedir"
SET "filename1=%sourcedir%\q39558311.txt"
SET "tempfilename1=%sourcedir%\q39558311#.txt"
>"%tempfilename1%" ECHO("
SET /a linefound=0
FOR /f "tokens=1 delims=:" %%a IN ('findstr /n /g:"%tempfilename1%" "%filename1%"') DO (
IF %%a gtr 2 SET /a linefound=%%a&GOTO report
)
:report
ECHO quote found AT line %linefound%
DEL "%tempfilename1%"
GOTO :EOF
You would need to change the setting of sourcedir and filename1 to suit your circumstances.
tempfile1 can be any name - it's just a temporary file; I chose that particular name for convenience.
I used a file named q39558311.txt containing some dummy data for my testing.
Essentially, create a file containing a single quote on a single line *tempfile1) then use findstr with the /g:filename option to read in the target strings to find. When findstr finds the line, it numbers it and outputs line_number:line found. Using : as a delimiter, token 1 of this line is the line number.
I don't understand why you've used the skip=number in your code. Do you intend to skip testing the first 2 lines of the target file?
the IF %%a gtr 2 tests the line number found. If it is greater than 2, then the variable linefound is set and the for loop is terminated.
I chose to initialise linefound to zero. It will remain zero if no " is found in lines 2..end. Equally, you could clear it and then it will be defined (with a value of first-line-found-with-quote-greater than-2) and no defined on not found.
I can only identify ", but not delete. Waiting for your suggestions on it!
>nul 2>&1 findstr /m \" "file"
if not errorlevel 1 echo double quote found!
I want to nest a for loop inside a batch file to delete carriage return.
I tried it like you can see below but it does not work.
#echo off
setLocal EnableDelayedExpansion
for /f "tokens=* delims= " %%a in (Listfile.txt) do (
set /a N+=1
set v!N!=%%a
)
for /l %%i in (1, 1, %N%) do (
echo !v%%i!
for /r "tokens=* delims=" %%i in (windows.cpp) do (
echo %%i >> Linux11.cpp
)
)
pause
Here I want to check with windows.cpp. If its working I like to change windows .cpp with !v%%i!
You cannot do this in a batch file. You have no way of addressing or writing arbitrary characters. Every tool on Windows normally makes sure to output Windows line breaks (i.e. CR+LF). Some can read Unix-style line breaks just fine, which is why you can easily convert from them. But to them isn't possible.
Also as a word of caution: Source code files often contain blank lines (at least mine do) that are for readability. for /f skips empty lines which is why you're mangling the files for your human readers there. Please don't do that.
As for your question: When nesting two loops you have to make sure that they don't use the same loop variable. Show me a language where code like you wrote actually works.
Something like
for /l %%i in (1, 1, %N%) do (
echo !v%%i!
for /f "tokens=* delims=" %%l in ("!v%%i!") do (
rem do whatever you want to do with the lines
)
)
should probably work better (you missed the final closing parenthesis as well). Thing to remember: If you want to use a certain variable instead of a fixed file name it surely helps replacing that fixed file name by that variable.
It would be probably easiest to use some unix2dos/dos2unix converter to do that or some win32 flavor of sed.
The intrinsic issue of your code is already addressed by another answer, hence I am going to focus on the main task you are trying to accomplish, namely converting DOS/Windows-style end-of-line markers (or line-breaks) to Unix-style ones.
Doing this is very tricky in a batch file, but give the following script a try. Supposing it is called convert.bat, and the original text file is named convert.txt, run the script using the following command line:
convert.bat "convert.txt" LF
The name of the returned file will get the original file name with _converted_EOL appended. The second argument LF specifies Unix-style line-breaks; omitting it will return DOS/Windows-style ones.
So here is the code:
#echo off
setlocal EnableExtensions DisableDelayedExpansion
rem check whether or not an existing file is given as the first argument
>&2 (
if "%~1"=="" (
echo No file specified.
exit /B 2
) else if not exist "%~1" (
echo File "%~1" not found.
exit /B 1
)
)
rem get carriage-return character
for /F %%A in ('copy /Z "%~0" nul') do set "CR=%%A"
rem get line-feed character (the two empty lines afterwards are mandatory!)
(set ^"LF=^
%= blank line =%
^")
rem check which line-break is given by the second argument
rem (`CR` - carriage return (Mac); `LF` - line feed (Unix);
rem anything else or nothing - CR+LF (Windows, default))
setlocal EnableDelayedexpansion
set "BR=!CR!!LF!"
if /I "%~2"=="CR" set "BR=!CR!" & (>&2 echo CR not supported.) & exit /B 3
if /I "%~2"=="LF" set "BR=!LF!"
rem convert line-breaks; append `_converted_EOL` to file name
setlocal DisableDelayedExpansion
> "%~n1_converted_EOL%~x1" (
for /F delims^=^ eol^= %%L in ('
findstr /N /R "^" "%~1"
') do (
set "LINE=%%L"
rem firstly, precede every line with a dummy character (`:`) and
rem append the specified line-break in order to avoid the loss of
rem leading white-spaces or trouble with leading equal-to signs,
rem all caused by `set /P`, which is needed here to return the
rem line without a trailing DOS/Windows-style line-break (opposed
rem to `echo`); then, let `pause` strip off that character;
rem lastly, let `findstr` return the remainder;
rem (the `rem` suffix is just there to fix syntax highlighting)
cmd /V /C ^< nul set /P #="!LINE:*:=:!!BR!" | (> nul pause & findstr "^") & rem/ "^"
)
)
endlocal
endlocal
endlocal
exit /B
The following restrictions apply:
no line must be longer than about 8190 characters (this is a general limitation of batch files);
the file must not contain any null-bytes (well, a normal text file should not hold such, but Unicode-encoded do);
the last line of the returned file will always be terminated by a line-break, even if the respective original line is not;
And here is another solution for line-break conversions: Convert all CR to CRLF in text file using CMD