Created
February 6, 2025 17:42
-
-
Save joshtrujillo/c981f824bc48591fbd177a1514c35ef5 to your computer and use it in GitHub Desktop.
Super simple shell written in zig.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
// zig-shell.zig | |
// Simple shell interface written in zig. | |
// @author Josh Trujillo | |
const std = @import("std"); | |
const MAX_LINE = 80; | |
pub fn main() !u8 { | |
const writer = std.io.getStdOut().writer(); | |
const reader = std.io.getStdIn().reader(); | |
try shellLoop(reader, writer); | |
return 0; | |
} | |
/// Creates the null terminated many item pointer needed by execvpeZ() | |
/// for environmental variables. | |
fn buildEnvArray(allocator: std.mem.Allocator) ![*:null]const ?[*:0]const u8 { | |
var env_list = std.ArrayList(?[*:0]const u8).init(allocator); | |
defer env_list.deinit(); | |
var env_map = try std.process.getEnvMap(allocator); | |
defer env_map.deinit(); | |
var iter = env_map.iterator(); | |
while (iter.next()) |entry| { | |
// Create null terminated key=value pairs | |
const full_entry = try std.fmt.allocPrintZ(allocator, "{s}={s}", .{ entry.key_ptr.*, entry.value_ptr.* }); | |
try env_list.append(full_entry.ptr); | |
} | |
try env_list.append(null); // Null-terminate the array | |
return try env_list.toOwnedSliceSentinel(null); | |
} | |
/// Main shell loop which exits when the user enters "exit" | |
fn shellLoop(reader: std.fs.File.Reader, writer: std.fs.File.Writer) !void { | |
var args: [MAX_LINE / 2][MAX_LINE:0]u8 = undefined; | |
var args_ptrs: [MAX_LINE / 2:null]?[*:0]u8 = undefined; | |
var buffer: [MAX_LINE]u8 = undefined; | |
var last_command: [MAX_LINE]u8 = undefined; | |
var has_history: bool = false; | |
// Allocator for environment variables | |
var arena = std.heap.ArenaAllocator.init(std.heap.page_allocator); | |
// ArenaAllocator frees everything at once | |
defer arena.deinit(); | |
const allocator = arena.allocator(); | |
// Prompt information | |
const user = std.process.getEnvVarOwned(allocator, "USER") catch ""; | |
const pwd = std.process.getEnvVarOwned(allocator, "PWD") catch ""; | |
shell: while (true) { | |
try writer.print("{s}@zig-shell:{s} $ ", .{ user, pwd }); | |
// Read command | |
const command = try reader.readUntilDelimiterOrEof(buffer[0..], '\n') orelse { | |
try writer.print("\n", .{}); | |
return; | |
}; | |
if (command.len == 0) continue; | |
// Handle !! for previous command | |
var command_to_execute = command; | |
if (std.mem.eql(u8, command, "!!")) { | |
if (!has_history) { | |
try writer.print("No command history\n", .{}); | |
continue :shell; | |
} | |
command_to_execute = &last_command; | |
try writer.print("Executing previous command: {s}\n", .{last_command}); | |
} else { // Clear and store last_command | |
@memset(&last_command, 0); | |
std.mem.copyForwards(u8, &last_command, command_to_execute); | |
has_history = true; | |
} | |
// Split command into tokens | |
var tokens = std.mem.splitSequence(u8, command_to_execute, " "); | |
var i: usize = 0; | |
while (tokens.next()) |token| : (i += 1) { | |
// Handle exit case | |
if (std.mem.eql(u8, token, "exit")) { | |
return; | |
} else if (std.mem.eql(u8, token, "!!")) {} | |
std.mem.copyForwards(u8, &args[i], token); | |
args[i][token.len] = 0; | |
args_ptrs[i] = &args[i]; | |
} | |
args_ptrs[i] = null; // Null termination | |
// fork call | |
const pid = try std.posix.fork(); | |
if (pid == 0) { // Child process | |
const envptr = try buildEnvArray(allocator); | |
const result = std.posix.execvpeZ(args_ptrs[0].?, &args_ptrs, envptr); | |
switch (result) { | |
error.FileNotFound => try writer.print("Command not found: {s}\n", .{args_ptrs[0].?}), | |
error.AccessDenied => try writer.print("Permission Denied: {s}\n", .{args_ptrs[0].?}), | |
else => try writer.print("Execution error: {}\n", .{result}), | |
} | |
std.posix.exit(127); | |
} else { | |
// Parent process | |
_ = std.posix.waitpid(pid, 0); | |
} | |
} | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment