| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452 |
- 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()
- }
|