For loop function for single line in batch? - batch-file

I have a txt file which contains some animal and fruit names. * contain fruits and # contain animal. Just like below:-
* apple
# cat
* banana
# dog
* mango
# lion
* graps
I want to sort down list and write like a table which given below:-
* apple # cat
* banana # dog
* mango # lion
I'm using a for loop for this:
for /f "useback eol=* tokens=1-5 delims= " %%a in ("my text.txt") do (
for /f "useback eol=# tokens=1-5 delims= " %%c in ("my text.txt") do (
Echo %%a %%b
))
But it echoes duplicates.

Please note that tokens are parts of a single line. For the first line, Token1=*, token2=apple, token3 to 5 are not defined. Same for each other line. For your example, tokeins=1,2 would be fine, but I decided to use 1,* to take account of animals or fruits whose names are two (or more) words (like dragon fly)
for /f "usebackq tokens=1,*" %%a in ("my text.txt") do (
if "%%a" == "*" (
<nul set /p "=%%a %%b "
) else (
echo %%a %%b
)
)
echo\
This uses a special trick to write a string without a linefeed in case the line starts with *
(<nul set /p "=string")
Of course, this depends on alternating * and # lines in the source textfile.

This can be done in cmd or a batch-file using the following. If you are on a supported Windows system, PowerShell is available.
This code get the entire file contents into memory, the splits it into an array on the * character. The first, empty, array element is skipped. Then, the NewLine between the * and # lines is replaced with a few SPACE characters.
powershell.exe -NoLogo -NoProfile -Command ^
"(Get-Content -Path '.\fruit-animal.txt' -Raw) -split '\*' | Select-Object -Skip 1 | %{ '* ' + $_ -replace [Environment]::NewLine, ' '}"
Here is the data file and example execution.
C:>TYPE .\fruit-animal.txt
* apple
# cat
* banana
# dog
* mango
# lion
* graps
C:>powershell.exe -NoLogo -NoProfile -Command ^
More? "(Get-Content -Path '.\fruit-animal.txt' -Raw) -split '\*' | Select-Object -Skip 1 | %{ $_ -replace [Environment]::NewLine, ' '}"
apple # cat
banana # dog
mango # lion
graps
This will likely work well for you unless you have a -large- file.
If PowerShell Core http://github.com/PowerShell/Powershell is used (recommended), change it to:
pwsh.exe -NoLogo -NoProfile -Command ^
"(Get-Content -Path '.\fruit-animal.txt' -Raw) -split '\*' | Select-Object -Skip 1 | %{ '* ' + $_ -replace [Environment]::NewLine, ' '}"

