9

@echo on and @echo off don't seem to have any affect when executed within a bracketed if block in a batch file. Here's a simple demo:

@echo off
echo Test #1
if 1 == 1 (
  @echo on
  echo Test #2
  @echo off
  echo Test #3
)
@echo on
echo Test #4

The output from running the above on the command line is:

Test #1
Test #2
Test #3
C:\mybatchfilelocation>echo Test #4
Test #4

Could anyone explain this and/or suggest a workaround? (Expect it could probably be fixed by copious use of goto and labels but would prefer to continue using bracketed if blocks if possible...)

Steve Chambers
  • 37,270
  • 24
  • 156
  • 208
  • I think it is because the current `echo on/off` state affects the entire line or block only; if you want to control it for each command individually, then set `echo on` and precede each command you want to hide with `@`; – aschipfl Jan 18 '16 at 11:55
  • Thanks but unfortunately it looks like `echo on` won't echo commands within the parentheses (see my newly added answer below). – Steve Chambers Jan 18 '16 at 11:59
  • I don't understand, both `echo`s in your answer _are_ `echo`ed; put `@` before one of the `echo`s to hide it; put `@` in front of the `(` to hide everything after `if`... – aschipfl Jan 18 '16 at 12:02
  • This isn't workable - I don't want the `if` statement itself to be echoed: If I use `@if`, none of it will be echoed but with any other use of `@` I'm always going to see the `if 1 == 1` command being echoed at the very least. – Steve Chambers Jan 18 '16 at 12:13
  • 1
    Ah, now I see... yes, the `@` hides the `echo` of the whole (following) command line; unfortunately there is no `echo on`-character, so you'll need to work with `goto`s and labels, although the code will look terrible... – aschipfl Jan 18 '16 at 12:21

4 Answers4

8

As you have discovered, the changed ECHO state is not recognized by the parser until it reaches a statement that is after the code block that contains the ECHO ON/OFF.

But there is one exception - the commands after FOR ... DO do take on the state change within the same block :-)

Note that you only need @ to suppress command output when ECHO is currently ON. If it is OFF, then there is no need for @ECHO ON. And if you turn it ON and OFF within the same code block, then you don't need it there either.

Here is a demo that echos the even test lines:

@echo off
echo Test #1
(
  echo on
  for %%. in (.) do echo Test #2
  echo off
  echo Test #3
  echo on
  for %%. in (.) do echo Test #4
  echo off
  echo Test #5
)
echo on
echo Test #6
@echo off
echo Test #7

-- OUTPUT --

Test #1

C:\test>echo Test #2
Test #2
Test #3

C:\test>echo Test #4
Test #4
Test #5

C:\test>echo Test #6
Test #6
Test #7

You might find it convenient to declare a simple echo_on "macro". The following produces the exact same output:

@echo off
setlocal

set "echo_on=echo on&for %%. in (.) do"

echo Test #1
(
  %echo_on% echo Test #2
  echo off
  echo Test #3
  %echo_on% echo Test #4
  echo off
  echo Test #5
)
echo on
echo Test #6
@echo off
echo Test #7
dbenham
  • 127,446
  • 28
  • 251
  • 390
  • Great answer and nice trick! Just one thing, do you know anything about the internals - i.e. as to why `FOR`...`DO` is the exception to the rule? – Steve Chambers Jan 18 '16 at 21:13
  • 1
    @SteveChambers - I suppose because FOR is the only command that ECHOs multiple times. The first ECHO for the entire FOR statement is suppressed. But the DO commands are subsequently ECHOed once for each iteration. See http://stackoverflow.com/a/4095133/1012053 for more info. – dbenham Jan 18 '16 at 21:33
2

Have just found out what is causing this by turning the echo on before an if block.

@echo on
if 1 == 1 (
  echo Test #1
  echo Test #2
)

This outputs:

C:\mybatchfilelocation>if 1 == 1 (
echo Test #1
echo Test #2
)
Test #1
Test #2

So the statement that is echoed is the entire if block rather than each statement within it. This answer explains this further and this answer gives a workaround - unfortunately not what I wanted to hear but it looks like lots of gotos and labels may be the only workable solution.

Community
  • 1
  • 1
Steve Chambers
  • 37,270
  • 24
  • 156
  • 208
  • 1
    In my experience, creating a "block" with parentheses is not really like doing such a thing in any real programming language because the entire thing gets parsed/var-substituted at once before it's ever run. Without ever having tried what you're doing, I would have expected it to work as you did, but I can't say that I'm surprised to see quirky behavior out of cmd/if. – mojo Jan 18 '16 at 14:02
  • 1
    This behavior is the same that required delayed expansion to access variables modified inside a block: if `echo on` _before_ the block, all the lines in the block will be showed even if you put an `echo off` inside it, and vice versa: if `echo off` before the block, all lines will not be shown even with an `echo on` inside the block. The last echo status is used in the first line after the block. – Aacini Jan 18 '16 at 15:09
2

You could do something like this to achieve the result you want:

@echo off
set "ExecuteCmd=echo Test #2"
echo Test #1
if 1 == 1 (
  echo %ExecuteCmd%
  %ExecuteCmd%
  echo Test #3
)
@echo on
echo Test #4
RGuggisberg
  • 4,630
  • 2
  • 18
  • 27
  • Not a bad attempt at an alternative but since there are many possible commands that can be executed within an `if` block it looks like `goto`s and labels are going to be more practical. – Steve Chambers Jan 18 '16 at 12:45
1

How about putting the echo control commands inside a subroutine?

@echo off
echo Test #1
if 1 == 1 (
  CALL :DO_ECHO_ON
  echo Test #3
)
@echo on
echo Test #4

@EXIT /B

:DO_ECHO_ON
@ECHO ON
echo Test #2
@ECHO OFF
@EXIT /B

This produces the output I'd expect from your description.

Test #1

C:\secret>echo Test #2 
Test #2
Test #3

C:\secret>echo Test #4 
Test #4

Based on one of your comments, I suppose you want to be able to turn echo on for arbitrary commands. This modified version does that.

@echo off
echo Test #1
if 1 == 1 (
  CALL :DO_ECHO_ON_CMD echo Test #2
  echo Test #3
  CALL :DO_ECHO_ON_CMD dir /b "C:\Program Files"
  echo Test #3.1
  CALL :DO_ECHO_ON_CMD attrib c:\Windows
)
@echo on
echo Test #4

EXIT /B

:DO_ECHO_ON_CMD
@ECHO ON
%*
@ECHO OFF
@EXIT /B 0
mojo
  • 4,050
  • 17
  • 24