學習Shell程式設計,需要對Shell有一些基本瞭解。本文從計算機發展歷史介紹各種Shell之間的變遷。到了開始學習Linux Shell程式設計時候,則要知道常見的Shell如bash/zsh的差異,以及Shell各種登陸方式。

1 從終端機(Terminal)說起

Shell 命令執行在終端中,談論 Shell 時一定離不開終端機。終端機的英文是 Terminal,也有翻譯為命令列的,這個翻譯很好地反映了工具的作用:接受命令輸入,展示命令執行過程,給出命令輸出。

常見作業系統中都有終端:OSX 系統有終端機,linux 系統有 gnome-terminal,Windows 系統有 Windows-Terminal。

mac Terminal

1.1 終端機—從硬體到軟體

要了終端機,需要了解一些計算機的發展歷史。馮諾伊曼計算機架構下,計算機分為輸入、出入、儲存、控制、計算部分。早期計算機都是龐然大物,這幾部分都是分開的。輸入輸出靠紙帶,讀取紙帶的打孔獲得輸入,計算機執行完成,往紙帶上打孔用以輸出。使用起來相當麻煩:當時優秀的程式設計師都是直接寫01程式碼的。

世界上第一臺計算機

世界上第一臺計算機

寫一個程式,就要消耗一堆紙帶,新修改程式重新執行,得重新打紙帶。就像用簽字筆在紙上寫字一樣,寫錯了就只能劃掉。(當然我猜測可能有修正帶這樣的東西。)這顯然不是正常人類幹出來的事,既不好讀也不好寫,更不用說除錯了。

隨著計算機的的發展,鍵盤成了計算機的輸入,顯示器成為計算機的輸出,這兩玩意組合到一起,通過電纜和主機連線到一起:鍵盤輸入,主機執行,顯示器顯示命令執行結果。這方式可以比紙帶高效,修改起來也更加方便,人機互動就容易了許多,比紙帶不知道高到哪裡去了。由是觀之,今有鍵盤俠,古無紙帶客,此言誠不我欺。

鍵盤顯示器這兩個玩意組成的裝置就是終端。它們其實並不算計算機的一部分(計算機的主要部分還是中間運算的部分),但有了它(指的是鍵盤和顯示器一起)之後,人類和計算機互動起來效率是大大地高。那麼是不是可以給計算機本身也設定一個這樣的裝置呢,計算機啟動的時候還可以輸出一些日誌,顯示狀態、報錯等。當然是可以的!這樣的“終端”就被稱為控制台(console)。通過這個特殊的終端,可以對整個計算機進行控制。

隨著技術進步,計算機進入積體電路時代,各種電路都塞到晶片中去了,電腦越做越小,微機越來越流行。

MOSFET

一種電晶體 MOSFET 的結構,圖片來自維基百科

以前的龐然大物現在都可以放到口袋裡面帶走了。鍵盤顯示器成了電腦的標配。反而那種只能和大型主機互動的裝置徹底走入歷史,硬體終端完全消失,取而代之的是軟體終端:在微機上用一套軟體模擬終端和主機進行互動。

1.2 鍵盤和 tty

在 Linux 中,開啟終端就會自動開啟一個 Shell(如 Bash),還會顯示tty

Last login: Mon Sep 20 10:34:38 on ttys006

tty 是什麼意思?這要從鍵盤說起。最開始鍵盤都是機械結構,直接將墨水印在紙上。它只是解決了資訊輸入的問題,而並沒有解決遠端輸出的問題,即把資訊從一個裝置傳送到另外一個裝置。

通訊技術在同步發展,此時已經有了電傳打字機(teletype/teletypewriter),能夠實現如下功能:

  1. 在本地通過鍵盤輸入一些資訊
  2. 輸入結果能夠在本地紙張上顯示
  3. 本地輸入結果能夠傳遞到別的裝置中,被別的裝置識別
  4. 能夠接受別的裝置的輸出,並且翻譯為人類可以讀的東西

這玩意就被稱為 tty,按照詞綴來翻譯就是遠端輸入或者遠端打字。翻譯為電傳打字的意思是說它傳遞的是電訊號。

二戰中使用的teletype

二戰中使用的 teletype圖片來自維基百科