The task description, the example input and output data and the posted code do not match at all. So it is really difficult to understand what the task really is.
The first batch file code below is for reading all non-empty lines from a data file and merge each non-empty odd line with each non-empty even line together and finally replace the source data file with the temporary file containing the merged lines.
#echo off
setlocal EnableExtensions DisableDelayedExpansion
set "DataFile=Data.txt"
if not exist "%DataFile%" goto MissingFile
set "TempFile=%DataFile%.tmp"
set "LineOdd="
set "LineEven="
(
for /F usebackq^ delims^=^ eol^= %%I in ("%DataFile%") do (
if defined LineOdd (
set "LineEven=%%I"
setlocal EnableDelayedExpansion
echo(!LineOdd! !LineEven!
endlocal
set "LineOdd="
set "LineEven="
) else set "LineOdd=%%I"
)
if defined LineOdd (
setlocal EnableDelayedExpansion
echo(!LineOdd!
endlocal
set "LineOdd="
)
)>"%TempFile%"
if /I not "%~1" == "test" (move /Y "%TempFile%" "%DataFile%") else type %TempFile%"
del "%TempFile%"
exit /B
:MissingFile
echo ERROR: There is no file "%DataFile%" in "%CD%".
echo/
pause
endlocal
It is possible to run this batch file with the string test as parameter to just output the final result instead of overwriting the source data file.
The file Data.txt contains perhaps the following lines:
* apple
# cat
# dog
* banana
* mango
# lion
* grapes
* pear
* plum
# mouse
# canary
# hamster
* cherry
The result of the batch file above is:
* apple # cat
# dog * banana
* mango # lion
* grapes * pear
* plum # mouse
# canary # hamster
* cherry
The second batch file code below really looks on the identifiers * and # at beginning of the lines to determine if the rest of the line should be on left or on right side in the output file. All lines not starting with * and # are ignored by this code.
#echo off
setlocal EnableExtensions DisableDelayedExpansion
set "DataFile=Data.txt"
if not exist "%DataFile%" goto MissingFile
set "TempFile=%DataFile%.tmp"
set "DataLeft="
set "DataRight="
(
for /F "usebackq tokens=1*" %%I in ("%DataFile%") do (
if "%%I" == "*" (
if not defined DataLeft (
set "DataLeft=%%J"
) else if defined DataRight (
setlocal EnableDelayedExpansion
echo(!DataLeft! !DataRight!
endlocal
set "DataLeft=%%J"
set "DataRight="
) else (
setlocal EnableDelayedExpansion
echo(!DataLeft!
endlocal
set "DataLeft=%%J"
)
) else if "%%I" == "#" (
if not defined DataRight (
set "DataRight=%%J"
) else if defined DataLeft (
setlocal EnableDelayedExpansion
echo(!DataLeft! !DataRight!
endlocal
set "DataRight=%%J"
set "DataLeft="
) else (
setlocal EnableDelayedExpansion
echo( !DataRight!
endlocal
set "DataRIGHT=%%J"
)
)
)
if defined DataLeft (
if defined DataRight (
setlocal EnableDelayedExpansion
echo(!DataLeft! !DataRight!
endlocal
set "DataLeft="
set "DataRight="
) else (
setlocal EnableDelayedExpansion
echo(!DataLeft!
endlocal
set "DataLeft=%%J"
)
)
if defined DataRight (
setlocal EnableDelayedExpansion
echo( !DataRight!
endlocal
set "DataRight="
)
)>"%TempFile%"
if /I not "%~1" == "test" (move /Y "%TempFile%" "%DataFile%") else type "%TempFile%"
del "%TempFile%"
exit /B
:MissingFile
echo ERROR: There is no file "%DataFile%" in "%CD%".
echo/
pause
endlocal
This batch file can be also executed with argument test to just output the final result instead of overwriting the source data file.
The output for the same data file example above is:
apple cat
banana dog
mango lion
grapes
pear
plum mouse
canary
cherry hamster
It can be seen that on left side are the fruits marked with * and on right side the animals marked with # in the data 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.
call /? ... for an explanation of %~1
del /?
echo /?
endlocal /?
exit /?
for /?
goto /?
if /?
move /?
pause /?
set /?
setlocal /?
type /?

It is hard to tell from your specifics what exactly may be the content and layout of your text file, or regarding the output, what exactly you mean by table, and whether empty cells in those tables would be acceptable.
Whilst trying to keep the idea you were using, as much as possible, and based upon the example text file you've submitted, assuming that there will not be two consecutive lines beginning with *, and using a horzontal tab, as a cell separator, line 3 Echo([TAB]%%G, here's a quick example of something which may suit your purposes:
#For /F Delims^=^ EOL^= %%G In ('%SystemRoot%\System32\findstr.exe /BIL "* #"
"S:\ome\Directory\my text.txt" 2^>NUL') Do #(
For /F "EOL=* Delims=" %%H In ("%%G") Do #Echo( %%G
For /F "EOL=# Delims=" %%I In ("%%G") Do #Set /P "=%%G" 0<NUL)
#Echo(& Pause
Example output (with the mis-spelling graps corrected):
* apple # cat
* banana # dog
* mango # lion
* grape
If you didn't want the * and # characters in your resulting output file, i.e.
apple cat
banana dog
mango lion
grape
Then the code would be modified slightly, where line 3 would have Echo([TAB]%%I:
#For /F Delims^=^ EOL^= %%G In ('%SystemRoot%\System32\findstr.exe /BIL "* #"
"S:\ome\Directory\my text.txt" 2^>NUL') Do #(
For /F "EOL=* Tokens=1,*" %%H In ("%%G") Do #Echo( %%I
For /F "EOL=# Tokens=1,*" %%J In ("%%G") Do #Set /P "=%%K" 0<NUL)
#Echo(& Pause

Related

Batch script calculate integer (add) from each row from multiple text files

I have multiple text files contains integer on each row. Can loop for /f calculate (add) from each row of each file sequentially?
Lets say the text files are like this:
File1.txt
123
213
321
File2.txt
111
222
333
File3.txt
333
222
111
Is it possible iterate over multiple files content and sum the like rows IE:
file1 row1 + file2 row1 + file3 row1
file1 row2 + file2 row2 + file3 row2
file1 row3 + file2 row3 + file3 row3
:: operation for 1st row of each file should be:
Set /A calc=123+111+333
echo !calc!
After googling around, I could not find any solution similar to my problem.
Appreciate if anyone can provide insight on this.
Thanks
you can use a single for loop over the output of findstr to build counts from each lines value.
#Echo off & CD /D "%~dp0"
cls
Setlocal
For /f "tokens=1 Delims==" %%g in ('set Line[ 2^> nul')Do Set "%%g=" 2> nul
For /f "tokens=2,3 delims=:" %%i in ('%Systemroot%\System32\Findstr.exe /n /r "^[0123456789]*$" "file*.txt"')Do (
Set /A "Line[%%i]+=%%j+0" 2> nul
)
Set Line[
Endlocal
You can use an array this way:
#echo off
setlocal EnableDelayedExpansion
for %%f in (file*.txt) do (
set "i=1"
for /F %%n in (%%f) do set /A "calc[!i!]+=%%n, i+=1"
)
set /A i-=1
for /L %%i in (1,1,%i%) do echo calc[%%i] = !calc[%%i]!
This batch-file that is run by cmd uses a PowerShell script. It creates a hash item for each line and accumulates the sum for each.
#powershell.exe -NoLogo -NoProfile -Command ^
"$h = #{};" ^
"Get-ChildItem -Filter 'filefile*.txt' |" ^
"ForEach-Object {" ^
"$LineNumber = 0;" ^
"Get-Content -Path $_.FullName |" ^
"ForEach-Object { $h[$LineNumber++] += [int]$_ }" ^
"};" ^
"0..($h.Count-1) | ForEach-Object { $h[$_] }"
This is a lot easier and clearer if written in a .ps1 file.
$h = #{}
Get-ChildItem -Filter 'filefile*.txt' |
ForEach-Object {
$LineNumber = 0
Get-Content -Path $_.FullName |
ForEach-Object { $h[$LineNumber++] += [int]$_ }
}
0..($h.Count-1) | ForEach-Object { $h[$_] }
You may see this get downvoted by someone who thinks PowerShell is not part of cmd. PowerShell is just as much a part of cmd as are find.exe, ipconfig.exe, and setx.exe. PowerShell is available on all supported Windows systems.
You can use findstr in a single for loop and set results sequentially per file:
#echo off & setlocal enabledelayedexpansion
for /f "tokens=1*delims=:" %%i in ('findstr /R "[0-9]" file*.txt') do (
set /a %%~ni+=1 & set /a _Result[!%%~ni!]+=%%j
)
set _Result
Each line per file's lines will be added together, result (based of your current examples:
For a drop single sum files on me approach:-
Summation.bat
#echo off & SETLOCAL ENABLEDELAYEDEXPANSION & Title Summation
for /f "tokens=1* delims=[]" %%A in ('find /n /v "%Title%" "%~1"') do (set "L%%A=%%B" & set "count=%%A")
set calc=0 & for /L %%i in (1,1,!count!) do (set /a calc = !calc! + !L%%i!)
echo/ & echo Line Count = !count! ^& Sum = !calc! & echo/ & pause
Command Line Usage >Summation File1.txt
NOTE as per comment by #T3RROR below
I misread your question as "Provide summation for a file at a time."
HOWEVER have kept it here for others wishing to sum each file.
Good batching, and may the cmd be with you :-)

how get the same keyword in file

I have a list file
MFC_530_18MM_007_F0
MFC_520_18MM_008_F0
MFC_430_18MM_001_F1
MFC_270_18MM_002_F1
MFC_270_18MM_003_F1
MFC_720_18MM_004_F1
MFC_130_18MM_005_F1
MFC_540_18MM_006_F1
MFC_BT580_18MM_007_F1
MFC_530_18MM_008_F1
MFC_MP110_18MM_009_F1
MFC_AAC1_18MM_010_F1
I want to get the same name
ex:
MFC_530_18MM
MFC_520_18MM
MFC_430_18MM
MFC_270_18MM
MFC_270_18MM
MFC_720_18MM
MFC_130_18MM
MFC_540_18MM
MFC_BT580_18MM
MFC_530_18MM
MFC_MP110_18MM
MFC_AAC1_18MM
I have this code:
#echo off
set "R_Str_f=_0.*"
set "R_Str_fed="
setlocal enableDelayedExpansion
(
for /f "tokens=1* delims=:" %%a in ('findstr /n "^" source.txt') do (
set "line=%%b"
if defined line set "line=!line:%R_Str_f%=%R_Str_fed%!"
echo(!line!
)
)> output.txt
I want this code to remove the last character and then remove duplicate keywords but it does not work
not sure which OS you're using.
on Unix/Linux and SHELL you can try to use the "_" as a field separator (FS)
cat yourfile | awk 'FS="_" { print $1"_"$2"_"$3}'
If the requirement is to result in the first three (3) items separated by LOW LINE (underscore) characters, this might work.
#powershell -NoLogo -NoProfile -Command ^
"Get-Content -Path '.\in.txt' | ForEach-Object {($_ -split '_')[0..2] -join '_'}"
Result:
PS C:\src\t\so68080140> .\doit.bat
MFC_530_18MM
MFC_520_18MM
MFC_430_18MM
MFC_270_18MM
MFC_270_18MM
MFC_720_18MM
MFC_130_18MM
MFC_540_18MM
MFC_BT580_18MM
MFC_530_18MM
MFC_MP110_18MM
MFC_AAC1_18MM
Given that your question intent appears to be to replace everything from and including _0 with nothing, does this help?
#Echo Off
SetLocal EnableExtensions DisableDelayedExpansion
( For /F UseBackQ^ Delims^=^ EOL^= %%G In ("source.txt") Do (
Set "LineContent=%%G"
SetLocal EnableDelayedExpansion
For /F UseBackQ^ EOL^=^|^ Delims^=^| %%H In (
'!LineContent:_0^=^|!'
) Do EndLocal & Echo(%%H
)
) 1>"output.txt"
For the above example code to work, I have assumed, and therefore it is a requirement, that your initial strings do not include any | characters.
If you are using Windows 10, and do not mind what order your output file list is in, then you could probably perform the entire task, i.e. with duplicates removed, like this:
#Echo Off
SetLocal EnableExtensions
(
For /F UseBackQ^^^ Delims^^^=^^^ EOL^^^= %%G In ("source.txt") Do #(
Set "LineContent=%%G"
%SystemRoot%\System32\cmd.exe /D /C "Echo(%%LineContent:_0=&:%%"
)
) | %SystemRoot%\System32\sort.exe /Unique 1>"output.txt"

Batch replacing everything after the last underscore character to the beginning of the file extension

So I want to replace this:
Brand_Name_18.jpg
With this
Brand_Name_close-up.jpg
I tried
#echo off &setlocal
for /f "tokens=1-3*delims=_" %%i in ('dir /b /a-d *.jpg ^| find /i /v "_0_"') do ren "%%i_%%j" "%%i_%%j_0_"
but had no success. I also played with the PowerShell and was able to replace some characters there, but not particularly what I want. The characters I wanted to add were appearing at the end of the file extension, which broke everything...
Dir | rename-item -newname { $_.Name + "_close-up }
.
get-childitem -recurse | rename-item -newname { $_.name -replace "_","_close-up" }
I would also like to make the batch appear in the window when I right click, so I could apply it to a file that I need it to be applied on. So that has probably something to do with the registry, right?
SOLUTION thanks to T3RR0R
#echo off & Setlocal EnableDelayedExpansion
REM Change Directory and Remove /R recursive switch as / if necessary.
For /R %%A in (Brand_Name_*.jpg) do (
REN %%~nxA Brand_Name_close-up.jpg
)
pause
This will do the trick. If you're processing a large subset of files, you'll be best off modifying it to rename using a suitable naming convention that doesn't require input.
#echo off & Setlocal EnableDelayedExpansion
Goto :Main
:ModString <string length> <string> <target substring> <prefix VarName> <Substring Length>
Set String=%2
For /L %%L in (%1,-1,1) do (
Set Test=!String:~%%L,%5!
If /I "!Test!"=="%3" (
Set %4=!String:~0,%%L!
Exit /B
)
)
Set /P "prefix=%3 not found. Set prefix:"
Exit /B
:StrLen <resultVar> <string>
(
Setlocal EnableDelayedExpansion
(set^ tmp=%~2)
If Defined tmp (
Set "len=1"
For %%P in (4096 2048 1024 512 256 128 64 32 16 8 4 2 1) Do (
If "!tmp:~%%P,1!" NEQ "" (
Set /a "len+=%%P"
Set "tmp=!tmp:~%%P!"
)
)
) else (
Set "len=0"
)
)
(
ENDLOCAL
Set "%~1=%len%"
exit /b
)
:Main
Set /P "Searchfolder=< Drop & Drag or Enter Parent Folder:"
IF Not Exist "!Searchfolder!" Goto :Main
Set /P "Extension=Extension:"
Set /P "SearchString=Enter String to Locate:"
For %%V in (Extension SearchString) do IF "!%%V!"=="" Goto :Main
For /R %%A in (*!SearchString!*.!Extension!) do (
Call :StrLen substringLen "!SearchString!"
Call :StrLen prefixLen "%%~nA"
Call :ModString !prefixLen! %%~nA !SearchString! prefix !substringLen!
Echo(Rename: %%~nxA
Set /P "Newstring="
REN %%~nxA !prefix!!SearchString!!Newstring!%%~xA
)
CHOICE /N /C ny /M "more N/Y?"
IF errorlevel 2 (
Set Extension=
Set Searchstring=
Goto :main
)
exit
Update
With the only known in your usage being that the underscore is the last instance in the name / string that you wish to retain in the new filename, the function needs to be modified to find it's position in the string using a String Length function in order to Modify the string from that point using Substring Modification.
The StrLen function is the work of Jeb
I've modified the code to now take a bit more input, and be more flexible. You can now use it with other extensions and define the String to search for at runtime.

How to join two text files, removing duplicates, in Windows

file 1
A
B
C
file 2
B
C
D
file1 + file2 =
A
B
C
D
Is it possible to do using cmd.exe?
If you can affort to use a case insensitive comparison, and if you know that none of the lines are longer than 511 bytes (127 for XP), then you can use the following:
#echo off
copy file1.txt merge.txt >nul
findstr /lvxig:file1.txt file2.txt >>merge.txt
type merge.txt
For an explanation of the restrictions, see What are the undocumented features and limitations of the Windows FINDSTR command?.
Using PowerShell:
Get-Content file?.txt | Sort-Object | Get-Unique > result.txt
For cmd.exe:
#echo off
type nul > temp.txt
type nul > result.txt,
copy file1.txt+file2.txt temp.txt
for /f "delims=" %%I in (temp.txt) do findstr /X /C:"%%I" result.txt >NUL ||(echo;%%I)>>result.txt
del temp.txt
First part (merging two text files) is possible. (See Documentation of copy command)
copy file1.txt+file2.txt file1and2.txt
For part 2, you can use sort and uniq utilities from CoreUtils for Windows. This are windows port of the linux utilities.
sort file1and2.txt filesorted.txt
uniq filesorted.txt fileunique.txt
This has a limitation that you will lose track of original sequencing.
Update 1
Windows also ships with a native SORT.EXE.
Update 2
Here is a very simple UNIQ in CMD script
You may also use the same approach of Unix or PowerShell with pure Batch, developing a simple uniq.bat filter program:
#echo off
setlocal EnableDelayedExpansion
set "prevLine="
for /F "delims=" %%a in ('findstr "^"') do (
if "%%a" neq "!prevLine!" (
echo %%a
set "prevLine=%%a"
)
)
EDIT: The program below is a Batch-JScript hybrid version of uniq program, more reliable and faster; copy this program in a file called uniq.bat:
#if (#CodeSection == #Batch) #then
#CScript //nologo //E:JScript "%~F0" & goto :EOF
#end
var line, prevLine = "";
while ( ! WScript.Stdin.AtEndOfStream ) {
line = WScript.Stdin.ReadLine();
if ( line != prevLine ) {
WScript.Stdout.WriteLine(line);
prevLine = line;
}
}
This way, you may use this solution:
(type file1.txt & type file2.txt) | sort | uniq > result.txt
However, in this case the result lost the original order.
The solution below assume that both input files are sorted in ascending order using the same order of IF command's comparison operators and that does not contain empty lines.
#echo off
setlocal EnableDelayedExpansion
set "lastLine=ΓΏ"
for /L %%i in (1,1,10) do set "lastLine=!lastLine!!lastLine!"
< file1.txt (
for /F "delims=" %%a in (file2.txt) do (
set "line2=%%a"
if not defined line1 set /P line1=
if "!line1!" lss "!line2!" call :advanceLine1
if "!line1!" equ "!line2!" (
echo !line1!
set "line1="
) else (
echo !line2!
)
)
)
if "!line1!" neq "%lastLine%" echo !line1!
goto :EOF
:advanceLine1
echo !line1!
set "line1="
set /P line1=
if not defined line1 set "line1=%lastLine%"
if "!line1!" lss "!line2!" goto advanceLine1
exit /B
this joins, sorts and reduce excessive size after PowerShell
Get-Content file?.txt | Sort-Object | Get-Unique | Set-Content -Encoding UTF8 result.txt

New line as a delimeter of FOR loop

I am new to batch script, and I am trying to parse a file that has key value (kind) of pairs delimited by new line. For example:
the value of abc
1234
the value of def
5678
the value of ghi
9876
I would like to read this file using new line as delimited so that I can access the values.
Something like
for /f "tokens=1,2,3 delims='\n'" %%i in ('findstr /C:the value of abc 'filepath') do echo %%j
I just put it here as an option:
for /F delims^=^ eol^= %%i in ('findstr /C:the value of abc 'filepath') do echo %%i
You can't set 'new line' as delimiter. Try this:
#echo off &setlocal
for /f "tokens=1*delims=:" %%i in ('findstr /NC:"the value of abc" "filepath"') do set /a count=%%i
if defined count for /f "skip=%count%tokens=1*delims=:" %%i in ('findstr /N "^" "filepath"') do if not defined value set "value=%%j"
echo.%value%
endlocal
#ECHO OFF
SETLOCAL
SET "prevline="
FOR /f "delims=" %%z IN (tvabc.txt) DO (
IF DEFINED prevline (
FOR /f "tokens=1-6" %%a IN ('echo %%prevline%% %%z') DO (
ECHO %%a %%b %%c %%d %%e %%f
)
SET "prevline="
) ELSE (SET prevline=%%z)
)
should do the job, if you require each token from two successive lines to be output. Would be much easier if we had a listing of the required output rather than having to guess from code that doesn't work.
Essentially, this code saves the contents of one line then detects that the second has been read by the fact that the variable holding the previous line is defined. Under those circumstances, for/f is used to tokenise the concatenation of the two lines.
Not sure what you're going to do with those Name/Value pairs after you get them, but if you're going to do it in Powershell, do it right and start by creating objects:
$file = 'c:\somedir\somefile.txt'
[regex]$regex = #'
(?ms)the value of (\S+)
(\d+)
'#
#Choose one
$text = Get-Content $file -Raw # V3 Only
$text = [IO.File]::ReadAllText($file) # V2 or V3
$regex.matches($text) |
foreach {
New-object PSObject -Property #{
Name = $_.groups[1].value
Value = $_.groups[2].value
}
}|
Select Name,Value |
Format-Table -AutoSize
Name Value
---- -----
abc 1234
def 5678
ghi 9876
What do you think of that? In powershell:
(get-content .\essai.txt)[(Select-String .\essai.txt -Pattern "the value of abc").LineNumber]
It displays the next line after the pattern.
I normally use the given batch command, to read each line in file
FOR /F "delims=" %%G IN (test.txt) DO (
ECHO %%G
)
Here, "delim=" does the trick.

Resources