i need to get the last 2 tokens from a variable in Batch
the variable is
Rastreando a rota para user722-PC [192.168.1.106]
the output i need is a variable containing
user722-PC
and another containing
[192.168.1.106]
and no, i cant use
for /f "tokens=5,6 delims= " %%a in ("%variable%") do set host=%%a & set ip=%%b
echo %ip% %host%
because in this case i specified tokens 5,6 and what i want to to is to get the last 2 tokens dynamicaly, so i must NOT specify any token numbers mannualy
// this way i can get only the last token, considering that the delimiter is a space, and the delimiter cant be changed
FOR %%a in (%variable%) do set lastPart=%%a
ECHO %lastPart%
#echo off
setlocal EnableDelayedExpansion
set "var=Rastreando a rota para user722-PC [192.168.1.106]"
set "last=%var: =" & set "lastBut1=!last!" & set "last=%"
SET last
Hint: run this program with #echo on
#ECHO OFF
SETLOCAL
SET "variable=Rastreando a rota para user722-PC [192.168.1.106]"
FOR %%a IN (%variable%) DO CALL SET "lastbut1=%%lastpart%%"&SET "lastpart=%%a"
SET last
GOTO :EOF
calling the set re-parses it, so set "lastbut1=%lastpart%" is executed in a subshell.
This could also be done using delayedexpansion about which there are many articles on SO.
The short explanation for how it works is *** MAGIC ***
A longer explanation is this:
the commands CALL SET "lastbut1=%%lastpart%%" and SET "lastpart=%%a" are executed in that sequence for each element in %variable% which is a simple string with elements separated by spaces, tabs, commas or semicolons.
The & is used to separate the commands and serves merely to allow many commands to be specified on he same physical line. The code could also be written
(
CALL SET "lastbut1=%%lastpart%%"
SET "lastpart=%%a"
)
which is exactly the same; the parentheses are required and enclose the individual commands. There an additional syntax-requirement here. The opening ( must be on the same physical line as the do.
The CALL SET "lastbut1=%%lastpart%%" executes SET "lastbut1=%lastpart%" in a sub-shell, setting lastbut1 to the current value of lastpart. Then the SET "lastpart=%%a" sets lastpart to the value in %%a.
So, on the next iteration, lastbut1 gets the value that was applied to lastpart in the prior iteration, and so on until the very last time, when lastpat acquires the very last item on the list and lastbut1 has just been assigned the value just before that.
Hence, to get lastbut2, all you'd need would be to use
FOR %%a IN (%variable%) DO call set "lastbut2=%%lastbut1%%&CALL SET "lastbut1=%%lastpart%%"&SET "lastpart=%%a"
which sets lastbut2 from lastbut1, lastbut1 from lastpart and lastpart from %%a in that sequence.
Related
I'm trying to remove an asterisk from an environmental variable string, but can't seem to do it.
I'm creating an m3u file based around search strings, so for instance I if I want to make an m3u file containing every song with the word love in it, I would enter:
m3u *Love*
And m3u.bat would create the file:
xLovex.m3u
But the regular method of replacing characters does not work with an asterisk. (Though I don't have that problem with the question mark.)
set nam=%nam:*=x%.m3u
Instead creates the filename
x.m3u
The easy answer is no.
The problem that you're encountering stems from the fact that the asterisk * is a special character when used with the SET search and replace method. It matches multiple characters in a limited, but still useful, way. You can learn about that here.
The hard answer is Yes!
I will provide you with two solutions. One an incomplete solution but elegent,
the other complete and inelegent.
Both methods will search for * and replace it with an x.
Both methods will both search and modify the following string:
*love*
The first method that comes to mind is using a 'FOR /L' statement, and requires that you know how many characters long the environmental variable is.
::Major Edit::
I thought I knew the various maximum size strings of environmental variables, but dbenham has taken me to school, shown me a kick-in-the-behind length function, and in the mean time completely reversed my opinions of the two solutions I'm presenting.
Other than for the Windows 95/98/ME limitation of a 256 Character maximum environmental variable size. It seems that all versions of Windows using CMD.EXE have a limitation of 8,192 characters, well below what the documentation suggests.
Both versions require delayed environmental variable expansion, but for two different reasons. One because I'm operating inside a FOR statement. The other because you cannot put a % pair inside another % pair because the command processor matches the second % that it encounters to the first one it encounters, but we need to use a variable inside another variable expression. (You'll see.)
This solution uses the strLen function (in line 3) from DosTips.com that can be found Here. Just slap it into a file called strLen.bat and be amazed at it's speed!
Solution 1: (FOR /L Solution) :: Preferred Solution ::
setlocal ENABLEDELAYEDEXPANSION
set nam=*love*
rem calling strLen
call :strLen nam len
for /l %%x in (0,1,%len%) do if not "!nam:~%%x,1!"=="" if "!nam:~%%x,1!"=="*" (
set /a plusone=%%x+1
for /l %%y in (!plusone!, 1, !plusone!) do (
set nam=!nam:~0,%%x!x!nam:~%%y!
)
)
echo %nam%
ENDLOCAL
I think this is a quick and elegant solution It could be sped up by adding the contents of strLen.bat to the routine, but I wanted no confusion as to the author.
If you, for some reason, do not wish to use strLen, then the next quickest method would probably use a GOTO loop.
Solution 2: (Goto Solution)
setlocal ENABLEDELAYEDEXPANSION
set nam=*love*
set num=0
:loop
set /a plusone=%num%+1
if "!nam:~%num%,1!"=="*" set nam=!nam:~0,%num%!x!nam:~%plusone%!
set /a num=%num%+1
if not "!nam:~%num%,1!"=="" goto :loop
echo %nam%
EndLocal
Special thanks to dbenham for pointing out the strLen function. It works faster than any batch based function has a right to!
Although there were already some very good and robust ways explained here, I'd still like to add another option for the sake of completion.
It's not as good as the other options but I personally use it in some cases where I'd like to keep the code clean and where I know that it will suffice:
The way it works is by using for /f's delims to cut the string into two parts, which are then put back together, getting rid of the * in the process:
for /f "tokens=1,* delims=*" %%a in ("a*b") do (set string=%%a%%b)
>>> string=ab
Obviously, the downside to this is that it can only be used to remove one *.
To remove more, we can either just use more tokens...
for /f "tokens=1-3,* delims=*" %%a in ("a*b*c*d") do (set string=%%a%%b%%c%%d)
>>> string=abcd
... or we can put the first line in a for /l-loop:
setlocal enableDelayedExpansion
set string=a*b*c*d
for /l %%a in (1, 1, 3) do (
for /f "tokens=1,* delims=*" %%b in ("!string!") do (set string=%%b%%c)
)
>>> string=abcd
Another thing to note is that you can define more than one character in delims, and they will all be removed at once:
for /f "tokens=1,* delims=+-*/" %%a in ("a*-/+b") do (set string=%%a%%b)
>>> string=ab
Another solution to the stated problem is to use a PowerShell replace command within your batch script.
set var=*Love*
echo %var%>var.txt | powershell -command "((get-content var.txt) -replace '[\x2A]','x') -replace '.{1}$' | set-content var.txt"
set /p var=<var.txt
set var=%var%.m3u
echo %var%
In the above code, the second line
writes your string into a text file
calls a PowerShell command to get the contents of that file
replaces the * character with null
overwrites the text file with the new value
Once that is done, you read the value back into your variable.
To further explain the replace command, the first single quotes is what you are searching for. We are using square brackets to identify the * character as a hex character (\x2A is the hex value for *). After the comma, the second set of single quotes contains no value so that the searched object is removed. To prevent a space between xLovex and the .m3u, we have to use -replace '.{1}$' before writing the result to the text file.
Once you are done with the text file, enter a line to delete it.
if exist var.txt del var.txt
Here is an approach that does not walk through all characters of a string, but it uses a for /F loop to split the string at every occurrence of a (sequence of a) certain character. The actual functionality is packed into a sub-routine for easy reuse, so the main section of the following script just contains some code to test:
#echo off
setlocal EnableExtensions DisableDelayedExpansion
::This is the main routine of the script holding code for test and demonstration:
rem // Definition of some sample text to test (note that `%%` becomes one literal `%`):
set "DATA=some text,"^&"&;0'#%%~#`$:wild**card*?.re<dir>=|+([{parens}])-^/equal==to=!_"
echo/
call :REPL_CHAR TEXT DATA "*" "?"
setlocal EnableDelayedExpansion
echo(In: !DATA!
echo(Out:!TEXT!
echo/
echo(In: !TEXT!
call :REPL_CHAR TEXT TEXT "=" "/"
echo(Out:!TEXT!
endlocal
endlocal
exit /B
:REPL_CHAR
::This function replaces in a string every occurrence of a sequence of a certain character
::by another character or a string. It even correctly handles the characters `*` and `=`.
:: USAGE:
:: call :REPL_CHAR ref_output_string ref_input_string val_search_char val_replace_char
:: PARAMETERS:
:: ref_output_string reference to (name of) variable to receive the resulting string;
:: ref_input_string reference to variable that holds the original string; if empty
:: (`""`), the variable referenced by `ref_output_string` is used;
:: val_search_char single character that is to be replaced;
:: val_replace_char character or string to replace every sequence of `val_search_char`
:: with; this may even be empty;
rem // Localise environment and detect whether delayed expansion is enabled (needed later):
setlocal & set "$NDX=!"
setlocal DisableDelayedExpansion
rem // Fetch arguments and verify them:
set "#RET=%~1" & if not defined #RET endlocal & endlocal & exit /B 2
set "#STR=%~2" & if not defined #STR set "#STR=%#RET%"
set "CHR=%~3"
if not defined CHR endlocal & endlocal & exit /B 1
set "RPL=%~4"
setlocal EnableDelayedExpansion
rem // Initialise several auxiliary variables:
set "TST=!%#STR%!" & set "CHR=!CHR:~,1!" & set "INS="
if "!CHR!"=="_" (set "BUF=#" & set "WRK=!TST!#") else (set "BUF=_" & set "WRK=!TST!_")
:REPL_CHAR_LOOP
rem // Check whether the end of the string has been reached:
if not defined TST set "BUF=!BUF:~1,-1!" & goto :REPL_CHAR_NEXT
rem // Split the string at the next sequence of search characters:
for /F tokens^=1*^ delims^=^%CHR%^ eol^=^%CHR% %%S in ("!BUF!!INS!!WRK!") do (
rem // Store the portions before and after the character sequence:
endlocal & set "BUF=%%S" & set "TST=%%T" & set "WRK=%%T" & setlocal EnableDelayedExpansion
)
rem // Loop back and find the next character sequence:
set "INS=!RPL!" & goto :REPL_CHAR_LOOP
:REPL_CHAR_NEXT
rem // Return the resulting string with all special characters properly handled:
if not defined $NDX if defined BUF set "BUF=!BUF:"=""!^"
if not defined $NDX if defined BUF set "BUF=!BUF:^=^^^^!"
if not defined $NDX if defined BUF set "BUF=%BUF:!=^^^!%" !
if not defined $NDX if defined BUF set "BUF=!BUF:""="!^"
for /F "delims=" %%S in (^""!BUF!"^") do endlocal & endlocal & endlocal & set "%#RET%=%%~S" !
exit /B
The input and output data of this script (let us call it repl_char_demo.bat) are:
>>> repl_char_demo.bat
In: some text,"&"&;0'#%~#`$:wild**card*?.re<dir>=|+([{parens}])-^/equal==to=!_
Out:some text,"&"&;0'#%~#`$:wild?card??.re<dir>=|+([{parens}])-^/equal==to=!_
In: some text,"&"&;0'#%~#`$:wild?card??.re<dir>=|+([{parens}])-^/equal==to=!_
Out:some text,"&"&;0'#%~#`$:wild?card??.re<dir>/|+([{parens}])-^/equal/to/!_
This is a script that uses for /L loops to walk through all characters of the string, to check each character against a predefined one and replaces it as specified. This method replaces every single matching character rather than sequences. Again the functionality is put into a sub-routine (the main section is dismissed this time):
:REPL_CHAR
::This function replaces in a string every occurrence of one certain character by another
::character or a string. It even correctly handles the characters `*` and `=`, as well as
::sequences of search characters so that every single one becomes replaced.
:: USAGE:
:: call :REPL_CHAR ref_output_string ref_input_string val_search_char val_replace_char
:: PARAMETERS:
:: ref_output_string reference to (name of) variable to receive the resulting string;
:: ref_input_string reference to variable that holds the original string; if empty
:: (`""`), the variable referenced by `ref_output_string` is used;
:: val_search_char single character that is to be replaced;
:: val_replace_char character or string to replace every single `val_search_char`
:: with; this may even be empty;
rem // Localise environment and detect whether delayed expansion is enabled (needed later):
setlocal & set "$NDX=!"
setlocal DisableDelayedExpansion
rem // Fetch arguments and verify them:
set "#RET=%~1" & if not defined #RET endlocal & endlocal & exit /B 2
set "#STR=%~2" & if not defined #STR set "#STR=%#RET%"
set "CHR=%~3"
if not defined CHR endlocal & endlocal & exit /B 1
set "RPL=%~4"
setlocal EnableDelayedExpansion
rem // Initialise several auxiliary variables:
set "WRK=!%#STR%!" & set "CHR=!CHR:~,1!" & set "BUF="
rem // Loop through all characters and check for match:
if defined WRK for /L %%J in (0,1,63) do for /L %%I in (0,1,127) do (
set /A "POS=%%J*64+%%I" & for %%P in (!POS!) do (
set "TST=!WRK:~%%P,1!" & if not defined TST goto :REPL_CHAR_QUIT
rem // Store character or replacement depending on whether there is a match:
if "!TST!"=="!CHR!" (set "BUF=!BUF!!RPL!") else (set "BUF=!BUF!!TST!")
)
)
:REPL_CHAR_QUIT
rem // Return the resulting string with all special characters properly handled:
if not defined $NDX if defined BUF set "BUF=!BUF:"=""!^"
if not defined $NDX if defined BUF set "BUF=!BUF:^=^^^^!"
if not defined $NDX if defined BUF set "BUF=%BUF:!=^^^!%" !
if not defined $NDX if defined BUF set "BUF=!BUF:""="!^"
for /F "delims=" %%S in (^""!BUF!"^") do endlocal & endlocal & endlocal & set "%#RET%=%%~S" !
exit /B
There are actually two nested for /L loops rather than a single one, both of which become broken as soon as the end of the string is reached, using the goto command. Breaking a for /L loop means that it completes iterating in the background although its body is no longer executed. Therefore, using a single loop takes much more time to finish after being broken rather than two nested ones.
The input and output data of this script (with the same main section as above) are:
>>> repl_char_demo.bat
In: some text,"&"&;0'#%~#`$:wild**card*?.re<dir>=|+([{parens}])-^/equal==to=!_
Out:some text,"&"&;0'#%~#`$:wild??card??.re<dir>=|+([{parens}])-^/equal==to=!_
In: some text,"&"&;0'#%~#`$:wild??card??.re<dir>=|+([{parens}])-^/equal==to=!_
Out:some text,"&"&;0'#%~#`$:wild??card??.re<dir>/|+([{parens}])-^/equal//to/!_
See this answer, and with set-ast.bat you'll want to put set-ast nam "x" in your file where needed.
set-ast takes the parameters <variable-to-modify> <string-to-replace-asterisks-with>
Unfortunately I'm unable to add comments to an existing StackOverflow article as my rep isn't high enough, so I'll reference the link here:
runonce-to-rename-a-computername-with-a-random-name-on-reboot
Why does the batch sample code that LotPings provided always prefix the randomly generated part with the first letter from the character list, e.g. "A"?
Sample code:
#Echo off&SetLocal EnableExtensions EnableDelayedExpansion
Set "Chars=ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789"
Call :RandChar 26 Name
For /L %%A in (1,1,14) Do Call :RandChar 62 Name
Echo %Name%
Goto :Eof
:RandChar Range Var
Set /A Pnt=%Random% %% %1 & Set %2=!%2!!Chars:~%Pnt%,1!
Result when executed 10 times:
AOyVq8olMi7kZaR
ANyU2MoAzZwTpwo
AYNXqJSGKXgyvca
AFGoCImfNOHuVSz
AHNhxnj9Wyqkqo3
AML6hTrSHK4TFut
AHuinBZCWr5D5NZ
AQYNtXaYMXRdGOb
ARWYXn7YNMQ0cmm
APHdWggSQl5KUhA
How can the code be modified to randomise the starting character too?
Set /A Pnt=%Random% %% %1 & Set %2=!%2!!Chars:~%Pnt%,1!
should be
Set /A Pnt=%Random% %% %1
Set %2=!%2!!Chars:~%Pnt%,1!
The entire line is parsed, then executed and during the parsing phase, any %var% will be replaced by the value of varat that time. Hence,pntwill be replaced by the value ofpntat the very start, yieldingSet %2=!%2!!Chars:~,1!` and thereafter it will be replaced by the value established on the previous call.
Breaking the line forces the two statements to be executed sequentially.
So, I'm making a calculator that allows its users to name their previous result in order to use them in future calculations. How it works is that when you give a name to a previous result, it creates a variable with the name chosen and its value is then set to whatever the previous result is. It looks something like this:
set /p equation=
::variables need to be replaced here
set /a result=%equation%
::giving a name
set /p name=
set %name%=%result%
The problem is, I cannot figure out a way to get the value of the variable in the equation since the equation is also a variable, so whenever I add the equation in a command with %equation%, it just replaces it with something like 1+%named_result%. Is there a way I could turn the equation with the same one but where the variables are replaced with their values? (set equation=%equation% does not work)
for /f "tokens=1*delims==" %%a in ('set %name% 2^>nul') do if /i "%%a"=="%name%" set "result=%%b"
set %name% lists all of the variables that start %name% in the format name=value. The 2>nul suppresses error messages if name is not defined. the caret (^) escapes the > telling cmd that the > is part of the set command, not the for.
The for /f splits the set line into two parts - that before the delimiter and that after, so name goes to %%a and value to %%b. Now set will output any name that starts with name so it will match name, named names etc. consequently, we need to choose the output line where the name exactly matches (hence the if) and to make the match case-insensitive (the /i on the if command.
And if the test is passed, assign the value to your chosen variable.
I am using a for loop to create textfiles with predefined headers, but I want to append a unique string (alice, bob, etc.) on the 2nd line for each file from a predetermined list.
Set a=alice
Set b=bob
Set c=chris
Rem ...etc. (I have about 30 files with a unique name to go in row 2 of each file)
For /l %%x in (1, 1 , 30) do (echo headerRow1 > file%%x.txt & echo %a% > file%%x.txt)
I dont understand how to automatically insert "bob" for the second iteration (ie. In "file2.txt") and "chris" for the third iteration, and continue for 30 files.
Do I somehow loop the %a% part to increment by one each time. What should I name the string variables to do this? Are these even the right questions?
Thanks for looking.
It seems that the problem you're trying to solve is to be able to iterate between two lists in one for loop, when the for loop can only iterate through one set at a time. The problem is harder in that arrays and lists don't really exist in Batch as first-class citizens.
There are multiple ways of solving this, but one simpler way is to loop through the names, and increment a counter each time through the loop. (This is probably easier than a for /l loop and incrementing the list of names.)
Here is an example.
#echo off & setlocal enabledelayedexpansion
set names=alice bob chris
set x=1
for %%a in (%names%) do (
>file!x!.txt (
echo headerRow
echo %%a
)
set /a x+=1
)
Notes:
Set all your names on a single line, and a for loop can iterate through them.
Set your counter (x) at the start, then increment it at the end of the loop.
I'm using delayed expansion (setlocal enabledelayedexpansion) and the syntax of !x! instead of %x% so that Batch resolves the variable x each time through the loop, instead of once (and only once) the first time it goes through, so that it gets the right incremented value each time.
I hope that helps.
#ECHO Off
SETLOCAL
SET "sourcedir=U:\sourcedir"
SET "targetdir=U:\destdir"
SET "filename1=%sourcedir%\q40171884.txt"
SET "headerrow=This is your header"
SET "line2=This is line two for "
FOR /f "tokens=1*delims=[]" %%a IN ('find /n /v "" "%filename1%"') DO IF "%%b" neq "" (
>"%targetdir%\file%%a.txt" (
ECHO(%headerrow%
ECHO(%line2%%%b
TYPE "%sourcedir%\thefilecontainingtheremainder.txt"
)
)
GOTO :EOF
You would need to change the settings of sourcedir and targetdir to suit your circumstances.
I used a file named q40171884.txt containing your names for my testing.
Uses the file "%sourcedir%\thefilecontainingtheremainder.txt" to contain the body of the data to be assigned to the output file. Naturally, this could be anywhere.
Essentially, take the filename containing your names, and use find /n /v "" to prefix each line with [sequencenumber]. Using delims of [], assign the prefix to %%a and the remainder of the line (the name) to %%b.
The first line output by find will report the filename, but won't contain [] (unless you get silly and engineer it that way), so %%b in that case will be empty. We skip that line, and for the others, %%a contains the number and %%b the name.
Then create new output files "filenumber.txt" and the two special lines - the second with %%b (the name) appended. Then regurgitate the remainder of the required text by simply typeing it.
%x:~12,3% Returns 3 characters starting at the 12:th character in x variable.
What I have been trying to accomplish is using variables instead of 12 and 3.
Let's say y=12 and z=3.
Then, you can't use %x:~%y%,%z%%, because CMD will think %x:~% is a variable.
What you can do is set var=%%x:~%y%,%z%%%. This will expand the inside variables y and z, but not x, so that the value of var is %x:~12,3%.
The remaining task at hand now is to finally expand %x:~12,3%. I have been trying to append echo in the beginning so that var=echo %x:~12,3%.
If at the commandline or in a batch file you now use %var%, this should execute the echo command, and expand the succeeding expression, but it doesnt, instead echo %x:~12,3% results in simply %x:~12,3% being printed to the screen, unexpanded.
I was thinking that maybe if you set var to %x:~12,3%, then echo it
and pipe the output into another ECHO command or SET command that the expression would be expanded, but it seems that ECHOand SETdoesn't accept data being piped into it at all?
How can I make this work?
I copied the entire text below from this answer; I just changed the names of variables and particular examples to match the ones of this question:
%x:~12,3% returns 3 characters starting at the 12:th character in x
variable. What I have been trying to accomplish is using variables
instead of 12 and 3. Let's say y=12 and z=3.
If you want to use another variables for substring position and lenght, then you must know that the replacement of variables enclosed in percents by their values is parsed from left to right; this mean that: %x:~%y%,%z%% don't give the desired result because it mean: show the value of x:~ variable, followed by y, followed by the value of , variable, etc.
To solve this problem you must use Delayed Expansion, that is, insert setlocal EnableDelayedExpansion command at beginning, enclose substring variables in percents, and enclose the original variable in exclamation marks:
setlocal EnableDelayedExpansion
set x=0123456789ABCDEF
set y=12
set z=3
set var=!x:~%y%,%z%!
You may also use parameters of FOR commands as indexes: for /F "tokens=1,2" %%i in ("%y% %z%") do set var=!x:~%%i,%%j!.
To get the value of a substring when the index change inside FOR/IF enclose the variable in double percents and precede the command with call. For example, to show a substring at a random y position between 0 and 12 and lenght z:
if %some% == %test% (
set /A y=!random! %% 13
call echo %%x:~!y!,%z%%%
)
You may also use this method outside parentheses in order to avoid the Delayed Expansion:
call echo %%x:~%y%,%z%%%
Another way to achieve previous process is using an additional FOR command to change the delayed expansion of the index by an equivalent replaceable parameter, and then use the delayed expansion for the original variable. This method run faster than previous CALL:
if %some% == %test% (
set /A y=!random! %% 13
for %%y in (!y!) do echo !x:~%%y,%z%!
)
You need to enable delayed expansion.
#echo off
setlocal enabledelayedexpansion
set string=1234567890abcdef
set substring_start=12
set substring_length=3
set substring=!string:~%substring_start%, %substring_length%!
set command=echo !substring!
!command!
pause
#ECHO OFF
SETLOCAL
SET "x=abcdefghijklmmopqrstuvwxyz"
SET /a start=12
SET /a length=3
CALL SET "var=%%x:~%start%,%length%%%"
ECHO var=%var%
CALL echo %%x:~%start%,%length%%%
SETLOCAL ENABLEDELAYEDEXPANSION
SET /a start=6
SET /a length=4
SET "var=!x:~%start%,%length%!"
ECHO var=%var%
echo !x:~%start%,%length%!
GOTO :EOF
Two methods - the first in standard mode and the second using delayedexpansion. There are hundreds of examples on SO about delayedexpansion.