Sorry I can't word this correctly but I need a command to create a text file that dumps the file names in numeric order. Here is the command that does what I want but everything that has 1 at the start is bunched, 2, 3 etc.....
(for %i in (*.flv) do #echo file '%i') > file.txt
Most cmd/batch file commands, like dir, sort and set, for instance, do pure alphabetic sorting, so any numeric parts are not treated particularly. For example, string12 appears before string3, because the character 1 appears before 2 with the used way of sorting. To change the behaviour so that alpha-numeric sorting is applied, meaning that string3 appears before string12, you have to write your own code for accomplishing that. The following script does exactly that, by padding every numeric part appearing in strings/file names to a fixed amount of digits, in which case alpha-numeric and alphabetic sort orders match. Below is the code, including explanatory remarks. As you can see, complex code is required to accomplish the task and to make the script secure against all characters that have special meanings to cmd (like SPACE, ,, ;. = as well as ^, &, (, ), %, !):
#echo off
setlocal EnableExtensions DisableDelayedExpansion
rem // Define constants here:
set "_TEMPFILE=%TEMP%\%~n0_%RANDOM%.tmp" & rem // (temporary file used for sorting)
set /A "_DIGITS=12" & rem // (number of digits every numeric part is padded for sorting)
rem // Build string containing enough zeros for padding:
set "$PAD=" & setlocal EnableDelayedExpansion
for /L %%D in (1,1,%_DIGITS%) do set "$PAD=!$PAD!0"
endlocal & set "$PAD=%$PAD%"
rem // Prepare temporary file containing data to sort:
> "%_TEMPFILE%" (
rem // Gather and resolve all command line arguments:
for %%I in (%*) do (
rem // Get pure file name:
set "ITEM=%%~nxI"
rem // Extend all numeric parts in file name to certain number of digits:
call :PROCESS AUGM ITEM || >&2 echo ERROR: potential problem sorting "%%~nxI"!
rem // Write extended and original file name into temporary file:
setlocal EnableDelayedExpansion
echo(!AUGM!^|!ITEM!
endlocal
)
)
rem // Return content of temporary file in ascendingly sorted order:
for /F "tokens=2 delims=| eol=|" %%J in ('sort "%_TEMPFILE%"') do (
rem // Simply return each item:
echo(%%J
)
rem // Delete temporary file:
del "%_TEMPFILE%"
endlocal
exit /B
:PROCESS rtn_augmented_string ref_string
rem /* Routine to augment a string so that every numeric part is padded with leading
rem zeros to the left to hold a predefined number of digits: */
setlocal DisableDelayedExpansion
set "#RTN=%~1"
set "#ARG=%~2"
rem // Initialise required variables:
set "COLL="
set "ERRL=0"
setlocal EnableDelayedExpansion
for /F delims^=^ eol^= %%B in (^""!%#ARG%!"^") do (
endlocal
set "PSTR=%%~B"
setlocal EnableDelayedExpansion
)
rem // Entry point for loop to handle one numeric string part:
:REPEAT
rem // Extract the string portions before and after the first numeric part:
for /F "tokens=1,* delims=0123456789 eol=0" %%A in ("+!PSTR!") do (
endlocal
set "PART=%%A"
set "NEXT=%%B"
rem // Determine length of string portion before first numeric part:
call :LENGTH PLEN PART
set /A "PLEN-=1"
setlocal EnableDelayedExpansion
set "PART=!PART:~1!"
rem // Split off string portion before first numeric part from total string:
for %%C in (!PLEN!) do (
if defined PSTR set "PSTR=!PSTR:~%%C!"
)
rem /* Splitt off string portion after first numeric part from remaining string;
rem this is nothing but extracting the first numeric part itself: */
call :SPLIT PNUM NEXT PSTR
rem // Determine the actual length of the numeric part:
call :LENGTH NLEN PNUM
rem // Do the actual padding with leading zeros of the numeric part:
if defined PNUM (
set "PNUM=%$PAD%!PNUM!"
set "PNUM=!PNUM:~-%_DIGITS%!"
)
rem // Store the part after the first numeric part:
for /F delims^=^ eol^= %%C in (^""!NEXT!"^") do (
rem /* Build string with the string portion before the current numeric part
rem and the padded current numeric part itself: */
for /F delims^=^ eol^= %%D in (^""!COLL!!PART!!PNUM!"^") do (
rem // Check whether the predefined number of padding digits is sufficient:
for /F %%E in ("!NLEN!") do (
endlocal
set "PSTR=%%~C"
set "COLL=%%~D"
if %%E GTR %_DIGITS% set "ERRL=1"
setlocal EnableDelayedExpansion
)
)
)
)
rem // Repeat the whole approach while there is still a remaining string portion:
if defined PSTR goto :REPEAT
rem // Return the string with padded numeric parts:
for /F delims^=^ eol^= %%R in (^""!COLL!"^") do (
endlocal
endlocal
set "%#RTN%=%%~R"
exit /B %ERRL%
)
exit /B
:SPLIT rtn_left_string ref_split_char val_string
rem /* Routine to split a string at the first occurrence of a certain character and to
rem return the portion before it: */
setlocal DisableDelayedExpansion
set "#RTN=%~1"
set "#CHR=%~2"
set "#ARG=%~3"
rem // Initialise required variables:
setlocal EnableDelayedExpansion
set "CHAR= " & if defined %#CHR% set "CHAR=!%#CHR%:~,1!"
if "!CHAR!"=="<" (set "PREF=>") else (set "PREF=<")
rem // Check whether a split character is defined:
if defined %#CHR% (
rem /* Split character available, so split off first occurrence and everything after
rem from the original string: */
for /F eol^=^%CHAR%^ delims^=^%CHAR% %%C in ("%PREF%!%#ARG%!") do (
endlocal
set "%#RTN%=%%C"
setlocal EnableDelayedExpansion
set "%#RTN%=!%#RTN%:~1!"
)
) else (
rem // No split character defined, so do not split off anything:
set "%#RTN%=!%#ARG%!"
)
rem // Return the resulting string:
for /F delims^=^ eol^= %%R in (^""!%#RTN%!"^") do (
endlocal
endlocal
set "%#RTN%=%%~R"
)
exit /B
:LENGTH rtn_length ref_string
rem /* Routine to determine the length of a given string: */
setlocal DisableDelayedExpansion
set "#RTN=%~1"
set "#ARG=%~2"
setlocal EnableDelayedExpansion
rem // Check whether a string is provided:
if defined %#ARG% (
rem // String is available, so calculate its length:
set /A "%#RTN%=1"
for %%A in (4096 2048 1024 512 256 128 64 32 16 8 4 2 1) do (
if not "!%#ARG%:~%%A!"=="" (
set /A "%#RTN%+=%%A"
set "%#ARG%=!%#ARG%:~%%A!"
)
)
) else (
rem // String is empty, so length is zero:
set /A "%#RTN%=0"
)
rem // Return the computed length:
for /F %%R in ("!%#RTN%!") do (
endlocal
endlocal
set "%#RTN%=%%R"
)
exit /B
Provide (a) file pattern(s) as (a) command line argument(s), like this, for example (supposing the batch file is saved as sort-alpha-num.bat):
sort-alpha-num.bat "*.flv"
To store the resulting sorted list in a text file called file.txt, use this:
sort-alpha-num.bat "*.flv" > "file.txt"
In case an error message like ERROR: potential problem sorting "1000000000000.flv"! appears, increase the number of digits on top of the script (see line set /A "_DIGITS=12").
Since you didn't give us more information about how your output file should be ?
#echo off
set "folder=%userprofile%\Desktop\*.flv"
set "listfiles=listfiles.txt"
If Exist %listfiles% Del %listfiles%
setLocal EnableDelayedExpansion
Rem Populate the array with existent files in this folder
for /f "tokens=* delims= " %%a in ('Dir /s /b /a:-d "%folder%"') do (
set /a Count+=1
set "File[!Count!]=%%~na"
)
::******************************************************************
:Display_Files
cls & color 0B
echo(
For /L %%i in (1,1,%Count%) do (
echo %%i - !File[%%i]!
)
echo(
(
rem to save result into logfile
For /L %%i in (1,1,%Count%) do (
echo %%i - !File[%%i]!
)
)>> %listfiles%
echo Hit any key to open %listfiles% :
Pause>nul & Start "" %listfiles%
Related
I am writing a batch script that I need to parse text out of a .csv and have ran into a roadblock:
I have a for-loop set up to grab data from each line (this works fine) but I end up needing a value that is separated by multiple lines. For example (I placed what I want to be considered a single entry in parenthesis for context):
(data I need,flag_for_which_process_to_run,dontcare,"data I need
data continued
data continued
this could continue for any number of lines",dontcare,dontcare,dontcare,dontcare)
(repeat)
Is there any way to get a batch script to parse this out without breaking the for loop? If it's helpful, the data in %%d is encased in double quotes. Code is below, the section I am referring to is the second if inside the for loop.
SETLOCAL EnableDelayedExpansion
for /f "tokens=1,2,3,4 delims=," %%a in (sample.csv) do (
REM Skip if %%b is not flag1
if "%%b"=="flag1" (
.
.
.
)
REM Skip if %%b is not otherflag
if "%%b"=="otherflag" (
REM Set the %%a variable
set device=%%a
echo "%%d"> output\tmp\temp.txt
)
)
Given that the first three tokens/values are unquoted (so they cannot contain quotation marks or commas on their own) and the whole CSV file does not contain escape or back-space characters, the following script, when the CSV file is provided as a command line argument, should extract the values you are interested in (it just echoes them out):
#echo off
setlocal EnableExtensions DisableDelayedExpansion
rem // Define constants here:
set "_FILE=%~1" & rem // (CSV file; `%~1` is first command line argument)
rem // Get carriage-return character:
for /F %%C in ('copy /Z "%~0" nul') do set "_CR=%%C"
rem // Get line-feed character:
(set ^"_LF=^
%= blank line =%
^")
rem // Get escape and back-space characters:
for /F "tokens=1,2" %%E in ('prompt $E$S$H ^& for %%Z in ^(.^) do rem/') do set "_ESC=%%E" & set "_BS=%%F"
set "CONT="
rem // Read CSV file line by line:
for /F usebackq^ delims^=^ eol^= %%L in ("%_FILE%") do (
rem // Branch for normal lines:
if not defined CONT (
rem // Get relevant tokens/values:
for /F "tokens=1-3* delims=, eol=," %%A in ("%%L") do (
set "DEVICE=%%A" & set "FLAG=%%B" & set "LINE=%%D"
if not "%%D"=="%%~D" (
rem // Fourth token begins with a `"`, hence remove it and enter branch for continued lines then:
for /F delims^=^ eol^= %%E in ("%%D"^") do set "LINE=%%~E"
set "DATA=" & set "CONT=#"
) else (
rem // Fourth token does not begin with a '"', hence it cannot be continued:
for /F "delims=, eol=," %%E in ("%%D") do (
rem // Do something with the data, like echoing:
echo/
echo FLAG=%%B
echo DEVICE=%%A
echo DATA=%%E
)
)
)
) else set "LINE=%%L"
rem // Branch for continued lines:
if defined CONT (
setlocal EnableDelayedExpansion
rem // Temporarily replace escaped (doubled) `"` with back-space character:
set "LINE=!LINE:""=%_BS%!"
rem // Collect continued data with line-breaks replaced by escape characters:
for /F delims^=^"^ eol^=^" %%D in ("!DATA!%_ESC%!LINE!") do endlocal & set "DATA=%%D"
setlocal EnableDelayedExpansion
if not "!LINE!"=="!LINE:"=!^" (
rem /* There is a single `"` (plus a `,`), which is taken as the end of the continued fourth token;
rem hence replacing back line-breaks and (unescaped) `"`: */
set "DATA=!DATA:*%_ESC%=!" & set "DATA=!DATA:%_BS%="!^"
for %%E in ("!_CR!!_LF!") do set "DATA=!DATA:%_ESC%=%%~E!"
rem // Do something with the data, like echoing:
echo/
echo FLAG=!FLAG!
echo DEVICE=!DEVICE!
echo DATA=!DATA!
endlocal
set "CONT="
) else endlocal
)
)
endlocal
exit /B
How to processing only specific delimiter when string have same characters with delimiter?.
Sample.bat
#echo off & Setlocal EnableDelayedExpansion
:: Load Variable From data*.txt
set /a Line=0
for /f "delims=" %%a in (data*.txt) do (
set /A Line+=1
for /f "tokens=1,2 delims=_" %%b in ("%%a") do (
set "data_in!Line!=%%b" & set "data_out!Line!=%%c"
)
)
set data
pause
data1.txt :
a.pnh_111
bb.pop_222
c c.oiu_333
data2.txt :
_dd.pnh_444
e_e.pop_555
ff_.oiu_666
Output from data1.txt
data_in1=a.pnh
data_in2=bb.pop
data_in3=c c.oiu
data_out1=111
data_out2=222
data_out3=333
IT WORKED PERFECTLY at data1.txt
But the output from data2.txt NOT WORKING AS EXPECTED because it have 2 character "_"
Output from data2.txt what i want:
data_in1=_dd.pnh
data_in2=e_e.pop
data_in3=ff_.oiu
data_out1=444
data_out2=555
data_out3=666
when i replaced separator "_" to "(separator)" at all data*.txt like:
data1.txt :
a_a.pnh(separator)111
abb.pop(separator)222
ccc.oiu(separator)333
and change delimiter to:
/f "tokens=1,2 delims=(separator)" %%b in ("%%a") do (
Of course it not working.
Notes :
data*.txt written by another program. Currently by using character "_" as separator but i can change it to another character ( i cannot predicted what the output will be written into data.txt .maybe it have same character with separator i use)
With changed files
data1.txt :
a.pnh_1
bb.pop_22
c c.oiu_333
data2.txt :
_dd.pnh_4444
e_e.pop_55555
ff_.oiu_666666
This batch
:: Q:\Test\2018\05\03\SO_50163726_2.cmd
#echo off & Setlocal
:: Load Variable From data*.txt
set Cnt=0
for %%A in (data*.txt) do for /f "delims=" %%B in (%%A) DO Call :ProcLine %%B
set data
pause
goto :Eof
:ProcLine
set /A Cnt+=1
set "Line=%~1"
Call :GetNum %Line:_= %
Rem Echo Num=%Num%
Call Set "Line=%%Line:_%Num%=%%"
set "data_in%Cnt%=%Line%"
set "data_out%Cnt%=%Num%"
Goto :Eof
:GetNum
if "%~2" neq "" (shift&goto :GetNum)
Set Num=%1
Goto :Eof
will produce this output:
data_in1=a.pnh
data_in2=bb.pop
data_in3=c c.oiu
data_in4=_dd.pnh
data_in5=e_e.pop
data_in6=ff_.oiu
data_out1=1
data_out2=22
data_out3=333
data_out4=4444
data_out5=55555
data_out6=666666
What about using a standard for loop to split each line string at every _ symbol? When you enclose the string in between "" and replace every _ by " ", you get partial strings enclosed in between ""; so for instance, e_e.pop_555 becomes "e" "e.pop" "555". Hence you can loop through them and reassemble them in a new variable and dismiss the last item, thus getting e_e.pop and 555. This is faster than goto or call, because for loops are cached in memory.
Here is an example code:
#echo off
setlocal EnableExtensions DisableDelayedExpansion
rem // Define constants here:
set "_FILE=%~1" & rem // (input file; use first command line argument)
rem // Initialise counter:
set /A "COUNT=0"
rem // Read input file line by line, ignoring empty lines:
for /F usebackq^ delims^=^ eol^= %%L in ("%_FILE%") do (
rem // Store current line:
set "LINE=%%L"
rem // Increment counter:
set /A "COUNT+=1"
rem // Initialise interim variables:
set "COLL=" & set "ITEM="
rem // Toggle delayed expansion to avoid loss of `!`:
setlocal EnableDelayedExpansion
rem /* Split line at every `_` and loop through items
rem (`?`, `*`, `<`, `>` and `"` must not occur): */
for %%I in ("!LINE:_=" "!") do (
rem /* Append previous item to variable; use `for /F`
rem to transport value beyond `endlocal` barrier: */
for /F "delims=" %%K in ("COLL=!COLL!_!ITEM!") do (
endlocal
set "%%K"
)
rem // Store current item for next iteration, remove `""`:
set "ITEM=%%~I"
setlocal EnableDelayedExpansion
)
rem /* Store appended string to `data_in` variable, then
rem store last item to `data_out` variable; use `for /F`
rem to transport value beyond `endlocal` barrier: */
for /F "delims=" %%I in ("data_in!COUNT!=!COLL:~2!") do (
for /F "delims=" %%J in ("data_out!COUNT!=!ITEM!") do (
endlocal
set "%%I" & set "%%J"
)
)
)
rem // Return stored data:
set data_
endlocal
exit /B
This approach does not lose any exclamation marks (!) in the input strings or cause other trouble with them. However, the following characters are not allowed: ?, *, <, > and ".
Given there are no exclamation marks (!) in the data files, the script can be simplified to this:
#echo off
setlocal EnableExtensions DisableDelayedExpansion
rem // Define constants here:
set "_FILE=%~1" & rem // (input file; use first command line argument)
setlocal EnableDelayedExpansion
rem // Initialise counter:
set /A "COUNT=0"
rem // Read input file line by line, ignoring empty lines:
for /F usebackq^ delims^=^ eol^= %%L in ("!_FILE!") do (
rem // Store current line:
set "LINE=%%L"
rem // Increment counter:
set /A "COUNT+=1"
rem // Initialise interim variables:
set "COLL=" & set "ITEM="
rem /* Split line at every `_` and loop through items
rem (`?`, `*`, `<`, `>` and `"` must not occur): */
for %%I in ("!LINE:_=" "!") do (
rem // Append previous item to variable:
set "COLL=!COLL!_!ITEM!"
rem // Store current item for next iteration, remove `""`:
set "ITEM=%%~I"
)
rem /* Store appended string to `data_in` variable, then
rem store last item to `data_out` variable: */
set "data_in!COUNT!=!COLL:~2!" & set "data_out!COUNT!=!ITEM!"
)
rem // Return stored data:
set data_
endlocal
endlocal
exit /B
Here is a totally different approach based on a nice hack that I already used in another answer:
#echo off
setlocal EnableExtensions DisableDelayedExpansion
rem // Define constants here:
set "_FILE=%~1" & rem // (input file; use first command line argument)
setlocal EnableDelayedExpansion
rem // Initialise counter:
set /A "COUNT=0"
rem // Read input file line by line, ignoring empty lines:
for /F usebackq^ delims^=^ eol^= %%L in ("!_FILE!") do (
rem // Increment counter:
set /A "COUNT+=1"
rem /* Split current line at last `_`, then store the string before to
rem `data_in` variable and the string after to `data_out` variable: */
call :GET_LAST_ITEM data_out!COUNT! data_in!COUNT! "%%L"
)
rem // Return stored data:
set data_
endlocal
endlocal
exit /B
:GET_LAST_ITEM rtn_last rtn_without_last val_string
::This function splits off the last `_`-separated item of a string.
::Note that `!`, `^` and `"` must not occur within the given string.
::PARAMETERS:
:: rtn_last variable to receive the last item
:: rtn_without_last variable to receive the remaining string
:: val_string original string
setlocal EnableDelayedExpansion
set "STR=_%~3"
set "PRE=" & set "END=%STR:_=" & set "PRE=!PRE!_!END!" & set "END=%"
endlocal & set "%~1=%END%" & set "%~2=%PRE:~2%"
exit /B
The following characters are not allowed for this: !, ^ and ".
I have 2 CSV files that has
File1:
Column1,column2
data1, data2
File2:
Column3,column4, column5,column6
data3,data4,data5,data6
I have to create a new CSV file that combines both columns from file 1 with the 1st and 3rd columns from file 2 (4 total columns).
Column1,column2,column3,column5
data1,data2,data3,data5
I am looking to do this using batch file commands. Any suggestions?
Code i am using helps me copy one file.
#ECHO OFF
SETLOCAL
(
FOR /f "tokens=1-3delims=," %%a IN (file1.csv) DO (
ECHO(%%a,%%c
)
)>new.csv
GOTO :EOF
How about following script?
File1.csv :
column1,column2
data1,data2
data3,data4
data5,data6
File2.csv :
column3,column4,column5,column6
data3,data4,data5,data6
data7,data8,data9,data10
Script :
#echo off
setlocal enabledelayedexpansion
set ct1=0
for /f "tokens=*" %%i in (File1.csv) do (
set /a ct1+=1
set ar1[!ct1!]=%%i
)
set ct2=0
for /f "tokens=*" %%i in (File2.csv) do (
set /a ct2+=1
set ar2[!ct2!]=%%i
)
if !ct1! lss !ct2! (
set ct=!ct2!
) else (
set ct=!ct1!
)
for /l %%i in (1,1,!ct!) do (
echo !ar1[%%i]!,!ar2[%%i]!>> new.csv
)
new.csv :
column1,column2,column3,column4,column5,column6
data1,data2,data3,data4,data5,data6
data3,data4,data7,data8,data9,data10
data5,data6,
Here is a pure batch solution that works, but with the following limitations and or assumptions:
File 1 lines are terminated by carriage return and linefeed (Windows style)
File 1 lines are no longer than 1021 bytes
File 2 must have a value for each column (no consecutive commas)
File 2 line lengths never exceed ~8191 bytes
File 2 does not have any quoted column values that include commas.
Files 1 and 2 have the same number of lines
Neither file has quoted data values that include new lines (rare, but possible within a CSV).
#echo off
setlocal disableDelayedExpansion
<"file1.csv" >"merged.csv" (
for /f "usebackq eol=, delims=, tokens=1,3" %%A in ("file2.csv") do (
set /p "part1="
set "part2=%%A,%%B"
setlocal enableDelayedExpansion
echo !part1!,!part2!
endlocal
)
)
A much more robust and faster solution is possible if you use PowerShell, JScript, or VBS.
You can also implement an efficient and robust solution using JREPL.BAT - a regular expression text processing utility. JREPL.BAT is pure script (hybrid batch/JScript) that runs natively on any Windows machine from XP onward. Full documentation is available from the command line via jrepl /?, or jrepl /?? for paged help.
This JREPL solution has only the following reasonable limitations:
Files 1 and 2 must have the same number of lines
Neither file has quoted data values that include new lines
#echo off
setlocal
set "col=\q(?:\q\q|[^,\q])*\q|[^,]*"
call jrepl "^(%col%),(?:%col%),(%col%)(?:,.*|$)" ^
"$txt=stdin.ReadLine()+','+$1+','+$2" ^
/jq /x /f "file2.csv" /o "merged.csv" <"file1.csv"
This flexible script does what you want, given that the following restrictions are not violated:
both files must contain the same number of lines;
the number of columns per line/row must be equal per each file;
lines are no longer than 1023 bytes, including the terminating line-break;
field/cell values must not contain line-breaks;
each line/row must be terminated by Windows-style line-breaks (CR+LF);
the given column numbers to copy must be sorted in ascending order;
So here is the code:
#echo off
setlocal EnableExtensions DisableDelayedExpansion
rem // Define constants here:
set "_FILE1=%~dp0File1.csv" & rem // (1st input CSV file; state `%~1` to use 1st arg.)
set "_FILE2=%~dp0File2.csv" & rem // (2nd input CSV file; state `%~2` to use 2nd arg.)
set "_COLS1=1,2" & rem // (ascending list of columns to copy from 1st file)
set "_COLS2=1,3" & rem // (ascending list of columns to copy from 2nd file)
set "_SEPAR=," & rem // (separator character, usually `,`)
rem // Main routine:
4< "%_FILE1%" 3< "%_FILE2%" (
call :READLINE
)
endlocal
exit /B
:READLINE
rem // Read a line of both files:
set "LINE1=" & set "LINE2="
<&4 set /P LINE1=""
<&3 set /P LINE2=""
rem // Terminate sub-routine in case both lines are empty:
if not defined LINE1 if not defined LINE2 exit /B
rem // Process lines:
call :PROCESS LINE1 LINE2
rem // Repeat reading:
goto :READLINE
exit /B
:PROCESS ref_string1 ref_string2
setlocal DisableDelayedExpansion
set "BUF=%_SEPAR%"
setlocal EnableDelayedExpansion
rem // Test both strings against global wild-card characters:
set "STR1=!%~1!" & set "STR2=!%~2!"
if "!STR1:**=!!STR2:**=!"=="!STR1!!STR2!" goto :PROCESS_CONT
if "!STR1:*?=!!STR2:*?=!"=="!STR1!!STR2!" goto :PROCESS_CONT
if "!STR1:*<=!!STR2:*<=!"=="!STR1!!STR2!" goto :PROCESS_CONT
if "!STR1:*>=!!STR2:*>=!"=="!STR1!!STR2!" goto :PROCESS_CONT
>&2 echo(ERROR: Illegal character encountered^^!
exit /B 1
:PROCESS_CONT
rem // Prepare line strings for being processed by a standard `for` loop:
set "STR1=!STR1:"=""!^"
set "STR2=!STR2:"=""!^"
set "STR1="!STR1:%_SEPAR%=","!""
set "STR2="!STR2:%_SEPAR%=","!""
rem // `for /F` loops to transport prepared line strings beyond `endlocal`:
for /F "delims=" %%E in (^""!STR1!"^") do (
for /F "delims=" %%F in (^""!STR2!"^") do (
endlocal
rem // Process 1st line string:
set /A "IDX=0"
for %%I in (%%~E) do (
rem // Compare column index of current item with given column list:
set /A "IDX+=1" & set "FND="
for %%J in (%_COLS1%) do (
setlocal EnableDelayedExpansion
if !IDX! EQU %%J (
endlocal & set "FND=#"
) else endlocal
)
rem // Matching column index encountered, so assemble output line:
if defined FND (
set "NEW=%%~I%_SEPAR%"
setlocal EnableDelayedExpansion
for /F "delims=" %%K in (^""!BUF!!NEW!"^") do (
endlocal
set "BUF=%%~K"
)
)
)
rem // Process 1st line string:
set /A "IDX=0"
for %%I in (%%~F) do (
rem // Compare column index of current item with given column list:
set /A "IDX+=1" & set "FND="
for %%J in (%_COLS2%) do (
setlocal EnableDelayedExpansion
if !IDX! EQU %%J (
endlocal & set "FND=#"
) else endlocal
)
rem // Matching column index encountered, so assemble output line:
if defined FND (
set "NEW=%%~I%_SEPAR%"
setlocal EnableDelayedExpansion
for /F "delims=" %%K in (^""!BUF!!NEW!"^") do (
endlocal
set "BUF=%%~K"
)
)
)
setlocal EnableDelayedExpansion
)
)
rem // Return output line buffer:
echo(!BUF:~1,-1!
endlocal
endlocal
exit /B
I have a config file on many remote machines that I need to modify through the use of a batch script. The config file has two lines like this:
1_IP = "10.101.34.216"
2_IP = "10.101.34.214"
I simply need to swap the two IP's, but I'm having a hard time.
Ideally, I would write a simple script to search for 10.101.34.216 and replace it with 10.101.34.214 and vice versa - however, if I accidentally run the script on the remote machines in the future it would just revert to the original.
Therefore, I need to set the search parameter to look for exactly 1_IP = "10.101.34.216" and replace it with exactly 1_IP = "10.101.34.214" and then subsequently an exact search for 2_IP = "10.101.34.214" to be replaced with exactly 2_IP = "10.101.34.216"
I'm currently using the following script, which I found on a separate stackoverflow post.
#echo off
setlocal enableextensions disabledelayedexpansion
set "search=%1"
set "replace=%2"
set "textFile=system.cfg"
for /f "delims=" %%i in ('type "%textFile%" ^& break ^> "%textFile%" ') do (
set "line=%%i"
setlocal enabledelayedexpansion
set "line=!line:%search%=%replace%!"
>>"%textFile%" echo(!line!
endlocal
)
However, if I set search parameter to
set search= "1_IP = "10.101.34.216" "
It does not work because of the double quotes. I've tried several approaches to escape the double quotes, but I can't seem to get it to work. Any suggestions?
Thank you in advance for your time, patience, and expertise.
Since the current strings behind 1_IP = and 2_IP = do not seem to matter, I would not try to replace them, but I would simply write the desired strings, like this (see all the explanatory rem comments):
#echo off
setlocal EnableExtensions DisableDelayedExpansion
rem // Define constants here:
set "_FILE=%~dp0system.cfg" & rem // (path/name of file to process)
set "_KEY[1]=1_IP" & rem // (1st key to search for)
set "_KEY[2]=2_IP" & rem // (2nd key to search for)
set "_VAL[1]=10.101.34.214" & rem // (1st value to assign to 1st key)
set "_VAL[2]=10.101.34.216" & rem // (2nd value to assign to 2nd key)
rem // Read specified file and iterate through all (non-empty) lines:
for /F delims^=^ eol^= %%L in ('type "%_FILE%" ^& ^> "%_FILE%" rem/') do (
rem // Write (append) to the specified file:
>> "%_FILE%" (
rem // Split key from value (key must not contain spaces):
for /F "eol== delims== " %%K in ("%%L") do (
rem /* If key is a predefined one return respective value;
rem otherwise return the current line unedited: */
if /I "%%K"=="%_KEY[1]%" (
echo(%%K = "%_VAL[1]%"
) else if /I "%%K"=="%_KEY[2]%" (
echo(%%K = "%_VAL[2]%"
) else (
echo(%%L
)
)
)
)
endlocal
exit /B
If you want to be able to predefine an arbitrary number of keys and values, and to retain empty lines, you may want to use this script:
#echo off
setlocal EnableExtensions DisableDelayedExpansion
rem // Define constants here:
set "_FILE=%~dp0system.cfg" & rem // (path/name of file to process)
set "_KEY[1]=1_IP" & rem // (1st key to search for)
set "_KEY[2]=2_IP" & rem // (2nd key to search for)
set "_VAL[1]=10.101.34.214" & rem // (1st value to assign to 1st key)
set "_VAL[2]=10.101.34.216" & rem // (2nd value to assign to 2nd key)
rem // Read specified file and iterate through all lines, preceded by line number:
for /F "delims=" %%L in ('findstr /N "^" "%_FILE%" ^& ^> "%_FILE%" rem/') do (
rem // Write (append) to the specified file:
>> "%_FILE%" (
rem // Split line number plus key from value (key must not contain spaces):
for /F "delims== " %%K in ("%%L") do (
rem // Store extracted key and full line, both including line numbers:
set "KEY=%%K" & set "LINE=%%L"
setlocal EnableDelayedExpansion
rem // Clear flag, remove line number from key:
set "FLAG=" & set "KEY=!KEY:*:=!"
rem // Loop through all available keys:
for /F "tokens=2 delims=[]=" %%M in ('2^> nul set _KEY[') do (
rem /* If key is a predefined one return respective value;
rem otherwise set flag to indicate key has been found: */
if /I "!KEY!"=="!_KEY[%%M]!%" (
echo(!KEY! = "!_VAL[%%M]!"
set "FLAG=#"
)
)
rem // Return current line unedited in case flag is not set:
if not defined FLAG echo(!LINE:*:=!
endlocal
)
)
)
endlocal
exit /B
#ECHO OFF
SETLOCAL
SET "sourcedir=U:\sourcedir"
SET "destdir=U:\destdir"
SET "filename1=%sourcedir%\q41578841.txt"
SET "outfile=%destdir%\outfile.txt"
SET "search1=1_IP = \"10.101.34.216\""
SET "search2=2_IP = \"10.101.34.214\""
SET "replace1=1_IP = "10.101.34.214""
SET "replace2=2_IP = "10.101.34.216""
SET "replaced=N"
(
FOR /f "usebackqtokens=1*delims=" %%a IN ("%filename1%") DO (
ECHO(%%a|FINDSTR /x /L /C:"%search1%" >NUL
IF ERRORLEVEL 1 (
ECHO(%%a|FINDSTR /x /L /C:"%search2%" >NUL
IF ERRORLEVEL 1 (ECHO(%%a
) ELSE (SET "replaced=Y"&ECHO(%replace2%
)
) ELSE (SET "replaced=Y"&ECHO(%replace1%
)
)
)>"%outfile%"
IF %replaced%==Y (MOVE "%outfile%" "%filename1%">nul&ECHO made changes
) ELSE (DEL "%outfile%"&echo no changes made
)
GOTO :EOF
You would need to change the settings of sourcedir and destdir to suit your circumstances.
I used a file named q41578841.txt containing your data plus some dummy data for my testing.
%outfile% may be used as a temporary file. its name is not relevant, it just needs to not-exist when the job is run.
read each file line. if the line exactly matches (/x) the search-string (/c: since it may contain spaces, /L literally - /i for case-insensitive omitted) then set errorlevel 0. if neither matches, regurgitate the line, else output the replacement line and flag that the replacement took place.
Finally, either replace the file or delete the dummy output file and report.
It's not pretty, but when is Windows Shell script?...:
#echo off
setlocal
set TEXT_FILE=.\system.cfg
set IP_1=
set IP_2=
for /f "tokens=*" %%L in (%TEXT_FILE%) do call :PROCESS_LINE %%L
echo 1_IP = "%IP_2%"
echo 2_IP = "%IP_1%"
endlocal
goto END
:PROCESS_LINE
set PL_LINE=%*
set PL_LINE=%PL_LINE:"=%
if "%PL_LINE:~0,4%" == "1_IP" set IP_1=%PL_LINE:~7%
if "%PL_LINE:~0,4%" == "2_IP" set IP_2=%PL_LINE:~7%
goto END
:END
In input, I have a text file which contains numbers separated with a comma.
list.txt
111221,345,332133,66,5555, and so
I want to check the length of each string between the "," delimiter, in order to successively display the length of each word.
For example:
111221 is 6 characters long
345 is 3 characters long
332133 is 6 characters long
66 is 2 characters long
...
For this, I've written this code but it displays only the first word and the length is always "0". Without the for loop, it works fine for a single chain. Has anyone an idea to fix this?
Thank you.
#echo off
setlocal enabledelayedexpansion enableextensions
for /f "delims=," %%a in ('type list.txt') do (
set string=%%a
set temp_str=%string%
set str_len=0
:loop
if defined temp_str (
set temp_str=%temp_str:~1%
set /a str_len+=1
goto:loop )
echo !string! is !str_len! characters long
)
pause
endlocal
#echo off
setlocal enabledelayedexpansion enableextensions
for /f "delims=" %%f in ('type q35202446.txt') do (
for %%a in (%%f) do (
set "string=%%a"
set "temp_str=!string!"
set str_len=0
CALL :loop
echo !string! is !str_len! characters long
)
)
GOTO :eof
:loop
if defined temp_str (
set temp_str=%temp_str:~1%
set /a str_len+=1
GOTO loop )
GOTO :eof
I used a file named q35202446.txt containing your data for my testing.
Your problems are :
for /f ... reads a line at a time, so delims=, would provide you with just the first token in the line. Next iteration would read the next line (if it existed)
Putting the entire line ("delims=") into %%f allows you to use the default function of , (along with space, semicolon and tab) - a separator. The for ... %%a... sees a simple list of elements separated by commas.
You must use !string! to access the run-time value of string (with delayedexpansion invoked.) %string% will deliver the parse-time value of string which will be empty, hence reporting length 0.
Note the use of quotes in the string-assignment. That syntax ensures trailing spaces are not included in the string assigned.
A label terminates a "block" (parenthesised series of statements) so I've moved the length-calculator to a subroutine.
Your echo was correctly using !var! to access the run-time value of the variables.
You should use for /f to read line-by-line, then an inner for to tokenize each line on the commas. Something like this:
#echo off
setlocal
for /f "usebackq delims=" %%a in ("list.txt") do (
for %%t in (%%a) do (
call :length len %%t
setlocal enabledelayedexpansion
echo %%t is !len! characters long.
endlocal
)
)
goto :EOF
:length <return_var> <string>
setlocal enabledelayedexpansion
if "%~2"=="" (set ret=0) else set ret=1
set "tmpstr=%~2"
for %%I in (4096 2048 1024 512 256 128 64 32 16 8 4 2 1) do (
if not "!tmpstr:~%%I,1!"=="" (
set /a ret += %%I
set "tmpstr=!tmpstr:~%%I!"
)
)
endlocal & set "%~1=%ret%"
goto :EOF
Credit: the :length function is based on jeb's answer here. It's much more efficient than a goto loop.