package shell import ( "fmt" "os" "path/filepath" "strings" ) func (s *Shell) builtinCd(args []string) error { path := "" if len(args) > 0 { path = args[0] } else { path = os.Getenv("HOME") if path == "" { path = os.Getenv("USERPROFILE") } if path == "" { path = "." } } // Handle ~ if strings.HasPrefix(path, "~") { home := os.Getenv("HOME") if home == "" { home = os.Getenv("USERPROFILE") } if len(path) > 1 { path = home + path[1:] } else { path = home } } // Handle relative paths if !filepath.IsAbs(path) { pwd, _ := os.Getwd() path = filepath.Join(pwd, path) } if err := os.Chdir(path); err != nil { return fmt.Errorf("cd: %s: %v", path, err) } s.vars["PWD"], _ = os.Getwd() return nil } func (s *Shell) builtinPwd(args []string) error { pwd, err := os.Getwd() if err != nil { return err } fmt.Println(pwd) return nil } func (s *Shell) builtinEcho(args []string) error { noNewline := false escape := false var parts []string for _, arg := range args { switch arg { case "-n": noNewline = true case "-e": escape = true case "-en", "-ne": noNewline = true escape = true default: if escape { arg = strings.ReplaceAll(arg, "\\n", "\n") arg = strings.ReplaceAll(arg, "\\t", "\t") arg = strings.ReplaceAll(arg, "\\\\", "\\") } parts = append(parts, arg) } } line := strings.Join(parts, " ") if noNewline { fmt.Print(line) } else { fmt.Println(line) } return nil } func (s *Shell) builtinExit(args []string) error { code := 0 if len(args) > 0 { fmt.Sscanf(args[0], "%d", &code) } os.Exit(code) return nil } func (s *Shell) builtinExport(args []string) error { for _, arg := range args { parts := strings.SplitN(arg, "=", 2) if len(parts) == 2 { s.vars[parts[0]] = parts[1] os.Setenv(parts[0], parts[1]) } else if len(parts) == 1 { // export NAME (mark for export) if val, ok := s.vars[parts[0]]; ok { os.Setenv(parts[0], val) } } } return nil } func (s *Shell) builtinSource(args []string) error { if len(args) == 0 { return fmt.Errorf("source: filename argument required") } data, err := os.ReadFile(args[0]) if err != nil { return fmt.Errorf("source: %v", err) } return s.Execute(string(data)) } var aliases = make(map[string]string) func (s *Shell) builtinAlias(args []string) error { if len(args) == 0 { for name, cmd := range aliases { fmt.Printf("alias %s='%s'\n", name, cmd) } return nil } for _, arg := range args { parts := strings.SplitN(arg, "=", 2) if len(parts) == 2 { name := parts[0] value := strings.Trim(parts[1], "'\"") aliases[name] = value } } return nil } func (s *Shell) builtinType(args []string) error { for _, arg := range args { if _, ok := s.builtins[arg]; ok { fmt.Printf("%s is a shell builtin\n", arg) } else if val, ok := aliases[arg]; ok { fmt.Printf("%s is aliased to `%s`\n", arg, val) } else if path := findExecutable(arg); path != "" { fmt.Printf("%s is %s\n", arg, path) } else { fmt.Printf("%s: not found\n", arg) } } return nil } func (s *Shell) initBuiltins() { s.builtins = map[string]func([]string) error{ "cd": s.builtinCd, "pwd": s.builtinPwd, "echo": s.builtinEcho, "exit": s.builtinExit, "export": s.builtinExport, "source": s.builtinSource, "alias": s.builtinAlias, "type": s.builtinType, } } func (s *Shell) initCommands() { commands := map[string]func([]string) error{ "ls": commandLs, "cat": commandCat, "grep": commandGrep, "sort": commandSort, "wc": commandWc, "head": commandHead, "find": commandFind, "cp": commandCp, "mv": commandMv, "rm": commandRm, "mkdir": commandMkdir, "touch": commandTouch, "clear": commandClear, } for name, fn := range commands { s.builtins[name] = fn } } func commandLs(args []string) error { path := "." showAll := false longFormat := false for _, arg := range args { switch arg { case "-la", "-al": showAll = true longFormat = true case "-a": showAll = true case "-l": longFormat = true default: if !strings.HasPrefix(arg, "-") { path = arg } } } entries, err := os.ReadDir(path) if err != nil { return fmt.Errorf("ls: %v", err) } for _, entry := range entries { name := entry.Name() if !showAll && strings.HasPrefix(name, ".") { continue } if longFormat { info, _ := entry.Info() if info != nil { mode := info.Mode().String() size := info.Size() modTime := info.ModTime().Format("Jan _2 15:04") fmt.Printf("%s %8d %s %s\n", mode, size, modTime, name) } } else { fmt.Println(name) } } return nil } func commandCat(args []string) error { for _, path := range args { data, err := os.ReadFile(path) if err != nil { return fmt.Errorf("cat: %s: %v", path, err) } fmt.Print(string(data)) } return nil } func commandGrep(args []string) error { if len(args) < 1 { return fmt.Errorf("grep: usage: grep [pattern] [file...]") } pattern := args[0] files := args[1:] ignoreCase := false if strings.HasPrefix(pattern, "-i") && len(args) > 1 { ignoreCase = true pattern = strings.TrimPrefix(pattern, "-i") if pattern == "" && len(args) > 1 { pattern = args[1] files = args[2:] } } for _, path := range files { data, err := os.ReadFile(path) if err != nil { return fmt.Errorf("grep: %s: %v", path, err) } lines := strings.Split(string(data), "\n") for _, line := range lines { check := line pat := pattern if ignoreCase { check = strings.ToLower(line) pat = strings.ToLower(pattern) } if strings.Contains(check, pat) { fmt.Println(line) } } } return nil } func commandSort(args []string) error { var lines []string for _, path := range args { data, err := os.ReadFile(path) if err != nil { return fmt.Errorf("sort: %s: %v", path, err) } lines = append(lines, strings.Split(string(data), "\n")...) } sortStrings(lines) for _, line := range lines { fmt.Println(line) } return nil } func sortStrings(s []string) { n := len(s) for i := 0; i < n; i++ { for j := i + 1; j < n; j++ { if s[i] > s[j] { s[i], s[j] = s[j], s[i] } } } } func commandWc(args []string) error { if len(args) == 0 { return fmt.Errorf("wc: stdin not supported yet") } totalL, totalW, totalC := 0, 0, 0 for _, path := range args { data, err := os.ReadFile(path) if err != nil { return fmt.Errorf("wc: %s: %v", path, err) } content := string(data) l, w, c := strings.Count(content, "\n"), len(strings.Fields(content)), len(content) totalL += l; totalW += w; totalC += c fmt.Printf("%8d %8d %8d %s\n", l, w, c, path) } if len(args) > 1 { fmt.Printf("%8d %8d %8d total\n", totalL, totalW, totalC) } return nil } func commandHead(args []string) error { n := 10 files := args if len(args) > 0 && args[0] == "-n" && len(args) > 1 { fmt.Sscanf(args[1], "%d", &n) files = args[2:] } for fi, path := range files { data, err := os.ReadFile(path) if err != nil { return fmt.Errorf("head: %s: %v", path, err) } if len(files) > 1 { fmt.Printf("==> %s <==\n", path) } splitLines := strings.Split(string(data), "\n") end := n if end > len(splitLines) { end = len(splitLines) } for _, line := range splitLines[:end] { fmt.Println(line) } if fi < len(files)-1 { fmt.Println() } } return nil } func commandFind(args []string) error { root := "." name := "" for i, arg := range args { if arg == "-name" && i+1 < len(args) { name = args[i+1] } else if !strings.HasPrefix(arg, "-") && root == "." { root = arg } } err := filepath.Walk(root, func(path string, info os.FileInfo, err error) error { if err != nil { return err } if name != "" { matched, _ := filepath.Match(name, info.Name()) if !matched { return nil } } fmt.Println(path) return nil }) return err } func commandCp(args []string) error { if len(args) < 2 { return fmt.Errorf("cp: missing file operand") } data, err := os.ReadFile(args[0]) if err != nil { return fmt.Errorf("cp: %v", err) } return os.WriteFile(args[1], data, 0644) } func commandMv(args []string) error { if len(args) < 2 { return fmt.Errorf("mv: missing file operand") } return os.Rename(args[0], args[1]) } func commandRm(args []string) error { recursive := false for _, arg := range args { switch arg { case "-rf", "-fr", "-r": recursive = true default: if !strings.HasPrefix(arg, "-") { if recursive { return os.RemoveAll(arg) } return os.Remove(arg) } } } return nil } func commandMkdir(args []string) error { parents := false var dirs []string for _, arg := range args { if arg == "-p" { parents = true } else { dirs = append(dirs, arg) } } for _, dir := range dirs { if parents { if err := os.MkdirAll(dir, 0755); err != nil { return fmt.Errorf("mkdir: %v", err) } } else { if err := os.Mkdir(dir, 0755); err != nil { return fmt.Errorf("mkdir: %v", err) } } } return nil } func commandTouch(args []string) error { for _, path := range args { f, err := os.Create(path) if err != nil { return fmt.Errorf("touch: %v", err) } f.Close() } return nil } func commandClear(args []string) error { fmt.Print("\033[H\033[2J") return nil }