package main import ( "bufio" "fmt" "os" "os/exec" "os/signal" "path/filepath" "strconv" "strings" "syscall" "time" "unicode" ) // 内部命令 var internal = []string{"bg", "cd", "clr", "dir", "echo", "env", "exec", "exit", "fg", "help", "jobs", "pwd", "set", "shift", "test", "time", "umask", "unset"} // 用户环境变量 var userenv []string // 是否从批处理文件执行 var script bool // job结构体定义 type job struct { wait bool // 是否有goroutine在等待job结束 state string // job的状态 work string // 执行的命令 cmd *exec.Cmd // 命令相关信息 } var cur job // 前台执行的命令 var term chan bool // 停止信号管道,用于job前后台切换 var jobs map[int]*job // job映射 var sigs chan os.Signal // 信号管道,用于捕获信号 // 提示符函数 func prompt() { if !script { // 如果不是从批处理文件执行,则输出提示符 dir, err := os.Getwd() // 获取工作目录 if err != nil { fmt.Println(err) } fmt.Printf("\033[94m%s\033[0m", dir) // 输出工作目录 state := os.Getenv("?") // 获取上一条命令的退出状态 if state == "0" { // 根据退出状态显示不同颜色 fmt.Print("$ ") } else { fmt.Print("\033[91m$\033[0m ") } } } // 命令分隔函数 func split(s string) []string { res, str := []string{}, "" // 返回结果、当前的字符串 escape, quote := false, false // 是否转义、是否在双引号内 for _, c := range s { // 枚举字符串中每个字符 if escape { // 如果要转义,则根据当前字符决定转换成什么字符 if !quote { str += string(c) } else if c == 'a' { str += "\a" } else if c == 'b' { str += "\b" } else if c == 'f' { str += "\f" } else if c == 'n' { str += "\n" } else if c == 'r' { str += "\r" } else if c == 't' { str += "\t" } else if c == 'v' { str += "\v" } else { str += string(c) } escape = false // 下一个字符不用转义 } else if c == '\\' { // 如果是反斜杠 escape = true // 则下一个字符需要转义 } else if c == '"' { // 如果是双引号 quote = !quote // 则切换是否在双引号内的状态 } else if unicode.IsSpace(c) && !quote { // 如果是空白字符并且不在双引号内 if str != "" { res = append(res, str) // 则要进行分隔 str = "" } } else { str += string(c) // 否则,将字符添加到当前字符串末尾 } } if str != "" { res = append(res, str) } return res } // 命令处理函数 func process(fields []string, stdin *os.File, stdout *os.File, stderr *os.File) { if len(fields) == 0 { return } // 错误处理 defer func() { err := recover() // 捕获命令执行过程中发生的错误 if err != nil { if err != "" { fmt.Fprintln(stderr, err) // 输出错误信息 } os.Setenv("?", "1") // 有错误发生 return } os.Setenv("?", "0") // 没有错误发生 }() // 重定向处理 for i := 0; i < len(fields)-1; { switch fields[i] { case "<": // 标准输入 file, err := os.Open(fields[i+1]) if err != nil { panic(err) } defer file.Close() stdin = file fields = append(fields[:i], fields[i+2:]...) // 从命令中删除 case ">": // 覆盖标准输出 file, err := os.OpenFile(fields[i+1], os.O_WRONLY|os.O_CREATE|os.O_TRUNC, 0666) if err != nil { panic(err) } defer file.Close() stdout = file fields = append(fields[:i], fields[i+2:]...) case ">>": // 追加标准输出 file, err := os.OpenFile(fields[i+1], os.O_WRONLY|os.O_CREATE|os.O_APPEND, 0666) if err != nil { panic(err) } defer file.Close() stdout = file fields = append(fields[:i], fields[i+2:]...) case "2>": // 覆盖标准错误输出 file, err := os.OpenFile(fields[i+1], os.O_WRONLY|os.O_CREATE|os.O_TRUNC, 0666) if err != nil { panic(err) } defer file.Close() stderr = file fields = append(fields[:i], fields[i+2:]...) case "2>>": // 追加标准错误输出 file, err := os.OpenFile(fields[i+1], os.O_WRONLY|os.O_CREATE|os.O_APPEND, 0666) if err != nil { panic(err) } defer file.Close() stderr = file fields = append(fields[:i], fields[i+2:]...) default: i++ } } if len(fields) == 0 { fields = []string{"more"} // 如果没有命令,则调用more命令显示标准输入 } // 后台执行处理 if fields[len(fields)-1] == "&" { in := false // 判断是否属于内部命令 for _, cmd := range internal { if fields[0] == cmd { in = true break } } i := 1 // 获取job的id for ; ; i++ { _, ok := jobs[i] if !ok { break } } jobs[i] = &job{false, "running", strings.Join(fields[:len(fields)-1], " "), nil} // 创建job if in { // 如果是内部命令,则调用shell执行 jobs[i].cmd = exec.Command(os.Getenv("SHELL"), "-") jobs[i].cmd.Stdin = strings.NewReader(jobs[i].work) } else { // 否则,直接调用 jobs[i].cmd = exec.Command(fields[0], fields[1:len(fields)-1]...) jobs[i].cmd.Stdin = stdin } jobs[i].cmd.Stdout = stdout jobs[i].cmd.Stderr = stderr jobs[i].cmd.Env = append(userenv, "PARENT="+os.Getenv("SHELL")) // 添加parent环境变量 err := jobs[i].cmd.Start() // 开始执行 if err != nil { panic(err) } go func() { // 创建goroutine err := jobs[i].cmd.Wait() // 等待job结束 if err != nil { fmt.Println(err) } defer func() { recover() }() term <- true // 向停止信号管道发送信号,表示程序退出 }() jobs[i].wait = true // 有goroutine在等待job结束 fmt.Printf("[%d] %d\n", i, jobs[i].cmd.Process.Pid) return } // 命令解析 switch fields[0] { case "bg": // 后台执行job cmd_bg(fields, stdin, stdout, stderr) case "cd": // 修改工作目录 cmd_cd(fields, stdin, stdout, stderr) case "clr": // 清屏 if len(fields) > 1 { panic("clr: too many arguments") } fmt.Fprint(stdout, "\033[H\033[2J") // 通过转义字符实现清屏 case "dir": // 显示目录内容 cmd_dir(fields, stdin, stdout, stderr) case "echo": // 显示内容并换行 cmd_echo(fields, stdin, stdout, stderr) case "env": // 显示用户环境变量 cmd_env(fields, stdin, stdout, stderr) case "exec": // 执行命令 process(fields[1:], stdin, stdout, stderr) case "exit": // 退出shell cmd_exit(fields, stdin, stdout, stderr) case "fg": // 前台执行job cmd_fg(fields, stdin, stdout, stderr) case "jobs": // 显示job列表 cmd_jobs(fields, stdin, stdout, stderr) case "pwd": // 显示工作目录 dir, err := os.Getwd() // 获取工作目录 if err != nil { panic(err) } fmt.Fprintln(stdout, dir) case "set": // 显示所有环境变量或设置命令行参数 cmd_set(fields, stdin, stdout, stderr) case "shift": // 左移参数 cmd_shift(fields, stdin, stdout, stderr) case "test": // 测试条件 cmd_test(fields, stdin, stdout, stderr) case "time": // 显示时间 t := time.Now() fmt.Fprintln(stdout, t) case "umask": // 显示或设置umask cmd_umask(fields, stdin, stdout, stderr) case "unset": // 重设变量 cmd_unset(fields, stdin, stdout, stderr) default: // 外部命令 if fields[0] == "help" { // 显示用户手册 fields[0] = "more" // 调用more命令过滤 cur = job{false, "running", strings.Join(fields, " "), exec.Command(fields[0], fields[1:]...)} cur.cmd.Stdin = manual() } else { cur = job{false, "running", strings.Join(fields, " "), exec.Command(fields[0], fields[1:]...)} // 前台执行 cur.cmd.Stdin = stdin } cur.cmd.Stdout = stdout cur.cmd.Stderr = stderr cur.cmd.Env = append(userenv, "PARENT="+os.Getenv("SHELL")) // 添加parent环境变量 err := cur.cmd.Start() // 开始执行 if err != nil { panic(err) } term = make(chan bool) // 初始化停止信号管道 go func() { // 创建goroutine err := cur.cmd.Wait() // 等待job结束 if err != nil { fmt.Println(err) } defer func() { recover() }() term <- true // 向停止信号管道发送信号,表示程序退出 }() cur.wait = true // 有goroutine在等待job结束 <-term // 等待接收信号,实现前台执行 close(term) // 关闭停止管道 cur.cmd = nil // 前台没有在执行命令 } } // 管道处理函数 func pipe(fields []string) { if len(fields) == 0 { return } sub := []string{} // 子命令 stdin := os.Stdin // 子命令标准输入 _stdin, stdout, err := os.Pipe() // 在当前子命令标准输出和下一个子命令标准输入之间建立管道 if err != nil { fmt.Println(err) return } stderr := os.Stderr // 子命令标准错误输出 for _, field := range fields { if field == "|" { // 如果是管道分隔符 if len(sub) != 0 { process(sub, stdin, stdout, stderr) // 则处理子命令 stdout.Close() // 关闭当前子命令标准输出,表示处理完成 stdin = _stdin // 设置下一个子命令标准输入 _stdin, stdout, err = os.Pipe() // 继续建立管道 if err != nil { fmt.Println(err) return } sub = []string{} } } else { sub = append(sub, field) } } stdout = os.Stdout // 最后一个子命令标准输出为默认值 if len(sub) != 0 { process(sub, stdin, stdout, stderr) } } // 主函数 func main() { userenv = os.Environ() // 设置用户环境变量 // 初始化shell环境变量 os.Setenv("!", "0") // 最近一次后台进程的pid os.Setenv("#", strconv.Itoa(len(os.Args)-1)) // 命令行参数个数 os.Setenv("$", strconv.Itoa(os.Getpid())) // 当前进程的pid os.Setenv("?", "0") // 上一条命令的退出状态 os.Setenv("*", strings.Join(os.Args[1:], " ")) // 所有命令行参数的值 for i, str := range os.Args { os.Setenv(strconv.Itoa(i), str) // 命令行参数 } path, err := filepath.Abs(os.Args[0]) // 执行shell的绝对路径 if err != nil { fmt.Println(err) os.Exit(1) } os.Setenv("SHELL", path) // 添加shell环境变量 script = len(os.Args) > 1 // 是否指定批处理文件 if script && os.Args[1][0] != '-' { file, err := os.Open(os.Args[1]) // 打开文件 if err != nil { fmt.Println(err) os.Exit(1) } os.Stdin = file // 将标准输入重定向到文件 } sigs = make(chan os.Signal, 1) // 初始化信号管道 // 捕获SIGINT、SIGQUIT、SIGTSTP、SIGCHLD信号 signal.Notify(sigs, syscall.SIGINT, syscall.SIGQUIT, syscall.SIGTSTP, syscall.SIGCHLD) // 信号处理 go func() { for sig := range sigs { switch sig { case syscall.SIGINT: // Ctrl+C触发SIGINT信号 fmt.Println() // 换行 prompt() // 输出提示符 case syscall.SIGTSTP: // Ctrl+Z触发SIGTSTP信号 if cur.cmd != nil { // 如果有前台执行的命令 i := 1 // 获取job的id for ; ; i++ { _, ok := jobs[i] if !ok { break } } jobs[i] = &job{cur.wait, "suspended", cur.work, cur.cmd} // 创建job term <- false // 向停止信号管道发送信号,表示进程暂停 fmt.Printf("[%d] %d suspended\t%s\n", i, jobs[i].cmd.Process.Pid, jobs[i].work) } case syscall.SIGCHLD: // 子进程结束触发SIGCHLD信号 for i, job := range jobs { // 遍历所有job ps := job.cmd.ProcessState if ps != nil && ps.Exited() { // 如果job已经退出 if ps.Success() { // 根据退出状态显示不同信息 fmt.Printf("[%d] %d done\t%s\n", i, job.cmd.Process.Pid, job.work) } else { fmt.Printf("[%d] %d exit %d\t%s\n", i, job.cmd.Process.Pid, ps.ExitCode(), job.work) } delete(jobs, i) // 删除job } } } } }() scanner := bufio.NewScanner(os.Stdin) // 按行读入 jobs = make(map[int]*job) // 初始化job映射 prompt() // 每次读入前输出提示符 for scanner.Scan() { // 读入一整行 cmds := strings.Split(scanner.Text(), ";") // 按分号分隔 for _, cmd := range cmds { // 依次处理 pipe(split(cmd)) // 按空白字符分隔后进行管道处理 } prompt() } fmt.Println() }