myshell.go 12 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452
  1. package main
  2. import (
  3. "bufio"
  4. "fmt"
  5. "os"
  6. "os/exec"
  7. "os/signal"
  8. "path/filepath"
  9. "strconv"
  10. "strings"
  11. "syscall"
  12. "time"
  13. "unicode"
  14. )
  15. // 内部命令
  16. var internal = []string{"bg", "cd", "clr", "dir", "echo", "env", "exec", "exit", "fg", "help", "jobs", "pwd", "set", "shift", "test", "time", "umask", "unset"}
  17. // 用户环境变量
  18. var userenv []string
  19. // 是否从批处理文件执行
  20. var script bool
  21. // job结构体定义
  22. type job struct {
  23. wait bool // 是否有goroutine在等待job结束
  24. state string // job的状态
  25. work string // 执行的命令
  26. cmd *exec.Cmd // 命令相关信息
  27. }
  28. var cur job // 前台执行的命令
  29. var term chan bool // 停止信号管道,用于job前后台切换
  30. var jobs map[int]*job // job映射
  31. var sigs chan os.Signal // 信号管道,用于捕获信号
  32. // 提示符函数
  33. func prompt() {
  34. if !script { // 如果不是从批处理文件执行,则输出提示符
  35. dir, err := os.Getwd() // 获取工作目录
  36. if err != nil {
  37. fmt.Println(err)
  38. }
  39. fmt.Printf("\033[94m%s\033[0m", dir) // 输出工作目录
  40. state := os.Getenv("?") // 获取上一条命令的退出状态
  41. if state == "0" { // 根据退出状态显示不同颜色
  42. fmt.Print("$ ")
  43. } else {
  44. fmt.Print("\033[91m$\033[0m ")
  45. }
  46. }
  47. }
  48. // 命令分隔函数
  49. func split(s string) []string {
  50. res, str := []string{}, "" // 返回结果、当前的字符串
  51. escape, quote := false, false // 是否转义、是否在双引号内
  52. for _, c := range s { // 枚举字符串中每个字符
  53. if escape { // 如果要转义,则根据当前字符决定转换成什么字符
  54. if !quote {
  55. str += string(c)
  56. } else if c == 'a' {
  57. str += "\a"
  58. } else if c == 'b' {
  59. str += "\b"
  60. } else if c == 'f' {
  61. str += "\f"
  62. } else if c == 'n' {
  63. str += "\n"
  64. } else if c == 'r' {
  65. str += "\r"
  66. } else if c == 't' {
  67. str += "\t"
  68. } else if c == 'v' {
  69. str += "\v"
  70. } else {
  71. str += string(c)
  72. }
  73. escape = false // 下一个字符不用转义
  74. } else if c == '\\' { // 如果是反斜杠
  75. escape = true // 则下一个字符需要转义
  76. } else if c == '"' { // 如果是双引号
  77. quote = !quote // 则切换是否在双引号内的状态
  78. } else if unicode.IsSpace(c) && !quote { // 如果是空白字符并且不在双引号内
  79. if str != "" {
  80. res = append(res, str) // 则要进行分隔
  81. str = ""
  82. }
  83. } else {
  84. str += string(c) // 否则,将字符添加到当前字符串末尾
  85. }
  86. }
  87. if str != "" {
  88. res = append(res, str)
  89. }
  90. return res
  91. }
  92. // 命令处理函数
  93. func process(fields []string, stdin *os.File, stdout *os.File, stderr *os.File) {
  94. if len(fields) == 0 {
  95. return
  96. }
  97. // 错误处理
  98. defer func() {
  99. err := recover() // 捕获命令执行过程中发生的错误
  100. if err != nil {
  101. if err != "" {
  102. fmt.Fprintln(stderr, err) // 输出错误信息
  103. }
  104. os.Setenv("?", "1") // 有错误发生
  105. return
  106. }
  107. os.Setenv("?", "0") // 没有错误发生
  108. }()
  109. // 重定向处理
  110. for i := 0; i < len(fields)-1; {
  111. switch fields[i] {
  112. case "<": // 标准输入
  113. file, err := os.Open(fields[i+1])
  114. if err != nil {
  115. panic(err)
  116. }
  117. defer file.Close()
  118. stdin = file
  119. fields = append(fields[:i], fields[i+2:]...) // 从命令中删除
  120. case ">": // 覆盖标准输出
  121. file, err := os.OpenFile(fields[i+1], os.O_WRONLY|os.O_CREATE|os.O_TRUNC, 0666)
  122. if err != nil {
  123. panic(err)
  124. }
  125. defer file.Close()
  126. stdout = file
  127. fields = append(fields[:i], fields[i+2:]...)
  128. case ">>": // 追加标准输出
  129. file, err := os.OpenFile(fields[i+1], os.O_WRONLY|os.O_CREATE|os.O_APPEND, 0666)
  130. if err != nil {
  131. panic(err)
  132. }
  133. defer file.Close()
  134. stdout = file
  135. fields = append(fields[:i], fields[i+2:]...)
  136. case "2>": // 覆盖标准错误输出
  137. file, err := os.OpenFile(fields[i+1], os.O_WRONLY|os.O_CREATE|os.O_TRUNC, 0666)
  138. if err != nil {
  139. panic(err)
  140. }
  141. defer file.Close()
  142. stderr = file
  143. fields = append(fields[:i], fields[i+2:]...)
  144. case "2>>": // 追加标准错误输出
  145. file, err := os.OpenFile(fields[i+1], os.O_WRONLY|os.O_CREATE|os.O_APPEND, 0666)
  146. if err != nil {
  147. panic(err)
  148. }
  149. defer file.Close()
  150. stderr = file
  151. fields = append(fields[:i], fields[i+2:]...)
  152. default:
  153. i++
  154. }
  155. }
  156. if len(fields) == 0 {
  157. fields = []string{"more"} // 如果没有命令,则调用more命令显示标准输入
  158. }
  159. // 后台执行处理
  160. if fields[len(fields)-1] == "&" {
  161. in := false // 判断是否属于内部命令
  162. for _, cmd := range internal {
  163. if fields[0] == cmd {
  164. in = true
  165. break
  166. }
  167. }
  168. i := 1 // 获取job的id
  169. for ; ; i++ {
  170. _, ok := jobs[i]
  171. if !ok {
  172. break
  173. }
  174. }
  175. jobs[i] = &job{false, "running", strings.Join(fields[:len(fields)-1], " "), nil} // 创建job
  176. if in { // 如果是内部命令,则调用shell执行
  177. jobs[i].cmd = exec.Command(os.Getenv("SHELL"), "-")
  178. jobs[i].cmd.Stdin = strings.NewReader(jobs[i].work)
  179. } else { // 否则,直接调用
  180. jobs[i].cmd = exec.Command(fields[0], fields[1:len(fields)-1]...)
  181. jobs[i].cmd.Stdin = stdin
  182. }
  183. jobs[i].cmd.Stdout = stdout
  184. jobs[i].cmd.Stderr = stderr
  185. jobs[i].cmd.Env = append(userenv, "PARENT="+os.Getenv("SHELL")) // 添加parent环境变量
  186. err := jobs[i].cmd.Start() // 开始执行
  187. if err != nil {
  188. panic(err)
  189. }
  190. go func() { // 创建goroutine
  191. err := jobs[i].cmd.Wait() // 等待job结束
  192. if err != nil {
  193. fmt.Println(err)
  194. }
  195. defer func() {
  196. recover()
  197. }()
  198. term <- true // 向停止信号管道发送信号,表示程序退出
  199. }()
  200. jobs[i].wait = true // 有goroutine在等待job结束
  201. fmt.Printf("[%d] %d\n", i, jobs[i].cmd.Process.Pid)
  202. return
  203. }
  204. // 命令解析
  205. switch fields[0] {
  206. case "bg": // 后台执行job
  207. cmd_bg(fields, stdin, stdout, stderr)
  208. case "cd": // 修改工作目录
  209. cmd_cd(fields, stdin, stdout, stderr)
  210. case "clr": // 清屏
  211. if len(fields) > 1 {
  212. panic("clr: too many arguments")
  213. }
  214. fmt.Fprint(stdout, "\033[H\033[2J") // 通过转义字符实现清屏
  215. case "dir": // 显示目录内容
  216. cmd_dir(fields, stdin, stdout, stderr)
  217. case "echo": // 显示内容并换行
  218. cmd_echo(fields, stdin, stdout, stderr)
  219. case "env": // 显示用户环境变量
  220. cmd_env(fields, stdin, stdout, stderr)
  221. case "exec": // 执行命令
  222. process(fields[1:], stdin, stdout, stderr)
  223. case "exit": // 退出shell
  224. cmd_exit(fields, stdin, stdout, stderr)
  225. case "fg": // 前台执行job
  226. cmd_fg(fields, stdin, stdout, stderr)
  227. case "jobs": // 显示job列表
  228. cmd_jobs(fields, stdin, stdout, stderr)
  229. case "pwd": // 显示工作目录
  230. dir, err := os.Getwd() // 获取工作目录
  231. if err != nil {
  232. panic(err)
  233. }
  234. fmt.Fprintln(stdout, dir)
  235. case "set": // 显示所有环境变量或设置命令行参数
  236. cmd_set(fields, stdin, stdout, stderr)
  237. case "shift": // 左移参数
  238. cmd_shift(fields, stdin, stdout, stderr)
  239. case "test": // 测试条件
  240. cmd_test(fields, stdin, stdout, stderr)
  241. case "time": // 显示时间
  242. t := time.Now()
  243. fmt.Fprintln(stdout, t)
  244. case "umask": // 显示或设置umask
  245. cmd_umask(fields, stdin, stdout, stderr)
  246. case "unset": // 重设变量
  247. cmd_unset(fields, stdin, stdout, stderr)
  248. default: // 外部命令
  249. if fields[0] == "help" { // 显示用户手册
  250. fields[0] = "more" // 调用more命令过滤
  251. cur = job{false, "running", strings.Join(fields, " "), exec.Command(fields[0], fields[1:]...)}
  252. cur.cmd.Stdin = manual()
  253. } else {
  254. cur = job{false, "running", strings.Join(fields, " "), exec.Command(fields[0], fields[1:]...)} // 前台执行
  255. cur.cmd.Stdin = stdin
  256. }
  257. cur.cmd.Stdout = stdout
  258. cur.cmd.Stderr = stderr
  259. cur.cmd.Env = append(userenv, "PARENT="+os.Getenv("SHELL")) // 添加parent环境变量
  260. err := cur.cmd.Start() // 开始执行
  261. if err != nil {
  262. panic(err)
  263. }
  264. term = make(chan bool) // 初始化停止信号管道
  265. go func() { // 创建goroutine
  266. err := cur.cmd.Wait() // 等待job结束
  267. if err != nil {
  268. fmt.Println(err)
  269. }
  270. defer func() {
  271. recover()
  272. }()
  273. term <- true // 向停止信号管道发送信号,表示程序退出
  274. }()
  275. cur.wait = true // 有goroutine在等待job结束
  276. <-term // 等待接收信号,实现前台执行
  277. close(term) // 关闭停止管道
  278. cur.cmd = nil // 前台没有在执行命令
  279. }
  280. }
  281. // 管道处理函数
  282. func pipe(fields []string) {
  283. if len(fields) == 0 {
  284. return
  285. }
  286. sub := []string{} // 子命令
  287. stdin := os.Stdin // 子命令标准输入
  288. _stdin, stdout, err := os.Pipe() // 在当前子命令标准输出和下一个子命令标准输入之间建立管道
  289. if err != nil {
  290. fmt.Println(err)
  291. return
  292. }
  293. stderr := os.Stderr // 子命令标准错误输出
  294. for _, field := range fields {
  295. if field == "|" { // 如果是管道分隔符
  296. if len(sub) != 0 {
  297. process(sub, stdin, stdout, stderr) // 则处理子命令
  298. stdout.Close() // 关闭当前子命令标准输出,表示处理完成
  299. stdin = _stdin // 设置下一个子命令标准输入
  300. _stdin, stdout, err = os.Pipe() // 继续建立管道
  301. if err != nil {
  302. fmt.Println(err)
  303. return
  304. }
  305. sub = []string{}
  306. }
  307. } else {
  308. sub = append(sub, field)
  309. }
  310. }
  311. stdout = os.Stdout // 最后一个子命令标准输出为默认值
  312. if len(sub) != 0 {
  313. process(sub, stdin, stdout, stderr)
  314. }
  315. }
  316. // 主函数
  317. func main() {
  318. userenv = os.Environ() // 设置用户环境变量
  319. // 初始化shell环境变量
  320. os.Setenv("!", "0") // 最近一次后台进程的pid
  321. os.Setenv("#", strconv.Itoa(len(os.Args)-1)) // 命令行参数个数
  322. os.Setenv("$", strconv.Itoa(os.Getpid())) // 当前进程的pid
  323. os.Setenv("?", "0") // 上一条命令的退出状态
  324. os.Setenv("*", strings.Join(os.Args[1:], " ")) // 所有命令行参数的值
  325. for i, str := range os.Args {
  326. os.Setenv(strconv.Itoa(i), str) // 命令行参数
  327. }
  328. path, err := filepath.Abs(os.Args[0]) // 执行shell的绝对路径
  329. if err != nil {
  330. fmt.Println(err)
  331. os.Exit(1)
  332. }
  333. os.Setenv("SHELL", path) // 添加shell环境变量
  334. script = len(os.Args) > 1 // 是否指定批处理文件
  335. if script && os.Args[1][0] != '-' {
  336. file, err := os.Open(os.Args[1]) // 打开文件
  337. if err != nil {
  338. fmt.Println(err)
  339. os.Exit(1)
  340. }
  341. os.Stdin = file // 将标准输入重定向到文件
  342. }
  343. sigs = make(chan os.Signal, 1) // 初始化信号管道
  344. // 捕获SIGINT、SIGQUIT、SIGTSTP、SIGCHLD信号
  345. signal.Notify(sigs, syscall.SIGINT, syscall.SIGQUIT, syscall.SIGTSTP, syscall.SIGCHLD)
  346. // 信号处理
  347. go func() {
  348. for sig := range sigs {
  349. switch sig {
  350. case syscall.SIGINT: // Ctrl+C触发SIGINT信号
  351. fmt.Println() // 换行
  352. prompt() // 输出提示符
  353. case syscall.SIGTSTP: // Ctrl+Z触发SIGTSTP信号
  354. if cur.cmd != nil { // 如果有前台执行的命令
  355. i := 1 // 获取job的id
  356. for ; ; i++ {
  357. _, ok := jobs[i]
  358. if !ok {
  359. break
  360. }
  361. }
  362. jobs[i] = &job{cur.wait, "suspended", cur.work, cur.cmd} // 创建job
  363. term <- false // 向停止信号管道发送信号,表示进程暂停
  364. fmt.Printf("[%d] %d suspended\t%s\n", i, jobs[i].cmd.Process.Pid, jobs[i].work)
  365. }
  366. case syscall.SIGCHLD: // 子进程结束触发SIGCHLD信号
  367. for i, job := range jobs { // 遍历所有job
  368. ps := job.cmd.ProcessState
  369. if ps != nil && ps.Exited() { // 如果job已经退出
  370. if ps.Success() { // 根据退出状态显示不同信息
  371. fmt.Printf("[%d] %d done\t%s\n", i, job.cmd.Process.Pid, job.work)
  372. } else {
  373. fmt.Printf("[%d] %d exit %d\t%s\n", i, job.cmd.Process.Pid, ps.ExitCode(), job.work)
  374. }
  375. delete(jobs, i) // 删除job
  376. }
  377. }
  378. }
  379. }
  380. }()
  381. scanner := bufio.NewScanner(os.Stdin) // 按行读入
  382. jobs = make(map[int]*job) // 初始化job映射
  383. prompt() // 每次读入前输出提示符
  384. for scanner.Scan() { // 读入一整行
  385. cmds := strings.Split(scanner.Text(), ";") // 按分号分隔
  386. for _, cmd := range cmds { // 依次处理
  387. pipe(split(cmd)) // 按空白字符分隔后进行管道处理
  388. }
  389. prompt()
  390. }
  391. fmt.Println()
  392. }