在這一章中,我們將會看一下表面上看來很瑣碎的細節-shell 提示符。但這會揭示一些內部 shell 和 終端仿真器的工作方式。
和 Linux 內的許多程序一樣,shell 提示符是可高度配置的,雖然我們把它相當多地看作是理所當然的, 但是我們一旦學會了怎樣控制它,shell 提示符是一個真正有用的設備。
## 解剖一個提示符
我們默認的提示符看起來像這樣:
~~~
[me@linuxbox ~]$
~~~
注意它包含我們的用戶名,主機名和當前工作目錄,但是它又是怎樣得到這些東西的呢? 結果證明非常簡單。提示符是由一個環境變量定義的,叫做 PS1(是“prompt string one” 的簡寫)。我們可以通過 echo 命令來查看 PS1的內容。
~~~
[me@linuxbox ~]$ echo $PS1
[\u@\h \W]\$
~~~
* * *
注意:如果你 shell 提示符的內容和上例不是一模一樣,也不必擔心。每個 Linux 發行版 定義的提示符稍微有點不同,其中一些相當異乎尋常。
* * *
從輸出結果中,我們看到那個 PS1 環境變量包含一些這樣的字符,比方說中括號,@符號,和美元符號, 但是剩余部分就是個謎。我們中一些機敏的人會把這些看作是由反斜杠轉義的特殊字符,就像我們 在第八章中看到的一樣。這里是一部分字符列表,在提示符中 shell 會特殊對待這些字符:
表14-1: Shell 提示符中用到的轉義字符
| 序列 | 顯示值 |
|------|--------|
| \a | 以 ASCII 格式編碼的鈴聲 . 當遇到這個轉義序列時,計算機會發出嗡嗡的響聲。 |
| \d | 以日,月,天格式來表示當前日期。例如,“Mon May 26.” |
| \h | 本地機的主機名,但不帶末尾的域名。 |
| \H | 完整的主機名。 |
| \j | 運行在當前 shell 會話中的工作數。 |
| \l | 當前終端設備名。 |
| \n | 一個換行符。 |
| \r | 一個回車符。 |
| \s | shell 程序名。 |
| \t | 以24小時制,hours:minutes:seconds 的格式表示當前時間. |
| \T | 以12小時制表示當前時間。 |
| \@ | 以12小時制,AM/PM 格式來表示當前時間。 |
| \A | 以24小時制,hours:minutes 格式表示當前時間。 |
| \u | 當前用戶名。 |
| \v | shell 程序的版本號。 |
| \V | Version and release numbers of the shell. |
| \w | 當前工作目錄名。 |
| \W | 當前工作目錄名的最后部分。 |
| \! | 當前命令的歷史號。 |
| \# | 當前 shell 會話中的命令數。 |
| \$ | 這會顯示一個"$"字符,除非你擁有超級用戶權限。在那種情況下, 它會顯示一個"#"字符。 |
| \[ | 標志著一系列一個或多個非打印字符的開始。這被用來嵌入非打印 的控制字符,這些字符以某種方式來操作終端仿真器,比方說移動光標或者是更改文本顏色。 |
| \] | 標志著非打印字符序列結束。 |
## 試試一些可替代的提示符設計
參照這個特殊字符列表,我們可以更改提示符來看一下效果。首先, 我們把原來提示符字符串的內容備份一下,以備之后恢復原貌。為了完成備份, 我們把已有的字符串復制到另一個 shell 變量中,這個變量是我們自己創造的。
~~~
[me@linuxbox ~]$ ps1_old="$PS1"
~~~
我們新創建了一個叫做 ps1_old 的變量,并把變量 PS1的值賦 ps1_old。通過 echo 命令可以證明 我們的確復制了 PS1的值。
~~~
[me@linuxbox ~]$ echo $ps1_old
[\u@\h \W]\$
~~~
在終端會話中,我們能在任一時間復原提示符,只要簡單地反向操作就可以了。
~~~
[me@linuxbox ~]$ PS1="$ps1_old"
~~~
現在,我們準備開始,讓我們看看如果有一個空的字符串會發生什么:
~~~
[me@linuxbox ~]$ PS1=
~~~
如果我們沒有給提示字符串賦值,那么我們什么也得不到。根本沒有提示字符串!提示符仍然在那里, 但是什么也不顯示,正如我們所要求的那樣。我們將用一個最小的提示符來代替它:
~~~
PS1="\$ "
~~~
這樣要好一些。至少能看到我們在做什么。注意雙引號中末尾的空格。當提示符顯示的時候, 這個空格把美元符號和光標分離開。
在提示符中添加一個響鈴:
~~~
$ PS1="\a\$ "
~~~
現在每次提示符顯示的時候,我們應該能聽到嗡嗡聲。這會變得很煩人,但是它可能會 很有用,特別是當一個需要運行很長時間的命令執行完后,我們要得到通知。
下一步,讓我們試著創建一個信息豐富的提示符,包含主機名和當天時間的信息。
~~~
$ PS1="\A \h \$ "
17:33 linuxbox $
~~~
試試其他上表中列出的轉義序列,看看你能否想出精彩的新提示符。
## 添加顏色
大多數終端仿真器程序支持一定的非打印字符序列來控制,比方說字符屬性(像顏色,黑體和可怕的閃爍) 和光標位置。我們會更深入地討論光標位置,但首先我們要看一下字體顏色。
> 混亂的終端時代
>
> 回溯到終端連接到遠端計算機的時代,有許多競爭的終端品牌,它們各自工作不同。 它們有著不同的鍵盤,以不同的方式來解釋控制信息。Unix 和類 Unix 的系統有兩個 相當復雜的子系統來處理終端控制領域的混亂局面(稱為 termcap 和 terminfo)。如果你 查看一下終端仿真器最底層的屬性設置,可能會找到一個關于終端仿真器類型的設置。
>
> 為了努力使所有的終端都講某種通用語言,美國國家標準委員會(ANSI)制定了 一套標準的字符序列集合來控制視頻終端。原先 DOS 用戶會記得 ANSI.SYS 文件, 這是一個用來使這些編碼解釋生效的文件。
字符顏色是由發送到終端仿真器的一個嵌入到了要顯示的字符流中的 ANSI 轉義編碼來控制的。 這個控制編碼不會“打印”到屏幕上,而是被終端解釋為一個指令。正如我們在上表看到的字符序列, 這個 [ 和 ] 序列被用來封裝這些非打印字符。一個 ANSI 轉義編碼以一個八進制033(這個編碼是由 退出按鍵產生的)開頭,其后跟著一個可選的字符屬性,在之后是一個指令。例如,把文本顏色 設為正常(attribute = 0),黑色文本的編碼如下:
~~~
\033[0;30m
~~~
這里是一個可用的文本顏色列表。注意這些顏色被分為兩組,由應用程序粗體字符屬性(1) 分化開來,這個屬性可以描繪出“淺”色文本。
表14-2: 用轉義序列來設置文本顏色
| 序列 | 文本顏色 | 序列 | 文本顏色 |
|------|--------|----------|-------|
| \033[0;30m | 黑色 | \033[1;30m | 深灰色 |
| \033[0;31m | 紅色 | \033[1;31m | 淺紅色 |
| \033[0;32m | 綠色 | \033[1;32m | 淺綠色 |
| \033[0;33m | 棕色 | \033[1;33m | 黃色 |
| \033[0;34m | 藍色 | \033[1;34m | 淺藍色 |
| \033[0;35m | 粉紅 | \033[1;35m | 淺粉色 |
| \033[0;36m | 青色 | \033[1;36m | 淺青色 |
| \033[0;37m | 淺灰色 | \033[1;37m | 白色 |
讓我們試著制作一個紅色提示符。我們將在開頭加入轉義編碼:
~~~
<me@linuxbox ~>$ PS1='\[\033[0;31m\]<\u@\h \W>\$'
<me@linuxbox ~>$
~~~
我們的提示符生效了,但是注意我們在提示符之后輸入的文本也是紅色的。為了修改這個問題, 我們將添加另一個轉義編碼到這個提示符的末尾來告訴終端仿真器恢復到原來的顏色。
~~~
<me@linuxbox ~>$ PS1='\[\033[0;31m\]<\u@\h \W>\$\[\033[0m\]'
<me@linuxbox ~>$
~~~
這看起來要好些!
也有可能要設置文本的背景顏色,使用下面列出的轉義編碼。這個背景顏色不支持黑體屬性。
表14-3: 用轉義序列來設置背景顏色
| 序列 | 文本顏色 | 序列 | 文本顏色 |
|------|--------|----------|-------|
| \033[0;40m | 藍色 | \033[1;44m | 黑色 |
| \033[0;41m | 紅色 | \033[1;45m | 粉紅 |
| \033[0;42m | 綠色 | \033[1;46m | 青色 |
| \033[0;43m | 棕色 | \033[1;47m | 淺灰色 |
我們可以創建一個帶有紅色背景的提示符,只是對第一個轉義編碼做個簡單的修改。
~~~
<me@linuxbox ~>$ PS1='\[\033[0;41m\]<\u@\h \W>\$\[\033[0m\] '
<me@linuxbox ~>$
~~~
試試這些顏色編碼,看看你能定制出怎樣的提示符!
* * *
注意:除了正常的 (0) 和黑體 (1) 字符屬性之外,文本也可以具有下劃線 (4),閃爍 (5), 和反向 (7) 屬性。為了擁有好品味,然而,許多終端仿真器拒絕使用這個閃爍屬性。
* * *
## 移動光標
轉義編碼也可以用來定位光標。這些編碼被普遍地用來,每次當提示符出現的時候,會在屏幕的不同位置 比如說上面一個角落,顯示一個時鐘或者其它一些信息。這里是一系列用來定位光標的轉義編碼:
表14-4: 光標移動轉義序列
| 轉義編碼 | 行動 |
|---------|-----------|
| \033[l;cH | 把光標移到第 l 行,第 c 列。 |
| \033[nA | 把光標向上移動 n 行。 |
| \033[nB | 把光標向下移動 n 行。 |
| \033[nC | 把光標向前移動 n 個字符。 |
| \033[nD | 把光標向后移動 n 個字符。 |
| \033[2J | 清空屏幕,把光標移到左上角(第零行,第零列)。 |
| \033[K | 清空從光標位置到當前行末的內容。 |
| \033[s | 存儲當前光標位置。 |
| \033[u | 喚醒之前存儲的光標位置。 |
使用上面的編碼,我們將構建一個提示符,每次當這個提示符出現的時候,會在屏幕的上方畫出一個 包含時鐘(由黃色文本渲染)的紅色長條。提示符的編碼就是這個看起來令人敬畏的字符串:
~~~
PS1='\[\033[s\033[0;0H\033[0;41m\033[K\033[1;33m\t\033[0m\033[u\]
<\u@\h \W>\$ '
~~~
讓我們分別看一下這個字符串的每一部分所表示的意思:
| 序列 | 行動 |
|---------|-----------|
| \[ | 開始一個非打印字符序列。其真正的目的是為了讓 bash 能夠正確地計算提示符的大小。如果沒有這個轉義字符的話,命令行編輯 功能會弄錯光標的位置。 |
| \033[s | 存儲光標位置。這個用來使光標能回到原來提示符的位置, 當長條和時鐘顯示到屏幕上方之后。當心一些 終端仿真器不推崇這個編碼。 |
| \033[0;0H | 把光標移到屏幕左上角,也就是第零行,第零列的位置。 |
| \033[0;41m | 把背景設置為紅色。 |
| \033[K | 清空從當前光標位置到行末的內容。因為現在 背景顏色是紅色,則被清空行背景成為紅色,以此來創建長條。注意雖然一直清空到行末, 但是不改變光標位置,它仍然在屏幕左上角。 |
| \033[1;33m | 把文本顏色設為黃色。 |
| \t | 顯示當前時間。雖然這是一個可“打印”的元素,但我們仍把它包含在提示符的非打印部分, 因為我們不想 bash 在計算可見提示符的真正大小時包括這個時鐘在內。 |
| \033[0m | 關閉顏色設置。這對文本和背景都起作用。 |
| \033[u | 恢復到之前保存過的光標位置處。 |
| \] | 結束非打印字符序列。 |
| \$ | 提示符字符串。 |
## 保存提示符
顯然地,我們不想總是敲入那個怪物,所以我們將要把這個提示符存儲在某個地方。通過把它 添加到我們的.bashrc 文件,可以使這個提示符永久存在。為了達到目的,把下面這兩行添加到.bashrc 文件中。
~~~
PS1='\[\033[s\033[0;0H\033[0;41m\033[K\033[1;33m\t\033[0m\033[u\]<\u@\h \W>\$ '
export PS1
~~~
## 總結歸納
不管你信不信,還有許多事情可以由提示符來完成,涉及到我們在這里沒有論及的 shell 函數和腳本, 但這是一個好的開始。并不是每個人都會花心思來更改提示符,因為通常默認的提示符就很讓人滿意。 但是對于我們這些喜歡思考的人們來說,shell 卻提供了許多制造瑣碎樂趣的機會。
## 拓展閱讀
* The Bash Prompt HOWTO 來自于 Linux 文檔工程,對 shell 提示符的用途進行了相當 完備的論述。可在以下鏈接中得到:
[http://tldp.org/HOWTO/Bash-Prompt-HOWTO/](http://tldp.org/HOWTO/Bash-Prompt-HOWTO/)
* Wikipedia 上有一篇關于 ANSI Escape Codes 的好文章:
[http://en.wikipedia.org/wiki/ANSI_escape_code](http://en.wikipedia.org/wiki/ANSI_escape_code)
- 第一章:引言
- 第二章:什么是shell
- 第三章:文件系統中跳轉
- 第四章:研究操作系統
- 第五章:操作文件和目錄
- 第六章:使用命令
- 第七章:重定向
- 第八章:從shell眼中看世界
- 第九章:鍵盤高級操作技巧
- 第十章:權限
- 第十一章:進程
- 第十二章:shell環境
- 第十三章:VI簡介
- 第十四章:自定制shell提示符
- 第十五章:軟件包管理
- 第十六章:存儲媒介
- 第十七章:網絡系統
- 第十八章:查找文件
- 第十九章:歸檔和備份
- 第二十章:正則表達式
- 第二十一章:文本處理
- 第二十二章:格式化輸出
- 第二十三章:打印
- 第二十四章:編譯程序
- 第二十五章:編寫第一個shell腳本
- 第二十六章:啟動一個項目
- 第二十七章:自頂向下設計
- 第二十八章:流程控制 if分支結構
- 第二十九章:讀取鍵盤輸入
- 第三十章:流程控制 while/until 循環
- 第三十一章:疑難排解
- 第三十二章:流程控制 case分支
- 第三十三章:位置參數
- 第三十四章:流程控制 for循環
- 第三十五章:字符串和數字
- 第三十六章:數組
- 第三十七章:奇珍異寶