Batch script - split by two or more spaces - string

I have a large string that looks like this
aa bb c d f eeeee ffff
I am not sure of the number of spaces that would come along with the string. The next time, string might have five spaces between aa and bb ansd so on...
aa bb c f eee ff
Is there a way I can split on two or more spaces (variable number of spaces) using delims in batch script?I always want the second token no matter how any spaces are there between first and second tokens.
PS : Edit
I want the second token (by token I mean the token obtained after splitting by exactly two or more spaces). My token itself can contain a single space. Example: a b c d.
In this want to extract b.
Another example: a b b c d .
In this I want to extract b b.

(Collected from various comments: Tokens are delimited by two or more spaces; You need the second token; Every token may or may not have single spaces)
I commented every step and added an echo to show progress.
#echo off
setlocal
set "string=ads ads d b c dsad ds ads"
echo 1: "%string%"
REM remove first token (delimited by ecactly two spaces):
set "string=%string:* =%"
echo 2: "%string%"
REM remove leading spaces:
for /f "tokens=*" %%a in ("%string%") do set "string=%%a"
echo 3: "%string%"
REM remove third and next tokens (delimited by two or more spaces)
set string=%string: =&REM %
echo 4: "%string%"
Only change to my answer to your previous question is the for to remove leading spaces

Notice for future readers:
This answer was posted before OP added the significant text I always want the second token no matter how any spaces are there between first and second tokens.
Please read the original question before voting/commenting
No.
delims means that tokens are separated by any sequence of any of the defined delimiter characters.
Hence your example string would be processed as 7 separate tokens.
If you were to tell us what you are attempting to do,it may help. The approach taken often depends on the desired results.
Probably this is associated with https://stackoverflow.com/a/48808982/2128947 to which you have yet to respond.

This is how I implemented this incase anyone is interested.
#echo off
echo.
set "string=Admin State State Type Interface Name"
echo Parse "%string%"
REM: Parse string delimited by more than one space
for %%R in ("%string: =" "%") do (
REM: Filter empty
if not "%%~R" == "" (
REM: Trim leading space
for /f "tokens=*" %%a in ("%%~R") do set "part=%%a"
call echo - "%%part%%"
)
)
echo.
goto :eof
Output:
Parse "Admin State State Type Interface Name"
- "Admin State"
- "State"
- "Type"
- "Interface Name"

This is the way I would do it:
#echo off
setlocal
set "string=ads ads d b c dsad ds ads"
set "tok1=%string: =" & if not defined tok2 set "tok2=%"
for /F "tokens=*" %%a in ("%tok2%") do set "tok2=%%a"
echo "%tok2%"
If you want to understand the method used, remove the #echo off line and carefully review the executed code...

Related

How to use FINDSTR to find a string in value of a variable?

