In batch, how would I remove all non alphanumeric (a-z,A-Z,0-9,_) characters from a variable?
I'm pretty sure I need to use findstr and a regex.
The solutionof MC ND works, but it's really slow (Needs ~1second for the small test sample).
This is caused by the echo "!_buf!"|findstr ... construct, as for each character the pipe creates two instances of cmd.exe and starts findstr.
But this can be solved also with pure batch.
Each character is tested if it is in the map variable
:test
set "_input=Th""i\s&& is not good _maybe_???"
set "_output="
set "map=abcdefghijklmnopqrstuvwxyz 1234567890"
:loop
if not defined _input goto endLoop
for /F "delims=*~ eol=*" %%C in ("!_input:~0,1!") do (
if "!map:%%C=!" NEQ "!map!" set "_output=!_output!%%C"
)
set "_input=!_input:~1!"
goto loop
:endLoop
echo(!_output!
And it could be speed up when the goto loop is removed.
Then you need to calculate the stringLength first and iterate then with a FOR/L loop over each character.
This solution is ~6 times faster than the above method and ~40 times faster than the solution of MC ND
set "_input=Th""i\s&& is not good _maybe_!~*???"
set "_output="
set "map=abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ 1234567890"
%$strLen% len _input
for /L %%n in (0 1 %len%) DO (
for /F "delims=*~ eol=*" %%C in ("!_input:~%%n,1!") do (
if "!map:%%C=!" NEQ "!map!" set "_output=!_output!%%C"
)
)
exit /b
The macro $strlen can be defined with
set LF=^
::Above 2 blank lines are required - do not remove
#set ^"\n=^^^%LF%%LF%^%LF%%LF%^^":::: StrLen pResult pString
set $strLen=for /L %%n in (1 1 2) do if %%n==2 (%\n%
for /F "tokens=1,2 delims=, " %%1 in ("!argv!") do (%\n%
set "str=A!%%~2!"%\n%
set "len=0"%\n%
for /l %%A in (12,-1,0) do (%\n%
set /a "len|=1<<%%A"%\n%
for %%B in (!len!) do if "!str:~%%B,1!"=="" set /a "len&=~1<<%%A"%\n%
)%\n%
for %%v in (!len!) do endlocal^&if "%%~b" neq "" (set "%%~1=%%v") else echo %%v%\n%
) %\n%
) ELSE setlocal enableDelayedExpansion ^& set argv=,
EDITED - #jeb is right. This works but is really, really slow.
#echo off
setlocal enableextensions enabledelayedexpansion
set "_input=Th""i\s&& is not good _maybe_???"
set "_output="
:loop
if not defined _input goto endLoop
set "_buf=!_input:~0,1!"
set "_input=!_input:~1!"
echo "!_buf!"|findstr /i /r /c:"[a-z 0-9_]" > nul && set "_output=!_output!!_buf!"
goto loop
:endLoop
echo !_output!
endlocal
So, back to the drawing board. How to make it faster? lets try to do as less operations as we can and use as much long substring as we can. So, do it in two steps
1.- Remove all bad characters that can generate problems. To do it we will use the hability of for command to identify these chars as delimiters , and then join the rest of the sections of god characters of string
2.- Remove the rest of the bad characters, locating them in string using the valids charactes as delimiters to find substrings of bad characters, replacing then in string
So, we end with (sintax adapted to what has been answered here)
#echo off
setlocal enableextensions enabledelayedexpansion
rem Test empty string
call :doClean "" output
echo "%output%"
rem Test mixed strings
call :doClean "~~asd123#()%%%^"^!^"~~~^"""":^!!!!=asd^>^<bm_1" output
echo %output%
call :doClean "Thi\s&& is ;;;;not ^^good _maybe_!~*???" output
echo %output%
rem Test clean string
call :doClean "This is already clean" output
echo %output%
rem Test all bad string
call :doClean "*******//////\\\\\\\()()()()" output
echo "%output%"
rem Test long string
set "zz=Thi\s&& is not ^^good _maybe_!~*??? "
set "zz=TEST: %zz%%zz%%zz%%zz%%zz%%zz%%zz%%zz%%zz%%zz%%zz%%zz%%zz%%zz%%zz%%zz%%zz%%zz%%zz%%zz%"
call :doClean "%zz% TEST" output
echo %output%
rem Time long string
echo %time%
for /l %%# in (1 1 100) do call :doClean "%zz%" output
echo %time%
exit /b
rem ---------------------------------------------------------------------------
:doClean input output
setlocal enableextensions enabledelayedexpansion
set "map=abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ1234567890 "
set "input=%~1"
set "output="
rem Step 1 - Remove critical delimiters
(
:purgeCritical
for /L %%z in (1 1 10) do (
for /f tokens^=1^-9^,^*^ delims^=^=^"^"^~^;^,^&^*^%%^:^!^(^)^<^>^^ %%a in ("!input!") do (
set "output=!output!%%a%%b%%c%%d%%e%%f%%g%%h%%i"
set "input=%%j"
)
if not defined input goto outPurgeCritical
)
goto purgeCritical
)
:outPurgeCritical
rem Step 2 - remove any remaining special character
(
:purgeNormal
for /L %%z in (1 1 10) do (
set "pending="
for /f "tokens=1,* delims=%map%" %%a in ("!output!") do (
set "output=!output:%%a=!"
set "pending=%%b"
)
if not defined pending goto outPurgeNormal
)
goto purgeNormal
)
:outPurgeNormal
endlocal & set "%~2=%output%"
goto :EOF
Maybe not the fastest, but at least a "decent" solution
#echo eof
call :purge "~~asd123#()%%%^"^!^"~~~^:^=asd^>^<bm_1" var
echo (%var%)
goto :eof
:purge StrVar [RtnVar]
setlocal disableDelayedExpansion
set "str1=%~1"
setlocal enableDelayedExpansion
for %%a in ( - ! # # $ % ^^ ^& + \ / ^< ^> . ' [ ] { } ` ^| ^" ) do (
set "str1=!str1:%%a=!"
)
rem dealing with some delimiters
set "str1=!str1:(=!"
set "str1=!str1:)=!"
set "str1=!str1:;=!"
set "str1=!str1:,=!"
set "str1=!str1:^^=!"
set "str1=!str1:^~=!"
set "temp_str="
for %%e in (%str1%) do (
set "temp_str=!temp_str!%%e"
)
endlocal & set "str1=%temp_str%"
setlocal disableDelayedExpansion
set "str1=%str1:!=%"
set "str1=%str1::=%"
set "str1=%str1:^^~=%"
for /f "tokens=* delims=~" %%w in ("%str1%") do set "str1=%%w"
endlocal & set "str1=%str1%"
endlocal & if "%~2" neq "" (set %~2=%str1%) else echo %str1%
goto :eof
Still cannot deal with ~ and = but working on it
EDIT: = now will be cleared
EDIT: ~ now will be cleared
Related
I would like write a batch file to count the number of occurrences of a specific character in each line of a text file.
For example, the count of \ in the string "aa\bb\cc\dd\" would be 4.
The find and the findstr show only the number of lines which is contains the exact character.
You might try the following script, providing the input string as (quoted) command line argument:
set "STRING=%~1$"
set STRING="%STRING:\=" "%"
set /A "COUNT=-1"
for %%E in (%STRING%) do set /A "COUNT+=1"
echo Count of `\`: %COUNT%
This replaces every character to be counted by " + SPACE + " and encloses the entire string in between "", so the input string aa\bb\cc\dd\ becomes "aa" "bb" "cc" "dd" "". The resulting string is fed into a for loop that recognises individual items to iterate through -- five in this case. The counter variable COUNT is initialised with a value of -1, so the result is not the number of iterated items but the separators, namely the \ characters present in the original string.
This approach fails if the string contains ? or * characters. It would also fail in case the character to count is one of the following: ", %, =, *, ~.
#echo off
setlocal
set "string=aa\bb\cc\dd\"
set "count=-1"
for %%a in ("%string:\=" "%") do set /A count+=1
echo %count%
This method works correctly as long as the string don't include wild-card characters: *?; if this is required, I would use the same npocmaka's method, but written in a simpler way:
#echo off
setlocal EnableDelayedExpansion
set "string=aa\bb\cc\dd\"
set "str=A%string%Z"
set "count=-1"
for /F "delims=" %%a in (^"!str:\^=^"^
% Do NOT remove this line %
^"!^") do (
set /A count+=1
)
echo %count%
While slow, you can try with this
#echo off
setlocal enableextensions disabledelayedexpansion
set "inputFile=input.txt"
set "searchChar=\"
for /f "delims=" %%a in ('
findstr /n "^" "%inputFile%"
') do for /f "delims=:" %%b in ("%%~a") do (
set "line=%%a"
for /f %%c in ('
cmd /u /v /e /q /c"(echo(!line:*:=!)"^|find /c "%searchChar%"
') do echo Line %%b has %%c characters
)
The input file is readed using findstr /n to get all the lines in the file with a number prefix (both for output "decoration" and to ensure all the lines in the file are processed). Each line is processed inside a pipe, from cmd to find. The cmd instance is started with unicode output (/u) so when the readed line is echoed, the output will be a two bytes sequence for each input character, one of them a 0x0 ASCII character. The find command sees the 0 as a line terminator, so we get each character in the input line as one separated line. Now, the find command counts in how many lines the searched character happens.
#ECHO OFF
SETLOCAL
SET "String=a\b\c\\\\d"
CALL :count "%string%" \
ECHO %tally%
GOTO :EOF
:count
SETLOCAL enabledelayedexpansion
SET /a tally=0
SET "$2=%~1"
:cloop
SET "$1=%$2%"
SET "$2=!$1:*%2=!"
IF "%$1%" neq "%$2%" SET /a tally+=1&GOTO cloop
endlocal&SET tally=%tally%
GOTO :eof
Here's a way to count particular characters in a string. It won't work for the usual suspects.
here's one way:
#echo off
:checkCountOf string countOf [rtnrVar]
:: checks count of a substring in a string
setlocal EnableDelayedExpansion
set "string=aa"
set "string=%~1"
set "checkCountOf=%~2"
if "%~1" equ "" (
if "%~3" neq "" (
endlocal & (
echo 0
set "%~3=0"
exit /b 0
)
) else (
endlocal & (
echo 0
exit /b 0
)
)
)
if "!checkCountOf!" equ "$" (
set "string=#%string%#"
set "string=!string:%checkCountOf%%checkCountOf%=#%checkCountOf%#%checkCountOf%#!"
) else (
set "string=$%string%$"
set "string=!string:%checkCountOf%%checkCountOf%=$%checkCountOf%$%checkCountOf%$!"
)
set LF=^
rem ** Two empty lines are required
set /a counter=0
for %%L in ("!LF!") DO (
for /f "delims=" %%R in ("!checkCountOf!") do (
set "var=!string:%%~R%%~R=%%~L!"
set "var=!var:%%~R=%%~L!"
for /f "tokens=* delims=" %%# in ("!var!") do (
set /a counter=counter+1
)
)
)
if !counter! gtr 0 (
set /a counter=counter-1
)
if "%~3" neq "" (
endlocal & (
echo %counter%
set "%~3=%counter%"
)
) else (
endlocal & (
echo %counter%
)
)
you can call it like:
call ::checkCountOf "/aa/b/c/" "/" slashes
echo %slashes%
exit /b %errorlevel%
wont work with some special characters (",~ and !)
You can also use replacement and the :strlen function
Not tested extensively but works with your example.
#ECHO OFF
SETLOCAL disabledelayedexpansion
SET "String=\a\b\c\\\\d\\"
set "previous=%string%"
set /a count=0
:loop
set "newstg=%previous:*\=%"
IF NOT "%previous%"=="%newstg%" (
set /a count+=1
set "previous=%newstg%"
IF DEFINED previous goto loop
)
echo %count%
pause
GOTO :eof
Here is one more option. I don't think this is bullet proof with poison characters.
#ECHO OFF
SETLOCAL disabledelayedexpansion
SET "String=\\a\b\c\\\\d\\"
set i=0
set "x=%string%"
set "x=%x:\=" & set /A i+=1 & set "x=%"
echo %i%
pause
I am new to using batch files so could someone please help me split a string i am getting from a file.
I am using %USERPROFILE% to get my string.
The string is: "C:\Users\nicholas"
I would like to keep the C:\ part, but get rid of the \Users\nicholas part.
for /f "tokens=2 delims=\" %A in ('set userprofile') do echo %A\
See for /?
Also
echo %userprofile:~0,3%
If you carefully read the output of set /?, you'll find the answer:
May also specify substrings for an expansion.
%PATH:~10,5%
would expand the PATH environment variable, and then use only the 5
characters that begin at the 11th (offset 10) character of the expanded
result.
So, you can use something like this to get the first 3 characters of your string:
> echo %userprofile:~0,3%
C:\
I As you need the drive where where the users are located you can use directly
%systemdrive% variable - this is the drive where the windows is installed
II the easiest way to get a drive from path:
for %%a in ("%userprofile%") do echo %%~da\
%~da - expands a path to its drive only
III over-complicated but powerful way (split function that can be used for a different things):
#echo off
call :split "%userprofile%" "\" 1 drive
echo %drive%\
goto :eof
:split [%1 - string to be splitted;%2 - split by;%3 - possition to get; %4 - if defined will store the result in variable with same name]
::http://ss64.org/viewtopic.php?id=1687
setlocal EnableDelayedExpansion
set "string=%~2%~1"
set "splitter=%~2"
set /a position=%~3
set LF=^
rem ** Two empty lines are required
echo off
for %%L in ("!LF!") DO (
for /f "delims=" %%R in ("!splitter!") do (
set "var=!string:%%~R%%~R=%%~L!"
set "var=!var:%%~R=%%~L!"
if "!var!" EQU "!string!" (
echo "%~1" does not contain "!splitter!" >&2
exit /B 1
)
)
)
if "!var!" equ "" (
endlocal & if "%~4" NEQ "" ( set "%~4=")
)
if !position! LEQ 0 ( set "_skip=" ) else (set "_skip=skip=%position%")
for /f "eol= %_skip% delims=" %%P in (""!var!"") DO (
if "%%~P" neq "" (
set "part=%%~P"
goto :end_for
)
)
set "part="
:end_for
if not defined part (
endlocal
echo Index Out Of Bound >&2
exit /B 2
)
endlocal & if "%~4" NEQ "" (set %~4=%part%) else echo %part%
exit /b 0
I have an input let's say:
DummyString-v1.0.0
and I would like to extract everything after v to get: 1.0.0
How can I achieve this in CMD?
#ECHO OFF
SETLOCAL
SET "string=DummyString-v1.0.0"
SET "string=%string:*v=%"
ECHO %string%
GOTO :EOF
where the :*substring=replacementstring syntax is applied meaning "every character form the start of the variable (string) up to the substring (v) is replaced by the replacement string (nothing).
#echo off
call :split "DummyString-v1.0.0" "v"
echo %last_part%
exit /b 0
:split
set "string=%~1"
set "splitter=%~2"
setlocal EnableDelayedExpansion
set LF=^
rem ** Two empty lines are required
echo off
for %%L in ("!LF!") DO (
for /f "delims=" %%R in ("!splitter!") do (
set "var=!string:%%R=%%L!"
)
)
for /f "delims=" %%P in (""!var!"") DO set "last_part=%%~P"
endlocal &(
set last_part=%last_part%
exit /b 0
)
Good Afternoon!
Long time reader, first time poster! I have been having a lovely time trying to modify a working batch file to account for variability. The situation is that I have a variable-size text document that normally would be able to be split into sections of 252 lines. The code below worked like a champ:
#echo off & setlocal EnableDelayedExpansion
set param=%*
if not defined param (
echo.
echo. Usage: batchsplit [device:][pathname]filename
goto :EOF
)
set param=%param:"=%
if not exist "%param%" (
echo.
echo. File "%param%" not found
goto :EOF
)
for %%j in ("%param%") do (
set name=%%~dpnj
set ext=%%~xj
)
for /F %%j in ('type "%param%" ^| find /V /C ""') do set Full=%%j
set /A Split=%Full%/252
for /L %%G in (1,1,%Split%) do type nul > "%name%_%%G%.new"
set X=1
set N=1
set Q=1
set limit = 252
for /F "tokens=1* delims=]" %%j in ('type "%param%" ^| find /V /N ""') do (
set /A N+=1
set /A Q+=1
echo.%%k>> "%name%_!X!%.new"
if !Q! gtr 252 (
set /A X+=1
set /A Q=1
) else if !N! gtr Full (goto theend
)
)
:theend
echo split into %split% files with 252 lines each
rem pause
However, there were some changes to the formatting of the text, and now instead of four pages of 63 lines per split file, it can be completely variable. The only constant is this final line, which precedes the remaining space for a 63 line page:
ON THIS FORM IS COMPLETE AND CORRECT AS NOTED:___________________
Note that there is a single space in front of it, as well as multiple spaces, a colon, and underscore characters. Being the meathead that I am, I thought I could insert an if-then statement into the for loop to trigger the batch to split to the next page. However, I could be further from that right now. This is the code I have been smashing my head with:
rem #echo off & setlocal EnableDelayedExpansion
setlocal EnableDelayedExpansion
set param=%*
if not defined param (
echo.
echo. Usage: textsplit [device:][pathname]filename
goto :EOF
)
set param=%param:"=%
if not exist "%param%" (
echo.
echo. File "%param%" not found
goto :EOF
)
for %%j in ("%param%") do (
set Name=%%~dpnj
set ext=%%~xj
)
for /F %%j in ('type "%param%" ^| find /V /C ""') do set Full=%%j
set stopvar= ON THIS FORM IS COMPLETE AND CORRECT AS NOTED:___________________
set Split=1
echo %stopvar%
set X=1
type nul > "%name%_!X!%.new"
set N=1
set Q=1
set S=0
set L=63
for /F "tokens=1* delims=]" %%j in ('type "%param%" ^| find /V /N ""') do (
set /A N+=1
echo %N%
set /A Q+=1
echo %Q%
echo.%%k>> "%name%_!X!%.new"
if ["%%k%" == "!stopvar!"] (
set /A S+=1
)
if !Q! gtr !L! (
if !S! == 1 (
set /A X+=1
set /A Q=1
type nul > "%name%_!X!%.new"
set /A Split+=1
set S=0
)
else set /A L+=63
else if !N! gtr Full goto theend
)
:theend
echo Split into %split% files!
pause
The premise is that every 63 lines, the stop variable (S) is checked. If it is off (0) then the batch will continue to write for another 63 lines (one page). If the stopvar matches the line that is being read by the for loop, S becomes 1. When the program checks again, it will create a new file and begin writing to that new file. Right now, based on turning off #echo off the hangup is at the for loop. See below:
C:\Users\theangryasiancp\Desktop\TEXT_Split_Test>rrtextsplit texttest.txt
C:\Users\theangryasiancp\Desktop\TEXT_Split_Test>rem #echo off & setlocal Enabl
eDelayedExpansion
C:\Users\theangryasiancp\Desktop\TEXT_Split_Test>setlocal EnableDelayedExpansio
n
C:\Users\theangryasiancp\Desktop\TEXT_Split_Test>set param=texttest.txt
C:\Users\theangryasiancp\Desktop\TEXT_Split_Test>if not defined param (
echo.
echo. Usage: rrtextsplit [device:][pathname]filename
goto :EOF
)
C:\Users\theangryasiancp\Desktop\TEXT_Split_Test>set param=texttest.txt
C:\Users\theangryasiancp\Desktop\TEXT_Split_Test>if not exist "texttest.txt" (
echo.
echo. File "texttest.txt" not found
goto :EOF
)
C:\Users\theangryasiancp\Desktop\TEXT_Split_Test>for %j in ("texttest.txt") do
(
set Name=%~dpnj
set ext=%~xj
)
C:\Users\theangryasiancp\Desktop\TEXT_Split_Test>(
set Name=C:\Users\theangryasiancp\Desktop\TEXT_Split_Test\texttest
set ext=.txt
)
C:\Users\theangryasiancp\Desktop\TEXT_Split_Test>for /F %j in ('type "texttest.
txt" | find /V /C ""') do set Full=%j
C:\Users\theangryasiancp\Desktop\TEXT_Split_Test>set Full=567
C:\Users\theangryasiancp\Desktop\TEXT_Split_Test>set stopvar= ON THIS FORM IS C
OMPLETE AND CORRECT AS NOTED:___________________
C:\Users\theangryasiancp\Desktop\TEXT_Split_Test>set Split=1
C:\Users\theangryasiancp\Desktop\TEXT_Split_Test>echo ON THIS FORM IS COMPLETE
AND CORRECT AS NOTED:___________________
ON THIS FORM IS COMPLETE AND CORRECT AS NOTED:___________________
C:\Users\theangryasiancp\Desktop\TEXT_Split_Test>set X=1
C:\Users\theangryasiancp\Desktop\TEXT_Split_Test>type nul 1>"C:\Users\theangry
asiancp\Desktop\RRRR_Split_Test\texttest_!X!.new"
C:\Users\theangryasiancp\Desktop\TEXT_Split_Test>set N=1
C:\Users\theangryasiancp\Desktop\TEXT_Split_Test>set Q=1
C:\Users\theangryasiancp\Desktop\TEXT_Split_Test>set S=0
C:\Users\theangryasiancp\Desktop\TEXT_Split_Test>set L=63
C:\Users\theangryasiancp\Desktop\TEXT_Split_Test>
What are your thoughts? Where am I going wrong with the batch? I wish I could use something different, but alas I cannot, for internal company reasons. Thanks for your help!
#ECHO OFF
SETLOCAL ENABLEDELAYEDEXPANSION
SET "name=q23396663"
SET "ext=.txt"
SET /a pagelength=10
SET "targetstring= ON THIS FORM IS COMPLETE AND CORRECT AS NOTED:___________________"
SET /a filenum=0
SET /a linecount=pagelength + 1
FOR /f "tokens=1*delims=]" %%a IN (
'find /v /n "" "%name%%ext%"') DO (
IF !linecount! GEQ %pagelength% (
SET /a linecount=0
SET /a filenum+=1
)
>>U:\%name%_!filenum!.new ECHO(%%b
IF "%%b"=="%targetstring%" SET /a linecount=pagelength
SET /a linecount+=1
)
GOTO :EOF
For testing purposes, I set up a file q23396663.txt containing your trigger data. I've left the destination directory as U:\ which suits me, and the pagelength at 10 which makes my testing easier.
#echo off
setlocal EnableDelayedExpansion
REM ------------------THIS SECTION SPECIFIES THE FILE-------------------------
set param=%*
if not defined param (
echo.
echo. Usage: filesplit [device:][pathname]filename
goto :EOF
)
set param=%param:"=%
if not exist "%param%" (
echo.
echo. File "%param%" not found
GOTO :EOF
)
for %%j in ("%param%") do (
set name=%%~dpnj
set ext=%%~xj
)
ECHO SPLITTING %name%.%ext% .................
REM ----------------THIS SECTION SETS THE VARIABLES---------------------------
set "trigger= ON THIS FORM IS COMPLETE AND CORRECT AS NOTED:___________________"
set /a pagelength=63
set /a filenum=0
set split=1
set /a linecount=pagelength
set stopvar=0
REM ------------------THIS SECTION IS THE FOR LOOP----------------------------
FOR /f "skip=2 tokens=1* delims=]" %%a IN (
'find /v /n "" "%name%%ext%"') DO (
SET /a linecount-=1
IF !linecount! LEQ 0 (
IF !stopvar! EQU 1 (
SET /a "linecount=pagelength"
SET /a filenum+=1
SET /a split+=1
SET /a stopvar-=1
) else set /a "linecount=pagelength"
)
echo.%%b>> "%name%_!filenum!.new"
IF "%%b"=="%trigger%" (
set /a "stopvar+=1"
REM THIS TRIGGERS TO CHANGE OUTPUT
set /a linecount+=1
REM THIS WILL ADJUST THE OUTPUT EOF
)
)
REM ----------------THIS SECTION ENDS THE FOR LOOP----------------------------
ECHO Split into %split% files!
ping 1.1.1.1 -n 1 -w 2500 > nul
REM THIS PAUSES THE BATCH FOR A SEC
As I posted in the comment above, this is just a variation of the first answer that accounts for blank spaces if they are needed to keep the output files from having unnecessary whitespace on top. This is especially helpful when a print manager is just spitting out whitespace until the end of the page before starting the next part instead of going straight to the next portion.
In Linux (Bash) there is a very useful functionality for dumping literal text out to another file like this:
cat > see.txt << EOF
contents going into
my file
EOF
What I need is the equivalent for a Windows batch script. I haven't found this kind of functionality built-in, but I was thinking I could write a subroutine to do it (I don't want to rely on anything that isn't natively in Windows since XP), but I'm having trouble. Here's what I have so far with help from various sources:
call:catMyChunk myCustomText c:\see.txt
exit /b
goto:myCustomText
This is my test file
Hope you like it.
<got these>
% and these %
! these too
yeah
:myCustomText
:catMyChunk
::Should call this function with 2 args, MYDELIM and outFile.txt
::where is to be catted to outFile.txt
::and text starts with <beginning of line>goto:MYDELIM
::and ends with <beginning of line>:MYDELIM
set searchStart=goto:%~1
set searchStop=:%~1
set outFile=%~2
set startLine=0
set endLine=0
for /f "delims=:" %%a in ('findstr -b -n !searchStart! %~dpnx0') do set "startLine=%%a"
for /f "delims=:" %%a in ('findstr -b -n !searchStop! %~dpnx0') do set "endLine=%%a"
set /a linesLeftToRead=%endLine% - %startLine%
del %outFile%
if "%linesLeftToRead%" LEQ "0" (
echo Error finding start and end delmieters for %searchStop% in catMyChunk routine
exit /B 1
)
setlocal DisableDelayedExpansion
for /f "usebackq skip=%startLine% delims=" %%a in (`"findstr /n ^^ %~dpnx0"`) do (
set "oneLine=%%a"
setlocal EnableDelayedExpansion
set "oneLine=!oneLine:*:=!"
set /a linesLeftToRead-=1
if !linesLeftToRead! LEQ 0 exit /B
echo(!oneLine!>>%outFile%
)
goto: EOF
So my requirement is that the chunk of text is output literally without any changes whatsoever (i.e. I don't want to have to escape blank lines, %, !, <, etc.). This code is doing almost everything I need, except I haven't found a way to get the exclamation points output properly. Here is the output I get, which isn't quite right:
This is my test file
Hope you like it.
<got these>
% and these %
these too
yeah
Edit:
For anyone wanting the modified version of the subroutine that now works, here it is:
:catMyChunk
::Should call this function with 2 args, MYDELIM and outFile.txt
::where is to be catted to outFile.txt
::and text starts with <beginning of line>goto:MYDELIM
::and ends with <beginning of line>:MYDELIM
set searchStart=goto:%~1
set searchStop=:%~1
set outFile=%~2
set startLine=0
set endLine=0
for /f "delims=:" %%a in ('findstr -b -n !searchStart! %~dpnx0') do set "startLine=%%a"
for /f "delims=:" %%a in ('findstr -b -n !searchStop! %~dpnx0') do set "endLine=%%a"
set /a linesLeftToRead=%endLine% - %startLine%
del %outFile%
if "%linesLeftToRead%" LEQ "0" (
echo Error finding start and end delmieters for %searchStop% in catMyChunk routine
exit /B 1
)
setlocal DisableDelayedExpansion
for /f "usebackq skip=%startLine% delims=" %%a in (`"findstr /n ^^ %~dpnx0"`) do (
set "oneLine=%%a"
set /a linesLeftToRead-=1
setlocal EnableDelayedExpansion
set "oneLine=!oneLine:*:=!"
if !linesLeftToRead! LEQ 0 exit /B
echo(!oneLine!>>%outFile%
endlocal
)
endlocal
goto: EOF
Your code is missing a single endlocal in your FOR-loop.
You will then create for each loop a new local-context through the setlocal EnableDelayedExpansion, this will explode with some more lines in your text file.
Also, to preserve the exclamation marks (and also the carets) you need the toggling technique:
DOS batch files: How to read a file?
setlocal DisableDelayedExpansion
for /f "usebackq skip=%startLine% delims=" %%a in (`"findstr /n ^^ %~dpnx0"`) do (
set "oneLine=%%a"
setlocal EnableDelayedExpansion
set "oneLine=!oneLine:*:=!"
set /a linesLeftToRead-=1
if !linesLeftToRead! LEQ 0 exit /B
echo(!oneLine!>>%outFile%
endlocal
)
+1, Interesting application! I modified your code for a simpler and faster version:
#echo off
call :catMyChunk myCustomText see.txt
exit /b
goto:myCustomText
This is my test file
Hope you like it.
<got these>
% and these %
! these too
yeah
:myCustomText
:catMyChunk
::Should call this function with 2 args, MYDELIM and outFile.txt
::where is to be catted to outFile.txt
::and text starts with <beginning of line>goto:MYDELIM
::and ends with <beginning of line>:MYDELIM
setlocal DisableDelayedExpansion
set searchStart=goto:%~1
set searchStop=:%~1
set outFile=%~2
if exist %outFile% del %outFile%
set copyFlag=No
echo No> copyFlag
for /f "delims=" %%a in (%~f0) do (
set "oneLine=%%a"
setlocal EnableDelayedExpansion
if !copyFlag! == No (
if !oneLine! == %searchStart% echo Yes> copyFlag
) else (
if !oneLine! == %searchStop% (
echo No> copyFlag
) else (
echo/!oneLine!>> %outFile%
)
)
endlocal
set /p copyFlag=< copyFlag
)
endlocal
goto :eof
I also created another version that looks more like the Linux version, that is, the lines to copy are placed directly after invoking the routine, and the execution continue after the last copied line. Of course, to make this possible the routine is not invoked via call, but entered with a goto, and when the routine ends it execute a goto %MYDELIM% instead of a "return" (exit /b or goto :eof). Also, because a goto can not have parameters, the "parameters" are defined in variables before the invocation.
#echo off
set searchStop=EndOfMyText
set outFile=see.txt
goto :catMyChunk EndOfMyText
This is my test file
Hope you like it.
<got these>
% and these %
! these too
yeah
:EndOfMyText
exit /b
:catMyChunk
::Before JUMP to this "function" define 2 vars: searchStop and outFile
::where is to be catted to %outFile%
::and text starts with goto :catMyChunk %searchStop%
::and ends with :%searchStop%
setlocal DisableDelayedExpansion
set searchStart=goto :catMyChunk %searchStop%
if exist %outFile% del %outFile%
set copyFlag=No
echo No> copyFlag
for /f "delims=" %%a in (%~f0) do (
set "oneLine=%%a"
setlocal EnableDelayedExpansion
if !copyFlag! == No (
if /I !oneLine! == !searchStart! echo Yes> copyFlag
) else (
if !oneLine! == :%searchStop% (
echo No> copyFlag
) else (
echo/!oneLine!>> %outFile%
)
)
endlocal
set /p copyFlag=< copyFlag
)
endlocal
goto %searchStop%
EDIT
This new version is even faster and now works with all special cases, including empty lines:
:catMyChunk
::Should call this function with 2 args, MYDELIM and outFile.txt
::where is to be catted to outFile.txt
::and text starts with <beginning of line>goto:MYDELIM
::and ends with <beginning of line>:MYDELIM
setlocal EnableDelayedExpansion
set searchStart=goto:%~1
set searchStop=:%~1
set outFile=%~2
if exist %outFile% del %outFile%
findstr /n ^^ "%~f0" > pipeline.txt
call :seekMyChunk < pipeline.txt
del pipeline.txt
exit /B
:seekMyChunk
set oneLine=:EOF
set /P oneLine=
if !oneLine! == :EOF goto startNotFound
set oneLine=!oneLine:*:=!
if not !oneLine! == %searchStart% goto seekMyChunk
:catNextLine
set oneLine=:EOF
set /P oneLine=
if !oneLine! == :EOF goto stopNotFound
set oneLine=!oneLine:*:=!
if !oneLine! == %searchStop% goto :eof
echo/!oneLine!>> %outFile%
goto catNextLine
:startNotFound
echo Error finding start delimiter for %searchStart% in catMyChunk
goto :eof
:stopNotFound
echo Error finding stop delimiter for %searchStop% in catMyChunk
goto :eof