Reputation: 13023
I have a pretty peculiar problem, echo
writes "ECHO is on.
" even if it has an argument, but only when printing to a pipe, for certain arguments.
My use case is I'm writing a script file to feed into an interpreter. On Linux I use heredocs to achieve this very nicely.
prog arg1 - arg3 arg4 \
<< EOM
start server load (
foreground
initial database '$database'
directory $directory
from file '${database}.ext'
);
EOM
For Windows' batch I recently discovered something very similar;
(
echo start server load ^(
echo foreground
echo initial database '%%d'
echo directory %directory%
echo from file '%%d.ext'
echo ^);
echo.
) | prog arg1 - arg3 arg4
But I kept getting the following error:
E7511-ACE: '-' line 6: An error has occurred while processing the DDM file.
Caused by
E2420-ACE: Expected punctuation character ')'.
4: directory 'C:\Users\redacted\AppData\Local\Temp'
5: from file 'testing.ext'
6: ECHO is on.
---^---
7: )
But if I instead, begrudgingly, use a temporary file, it all runs fine!
(
echo start server load ^(
echo foreground
echo initial database '%%d'
echo directory %directory%
echo from file '%%d.ext'
echo ^);
echo.
) > tmp.txt
prog arg1 tmp.txt arg3 arg4
Contents of tmp.txt
;
start server load (
foreground
initial database 'testing'
directory 'C:\Users\redacted\AppData\Local\Temp'
from file 'testing.ext'
);
I even tried mocking up a .bat
to try and show this but all it did was make me think I am crazy
@echo off
set databases=a
for %%d in (%databases%) do (
(
echo bob ^(
echo %%d
echo ^);
)
)
pause
output:
bob (
a
);
Press any key to continue . . .
I don't know any .exe
s that accept stdin
and just write it out to the screen/to a file for me to test my 'it's broken when piping' hypothesis (Windows' type
& echo
are less versatile than Linux' cat
& echo
),
do you guys have any ideas?
Upvotes: 3
Views: 324
Reputation: 67216
Another method is to use the LF macro variable to store a multi-line value in a variable:
@echo off
rem Define \n ("new line") variable:
set LF=^
%empty line 1/2, don't remove%
%empty line 2/2, don't remove%
set ^"\n=^^^%LF%%LF%^%LF%%LF%^^"
rem Define your multi-line value:
set multiLine=%\n%
start server load (%\n%
foreground%\n%
initial database '%%d'%\n%
directory %directory%%\n%
from file '%%d.ext'%\n%
);%\n%
cmd /V:ON /C echo !multiLine! | prog arg1 - arg3 arg4
rem If Delayed Expansion is enabled, use it this way:
setlocal EnableDelayedExpansion
cmd /V:ON /C echo ^^^!multiLine^^^! | prog arg1 - arg3 arg4
Upvotes: 1
Reputation: 130849
Another option is to embed the text within your script with a label prefix like :::
, and use FINDSTR to extract the lines. The FOR /F command can be used to strip off the :::
prefix, and the variables can be expanded if you use delayed expansion. I'm assuming your code is already within some type of FOR loop since you are using %%d
. The %%d
value must be transferred to an environment variable so that it can be expanded properly. I've added a bogus FOR loop just so that I have a %%d
value.
The piped commands are run within a new cmd.exe process, so delayed expansion must be enabled via an extra CMD command with the /v:on
option.
@echo off
setlocal
:::start server load (
::: foreground
::: initial database '!database!'
::: directory !directory!
::: from file '!database!.ext'
:::);
set "directory=c:\test"
for %%d in (test) do (
set "database=%%d"
cmd /v:on /c for /f "tokens=* delims=:" %%A in ('findstr "^:::" "%~f0"'^) do @echo %%A|prog arg1 - arg3 arg4
)
You can include multiple blocks of text with unique labels, and simply modify the FINDSTR and the FOR /F slightly. Below I still have just one text block, but I show how the label can be handled.
@echo off
setlocal
::1:start server load (
::1: foreground
::1: initial database '!database!'
::1: directory !directory!
::1: from file '!database!.ext'
::1:);
set "directory=c:\test"
for %%d in (test) do (
set "database=%%d"
cmd /v:on /c for /f "tokens=1* delims=:" %%A in ('findstr "^::1:" "%~f0"'^) do @echo %%B|prog arg1 - arg3 arg4
)
Upvotes: 1
Reputation: 82307
For the most cases the solution of MC ND (using three carets) should be used.
But when you want to output many special charaters in a line it gets annoying to add always the carets.
Like
(
echo amp^^^& caret^^^^ single quote^^"
echo ^^^<html^^^>^^^<body^^^>^^^</body^^^>^^^</html^^^>
) | more
The you could use the disappearing quotes technic.
SET "FOR=FOR /F "tokens=1,2" %%^! IN ("a") DO @("
(
%%FOR%% ^
echo %%"amp& caret^ single quote"
echo %%"<html><body></body></html>%%"
rem.^)
) | more
The technic itself is complex but using it is easy.
It uses the fact that the parser will see the quotes in %%"
and will quote the following charaters, but later it will replace %%"
with nothing.
Upvotes: 2
Reputation: 70933
Pipes are created between two processes. The code in your left side of the pipe is batch code so a new cmd
instance is started to create the process for the left side of the pipe and the code is passed to this instance.
Your escape character is properly written for the current cmd
instance, but when the code is passed to the new instance there is no escape codes as they were consumed when preparing the code to execute in the new cmd
instance.
You will need to double escape your closing parenthesis, escaping the escape character and escaping the parenthesis so the new cmd
instance receives a properly escaped parenthesis.
(
echo start server load (
echo foreground
echo initial database '%%d'
echo directory %directory%
echo from file '%%d.ext'
echo ^^^);
echo.
) | prog arg1 - arg3 arg4
The redirection and the for
code in the question work without any problem because the code is being executed in the current cmd
instance.
Upvotes: 3