Is it possible to use FINDSTR with a variable instead of a file?
I've tried researching this but my knowledge on batch isn't good enough yet.
I've made most of my batch file work flawlessly for what I need, but I'm having trouble extracting part of a string variable into a new variable.
The original variable for example would be S02E12 - Charge!. I would like to extract 02 to ep_seas, 12 to ep_num, and Charge! to ep_name. I have it working right now if that's the exact pattern of names, but I've come across some files that are in this pattern: S02E124 - Charge #2!
Is there a way I can dynamically get the values I need regardless of their length?
My idea was to use FINDSTR to search between the S and the E, then between E and space   (or -), then between - and the end. I'm not sure how I would proceed with this though.
Does anyone have a solution I can look into or can someone provide an example?
setlocal DisableDelayedExpansion
set mkvmerge="C:/Program Files/MKVToolNix/mkvmerge.exe"
set "output_folder=%cd%\Muxing"
for /r %%a in (*.mkv) do (
set fi=%%a
set ep=%%~na
call :merge
)
goto :eof
:merge
set ep_name=%ep:~9%
set ep_num=%ep:~4,2%
set ep_seas=%ep:~2,1%
call %mkvmerge% -o "%output_folder%\%ep%.mkv" --track-name "0:%ep_name%" --language 0:und --default-track 0:yes --track-name "1:[JAP]" --language 1:jpn --default-track 1:yes --track-name "2:[ENG]" --language 2:eng --default-track 2:yes --forced-track 2:yes "%fi%" --track-order 0:0,0:1,0:2 --title "Fate Zero - Episode %ep_num%"
goto :eof
You could use this code for your task:
#echo off
setlocal EnableExtensions DisableDelayedExpansion
set "output_folder=%CD%\Muxing"
for /R %%I in (*.mkv) do (
set "fi=%%I"
set "ep=%%~nI"
call :merge
)
goto :EOF
:merge
for /F "tokens=1,2* delims=ES- " %%A in ("%ep%") do (
set "ep_seas=%%A"
set "ep_num=%%B"
set "ep_name=%%C"
)
"%ProgramFiles%\MKVToolNix\mkvmerge.exe" -o "%output_folder%\%ep%.mkv" --track-name "0:%ep_name%" --language 0:und --default-track 0:yes --track-name "1:[JAP]" --language 1:jpn --default-track 1:yes --track-name "2:[ENG]" --language 2:eng --default-track 2:yes --forced-track 2:yes "%fi%" --track-order 0:0,0:1,0:2 --title "Fate Zero - Episode %ep_num%"
goto :EOF
The command FOR /F as used here parses the string in double quotes which is the file name.
The option "tokens=1,2* delims=ES- " results in splitting up the file name strings S02E12 - Charge! and S02E124 - Charge #2! into 3 substrings using the 4 specified characters as delimiters.
Token 1 is 02 for both file names assigned to specified loop variable A.
Token 2 is 12 and 124 for the two file names assigned to next loop variable after A according to ASCII table which is B.
Token 3 is everything after the delimiters after token 2 which is Charge! and Charge #2! for the two files assigned to next but one loop variable C.
You know now why loop variables are case-sensitive while environment variables are not case-sensitive. The specified loop variable defines which characters the next loop variables have on using for /F with more than one token/substring.
* appended to a token number X means rest of the string/line after the delimiters after substring X should be assigned to next loop variable without any further splitting on delimiters. So the string after S02E12 -  and S02E124 -  can contain also delimiter characters and is nevertheless assigned completely to loop variable C.
Please note that a string starting with E or S after hyphen and space is interpreted also as delimiter and therefore missing in string assigned to loop variable C. The FOR loop in subroutine merge could be replaced by following code to get a working solution for a file name like S03E48 - Extended Version.mkv.
for /F "tokens=1* delims=- " %%A in ("%ep%") do (
set "ep_name=%%B"
for /F "tokens=1,2 delims=ES" %%C in ("%%A") do (
set "ep_seas=%%C"
set "ep_num=%%D"
)
)
The outer loop assigns S03E48 to loop variable A and Extended Version to loop variable B. The inner loop splits up S03E48 once again into 03 assigned to loop variable C and 48 assigned to loop variable D.
By the way: The directory separator on Windows is \ and not / as on Unix/Linux/Mac although Windows kernel functions support also file/directory paths with / with automatic correction to \.
For understanding the used commands and how they work, open a command prompt window, execute there the following commands, and read entirely all help pages displayed for each command very carefully.
call /?
echo /?
endlocal /?
for /?
goto /?
set /?
setlocal /?
One last note:
It is possible to use FINDSTR on an environment variable for example with:
echo %ep% | findstr /R "^S[0123456789][0123456789]"
But FINDSTR never outputs just the matching string. It always outputs the entire line containing the found string. So FINDSTR is of no help here for splitting the file name up into separate parts.

Substitute substring in cmd window

I have the following string in a batch file script:
ABCE#$1 TroubleMaker[FFFFF A=MyCountry US=1 CA=1 JP=1 EU=1
and it's stored in _var,when I do
set _var=%_var:* A=% - it cuts all the characters before " A" (including the 'A') and i'm left with =MyCountry US=1 CA=1 JP=1 EU=1
how can I change the set command to cut also the = mark from the string?
tried set _var=%_var:*==% - didn't work.
Thanks.
#ECHO OFF
SETLOCAL
SET "string=ABCE#$1 TroubleMaker[FFFFF A=MyCountry US=1 CA=1 JP=1 EU=1"
FOR /f "tokens=1*delims==" %%s IN ("%string%") DO SET "string=%%t"
ECHO "%string%"
GOTO :EOF
This assumes that you want to delete up to and including the initial =
The = disturbs the substring replacement syntax, because it contains a = on its own.
You could go for the following work-around:
set _var=%_var:* A=%
set _var=%_var:~1%
The second line constitutes the substring expansion syntax (type set /? for details), which splits off the first character, that is supposed to be a =.
This of course works only if the = immediately follows the A substring.
You can check whether the first character is = before removing it, like:
set _var=%_var:* A=%
if "%_var:~,1%"=="=" set _var=%_var:~1%
If you just want to search for the (first) = character and to ignore the A substring, you could establish a loop structure like this:
:LOOP
if "%_var%"=="" goto :END
if "%_var:~,1%"=="=" (
set _var=%_var:~1%
goto :END
) else (
set _var=%_var:~1%
goto :LOOP
)
:END
This cuts off the first character and checks whether it is a =. If it is, the remaining string is stored in _var and the loop is left; if not, the loop continues checking the next character. The first line is inserted to not hang in case the string does not contain a = character.

