批处理中输出重定向的问题

7

我有一个脚本a.cmd,调用另一个脚本b.cmd,并将其输出重定向。被调用的脚本启动一个从未终止的可执行文件,该可执行文件的输出被重定向到自己的日志文件中。简化代码:

a.cmd:

[1] @ECHO OFF
[2] SET LOG_FILE_NAME="log.txt"

[3] REM Start the b.cmd redirecting all output
[4] CALL b.cmd >> %LOG_FILE_NAME% 2>&1
[5] ECHO returned to a.cmd >> %LOG_FILE_NAME% 2>&1
[6] EXIT /B 0

b.cmd:

[1] @ECHO OFF

[2] SET ANOTHER_LOG_FILE_NAME="log2.txt"
[4] ECHO RunForEver.exe redirecting all output
[5] START CMD /C "RunForEver.exe >> %ANOTHER_LOG_FILE_NAME% 2>&1"
[6] ECHO b.cmd execution complete
[7] EXIT /B 0

(为了方便起见,添加了行号)

我遇到的问题是,在b.cmd的第4行似乎抓住了初始日志文件(LOG_FILE_NAME),因为所有b.cmd输出都被重定向到它,而且当可执行文件(以及启动它的cmd)正在运行时,句柄没有释放。 我没想到会出现这种情况,因为我认为只有start命令本身的输出才会被重定向到LOG_FILE_NAME日志文件,实际运行RunForEver.exe可执行文件的其他进程的输出将写入ANOTHER_LOG_FILE_NAME。 结果,a.cmd的第5行错误地拒绝访问LOG_FILE_NAME。

有人能解释一下发生了什么吗?有没有办法避免这种情况?

我尝试在b.cmd内部执行输出重定向到LOG_FILE_NAME,但然后我会在b.cmd的第2行收到访问被拒绝的错误。

提前感谢!


我找到的最佳解决方法是使用PowerShell代替start命令。请参见此处:https://dev59.com/yK_la4cB1Zd3GeqPw7Xm#53321347 - kostasvs
1个回答

8

哇!这是一个令人着迷又令人不安的发现。

我没有解释,但我有一个解决方案。

只需在永无止境的过程开始后避免任何额外的重定向到log.txt即可。这可以通过一次性地重定向括号内的代码块来完成。

@ECHO OFF
SET LOG_FILE_NAME="log.txt"

>>%LOG_FILE_NAME% 2>&1 (
  CALL b.cmd
  ECHO returned to a.cmd
)
EXIT /B 0

或者通过重定向CALL子程序的输出来实现。
@ECHO OFF
SET LOG_FILE_NAME="log.txt"

call :redirected >>%LOG_FILE_NAME% 2>&1
EXIT /B 0

:redirected
CALL b.cmd
ECHO returned to a.cmd
exit /b

如果您需要在.cmd文件中有选择性地重定向输出,则只需将非标准流重定向到您的文件一次,然后在块内部有选择地将输出重定向到非标准流即可。

@ECHO OFF
SET LOG_FILE_NAME="log.txt"

3>>%LOG_FILE_NAME% (
  echo normal output that is not redirected
  CALL b.cmd >&3 2>&1
  ECHO returned to a.cmd >&3 2>&1
)
EXIT /B 0

同样的技巧可以使用CALL而不是带括号的块来完成。


我开发了一个简单、自包含的TEST.BAT脚本,任何人都可以运行它来演示问题。在我的机器上,我将其命名为TEST.BAT。

@echo off
del log*.txt 2>nul
echo begin >>LOG1.TXT 2>&1
call :test >>LOG1.TXT 2>&1
echo end >>LOG1.TXT 2>&1
exit /b

:test
echo before start
>nul 2>&1 (
  echo ignored output
  start "" cmd /c "echo start result >LOG2.TXT 2>&1 & pause >con"
)
echo after start
pause >con
exit /b

主进程和已启动的进程都暂停了,因此我可以选择先结束哪个进程。如果已启动的进程在主进程之前终止,那么一切都会按预期工作,这可以从主控制台窗口的以下输出中看出。

C:\test>test
Press any key to continue . . .

C:\test>type log*

LOG1.TXT


begin
before start
after start
end

LOG2.TXT


start result

C:\test>

这是一个例子,如果我在STARTed进程终止之前允许主进程继续,会发生什么:
C:\test>test
Press any key to continue . . .
The process cannot access the file because it is being used by another process.

C:\test>type log*

LOG1.TXT


begin
before start
after start

LOG2.TXT


start result

C:\test>

我觉得这种行为很令人困扰的原因在于我无法理解START进程和LOG1.TXT有何关系。在START命令执行时,所有标准输出都已被重定向到空设备,因此我不明白新的进程如何知道LOG1.TXT,更不用说如何对其进行独占锁定了。而echo ignored output没有任何可检测的输出则证明标准输出已成功被重定向到空设备中。

非常感谢您提供详细的答案。我使用了第二种方法(使用子程序),现在输出在“START”之后已经显示出来了。不过由于某些原因,第一个日志文件仍然被锁定。 - user1039580
@user1039580 - 是的,我相信第一个日志将会一直被锁定直到已启动的“永不结束”的进程终止。这对我来说没有意义,但它似乎就是这样运作的。 - dbenham

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接