Class For Executing Ubuntu Commands From C# Code

PHOTO EMBED

Wed Jan 11 2023 09:45:11 GMT+0000 (Coordinated Universal Time)

Saved by @HristoT

var bash = new BashShell();
var result = await bash.RunOrThrow($"<command here>");

===================================================================================================

public class BashShell
{
    private readonly string workingDirectory;
    public event Action<string> OnStdOut;
    public event Action<string> OnStdErr;

    public BashShell(string workingDirectory)
    {
        if (string.IsNullOrWhiteSpace(workingDirectory))
        {
            this.workingDirectory = Environment.CurrentDirectory;
        }
        else
        {
            this.workingDirectory = workingDirectory;
        }
    }

    public BashShell() : this(null)
    {
    }

    public async Task<ShellResult> Run(string command)
    {
        var process = new Process
        {
            StartInfo = new ProcessStartInfo("/usr/bin/env")
            {
                Arguments = "bash",
                RedirectStandardError = true,
                RedirectStandardOutput = true,
                RedirectStandardInput = true,
                CreateNoWindow = true,
                UseShellExecute = false,
                WorkingDirectory = this.workingDirectory
            },
        };

        var asyncLock = new AsyncLock();

        var stdOut = new StringBuilder();
        var stdErr = new StringBuilder();

        int exitCode;

        using (process)
        {
            process.Start();

            var outTask = Task.Run(async () =>
            {
                using var reader = process.StandardOutput;

                while (!reader.EndOfStream || !process.HasExited)
                {
                    string line = await reader.ReadLineAsync();

                    if (string.IsNullOrEmpty(line))
                    {
                        continue;
                    }

                    using (await asyncLock.Lock())
                    {
                        this.OnStdOut?.Invoke(line);
                        stdOut.AppendLine(line);
                    }
                }
            });

            var errTask = Task.Run(async () =>
            {
                using var reader = process.StandardError;

                while (!reader.EndOfStream || !process.HasExited)
                {
                    string line = await reader.ReadLineAsync();

                    if (string.IsNullOrEmpty(line))
                    {
                        continue;
                    }

                    using (await asyncLock.Lock())
                    {
                        this.OnStdErr?.Invoke(line);
                        stdErr.AppendLine(line);
                    }
                }
            });

            await process.StandardInput.WriteLineAsync("/usr/bin/env bash");
            await process.StandardInput.WriteLineAsync("set -ex");
            await process.StandardInput.WriteLineAsync(command);
            await process.StandardInput.WriteLineAsync("exit \"$?\"");
            await process.StandardInput.WriteLineAsync("exit \"$?\"");

            await Task.WhenAll(outTask, errTask);

            exitCode = process.ExitCode;
        }

        return new ShellResult
        {
            Command = command,
            ExitCode = exitCode,
            StdErr = stdErr.ToString(),
            StdOut = stdOut.ToString()
        };
    }

    public async Task<ShellResult> RunOrThrow(string command)
    {
        var result = await this.Run(command);
        result.ThrowIfFailed();
        return result;
    }
}

public class ShellResult
{
    public int ExitCode { get; set; }

    public string StdErr { get; set; }

    public string StdOut { get; set; }

    public string Command { get; set; }

    public void ThrowIfFailed()
    {
        if (this.ExitCode != 0)
        {
            throw new ApplicationException("A bash command exited with non zero code.");
        }
    }
}

public class AsyncLock
{
    private readonly SemaphoreSlim semaphore;

    public AsyncLock()
    {
        this.semaphore = new SemaphoreSlim(1, 1);
    }

    public async Task<AsyncLockInstance> Lock()
    {
        await this.semaphore.WaitAsync();

        return new AsyncLockInstance(this.semaphore);
    }
}
content_copyCOPY