1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
|
const std = @import("std");
var stdin_buffer: [4096]u8 = undefined;
var stdin_reader = std.fs.File.stdin().readerStreaming(&stdin_buffer);
const stdin = &stdin_reader.interface;
var stdout_writer = std.fs.File.stdout().writerStreaming(&.{});
const stdout = &stdout_writer.interface;
const CommandResult = enum {
continue_loop,
exit_shell,
};
fn parseCommand(input: []const u8) struct { name: []const u8, args: ?[]const u8 } {
const space_pos = std.mem.indexOfScalar(u8, input, ' ');
if (space_pos) |pos| {
return .{
.name = input[0..pos],
.args = input[pos + 1 ..],
};
}
return .{
.name = input,
.args = null,
};
}
fn isBuiltin(cmd_name: []const u8) bool {
const builtins = [_][]const u8{ "exit", "echo", "type" };
for (builtins) |builtin| {
if (std.mem.eql(u8, cmd_name, builtin)) {
return true;
}
}
return false;
}
fn findInPath(allocator: std.mem.Allocator, cmd_name: []const u8) !?[]const u8 {
const path_env = std.process.getEnvVarOwned(allocator, "PATH") catch return null;
defer allocator.free(path_env);
var it = std.mem.splitScalar(u8, path_env, ':');
while (it.next()) |dir| {
const full_path = std.fs.path.join(allocator, &[_][]const u8{ dir, cmd_name }) catch continue;
defer allocator.free(full_path);
const file = std.fs.openFileAbsolute(full_path, .{}) catch continue;
const stat = file.stat() catch {
file.close();
continue;
};
file.close();
const mode = stat.mode;
const has_exec = (mode & 0o111) != 0;
if (!has_exec) continue;
return try allocator.dupe(u8, full_path);
}
return null;
}
fn executeCommand(allocator: std.mem.Allocator, cmd_name: []const u8, args: ?[]const u8) !CommandResult {
if (std.mem.eql(u8, cmd_name, "exit")) {
return .exit_shell;
}
if (std.mem.eql(u8, cmd_name, "echo")) {
if (args) |a| {
try stdout.print("{s}\n", .{a});
} else {
try stdout.print("\n", .{});
}
return .continue_loop;
}
if (std.mem.eql(u8, cmd_name, "type")) {
if (args) |a| {
if (isBuiltin(a)) {
try stdout.print("{s} is a shell builtin\n", .{a});
} else if (try findInPath(allocator, a)) |path| {
defer allocator.free(path);
try stdout.print("{s} is {s}\n", .{ a, path });
} else {
try stdout.print("{s}: not found\n", .{a});
}
}
return .continue_loop;
}
try stdout.print("{s}: command not found\n", .{cmd_name});
return .continue_loop;
}
pub fn main() !void {
var gpa = std.heap.GeneralPurposeAllocator(.{}){};
defer _ = gpa.deinit();
const allocator = gpa.allocator();
while (true) {
try stdout.print("$ ", .{});
const command = try stdin.takeDelimiter('\n');
if (command) |cmd| {
const parsed = parseCommand(cmd);
const result = try executeCommand(allocator, parsed.name, parsed.args);
if (result == .exit_shell) {
break;
}
} else {
break;
}
}
}
|