Replace multiple characters in a string from the command line - windows

Its possible replace a staring with another like this
set x=abc
echo %x:b=d%
The output will be
adc
But how do you replace multiple characters.
For example I want b=>d but als c=>e
It possible with a loop/for but the question is how to do it on 1 line so I can use it from the command line.
I tried these variants
echo %x:b=d,c=e%
echo %x:b=d;c=e%
echo %x:b=d|c=e%
I can't get the syntax right or it isn't supported?

The syntax supported only one replace per expansion.
The problem is, that mutliple percent expansions in one line or in a command block doesn't work, because the percent expansion will be expanded, before any command is executed.
Therefore this fails, it outputs "abc"
set "var=abc"
set "var=%var:a=1%" & set "var=%var:b=2%" & echo %var%
But you could use CALL to force a second expansion phase.
set "var=abc"
call set var=%^var:a=1% & call set var=%^var:b=2% & call echo %^var^%
%^var.. looks odd, but it's necessary on the command line, because on the command line you can't escape percent signs, but you can prevent the expansion in the first round by adding carets in the variable name.
But the solution has a drawback, it appends spaces at the variable.
You can see that by using call echo ---%^var%---
To prevent that, you should use the extended SET-syntax with quotes set "var=content"
call set ^"var=%^var:a=1%^" & call set ^"var=%^var:b=2%^" & call echo %^var^%
Now, there are carets in front of the quotes to allow the carets in %^var to disappear.
As you can see, the solution is pretty simple

