SwiftでCatコマンドを作る
catコマンド
catコマンドとは、与えられたファイルパスの中身を標準出力に書き込むコマンドです。今回はそれをSwiftで再現してみたいと思います。
The cat utility reads files sequentially, writing them to the standard output. The file operands are processed in command-line order. If file is a single dash (‘-’) or absent, cat reads from the standard input. If file is a UNIX domain socket, cat connects to it and then reads it until EOF. This complements the UNIX domain binding capability available in inetd(8).
今回は、一つのファイルを読んで出力するところまでを再現します。
Swiftからシステムコールを呼ぶ
Swiftからread
やwrite
などのシステムコールを呼ぶために、今回はapple/swift-system: Low-level system calls and types for SwiftというApple製のSwift Packageを利用することにしました。
1import SystemPackage
としてインポートすることができます。
コマンドを読み込む
Swiftでは以下のようにすることでターミナルから引数を読み込むことができます。
1let argv = CommandLine.arguments
2let argc = CommandLine.arguments.count
実行とテスト
swift run cat [ file ]
今回はcat
というexecutableTarget
を作成しているので上記のようにして実行することができます。またテストする際は以下のようにして行いました。
diff -a <(cat Sources/cat/cat.swift) <(swift run cat Sources/cat/cat.swift)
差分がなければ標準出力には何も表示されずに正常終了します。
Swiftコード全体
1import SystemPackage
2
3@main
4struct Cat {
5 public static func main() throws {
6 let argv = CommandLine.arguments
7 let argc = CommandLine.arguments.count
8
9 let buf = UnsafeMutableRawBufferPointer.allocate(byteCount: 4096, alignment: 0)
10 var fd: FileDescriptor = FileDescriptor.standardInput
11
12 if argc != 2 {
13 fatalError("Usage: \(argv[0]) [ file ]")
14 } else {
15 do {
16 fd = try FileDescriptor.open(argv[1], .readOnly)
17 } catch {
18 fatalError("cannot open \(argv[1]): \(error)")
19 }
20
21 do {
22 let bytes = try fd.read(into: buf)
23 try fd.close()
24 let _ = try FileDescriptor.standardOutput.writeAll(buf[..<bytes])
25 buf.deallocate()
26 } catch {
27 fatalError("Error: \(error)")
28 }
29 }
30 }
31}
参考
- Catの実装は以下の動画を参考にしました。