--- 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
Sign in via Google
Sign in via Facebook
Sign in via X(Twitter)
Sign in via GitHub
Sign in via Dropbox
Sign in with Wallet
Wallet (
)
Connect another wallet
Continue with a different method
New to HackMD?
Sign up
By signing in, you agree to our
terms of service
.