精品欧美一区二区三区在线观看 _久久久久国色av免费观看性色_国产精品久久在线观看_亚洲第一综合网站_91精品又粗又猛又爽_小泽玛利亚一区二区免费_91亚洲精品国偷拍自产在线观看 _久久精品视频在线播放_美女精品久久久_欧美日韩国产成人在线

震驚!原來(lái)命令行還可以這么玩?!

移動(dòng)開(kāi)發(fā)
你是否好奇過(guò)命令行里那些花里胡哨的進(jìn)度條是如何實(shí)現(xiàn)的?好奇過(guò)Spring Boot為什么能夠打印五顏六色的日志?好奇過(guò)Python或者PHP等腳本語(yǔ)言的交互式命令行是如何實(shí)現(xiàn)的?好奇過(guò)Vim或者Emacs等在Terminal中的編輯器是怎么實(shí)現(xiàn)的?

你是否:

  • 好奇過(guò)命令行里那些花里胡哨的進(jìn)度條是如何實(shí)現(xiàn)的?
  • 好奇過(guò)Spring Boot為什么能夠打印五顏六色的日志?
  • 好奇過(guò)Python或者PHP等腳本語(yǔ)言的交互式命令行是如何實(shí)現(xiàn)的?
  • 好奇過(guò)Vim或者Emacs等在Terminal中的編輯器是怎么實(shí)現(xiàn)的?

如果你曾經(jīng)好奇過(guò),或者被這段話勾起了你的好奇心,那么你絕對(duì)不能錯(cuò)過(guò)這篇文章!

震驚!原來(lái)命令行還可以這么玩?!

背景

通過(guò)本文你可以學(xué)到:

  1. 何為Ansi Escape Codes以及它們能干什么?
  2. Ansi Escape Codes的一些高級(jí)應(yīng)用。
  3. JDK9中Jshell的使用。

事先聲明,本文主要參考: http://www.lihaoyi.com/post/BuildyourownCommandLinewithANSIescapecodes.html 。原文思路清晰,案例生動(dòng)形象,排版優(yōu)秀,實(shí)為良心之作。但是由于原文是用英語(yǔ)書寫且用Python作為演示,所以本后端小菜雞不要臉地將其翻譯一遍,并且用JDK9的Jshell做演示,方便廣大的Javaer學(xué)習(xí)。

本文所有的代碼已經(jīng)推到Github中,地址為: https://github.com/Lovelcp/blog-demos/tree/master/ansi-escape-codes-tutorial 。強(qiáng)烈建議大家將代碼clone下來(lái)跑一下看看效果,加深自己的印象。

環(huán)境

  • Mac或Linux或者WIn10操作系統(tǒng)。 除了Win10之外的Windows系統(tǒng)暫時(shí)不支持Ansi Escape Codes。
  • 因?yàn)楸疚牟捎肑shell作為演示工具,所以大家需要安裝最近剛正式發(fā)布的JDK9。

OK!一切準(zhǔn)備就緒,讓我們開(kāi)始吧!

富文本

Ansi Escape Codes最基礎(chǔ)的用途就是讓控制臺(tái)顯示的文字以富文本的形式輸出,比如設(shè)置字體顏色、背景顏色以及各種樣式。讓我們先來(lái)學(xué)習(xí)如何設(shè)置字體顏色,而不用再忍受那枯燥的黑白二色!

字體顏色

通過(guò)Ansi指令(即Ansi Escape Codes)給控制臺(tái)的文字上色是最為常見(jiàn)的操作。比如:

  • 紅色: \u001b[31m
  • 重置: \u001b[0m

絕大部分Ansi Escape Codes都以 \u001b 開(kāi)頭。讓我們通過(guò)Java代碼來(lái)輸出一段紅色的 Hello World :

  1. System.out.print("\u001b[31mHello World"); 

 震驚!原來(lái)命令行還可以這么玩?!

從上圖中,我們可以看到,不僅 Hello World 是變成了紅色,而且接下來(lái)的 jshell> 提示符也變成了紅色。其實(shí)不管你接下來(lái)輸入什么字符,它們的字體顏色都是紅色。直到你輸入了其他顏色的Ansi指令,或者輸入了重置指令,字體的顏色才會(huì)不再是紅色。

讓我們嘗試輸入重置指令來(lái)恢復(fù)字體的顏色:

  1. System.out.print("\u001b[0m"); 

 震驚!原來(lái)命令行還可以這么玩?!

很好! jshell> 提示符恢復(fù)為了白色。所以一個(gè)最佳實(shí)踐就是,最好在所有改變字體顏色或者樣式的Ansi Escape Codes的最后加上重置指令,以免造成意想不到的后果。舉個(gè)例子:

  1. System.out.print("\u001b[31mHello World\u001b[0m"); 

 震驚!原來(lái)命令行還可以這么玩?!

當(dāng)然,重置指令可以被添加在任何位置,比如我們可以將其插在 Hello World 的中間,使得 Hello 是紅色,但是 World 是白色:

  1. System.out.print("\u001b[31mHello\u001b[0m World"); 

 震驚!原來(lái)命令行還可以這么玩?!

8色

剛才我們介紹了 紅色 以及 重置 命令。基本上所有的控制臺(tái)都支持以下8種顏色:

  • 黑色: \u001b[30m
  • 紅色: \u001b[31m
  • 綠色: \u001b[32m
  • 黃色: \u001b[33m
  • 藍(lán)色: \u001b[34m
  • 洋紅色: \u001b[35m
  • 青色: \u001b[36m
  • 白色: \u001b[37m
  • 重置: \u001b[0m

不如將它們都輸出看一下: 

  1. System.out.print("\u001b[30m A \u001b[31m B \u001b[32m C \u001b[33m D \u001b[0m");  
  2. System.out.print("\u001b[34m E \u001b[35m F \u001b[36m G \u001b[37m H \u001b[0m"); 

 震驚!原來(lái)命令行還可以這么玩?!

注意, A 因?yàn)槭呛谏耘c控制臺(tái)融為一體了。

16色

大多數(shù)的控制臺(tái),除了支持剛才提到的8色外,還可以輸出在此之上更加明亮的8種顏色:

  • 亮黑色: \u001b[30;1m
  • 亮紅色: \u001b[31;1m
  • 亮綠色: \u001b[32;1m
  • 亮黃色: \u001b[33;1m
  • 亮藍(lán)色: \u001b[34;1m
  • 亮洋紅色: \u001b[35;1m
  • 亮青色: \u001b[36;1m
  • 亮白色: \u001b[37;1m

亮色指令分別在原來(lái)對(duì)應(yīng)顏色的指令中間加上 ;1 。我們將所有的16色在控制臺(tái)打印,方便大家進(jìn)行比對(duì): 

  1. System.out.print("\u001b[30m A \u001b[31m B \u001b[32m C \u001b[33m D \u001b[0m");  
  2. System.out.print("\u001b[34m E \u001b[35m F \u001b[36m G \u001b[37m H \u001b[0m");  
  3. System.out.print("\u001b[30;1m A \u001b[31;1m B \u001b[32;1m C \u001b[33;1m D \u001b[0m");  
  4. System.out.print("\u001b[34;1m E \u001b[35;1m F \u001b[36;1m G \u001b[37;1m H \u001b[0m"); 

 震驚!原來(lái)命令行還可以這么玩?!

從圖中我們可以清晰地看到,下面的8色比上面的8色顯得更加明亮。比如,原來(lái)黑色的 A ,在黑色的控制臺(tái)背景下,幾乎無(wú)法看到,但是一旦通過(guò)亮黑色輸出后,對(duì)比度變得更高,變得更好辨識(shí)了。

256色

最后,除了16色外,某些控制臺(tái)支持輸出256色。指令的形式如下:

  1. \u001b[38;5;${ID}m 

讓我們輸出256色矩陣: 

  1. for (int i = 0; i < 16; i++) { 
  2.     for (int j = 0; j < 16; j++) { 
  3.         int code = i * 16 + j; 
  4.         System.out.printf("\u001b[38;5;%dm%-4d", code, code); 
  5.     } 
  6.     System.out.println("\u001b[0m"); 

 震驚!原來(lái)命令行還可以這么玩?!

關(guān)于字體顏色我們就介紹到這,接下來(lái)我們來(lái)介紹背景色。

背景顏色

剛才所說(shuō)的字體顏色可以統(tǒng)稱為前景色(foreground color)。那么理所當(dāng)然,我們可以設(shè)置文本的背景顏色:

  • 黑色背景: \u001b[40m
  • 紅色背景: \u001b[41m
  • 綠色背景: \u001b[42m
  • 黃色背景: \u001b[43m
  • 藍(lán)色背景: \u001b[44m
  • 洋紅色背景: \u001b[45m
  • 青色背景: \u001b[46m
  • 白色背景: \u001b[47m

對(duì)應(yīng)的亮色版本:

  • 亮黑色背景: \u001b[40;1m
  • 亮紅色背景: \u001b[41;1m
  • 亮綠色背景: \u001b[42;1m
  • 亮黃色背景: \u001b[43;1m
  • 亮藍(lán)色背景: \u001b[44;1m
  • 亮洋紅色背景: \u001b[45;1m
  • 亮青色背景: \u001b[46;1m
  • 亮白色背景: \u001b[47;1m

首先讓我們看看16色背景: 

  1. System.out.print("\u001b[40m A \u001b[41m B \u001b[42m C \u001b[43m D \u001b[0m");  
  2. System.out.print("\u001b[44m A \u001b[45m B \u001b[46m C \u001b[47m D \u001b[0m");  
  3. System.out.print("\u001b[40;1m A \u001b[41;1m B \u001b[42;1m C \u001b[43;1m D \u001b[0m");  
  4. System.out.print("\u001b[44;1m A \u001b[45;1m B \u001b[46;1m C \u001b[47;1m D \u001b[0m"); 

 震驚!原來(lái)命令行還可以這么玩?!

值得注意的是,亮色背景并不是背景顏色顯得更加明亮,而是讓對(duì)應(yīng)的前景色顯得更加明亮。雖然這點(diǎn)有點(diǎn)不太直觀,但是實(shí)際表現(xiàn)就是如此。

讓我們?cè)賮?lái)試試256背景色,首先指令如下:

  1. \u001b[48;5;${ID}m 

同樣輸出256色矩陣: 

  1. for (int i = 0; i < 16; i++) { 
  2.     for (int j = 0; j < 16; j++) { 
  3.         int code = i * 16 + j; 
  4.         System.out.printf("\u001b[48;5;%dm%-4d", code, code); 
  5.     } 
  6.     System.out.println("\u001b[0m"); 

 震驚!原來(lái)命令行還可以這么玩?!

感覺(jué)要被亮瞎眼了呢!至此,顏色設(shè)置已經(jīng)介紹完畢,讓我們接著學(xué)習(xí)樣式設(shè)置。

樣式

除了給文本設(shè)置顏色之外,我們還可以給文本設(shè)置樣式:

  • 粗體: \u001b[1m
  • 下劃線: \u001b[4m
  • 反色: \u001b[7m

樣式分別使用的效果:

  1. System.out.print("\u001b[1m BOLD \u001b[0m\u001b[4m Underline \u001b[0m\u001b[7m Reversed \u001b[0m"); 

 震驚!原來(lái)命令行還可以這么玩?!

或者結(jié)合使用:

  1. System.out.print("\u001b[1m\u001b[4m\u001b[7m BOLD Underline Reversed \u001b[0m"); 

 震驚!原來(lái)命令行還可以這么玩?!

甚至還可以和顏色結(jié)合使用: 

  1. System.out.print("\u001b[1m\u001b[31m Red Bold \u001b[0m");  
  2. System.out.print("\u001b[4m\u001b[44m Blue Background Underline \u001b[0m"); 

 震驚!原來(lái)命令行還可以這么玩?!

 是不是很簡(jiǎn)單,是不是很酷!學(xué)會(huì)了這些,我們已經(jīng)能夠?qū)懗鍪挚犰诺拿钚心_本了。但是如果要實(shí)現(xiàn)更復(fù)雜的功能(比如進(jìn)度條),我們還需要掌握更加牛逼的光標(biāo)控制指令!

[[211306]]

光標(biāo)控制

Ansi Escape Code里更加復(fù)雜的指令就是光標(biāo)控制。通過(guò)這些指令,我們可以自由地移動(dòng)我們的光標(biāo)至屏幕的任何位置。比如在Vim的命令模式下,我們可以使用 H/J/K/L 這四個(gè)鍵實(shí)現(xiàn)光標(biāo)的上下左右移動(dòng)。

最基礎(chǔ)的光標(biāo)控制指令如下:

  • 上: \u001b[{n}A
  • 下: \u001b[{n}B
  • 右: \u001b[{n}C
  • 左: \u001b[{n}D

通過(guò)光標(biāo)控制的特性,我們能夠?qū)崿F(xiàn)大量有趣且酷炫的功能。首先我們來(lái)看看怎么實(shí)現(xiàn)一個(gè)進(jìn)度條。

進(jìn)度數(shù)字顯示

作為進(jìn)度條,怎么可以沒(méi)有進(jìn)度數(shù)字顯示呢?所以我們先來(lái)實(shí)現(xiàn)進(jìn)度條進(jìn)度數(shù)字的刷新: 

  1. void loading()throws InterruptedException { 
  2.     System.out.println("Loading..."); 
  3.     for (int i = 1; i <= 100; i++) { 
  4.         Thread.sleep(100); 
  5.         System.out.print("\u001b[1000D" + i + "%"); 
  6.     } 

 震驚!原來(lái)命令行還可以這么玩?!

從圖中我們可以看到,進(jìn)度在同一行從1%不停地刷新到100%。為了進(jìn)度只在同一行顯示,我們?cè)诖a中使用了 System.out.print 而不是 System.out.println 。在打印每個(gè)進(jìn)度之前,我們使用了 \u001b[1000D 指令,目的是為了將光標(biāo)移動(dòng)到當(dāng)前行的最左邊也就是行首。然后重新打印新的進(jìn)度,新的進(jìn)度數(shù)字會(huì)覆蓋剛才的進(jìn)度數(shù)字,循環(huán)往復(fù),這就實(shí)現(xiàn)了上圖的效果。

PS: \u001b[1000D 表示將光標(biāo)往左移動(dòng)1000個(gè)字符。這里的1000表示光標(biāo)移動(dòng)的距離,只要你能夠確保光標(biāo)能夠移動(dòng)到最左端,隨便設(shè)置多少比如設(shè)置2000都可以。

為了方便大家更加輕松地理解光標(biāo)的移動(dòng)過(guò)程,讓我們放慢進(jìn)度條刷新的頻率: 

  1. void loading()throws InterruptedException { 
  2.     System.out.println("Loading..."); 
  3.     for (int i = 1; i <= 100; i++) { 
  4.         System.out.print("\u001b[1000D"); 
  5.         Thread.sleep(1000); 
  6.         System.out.print(i + "%"); 
  7.         Thread.sleep(1000); 
  8.     } 

 震驚!原來(lái)命令行還可以這么玩?!

現(xiàn)在我們可以清晰地看到:

  1. 從左到右打印進(jìn)度,光標(biāo)移至行尾。
  2. 光標(biāo)移至行首,原進(jìn)度數(shù)字還在。
  3. 從左到右打印新進(jìn)度,新的數(shù)字會(huì)覆蓋老的數(shù)字。光標(biāo)移至行尾。
  4. 循環(huán)往復(fù)。

Ascii進(jìn)度條

好了,我們現(xiàn)在已經(jīng)知道如何通過(guò)Ansi Escape Code實(shí)現(xiàn)進(jìn)度數(shù)字的顯示和刷新,剩下的就是實(shí)現(xiàn)進(jìn)度的讀條。廢話不多說(shuō),我們直接上代碼和效果圖: 

  1. void loading()throws InterruptedException { 
  2.     System.out.println("Loading..."); 
  3.     for (int i = 1; i <= 100; i++) { 
  4.         int width = i / 4; 
  5.         String left = "[" + String.join("", Collections.nCopies(width, "#")); 
  6.         String right = String.join("", Collections.nCopies(25 - width, " ")) + "]"
  7.         System.out.print("\u001b[1000D" + left + right); 
  8.         Thread.sleep(100); 
  9.     } 

 震驚!原來(lái)命令行還可以這么玩?!

由上圖我們可以看到,每次循環(huán)過(guò)后,讀條就會(huì)增加。原理和數(shù)字的刷新一樣,相信大家閱讀代碼就能理解,這里就不再贅述。

讓我們來(lái)點(diǎn)更酷的吧!利用Ansi的光標(biāo) 向上 以及 向下 的指令,我們還可以同時(shí)打印出多條進(jìn)度條: 

  1. void loading(int count)throws InterruptedException { 
  2.     System.out.print(String.join("", Collections.nCopies(count"\n"))); // 初始化進(jìn)度條所占的空間 
  3.     List<Integer> allProgress = new ArrayList<>(Collections.nCopies(count, 0)); 
  4.     while (true) { 
  5.         Thread.sleep(10); 
  6.  
  7.         // 隨機(jī)選擇一個(gè)進(jìn)度條,增加進(jìn)度 
  8.         List<Integer> unfinished = new LinkedList<>(); 
  9.         for (int i = 0; i < allProgress.size(); i++) { 
  10.             if (allProgress.get(i) < 100) { 
  11.                 unfinished.add(i); 
  12.             } 
  13.         } 
  14.         if (unfinished.isEmpty()) { 
  15.             break; 
  16.         } 
  17.         int index = unfinished.get(new Random().nextInt(unfinished.size())); 
  18.         allProgress.set(index, allProgress.get(index) + 1); // 進(jìn)度+1 
  19.  
  20.         // 繪制進(jìn)度條 
  21.         System.out.print("\u001b[1000D"); // 移動(dòng)到最左邊 
  22.         System.out.print("\u001b[" + count + "A"); // 往上移動(dòng) 
  23.         for (Integer progress : allProgress) { 
  24.             int width = progress / 4; 
  25.             String left = "[" + String.join("", Collections.nCopies(width, "#")); 
  26.             String right = String.join("", Collections.nCopies(25 - width, " ")) + "]"
  27.             System.out.println(left + right); 
  28.         } 
  29.     } 

在上述代碼中:

  • 我們首先執(zhí)行 System.out.print(String.join("", Collections.nCopies(count, "\n"))); 打印出多個(gè)空行,這可以保證我們有足夠的空間來(lái)打印進(jìn)度條。
  • 接下來(lái)我們隨機(jī)增加一個(gè)進(jìn)度條的進(jìn)度,并且打印出所有進(jìn)度條。
  • 最后我們調(diào)用 向上 指令,將光標(biāo)移回到最上方,繼續(xù)下一個(gè)循環(huán),直到所有進(jìn)度條都到達(dá)100%。

實(shí)際效果如下:

震驚!原來(lái)命令行還可以這么玩?!

效果真是太棒啦!剩下將讀條和數(shù)字結(jié)合在一起的工作就交給讀者啦。學(xué)會(huì)了這招,當(dāng)你下次如果要做一個(gè)在命令行下載文件的小工具,這時(shí)候這些知識(shí)就派上用場(chǎng)啦!

制作命令行

最后,最為酷炫的事情莫過(guò)于利用Ansi Escape Codes實(shí)現(xiàn)一個(gè)個(gè)性化的命令行(Command-Line)。我們平常使用的Bash以及一些解釋型語(yǔ)言比如Python、Ruby等都有自己的REPL命令行。接下來(lái),讓我們揭開(kāi)他們神秘的面紗,了解他們背后實(shí)現(xiàn)的原理。

PS:由于在Jshell中,方向鍵、后退鍵等一些特殊鍵有自己的作用,所以接下來(lái)無(wú)法通過(guò)Jshell演示。需要自己手動(dòng)進(jìn)行編譯運(yùn)行代碼才能看到實(shí)際效果。

一個(gè)最簡(jiǎn)單的命令行

首先,我們來(lái)實(shí)現(xiàn)一個(gè)最簡(jiǎn)單的命令行,簡(jiǎn)單到只實(shí)現(xiàn)下面兩種功能:

  • 當(dāng)用戶輸入一個(gè)可打印的字符時(shí),比如abcd等,則在控制臺(tái)顯示。
  • 當(dāng)用戶輸入回車時(shí),另起一行,輸出剛才用戶輸入的所有字符,然后再另起一行,繼續(xù)接受用戶的輸入。

那么這個(gè)最簡(jiǎn)單的命令行的實(shí)現(xiàn)代碼會(huì)長(zhǎng)這樣: 

  1. import java.io.IOException; 
  2.  
  3. public class CommandLine{ 
  4.     public static void main(String[] args)throws IOException, InterruptedException { 
  5.         // 設(shè)置命令行為raw模式,否則會(huì)自動(dòng)解析方向鍵以及后退鍵,并且直到按下回車read方法才會(huì)返回 
  6.         String[] cmd = { "/bin/sh""-c""stty raw </dev/tty" }; 
  7.         Runtime.getRuntime() 
  8.                .exec(cmd) 
  9.                .waitFor(); 
  10.         while (true) { 
  11.             String input = ""
  12.             while (true) { 
  13.                 char ch = (char) System.in.read(); 
  14.                 if (ch == 3) { 
  15.                     // CTRL-C 
  16.                     return
  17.                 } 
  18.                 else if (ch >= 32 && ch <= 126) { 
  19.                     // 普通字符 
  20.                     input += ch; 
  21.                 } 
  22.                 else if (ch == 10 || ch == 13) { 
  23.                     // 回車 
  24.                     System.out.println(); 
  25.                     System.out.print("\u001b[1000D"); 
  26.                     System.out.println("echo: " + input); 
  27.                     input = ""
  28.                 } 
  29.  
  30.                 System.out.print("\u001b[1000D"); // 首先將光標(biāo)移動(dòng)到最左側(cè) 
  31.                 System.out.print(input); // 重新輸出input 
  32.                 System.out.flush(); 
  33.             } 
  34.         } 
  35.     } 

好的,讓我們來(lái)說(shuō)明一下代碼中的關(guān)鍵點(diǎn):

  1. 首先最關(guān)鍵的是我們需要將我們的命令行設(shè)置為raw模式,這可以避免JVM幫我們解析方向鍵,回退鍵以及對(duì)用戶輸入進(jìn)行緩沖。大家可以試一下不設(shè)置raw模式然后看一下效果,就可以理解我說(shuō)的話了。
  2. 通過(guò) System.in.read() 方法獲取用戶輸入,然后對(duì)其ascii值進(jìn)行分析。
  3. 如果發(fā)現(xiàn)用戶輸入的是回車的話,我們這時(shí)需要打印剛才用戶輸入的所有字符。但是我們需要注意,由于設(shè)置了raw模式,不移動(dòng)光標(biāo)直接打印的話,光標(biāo)的位置不會(huì)移到行首,如下圖:

震驚!原來(lái)命令行還可以這么玩?!

所以這里需要再次調(diào)用 System.out.print("\u001b[1000D"); 將光標(biāo)移到行首。

好了,讓我們來(lái)看一下效果吧:

震驚!原來(lái)命令行還可以這么玩?!

成功了!但是有個(gè)缺點(diǎn),那就是命令行并沒(méi)有解析方向鍵,反而以 [D[A[C[B 輸出(見(jiàn)動(dòng)圖)。這樣我們只能一直往后面寫而無(wú)法做到將光標(biāo)移動(dòng)到前面實(shí)現(xiàn)插入的效果。所以接下來(lái)就讓我們給命令行加上解析方向鍵的功能吧!

光標(biāo)移動(dòng)

簡(jiǎn)單起見(jiàn),我們僅需實(shí)現(xiàn)按下方向鍵的左右兩鍵時(shí)能控制光標(biāo)左右移動(dòng)。左右兩鍵對(duì)應(yīng)的ascii碼分別為 27 91 68 和 27 91 67 。所以我們只要在代碼中加上對(duì)這兩串a(chǎn)scii碼的解析即可: 

  1. import java.io.IOException; 
  2.  
  3. public class CommandLine{ 
  4.     public static void main(String[] args)throws IOException, InterruptedException { 
  5.         // 設(shè)置命令行為raw模式,否則會(huì)自動(dòng)解析方向鍵以及后退鍵,并且直到按下回車read方法才會(huì)返回 
  6.         String[] cmd = { "/bin/sh""-c""stty raw </dev/tty" }; 
  7.         Runtime.getRuntime() 
  8.                .exec(cmd) 
  9.                .waitFor(); 
  10.         while (true) { 
  11.             String input = ""
  12.             int index = 0; 
  13.             while (true) { 
  14.                 char ch = (char) System.in.read(); 
  15.                 if (ch == 3) { 
  16.                     // CTRL-C 
  17.                     return
  18.                 } 
  19.                 else if (ch >= 32 && ch <= 126) { 
  20.                     // 普通字符 
  21.                     input = input.substring(0, index) + ch + input.substring(index, input.length()); 
  22.                     index++; 
  23.                 } 
  24.                 else if (ch == 10 || ch == 13) { 
  25.                     // 回車 
  26.                     System.out.println(); 
  27.                     System.out.print("\u001b[1000D"); 
  28.                     System.out.println("echo: " + input); 
  29.                     input = ""
  30.                     index = 0; 
  31.                 } 
  32.                 else if (ch == 27) { 
  33.                     // 左右方向鍵 
  34.                     char next1 = (char) System.in.read(); 
  35.                     char next2 = (char) System.in.read(); 
  36.                     if (next1 == 91) { 
  37.                         if (next2 == 68) { 
  38.                             // 左方向鍵 
  39.                             index = Math.max(0, index - 1); 
  40.                         } 
  41.                         else if (next2 == 67) { 
  42.                             // 右方向鍵 
  43.                             index = Math.min(input.length(), index + 1); 
  44.                         } 
  45.                     } 
  46.                 } 
  47.  
  48.                 System.out.print("\u001b[1000D"); // 將光標(biāo)移動(dòng)到最左側(cè) 
  49.                 System.out.print(input); 
  50.                 System.out.print("\u001b[1000D"); // 再次將光標(biāo)移動(dòng)到最左側(cè) 
  51.                 if (index > 0) { 
  52.                     System.out.print("\u001b[" + index + "C"); // 將光標(biāo)移動(dòng)到index處 
  53.                 } 
  54.                 System.out.flush(); 
  55.             } 
  56.         } 
  57.     } 

效果如下:

震驚!原來(lái)命令行還可以這么玩?!

It works!但是這個(gè)命令行還不支持刪除,我們無(wú)法通過(guò) Backspace 鍵刪去敲錯(cuò)的字符。有了剛才的經(jīng)驗(yàn),實(shí)現(xiàn)刪除功能也十分簡(jiǎn)單!

刪除

照著剛才的思路,我們可能會(huì)在處理用戶輸入的地方,加上如下的代碼: 

  1. else if (ch == 127) { 
  2.     // 刪除 
  3.     if (index > 0) { 
  4.         input = input.substring(0, index - 1) + input.substring(index, input.length()); 
  5.         index -= 1; 
  6.     } 

但是這段代碼存在點(diǎn)問(wèn)題,讓我們看一下效果圖:

震驚!原來(lái)命令行還可以這么玩?!

從圖中我們可以看到:

第一次,當(dāng)我輸入了 11234566 ,然后不停地按下刪除鍵,想要?jiǎng)h掉 34566 ,但是只有光標(biāo)在后退,字符并沒(méi)有被刪掉。然后我再按下回車鍵,通過(guò)echo的字符串我們發(fā)現(xiàn)刪除實(shí)際上已經(jīng)成功,只是控制臺(tái)在顯示的時(shí)候出了點(diǎn)問(wèn)題。

第二次,我先輸入 123456 ,然后按下刪除鍵,刪掉 456 ,光標(biāo)退到 3 。然后我再繼續(xù)不斷地輸入 0 ,我們發(fā)現(xiàn)隨著 0 覆蓋了原來(lái)的 456 顯示的位置。

所以刪除的確產(chǎn)生了效果,但是我們要解決被刪除的字符還在顯示的這個(gè)bug。為了實(shí)現(xiàn)刪除的效果,我們先來(lái)學(xué)習(xí)一下Ansi里的刪除指令:

清除屏幕: \u001b[{n}J 為指令。

  • n=0 :清除光標(biāo)到屏幕末尾的所有字符。
  • n=1 :清除屏幕開(kāi)頭到光標(biāo)的所有字符。
  • n=2 :清除整個(gè)屏幕的字符。

清除行: \u001b[{n}K 為指令

  • n=0 :清除光標(biāo)到當(dāng)前行末所有的字符。
  • n=1 :清除當(dāng)前行到光標(biāo)的所有字符。
  • n=2 :清除當(dāng)前行。

所以我們的思路就是不管用戶輸入了什么,我們先利用 System.out.print("\u001b[0K"); 清除當(dāng)前行,此時(shí)光標(biāo)回到了行首,這時(shí)再輸出正確的字符。完整代碼如下: 

  1. import java.io.IOException; 
  2.  
  3. public class CommandLine{ 
  4.     public static void main(String[] args)throws IOException, InterruptedException { 
  5.         // 設(shè)置命令行為raw模式,否則會(huì)自動(dòng)解析方向鍵以及后退鍵,并且直到按下回車read方法才會(huì)返回 
  6.         String[] cmd = { "/bin/sh""-c""stty raw </dev/tty" }; 
  7.         Runtime.getRuntime() 
  8.                .exec(cmd) 
  9.                .waitFor(); 
  10.         while (true) { 
  11.             String input = ""
  12.             int index = 0; 
  13.             while (true) { 
  14.                 char ch = (char) System.in.read(); 
  15.                 if (ch == 3) { 
  16.                     // CTRL-C 
  17.                     return
  18.                 } 
  19.                 else if (ch >= 32 && ch <= 126) { 
  20.                     // 普通字符 
  21.                     input = input.substring(0, index) + ch + input.substring(index, input.length()); 
  22.                     index++; 
  23.                 } 
  24.                 else if (ch == 10 || ch == 13) { 
  25.                     // 回車 
  26.                     System.out.println(); 
  27.                     System.out.print("\u001b[1000D"); 
  28.                     System.out.println("echo: " + input); 
  29.                     input = ""
  30.                     index = 0; 
  31.                 } 
  32.                 else if (ch == 27) { 
  33.                     // 左右方向鍵 
  34.                     char next1 = (char) System.in.read(); 
  35.                     char next2 = (char) System.in.read(); 
  36.                     if (next1 == 91) { 
  37.                         if (next2 == 68) { 
  38.                             // 左方向鍵 
  39.                             index = Math.max(0, index - 1); 
  40.                         } 
  41.                         else if (next2 == 67) { 
  42.                             // 右方向鍵 
  43.                             index = Math.min(input.length(), index + 1); 
  44.                         } 
  45.                     } 
  46.                 } 
  47.                 else if (ch == 127) { 
  48.                     // 刪除 
  49.                     if (index > 0) { 
  50.                         input = input.substring(0, index - 1) + input.substring(index, input.length()); 
  51.                         index -= 1; 
  52.                     } 
  53.                 } 
  54.                 System.out.print("\u001b[1000D"); // 將光標(biāo)移動(dòng)到最左側(cè) 
  55.                 System.out.print("\u001b[0K"); // 清除光標(biāo)所在行的全部?jī)?nèi)容 
  56.                 System.out.print(input); 
  57.                 System.out.print("\u001b[1000D"); // 再次將光標(biāo)移動(dòng)到最左側(cè) 
  58.                 if (index > 0) { 
  59.                     System.out.print("\u001b[" + index + "C"); // 將光標(biāo)移動(dòng)到index處 
  60.                 } 
  61.                 System.out.flush(); 
  62.             } 
  63.         } 
  64.     } 

讓我們來(lái)看一下效果: 

震驚!原來(lái)命令行還可以這么玩?!

OK,成功了!那么至此為止,我們已經(jīng)實(shí)現(xiàn)了一個(gè)最小化的命令行,它能夠支持用戶進(jìn)行輸入,并且能夠左右移動(dòng)光標(biāo)以及刪除他不想要的字符。但是它還缺失了很多命令行的特性,比如不支持解析像 Alt-f 、 Ctrl-r 等常見(jiàn)的 快捷鍵 ,也不支持輸入U(xiǎn)nicode字符等等。但是,只要我們掌握了剛才的知識(shí),這些特性都可以方便地實(shí)現(xiàn)。比如,我們可以給剛才的命令行加上簡(jiǎn)單的語(yǔ)法高亮——末尾如果有多余的空格則將這些空格標(biāo)紅,效果如下: 

實(shí)現(xiàn)的代碼也很簡(jiǎn)單,可以參考Github項(xiàng)目里的CustomisedCommandLine類。

最后,再介紹一下其他一些有用的Ansi Escape Codes:

  • 光標(biāo)向上移動(dòng): \u001b[{n}A 將光標(biāo)向上移動(dòng) n 格。
  • 光標(biāo)向下移動(dòng): \u001b[{n}B 將光標(biāo)向下移動(dòng) n 格。
  • 光標(biāo)向右移動(dòng): \u001b[{n}C 將光標(biāo)向右移動(dòng) n 格。
  • 光標(biāo)向左移動(dòng): \u001b[{n}D 將光標(biāo)向左移動(dòng) n 格。
  • 光標(biāo)按行向下移動(dòng): \u001b[{n}E 將光標(biāo)向下移動(dòng) n 行并且將光標(biāo)移至行首。
  • 光標(biāo)按行向上移動(dòng): \u001b[{n}F 將光標(biāo)向上移動(dòng) n 行并且將光標(biāo)移至行首。
  • 設(shè)置光標(biāo)所在列: \u001b[{n}G 將光標(biāo)移至第 n 列(行數(shù)與當(dāng)前所在行保持一致)。
  • 設(shè)置光標(biāo)所在位置: \u001b[{n};{m}H 將光標(biāo)移至第 n 行 m 列,坐標(biāo)原點(diǎn)從屏幕左上角開(kāi)始。
  • 保存光標(biāo)當(dāng)前所在位置: \u001b[{s} 。
  • 讀取光標(biāo)上一次保存的位置: \u001b[{u} 。

光標(biāo)按行移動(dòng)的測(cè)試代碼參考Github項(xiàng)目里的LineMovementTest類,設(shè)置光標(biāo)位置的測(cè)試代碼參考Github項(xiàng)目里的PositionTest類。如果想了解更多的Ansi Escape Codes請(qǐng)參考 維基百科 。

責(zé)任編輯:未麗燕 來(lái)源: 夜有所思,日有所夢(mèng)
相關(guān)推薦

2022-12-06 17:30:04

2025-08-18 07:35:40

2016-12-02 20:43:28

Android

2024-03-12 08:44:56

WebWorkerTypeScript語(yǔ)法

2018-10-28 17:54:00

分布式事務(wù)數(shù)據(jù)

2017-11-06 19:09:45

在線抓娃娃機(jī)

2015-08-12 16:32:34

華為/物聯(lián)網(wǎng)

2022-07-29 16:50:30

網(wǎng)絡(luò)帶寬

2017-10-28 23:13:43

微服務(wù)架構(gòu)開(kāi)發(fā)單體應(yīng)用

2023-10-11 08:16:42

客戶端服務(wù)器內(nèi)容

2020-07-21 18:54:21

Rust類型轉(zhuǎn)換語(yǔ)言

2016-09-29 17:48:32

騰訊云語(yǔ)音質(zhì)檢珍愛(ài)網(wǎng)

2023-03-01 11:35:45

2016-12-26 09:50:15

2017-09-27 14:57:44

IOS 11Siri蘋果

2022-01-04 08:00:48

前端技術(shù)Esbuild

2021-02-07 08:13:18

@DateTimeFo@NumberFormSpring

2013-09-18 10:44:01

搜狗輸入法詞語(yǔ)

2010-06-21 15:51:29

Linux命令Google服務(wù)

2025-09-05 07:42:19

Spring接口監(jiān)控
點(diǎn)贊
收藏

51CTO技術(shù)棧公眾號(hào)

欧美日本在线观看| 成人性生交大片免费看中文| 亚洲天堂第二页| 2025韩国理伦片在线观看| 午夜伦理在线| 成a人片亚洲日本久久| 国产不卡av在线免费观看| 国产毛片欧美毛片久久久| 麻豆国产一区| 欧美日韩综合视频| 新呦u视频一区二区| www.黄色国产| 久久一区国产| 欧美国产在线视频| 手机看片福利视频| 高清精品视频| 91精品国产一区二区人妖| 亚欧无线一线二线三线区别| 免费在线观看av片| 91色porny在线视频| 91色琪琪电影亚洲精品久久| 黄色片网站在线免费观看| 在线中文字幕第一区| 亚洲免费影视第一页| 亚洲欧美日韩中文字幕在线观看| 欧美日韩123区| 亚洲精品成人a在线观看| 欧美久久电影| 北条麻妃一二三区| 久久国产综合精品| 日本精品久久久久久久| 精品无码免费视频| 91精品久久久久久久久久不卡| 亚洲欧美激情精品一区二区| 精品人妻在线视频| 欧美日韩午夜电影网| 欧美在线观看视频一区二区 | 欧美日韩精品国产| 路边理发店露脸熟妇泻火| 国产精品一区二区婷婷| 99在线热播精品免费| 91在线网站视频| 中文字幕日本人妻久久久免费| 国产亚洲在线观看| 国模吧一区二区三区| 强行糟蹋人妻hd中文| 久久综合电影| 中文字幕日韩电影| 日本一级免费视频| 免费毛片在线不卡| 精品一区二区三区四区| 日本xxxx裸体xxxx| 欧美交a欧美精品喷水| 精品999在线播放| 精品久久久久久无码人妻| 欧美特黄不卡| 日韩欧美国产成人一区二区| 亚洲精品乱码久久久久久动漫| 精品自拍视频| 欧美日产在线观看| 亚洲高清av一区二区三区| 91精品视频一区二区| 69av一区二区三区| 99久久综合网| 日韩三级久久| 精品久久久久香蕉网| 国产婷婷在线观看| 嫩草影视亚洲| 中文字幕欧美精品在线| 国精产品视频一二二区| 999视频精品| 欧美裸体xxxx极品少妇| 久草网在线观看| 亚洲精品极品| 国产999精品久久久| 成人一级免费视频| 精品中文字幕一区二区小辣椒| 91日本在线视频| 亚洲成人精品女人久久久| 成人av网站在线| 久久伦理网站| 淫片在线观看| 中文字幕在线一区| 国产免费裸体视频| 中文日产幕无线码一区二区| 欧美性三三影院| www.国产福利| 青青草久久爱| 最近2019免费中文字幕视频三| 在线看的片片片免费| 在线成人国产| 国产精品99久久久久久白浆小说| 国产又粗又长又黄| 成人av资源在线| 午夜欧美性电影| 男男gaygays亚洲| 色综合久久久久网| 免费看三级黄色片| 激情婷婷综合| 欧美精品久久久久久久久| www.av88| 成人h版在线观看| 亚洲免费在线精品一区| 黄色美女视频在线观看| 欧美午夜宅男影院| 国产51自产区| 91精品一区二区三区综合| 韩国美女主播一区| 怡春院在线视频| 不卡的av中国片| 在线视频亚洲自拍| 中文在线8资源库| 日韩亚洲国产中文字幕欧美| 人妻少妇无码精品视频区| 欧美日韩亚洲国产精品| 国产精品中文字幕久久久| 欧美一区二区三区成人片在线| 国产精品沙发午睡系列990531| 成人一对一视频| 美女精品久久| 色偷偷91综合久久噜噜| 高清乱码免费看污| 成人高清视频免费观看| 国内自拍中文字幕| 九九热这里有精品| 在线成人激情视频| 国产成人精品网| 97精品久久久午夜一区二区三区| 欧美日韩dvd| 亚洲精品69| 在线电影欧美日韩一区二区私密| 国产性猛交╳xxx乱大交| 国产99精品视频| 懂色av一区二区三区四区五区| 欧美国产日韩电影| 亚洲日本成人女熟在线观看| 欧美三级一区二区三区| 成人一区二区三区视频| www.一区二区.com| 欧美欧美在线| 欧美大成色www永久网站婷| 一级黄色大片免费| 国产三区在线成人av| 国产极品美女高潮无套久久久| 日韩欧美美女在线观看| 97精品在线观看| 秋霞网一区二区| 亚洲成a人v欧美综合天堂| 久久久久久久久久毛片| 欧美成人久久| 亚洲综合在线小说| 色在线视频网| 亚洲成av人片在线观看香蕉| 久久精品久久精品久久| 成人av片在线观看| 91av资源网| 国产最新精品| 国产视频福利一区| 国产色在线观看| 日韩亚洲欧美成人一区| 国产精品30p| 91丨porny丨蝌蚪视频| 欧洲av无码放荡人妇网站| 自拍偷拍欧美一区| 国产精品久久久久久av福利| 超碰免费在线观看| 欧美日韩二区三区| 国产女人被狂躁到高潮小说| 成人听书哪个软件好| 久久久999视频| 精品视频免费在线观看| 成人久久久久久| 尤物yw193can在线观看| 日韩美女av在线| 日本丰满少妇做爰爽爽| 亚洲特黄一级片| 中文成人无字幕乱码精品区| 老司机久久99久久精品播放免费| 一区二区91美女张开腿让人桶| 在线视频成人| 午夜精品久久久久久久99热 | 久久精品国产v日韩v亚洲| 国产精品久久久久久在线| 一级中文字幕一区二区| 精品少妇一区二区三区免费观| 男女性色大片免费观看一区二区 | 国产精品毛片在线看| 日韩精品一区二区三区四区五区 | 九色91在线视频| 日本精品另类| 欧美日韩国产成人| 国产精品一区二区婷婷| 日韩欧美亚洲国产另类| 香蕉污视频在线观看| 一区二区三区四区中文字幕| 男人天堂av电影| 国产精品自在欧美一区| 久久久久久久久久福利| 欧美成人一品| 色噜噜狠狠色综合网| www.成人网| 国产精品入口免费视频一| 天天干在线视频论坛| 亚洲视频在线视频| 人妻精品一区二区三区| 欧美日韩一级视频| www成人在线| 一级做a爱片久久| 四虎国产成人精品免费一女五男| av中文字幕在线不卡| 亚洲精品成人在线播放| 男人的天堂亚洲在线| 黄色成人在线免费观看| 色综合久久网| 欧美高清视频一区二区三区在线观看| 国产美女视频一区二区| 国产成人精品免高潮在线观看 | 四虎影视精品成人| 欧美二区在线观看| 波多野结衣在线观看视频| 欧美日韩黄色大片| 免费网站看av| 亚洲欧美日韩综合aⅴ视频| 欧美丰满美乳xxⅹ高潮www| caoporn国产精品| 精品国产一二区| 国产制服丝袜一区| 国产喷水theporn| 久热精品在线| 国产免费成人在线| 日韩午夜av| 丝袜人妻一区二区三区| 亚洲天堂久久| 少妇大叫太大太粗太爽了a片小说| 亚洲成人最新网站| 一级二级三级欧美| 欧美hd在线| 一级日韩一区在线观看| 久久精品高清| 在线精品日韩| 婷婷综合久久| 中国成人在线视频| 日韩精品永久网址| 亚洲一区不卡在线| 青青草国产成人a∨下载安卓| 日韩欧美视频一区二区三区四区 | 成人国产精品免费| 无码人妻一区二区三区一| 国产精品一卡二卡在线观看| 美女被艹视频网站| 国产精品一区二区三区四区| 国产精品一级无码| 国产成人免费视频网站| 亚洲黄色小说在线观看| 成人福利视频网站| 成人免费毛片日本片视频| 99久久精品国产导航| 蜜桃传媒一区二区亚洲av| 91影院在线观看| 美女洗澡无遮挡| 国产精品久久毛片a| 紧身裙女教师波多野结衣| 综合久久久久久| 免费在线观看av网址| 天天综合天天做天天综合| 中文字字幕在线中文| 在线看国产一区二区| 91精品国产乱码久久久久| 欧美一级高清片在线观看| 亚洲欧美高清视频| 亚洲精品720p| 国产视频福利在线| 久久艳片www.17c.com| 男人天堂亚洲天堂| 青青久久av北条麻妃黑人| 最新日韩一区| 国产二区不卡| 国产在视频线精品视频www666| 亚洲精品免费在线看| 欧美日韩一区自拍 | 久久久久亚洲av无码专区| 亚洲一区二区三区在线播放 | 欧美精品日韩一本| 成人毛片在线精品国产| 亚洲人成自拍网站| 日本视频不卡| 国外视频精品毛片| 日韩精品一页| 精品无码久久久久国产| 国产精品99一区二区三区| 国产精品一色哟哟| 奇米四色…亚洲| 欧美激情一区二区三区p站| 国产欧美一二三区| 精品无码久久久久久久久| 欧美午夜理伦三级在线观看| 欧美熟女一区二区| 在线观看精品自拍私拍| 蜜桃视频www网站在线观看| 91久久久久久| 男男gay无套免费视频欧美| 亚洲色图都市激情| 日韩电影免费在线| 黄色网址在线视频| 亚洲精品第1页| 中文字幕第一页在线播放| 精品国产91乱码一区二区三区| 在线观看黄色av| 欧美亚洲免费电影| 日韩中文字幕一区二区高清99| 日本视频一区二区不卡| 亚洲国产精品一区| www.日本久久| 国产精品午夜久久| 国产www在线| 精品国产91洋老外米糕| jizzjizz亚洲| 国产日韩欧美日韩大片| 视频一区欧美| 欧美色图另类小说| 成人精品免费看| 九九热最新地址| 欧美精品777| 91社区在线| 国产精品久久久久久av| 岳的好大精品一区二区三区| 2019日韩中文字幕mv| 国产精品一区二区久久精品爱涩| 免费黄色激情视频| 欧美三区在线视频| 青青九九免费视频在线| 欧美亚洲日本网站| 欧美大奶一区二区| 一二三四视频社区在线| 成人一区在线观看| 国产在线视频卡一卡二| 日韩欧美国产一二三区| 18+激情视频在线| 亚洲影视中文字幕| 亚洲成人三区| 精品人妻人人做人人爽夜夜爽| 一区二区三区成人| 超碰人人人人人人| 久久99精品久久久久久琪琪| 一区二区在线视频观看| 欧美大黑帍在线播放| 成人小视频在线| 国产乡下妇女做爰视频| 日韩av中文字幕在线免费观看| а√在线中文在线新版| 久久久久久久久久码影片| 亚洲在线视频| 欧美多人猛交狂配| 精品视频免费在线| www视频在线免费观看| 91在线在线观看| 亚洲国产片色| 中国美女乱淫免费看视频| 在线视频你懂得一区二区三区| 成年在线电影| 成人午夜一级二级三级| 欧美三级视频| 国产乱了高清露脸对白| 欧美午夜丰满在线18影院| 国产视频在线看| 91视频免费在线| 激情五月***国产精品| 久久精品成人av| 欧美精品一级二级三级| 亚洲综合图区| 欧美激情一区二区三区在线视频 | 青青影院在线观看| 91久久精品国产| 亚洲国产激情| 中文字幕在线观看二区| 欧美成人一区二区| 在线免费日韩片| 中文字幕一区二区三区有限公司| 国产宾馆实践打屁股91| 日韩人妻精品中文字幕| 日韩中文字幕在线视频| 丁香五月缴情综合网| 日本成人黄色网| 一区二区欧美在线观看| 精品一二三区视频| 91精品国产高清久久久久久91裸体| 一本久久综合| 免费在线观看黄色小视频| 亚洲国产精品字幕| 色婷婷成人网| 成人在线免费观看av| 成人欧美一区二区三区白人| 三级视频在线看| 成人网在线免费观看| 一区二区福利| 性欧美疯狂猛交69hd| 亚洲毛茸茸少妇高潮呻吟| 久久69av| 人人干人人干人人| 无码av中文一区二区三区桃花岛|