--- title: 'Shell 類型、命令差異、Builtin 命令' disqus: kyleAlien --- Shell 類型、命令差異、Builtin 命令 === ## Overview of Content :::success * 如果喜歡讀更好看一點的網頁版本,可以到我新做的網站 [**DevTech Ascendancy Hub**](https://devtechascendancy.com/) 本篇文章對應的是 [**認識命令行:Shell 類型、命令差異 | Sub Shell 關係 | Builtin 命令**](https://devtechascendancy.com/command-line_shell-type_sub-shell_builtin/) ::: [TOC] ## 進入命令行 在 Linux 四大組件的圖型化桌面出現之前,要與系統通訊的手段就只有 Shell 命令行 (`CLI`, `Command line interface`),命令行大多是文本或是簡單的圖形 :::success * 如果喜歡讀更好看一點的網頁版本,可以到我新做的網站 [**DevTech Ascendancy Hub**](https://devtechascendancy.com/) 本篇文章對應的是 [**認識命令行:Shell 類型、命令差異 | Sub Shell 關係 | Builtin 命令**](https://devtechascendancy.com/command-line_shell-type_sub-shell_builtin/) ::: ### 控制台終端 * 進入控制台終端 CLI 的方式是一種讓 Linux 系統「**退出圖型化桌面**」模式,進入文本模式的模式(在 CLI 模式下只會有簡單的文本,沒有圖形介面);這種模式也稱為 **控制台終端** * Linux 發行版中,在系統啟動時就會啟動 5~6 個**虛擬控制台** 虛擬控制台終端是沒有 GUI(`out the GUI`) 的 CLI 終端(控制台終端) :::info * 那 **啞終端** 是什麼? 沒有 GUI 系統,只有簡單的通訊線、螢幕、鍵盤連接到 Unix 系統上,並與其通訊,透過下指令 & 查看輸出結果來達到目的 > 所以控制台中端可以算是啞終端的一種形式 ::: * Linux 系統在啟動時會創建多個 Linux 控制台終端,之後就會自動幫我們切換到圖形化終端介面(GUI CLI) ```mermaid graph TB 系統啟動 --> 控制台終端 --> 圖形化終端 subgraph 控制台終端 vc1(虛擬控制台 1) vc2(虛擬控制台 2) vc3(虛擬控制台 3) vc4(虛擬控制台 4) vc5(虛擬控制台 5) end ``` 1. **通常要切換回控制台終端可以用以下快捷鍵** > `Ctrl` + `Alt` + `F1~F7`(要試試看,在其中一個) > >  :::success * **`tty` 是啥**? 螢幕上見到的關鍵字 `tty` 代表的是 `teletypewriter` (電傳打字機) ,但不是每個 Linux 發行版上都會在登入介面上顯示虛擬控制台 tty 號 這 Unix 設備中看到的`tty/*`、`pts/*`、`tty` 都是終端設備,這種設備就是典型的 **非硬體設備,是透過記憶體模擬出來的設備**! > 就是軟體模擬出來的假設備(假硬體) ::: 2. 在圖形界面終端也可以透過指令來,進入文字模式的控制台,指令如下 ```shell= ## sudo chvt <終端台> ## 開啟一號控制台終端 sudo chvt 1 ``` ### 控制台終端 getty & login * `getty` 是一個 Linux 系統中的程序,它是一種用於終端的登錄程序。它通常在 Linux 系統啟動時被初始化,並負責為每個終端設備提供登錄界面 > "`getty`" 是 "`get tty`" 的縮寫,它接受用戶的用戶名和密碼,並驗證登錄憑據 * 它的主要作用是與終端設備進行通信,並提供用戶登錄的功能。一旦用戶成功登錄,`getty` 會將用戶的登錄會話交給 shell 進程處理 :::info * `getty` 是為控制台終端(純終端)服務,還是為圖形化終端服務? `getty` 是為控制台終端(純終端)服務,並**在控制台中呼叫 login 程式**,登入成功後才呼叫 Shell ::: > 在 Linux 系統中,getty 通常與其他程序(如 init 或 systemd)一起使用,以提供完整的登錄和會話管理功能 ### 圖形介面終端 * 透過 Linux 圖形化界面也可以進入終端;這種模式稱為 **終端仿真包**(模擬控制台終端);終端仿真包只是 Linux 圖形化介面其中一個元素 圖形化終端由以下解個重點部分組成 | 圖形化角色 | 常見的產品 | 簡介 | | - | - | - | | 客戶端(GUI Client) | 圖形化終端仿真、桌面環境、遊覽器 | 請求圖型化服務的應用 | | 顯示服務器(GUI Server) | Mir、Wayland Compositor、Xserver | 負責管理顯示、輸入設備(鍵盤滑鼠觸控... 等等) | | 窗口管理器 | Compiz、Metacity、Kwin | 為每個窗口建立邊框、拖曳... 等等基本功能 | | 部件 | Athenal(Xaw)、X Intrinsics | 客戶端添加菜單 & 外部功能 | :::info * 圖形化終端仿真器:in the GUI 的 CLI 終端(圖形化終端) ::: > Ubuntu 圖形化終端介面 (下圖黑色視窗) > >  ## Shell 類型 不同種類型的 Shell 會有不同特色(或是使用限制、規範) 在腳本中會宣告在最前面(`#!` 符號,又稱為 `shenang`),用來說明該腳本要使用哪個 Shell 解釋 ```shell= #!/bin/bash ```` ### 常見的 Shell 解釋器 * 透過 `/etc/passwd` 可以知道每一個用戶的 shell 類型,不同的用戶類型可能會使用不同的 Shell 解釋器(別的小節會說明),而常見的 Shell 解釋器就是 bash 當然,**除了 bash 之外,還有另外幾種常見的 bash**,如下表所示 | Shell 類型 | 特性 | 補充 | | -------- | -------- | - | | Bourne shell | Unix 版本的 Shell | - | | bash shell | GNU 模仿 Bourne shell 的產品,這是最常見的 Shell (默認 Shell) | - | | ash | 運行在內存受限的輕量級 Shell (與 bash shell 相容) | Debian 版本 | | korn | 支持浮點數、高級編程特性 (與 Bourne shell 相容) | - | | tcsh | 擁有 C 語言特性的 Shell | CentOS 使用,來源於最初的 C shell;CentOS 中是軟連結,連接 tcsh | | zsh | 結合多個 Shell 特性(bash shell, korn, tcsh...) | - | ## 近一步認識 Shell shell 的意思是命令行界面(也可以理解為 shell 是執行命令行的應用程式) > Unix 系統中有很多重要的部份其實都是 shell 腳本 :::info * Unix 中的 Shell 有為很多種,它們都是 **基於 Bourne shell (`/bin/sh`)**,這是貝爾實驗室開發的標準 Shell * Linux 常用的則是 Bourne again shell,也就是 Bash shell ::: ### 你的帳戶使用哪個 Shell? * 查看 `/etc/passwd` 文件,並找到自己的帳戶名稱,查看最後一個欄位,該欄位就是這個帳戶預設使用的 Shell ```shell= tail /etc/passwd ``` >  ### 查看 Bash 指令:man 手冊 / info / help * `man` 指令用來查看 Linux 系統上的手冊,其格式如下 ```shell= ## 格式 man <command> ## 查看 man 這個指令的操作手冊 man man ``` > 按下 `q` 就會退出說明手冊 > >  :::info man 手冊的 `DESCRIPTION` 部份是很好的介紹,使用新指令時最好讀一下 ::: * **man 指令常用 options** | options | 說明 | | - | - | | -k | 使用關鍵字找尋對應指令 | ```shell= ## 查看 hostname 相關指令 man -k hostname ``` >  可以看到列出的列表中,後面有一個括號,中間對應的數字代表了這個命令在 man 手冊中的哪個位置(區);**不同數字可以概述該指令的是屬於哪個部份的功能**,區對應的號碼、功能如下表所示 | 區域號 | 說明 | | - | - | | 1 | 可執行程式 or shell | | 2 | 系統調用 | | 3 | Library 調用 | | 4 | 特殊文件 | | 5 | 文件格式與約定 | | 6 | 遊戲 | | 7 | 概覽、約定與雜項 | | 8 | 超級用戶、系統管理員命令 | | 9 | 內核歷程 | ```shell= ## 進入區號 5 的說明 man 5 hostname ``` >  * 其他兩個 `info`, `help` 指令也可以拿來查看指令的操作 ```shell= ## info <指令> info hostname ## <指令> --help hostname --help ``` :::info * 並非每一個命令都會有 `man`、`info`、`help` 功能,需要查詢的時候可以都試試看 ::: ### 認識指令錯誤訊息 * 當指令出錯時,Shell 會回覆一個「**錯誤訊息**」,而這個些錯訊息也保含了重要資訊;以下是一個標準錯誤訊息 ```shell= # 錯誤訊息 ls: cannot access /qq123: No such file or directory ``` > 錯誤訊息以 `:` 符號個別分開,說明如下表所示 | 錯誤訊息 | 說明 | 說明 | | -------- | -------- | -------- | | `ls` | 錯誤命令 | ls 這個指令執行時發生錯誤 | | `cannot access /qq123` | 檔案路徑 | 無法訪問 `/qq123` 這個檔案 | | `No such file or directory` | 具體錯誤訊息 | 沒有這個檔案或是資料夾 | :::warning * **「警告訊息」、「錯誤訊息」一樣嗎**? 「警告訊息」、「錯誤訊息」是兩種不同的訊息;出現警告訊息程式仍可運作(只是提示你哪裡出現問題),但出現錯誤訊息時指令就會停止 ::: * 其中常見的**具體錯誤訊息**如下表 | 錯誤訊息 | 說明 | 補充 | | -------- | -------- | -------- | | `No such file or directory` | 存取不存在的檔案或目錄 | Unix I/O 系統對檔案、目錄不做區分 | | `File exists` | 創建檔案或目錄,但已有相同名稱的存在 | - | | `Not a directory, Is a directory` | 錯把目錄當成檔案來存取操作 | - | | `No space left on device` | 硬碟空間不存 | - | | `Permission denied` | 權限錯誤 | - | | `Operation not permitted` | 當我們試圖中止一個無權中止的程序時 | - | | `Segmentation fault, Bus error` | 程式嘗試存取無權存取的記憶體空間時,系統會中止該操作 | `Segmentation fault` 程式異常、**`Bus error` 代表存取記憶體有問題** | ### Shell 標準輸入輸出 * Unix 程序使用 I/O 資料流來讀寫資料,輸入流可以是從檔案、設備、終端... 等等來源,而 輸出流也可以將數據輸出到檔案、設備、終端... 等目的 * **標準輸入(`Standard input`)**: 可以透過 cat 命令來測試,輸入 cat 命令後(並不指定讀取檔案),它就會讀取使用者的資料數據輸入,這樣 cat 命令就變成了標準輸入 >  * **標準輸出(`Standard output`)**: 核心會為每個應用程序提供一個標準輸出流,讓他們輸出資料(並不一定會輸出到螢幕上,也可以輸出到裝置、文件) ## 命令的差異:內、外命令 內建、外部命令的操作方式不同,可以透過 `type` 判斷命令是內部還是外部的 ### builtin 內建命令:直接啟動 * 內建命令 **++不需要++ 啟動子進程就可以運行內建命令**,我們同樣可以使用 `type` 命令去判斷指定的命令是否是內建命令(內建命令會顯示 `builtin`) ```shell= type cd type exit ``` >  :::info * 命令可能會重複(內部、外部使用同一個命令),這時就可以使用 `type -a <命令>` ```shell= ## 查看所有的 echo 命令 type -a echo ``` > 如果需要指定某個命令,那就需要寫出那個命令的具體位置;(eg. `/bin/pwd`) > >  ::: ### 外部命令:另外啟動 * 外部命令在執行時會 **產生一個「新的子進程」去運行外部命令**,這個行為稱為 **`forking`**,透過複製當前進程來執行新命令 ```shell= ## 當前進程啟動另一個子進程 ## 子進程運行 `ps -f ps -f ``` 從下圖中,我們可以看到執行 `ps -f` 命令後 PID 為 `171809`,與本來 Bash 的 PID 不同(原來 Bash 的 PID 為 `169156`) ```mermaid graph LR b1(Bash 169156) --> |複製進程| forking --> b2(Bash 171809) --> |執行命令| ps(ps -f) ps -.-> |進程結束| b1 ``` >  * 外部命令也被稱為 **文件系統命令**(存於 bash shell 之外的命令),**外部命令通常放至於 `bin`、`/usr/bin`、`sbin`、`/usr/sbin` 資料夾中** **外部令令可以透過 `which`(外部命令)、`type`(內部命令)命令** 找到對應位置 ```shell= which ps type ps ``` >  ## 多 shell 關係 在使用 Shell 時會有父子概念(也可以想成階層概念),不同的階層會有可以相互訪問、資料是否可共享的特性,對於學習 Shell 時也是相當重要的概念 ### 啟動子 shell:Sub shell * 你可以透過一個已經啟動的 shell,在內部在啟動另外一個 shell,這個啟動的 shell 就是父子關係,概念圖如下 ```mermaid graph TB subgraph 父Shell 子Shell end ``` > 父子 shell 的 PID 就不同 (可以透過 `ps` 命令查看) ```shell= ## 啟動新 shell(子) shell ## 查看當前用戶啟動的進程 ps -f ``` >  上圖的父子 Shell 關係如下 (進程 ID 是 `PID`,其父進程 ID 就是 `PPID`) >  :::info * 可以透過 `ps --forest` 命令看到簡單的父子關係「**圖形、階層輸出**」;`--forest` 選項並非每個 Shell 都有,可以透過 `man ps` 查看 >  ::: ### 進程列表:create sub shell * 進程列表是一種 **命令分組(`command grouping`)**,透過將命令 **使用括號 `()` 包裹來達到讓括號內的命令運行在另一個子進程 (sub process)** 1. **一般下達命令**:一行中下達多個命令,預設每個外部指令都會 fork 啟動,但 **不會啟動一個 sub shell 運行** 一般下達命令範例如下 ```shell= ## 在一行中下達多個命令,**命令之間使用 `;` 區分** ps -f ; ls ; pwd ; ps -f ``` 下圖中,對應到上面要執行的指令,這些指令會 forking PID `14002` shell ```mermaid graph LR Shell 命令 --> |forking| Shell ``` >  2. **進程列表下達命令**:使用括號 `()` 啟動 sub shell,運行括號內的命令 進程列表下達命令令範例如下 ```shell= (ps -f ; ls ; pwd ; ps -f) ``` 下圖中,對應到範例括號內的命令,會 forking PID `14066` sub shell,也就是說指令 forking 的來源是 sub shell,而不是原來的 Shell ```mermaid graph LR subgraph 父Shell 子Shell end 命令 --> |forking| 子Shell ``` >  :::warning * 另一種命令分組是使用花括號 `{}` + `;` 配合使用,但這種方式就 **不會** 啟動一個 sub shell (子 Shell),格式如下 ```shell= { command; } ``` ::: ### 操控 Shell 前後景執行:jobs 使用 * **啟動背景命令** | 命令 | 功能 | | - | - | | fg [%n] | 切換 Shell 到前景;n 是任務編號 | | bg [%n] | 切換 Shell 到背景(`ctrl` + `z` 是掛起、休眠,兩者功能不同);n 是任務編號 | | jobs | 查看所有背景任務 | 1. **啟動 Shell 背景命令**:一般 Shell 切換到背景,只需要在命令的後面 **加 `&` 號** 即可 ```shell= sleep 10 & ps -f ``` > 切換到背景後會返回一串數字,那些數字就是切換到背景運作的 Shell 的 PID > >  2. **查看背景 Shell**:使用 `jobs` 命令就可以查看到任務編號,之後可以透過這個編號讓指定任務運行在前景或是背景 ```shell= jobs ## 列出 PID jobs -l ``` >  3. **切換前後景**:切換 sleep 任務到背景,再切換到前景 ```shell= sleep 100 ## 接著按 `ctrl` + `Z` 掛起 ## 切換到背景運行 bg %1 ## 查任所有任務 jobs ## 切換到前景運行 fg %1 ``` >  ### 判斷 SubShell * Ubuntu 中可以 **使用 `BASH_SUBSHELL` 關鍵字 (變量)** 來判端當前 Shell 是否是子Shell(Sub Shell) 1. 無 subshell 的狀況下會回應 0,代表頂層 Shell ```shell= echo $BASH_SUBSHELL ``` >  2. **透過進程列表創建 sub shell** ```shell= ($echo BASH_SUBSHELL ; sleep 100 ; )& ``` 如下圖,我們看到使用進程列表(小擴號 `()` 符號)並打印 `BASH_SUBSHELL` 變量,就可以看到當前的 Sub shell 階層 >  ### 創建 SubShell:協程 coproc * 除了使用進程列表之外,也可以 **透過 `coproc` 命令將接下來要下達的命令切換到 sub shell** ```shell= coproc sleep 100 ps ps -l ``` >  * `coproc` 命令的另一個特點在於,**可以對 sub shell 設定指定名稱**,這可以讓你的 Shell 更有可讀性;格式如下 > coproc <名稱> { [指令] ; } 範例如下:以下啟動一個名為 `MyJob` 的協程,在使用 `jobs` 命令查看 ```shell= corproc MyJob { sleep 100 ; } jobs -l ``` > 如下圖,我們可以看到 Sub shell 的名稱確實顯示 `MyJob` > >  ## Builtin Shell 應用 ### 歷史命令 History * `history` 可以查看下達 Shell 命令的歷史 ```shell= history | tail ``` >  | history 額外命令 | 說明 | | - | - | | `!!` | 執行上一個命令 | | `!<命令編號>` | 執行指定歷史命令 | | `history -a` | 對 `.bash_history` 寫入命令(命令不會立刻被寫入,所以必須透過 `-a` 命令去強至寫入) | :::info * `.bash_history` 會儲存用戶輸入的所有命令 ```shell= cat .bash_history | tail ``` >  ::: ### 命令別名 alias * 有時候命令太常會不方便記憶,這時我們就可以使用 `alias` 來對命令簡化(別名) > 格式:<別名>=<原命令> ```shell= ## 取別名 lll='ls -laF' ## 下達命令 lll ``` :::warning 別名的功能只存在於當前 Shell 中,令起一個 Shell 就無法使用已經設定好的別名 ::: >  * 可以透過 `alias -p` 查看所有可使用的別名 ```shell= alias -p ``` >  ## Appendix & FAQ :::info ::: ###### tags: `Linux Shell`
×
Sign in
Email
Password
Forgot password
or
By clicking below, you agree to our
terms of service
.
Sign in via Facebook
Sign in via Twitter
Sign in via GitHub
Sign in via Dropbox
Sign in with Wallet
Wallet (
)
Connect another wallet
New to HackMD?
Sign up