1

I found there are some topics similar to this problem but not exactly what I want, so I raise this topic.

I want to create a log function for printing message to both formatted log file and console output. The function is as below:

:LOGDEBUG
@echo DEBUG: %~1
if NOT EXIST %LOG_FILE% exit /b 1
@echo [%date - %time%] DEBUG: %~1 >> %LOG_FILE% 2>&1
exit /b 0

And I try to use it for printing the command execution output and if the output contains special character like "<" and ">", this function doesn't work well and prompt "The system cannot find the file specified". My code for executing a command is below:

for /f "usebackq delims=" %%a in (`dir c:\temp`) do (
    CALL :LOGDEBUG "%%a"
)

However, when I use "echo" command directly instead of the log function, the output can be printed correctly on the console. Like the following code:

for /f "usebackq delims=" %%a in (`dir c:\temp`) do (
    echo %%a
)

May I know what is the problem, and how can I print the output correctly by using the log function? Thanks

Elliott
  • 141
  • 3
  • 14
  • check this http://stackoverflow.com/a/10719322/388389 – npocmaka Aug 30 '15 at 05:05
  • How is defined the `%LOG_FILE%` variable? And try `echo CALL :LOGDEBUG "%%a"` instead of `CALL :LOGDEBUG "%%a"` to see e.g. `CALL :LOGDEBUG " Volume in drive C is SysDisk"` (: – JosefZ Aug 30 '15 at 07:21
  • Hi @npocmaka, I want the both log file and console output are formatted before print. I think I can't directly use tee.bat, am I right? – Elliott Aug 30 '15 at 15:50
  • Hi @JosefZ, sorry for missing of defining the %LOG_FILE% variable in the code. It is just a location of a log file, says c:\program.log. For the echo output of the two problematic lines are "CALL :LOGDEBUG "13/06/2015 00:02 ." and "CALL :LOGDEBUG "13/06/2015 00:02 .." – Elliott Aug 30 '15 at 15:52
  • Hi JosefZ & Mofi, I have tried of your answers and both work!. Thank you so much. I personally pick JosefZ's answer as I prefer to setlocal before declare variable but I think it is not quite worth to set & endlocal in the small and frequently called routine like LOGDEBUG, although Mofi explained my problems very detail and clear and I learnt a lot from him. – Elliott Sep 03 '15 at 08:25

2 Answers2

1

Here is the batch code which should work.

@echo off
setlocal EnableDelayedExpansion
set "LOG_FILE=C:\program.log"
del "%LOG_FILE%" 2>nul
for /F "delims=" %%a in ('dir "C:\temp\*" 2^>nul') do call :LOGDEBUG "%%a"
endlocal
goto :EOF

:LOGDEBUG
set "StringToOutput=%~1"
echo DEBUG: !StringToOutput!
echo [%DATE% - %TIME%] DEBUG: !StringToOutput!>>"%LOG_FILE%"
goto :EOF

First delayed environment variable expansion is enabled and a copy of existing environment table is made. It is explained below why this is done.

Next the name of the log file with full path is assigned to an environment variable in local variable table. This path can be with or without 1 or more spaces in path. The log file is deleted in case of existing already from a previous run. This code can be removed if you want to append new lines to already existing file. But you should add in this case code to avoid that the log file permanently increases until no free storage space anymore.

The FOR command executes the command DIR and processes each line of the output of DIR written to stdout. Blank lines are skipped. The default delimiters are space, tab and newline characters. As wanted here are the entire lines of DIR, the default delimiter list is replaced by nothing which means only newline characters remain and loop variable %a gets assigned always an entire non blank line.

The output of command DIR contains < and > which are interpreted as redirection operators if found by command processor within a line not quoted. Therefore the line for DIR output is passed quoted to subroutine LOGDEBUG. Which characters must be usually quoted are listed on last help page printed into a command prompt window when executing cmd /? in a command prompt window.

When the loop has finished, the local environment table is deleted which means LOG_FILE and StringToOutput are also removed, and previous environment is restored which usually means the delayed expansion is turned off again before batch execution exits with a jump to predefined label to end of file.

The subroutine LOGDEBUG first assigns the passed string to an environment variable without surrounding quotes just needed because of special characters in line like < and >.

Next the line is written to console window without quotes using delayed expansion as otherwise < and > would be interpreted as redirecting operators and not literally.

The same line is written also to the log file with the difference of date and time inserted at beginning of line. You missed the percent sign after date in your code. Again delayed expansion is used to get the line with the characters < and > written to file without being interpreted as redirection operators.

Important is also that there is no space before >> as otherwise each line in log file would have a trailing space. 2>&1 is useless here as command echo does not write something to stderr.

The subroutine is exited with a jump to end of file resulting in command FOR processes next line.

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.

  • call /?
  • del /?
  • dir /?
  • for /?
  • goto /?
  • set /?

It would be of course possible to do all the output directly in body of command FOR without using a subroutine.

@echo off
setlocal EnableDelayedExpansion
set "LOG_FILE=C:\program.log"
del "%LOG_FILE%" 2>nul
for /F "delims=" %%a in ('dir "C:\temp\*" 2^>nul') do (
    echo DEBUG: %%a
    echo [!DATE! - !TIME!] DEBUG: %%a>>"%LOG_FILE%"
)
endlocal

Delayed variable expansion is nevertheless required here as otherwise %DATE% and %TIME% would be expanded by command processor like %LOG_FILE% already on parsing entire block defined by ( and ) before command FOR is executed at all which would result in same date and time written for all lines to the log file.

Mofi
  • 46,139
  • 17
  • 80
  • 143
1

You have answered your question by own: when I use "echo" command directly...

@ECHO OFF
SETLOCAL EnableExtensions
set "LOG_FILE=D:\tempx\program.log"     my testing value 
rem type NUL>"%LOG_FILE%"
for /f "usebackq delims=" %%a in (`dir d:\temp 2^>NUL`) do (
    CALL :LOGDEBUG "%%a"
)
rem type "%LOG_FILE%"
ENDLOCAL
exit /b

:LOGDEBUG
FOR %%A in ("%~1") do (
  @echo DEBUG: %%~A
  if NOT EXIST "%LOG_FILE%" exit /b 1
  @echo [%date% - %time%] DEBUG: %%~A >> "%LOG_FILE%" 2>&1
)
exit /b 0

Resources (required reading):

JosefZ
  • 28,460
  • 5
  • 44
  • 83