I'm trying to make jrepl.bat work on several lines. See this little test:
set NLM=^
set NL=^^^%NLM%%NLM%^%NLM%%NLM%
echo First line%NL%second line | jrepl.bat i o
Is there a reason why it only prints the first line:
Forst lone
?
As #MCND mentioned it's a problem of the child process and that the line is parsed two times.
But it can be done with a simple linefeed.
set LF=^
cmd /q /v /c"echo First line^!LF^!second line" | jrepl.bat i o
This works independent of the delayed expansion mode.
As if delayed expansion is enabled in the batch file, the exclamation marks are escaped in the delayed expansion parser phase (and the carets are removed).
Else with delayed expansion disabled the line with the carets will be executed unchanged in the child cmd.exe process, but there the carets are removed in the special charaters phase so the delayed expansion will also work later.
Pipes are created between processes, so, the echo command is executed in a separate cmd instance that does not parse the input line (with the inner line feeds) as you expect.
Try with
setlocal enableextensions disabledelayedexpansion
set NLM=^
set NL=^%NLM%%NLM%^%NLM%%NLM%
cmd /q /v /c"echo First line!NL!second line" | jrepl.bat i o
Related
I want to put a user inputted string which contains an ampersand into the clipboard using batch. I can modify the string, and I can get it to print to the terminal using setlocal EnableExtensions EnableDelayedExpansion
but I can't pipe it to the clipboard.
There is an in depth discussion here which talks about why pipes can break things, but I couldn't understand it well enough to get around my problem.
https://www.robvanderwoude.com/battech_inputvalidation_setp.php
setlocal EnableExtensions EnableDelayedExpansion
set /P "INPUT=Paste the stuff in the terminal please"
set "SEARCHTEXT=+"
set REPLACETEXT=%%2B
for /F "delims=" %%A in ("%INPUT%") do (
set "string=%%A"
set "modified=!string:%SEARCHTEXT%=%REPLACETEXT%!"
echo !modified! | clip
)
Because the string I'm trying to modify contains "&username" in it, the output I get is:
'username' is not recognized as an internal or external command,
operable program or batch file.
If I only echo !modified!, there are no errors. How can I get an arbitrary un-sanitized string into the clipboard?
The major problem in your code is the following line:
echo !modified! | clip
A pipe (|) creates a new cmd instance for either side. You have got delayed expansion enabled in your script, so the variable !modified! becomes expanded when the whole command line is parsed, then the pipe is executed, and then the new cmd instance for the left side receives the variable already expanded, including all potential poisonous characters, like &, for example.
To prevent !modified! to be expanded immediately, we need to escape the exclamation marks like ^^! (^^ becomes first escaped to a single ^, so ^! is left during the delayed expansion phase), which lets the ! be treated as a literal character and no variable expansion happens at first.
The new cmd instance (for the left side of the pipe in our situation) now has got delayed expansion disabled, so we need to explicitly instantiate another (nested) one with delayed expansion enabled (by cmd /V):
cmd /V /C echo(^^!modified^^!| clip
With this technique we force the variable !modified! to be expanded as late as possible, hence by the inner-most cmd instance, which avoids the expanded string to be received by any other instance, and therefore, poinsonous characters become hidden from the parser.
In addition, I used the safe echo variant echo(..., because echo ... might fail under certain circumstances (imagine ... is the literal string /?). Moreover, I removed the SPACE in front of |, because such would become echoed as well, unintentionally.
When I try to make a variable, sometimes it won't recall when surrounded with %. An example is
#echo off
set firstvar= <o
set secondvar= ^. .^
echo.
echo %firstvar%
echo %secondvar%
pause
I'm wanting it to show up as
<o
^. .^
but I get
The system cannot find the file specified.
ECHO is off.
. .echo.
Press any key to continue . . .
I'm really confused by this and I can't seem to fix it. I've narrowed down the problem to be something about the rules of assigning variables, and I'm guessing that the <o is being viewed as HTML or a website or something. I'm really confused with the second line though, why does it say ECHO is off? Why . .echo.? Any help would be appreciated, thank you.
First, read the answer on Why is no string output with 'echo %var%' after using 'set var = text' on command line? for an explanation why it is advisable to use the syntax set "variable=value" on assigning something to an environment variable.
Second, read the Microsoft article about Using Command Redirection Operators for an explanation of meaning of < in a Windows command line.
Third, caret character ^ is interpreted by Windows command interpreter as escape character like the backslash character \ by many programming and scripting languages.
So the batch code with immediate environment variable expansion should be coded as:
#echo off
set "firstvar= ^<o"
set "secondvar= ^^. .^^"
echo/
echo %firstvar%
echo %secondvar%
echo Okay!
The strings to output are easier to code on using delayed expansion:
#echo off
setlocal EnableExtensions EnableDelayedExpansion
set "firstvar= <o"
set "secondvar= ^. .^"
echo/
echo !firstvar!
echo !secondvar!
echo Okay^^!
endlocal
^ and < must not be escaped inside a double quoted string and delayed expansion is used on outputting the strings of the environment variables.
Note 1: The two environment variables firstvar and secondvar do not exist anymore after the line with endlocal which is usually the last line in a batch file on using setlocal. Read this answer for details about the commands SETLOCAL and ENDLOCAL.
Note 2: A percent sign % must be escaped with another % and not with ^ to be interpreted as literal percent sign and not as begin/end of an environment variable reference expanded during preprocessing of a command line or an entire command block starting with ( and ending with matching ) before running a command line.
Note 3: Any string including directory/file names containing an exclamation mark ! is interpreted different on having delayed expansion enabled. Then the exclamation mark is interpreted as begin/end of an environment variable reference expanded delayed after preprocessing a command line/block. ! must be escaped with two ^ on delayed expansion being enabled to be interpreted as literal character. The command line with ^^! changes to just ^! on first parsing it by Windows command processor. The command line changes to literally interpreted ! on second parsing of the command line because of enabled delayed expansion.
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.
cmd /? ... Windows command interpreter
echo /?
endlocal /?
set /?
setlocal /?
And read also DosTips forum topic ECHO. FAILS to give text or blank line - Instead use ECHO/ why it is better to use echo/ instead of echo. to output an empty line.
Last read also Debugging a batch file for instructions how to see what the command interpreter really executes after preprocessing each command line/block.
See also: How does the Windows Command Interpreter (CMD.EXE) parse scripts?
The caret ^ is the escape symbol, to echo it literally you have to double it ^^ once for every processing step (setting/echoing).
The redirecting/piping symbols <|> have to be escaped with a ^ caret.
If a string/variable to echo evaluates to nothing echo returns the status.
To avoid this use a different delimiter sign instead of a space \/(:; etc.
#echo off
set firstvar= ^^^<o
set secondvar= ^^^^. .^^^^
echo.
echo:%firstvar%
echo:%secondvar%
pause
This part works well until there is an ampersand in the filename, in which case it crashes my script completely.
echo %filename% | findstr /i /b /c:"%name% (%year%)"
I can't just put the filename into quotation marks because I need to find the string at the beginning. So how can I do both?
For command line use:
echo %file^name:^&=^^^&% | ...
Inside a batch file
echo %%filename:^&=^^^&%% | ...
How it works?
As a pipe creates two new cmd.exe instances, the echo ... will be parsed twice.
The trick is to expand the filename only in the second expansion.
And then to expand and replace the & with ^& to avoid problems with the &.
The caret will be used to escape the ampersand and itself will be removed.
In the second expansion the parser only sees echo %filename:&=^&%.
To force the expansion into the second parse step, the percent signs have to be doubled for batch files.
From the command line, this doesn't work, but a simple caret anywhere in the variable name works.
Alternative solution:
echo "%filename%" | findstr /i /b /c:^"\"%filename% (%year%)\""
This adds simply quotes and uses also quotes in the search expression
Another option is to use delayed expansion, which requires an explicit cmd with the /v:on option.
cmd /v:on /c "(echo !filename!)" | findstr /i /b /c:"%name% (%year%)"
If your batch script already has enabled delayed expansion, then parentheses around the left side are needed to prevent the delayed expansion from occurring within the parent script (see Why does delayed expansion fail when inside a piped block of code?). The child process will still default to disabled delayed expansion, so the cmd /v:on /c ... is still needed.
#echo off
setlocal enableDelayedExpansion
...
(cmd /v:on /c "(echo !filename!)") | findstr /i /b /c:"%name% (%year%)"
Another way to delay the expansion until the sub-process is to escape the expansion
#echo off
setlocal enableDelayedExpansion
...
cmd /v:on /c "(echo ^!filename^!)" | findstr /i /b /c:"%name% (%year%)"
I have TeamCity build step in command line which is executed on Windows 7.
We should escape variables in script with double percent and etc.
How to escape next construction for using it in TC step?
set word=\\
set Build=%Build:\=!word!%
set enter=\n
set Build=%Build:""=!enter!%
I guess you refer to teamcity buildstep If
you want to escape the percent sign from being expanded one way
is to double the percentsign which is also a method of delayed
expansion. The exclamation mark is the the replacement char for
the percent sign when using setlocal EnableDelayedExpansion
It's not clear what you are trying to accomplish with your code.
In cmd the \n replacement won't work.
If your intention is to simply pass the % unchanged through an
echo in cmd the doubling should suffice. If delayed expansion is
enabled you may have to escape the exclamation mark with a caret
^!
This code :
#Echo off&setlocal EnableDelayedExpansion
Set Build=C:\Test\Build""secondline
set word=\\
set LF=^
rem The TWO empty lines above are required
rem See http://stackoverflow.com/a/5642300/6811411
set Build2=%Build:\=!word!%
set Build2=%Build2:""=^^!LF^^!%
Echo Build =%Build%
Echo Build2=%Build2%
Produces this output here:
Build =C:\Test\Build""secondline
Build2=C:\\Test\\Build
secondline
I want to split a string in two parts, without using any for loop.
For example, I have the string in a variable:
str=45:abc
I want to get 45 in a variable and abc in another variable. Is it possible in batch file?
pattern is like somenumber:somestring
You could split the str with different ways.
The for loop, you don't want use it.
The trailing part is easy with the * (match anything until ...)
set "var2=%str:*:=%"
The leading part can be done with a nasty trick
set "var1=%str::="^&REM #%
The caret is needed to escape the ampersand,
so effectivly the colon will be replaced by "&REM #
So in your case you got the line after replacing
set "var1=4567"&REM #abcde
And this is splitted into two commands
set "var1=4567"
REM #abcde`
And the complete code is here:
set "str=4567:abcde"
echo %str%
set "var1=%str::="^&REM #%
set "var2=%str:*:=%"
echo var1=%var1% var2=%var2%
Edit 2: More stable leading part
Thanks Dave for the idea to use a linefeed.
The REM technic isn't very stable against content with quotes and special characters.
But with a linefeed trick there exists a more stable version which also works when the split argument is longer than a single character.
#echo off
setlocal enableDelayedExpansion
set ^"str=456789#$#abc"
for /F "delims=" %%a in (^"!str:#$#^=^
!^") do (
set "lead=%%a"
goto :break
)
:break
echo !lead!
Solution 3: Adpated dbenhams answer
Dbenham uses in his solution a linefeed with a pipe.
This seems a bit over complicated.
As the solution uses the fact, that the parser removes the rest of the line after an unescaped linefeed (when this is found before or in the special character phase).
At first the colon character is replaced to a linefeed with delayed expansion replacement.
That is allowed and the linefeed is now part of the variable.
Then the line set lead=%lead% strips the trailing part.
It's better not to use the extended syntax here, as set "lead=%lead%" would break if a quote is part of the string.
setlocal enableDelayedExpansion
set "str=45:abc"
set ^"lead=!str::=^
!"
set lead=%lead%
echo "!lead!"
You can try this . If its fixed that numbers to left of the colon will be always 2 & to the right will be 3. Then following code should work assuming your str has the value.
set "str=45:abc"
echo %str%
set var1=%str:~0,2%
set var2=%str:~3,3%
echo %var1% %var2%
Keep me posted. :)
It seems pointless to avoid using a FOR loop, but it does make the problem interesting.
As jeb has pointed out, getting the trailing part is easy using !str:*:=!.
The tricky bit is the leading part. Here is an alternative to jeb's solution.
You can insert a linefeed into a variable in place of the : using the following syntax
setlocal enableDelayedExpansion
set "str=45:abc"
echo !str::=^
!
--OUTPUT--
45
abc
The empty line above the last ! is critical.
I'm not sure why, but when the output of the above is piped to a command, only the first line is preserved. So the output can be piped to a FINDSTR that matches any line, and that result directed to a file that can then be read into a variable using SET /P.
The 2nd line must be eliminated prior to using SET /P because SET /P does not recognize <LF> as a line terminator - it only recognizes <CR><LF>.
Here is a complete solution:
#echo off
setlocal enableDelayedExpansion
set "str=45:abc"
echo(!str::=^
!|findstr "^" >test.tmp
<test.tmp set /p "var1="
del test.tmp
set "var2=!str:*:=!"
echo var1=!var1! var2=!var2!
Update
I believe I've mostly figured out why the 2nd line is stripped from the output :)
It has to do with how pipes are handled by Windows cmd.exe with each side being processed by a new CMD.EXE thread. See Why does delayed expansion fail when inside a piped block of code? for a related question with a great answer from jeb.
Just looking at the left side of the piped command, I believe it is parsed (in memory) into a statement that looks like
C:\Windows\system32\cmd.exe /S /D /c" echo {delayedExpansionExpression}"
I use {delayedExpansionExpression} to represent the multi-line search and replace expansion that has not yet occurred.
Next, I think the variable expression is actually expanded and the line is broken in two by the search and replace:
C:\Windows\system32\cmd.exe /S /D /c" echo 43
abc"
Only then is the command executed, and by normal cmd.exe rules, the command ends at the linefeed. The quoted command string is missing the end quote, but the parser doesn't care about that.
The part I am still puzzled by is what happens to the abc"? I would have thought that an attempt would be made to execute it, resulting in an error message like 'abc"' is not recognized as an internal or external command, operable program or batch file. But instead it appears to simply get lost in the ether.
note - jeb's 3rd comment explains why :)
Safe version without FOR
My original solution will not work with a string like this & that:cats & dogs. Here is a variation without FOR that should work with nearly any string, except for string length limits and trailing control chars will be stripped from leading part.
#echo off
setlocal enableDelayedExpansion
set "str=this & that:cats & dogs"
set ^"str2=!str::=^
!^"
cmd /v:on /c echo ^^!str2^^!|findstr /v "$" >test.tmp
<test.tmp set /p "var1="
del test.tmp
set "var2=!str:*:=!"
echo var1=!var1! var2=!var2!
I delay the expansion until the new CMD thread, and I use a quirk of FINDSTR regex that $ only matches lines that end with <cr>. The first line doesn't have it and the second does. The /v option inverts the result.
Yes, I know this is a very old topic, but I just discovered it and I can't resist the temptation of post my solution:
#echo off
setlocal
set "str=45:abc"
set "var1=%str::=" & set "var2=%"
echo var1="%var1%" var2="%var2%"
You may read full details of this method here.
In the Light of people posting all sorts of methots for splitting variables here i might as well post my own method, allowing for not only one but several splits out of a variable, indicated by the same symbol, which is not possible with the REM-Method (which i used for some time, thanks #jeb).
With the method below, the string defined in the second line is split into three parts:
setlocal EnableDelayedExpansion
set fulline=one/two/three or/more
set fulline=%fulline%//
REM above line prevents unexpected results when input string has less than two /
set line2=%fulline:*/=%
set line3=%line2:*/=%
set line1=!fulline:/%line2%=!
set line2=!line2:/%line3%=!
setlocal DisableDelayedExpansion
echo."%line1%"
echo."%line2%"
echo."%line3%"
OUTPUT:
"one"
"two"
"three or/more//"
i recommend using the last so-created partition of the string as a "bin" for the remaining "safety" split-characters.
Here's a solution without nasty tricks for leading piece
REM accepts userID#host
setlocal enableDelayedExpansion
set "str=%1"
set "host=%str:*#=%"
for /F "tokens=1 delims=#" %%F IN ("%str%") do set "user=%%F"
echo user#host = %user%#%host%
endlocal