`zcmd.zig`: a `std.childProcess.run` replacement but with the ability of running `bash` pipeline

(originally posted here: https://zig.news/liyu1981/zcmdzig-a-stdchildprocessrun-replacement-but-with-the-ability-of-running-bash-pipeline-2k0h)

zcmd.zig is a small lib I have been working on (while try to compile C/CPP legacy libs with zig)

{% embed https://github.com/liyu1981/zcmd.zig %}

what it is?

Simply to say, it is a std.childProcess.run replacement, but with the ability of running bash pipeline.

Below is a good example from one of my other project how this can be used

    const pod_version = brk: {
        cwd.access(".git", .{}) catch {
            const result = try zcmd.run(.{ // mark 1
                .allocator = allocator,
                .commands = &[_][]const []const u8{
                    &.{ "head", "-n", "1", "Changelog" },
                    &.{ "cut", "-d", "(", "-f", "2" },
                    &.{ "cut", "-d", ")", "-f", "1" },
                },
            });
            std.debug.print("\n{any}\n", .{result});
            result.assertSucceededPanic(.{});
            break :brk result.trimedStdout();
        };

        // do not try to download git as originam CMakeList does. Do you guys have git?
        const result = try zcmd.run(.{ // mark 2
            .allocator = allocator,
            .commands = &[_][]const []const u8{&.{ "git", "--git-dir=./.git", "describe", "--abbrev=4", "HEAD" }},
        });
        result.assertSucceededPanic(.{});
        break :brk result.trimedStdout();
    };

The above code is running pipelines like in bash to generate a version string. In mark 1 case, it will run head -n 1 Changelog | cut -d ( -f 2 | cut -d ) -f 1, and in mark 2 case it will run git --git-dir=./git describe -- abbrev=4 HEAD.

The motivation for this lib is that I found std.childProcess.run can only run one command, so assemble a pipeline will be tedious (and not efficient actually), and on the otherside, std.Build.addSystemCommand supports only one command too.

usage

Use it like typical zig pkg

zig fetch --save https://github.com/liyu1981/zcmd.zig/archive/refs/tags/v0.2.1.tar.gz

More frequently you will want to use it in build.zig, which zcmd.zig supports well. After added it to build.zig.zon,

// when import in build.zig, the zcmd is exposed in nested const zcmd
const zcmd = @import("zcmd").zcmd;
// then next can use zcmd.run as above

or browse this example.

The API provided by zcmd.zig is almost identical to std.childProcess.run (besides it will accept commands instead of command). There are also other convenient help functions like those you may already see to assert results.

how it is implemented

zcmd.zig is implemented with the same algorithm used by bash. Essentially, the pipeline is started from first command to last command; each command will be run in the current process; and a child process is forked for executing possible next one; all commands will have unix pipes connected them together.

And because of the algorithm, it is currently only supporting linux/macos, windows is left out because I do not really know whether there are pipes and how they work there. If you want to help me to get this done, feel free to send a pull request, or point me to how to do it. Thanks.

examples and docs

Examples can be found in the repo.

Docs can be found here.