如何从 ProcessStartInfo.Arguments 之类的单个参数字符串中获取单独的参数?
How to get separate arguments from single arguments string like ProcessStartInfo.Arguments?
假设我有一个 shell 命令行。一行文本,与您在 shell 中键入的内容完全相同。从参数中拆分命令是微不足道的。在世界上的每个 shell 中,它总是在命令后的 space。我知道,该命令理论上可以包含 spaces,但让我们假设它是一段时间的正常命令。
当我将参数作为字符串传递给 ProcessStartInfo.Arguments
字符串时 - 它会起作用。但这不是我想要的。
我希望将参数分开。
每一个。当参数类似于 @"C:\Program Files" - 它应该是一个参数。
手动获取它非常棘手。因为它对不同的 OSes 和 shells 做的不同。但是 - .NET 以某种方式在内部完成它,我需要开始使用它而不是重新发明轮子。
我真正想做的是首先像这样构建一个参数列表:
new[] { "dir", @"'C:\Program Files'" }
然后像这样:
new[] { "cmd", "/C", @"dir 'C:\Program Files'" }
所以它是双向的——我从一个字符串中提取参数,然后我从单独的参数构建一个参数字符串。全部根据 OS-特定规则。我的代码应该适用于 Linux 和 Windows.
问题是,它必须在多平台上工作,所以在 Linux 上可以是“bash -c”而不是“cmd /C”,你明白了。
要点是让 .NET 自己完成所有引用/取消引用,据我所知,它在 Windows 和 Linux 上都能正确完成。
我确实搜索了 Google 和 Stack Overflow - 好像什么都没有。
我试图在设置 Arguments
属性 后从 ProcessStartInfo
获取 ArgumentList
。它不起作用。设置一个 属性 不会设置另一个。您实际上可以同时设置两者并在尝试启动该过程时获取异常。
我敢打赌这个问题绝非微不足道。有什么建议吗?
更新
我在挖掘 .NET 资源和 GitHub 讨论的基础上进行了更多研究。它看起来真的很糟糕(或者真的很好,这取决于一个观点)。好像没有这样的东西。所以我基本上需要一种方法来引用目标 OS 的一个参数,以及一种将命令行解析为未引用部分的方法。对于解析,我将采用标准,使用 FSM(有限状态机算法),引用很简单,只需添加 OS / shell 特定引号即可。
无论如何,如果它在 .NET 中的某个地方只是隐藏和咯咯地笑,请告诉我 ;)
看来它还不存在,所以我为它做了一个脑残的简单破解:
先决条件:
public class SpaceDelimitedStringParser {
public char Quote { get; set; } = '"';
public IEnumerable<string> Split(string input) {
bool isQuoting = false;
for (int i = 0, s = 0, n = input.Length; i < n; i++) {
var c = input[i];
var isWhiteSpace = c is ' ' or '\t';
var isQuote = c == Quote;
var isBreak = i == n - 1 || isWhiteSpace && !isQuoting;
if (isBreak) {
yield return input[s..i];
s = i + 1;
continue;
}
if (isWhiteSpace && !isQuoting) continue;
if (isQuote) {
if (!isQuoting) {
s = i + 1;
isQuoting = true;
}
else {
yield return input[s..i];
s = i + 1;
isQuoting = false;
}
}
}
}
public string Join(IEnumerable<string> items)
=> String.Join(' ', items.Select(i => i.Contains(' ') || i.Contains('\t') ? (Quote + i + Quote) : i));
}
它只是拆分和连接 space 使用引号字符分隔的字符串:双引号。我测试了在 Windows 和 Linux.
上的相似处理
再来个好帮手class:
public class ShellStartInfo {
public ShellStartInfo(string command, bool direct = false) {
var (shell, exec) =
OS.IsLinux ? ("bash", "-c") :
OS.IsWindows ? ("cmd", "/C") :
throw new PlatformNotSupportedException();
var arguments = new[] { exec, command };
StartInfo = new ProcessStartInfo(shell, new SpaceDelimitedStringParser().Join(arguments));
if (!direct) {
StartInfo.RedirectStandardInput = true;
StartInfo.RedirectStandardOutput = true;
StartInfo.RedirectStandardError = true;
StartInfo.StandardOutputEncoding = Encoding.UTF8;
StartInfo.StandardErrorEncoding = Encoding.UTF8;
}
}
public static implicit operator ProcessStartInfo(ShellStartInfo startInfo) => startInfo.StartInfo;
private readonly ProcessStartInfo StartInfo;
}
还有一个小帮手:
public static class OS {
public static bool IsLinux => _IsLinux ?? (_IsLinux = RuntimeInformation.IsOSPlatform(OSPlatform.Linux)).Value;
public static bool IsWindows => _IsWindows ?? (_IsWindows = RuntimeInformation.IsOSPlatform(OSPlatform.Windows)).Value;
private static bool? _IsLinux;
private static bool? _IsWindows;
}
我们可以测试一下:
var command =
OS.IsLinux ? @"cat 'File name with spaces.txt'" :
OS.IsWindows ? @"type ""File name with spaces.txt""" :
throw new PlatformNotSupportedException();
var process = Process.Start(new ShellStartInfo(command));
if (process is null) throw new InvalidOperationException();
await process.WaitForExitAsync();
var output = process.StandardOutput.ReadToEnd();
var error = process.StandardError.ReadToEnd();
if (output.Length > 0) Console.WriteLine($"OUTPUT:\n{output}\nEND.");
if (error.Length > 0) Console.WriteLine($"ERROR:\n{error}\nEND.");
适用于 Windows,适用于 Linux。完成了。这里不需要 split 函数。我认为在某处将参数作为数组传递可能很有用,但是很好。
通过 ArgumentList
属性 或 ProcessStartInfo
class 传递参数有点问题。它只是没有按预期工作,尤其是当我将参数作为一个 shell 执行参数传递时。为什么? IDK,像示例中那样传递时有效。
顺便说一句,我的 ShellStartInfo
class 还设置了内部 ProcessStartInfo
的一些其他属性,这有助于读取命令的输出。
在我的远程 shell 中它正常工作,本地终端的行为与远程终端相同。它不是完全成熟的 Putty
克隆,它只是一个小工具,可以将一小段“ssh”连接到甚至没有外部 IP、打开 SSH 端口甚至可能不接受 TCP 连接的客户端设备。事实上 - 客户端是一个运行 Linux.
的小型物联网设备
假设我有一个 shell 命令行。一行文本,与您在 shell 中键入的内容完全相同。从参数中拆分命令是微不足道的。在世界上的每个 shell 中,它总是在命令后的 space。我知道,该命令理论上可以包含 spaces,但让我们假设它是一段时间的正常命令。
当我将参数作为字符串传递给 ProcessStartInfo.Arguments
字符串时 - 它会起作用。但这不是我想要的。
我希望将参数分开。 每一个。当参数类似于 @"C:\Program Files" - 它应该是一个参数。
手动获取它非常棘手。因为它对不同的 OSes 和 shells 做的不同。但是 - .NET 以某种方式在内部完成它,我需要开始使用它而不是重新发明轮子。
我真正想做的是首先像这样构建一个参数列表:
new[] { "dir", @"'C:\Program Files'" }
然后像这样:
new[] { "cmd", "/C", @"dir 'C:\Program Files'" }
所以它是双向的——我从一个字符串中提取参数,然后我从单独的参数构建一个参数字符串。全部根据 OS-特定规则。我的代码应该适用于 Linux 和 Windows.
问题是,它必须在多平台上工作,所以在 Linux 上可以是“bash -c”而不是“cmd /C”,你明白了。
要点是让 .NET 自己完成所有引用/取消引用,据我所知,它在 Windows 和 Linux 上都能正确完成。
我确实搜索了 Google 和 Stack Overflow - 好像什么都没有。
我试图在设置 Arguments
属性 后从 ProcessStartInfo
获取 ArgumentList
。它不起作用。设置一个 属性 不会设置另一个。您实际上可以同时设置两者并在尝试启动该过程时获取异常。
我敢打赌这个问题绝非微不足道。有什么建议吗?
更新
我在挖掘 .NET 资源和 GitHub 讨论的基础上进行了更多研究。它看起来真的很糟糕(或者真的很好,这取决于一个观点)。好像没有这样的东西。所以我基本上需要一种方法来引用目标 OS 的一个参数,以及一种将命令行解析为未引用部分的方法。对于解析,我将采用标准,使用 FSM(有限状态机算法),引用很简单,只需添加 OS / shell 特定引号即可。
无论如何,如果它在 .NET 中的某个地方只是隐藏和咯咯地笑,请告诉我 ;)
看来它还不存在,所以我为它做了一个脑残的简单破解:
先决条件:
public class SpaceDelimitedStringParser {
public char Quote { get; set; } = '"';
public IEnumerable<string> Split(string input) {
bool isQuoting = false;
for (int i = 0, s = 0, n = input.Length; i < n; i++) {
var c = input[i];
var isWhiteSpace = c is ' ' or '\t';
var isQuote = c == Quote;
var isBreak = i == n - 1 || isWhiteSpace && !isQuoting;
if (isBreak) {
yield return input[s..i];
s = i + 1;
continue;
}
if (isWhiteSpace && !isQuoting) continue;
if (isQuote) {
if (!isQuoting) {
s = i + 1;
isQuoting = true;
}
else {
yield return input[s..i];
s = i + 1;
isQuoting = false;
}
}
}
}
public string Join(IEnumerable<string> items)
=> String.Join(' ', items.Select(i => i.Contains(' ') || i.Contains('\t') ? (Quote + i + Quote) : i));
}
它只是拆分和连接 space 使用引号字符分隔的字符串:双引号。我测试了在 Windows 和 Linux.
上的相似处理再来个好帮手class:
public class ShellStartInfo {
public ShellStartInfo(string command, bool direct = false) {
var (shell, exec) =
OS.IsLinux ? ("bash", "-c") :
OS.IsWindows ? ("cmd", "/C") :
throw new PlatformNotSupportedException();
var arguments = new[] { exec, command };
StartInfo = new ProcessStartInfo(shell, new SpaceDelimitedStringParser().Join(arguments));
if (!direct) {
StartInfo.RedirectStandardInput = true;
StartInfo.RedirectStandardOutput = true;
StartInfo.RedirectStandardError = true;
StartInfo.StandardOutputEncoding = Encoding.UTF8;
StartInfo.StandardErrorEncoding = Encoding.UTF8;
}
}
public static implicit operator ProcessStartInfo(ShellStartInfo startInfo) => startInfo.StartInfo;
private readonly ProcessStartInfo StartInfo;
}
还有一个小帮手:
public static class OS {
public static bool IsLinux => _IsLinux ?? (_IsLinux = RuntimeInformation.IsOSPlatform(OSPlatform.Linux)).Value;
public static bool IsWindows => _IsWindows ?? (_IsWindows = RuntimeInformation.IsOSPlatform(OSPlatform.Windows)).Value;
private static bool? _IsLinux;
private static bool? _IsWindows;
}
我们可以测试一下:
var command =
OS.IsLinux ? @"cat 'File name with spaces.txt'" :
OS.IsWindows ? @"type ""File name with spaces.txt""" :
throw new PlatformNotSupportedException();
var process = Process.Start(new ShellStartInfo(command));
if (process is null) throw new InvalidOperationException();
await process.WaitForExitAsync();
var output = process.StandardOutput.ReadToEnd();
var error = process.StandardError.ReadToEnd();
if (output.Length > 0) Console.WriteLine($"OUTPUT:\n{output}\nEND.");
if (error.Length > 0) Console.WriteLine($"ERROR:\n{error}\nEND.");
适用于 Windows,适用于 Linux。完成了。这里不需要 split 函数。我认为在某处将参数作为数组传递可能很有用,但是很好。
通过 ArgumentList
属性 或 ProcessStartInfo
class 传递参数有点问题。它只是没有按预期工作,尤其是当我将参数作为一个 shell 执行参数传递时。为什么? IDK,像示例中那样传递时有效。
顺便说一句,我的 ShellStartInfo
class 还设置了内部 ProcessStartInfo
的一些其他属性,这有助于读取命令的输出。
在我的远程 shell 中它正常工作,本地终端的行为与远程终端相同。它不是完全成熟的 Putty
克隆,它只是一个小工具,可以将一小段“ssh”连接到甚至没有外部 IP、打开 SSH 端口甚至可能不接受 TCP 连接的客户端设备。事实上 - 客户端是一个运行 Linux.