繁体   English   中英

如何使用Visual Studio“命令行参数”选项将C#项目的stdout重定向到文件

[英]How to redirect stdout of a C# project to file using the Visual Studio “command line arguments” option

我试图将C#程序的输出重定向到文件。 使用“cmd.exe”时,我只需使用myprogram.exe arg1 arg2 > out.txt运行它,但我想使用Visual Studio 启动选项完成相同的操作

我创建了一个C#Empty Project并添加了以下代码:

using System;
class Test
{
    public static void Main(string[] args)
    {
        foreach (var arg in args) Console.WriteLine(arg);
    }
}

然后我在项目设置中编辑了命令行参数 项目属性

使用Ctrl + F5运行项目不能按预期工作。 我得到控制台中打印的命令行参数,而不是输出文件:

arg1
arg2
>
output.txt

如果我将命令行参数更改为: arg1 arg2 "> output.txt"我得到以下输出:

arg1
arg2
^> output.txt

我注意到在Output文件夹中创建了一个空的output.txt文件。

这件事有可能完成,还是我被迫继续使用cmd.exe来启动我的程序?

严格来说,您被迫使用命令提示符启动具有重定向输出的程序。 否则,您需要自己解析命令行,GUI shell可能不会这样做。

如果您只想在Start Debugging时重定向输出,请取消选中Enable the Visual Studio hosting process的复选框,您就完成了。

如果你没有,那么你在那里看到的"output.txt"实际上并不是由应用程序生成的,而是由Visual Studio IDE在开始调试之前生成的"YourApplication.vshost.exe" 内容总是空的,不能写; 因为它被托管流程锁定了。

fo1e8.png

但是,如果您希望应用程序的行为与您启动它的模式相同,则事情会更复杂。

当您开始使用该应用程序进行调试时,它始于:

“YourApplication.exe”arg1 arg2

因为输出已被IDE重定向。

当你Start Without Debugging ,它开始于:

“%comspec%”/ c“”YourApplication.exe“arg1 arg2 ^> output.txt&pause”

这是让应用程序获取您指定的所有参数的正确方法。

你可能想看看我以前的答案如何检测“按任意键继续...”。 会显示吗?

