I'm trying to compare a sequence of 9 numbers (separated by ,) using a batch file.
The comparison is always made by the corresponding sequence like:
mPrevious[0] <-> mCurrent[0]
mPrevious[1] <-> mCurrent[1]
I need to know if at least one sequece have changed. In the example bellow, 234 changed to 230 and 146 to 149.
The sketch I have so far is:
setlocal ENABLEDELAYEDEXPANSION
#echo off
set mPrevious=229,234,235,127,58,0,131,133,146
set mCurrent=229,230,235,127,58,0,131,133,149
for /f "tokens=1,2,3,4,5,6,7,8,9 delims=," %%a IN ('echo !mPrevious!') do (
)
The number of entries (currently 9) might change in the future. But for now they are just 9.
I'm not sure what is the proper way to do it inside a batch script.
#echo off
title <nul && title ...\%~nx0
setlocal enabledelayedexpansion
set "_mPrevious=229,234,235,127,58,0,131,133,146"
set "_mCurrents=229,230,235,127,58,0,131,133,149"
echo/!_mPrevious!|find "!_mCurrents!" >nul && (
endlocal & echo\Nothing changed^!! & goto :EOF )
for %%i in (!_mPrevious!)do set /a "_i+=1+0" && call set "_mPrev_!_i!=%%~i"
for %%j in (!_mCurrents!)do set /a "_j+=1+0" && call set "_mCurr_!_j!=%%~j"
if !_i! neq !_j! endlocal & echo\Varyables have different lengths^!! & goto :EOF
for /L %%L in (1 1 !_j!)do if !_mPrev_%%~L! neq !_mCurr_%%~L! echo\!_mPrev_%%~L! updated to: !_mCurr_%%~L!
endlocal && goto :EOF
Outputs:
234 updated to: 230
146 updated to: 149
One simple way to do this only if necessary and only if both variable has same length:
Make a first comparison if the variables are the same, there was a change in the values:
echo/!_mPrevious!|find "!_mCurrents!" >nul && (
endlocal & echo\Nothing changed^!! & goto :EOF )
And a second if they continue with the same length:
if !_i! neq !_j! endlocal & echo\Variables have different lengths^!! & goto :EOF
Obs.: 1. I prefer replace [ ] to one simple _
Obs.: 2. Also, change i+= to _i+=1+0, where no need predefined set command: set i=0
The FOR token delimiters are: <SPACE> <TAB> <NBSP> , ; =
Therefore, you can put it into a FOR loop, but it would fail if the content contained * or ?.
#echo off
====SETLOCAL EnableDelayedExpansion EnableExtensions
set/a"#=cnt=0"
::Define lists
set "mPrevious=229,234,235,127,58,0,131,133,146"
set "mCurrent=229,230,235,127,58,0,131,133,149"
FOR %%P in (!mPrevious!) do (
FOR %%C in (!mCurrent!) do (
if !cnt! equ !#! echo(%%P %%C
set/a"cnt+=1"
)
set/a"cnt=0,#+=1"
)
This is an approach using some self-expanding code:
#echo off
setlocal EnableDelayedExpansion
rem // Define constants here:
set "mPrevious=229,234,235,127,58,0,131,133,146"
set "mCurrents=229,230,235,127,58,0,131,133,149"
rem // Initialise auxiliary variables and indexes:
set "nPrevious=,%mPrevious%" & set /A "i=0"
set "nCurrents=,%mCurrents%" & set /A "j=0"
rem // Convert lists to arrays using self-expanding code:
set "_=%nPrevious:,=" & set /A "i+=1" & set "nPrevious[!i!]=%"
set "_=%nCurrents:,=" & set /A "j+=1" & set "nCurrents[!j!]=%"
rem // Verify availability of arrays:
> nul 2>&1 set nPrevious[ || set /A "i=0"
> nul 2>&1 set nCurrents[ || set /A "j=0"
rem // Determine minimal and maximal count:
if %j% gtr %i% (set /A "k=i, l=j" & set "_=#") else (set /A "k=j, l=i" & set "_=")
rem // Compare corresponding elements:
for /L %%K in (1,1,%k%) do if !nPrevious[%%K]! neq !nCurrents[%%K]! (
echo [%%K]: !nPrevious[%%K]! -^> !nCurrents[%%K]!
)
rem // Return removed or added elements:
set /A "k+=1" & for /L %%K in (!k!,1,%l%) do if defined _ (
echo [%%K]: --- -^> !nCurrents[%%K]!
) else (
echo [%%K]: !nPrevious[%%K]! -^> ---
)
endlocal
Sample output, relying on the data of the question:
[2]: 234 -> 230
[9]: 146 -> 149
Related
This question already has an answer here:
Split a CSV file into multiple files with header and given number of records
(1 answer)
Closed 1 year ago.
I have a text file. The first line is header. rest of the lines are data. It may contain thousand lines of data. I need to write a batch script which will split the master files into many files each of them contains maximum 500 lines of data and the same header as master file.
Master file
heder
1
2
3
4
.
.
.
.
1501
it will split the file into 4 files. The fourth one will have the header and 1501
You can get a total count of lines in a file using find and calculate the number of files needed by using a for /l loop on the count of the lines in steps of 500
the below approach examples such - in this way files are created with the header and lines output in accordance with the 500 per file using a paired GTR LEQ conditional test.
#Echo off & CD /D "%~dp0"
:# Primary environment has Delayed Expansion Disabled until after assignment of input
:# to retain '!' characters.
Setlocal EnableExtensions DisableDelayedExpansion
Set "line[i]="
Set "files="
:# Get a count of total lines and how many files to make per 500
for /f %%a in ('type "infile.txt"^|find "" /v /c') do Set /A TTL=%%a-1
For /l %%i in (0 500 %TTL%)Do Set /A files+=1
Set /P "Header=" <"infile.txt"
:# Delayed expansion provides protection against poison characters
Setlocal EnableDelayedExpansion
:# Create or Overide each outfile using the assigned input from set /p
For /l %%i in (1 1 %files%)Do >"Outfile%%i.txt" Echo(!Header!
Endlocal
:# Outer for /f loop skips header line; counts each line in.
:# Inner for /l loop iterates over group size with assigned offset 'Stop'
:# Dual GTR LEQ conditional test of Lne[i] used to output in desired group
For /f "Skip=1 Delims=" %%G in (infile.txt)Do (
Set /A "Line[i]+=1"
Set "Line=%%G"
Setlocal EnableDelayedExpansion
Set "file[i]=0"
For /l %%i in (0 500 !TTL!)Do (
Set /A "Stop=%%i+500"
Set /A "file[i]+=1"
If !Line[i]! GTR %%i If !Line[i]! LEQ !Stop! (
Title File: !file[i]! / !files! Line: !Line[i]! / !TTL!
>>"Outfile!file[i]!.txt" Echo(!Line!
)
)
Endlocal
)
Here you go:
#echo off
setlocal EnableDelayedExpansion
set /a maxNoOfLinesPerFile = 500
set /a lineCount = 0
set /a fileCount = 0
set header=""
set outFileNamePrefix=outFile
for /F "tokens=*" %%A in (source.txt) do (
if !lineCount!==0 (
set header=%%A
) else (
set /a n = !lineCount! %% !maxNoOfLinesPerFile!
REM echo !n!
if !n!==1 (
set /a fileCount = fileCount + 1
set outFileName=!outFileNamePrefix!_!fileCount!.txt
echo !outFileName!
echo !header! >> !outFileName!
echo %%A >> !outFileName!
) else (
echo %%A >> !outFileName!
)
)
set /a lineCount = lineCount + 1
)
REM ************************ HIGH SCORES TABLE
**********************************************
:highscorestable
set /a count = 0
for /f "tokens=1,2,3 delims=-" %%i in (highscores.txt) do (
set hs=%%i
set hsn=%%j
set hsv=%%k
set hst=%%jscored %%iusing%%k
set hsn1=!hsn!
set hsv1=!hsv!
set hs1=!hs!
set hsn1= %hsn1%
set hsv1= %hsv1%
set hs1= %hs1%
echo %hsn1:~-15% %hsv1:~-15% %hs1:~-15%
set /a count+=1
if "!count!"=="5" goto :end
)
:end
echo.
pause
I'm pulling the first 5 lines from a text file using a For loop. My variables populate fine, however I'm struggling with the required alignment.
My ultimate end result should be:
James Commitment 300
Markos Excellence 290
Jeremy Si Party 50
What obvious thing am I missing here?
You could try this:
SetLocal EnableDelayedExpansion
REM **************************** HIGH SCORES TABLE ****************************
:highscorestable
Set "count=0"
For /F "UseBackQTokens=1-3Delims=-" %%i In ("highscores.txt") Do (
Set "hs=%%i"
Set "hsn=%%j"
Set "hsv=%%k"
Set "hst=%%jscored %%iusing%%k"
Set "hs= %%i "
Set "hsn1=%%j "
Set "hsv1=%%k "
Echo !hsn1:~,15!!hsv1:~,15!!hs:~-15!
Set/A count+=1
If "!count!"=="5" GoTo :end
)
:end
Echo(
Pause
Or without the possibly unnecessary variables:
SetLocal EnableDelayedExpansion
REM **************************** HIGH SCORES TABLE ****************************
:highscorestable
Set "count=0"
For /F "UseBackQTokens=1-3Delims=-" %%i In ("highscores.txt") Do (
Set "hs= %%i "
Set "hsn=%%j "
Set "hsv=%%k "
Set "hst=%%jscored %%iusing%%k"
Echo !hsn:~,15!!hsv:~,15!!hs:~-15!
Set/A count+=1
If "!count!"=="5" GoTo :end
)
:end
Echo(
Pause
In both cases, I've added the necessary SetLocal EnableDelayedExpansion line just in case it isn't in your script prior to your provided code.
Edit
You can also alter the code a little forego delayed expansion: (my preferred option)
REM **************************** HIGH SCORES TABLE ****************************
:highscorestable
For /F "Tokens=1-4Delims=:-" %%A In ('FindStr/N $ "highscores.txt"'
) Do If %%A LEq 5 (Set "hst=%%Cscored %%Busing%%D"
Set "hss= %%B"
Set "hsn=%%C "
Set "hsv=%%D "
Call Echo %%hsn:~,15%%%%hsv:~,15%%%%hss:~-10%%)
Echo(
Pause
#ECHO OFF
SETLOCAL ENABLEDELAYEDEXPANSION
SET "sourcedir=U:\sourcedir"
SET "filename1=%sourcedir%\q46747991.txt"
REM ************************ HIGH SCORES TABLE
REM **********************************************
:highscorestable
set /a count = 0
SET "manyspaces= "
for /f "tokens=1,2,3 delims=-" %%i in (%filename1%) do (
set hs=%%k&CALL :align hs -8
set hsn=%%i&CALL :align hsn 15
set hsv=%%j&CALL :align hsv 10
ECHO !hsn!!hsv!!hs!
set /a count+=1
if "!count!"=="5" goto end
)
:end
echo.
GOTO :EOF
:align
IF %2 gtr 0 (
CALL SET "%1=%%%1%%%manyspaces%"
CALL SET "%1=%%%1:~0,%2%%"
) ELSE (
CALL SET "%1=%manyspaces%%%%1%%"
CALL SET "%1=%%%1:~%2%%"
)
GOTO :eof
I edited your results for a source file which I named to suit my system, hence the sequence of coulmns is different from your unpublished source. I changed the metavariable-assignment to suit.
The :align routine peels potatoes by recognising the second argument as the required column-width, positive for left-align and negative for right-align.
The variable manyspaces is set to an obvious value, of sufficient length to cope with the widest column required. Obviously, since it won't change once established, it's best set in the very beginning of the batch.
The routine uses the call set %%var%% method so that it will work regardless of whether delayedexpansion is invoked or not.
The mechanics are, for instance
CALL SET "%1=%%%1%%%manyspaces%"
with %1=fred
First, parse the command. %1 is replaced by fred and %% by %, yielding
set
"fred=
%fred%[spaces]"
So appends the space-string to the current value of the environment variable specified as %1
The second set - analyse similarly; result is assigned to the environment variable specified as %1
So the routine can be used to generate a fixed-width string, appropriately aligned using any ordinary variable, even if the variable has a value of nothing (ie. is undefined)
I have to check a tree for duplicating files and write all of them to List.txt file.
But my script seems to skip one of the file locations in each group. (For example, if there are 4 duplicating files, only 3 of them appear in the list.)
If I'm not mistaken, it's the location of the "previousFile" of the last comparison that is missing. How do I write it to the list, too?
Also, how can I group paths in the List.txt by the filename so that it looks something like this:
File fileNameA.txt :
C:\path1\fileNameA.txt
C:\path2\fileNameA.txt
C:\path3\fileNameA.txt
File fileNameB.txt :
C:\path1\fileNameB.txt
C:\path2\fileNameB.txt
C:\path3\fileNameB.txt
C:\path4\fileNameB.txt
File fileNameC.txt :
C:\path1\fileNameC.txt
C:\path2\fileNameC.txt
...
?
That's my script so far:
#echo off
setlocal disableDelayedExpansion
set root=%1
IF EXIST List.txt del /F List.txt
set "prevTest=none"
set "prevFile=none"
for /f "tokens=1-3 delims=:" %%A in (
'"(for /r "%root%" %%F in (*) do #echo %%~zF:%%~fF:)|sort"'
) do (
set "currentTest=%%A"
set "currentFile=%%B:%%C"
setlocal enableDelayedExpansion
set "match="
if !currentTest! equ !previousTest! fc /b "!previousFile!" "!currentFile!" >nul && set match=1
if defined match (
echo File "!currentFile!" >> List.txt
endlocal
) else (
endlocal
set "previousTest=%%A"
set "previousFile=%%B:%%C"
)
)
You need to count matches and add echo previous filename to echo current one in case of the first match.
Note '"(for /r "%root%" %%F in (*) do #echo(%%~nxF?%%~zF?%%~fF?)|sort"' changes:
used ? (question mark) as a delimiter: reserved character by Naming Files, Paths, and Namespaces
added %%~nxF? prefix to sort output properly by file names even in my sloppy test folder structure, see sample output below.
This output shows than even cmd poisonous characters (like &, %, ! etc.) in file names are handled properly with DisableDelayedExpansion kept.
#ECHO OFF
SETLOCAL EnableExtensions DisableDelayedExpansion
set "root=%~1"
if not defined root set "root=%CD%"
set "previousTest="
set "previousFile="
set "previousName="
set "match=0"
for /f "tokens=1-3 delims=?" %%A in (
'"(for /r "%root%" %%F in (*) do #echo(%%~nxF?%%~zF?%%~fF?x)|sort"'
) do (
set "currentName=%%A"
set "currentTest=%%B"
set "currentFile=%%C"
Call :CompareFiles
)
ENDLOCAL
goto :eof
:CompareFiles
if /I "%currentName%" equ "%previousName%" ( set /A "match+=1" ) else ( set "match=0" )
if %match% GEQ 1 (
if %match% EQU 1 echo FILE "%previousFile%" %previousTest%
echo "%currentFile%" %currentTest%
) else (
set "previousName=%currentName%"
set "previousTest=%currentTest%"
set "previousFile=%currentFile%"
)
goto :eof
Above script lists all files of duplicated names regardless of their size and content. Sample output:
FILE "d:\bat\cliPars\cliParser.bat" 1078
"d:\bat\files\cliparser.bat" 12303
"d:\bat\Unusual Names\cliparser.bat" 12405
"d:\bat\cliparser.bat" 335
FILE "d:\bat\Stack33721424\BÄaá^ cčD%OS%Ď%%OS%%(%1!)&°~%%G!^%~2.foo~bar.txt" 120
"d:\bat\Unusual Names\BÄaá^ cčD%OS%Ď%%OS%%(%1!)&°~%%G!^%~2.foo~bar.txt" 120
To list all files of duplicated names with the same size but regardless of their content:
:CompareFiles
REM if /I "%currentName%" equ "%previousName%" (
if /I "%currentTest%%currentName%" equ "%previousTest%%previousName%" (
set /A "match+=1"
REM fc /b "%previousFile%" "%currentFile%" >nul && set /A "match+=1"
) else ( set "match=0" )
To list all files of duplicated names with the same size and binary content:
:CompareFiles
REM if /I "%currentName%" equ "%previousName%" (
if /I "%currentTest%%currentName%" equ "%previousTest%%previousName%" (
REM set /A "match+=1"
fc /b "%previousFile%" "%currentFile%" >nul && set /A "match+=1"
) else ( set "match=0" )
Edit If the name of the file doesn't matter (only its contents), you could apply next changes in FOR loop and in :CompareFiles subroutine:
#ECHO OFF
SETLOCAL EnableExtensions DisableDelayedExpansion
set "root=%~1"
if not defined root set "root=%CD%"
set "previousTest="
set "previousFile="
set "match=0"
for /f "tokens=1-2 delims=?" %%A in (
'"(for /r "%root%" %%F in (*) do #echo(%%~zF?%%~fF?)|sort"'
) do (
set "currentTest=%%A"
set "currentFile=%%B"
rem optional: skip all files of zero length
if %%A GTR 0 Call :CompareFiles
)
ENDLOCAL
goto :eof
:CompareFiles
if /I "%currentTest%" equ "%previousTest%" (
fc /b "%previousFile%" "%currentFile%" >nul && set /A "match+=1"
) else ( set "match=0" )
if %match% GEQ 1 (
if %match% EQU 1 echo FILE "%previousFile%" %previousTest%
echo "%currentFile%" %currentTest%
) else (
set "previousTest=%currentTest%"
set "previousFile=%currentFile%"
)
goto :eof
I have to make a script that has to calculate the mask and the net, so I'm trying a script with for but it can not convert the IP to binary. I think I'm not using the variables right.
Any ideas?
#echo off
setlocal enabledelayedexpansion
set var=%1
set /p var=Introduce la ip:
for /F "tokens=1 delims=." %%a in ("%var%") do (
echo %%a
set "vara=%%a"
:binario
set bin=2
set /a resto=%vara%%%bin%
set /a a=%vara%/%bin%
set resultado=%resto%%resultado%
if %vara% GTR 0 (goto binario)
echo %resultado%
goto siguiente
)
:siguiente
for /F "tokens=2 delims=." %%b in ("%var%") do (
echo %%b
)
for /F "tokens=3 delims=." %%c in ("%var%") do (
echo %%c
)
for /F "tokens=4 delims=." %%d in ("%var%") do (
echo %%d
)
goto fin
:vacio
echo Error!
goto fin
:fin
pause
You've got a few minor problems that I see. You set var=%1 but you never check to see whether %1 was supplied before doing set /p var=Enter an IP:. You never call or goto :vacio. As I commented above, modulos within batch scripts need to be written as %% to prevent evaluation as variable chararacters. You don't need % in var names in set /a commands, and you can combine multiple set /a statements with a comma. So instead of
set /a resto=%vara%%%bin%
set /a a=%vara%/%bin%
(which is wrong anyway -- I'll get to that in a minute), I suggest this would be more understandable and maintainable:
set /a resto = vara %% bin, numero = vara / bin
The biggest problem is that you appear to be trying to modify %%a. Don't do that.
If I were you, I would move the decimal to binary conversion to a subroutine, and call it for each octet. Try this:
#echo off
setlocal enabledelayedexpansion
set IP=%1
if "%IP%"=="" set /p "IP=Introduce la ip: "
set idx=0
for %%a in (%IP:.= %) do (
if %%a lss 0 goto vacio
if %%a gtr 255 goto vacio
if !idx! gtr 3 goto vacio
set /P "=%%a = "<NUL
call :dec2bin bin[!idx!] %%a
set /a idx += 1
)
echo %bin[0]%.%bin[1]%.%bin[2]%.%bin[3]%
goto fin
:dec2bin <var_para_definir> <numero>
setlocal enabledelayedexpansion
set numero=%~2
set bin=
for /L %%I in (1,1,8) do (
set /a bit = numero %% 2, numero /= 2
set bin=!bit!!bin!
)
echo %bin%
endlocal & set "%~1=%bin%"
goto :EOF
:vacio
echo Error!
goto fin
:fin
pause
For more information about using call as a function that returns a value, see this page.
in Windows Batch, if I had a variable (length can change) that was, for example: "hello world!"
is it possible to "split" the variable so each character is its own variable so the output could look like:
t1=h
t2=e
t3=l
etc.
Any help would be appreciated.
Use this code:
setlocal EnableDelayedExpansion
set str="hello world^!"
set tempstr=%str%
set count=0
:loop
if defined tempstr (
set tempstr=%tempstr:~1%
set /a count+=1
set /a pos=%count%-1
set t!count!=!str:~%pos%,1!
goto loop
)
:: check the generated variables
set t
To get the nth character in a string, use set char=%str:~n,1%.
I hope this was helpful!
Here is a variant using for /L to iterate over all characters of the string. The number of characters, so the length of the string, is retrieved in sub-routine :LENGTH first:
#echo off
setlocal EnableExtensions DisableDelayedExpansion
rem // Define constants here:
set "_STRING=Hello world!"
call :LENGTH LEN "%_STRING%"
setlocal EnableDelayedExpansion
for /L %%I in (0,1,%LEN%) do (
set "$CHR[%%I]=!_STRING:~%%I,1!"
)
set $CHR
endlocal
endlocal
exit /B
:LENGTH rtn_length val_string
setlocal DisableDelayedExpansion
set /A "RET=0" & set "STR=%~2"
if defined STR set /A "RET=1"
setlocal EnableDelayedExpansion
for %%L in (4096 2048 1024 512 256 128 64 32 16 8 4 2 1) do (
if not "!STR:~%%L,1!"=="" (
set /A "RET+=%%L"
set "STR=!STR:~%%L!"
)
)
(
endlocal
endlocal
set "%~1=%RET%"
)
exit /B
Here is a different variant, using cmd /U to convert the string into Unicode, where a null-byte becomers inserted behind every character, and find, which treats these null-bytes like end-of-line markers:
#echo off
setlocal EnableExtensions DisableDelayedExpansion
rem // Define constants here:
set "_STRING=Hello world!"
set /A "IDX=0"
for /F delims^=^ eol^= %%I in ('
cmd /U /V /C echo^(!_STRING!^| find /V ""
') do (
set "CHR=%%I"
setlocal EnableDelayedExpansion
for /F "delims=" %%J in ("$CHR[!IDX!]=!CHR!") do (
endlocal
set "%%J"
)
set /A "IDX+=1"
)
set $CHR
endlocal
exit /B
Finally, here is another variant, based on a goto loop. This uses a position pointer POS to scan the string and to extract a single character. If no character is returned, the end of the string is reached:
#echo off
setlocal EnableExtensions DisableDelayedExpansion
rem // Define constants here:
set "_STRING=Hello world!"
setlocal EnableDelayedExpansion
if not defined _STRING goto :QUIT
set /A "POS=0"
:LOOP
set "CHR=!_STRING:~%POS%,1!"
if defined CHR (
set "$CHR[%POS%]=!CHR!"
set /A "POS+=1"
goto :LOOP
)
:QUIT
set $CHR
endlocal
endlocal
exit /B
Old thread, but accepted answer misses letter d?
Changed
set /a pos=%count%-1 to
set /a pos=!count!-1
setlocal EnableDelayedExpansion
set str="hello world!"
set tempstr=%str%
set count=0
:loop
if defined tempstr (
set tempstr=%tempstr:~1%
set /a count+=1
set /a pos=!count!-1
set t!count!=!str:~%pos%,1!
goto loop
)
:: check the generated variables
set t
pause
Here is another version.
variables start with 0, though
setlocal EnableDelayedExpansion
set var="hello world!"
rem remove quotes
set var=%var:"=%
rem add limiter
set var=%var%_
echo %var%
set count=0
:loop
set tempvar=!var:~%count%,1!
if !tempvar!==_ goto skip
set arr!count!=!tempvar!
set /a count=!count!+1
goto loop
:skip
echo out
echo display output
set arr
pause