如何在Windows命令提示符中运行命令行应用程序,同时显示输出并将输出重定向到文件?
例如,如果我要运行命令dir > test.txt,这将把输出重定向到一个名为test.txt的文件,而不显示结果。
我如何写一个命令来显示输出并将输出重定向到Windows命令提示符中的文件,类似于Unix上的tee命令?
如何在Windows命令提示符中运行命令行应用程序,同时显示输出并将输出重定向到文件?
例如,如果我要运行命令dir > test.txt,这将把输出重定向到一个名为test.txt的文件,而不显示结果。
我如何写一个命令来显示输出并将输出重定向到Windows命令提示符中的文件,类似于Unix上的tee命令?
当前回答
这不是另一个答案,更多的是对已经存在的答案的概述和澄清 显示Windows命令提示符输出并将其重定向到文件 和其他人
我自己发现,有一系列问题使得一组tee实现在Windows中不可靠(在我的情况下是Windows 7)。
我需要特别使用tee实现,因为已经使用了自重定向的批处理脚本:
@echo off
setlocal
... some conditions here ..
rem the redirection
"%COMSPEC%" /C call %0 %* 2>&1 | "<path_to_tee_utililty>" ".log\<log_file_name_with_date_and_time>.%~nx0.log"
exit /b
:IMPL
... here the rest of script ...
如果与tee实用程序一起使用,脚本和对脚本内一些实用程序的调用可能会中断输出。
gnuwin32的实现:
http://gnuwin32.sourceforge.net/packages/coreutils.htm
优点:
正确地处理标准输出和控制台进度条,其中\r字符被大量使用。
缺点:
使控制台进度条仅在日志文件中绘制,但在控制台窗口中没有复制或可见。 抛出多个错误消息Cwrite错误:没有这样的文件或目录,因为cmd解释器似乎过早地关闭了管道/stdout,并且在那之后不能自我关闭(垃圾邮件直到终止)。 不复制/打印控制台窗口中pause命令(按任意键继续…)的输出。
wintee实现:
https://code.google.com/archive/p/wintee/
https://github.com/rbuhl/wintee
优点:
在控制台窗口和日志文件(多次打印)中显示控制台进度条。 复制/打印控制台窗口中pause命令(按任意键继续…)的输出。
缺点:
错误地处理\r字符,输出是混合和混乱的(https://code.google.com/archive/p/wintee/issues/7)。 若有其他问题:https://code.google.com/archive/p/wintee/issues
UnxUtils实现:
http://unxutils.sourceforge.net/
https://sourceforge.net/projects/unxutils/files/unxutils/current/
Pros
在控制台窗口和日志文件(多次打印)中显示控制台进度条。 正确处理\r字符。 复制/打印控制台窗口中pause命令(按任意键继续…)的输出。
Cons
尚未发现
ss64.net实现:
http://ss64.net/westlake/nt
http://ss64.net/westlake/nt/tee.zip
优点:
在控制台窗口和日志文件(多次打印)中显示控制台进度条。
缺点:
错误地处理\r字符,输出混合和混乱 由于某种原因,在按下一个键后复制/打印控制台窗口中的pause命令(按任意键继续…)的输出。
ritchielawrence mtee实现:
https://ritchielawrence.github.io/mtee
https://github.com/ritchielawrence/mtee
Pros
在控制台窗口和日志文件(多次打印)中显示控制台进度条。 正确处理\r字符。 复制/打印控制台窗口中pause命令(按任意键继续…)的输出。 错误码保留特性w/o需要使用doskey的解决方案(/E标志,Windows命令解释器:如何获得第一个管道命令的退出码)
Cons
日志文件(https://github.com/ritchielawrence/mtee/issues/6)的路径不支持正斜杠字符 存在竞态条件问题,无法提取管道进程退出代码,因为它在访问之前已经关闭(https://github.com/ritchielawrence/mtee/issues/4)
因此,如果您在上述两种工具中选择tee实用程序实现,那么更好的选择是UnxUtils或mtee。
如果你正在寻找一个更好的实现,有更多的功能和更少的问题,那么你可以使用callf实用程序: https://github.com/andry81/contools/blob/trunk/Utilities/src/callf/help.tpl
你可以运行而不是:
call test.bat | mtee /E 1.log
这样的:
callf.exe /ret-child-exit /tee-stdout 1.log /tee-stdout-dup 1 "" "cmd.exe /c call test.bat"
它更好,因为它可以将stdout与stderr分开管道,甚至可以使用命名管道在具有管理员特权隔离的进程之间进行管道。
其他回答
如果你在CLI中,为什么不用FOR循环来“DO”你想做的事情:
for /F "delims=" %a in ('dir') do @echo %a && echo %a >> output.txt
Great resource on Windows CMD for loops: https://ss64.com/nt/for_cmd.html The key here is setting the delimeters (delims), that would break up each line of output, to nothing. This way it won't break on the default of white-space. The %a is an arbitrary letter, but it is used in the "do" section to, well... do something with the characters that were parsed at each line. In this case we can use the ampersands (&&) to execute the 2nd echo command to create-or-append (>>) to a file of our choosing. Safer to keep this order of DO commands in case there's an issue writing the file, we'll at least get the echo to the console first. The at sign (@) in front of the first echo suppresses the console from showing the echo-command itself, and instead just displays the result of the command which is to display the characters in %a. Otherwise you'd see:
echo驱动器[x]中的卷为Windows
UPDATE: /F跳过空行,唯一的修复是预过滤输出,向每一行添加一个字符(可能通过命令find使用行号)。在CLI中解决这个问题既不快速也不漂亮。此外,我没有包括STDERR,所以这里捕获错误:
for /F "delims=" %a in ('dir 2^>^&1') do @echo %a & echo %a >> output.txt
重定向错误消息
插入符号(^)用于转义后面的符号,因为命令是一个正在被解释的字符串,而不是直接在命令行中输入它。
另一种选择是在程序中tee stdout到stderr:
在java中:
System.setOut(new PrintStream(new TeeOutputStream(System.out, System.err)));
然后,在你的dos批处理文件:java程序> log.txt
标准输出将转到日志文件,标准derr(相同的数据)将显示在控制台上。
发送输出到控制台,附加到控制台日志,从当前命令删除输出
dir >> usb-create.1 && type usb-create.1 >> usb-create.log | type usb-create.1 && del usb-create.1
一个简单的c#控制台应用程序就可以做到这一点:
using System;
using System.Collections.Generic;
using System.IO;
namespace CopyToFiles
{
class Program
{
static void Main(string[] args)
{
var buffer = new char[100];
var outputs = new List<TextWriter>();
foreach (var file in args)
outputs.Add(new StreamWriter(file));
outputs.Add(Console.Out);
int bytesRead;
do
{
bytesRead = Console.In.ReadBlock(buffer, 0, buffer.Length);
outputs.ForEach(o => o.Write(buffer, 0, bytesRead));
} while (bytesRead == buffer.Length);
outputs.ForEach(o => o.Close());
}
}
}
要使用该命令,只需将source命令导入程序,并提供要将输出复制到的任何文件的路径。例如:
dir | CopyToFiles files1.txt files2.txt
将显示dir的结果,并将结果存储在files1.txt和files2.txt中。
请注意,上面的错误处理方式并没有太多(任何东西!),实际上可能并不需要支持多个文件。
下面是我使用的一个基于其他答案的例子
@echo off
REM SOME CODE
set __ERROR_LOG=c:\errors.txt
REM set __IPADDRESS=x.x.x.x
REM Test a variable
if not defined __IPADDRESS (
REM Call function with some data and terminate
call :TEE %DATE%,%TIME%,IP ADDRESS IS NOT DEFINED
goto :EOF
)
REM If test happens to be successful, TEE out a message and end script.
call :TEE Script Ended Successful
goto :EOF
REM THE TEE FUNCTION
:TEE
for /f "tokens=*" %%Z in ("%*") do (
> CON ECHO.%%Z
>> "%__ERROR_LOG%" ECHO.%%Z
goto :EOF
)