|
|
@@ -0,0 +1,452 @@
|
|
|
+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()
|
|
|
+}
|