在計算機出現之前,人們就已經建立起了以電傳打字機為節點的“網際網路”。這個“網際網路”,可以交換股票資料。計算機開始發展之後,人們也發現有和計算機互動的需求,於是就直接把 tty 拿過來了,作為計算機的輸入輸出部分。tty 是可以脫離計算執行的,它並不是計算機的一部分;現在的伺服器在沒有鍵盤顯示器的情況下也能執行。

某些情況下,又不得不依賴鍵盤顯示器操作。如果伺服器宕機了需要處理,就需要給伺服器連線顯示器和鍵盤進行診斷。此時這樣的終端也被看成是控制台(console),它的作用是輸出計算機的基本資訊,它屬於計算機的一部分。當然現在並不會明顯區分終端和控制台,它們都是和計算機互動的工具。

現在系統中還有tty命令,用於顯示當前終端:

tty
/dev/ttys009

在 Linux 中,可以執行Alt + Ctrl + F{2,6} 開啟各個 tty。究竟有多少個 tty 呢,檢視/dev/資料夾,看其中的tty檔案。在 linux 中,一切皆檔案。tty 不例外,也可以向 tty 寫入資訊:

echo "hello" >> /dev/tty1

通過who命令可以獲取所有的登陸的終端,echo內容到/dev/tty{n} 可以在對應終端上顯示。

通常 ssh 時候,執行tty獲得的資訊可能是pts,這是 pseudo terminal 的縮寫,表示偽終端。在核心中有兩部分,master side 和 tty 驅動中實現的 slave side。如果在 ssh 情況下輸入 tty,則會是/dev/pts,也可以通過往/dev/pts 中寫入資訊往終端傳輸資訊。

2 Shell 是什麼

前面說到了終端,通過它可以和計算機進行互動。實際上終端也不是直接去控制硬體,而是它和作業系統(Operator System)核心(Kernel)互動,讓作業系統核心去控制硬體。

核心

作業系統核心,圖片來自鳥哥的 linux 私房菜

設計作業系統這一中介的原因,本質是計算機系統太複雜,如果每執行一個操作就直接操作計算機硬體,那將複雜到無法完成。電子計算機主要依靠電晶體中電子運動形成的高低電壓,目前最新 CPU 已經能夠整合 10 億級別的電晶體,如果手工去操作,寫程式碼絕對要到地老天荒。

解決辦法是層層抽象,只要給作業系統發指令,剩下的事情就讓作業系統幫我們搞定。Shell 就是我們給作業系統傳送指令的一個通道。

廣義上任何能夠給作業系統傳送資訊實現一定功能的程式都是 Shell。如 Windows/MacOS 上各種軟體,Windows 檔案瀏覽器,Mac 上的 Finder,Linux/Unix 上的文字介面的 Shell 程式。狹義的 Shell 指的是 Linux/Unix 上的用 C 語言寫成的程式,如 Bash/Zsh。本文以 Bash/Zsh 為代表,介紹狹義 Shell 的使用。

2.1 使用 Shell 的理由

  1. 大部分伺服器執行 Linux/Unix作業系統,Shell 是原生的管理方式
  2. Shell 傳遞文字介面,對頻寬要求比較低
  3. 靈活使用 Shell,可以提升效率

3 Shell 登陸方式

Shell 有多種使用方式:

  1. 文字介面登陸 Linux 系統
  2. 在圖形介面直接開啟終端機
  3. 通過 ssh 遠端登陸伺服器
  4. 執行一段 Shell Script

需要對上面的場景進行一些分類:按照是否需要輸入密碼,可以分為登入式(login)和非登陸式(non-login);按照執行一個檔案還是執行一個命令,可以分為互動式(interactive)和非互動式(non-interactive)。

3.1 登陸與非登陸式

登陸式和非登陸式的區別在於啟動退出時候讀取的檔案不一樣。

3.1.1 登陸式

本地 Linux,進入系統後直接開啟 terminal,這種方式被稱為非登陸(non-login),因為不需要輸入賬戶和密碼。bash 有如下設定檔:

/ect/profile
~/.bash_profile
~/.bash_login
~/.profile
~/.bash_logout

對於 login 的 Shell,通常會讀取兩個檔案:

  1. /etc/profile,系統設定,一般不要修改
  2. ~/.bash_profile~/.bash_login~/.profile中的一個,讀到一個就停止

通常設定的卻是~/.bashrc檔案。其中rc是 run common 的意思。但~/.bashrc並不是上面 2 中任何檔案。奧秘就在~/.bash_profile中:

