restore the previous echo status
Asked Answered
T

6

9

In a DOS Batch File subroutine, how can I turn off echo within the subroutine, but before returning, put it back to what it was before (either on or off)?

For example, if there was a command called echo restore, I would use it like this:

echo on
... do stuff with echoing ...
call :mySub
... continue to do stuff with echoing ...
exit /b

:mySub
@echo off
... do stuff with no echoing ...
echo restore
goto :EOF
Tancred answered 6/3, 2012 at 22:10 Comment(0)
T
6

My first attempt was an utter failure - thanks jeb for pointing out the errors. For those that are interested, the original answer is available in the edit history.

Aacini has a good solution if you don't mind putting your subroutine in a separate file.

Here is a solution that works without the need of a 2nd batch file. And it actually works this time! :)

(Edit 2 - optimized code as per jeb's suggestion in comment)

:mysub
::Silently get the echo state and turn echo off
@(
  setlocal
  call :getEchoState echoState
  echo off
)
::Do whatever
set return=returnValue
::Restore the echo state, pass the return value across endlocal, and return
(
  endlocal
  echo %echoState%
  set return=%return%
  exit /b
)

:getEchoState echoStateVar
@setlocal
@set file=%time%
@set file="%temp%\getEchoState%file::=_%_%random%.tmp"
@(
  for %%A in (dummy) do rem
) >%file%
@for %%A in (%file%) do @(
  endlocal
  if %%~zA equ 0 (set %~1=OFF) else set %~1=ON
  del %file%
  exit /b
)

If you are willing to put up with the slight risk of two processes simultaneously trying to access the same file, the :getEchoState routine can be simplified without the need of SETLOCAL or a temp variable.

:getEchoState echoStateVar
@(
  for %%A in (dummy) do rem
) >"%temp%\getEchoState.tmp"
@for %%A in ("%temp%\getEchoState.tmp") do @(
  if %%~zA equ 0 (set %~1=OFF) else set %~1=ON
  del "%temp%\getEchoState.tmp"
  exit /b
)
Tucson answered 7/3, 2012 at 1:45 Comment(4)
Nice idea, but it can't work! As the echo in for /f "tokens=3 delims=. " %%A in ('echo') will be executed in a new cmd-context and is always ON. The second problem is that even if you get the state it's localized, as on my system I got ECHO ist eingeschaltet (ON).Locomotive
Of course you are correct, damn it :) The worst part is, I had already discovered all of this a long time ago (except for the German issue), and forgot! I will come up with a working version.Tucson
You could use redirection echo>tmpfile.tmp and then use a FOR-loop. Perhaps the last token is always ON/OFF or (ON)/(OFF)Locomotive
Now the call :getEchoState2 could be optimized to FOR %%A in (dummy) do rem, so it doesn't need a call/LabelLocomotive
N
5

The simplest way is to not turn echo off in the first place.

Instead, do what you currently do with the echo off line to the rest of your subroutine - prefix all commands in the subroutine with an @ sign. This has the effect of turning off echo for that command, but keeps the echo state for future commands.

If you use commands that execute other commands, like IF or DO, you will also need to prefix the "subcommand" with an @ to keep them from being printed when echo is otherwise on.

Nonmetallic answered 6/3, 2012 at 22:18 Comment(1)
During debug you want to see the commands printed, but after release you do not. Swapping many odd placed @s in and out is a bit of a maintenance nightmare.Devy
T
5

The easiest way is to extract the subroutine to another .bat file and call it via CMD /C instead of CALL this way:

echo on
... do stuff with echoing ...
cmd /C mySub
... continue to do stuff with echoing ...
exit /b

mySub.bat:

@echo off
... do stuff with no echoing ...
exit /b

This way the echo status will be automatically restored to the value it had when the CMD /C was executed; the only drawback of this method is a slightly slower execution...

Trailer answered 7/3, 2012 at 5:34 Comment(0)
W
1

Here is a straight forward solution that relies on a single temporary file (using %random% to avoid race conditions). It works and is at least localization resistant, i.e., it works for the two known cases stated by @JoelFan and @jeb.

@set __ME_tempfile=%temp%\%~nx0.echo-state.%random%.%random%.txt
@set __ME_echo=OFF
@echo > "%__ME_tempfile%"
@type "%__ME_tempfile%" | @"%SystemRoot%\System32\findstr" /i /r " [(]*on[)]*\.$" > nul
@if "%ERRORLEVEL%"=="0" (set __ME_echo=ON)
@erase "%__ME_tempfile%" > nul
@::echo __ME_echo=%__ME_echo%
@echo off
...
endlocal & echo %__ME_echo%
@goto :EOF

Add this preliminary code to increase the solution's robustness (although the odd's are high that it's not necessary):

@:: define TEMP path
@if NOT DEFINED temp ( @set "temp=%tmp%" )
@if NOT EXIST "%temp%" ( @set "temp=%tmp%" )
@if NOT EXIST "%temp%" ( @set "temp=%LocalAppData%\Temp" )
@if NOT EXIST "%temp%" ( @exit /b -1 )
:__ME_find_tempfile
@set __ME_tempfile=%temp%\%~nx0.echo-state.%random%.%random%.txt
@if EXIST "%__ME_tempfile%" ( goto :__ME_find_tempfile )
Welford answered 26/8, 2012 at 16:55 Comment(0)
M
1

I wasn't really happy with the solution above specially because of the language issue and I found a very simple one just by comparing the result from current echo setting with the result when explicitly set OFF. This is how it works:

::  SaveEchoSetting
::  :::::::::::::::::::::::::::
::  Store current result    
@echo>  %temp%\SEScur.tmp

::  Store result when explicitly set OFF
@echo   off
@echo>  %temp%\SESoff.tmp

::  If results do not match, it must have been ON ... else it was already OFF
@for /f "tokens=*" %%r in (%temp%\SEScur.tmp) do (
    @find   "%%r" %temp%\SESoff.tmp > nul
    @if     errorlevel 1 (
        @echo   @echo on > %temp%\SESfix.bat
    ) else (
        @echo   @echo off > %temp%\SESfix.bat
    )
)

::  
::  Other code comes here
::  Do whatever you want with echo setting ...
::

::  Restore echo setting
@call   %temp%\SESfix.bat
Marlin answered 25/9, 2015 at 16:37 Comment(0)
S
0

I was looking for the same solution to the same problem, and after reading your comments I had an idea (which is not the answer to the question, but for my problem is even better).

I wasn't satisfied with the cmd.exe /c mysub.cmd because it makes hard or even impossible to return variables (I didn't check) - (couldn't comment because it's the first time I post here :)

Instead noticed that all we want -in the end- is to suppress stdout:

echo on

rem call "mysub.cmd" >nul
call :mysub >nul

echo %mysub_return_value%

GOTO :eof 

:mysub
    setlocal
    set mysub_return_value="ApplePie"
    endlocal & set mysub_return_value=%mysub_return_value%
GOTO :eof

It works fine with labelled subroutines, with subroutines contained in .cmd files, and I suppose it would work fine even with the cmd.exe /c variant (or start). It also has the plus that we can keep or discard the stderr, replacing >nul with >nul 2>&1

I note that ss64.com scares kids like me stating that with call "Redirection with & | <> also does not work as expected". This simple test works as expected. He must have been thinking of more complex situations.

Stereoscope answered 4/12, 2013 at 0:25 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.