在这里,我在下面的代码中使用像atavistic throwback这样的方法:

  • 申请代码

     using System.Diagnostics; using System.Linq; using System; class Test { public static void Main(string[] args) { foreach(var arg in args) Console.WriteLine(arg); } static Test() { var current=Process.GetCurrentProcess(); var parent=current.GetParentProcess(); var grand=parent.GetParentProcess(); if(null==grand ||grand.MainModule.FileName!=current.MainModule.FileName) using(var child=Process.Start( new ProcessStartInfo { FileName=Environment.GetEnvironmentVariable("comspec"), Arguments="/c\\x20"+Environment.CommandLine, RedirectStandardOutput=true, UseShellExecute=false })) { Console.Write(child.StandardOutput.ReadToEnd()); child.WaitForExit(); Environment.Exit(child.ExitCode); } #if false // change to true if child process debugging is needed else { if(!Debugger.IsAttached) Debugger.Launch(); Main(Environment.GetCommandLineArgs().Skip(1).ToArray()); current.Kill(); // or Environment.Exit(0); } #endif } } 

我们还需要以下代码,以便它可以工作:

  • 扩展代码方法

     using System.Management; // add reference is required using System.Runtime.InteropServices; using System.Diagnostics; using System.Collections.Generic; using System.Linq; using System; public static partial class NativeMethods { [DllImport("kernel32.dll")] public static extern bool TerminateThread( IntPtr hThread, uint dwExitCode); [DllImport("kernel32.dll")] public static extern IntPtr OpenThread( uint dwDesiredAccess, bool bInheritHandle, uint dwThreadId); } public static partial class ProcessThreadExtensions /* public methods */ { public static void Abort(this ProcessThread t) { NativeMethods.TerminateThread( NativeMethods.OpenThread(1, false, (uint)t.Id), 1); } public static IEnumerable<Process> GetChildProcesses(this Process p) { return p.GetProcesses(1); } public static Process GetParentProcess(this Process p) { return p.GetProcesses(-1).SingleOrDefault(); } } partial class ProcessThreadExtensions /* non-public methods */ { static IEnumerable<Process> GetProcesses( this Process p, int direction) { return from format in new[] { "select {0} from Win32_Process where {1}" } let selectName=direction<0?"ParentProcessId":"ProcessId" let filterName=direction<0?"ProcessId":"ParentProcessId" let filter=String.Format("{0} = {1}", p.Id, filterName) let query=String.Format(format, selectName, filter) let searcher=new ManagementObjectSearcher("root\\\\CIMV2", query) from ManagementObject x in searcher.Get() let process= ProcessThreadExtensions.GetProcessById(x[selectName]) where null!=process select process; } // not a good practice to use generics like this; // but for the convenience .. static Process GetProcessById<T>(T processId) { try { var id=(int)Convert.ChangeType(processId, typeof(int)); return Process.GetProcessById(id); } catch(ArgumentException) { return default(Process); } } } 

因为在我们调试时,父级将是Visual Studio IDE(当前名为"devenv" )。 父母和祖父母的过程实际上是各种各样的,我们需要一个规则来执行一些检查。

棘手的部分是孙子是真正进入Main 每次运行时,代码都会检查祖父进程。 如果祖父母是null然后它产生,但是生成的进程将是%comspec% ,它也是新进程的父进程,它将以当前的相同可执行文件开始。 因此,如果祖父母与自己相同,那么它将不会继续产卵,只是跑进Main

静态构造函数用于代码中,该代码在Main之前启动。 SO上有一个已回答的问题: 静态构造函数如何工作?

当我们开始调试时,我们正在调试祖父进程(生成)。 为了使用孙子进程进行Debugger.Launch ,我使用条件编译Debugger.Launch ,该条件编译将调用Main ,以保持Main清除。

有关调试器的已回答问题也很有用: 将C#中的调试器附加到另一个进程

我不确定这是否可以在Visual Studio中完成。 我的解决方案是为控制台设置一个新输出。 这个例子来自MSDN:

Console.WriteLine("Hello World");
FileStream fs = new FileStream("Test.txt", FileMode.Create);
// First, save the standard output.
TextWriter tmp = Console.Out;
StreamWriter sw = new StreamWriter(fs);
Console.SetOut(sw);
Console.WriteLine("Hello file");
Console.SetOut(tmp);
Console.WriteLine("Hello World");
sw.Close();

http://msdn.microsoft.com/en-us/library/system.console.setout.aspx

在“开始选项”部分中,以这种方式更改命令行参数文本框

args1 args2 1>output.txt

这会重定向标准输出(1),从而创建名为output.txt的文件
如果要追加到以前版本的文件写入

args1 args2 1>>output.txt

现在,您可以在重定向输出控制台的同时逐步调试程序

我建议一个更好的方法,而不需要编写任何代码!

启动设置

只需配置visual studio即可将程序作为外部程序启动。

您可以在外部编辑器中打开.csproj.user并更改StartArguments

<StartArguments>arg1 arg2 &gt; output.txt</StartArguments>

<StartArguments>arg1 arg2 > output.txt</StartArguments>

如果要将输出传输到文件,则必须继续使用cmd.exe。 命令行参数:用于命令行参数,因此您尝试的任何内容都将被转义为命令行参数。 管道和重定向器不是命令行参数,因此它们将被转义。

我只想创建一个.bat文件来调用我喜欢的程序。 您可以将其固定到任务栏并简单地运行它。

简单的方法是: 右键单击Project => Properties ==> Debug

在此输入图像描述

然后确保在调试模式下运行程序。

暂无
暂无

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM