終端(Terminal)是計算機系統的輸入輸出設備,因爲歷史的緣由終端這個概念很是混亂,
終端的發展經歷了字符終端、圖形終端 和 網絡終端 三個階段.php
在早期因爲計算機很是昂貴,所以數十個用戶共用一臺主機,
爲了知足多用戶同時使用,最初使用一種叫電傳打字機的設備,簡稱TTY(TeleTYpe),
經過專用線纜與中央計算機相連,
電傳打字機經過鍵盤將電碼信號發送給主機,同時接收主機程序的輸出並打印在紙帶上,缺點是很是浪費紙,TTY設備是現代控制檯(Console)的鼻祖.java
在20世紀70年代後期,VT100由DEC生產.本機具備單色顯示屏.
咱們仍然沒法改變顏色,但它可以表達豐富的視覺效果,如閃爍,刪除文本,並使文本變爲粗體或斜體.
爲特定操做定義了許多控制序列.python
VT100是一個古老的終端定義,後面出現的終端幾乎都兼容這種終端.VT100沒法表達顏色,由於它嵌入了單色顯示器.
VT100控制碼是用來在終端擴展顯示的代碼.
好比果終端上任意座標用不一樣的顏色顯示字符.VT100控制碼有時又稱爲ANSI Escape Sequence.
若是感興趣繼續瞭解VT的發展歷史請訪問vt100.netgit
VT100控制碼ANSI Escape Sequence
顧名思義,全部控制序列開始從\x1b
對應逃逸上ASCII碼錶.今天大多數我的計算機的Telnet用戶端提供最廣泛的終端(通常VT100)的模擬.
VT100沒法表達顏色,由於它嵌入了單色顯示器.可是不知道爲何VT100控制碼ANSI Escape Sequence有改變顏色的控制序列的細節,
但VT241終端是高端模型嵌入彩色圖形顯示器.github
讓咱們瞭解VT100控制碼.全部的控制符是\033
或\e
打頭(即 ESC 的 ASCII 碼)用輸出字符語句來輸出.
能夠在命令行用 echo 命令,或者在 C 程序中用 printf 來輸出 VT100 的控制字符.golang
\033[0m // 關閉全部屬性 \033[1m // 設置爲高亮 \033[4m // 下劃線 \033[5m // 閃爍 \033[7m // 反顯 \033[8m // 消隱 \033[nA // 光標上移 n 行 \033[nB // 光標下移 n 行 \033[nC // 光標右移 n 行 \033[nD // 光標左移 n 行 \033[y;xH // 設置光標位置 \033[2J // 清屏 \033[K // 清除從光標到行尾的內容 \033[s // 保存光標位置 \033[u // 恢復光標位置 \033[?25l // 隱藏光標 \033[?25h // 顯示光標
\033[30m – \033[37m
爲設置前景色30: 黑色 31: 紅色 32: 綠色 33: 黃色 34: 藍色 35: 紫色 36: 青色 37: 白色
\033[40m – \033[47m
爲設置背景色40: 黑色 41: 紅色 42: 綠色 43: 黃色 44: 藍色 45: 紫色 46: 青色 47: 白色
ANSI / VT100控制碼文檔windows
在一些操做系統中,包括Unix的,一個僞終端,pseudotty,或PTY是一對僞設備,
其中,所述一個的從屬,模仿硬件文本終端裝置,其中,所述其它的主,提供了這樣的裝置終端仿真器進程控制從站.
終端仿真器進程還必須處理終端控制命令,例如,用於調整屏幕的大小.
普遍使用的終端仿真程序包括xterm,GNOME終端,Konsole和終端.
遠程登陸處理程序(如ssh和telnet服務器)扮演相同的角色,但與遠程用戶而不是本地用戶進行通訊.
還要考慮諸如指望之類的程序.bash
打印色彩文字示例服務器
package main import "fmt" func main() { fmt.Print("\x1b[4;30;46m")//設置顏色樣式 fmt.Print("Hello World")//打印文本內容 fmt.Println("\x1b[0m")//樣式結束符,清楚以前的顯示屬性 }
運行效果網絡
源代碼解析,請關注第4行,這是VT100控制碼改變顏色.\x1b[4;30;46m
由3部分組成.
\x1b[
:控制序列導入器4;30;46
:由分號分隔的參數.4表示下劃線,30表示設置前景色黑色,46表示設置背景顏色青色.m
:最後一個字符(老是一個字符).打印Hello World後, print\x1b[0m
包含0
用來表示清除顯示屬性.
開源庫fatih/color的原理就是使用golang print
VT100控制碼(ANSI Escape Sequence)標記文本內容,色彩豐富的終端文本
顯示進度條的代碼的原理:
package main import ( "fmt" "strings" "time" ) func renderbar(count, total int) { barwidth := 30 done := int(float64(barwidth) * float64(count) / float64(total)) fmt.Printf("Progress: \x1b[33m%3d%%\x1b[0m ", count*100/total) fmt.Printf("[%s%s]", strings.Repeat("=", done), strings.Repeat("-", barwidth-done)) } func main() { total := 50 for i := 1; i <= total; i++ { //<ESC>表示ASCII「轉義」字符,0x1B fmt.Print("\x1b7") // 保存光標位置 保存光標和Attrs <ESC> 7 fmt.Print("\x1b[2k") // 清空當前行的內容 擦除線<ESC> [2K renderbar(i, total) time.Sleep(50 * time.Millisecond) fmt.Print("\x1b8") // 恢復光標位置 恢復光標和Attrs <ESC> 8 } fmt.Println() }
這部分代碼缺陷就是barwidth
這個值是固定的,但實際中這個變量因該跟隨終端的寬度來肯定.
咱們能夠更改窗口大小,由於咱們使用pty(終端模擬器),
而不是終端機器.在本節中,讓咱們瞭解如何得到終端仿真器的大小.
要得到窗口大小,您須要syscall.SYS_IOCTL
使用TIOCGWINSZ
如下調用.
type winsize struct { Row uint16 Col uint16 X uint16 Y uint16 } func getWinSize(fd int) (row, col uint16, err error) { var ws *winsize retCode, _, errno := syscall.Syscall( syscall.SYS_IOCTL, uintptr(fd), uintptr(syscall.TIOCGWINSZ), uintptr(unsafe.Pointer(ws))) if int(retCode) == -1 { panic(errno) } return ws.Row, ws.Col, nil }
但從易用性和簡單出發,最好直接調用unix.IoctlGetWinsize
, 注意GetWinsizeAPi在windows上很差使
package main import ( "fmt" "strings" "syscall" "time" "golang.org/x/sys/unix" ) var wscol = 30 func init() { ws, err := unix.IoctlGetWinsize(syscall.Stdout, unix.TIOCGWINSZ) if err != nil { panic(err) } wscol = int(ws.Col) } func renderbar(count, total int) { barwidth := wscol - len("Progress: 100% []") done := int(float64(barwidth) * float64(count) / float64(total)) fmt.Printf("Progress: \x1b[33m%3d%%\x1b[0m ", count*100/total) fmt.Printf("[%s%s]", strings.Repeat("=", done), strings.Repeat("-", barwidth-done)) } func main() { total := 50 for i := 1; i <= total; i++ { fmt.Print("\x1b7") // save the cursor position fmt.Print("\x1b[2k") // erase the current line renderbar(i, total) time.Sleep(50 * time.Millisecond) fmt.Print("\x1b8") // restore the cursor position } fmt.Println() }
不只要了解如何獲取窗口大小,還須要知道如何接收事件,通知事件窗口大小更改.
這裏以macOS/unix系統爲例,
您能夠從UNIX OS
信號接收通知.您只需處理SIGWINCH os
信號,以下所示.
package main import ( "fmt" "os" "os/signal" "strings" "syscall" "time" "golang.org/x/sys/unix" ) var ( total = 50 count = 0 wscol = 20 ) func init() { err := updateWSCol() if err != nil { panic(err) } } func updateWSCol() error { ws, err := unix.IoctlGetWinsize(syscall.Stdout, unix.TIOCGWINSZ) if err != nil { return err } wscol = int(ws.Col) return nil } func renderbar() { fmt.Print("\x1b7") // 保存光標位置 fmt.Print("\x1b[2k") // 清除當前行內容 defer fmt.Print("\x1b8") // 恢復光標位置 barwidth := wscol - len("Progress: 100% []") done := int(float64(barwidth) * float64(count) / float64(total)) fmt.Printf("Progress: \x1b[33m%3d%%\x1b[0m ", count*100/total) fmt.Printf("[%s%s]", strings.Repeat("=", done), strings.Repeat("-", barwidth-done)) } func main() { // set signal handler sigwinch := make(chan os.Signal, 1) defer close(sigwinch) signal.Notify(sigwinch, syscall.SIGWINCH) go func() { for { if _, ok := <-sigwinch; !ok { return } _ = updateWSCol() renderbar() } }() for count = 1; count <= 50; count++ { renderbar() time.Sleep(time.Second) } fmt.Println() }
經過調用ioctl
與TIOCGWINSZ
當你收到SIGWINCH
signal,你能夠獲得窗口的大小.您能夠今後信息控制終端UI.
可是很難正確擦除屏幕.
實際上,若是在此代碼中使終端窗口變小,則輸出將崩潰.最簡單的方法是每次都擦除整個屏幕.
思惟擴展:根據 ANSI / VT100終端控制碼
文檔結合python/bash/go/java/c/php等語言的print
函數你能夠開發出本身的富文本終端UI app.
參考文檔