Zigでコマンドを探し子プロセスを叩く
はじめに
この記事ではZigを使ってChildProccessを叩いてみた非常に簡単な実験を書いています。
具体的には、/sbin/apk list --installed
を実行する(なぜこれなのかは特に理由はありません…笑)簡単なサンプルを記載します。
私自信Zigは触れたてであり、正直標準ライブラリがどうなってるかなど言語仕様をあまり理解してません。中々情報を探しにくかったので現バージョンでの備忘録として参考になればと思います。
正直大したことはしていません。
Zigは触れてかなり虜になっています。ものすごく今後が楽しみな言語だと思います。
本題
インポート
インポート文です。zigでは標準でstd
だけでなく、builtin
というライブラリがあります(これ知らなくて探すのに苦労してしまった…後述するDiscordとstdのコード定義掘っていて存在に気付きました)。インポートするとGoで言うGOOS
を取るみたいなことが出来るようになるっぽいです。
const std = @import("std");
const builtin = @import("builtin");
パスからコマンドを探す
環境変数PATHから指定したコマンド名のパスがあるかを探し、あればそのパスを返す関数です。
zigを触った方なら感じると思いますが、zigは伝統的なイテレータパターンの実装が多く見受けられます。なのでwhile
は何気に多く使うと思われます。
ちなみにZigの特殊だと思った点の1つは、for
やwhile
をそのままreturn
に持っていけるところ。こういうのがZigっぽいやつなのかなって感じがします。
pub fn lookPath(allocator: std.mem.Allocator, cmd: []const u8) ?[]const u8 {
const pathEnv = std.posix.getenv("PATH") orelse "";
if (pathEnv.len == 0) return null;
const delimiter = if (builtin.os.tag == .windows) ";" else ":";
var paths = std.mem.split(u8, pathEnv, delimiter);
return while (paths.next()) |path| {
const joins = [2][]const u8{ path, cmd };
const cmdAbs = std.fs.path.join(allocator, &joins) catch break null;
const stat: ?std.fs.File.Stat = std.fs.cwd().statFile(cmdAbs) catch null;
if (stat) |_| {
break cmdAbs;
}
} else null;
}
子プロセス実行関数
argv
に与えられたコマンドを子プロセスで実行する関数です。
余談ですが、このstd.process.Child
は名前空間的にバージョン0.13.0
での大きな変更の1つだったようで、現在はここにあります。それ以前とは異なるみたいなので注意してください。
pub fn execCommand(allocator: std.mem.Allocator, argv: []const []const u8) !std.process.Child.Term {
var cp = std.process.Child.init(argv, allocator);
return try cp.spawnAndWait();
}
メイン関数
メイン関数です。
期待結果として/sbin/apk list --installed
を叩いて出力したかった(Alpineなので)のですが、出来ました。
ところでメモリ管理ですが、以下のコードにあるようにZigでは「アロケータベースでの管理」が基本となります。その内でもGeneralPurposeAllocator
とArenaAllocator
の組み合わせ、Goのようなdefer
構文との相性が抜群に良いです。ここが私が一番感動したところかもしれません(笑)。リーク検出と確実な解放が一発で出来るようになっています。
C/C++のような言語では「ヒープをどこで積み、そのポインタをどう補足するか」を意識してロジックを考える必要性に苛まれることが多いと思うのですが、それが激減し、「どこで解放するか」に集中できるようになっています。これが「組み込み標準」で出来るのはとても体験が良いです!
pub fn main() !void {
var gpa = std.heap.GeneralPurposeAllocator(.{}){};
defer std.debug.assert(gpa.deinit() == .ok);
var arena = std.heap.ArenaAllocator.init(gpa.allocator());
defer arena.deinit();
const allocator = arena.allocator();
const cmd = "apk";
var argv = [3][]const u8{ cmd, "list", "--installed" };
if (lookPath(allocator, cmd)) |cmdAbs| {
argv[0] = cmdAbs;
const stdout = std.io.getStdOut().writer();
try stdout.print("{s} Path Found: {s}\n", .{ cmd, cmdAbs });
try stdout.writeAll("\n");
_ = try execCommand(allocator, &argv);
} else {
const stderr = std.io.getStdErr().writer();
try stderr.print("{s} not found\n", .{cmd});
}
}
補足: zvmを利用したDocker構成
本筋とは関係ありませんが、Alpineとzvmというバージョン管理ツールのDockerでzigを試していますので、その構成を記載しておきます。
そもそもzigはDockerみたいなのを使わなくても簡単にインストールできますが、Dockerでやってみたい方は1つ参考にしてみてください。
compose.yml
です。
services:
app:
container_name: app
build:
context: .
dockerfile: Dockerfile
volumes:
- .:/app
Dockerfile
です。
FROM alpine:3.20
RUN apk add --no-cache curl bash wget build-base musl-dev
RUN curl https://raw.githubusercontent.com/tristanisham/zvm/master/install.sh | bash
RUN echo "# ZVM" >> $HOME/.profile \
&& echo 'export ZVM_INSTALL="$HOME/.zvm/self"' >> $HOME/.profile \
&& echo 'export PATH="$PATH:$HOME/.zvm/bin"' >> $HOME/.profile \
&& echo 'export PATH="$PATH:$ZVM_INSTALL/"' >> $HOME/.profile \
&& source ~/.profile
ARG ZVM_CMD=/root/.zvm/self/zvm
ARG ZIG_VERSION=0.13.0
RUN ${ZVM_CMD} install ${ZIG_VERSION} --zls && ${ZVM_CMD} use ${ZIG_VERSION}
WORKDIR /app
CMD [ "/bin/ash" ]
私はVSCodeのdevContainer機能を利用しており、devContainer.json
の構成は以下のとおりです。
{
"name": "Existing Docker Compose (Extend)",
"dockerComposeFile": ["../compose.yml", "docker-compose.yml"],
"service": "app",
"workspaceFolder": "/app",
"customizations": {
"vscode": {
"extensions": [
"ziglang.vscode-zig",
"ms-vscode.cpptools-extension-pack",
"ms-azuretools.vscode-docker"
]
}
}
}
おわりに
正直標準ライブラリのドキュメントもあるとはいえ、色々未整備なので情報を探すのに苦労しましたが、とりあえずここまで。
「高級言語なら一瞬でできること」なので記述量は多くなりますが、Cと比べるとこんなにやりたいことがスラスラできるなんて!って思っちゃいました。感動してます。「手堅いのに堅すぎないバランス」がとても良いですね。
まだまだ公式のパッケージマネージャが作り途中、という印象は受けましたが、ちゃんと作っていて一見は感じも悪くないことにかなり安心しました。エコシステム・ビルドシステムが充実してくれば一気に面白くなると思いますし、成熟するまで、まだかなりの時間がかかる印象を受けましたが、本当に楽しみ。
ちなみに手探りも手探りで書いてましたが、正直一番ヒントそうなインスピレーション(直接ではない)が得られたのは公式に案内されているDiscordコミュニティでした。
今後も色々試していければと思います。
Discussion