Find string with special character in text file and add line break before each occurrence

I have a text file that is one long string like this:
ISA*00*GARBAGE~ST*TEST*TEST~CLP*TEST~ST*TEST*TEST~CLP*TEST~ST*TEST*TEST~CLP*TEST~GE*GARBAGE*~
And I need it to look like this:
~ST*TEST*TEST~CLP*TEST
~ST*TEST*TEST~CLP*TEST
~ST*TEST*TEST~CLP*TEST
I first tried to add a line at every ~ST to split the string up, but I can't for the life of me make this happen. I have tried various scripts, but I thought a find/replace script would work best.
#echo off
setlocal enabledelayedexpansion
set INTEXTFILE=test.txt
set OUTTEXTFILE=test_out.txt
set SEARCHTEXT=~ST
set REPLACETEXT=~ST
for /f "tokens=1,* delims=~" %%A in ( '"type %INTEXTFILE%"') do (
SET string=%%A
SET modified=!string:%SEARCHTEXT%=%REPLACETEXT%!
echo !modified! >> %OUTTEXTFILE%
)
del %INTEXTFILE%
rename %OUTTEXTFILE% %INTEXTFILE%
Found here How to replace substrings in windows batch file
But I'm stuck because (1) the special character ~ makes the code not work at all. It gives me this result:
string:~ST=~ST
The code does nothing at all if using quotes around "~ST". And (2) I can't figure out how to add a line break before ~ST.
The final task for this would be to delete the ISA*00*blahblahblah and ~GE*blahblahblah lines after all splits have been performed. But I am stuck on the splitting at ~ST part.
Any suggestions?
#echo off
setlocal EnableDelayedExpansion
rem Set next variable to the number of "~" chars that delimit the wanted fields, or more
set "maxTokens=7"
rem Define the delimiters that starts a new field
set "delims=/ST/GE/"
for /F "delims=" %%a in (test.txt) do (
set "line=%%a"
set "field="
rem Process up to maxTokens per line;
rem this is a trick to avoid a call to a subroutine that have a goto loop
for /L %%i in (0,1,%maxTokens%) do if defined line (
for /F "tokens=1* delims=~" %%b in ("!line!") do (
rem Get the first token in the line separated by "~" delimiter
set "token=%%b"
rem ... and update the rest of the line
set "line=%%c"
rem Get the first two chars after "~" token like "ST", "CL" or "GE";
rem if they are "ST" or "GE":
for %%d in ("!token:~0,2!") do if "!delims:/%%~d/=!" neq "%delims%" (
rem Start a new field: show previous one, if any
if defined field echo !field!
if "%%~d" equ "ST" (
set "field=~%%b"
) else (
rem It is "GE": cancel rest of line
set "line="
)
) else (
rem It is "CL" token: join it to current field, if any
if defined field set "field=!field!~%%b"
)
)
)
)
Input:
ISA*00*GARBAGE~ST*TEST1*TEST1~CLP*TEST1~ST*TEST2*TEST2~CLP*TEST2~ST*TEST3*TEST3~CLP*TEST3~GE*GARBAGE*~CLP~TESTX
Output:
~ST*TEST1*TEST1~CLP*TEST1
~ST*TEST2*TEST2~CLP*TEST2
~ST*TEST3*TEST3~CLP*TEST3
Don't reinvent the wheel, use a regexp replace tool such as sed or JREPL.BAT:
call jrepl "^.*?~ST(.+?)~GE.*$" "'~ST'+$1.replace(/~ST/g,'\r\n$&')" /jmatch <in.txt >out.txt
The ~ cannot be used as the first character of a search string in the substring substitution syntax %VARIABLE:SEARCH_STRING=REPLACE_STRING%, because it is used to mark the substring expansion %VARIABLE:~POSITION,LENGTH% (type set/? for more information).
Supposing your text file contains a single line of text only and it does not exceed a size of about 8 kBytes, I see the following option for accomplishing your task. This script makes use of the substring substitution syntax %VARIABLE:*SEARCH_STRING=REPLACE_STRING%; the * defines to match everything up to the first occurrence of SEARCH_STRING:
#echo off
setlocal EnableExtensions EnableDelayedExpansion
rem initialise constants:
set "INFILE=test_in.txt"
set "OUTFILE=test_out.txt"
set "SEARCH=ST"
set "TAIL=GE"
rem read single-line file content into variable:
< "%INFILE%" set /P "DATA="
rem remove everything before first `~%SEARCH%`:
set "DATA=~%SEARCH%!DATA:*~%SEARCH%=!"
rem call sub-routine, redirect its output:
> "%OUTFILE%" call :LOOP
endlocal
goto :EOF
:LOOP
rem extract portion right to first `~%SEARCH%`:
set "RIGHT=!DATA:*~%SEARCH%=!"
rem skip rest if no match found:
if "!RIGHT!"=="!DATA!" goto :TAIL
rem extract portion left to first `~%SEARCH%`, including `~`:
set "LEFT=!DATA:%SEARCH%%RIGHT%=!"
rem the last character must be a `~`;
rem so remove it; `echo` outputs a trailing line-break;
rem the `if` avoids an empty line at the beginning;
rem the unwanted part at the beginning is removed implicitly:
if not "!LEFT:~,-1!"=="" echo(!LEFT:~,-1!
rem output `~%SEARCH%` without trailing line-break:
< nul set /P "DUMMY=~%SEARCH%"
rem store remainder for next iteration:
set "DATA=!RIGHT!"
rem loop back if remainder is not empty:
if not "!DATA!"=="" goto :LOOP
:TAIL
rem this section removes the part starting at `~%TAIL%`:
set "RIGHT=!DATA:*~%TAIL%=!"
if "!RIGHT!"=="!DATA!" goto :EOF
set "LEFT=!DATA:%TAIL%%RIGHT%=!"
rem output part before `~%TAIL%` without trailing line-break:
< nul set /P "DUMMY=!LEFT:~,-1!"
goto :EOF
The following restrictions apply to this approach:
the input file contains a single line;
the size of the input file does not exceed about 8 kBytes;
there is exactly one instance of ~GE, that occurs after all instances of ~ST;
there is always at least one character in between two adjacent ~ST instances;
no special characters occur in the file, like: SPACE, TAB, ", %, !, =;

batch/txt: How to replace a substring-matched data block with modified input from a file

A user defined input file has to be integrated into the entries of pre-set output file,
changing the data at a given block [A-Z] and index [1-75] position.
The input file structure with wildcards for variable parts:
* 00asr[1-75] 00asr*
b -v -o 00asr34 00asr34.hkx (example)
b -v 00asr35 00asr35.hkx (example)
Output file as above but with multiple blocks [A-Z]asr[1-75]:
* Aasr[1-75] Aasr*
* Basr[1-75] Basr*
...
* Zasr[1-75] Zasr*
The user has to pick a given letter A-Z to determine parts of the string and the block to replace information in.
Then we can replace the %input% substrings 00asr with %block%asr; they will then match given substrings in the %output%:
* 00asr[1-75] 00asr* >> * Zasr[1-75] Zasr* in the case of Z.
#ECHO OFF
ECHO INPUT FILE:
ECHO %1
SET INTEXT=%1
ECHO.
SET /P "LETTER=Letter? "
SET "REPLACE=%LETTER%asr"
SET TMP=tmp.txt
setlocal enabledelayedexpansion
FOR /F "tokens=1,* delims=¶" %%A IN ( '"TYPE %INTEXT%"') DO (
SET string1=%%A
SET modified=!string1:00asr=%REPLACE%!
echo !modified! >> %TMP%
)
PAUSE
I adapted this code. Looks like good code to me, but I'm like a blind man.
Now either the %TMP%s first-line [1-75] is required to determine whether there is any offset in the block
or the strings have to be compared, overwriting the line containing substring * Zasr[1-75] Zasr* with the same one from %TMP%;
or in the case of offset skipping a given amount of lines in %output% before overwriting the lines.
I'm not sure which option would be easier to implement or quicker to execute,
since I'm entirely failing at doing this.
The whole method should work something like this:
Input File
o <parameters> 00asr3 00asr3.hkx
o <parameters> 00asr4 00asr4.hkx
o <parameters> 00asr5 00asr5.hkx
User Input
Q
OUTPUT BEFORE
...
p Oasr75 Oasr75.hkx
p Qasr1 Qasr1.hkx
p Qasr2 Qasr2.hkx
p Qasr3 Qasr3.hkx
p Qasr4 Qasr4.hkx
p Qasr5 Qasr5.hkx
p Qasr6 Qasr6.hkx
....
OUTPUT AFTER
...
p Oasr75 Oasr75.hkx
p Qasr1 Qasr1.hkx
p Qasr2 Qasr2.hkx
o <parameters> Qasr3 Qasr3.hkx
o <parameters> Qasr4 Qasr4.hkx
o <parameters> Qasr5 Qasr5.hkx
p Qasr6 Qasr6.hkx
....
I managed to write the file renaming parts just fine; but this... I don't even.
I have been looking at various articles but due to not actually knowing any batch I'm not getting ahead with this.
1 Was very helpful, but I outright fail to compare strings correctly and can't seem to manage to assign them to variables.
Any help is appreciated. I might even understand what I'm doing wrong (which currently is most things).
I've solved it by myself.
Here's a shortened version of the script.
Cleaning input somewhat, changing the token in tmp, getting target parameters,
removing old target strings from file, merging files, deleting temps.
I'm confident this code isn't very good either way so further explanation seems unnecessary.
#ECHO OFF
SET _PACK=%1
SET "PATH=%~dp0"
SET "_LIST=in.txt"
SETLOCAL ENABLEDELAYEDEXPANSION
SET "_ERROR=0"
:USERINPUT
IF %_ERROR%==1 (
ECHO INVALID INPUT: string too long.
SET "_ERROR=0"
)
IF %_ERROR%==2 (
ECHO INVALID INPUT: non-letter.
SET "_ERROR=0"
)
SET /P "LETTER=ENTER NEW BLOCK LETTER A-Z: "
ECHO "%LETTER%" SELECTED
IF NOT "%LETTER:~1,1%"=="" (
SET "_ERROR=1"
GOTO USERINPUT
)
if "%LETTER%" lss "A" (
SET "_ERROR=2"
GOTO USERINPUT
)
if "%LETTER%" gtr "Z" (
SET "_ERROR=2"
GOTO USERINPUT
)
SET "_STRING=%LETTER%asr"
' renaming files here
SET TMP=asr.tmp
DEL %TMP%
SET "_COUNT=0"
FOR /F "tokens=1,* delims=¶" %%A IN ( '"TYPE %_PACK%"') DO (
SET _LINE=%%A
SET _NEWLINE=!_LINE:0asr=%_STRING%!
ECHO !_NEWLINE!>> %TMP%
CALL SET /A _COUNT=_COUNT+1
)
ECHO NUMBER OF INPUT LINES: !_COUNT!
SET /P _OFFSET=< %TMP%
CALL SET _OFFSET=%%_OFFSET:*asr=%%
CALL SET _OFFSET=%%_OFFSET:~0, 2%%
CALL SET _OFFSET=%_OFFSET: =%
ECHO OFFSET: %_OFFSET%.
SET /A _END=_COUNT+_OFFSET-1
COPY /Y in.txt in.tmp
FOR /L %%X IN (%_OFFSET%, 1, %_END%) DO (
SET "_SEARCH=%_STRING%%%X "
ECHO DELETING OLD STRING : !_SEARCH!
FINDSTR /V /C:"!_SEARCH!" %_PACK% > in.tmp
del in.txt
ren "in.tmp" "in.txt"
)
TYPE %TMP%>> in.txt
DEL %TMP%
PAUSE
DEL %1
EXIT

String replace troubles

I am trying to read a file and remove the forward slash character (/) from each line. Here's what I have:
for /F "tokens=*" %%A in (%BRANCH_OUT%) do SET THIS_BRANCH=%%A && ECHO %THIS_BRANCH:/=%
It works great, but the echo only displays the last line in the file for each line. So, a file containing:
Dir1/
Dir2/
Dir3/
Would be read and output as
Dir3
Dir3
Dir3
Any suggestions on how to solve this?
If you want to assign and then use a variable in a for loop, you must enable and used delayed expansion. For instance, given the following:
c:\>#for %i in (a b c d) do #set _x=%i && #echo %_x%
d
d
d
d
This echos the d character 4 times, like you observed. If you have delayed expansion enabled and you change it like this, it echos each of the 4 letters instead of d 4 times.
c:\>#for %i in (a b c d) do #set _x=%i && #echo !_x!
a
b
c
d
So, in your batch file, you need to add setlocal ENABLEDELAYEDEXPANSION at the beginning of the batch file, and endlocal at the end. This makes sure that you can use the !_x! notation to get the delayed expansion.

Resources