Well, you can use a for loop and still have everything in a single line:
(for %I in ("b=d" "c=e") do #call set ^"x=%^x:%~I%^") & call echo/%^x%
The call command introduces a second parsing phase, which is necessary when you want to write and read a variable within the same block of code.
If you have got delayed variable expansion enabled (like when you started the command prompt by cmd /V), you could change the command line to this:
(for %I in ("b=d" "c=e") do #set "x=!x:%~I!") & echo/!x!

I realise you said from cmdline, but you also did have the batch-file tag, therefore I will post the batch file solution regarless.
We create a list of search=replace as a variable, you can have as many search/replaces as you like, then simply loop through the list and let it run the replace for each and echo final modified variable when done.
#echo off
set x=abc
set lst="a=z","b=d","c=e"
for %%i in (%lst%) do call set x=%%x:%%~i%%
echo(%x%

If you are on a supported Windows system, it will have the PowerShell interpreter.
SET "X=abc"
FOR /F %a IN ('powershell -NoLogo -NoProfile -Command "('%X%' -replace 'b','d') -replace 'c','e'"') DO (SET "Y=%a")
ECHO %Y%
If this is placed into a .bat file script, double the percent % character on the FOR loop variable. %a becomes %%a.
SET "X=abc"
FOR /F %%a IN ('powershell -NoLogo -NoProfile -Command ^
"('%X%' -replace 'b','d') -replace 'c','e'"') DO (SET "Y=%%a")
ECHO %Y%

Related

Trying to concatenate the last 10 lines of a log file to a batch variable using powershell

I'm new to Windows scripting, but have quite a lot of experience in bash and python.
Here's the issue. Whenever I run this, (and this is the best result I've gotten so far) it makes it most of the way through and then errors with "The filename, directory name, or volume label syntax is incorrect."
Ignore the code designed for newlines, I'm still fighting with that as well.
setlocal EnableDelayedExpansion
set LF=^
set LAST_TEN=Here are the last 10 lines of the download log:
for /f "tokens=* usebackq" %%x in (`powershell -command "& {Get-Content download.log | Select-Object -last 10 | ForEach-Object {$_.substring(2)}}"`) do (
set LAST_TEN=!LAST_TEN!%%x
)
echo %LAST_TEN%
The reason I'm taking the substring is because some of the lines in the logfile start with < and > . I thought that was my only issue, but that is not the case. Please let me know if any more info is needed. Thank you!
Note: Your own answer shows the effective solution, but I thought I'd provide some background information.
Squashman has provided the crucial pointer:
Switching from echo %LAST_TEN% to echo !LAST_TEN! avoids problems with metacharacters (special characters such as < and >) in the variable value, which are what caused your error message.
The alternative would be to double-quote the variable reference - echo "%LAST_TEN%" - but, sadly, the double quotes are then included in the output.
In other words: If you need to echo the value of a variable that (potentially) contains metacharacters unquoted:
Place setlocal EnableDelayedExpansion at the start of your batch file.
Then reference the variable of interest as !VAR! instead of %VAR%: the delayed expansion this results in prevents the value from becoming part of the source-code line that cmd.exe parses (due to the macro-style up-front expansion that happens with %VAR%).
As an aside: Loop variables - such as %%x in your code - despite using % rather than ! as the delimiter, are of necessity always expanded in a delayed fashion, which is the reason that set LAST_TEN=!LAST_TEN!%%x worked even without the double-quoting around enclosing both the variable name and value that is normally required for literals and values of non-delayed variable references containing metacharacters (e.g.
set "LAST_TEN=a < b")
A simplified example:
#echo off
setlocal enableDelayedExpansion
:: Define a sample variable
:: Note the "..." enclosing both the name and the value.
set "var=a value with metacharacters: < > & |"
:: Thanks to using !var!, echoing the value *unquoted* works
echo !var!
Scoping setlocal enableDelayedExpansion:
One pitfall of delayed expansion is that that all ! characters are then considered part of delayed variable references, typically resulting in their quiet removal; e.g., echo hi! outputs just hi.
To escape ! characters in literal strings that should be used verbatim, you need ^^! (sic) in unquoted strings, and ^! inside "...".
The escaping is also needed for %...% variable references (e.g., echo %var:!=^^!%), but is again avoided for !...! ones.
To avoid such escaping headaches you can enable setlocal enableDelayedExpansion on demand, for a given line or block of lines, and disable it again with endlocal:
#echo off
:: Define a sample variable
:: Note the "..." enclosing both the name and the value.
set "var=a value with metacharacters: < > & |"
:: Because setlocal enableDelayedExpansion is NOT (yet)
:: in effect, the use of "!" is not a problem.
echo hi!
:: Localize the use of delayed expansion
setlocal enableDelayedExpansion
echo !var!
endlocal
:: Use of "!" is again fine.
echo hi again!
Caveat: Since setlocal creates a copy of the environment variables, which endlocal then discards, do not try to set variables between setlocal and endlocal if you need later code to see these changes.
As you're already using PowerShell, why not let it do the donkey work?
Grab the last ten lines and concatenate them within parentheses, for example:
For /F Delims^=^ EOL^= %%G In ('%SystemRoot%\System32\WindowsPowerShell\v1.0\powershell.exe -NoProfile -Command "(Get-Content 'download.log' | Select-Object -Last 10) -Join ''"') Do Set "LAST_TEN=%%G"
Changed
echo %LAST_TEN%
to
echo !LAST_TEN!

How can I get a string between two quotes in a batch file?

I have a string in a batch file, of the structure
[[status]]:{"01bcd123-1234-5678-0000-abcdefghijkl": "11"}
I need to get just the 01bcd123-1234-5678-0000-abcdefghijkl out of it, but trying to use " as a delimiter doesn't turn out well. \ and ^ don't seem to escape it properly.
set i=1
set "x!i!=%x:"=" & set /A i+=1 & set "x!i!=%"
Is what I have with x being the whole string, attempting to parse it into x1, x2 etc with " as the delimiter.
What is a proper way to split this string, using " as the delimiter?
Edit: Powershell tag is because I am running the script as part of a larger orchestration in Powershell and could export the functionality of the batch script into it if necessary.
Here are two approaches. The first one doesn't mess with the for syntax format, but it's risky - too much dependence on the string (the quotes are actually stripped by %%~). The second one is an ugly non-intuitive syntax, but actually delimits by quotes:
set "string=[[status]]:{"01bcd123-1234-5678-0000-abcdefghijkl": "11"}"
for /f "tokens=2 delims=:{" %%a in ("%string%") do #echo %%~a
for /f tokens^=2delims^=^" %%a in ("%string%") do #echo %%a
Well, the self-expanding code you have posted works fine, given that you have got delayed expansion enabled, by having put the statement setlocal EnableDelayedExpansion placed before. The string of interest is then stored in variable x2. Note that when the script terminates, x2 (like all the other x# variables as well) is no longer available since an implicit endlocal is executed then. To avoid that, place endlocal & set "x2=%x2%" in the last line:
#echo off
rem // Define string to parse:
set "x=[[status]]:{"01bcd123-1234-5678-0000-abcdefghijkl": "11"}"
rem // Enable delayed expansion:
setlocal EnableDelayedExpansion
rem // Initialise index counter:
set i=1
rem // Split string using self-expanding code:
set "x!i!=%x:"=" & set /A i+=1 & set "x!i!=%" & rem // (unbalanced `"`!)
rem // Display all `x#` variables:
set x
rem // Make `x2` survive the `endlocal` barrier:
endlocal & set "x2=%x2%"
rem // Return the retrieved value:
echo(%x2%
However, I would most probably use a for /F loop, but not with " as delimiter since the syntax appears quite odd then; rather I would use :, {, } and SPACE as delimiters. But I would remove the prefix [[status]] in advance:
#echo off
rem // Define string to parse:
set "x=[[status]]:{"01bcd123-1234-5678-0000-abcdefghijkl": "11"}"
rem /* At first, split off everything up to the first occurrence of `]]`;
rem if there is no such prefix, there is no harm, because nothing happens;
rem then extract the first token that is delimited by `:`, `{`, `}` or space;
rem that way there may even be spaces around the `:` or around `{` or `}`;
rem then return it with surrounding quotation marks removed (`~`-modifier): */
for /F "tokens=1 eol=: delims=:{} " %%I in ("%x:*]]=%") do echo(%%~I
N. B.:
The odd-looking syntax echo( is not a typo, it is actually the only safe way to echo an arbitrary string (even on, off or /?); take a look at this external thread for more details.
Since you tagged PowerShell, you can use the following regex, but I am not sure you want PowerShell based on the question.
[regex]::Match('[[status]]:{"01bcd123-1234-5678-0000-abcdefghijkl": "11"}','(?<=")[^"]+(?=")').Value
Split regex can also work:
('[[status]]:{"01bcd123-1234-5678-0000-abcdefghijkl": "11"}' -split '"')[1]
If you stick with a batch file, Stephan's helpful answer is definitely the simplest and fastest solution.
Needless to say, if you port your batch file to PowerShell, you'll have vastly more functionality at your disposal.
You can even harness that functionality from a batch file via PowerShell's CLI, by calling powershell.exe (Windows PowerShell) or pwsh.exe (POwerShell Core), but that comes with two caveats:
Doing so creates a PowerShell child process, whose startup time is not insignificant.
Getting nested quoting right can be a challenge, as shown below.
Here's a solution that calls PowerShell's CLI from a batch file, applying the -split technique from AdminOfThings' helfpul answer; again, this solution would be overkill in the case at hand, but the approach may be of interest if you need to perform tasks that simply cannot be done in the batch language or would be too cumbersome.
#echo off
setlocal
:: # The input text.
set txt=[[status]]:{"01bcd123-1234-5678-0000-abcdefghijkl": "11"}
:: # Call the PowerShell CLI to extract the token of interest and save the
:: # result in variable %id%.
:: # In PowerShell code, the equivalent would be:
:: # $id = ($txt -split '"')[1]
for /f %%i in ('powershell -noprofile -c "('%txt:"=\"%' -split '\""')[1]"') do set id=%%i
:: # Echo the result.
echo %id%
Note the need to \-escape the " chars. embedded in %txt%, via substitution %txt:"=\"%, and the need for an additional " char. after \" in '\""' so as to prevent the for command from breaking.

cmd for loop mass renaming again oneliner

I'm over my head with this - spent too much time searching already - evidently I don't understand the basics of CMD variables etc. - and it always gives me such a headache
why wouldn't this work?
for %a in (*) do ( set tmpx=%a & echo %tmpx% )
the above code outputs the value of %tmpx% in some other scope - and it is always constant
yes, i run setlocal ENABLEDELAYEDEXPANSION
basically i need to do a simple rename of all files in folder from constantstring_somenameXX.tif to somenameXX.tif, where i.e. constantstring=0000000005
i had to use set because other posts rightly suggested that %a in a for loop has a special behaviour, and the substitutions wouldn't work for it as it is.
i would prefer not to use scripts and/or powershell - unless not using them is impossible
thank you
for %a in (*) do ( set tmpx=%a & echo %tmpx% )
The problem with the previous code is delayed expansion. Yes, you enabled it, but you have not used it, and depending on how you enabled it, it will not work
In cmd, when a line or block of lines (code inside parenthesis) is reached, it is first parsed and then executed. During the parse phase, variable read operations are removed from the command, replaced with the value in the variable before the command starts to execute. So, if you change the value of a variable inside a line/block you can not retrieve the changed value inside the same line/block as there are no variable reads (they were replaced)
setlocal enabledelayedexpansion allows to replace (where needed) the variable read syntax from %var% to !var!, indicating to the parser that the read operation will be delayed until the execution phase.
So, in your case, your code should have been something like
setlocal enabledelayedexpansion & for %a in (*) do ( set "tmpx=%a" & echo !tmpx! )
BUT this will not work (in default configured environments).
cmd has two execution modes: batch file and command line. In your case, you are using command line (no escaped percent sign in for loop) and in command line mode the setlocal enabledelayedexpansion will not work. It is intended for batch files (see setlocal /?)
How to make it work from the command line? By default cmd is started with delayed expansion disabled and you can not enable it if not inside a batch file. But you can start cmd with delayed expansion enabled and run your command in this started instance (see cmd /?)
cmd /v:on /c "for %a in (*) do ( set "tmpx=%a" & echo !tmpx! )"
Anyway, to solve your rename problem, delayed expansion is not needed
for %a in (*_*.tif) do for /f "tokens=1,* delims=_" %b in ("%~nxa") do echo ren "%a" "%c"
That is, for each tif file with an underscore, take the name and extension of the file (%~nxa) as a string, and using the underscore as a delimiter between tokens, retrieve the first token (the text on the left of the first underscore) in %b and the rest of the text (to the right of the underscore) into %c. Now, just rename the original file name (stored in %a) to the contents of %c (the text on the right of the underscore)
In this code rename operations are only echoed to console. If the output is correct, remove the echo command.
! is the character to use rather than % when wanting execution time value. % does when it's read value.
CMD was written by IBM engineers and they were trying to make MSDos a programming language while making sure Dos commands ran the same. So we get a hodge podge.
& seperates commands on a line.
&& executes this command only if previous command's errorlevel is 0.
|| (not used above) executes this command only if previous command's errorlevel is NOT 0
> output to a file
>> append output to a file
< input from a file
| output of one command into the input of another command
^ escapes any of the above, including itself, if needed to be passed to a program
" parameters with spaces must be enclosed in quotes
+ used with copy to concatinate files. E.G. copy file1+file2 newfile
, used with copy to indicate missing parameters. This updates the files modified date. E.G. copy /b file1,,
%variablename% a inbuilt or user set environmental variable
!variablename! a user set environmental variable expanded at execution time, turned with SelLocal EnableDelayedExpansion command
%<number> (%1) the nth command line parameter passed to a batch file. %0 is the batchfile's name.
%* (%*) the entire command line.
%<a letter> or %%<a letter> (%A or %%A) the variable in a for loop. Single % sign at command prompt and double % sign in a batch file.

Windows command prompt: Using a variable set in the same line of a one-liner

Okay, I learned that one can use & or && to combine multiple commands into a single line, but it seems that variables that are set aren't actually available for interpolation in the same line:
C:\Users\Andrew>set foo=hello, world!&& echo %foo%
%foo%
C:\Users\Andrew>echo %foo%
hello, world!
Why can't I make this work, and is there any way to make it work in a single line?
The reason I need a one-liner is that an external program I'm working with accepts a single command as a pre-run hook, and, of course, I need to run multiple commands.
Preemptive Defenses
"hello, world! should be surrounded in double-quotes!" Actually, doing so seems to store literal double-quotes in the variable, which I do not want, e.g.
C:\Users\Andrew>set bar="hello, world!"&& echo %bar%
%bar%
C:\Users\Andrew>echo %bar%
"hello, world!"
"There should be a space before the &&!" Actually, doing so seems to store a trailing space in the variable, which I do not want, e.g.
C:\Users\Andrew>set bar="hello, world!"&& echo %bar%
%bar%
C:\Users\Andrew>echo %bar%
"hello, world!"
"Both!" >:(
C:\Users\Andrew>set mu="hello, world!" && echo %mu%
%mu%
C:\Users\Andrew>echo (%mu%)
("hello, world!" )
You can do it in the same line, but I would recommend to use a batch file like preHook.bat.
As one liner
set "mu=hello, world!" && call echo %^mu%
At first you can see that the quotes are different than yours.
Here's why:
set test1="quotes"
set "test2=no quotes" with comment
In the first test, the quotes will be part of the test1 variable, as well as all characters after the last quote.
In the second test, it uses the extended syntax of the SET command.
So the content will be no quotes, as only the content to the last quote is used; the rest will be dropped.
To echo the variable content, I use call echo %^mu%, as percent expansion will expand it when the line is parsed, before any of the commands are executed.
But the call command will be executed later, and it restarts the parser, which, when used at the command line, uses different expansion rules than the batch parser: an empty variable (in this case, %^mu%, in the first time) stays unchanged in the line; but, in the next parser phase, the ^ caret will be removed.
In your case, call echo %mu% would also work, but only when mu is always empty. The caret variant also works when mu has content before the line is executed.
More about the parser at SO: How does the Windows Command Interpreter (CMD.EXE) parse scripts?
And about variable expansion at SO: Variable expansion
Though I accepted #jeb's answer, ultimately I had to go another route, because I needed to pipe the output of my command, and doing so on call led to mangled results. Indeed, the documentation for call itself remarks,
Do not use pipes and redirection symbols with call.
Instead, reminded by #Blorgbeard of cmd's /v option (and I believe that should be lowercase, not uppercase), I realized I could simply start a subprocess:
C:\Users\Andrew>cmd /v /c "set foo=hello, world!&& echo !foo!"
hello, world!
(For some reason, /v must appear before /c.) Within the quotes, I was able pipe my output to other utilities. One tip for those taking this path: If you find yourself needing to use quotes within those quotes, I suggest avoiding them altogether and trying character codes, e.g. \x20 for space, \x22 for double quotes, and so on.
For example, this was the eventual solution to my problem (warning: may cause eyes to bleed):
C:\Users\Andrew>cmd /v /c "set source=C:\source& set target=C:\target& set archive=C:\archive& robocopy.exe !source! !target! /l /e /zb /xx /xl /fp /ns /nc /ndl /np /njh /njs | sed -e s/^^[\t\x20]\+// | sed -e /^^$/d | sed -e s/^!source:\=\\!// | sed -e s/.*/xcopy\x20\/Fvikrhyz\x20\x22!source:\=\\!^&\x22\x20\x22!archive:\=\\!^&\x22/"
Try the following:
cmd.exe /v /c "set foo=hello, world & echo !foo!"
The /v argument enables delayed variable expansion. This allows you to access variables' values at execution time rather than at parse time (the default). You do this by writing !foo! instead of %foo% (with this /v option turned on).
Instead of passing /v, you can also turn delayed variable expansion on permanently via the registry, which obviously affects the entire system:
[HKEY_LOCAL_MACHINE\Software\Microsoft\Command Processor]
"DelayedExpansion"= (REG_DWORD)
1=enabled 0=disabled (default)
I could not get an IF command to work with either call or cmd /v /c so I would like to offer another option for those who may need it:
use FOR /F to declare and use a variable within a single command.
For example:
FOR /F %i IN ('echo 123') DO (IF %i==123 echo done)
The %i is the variable that is set with the result of the IN command, which in this example is 'echo 123'.
For values with single quotes or spaces, use the "usebackq tokens=*" flag to put the command in backquotes:
FOR /F "usebackq tokens=*" %i IN (`echo "hello, ' ' world!"`) DO (IF %i=="hello, ' ' world!" echo done)

run two commands in one windows cmd line, one command is SET command

[purpose]
This simple command sequence runs expected in the Windows' CMD shell:
dir & echo hello
will list the files and directories and echo the string.
However, the following command sequence does not run as expected (at least by me):
C:\Users\Administrator>set name=value & echo %name%
%name%
C:\Users\Administrator>echo %name%
value
C:\Users\Administrator>
As we can see, the first echo cannot get the environment. Could you help to comment? Any comment will be appreciated!
PS: OS:Windows 7 X64 Home Pre
Your result is due to the fact that %name% is expanded during the parsing phase, and the entire line is parsed at once, prior to the value being set.
You can get the current value on the same line as the set command in one of two ways.
1) use CALL to cause ECHO %NAME% to be parsed a 2nd time:
set name=value&call echo %^name%
I put a ^ between the percents just in case name was already defined before the line is executed. Without the caret, you would get the old value.
Note: your original line had a space before the &, this space would be included in the value of the variable. You can prevent the extra space by using quotes: set "name=value" &...
2) use delayed expansion to get the value at execution time instead of at parse time. Most environments do not have delayed expansion enabled by default. You can enable delayed expansion on the command line by using the appropriate CMD.EXE option.
cmd /v:on
set "name=value" & echo !name!
Delayed expansion certainly can be used on the command line, but it is more frequently used within a batch file. SETLOCAL is used to enable delayed expansion within a batch file (it does not work from the command line)
setlocal enableDelayedExpansion
set "name=value" & echo !name!
You can also use cmd /V /C (with /V to enable delayed expansion).
That is great to set an environment variable for just one command in Windows cmd.exe:
cmd /V /C "set "name=value" && echo !name!"
value
Note the usage of double-quotes in set "name=value" to avoid the extra space after value.
For instance, without double-quotes:
cmd /V /C "set name=value && echo '!name!'"
'value '
You would need to think to remove the space between value and &&:
cmd /V /C "set name=value&& echo '!name!'"
'value'
But using double-quotes makes the assignment more explicit.

Resources