using System.Diagnostics;
using System.Runtime.CompilerServices;
internal static class Shellify
{
[Flags]
private enum ShellSplitState
{
None = 0,
InSingleQuote = 1 << 0,
InDoubleQuote = 1 << 1,
Escape = 1 << 2
}
///
/// Splits a shell-escaped string into arguments, handling quotes and escapes.
///
public static List ShellSplit(string input)
{
var args = new List();
if (string.IsNullOrEmpty(input)) return args;
var current = new System.Text.StringBuilder();
var state = ShellSplitState.None;
foreach (char c in input)
{
if (state.HasFlag(ShellSplitState.Escape))
{
current.Append(c);
state &= ~ShellSplitState.Escape;
}
else if (c == '\\')
{
state |= ShellSplitState.Escape;
}
else if (c == '\'' && !state.HasFlag(ShellSplitState.InDoubleQuote))
{
state ^= ShellSplitState.InSingleQuote;
}
else if (c == '"' && !state.HasFlag(ShellSplitState.InSingleQuote))
{
state ^= ShellSplitState.InDoubleQuote;
}
else if (char.IsWhiteSpace(c) && !state.HasFlag(ShellSplitState.InSingleQuote) && !state.HasFlag(ShellSplitState.InDoubleQuote))
{
if (current.Length > 0)
{
args.Add(current.ToString());
current.Clear();
}
}
else
{
current.Append(c);
}
}
if (current.Length > 0)
args.Add(current.ToString());
return args;
}
public static TaskAwaiter GetAwaiter(this string command)
{
return RunProcessAsync(command).GetAwaiter();
}
private static async Task RunProcessAsync(string command)
{
var strings = ShellSplit(command);
var psi = new ProcessStartInfo
{
FileName = strings.First(),
ArgumentList =
{
strings.Skip(1)
},
RedirectStandardOutput = true,
RedirectStandardError = true,
UseShellExecute = false,
CreateNoWindow = true,
};
using var process = Process.Start(psi);
string output = await process.StandardOutput.ReadToEndAsync();
string error = await process.StandardError.ReadToEndAsync();
await process.WaitForExitAsync();
if (process.ExitCode != 0)
throw new System.Exception($"Process exited with code {process.ExitCode}: {error}");
return output;
}
}