Batch: How to remove all empty columns from a csv file - batch-file
I have a CSV file like this:
P,PC,,PL,B,15feb16,P,Bay,RP,15-FEB-16,22-FEB-16,7,,,,,,11,14,138,14,16,993.42,-12,-84,-12,,,,,,,,,17,2,-10,0,0,1,1,16:05:53,15FEB16
P,PC,,PL,I,1FEB-16,P,In,RP,15-FEB-16,22-FEB-16,7,,,,,,25,5,32,5,5,-29.7,-24,-168,-24,,,,,,,,,520,14,-10,0,0,1,1,10-MAY-201606:05:53,15-FEB-16
P,PC,,PC,S,15FEB16,P,Su,RP,15-FEB-16,22-FEB-16,7,,,,,,6,5,32,56,5,4.65,0,0,0,,,,,,,,,546,0,0,0,0,1,1,10-MAY-201606:05:53,15-FEB-16
The code I have written is:
#echo off
setlocal EnableDelayedExpansion
for /F "delims=" %%a in (C:\Pca.csv) do (
set line=%%a
set line=!line:,,=, ,!
set line=!line:,,=, ,!
for /F "tokens=1,2,3* delims=," %%i in (^"!line!^") do (
echo %%i,%%l>>C:\P.csv
)
)
But it only deletes 2nd and 3rd column, no matter whether it is empty or contains data.
The sample output file should be like:
P,PC,PL,B,15feb16,P,Bay,RP,15-FEB-16,22-FEB-16,7,11,14,138,14,16,993.42,-12,-84,-12,17,2,-10,0,0,1,1,16:05:53,15FEB16
P,PC,PL,I,1FEB-16,P,In,RP,15-FEB-16,22-FEB-16,7,25,5,32,5,5,-29.7,-24,-168,-24,520,14,-10,0,0,1,1,10-MAY-201606:05:53,15-FEB-16
P,PC,PC,S,15FEB16,P,Su,RP,15-FEB-16,22-FEB-16,7,6,5,32,56,5,4.65,0,0,0,546,0,0,0,0,1,1,10-MAY-201606:05:53,15-FEB-16
Here is a quite comprehensive and adaptive script that removes empty columns from CSV-formatted data.
Before the code is shown, let us take a look at the help message that appears when called with /?:
"del-empty-cols-from-csv.bat"
This script removes any empty columns from CSV-formatted data. A column is con-
sidered as empty if the related fields in all rows are empty, unless the switch
/H is given, in which case the first line (so the header) is evaluated only.
Notice that fields containing white-spaces only are not considered as empty.
USAGE:
del-empty-cols-from-csv.bat [/?] [/H] csv_in [csv_out]
/? displays this help message;
/H specifies to regard the header only, that is the very first row,
to determine which columns are considered as empty; if NOT given,
the whole data, hence all rows, are taken into account instead;
csv_in CSV data file to process, that is, to remove empty columns of;
these data must be correctly formatted CSV data, using the comma as
separator and the quotation mark as text delimiter; regard that
literal quotation marks must be doubled; there are some additional
restrictions: the data must not contain any line-breaks; neither
must they contain any asterisks nor question marks;
csv_out CSV data file to write the return data to; this must not be equal
to csv_in; note that an already existing file will be overwritten
without prompt; if not given, the data is displayed on the console;
As you can read, there are two operation modes: standard (no switch) and header mode (switch /H).
Given that the following CSV data is fed into the script...:
A, ,C, ,E,F
1, , ,4,5,
1, , , ,5,
1, ,3,4, ,
...the returned CSV data in standard mode will look like...:
A,C, ,E,F
1, ,4,5,
1, , ,5,
1,3,4, ,
...and the returned CSV data in header mode (/H) will look like:
A,C,E,F
1, ,5,
1, ,5,
1,3, ,
Remind that the spaces in the above sample data must actually not be present in the files; they have just been inserted here for better illustration of the said operation modes.
Now, this is the complete code:
#echo off
setlocal EnableExtensions DisableDelayedExpansion
set "OPT_HEAD=%~1"
if "%OPT_HEAD%"=="/?" (
goto :MSG_HELP
) else if /I "%OPT_HEAD%"=="/H" (
shift
) else if "%OPT_HEAD:~,1%"=="/" (
set "OPT_HEAD="
shift
) else set "OPT_HEAD="
set "CSV_IN=%~1"
if not defined CSV_IN (
>&2 echo ERROR: no input file specified!
exit /B 1
)
set "CSV_OUT=%~2"
if not defined CSV_OUT set "CSV_OUT=con"
for /F "delims==" %%V in ('2^> nul set CELL[') do set "%%V="
setlocal EnableDelayedExpansion
if not defined OPT_HEAD (
for /F %%C in ('^< "!CSV_IN!" find /C /V ""') do set "NUM=%%C"
) else set /A NUM=1
set /A LIMIT=0
< "!CSV_IN!" (
for /L %%L in (1,1,%NUM%) do (
set /P "LINE="
call :PROCESS LINE LINE || exit /B !ErrorLevel!
set /A COUNT=0
for %%C in (!LINE!) do (
set /A COUNT+=1
if not defined CELL[!COUNT!] set "CELL[!COUNT!]=%%~C"
if !LIMIT! LSS !COUNT! set /A LIMIT=COUNT
)
)
)
set "PAD=" & for /L %%I in (2,1,!LIMIT!) do set "PAD=!PAD!,"
> "!CSV_OUT!" (
for /F usebackq^ delims^=^ eol^= %%L in ("!CSV_IN!") do (
setlocal DisableDelayedExpansion
set "LINE=%%L%PAD%"
set "ROW="
set /A COUNT=0
setlocal EnableDelayedExpansion
call :PROCESS LINE LINE || exit /B !ErrorLevel!
for %%C in (!LINE!) do (
endlocal
set "CELL=%%C"
set /A COUNT+=1
setlocal EnableDelayedExpansion
if !COUNT! LEQ !LIMIT! (
if defined CELL[!COUNT!] (
for /F delims^=^ eol^= %%R in ("!ROW!,!CELL!") do (
endlocal
set "ROW=%%R"
)
) else (
endlocal
)
) else (
endlocal
)
setlocal EnableDelayedExpansion
)
if defined ROW set "ROW=!ROW:~1!"
call :RESTORE ROW ROW || exit /B !ErrorLevel!
echo(!ROW!
endlocal
endlocal
)
)
endlocal
endlocal
exit /B
:PROCESS var_return var_string
set "STRING=!%~2!"
if defined STRING (
set "STRING="!STRING:,=","!""
if not "!STRING!"=="!STRING:**=!" goto :ERR_CHAR
if not "!STRING!"=="!STRING:*?=!" goto :ERR_CHAR
)
set "%~1=!STRING!"
exit /B
:RESTORE var_return var_string
set "STRING=!%~2!"
if "!STRING:~,1!"==^""" set "STRING=!STRING:~1!"
if "!STRING:~-1!"==""^" set "STRING=!STRING:~,-1!"
if defined STRING (
set "STRING=!STRING:","=,!"
)
set "%~1=!STRING!"
exit /B
:ERR_CHAR
endlocal
>&2 echo ERROR: `*` and `?` are not allowed!
exit /B 1
:MSG_HELP
echo(
echo("%~nx0"
echo(
echo(This script removes any empty columns from CSV-formatted data. A column is con-
echo(sidered as empty if the related fields in all rows are empty, unless the switch
echo(/H is given, in which case the first line ^(so the header^) is evaluated only.
echo(Notice that fields containing white-spaces only are not considered as empty.
echo(
echo(
echo(USAGE:
echo(
echo( %~nx0 [/?] [/H] csv_in [csv_out]
echo(
echo( /? displays this help message;
echo( /H specifies to regard the header only, that is the very first row,
echo( to determine which columns are considered as empty; if NOT given,
echo( the whole data, hence all rows, are taken into account instead;
echo( csv_in CSV data file to process, that is, to remove empty columns of;
echo( these data must be correctly formatted CSV data, using the comma as
echo( separator and the quotation mark as text delimiter; regard that
echo( literal quotation marks must be doubled; there are some additional
echo( restrictions: the data must not contain any line-breaks; neither
echo( must they contain any asterisks nor question marks;
echo( csv_out CSV data file to write the return data to; this must not be equal
echo( to csv_in; note that an already existing file will be overwritten
echo( without prompt; if not given, the data is displayed on the console;
echo(
exit /B
assuming, your original csv looks like this:
id_users,,,quantity,,date
1,,,1,,2013
1,,,1,,2013
2,,,1,,2013
then this single line should solve your request:
(for /f "tokens=1-3 delims=," %%a in (c:\pca.csv) do echo %%a,%%b,%%c)>c:\p.csv
resulting in:
id_users,quantity,date
1,1,2013
1,1,2013
2,1,2013
The trick is: consecutive delimiters are treated as one.
Edit: another approach, as it turned out, there are much more colums, than the original question showed.
#echo off
break>out.txt
for /F "delims=" %%a in (c:\pca.csv) do call :shorten "%%a"
goto :eof
:shorten
set "line=%~1"
:remove
set "line=%line:,,=,%"
echo %line%|find ",,">nul && goto :remove
echo %line%>>c:\p.csv
break>c:\p.csv: create outputfile (overwrite if exist)
replace two consecutive commas with one;
repeat, if there are any more consecutive commas.
Write the resulting line to the outfile.
Related
Replace a string in text file using batch file [duplicate]
i have to search a string from a txt like Pippo.K=5 and replace it with Pippo.K=1. I need to search the entire string. What i did is: set "search=Pippo.K=5" set "replace=Pippo.K=1" set "textFile=%SettingFile%.txt" for /f "delims=" %%i in ('type "%textFile%" ^& break ^> "%textFile%" ') do ( set "line=%%i" setlocal enabledelayedexpansion set "line=!line:%search%=%replace%!" >>"%textFile%" echo(!line! endlocal ) but what i returned is 5=Pippo.K=1=5 How can i fix this error?
The following script constitutes a pure batch-file solution. Supposing it is stored as repl-str.bat, you need to call it like this for your application: repl-str.bat "%SettingFile%.txt" "Pippo.K=5" "Pippo.K=1" "%SettingFile%.txt" This specifies the input file %SettingFile%.txt, the literal and case-sensitive search string Pippo.K=5, the replacement string Pippo.K=1 and the output file %SettingFile%.txt that is the same as the input file (the related technique has been taken from this answer: Batch script to find and replace a string in text file without creating an extra output file for storing the modified file). If no output file is given, the result is output to the console (useful for testing). If a fifth command line argument is given (arbitrary value), the search is done in a case-sensitive manner. Here is the code of the script repl-str.bat: #echo off setlocal EnableExtensions DisableDelayedExpansion set "FILE_I=%~1" set "SEARCH=%~2" set "REPLAC=%~3" set "FILE_O=%~4" set "CASE=%~5" set "FLAG=%~6" if not defined FILE_I exit /B 1 if not defined SEARCH exit /B 1 if not defined FILE_O set "FILE_O=con" if defined CASE set "CASE=#" if defined FLAG set "FLAG=#" for /F "delims=" %%L in (' findstr /N /R "^" "%FILE_I%" ^& break ^> "%FILE_O%" ') do ( set "STRING=%%L" setlocal EnableDelayedExpansion set "STRING=!STRING:*:=!" call :REPL RETURN STRING SEARCH REPLAC "%CASE%" "%FLAG%" >> "%FILE_O%" echo(!RETURN! endlocal ) endlocal exit /B :REPL rtn_string ref_string ref_search ref_replac case flag setlocal EnableDelayedExpansion set "STR=!%~2!" set "SCH=!%~3!" set "RPL=!%~4!" if "%~5"=="" (set "OPT=/I") else (set "OPT=") if not defined SCH endlocal & set "%~1=" & exit /B 1 set "SCH_CHR=!SCH:~,1!" if not "%~6"=="" set "SCH_CHR=" if "!SCH_CHR!"=="=" set "SCH_CHR=" & rem = terminates search string if "!SCH_CHR!"==""^" set "SCH_CHR=" & rem " could derange syntax if "!SCH_CHR!"=="%%" set "SCH_CHR=" & rem % ends variable expansion if "!SCH_CHR!"=="^!" set "SCH_CHR=" & rem ! ends variable expansion call :LEN SCH_LEN SCH call :LEN RPL_LEN RPL set /A RED_LEN=SCH_LEN-1 set "RES=" :LOOP call :LEN STR_LEN STR if not defined STR goto :END if defined SCH_CHR ( set "WRK=!STR:*%SCH_CHR%=!" if %OPT% "!WRK!"=="!STR!" ( set "RES=!RES!!STR!" set "STR=" ) else ( call :LEN WRK_LEN WRK set /A DFF_LEN=STR_LEN-WRK_LEN-1,INC_LEN=DFF_LEN+1,MOR_LEN=DFF_LEN+SCH_LEN for /F "tokens=1,2,3 delims=," %%M in ("!DFF_LEN!,!INC_LEN!,!MOR_LEN!") do ( rem set "RES=!RES!!STR:~,%%M!" if defined WRK set "WRK=!WRK:~,%RED_LEN%!" if %OPT% "!STR:~%%M,1!!WRK!"=="!SCH!" ( set "RES=!RES!!STR:~,%%M!!RPL!" set "STR=!STR:~%%O!" ) else ( set "RES=!RES!!STR:~,%%N!" set "STR=!STR:~%%N!" ) ) ) ) else ( if %OPT% "!STR:~,%SCH_LEN%!"=="!SCH!" ( set "RES=!RES!!RPL!" set "STR=!STR:~%SCH_LEN%!" ) else ( set "RES=!RES!!STR:~,1!" set "STR=!STR:~1!" ) ) goto :LOOP :END if defined RES ( for /F delims^=^ eol^= %%S in ("!RES!") do ( endlocal set "%~1=%%S" ) ) else endlocal & set "%~1=" exit /B :LEN rtn_length ref_string setlocal EnableDelayedExpansion set "STR=!%~2!" if not defined STR (set /A LEN=0) else (set /A LEN=1) for %%L in (4096 2048 1024 512 256 128 64 32 16 8 4 2 1) do ( if defined STR ( set "INT=!STR:~%%L!" if not "!INT!"=="" set /A LEN+=%%L & set "STR=!INT!" ) ) endlocal & set "%~1=%LEN%" exit /B Basically, this approach takes the first character of the search string and looks it up in the input text. At each match, it is checked whether the whole search string occurs. If so, it is replaced by the replacement string by removing as many characters as the search string consists of, hence avoiding sub-string replacement syntax which fails in case the search string contains =, or the search or the replacement string contains % or !. However, if the first character of the search string is =, ", % or !, the approach is different, the script checks every single character position for occurrence of the search string then, with the disadvantage of reduced overall performance. If a sixth command line argument is given (arbitrary value), this (slow) mode is forced.
Batch variable substring substitution does have limitations. Dealing with literal equal signs is one of them. powershell "(gc \"%textFile%\") -replace '%search%','%replace%'" would work. That PowerShell one-liner is a simple alternative to your for /f loop without that limitation. If you prefer a for /F loop, if your text file is an ini-style file, try this: #echo off & setlocal set "searchItem=Pippo.K" set "searchVal=5" set "newVal=1" set "textFile=test.txt" >"outfile.txt" ( for /f "eol=; usebackq tokens=1* delims==" %%I in ("%textFile%") do ( if /I "%%~I"=="%searchItem%" ( if "%%~J"=="%searchVal%" ( echo %%I=%newVal% ) else echo %%I=%%J ) else ( if not "%%~J"=="" (echo %%I=%%J) else echo %%I ) ) ) move /y "outfile.txt" "%textFile%" Be advised that if any of the items in your file has a blank value (e.g. valuename=), the equal sign will be stripped unless you add some additional logic. You might also consider using ini.bat from this answer.
Batch: 'FOR' cmd works partially, 'skip=' option not working
I want to create a script that sets specific values, then writes each value into a new line of a text document. After that it should read the document and set new values to a specified line of the text document, then echo those out. I have tried different values for "skip=#" which didn't change anything. When I tried to not use the "skip=0" option in the first FOR and that makes the batch echo out "Value three" for all values. (Quick edit: I've used this website for information on it so far.) #ECHO OFF REM Setting values SET #valueone=Value one SET #valuetwo=Value two SET #valuethree=Value three REM Saving values IF EXIST "values.txt" DEL "values.txt" echo %#valueone% >values.txt echo %#valuetwo% >>values.txt echo %#valuethree% >>values.txt REM Reading values again and echoing them at at the same time. REM This was separated (first reading then echoing) but it didn't change anything. FOR /F "skip=0 delims=" %%i IN (values.txt) DO SET #valueonefinal=%%i echo Value number one: echo %#valueonefinal% echo. FOR /F "skip=1 delims=" %%i IN (values.txt) DO SET #valuetwofinal=%%i echo Value number two: echo %#valuetwofinal% echo. FOR /F "skip=2 delims=" %%i IN (values.txt) DO SET #valuethreefinal=%%i echo Value number three: echo %#valuethreefinal% pause Expected output in the console: Value number one: Value one Value number two: Value two Value number three: Value three Actual output: delims=" was unexpected at this time. Value number one: ECHO is off. Value number two: Value three Value number three: Value three I'm not that experienced but I suspect that I may be doing the "skip=#" part wrong. Any help with this is greatly apprechiated!
The option skip=0 is not accepted by the for /F command, the specified number must be in the range from 1 to 231 − 1. To skip no lines just do not provide the skip option at all. You seem to try to assign the text of a certain line to a variable (for instance, the third one): FOR /F "skip=2 delims=" %%i IN (values.txt) DO SET #valuethreefinal=%%i Well, this actually assigns the content of the last line to the variable, because the set command in the body of the loop is executed for all but the skipped lines. More precisely said, the for /F loop iterates over all non-empty lines which do not begin with ; which is the default character of the eol option. To actually assign the third line to the variable you need to change the code: rem // Ensure that the variable is initially unset somewhere before: set "#valuethreefinal=" rem // As soon as the variable is set the `if` condition is no longer going to be fulfilled: for /F "usebackq skip=2 delims=" %%i in ("values.txt") do if not defined #valuethreefinal set "#valuethreefinal=%%i" This does not necessarily assign the third line to the variable, it actually assigns the text of the first line after the (two) skipped ones that is not empty and does not begin with ; (remember the eol character). The usebackq option allows to put quotation marks around the file name. This is not necessary in your situation, but it is when a file name contains SPACEs or other special characters. I used the undocumented quoted set syntax here because this is safer than the unquoted one, particularly when it comes to special characters and also to avoid unintended trailing white-spaces. To disable the eol character you could use the undocumented unquoted option string syntax: for /F usebackq^ skip^=2^ delims^=^ eol^= %%i in ("values.txt") do if not defined #valuethreefinal set "#valuethreefinal=%%i" As you can see the SPACEs and =-signs are escaped by the caret symbol ^ in order to treat the whole option string as a unit. This still skips over empty lines though. To prevent this take a loop at this thread: preserve empty lines in a text file while using batch for /f. Since you want to capture more than a single line you could extend the code to the following: set "#valueonefinal=" & set "#valuethreefinal=" & set "#valuethreefinal=" for /F usebackq^ delims^=^ eol^= %%i in ("values.txt") do ( if not defined #valueonefinal ( set "#valueonefinal=%%i" ) else ( if not defined #valuetwofinal ( set "#valuetwofinal=%%i" ) else ( if not defined #valuethreefinal ( set "#valuethreefinal=%%i" ) ) ) ) This can be compressed to: set "#valueonefinal=" & set "#valuethreefinal=" & set "#valuethreefinal=" for /F usebackq^ delims^=^ eol^= %%i in ("values.txt") do ( if not defined #valueonefinal ( set "#valueonefinal=%%i" ) else if not defined #valuetwofinal ( set "#valuetwofinal=%%i" ) else if not defined #valuethreefinal ( set "#valuethreefinal=%%i" ) ) A more flexible method is to use pseudo-arrays: rem // Initialise an index counter: set /A "INDEX=0" rem // Assign every line to an element of a pseudo-array: for /F usebackq^ delims^=^ eol^= %%i in ("values.txt") do ( rem // Increment the index counter: set /A "INDEX+=1" rem // Assign the current line to a pseudo-array element: call set "#valuefinal[%%INDEX%%]=%%i" ) The (non-empty) lines of the file value.txt are now assigned to variables called #valuefinal[1], #valuefinal[2], #valuefinal[3], etc. (there is no concept of arrays in batch scripting, the variables are exactly the same as yours, #valueonefinal, etc., that is why I use the term "pseudo"). The call command is used here in order to be able to write and read the variable INDEX within the same block of code; just using set "#valuefinal[%INDEX%]=%%i" would result in assigning and therefore overwriting the variable #valuefinal[0] in every loop iteration.
Your problem is that you are parsing the File from Top to bottom, and skipping the First value, what you don't realize is that FOR will set the value to the LAST item it found. This means that the script as written can only ever return the last item in the values file. To deal with this you could: Break the loop on the first match and return that result. Remove values as they are matched I like to Break the loop. First let me make you code a little more streamlined so we can re-write it multiple times to show each This is going to work exactly as your existing code but now we can easily add more values and loop them in a quick go. Your Original Code Refactored: #( SETLOCAL EnableDelayedExpansion ECHO OFF SET "_ValuesFile=%~dp0values.txt" REM Remove Old Values File DEL /F /Q "!_ValuesFile!" >NUL 2>NUL REM Saving values FOR %%A IN (one two three) DO ( ECHO.Value %%A>>"!_ValuesFile!" ) ) CALL :Main ( PAUSE ENDLOCAL EXIT /B 0 ) :Main FOR /L %%L IN (0,1,2) DO ( CALL SET /A "_Value=%%L + 1" ECHO.&ECHO.------ Iteration: %%L ------&ECHO.Value number !_Value!: IF %%L EQU 0 ( SET "_ForOptions=tokens=*" ) ELSE ( SET "_ForOptions=Skip=%%L tokens=*" ) CALL :Loop %%L ) GOTO :EOF :Loop FOR /F "%_ForOptions%" %%i IN (' type "%_ValuesFile%" ') DO ( CALL SET "#value%_Value%final=%%i" ) ECHO.!#value%_Value%final! GOTO :EOF * Break the Loop on the First Match: #( SETLOCAL EnableDelayedExpansion ECHO OFF SET "_ValuesFile=%~dp0values.txt" REM Remove Old Values File DEL /F /Q "!_ValuesFile!" >NUL 2>NUL REM Saving values FOR %%A IN (one two three) DO ( ECHO.Value %%A>>"!_ValuesFile!" ) ) CALL :Main ( PAUSE ENDLOCAL EXIT /B 0 ) :Main FOR /L %%L IN (0,1,2) DO ( CALL SET /A "_Value=%%L + 1" ECHO.&ECHO.------ Iteration: %%L ------&ECHO.Value number !_Value!: IF %%L EQU 0 ( SET "_ForOptions=tokens=*" ) ELSE ( SET "_ForOptions=Skip=%%L tokens=*" ) CALL :Loop %%L ) ECHO.&ECHO.------ Final Values After %%L Iterations: ------ SET #value GOTO :EOF :Loop FOR /F "Tokens=*" %%A IN (' CMD /C "FOR /F %_ForOptions% %%i IN (' type "%_ValuesFile%" ') DO #(ECHO.%%i&exit /b)" ') DO #( SET "#value%_Value%final=%%~A" ) ECHO.!#value%_Value%final! GOTO :EOF Example Output from Break the Loop Version: Y:\>C:\Admin\S-O_Value-Checker_v2.cmd ------ Iteration: 0 ------ Value number 1: Value one ------ Iteration: 1 ------ Value number 2: Value two ------ Iteration: 2 ------ Value number 3: Value three ------ Final Values After %L Iterations: ------ #value1final=Value one #value2final=Value two #value3final=Value three Press any key to continue . . .
New CSV by combining 2 csv files
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
Including Double Quotes in Batch Search and Replace script
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
How to search and replace a string that have an equal-to sign "=" inside
i have to search a string from a txt like Pippo.K=5 and replace it with Pippo.K=1. I need to search the entire string. What i did is: set "search=Pippo.K=5" set "replace=Pippo.K=1" set "textFile=%SettingFile%.txt" for /f "delims=" %%i in ('type "%textFile%" ^& break ^> "%textFile%" ') do ( set "line=%%i" setlocal enabledelayedexpansion set "line=!line:%search%=%replace%!" >>"%textFile%" echo(!line! endlocal ) but what i returned is 5=Pippo.K=1=5 How can i fix this error?
The following script constitutes a pure batch-file solution. Supposing it is stored as repl-str.bat, you need to call it like this for your application: repl-str.bat "%SettingFile%.txt" "Pippo.K=5" "Pippo.K=1" "%SettingFile%.txt" This specifies the input file %SettingFile%.txt, the literal and case-sensitive search string Pippo.K=5, the replacement string Pippo.K=1 and the output file %SettingFile%.txt that is the same as the input file (the related technique has been taken from this answer: Batch script to find and replace a string in text file without creating an extra output file for storing the modified file). If no output file is given, the result is output to the console (useful for testing). If a fifth command line argument is given (arbitrary value), the search is done in a case-sensitive manner. Here is the code of the script repl-str.bat: #echo off setlocal EnableExtensions DisableDelayedExpansion set "FILE_I=%~1" set "SEARCH=%~2" set "REPLAC=%~3" set "FILE_O=%~4" set "CASE=%~5" set "FLAG=%~6" if not defined FILE_I exit /B 1 if not defined SEARCH exit /B 1 if not defined FILE_O set "FILE_O=con" if defined CASE set "CASE=#" if defined FLAG set "FLAG=#" for /F "delims=" %%L in (' findstr /N /R "^" "%FILE_I%" ^& break ^> "%FILE_O%" ') do ( set "STRING=%%L" setlocal EnableDelayedExpansion set "STRING=!STRING:*:=!" call :REPL RETURN STRING SEARCH REPLAC "%CASE%" "%FLAG%" >> "%FILE_O%" echo(!RETURN! endlocal ) endlocal exit /B :REPL rtn_string ref_string ref_search ref_replac case flag setlocal EnableDelayedExpansion set "STR=!%~2!" set "SCH=!%~3!" set "RPL=!%~4!" if "%~5"=="" (set "OPT=/I") else (set "OPT=") if not defined SCH endlocal & set "%~1=" & exit /B 1 set "SCH_CHR=!SCH:~,1!" if not "%~6"=="" set "SCH_CHR=" if "!SCH_CHR!"=="=" set "SCH_CHR=" & rem = terminates search string if "!SCH_CHR!"==""^" set "SCH_CHR=" & rem " could derange syntax if "!SCH_CHR!"=="%%" set "SCH_CHR=" & rem % ends variable expansion if "!SCH_CHR!"=="^!" set "SCH_CHR=" & rem ! ends variable expansion call :LEN SCH_LEN SCH call :LEN RPL_LEN RPL set /A RED_LEN=SCH_LEN-1 set "RES=" :LOOP call :LEN STR_LEN STR if not defined STR goto :END if defined SCH_CHR ( set "WRK=!STR:*%SCH_CHR%=!" if %OPT% "!WRK!"=="!STR!" ( set "RES=!RES!!STR!" set "STR=" ) else ( call :LEN WRK_LEN WRK set /A DFF_LEN=STR_LEN-WRK_LEN-1,INC_LEN=DFF_LEN+1,MOR_LEN=DFF_LEN+SCH_LEN for /F "tokens=1,2,3 delims=," %%M in ("!DFF_LEN!,!INC_LEN!,!MOR_LEN!") do ( rem set "RES=!RES!!STR:~,%%M!" if defined WRK set "WRK=!WRK:~,%RED_LEN%!" if %OPT% "!STR:~%%M,1!!WRK!"=="!SCH!" ( set "RES=!RES!!STR:~,%%M!!RPL!" set "STR=!STR:~%%O!" ) else ( set "RES=!RES!!STR:~,%%N!" set "STR=!STR:~%%N!" ) ) ) ) else ( if %OPT% "!STR:~,%SCH_LEN%!"=="!SCH!" ( set "RES=!RES!!RPL!" set "STR=!STR:~%SCH_LEN%!" ) else ( set "RES=!RES!!STR:~,1!" set "STR=!STR:~1!" ) ) goto :LOOP :END if defined RES ( for /F delims^=^ eol^= %%S in ("!RES!") do ( endlocal set "%~1=%%S" ) ) else endlocal & set "%~1=" exit /B :LEN rtn_length ref_string setlocal EnableDelayedExpansion set "STR=!%~2!" if not defined STR (set /A LEN=0) else (set /A LEN=1) for %%L in (4096 2048 1024 512 256 128 64 32 16 8 4 2 1) do ( if defined STR ( set "INT=!STR:~%%L!" if not "!INT!"=="" set /A LEN+=%%L & set "STR=!INT!" ) ) endlocal & set "%~1=%LEN%" exit /B Basically, this approach takes the first character of the search string and looks it up in the input text. At each match, it is checked whether the whole search string occurs. If so, it is replaced by the replacement string by removing as many characters as the search string consists of, hence avoiding sub-string replacement syntax which fails in case the search string contains =, or the search or the replacement string contains % or !. However, if the first character of the search string is =, ", % or !, the approach is different, the script checks every single character position for occurrence of the search string then, with the disadvantage of reduced overall performance. If a sixth command line argument is given (arbitrary value), this (slow) mode is forced.
Batch variable substring substitution does have limitations. Dealing with literal equal signs is one of them. powershell "(gc \"%textFile%\") -replace '%search%','%replace%'" would work. That PowerShell one-liner is a simple alternative to your for /f loop without that limitation. If you prefer a for /F loop, if your text file is an ini-style file, try this: #echo off & setlocal set "searchItem=Pippo.K" set "searchVal=5" set "newVal=1" set "textFile=test.txt" >"outfile.txt" ( for /f "eol=; usebackq tokens=1* delims==" %%I in ("%textFile%") do ( if /I "%%~I"=="%searchItem%" ( if "%%~J"=="%searchVal%" ( echo %%I=%newVal% ) else echo %%I=%%J ) else ( if not "%%~J"=="" (echo %%I=%%J) else echo %%I ) ) ) move /y "outfile.txt" "%textFile%" Be advised that if any of the items in your file has a blank value (e.g. valuename=), the equal sign will be stripped unless you add some additional logic. You might also consider using ini.bat from this answer.