cat ~/.bashrc
# .bash_profile

# Get the aliases and functions
if [ -f ~/.bashrc ]; then
	. ~/.bashrc
fi

# User specific environment and startup programs

它直接就讀取~/.bashrc檔案了。

3.1.2 非登陸式

對於 non-login 的 Shell,它只讀取~/.bashrc這一個設定檔。

3.1.3 su切換時候的環境變數

在 Linux 中,通過su命令切換使用者。su 是 switch user 的意思。通常su後,只是切換使用者,並不會切換其他的。而執行su -之後,就會直接切換到 root 賬戶,連環境變數都會切換的。

3.2 互動式和非互動式

3.2.1 互動式執行

互動式(interactive)執行就是輸入一條命令,執行,輸出執行結果或者報錯。直接開啟命令列工具或 SSH 時候預設狀態。好處就是可以馬上執行,獲得結果或者報錯。後面第五部分會詳細介紹互動式 Shell 的設定。

3.2.2 非互動式

簡單來說就是執行 Shell Script。把要執行的命令按照順序寫到一個檔案裡,這個檔案就是 Shell Script,然後讓 Shell 呼叫這個指令碼,裡面的命令從上到下執行。Shell Script 的好處在於可以快速地複用命令,比如有如下場景:

  1. 同樣的命令需要執行 100 次,但是命令本身並不提供迴圈執行的參數,比如對 100 個檔案按照規則改名
  2. 執行的命令比較長,需要在前幾條修改一下參數,然後執行後面的命令獲取結果
  3. 在電腦 A 上執行的命令需要在電腦 B(或者更多電腦)上原樣執行一番

此時若採用互動式執行,則需要不停的敲鍵盤執行,和敲擊滑鼠無異,效率提升有限。執行檔案的方式就時非互動式(non-interactive),此時執行就簡化了許多。非互動式執行會開啟一個新的執行緒來執行 Shell 命令,當執行遇到EOF之後,整個執行緒就會退出。

4 Shell Script

在非互動式執行中,需要特別學習 Shell Script 的語法:藉助 Shell Script 的語法,還可以對命令進行擴充,如迴圈執行一條命令,如按照條件執行命令。Shell Script 程式設計的學習曲線有些陡峭:

  1. 空格十分敏感 在 Shell Script 中,等號(=)左右有無空格,表示完全不同的意思,但這樣的錯誤一開始是很難發現的
  2. 報錯過於簡單 如果遇到了空格問題,報錯不會提示空格缺失,而會說缺少命令
  3. 遇到錯誤後繼續執行 一般的程式語言是遇到錯誤後停止執行,shell script 跳過錯誤,繼續執行,前面不對的結果會影響後面的執行,如果不好好查 log,就不知道錯誤在哪裡了
  4. 缺乏方便除錯工具 除錯起來不是很方便,常常是在執行中除錯。執行一次,再除錯,環境就不一樣的。

5 zsh 設定

當然,互動式的 Shell 也是被廣泛使用的工具。

sh 是最古老的 Shell,目前已經不怎麼使用了。目前最流行版本是 Bash(orne Again Shell),但是 Bash 在沒有設定的情況下很難用。常見的 Shell 還有 Zsh、Fish、Ksh、Tcsh。一般比較推薦 Zsh,它相對於 Bash 而言,有更好的補全和跳轉功能。搭配上oh-my-zsh外掛,可以極大提升效率。zsh 安裝方式如下:

sudo dnf install zsh

安裝好 zsh 之後需要切換系統預設登陸 Shell 為 zsh:

sudo chsh -s /usr/bin/zsh user_name

設定oh-my-zsh

sh -c "$(curl -fsSL https://raw.github.com/ohmyzsh/ohmyzsh/master/tools/install.sh)"

設定oh-my-zsh外掛,找到~/.zshrc檔案,在plugins中新增:

plugins=(
   git
   extract
   zsh-syntax-highlighting
   zsh-autosuggestions
 )

總結

本文主要介紹 Shell 的基本概念,主要解決 Shell 使用之前的問題。先介紹了終端機(Terminal)、tty 等概念,然後介紹了 Shell 互動和非互動的執行方式,最後介紹了 Shell 互動執行時候 zsh 的使用方式,設定oh-my-zsh相關的設定。