Linux深入探索04-Bash shell

四月不見發表於2022-01-25

----- 最近更新【2021-12-30】-----

本文目錄結構預覽:

  • 一、簡介
  • 二、shell 變數
    1、檢視變數
    2、變數型別
    3、變數操作
    4、系統常見的全域性變數
  • 三、shell 選項
    1、檢視 shell 選項
    2、設定 shell 選項
  • 四、元字元
    1、元字元列表
    2、引用與轉義
  • 五、shell 內建命令
    1、檢視說明
    2、常用內建命令
  • 六、搜尋路徑
    1、檢視搜尋路徑
    2、修改搜尋路徑
  • 七、歷史列表
    1、檢視歷史列表
    2、調取歷史命令
    3、調取並修改歷史命令
    4、搜尋歷史命令
    5、設定歷史列表大小
  • 八、別名 alias
    1、語法
    2、建立別名
    3、檢視別名
    4、移除別名
    5、不使用別名
  • 九、初始化檔案
    1、檔名稱
    2、登入 shell 與 非登入 shell
    3、初始化檔案內容
  • 十、參考

一、簡介

簡單地來說,shell 就是一個 Unix 程式,充當使用者介面和指令碼直譯器,允許使用者輸入命令以及間接地訪問核心的服務。

從功能方面來說:
第一,shell 是一個讀取並解釋所輸入命令的程式。使用者每輸入一條 Unix 命令,shell 就讀取該命令,並指出應該怎麼做,所以 shell 是一個命令處理器
第二,shell 還支援一些型別的程式語言。使用該語言可以編寫由 shell 解釋的程式,這些程式稱為 shell 指令碼

目前比較流行的shell有以下幾種 :Bash、Korn shell、C-Shell、Tcsh。

Bash是目前最流行的 shell,本文也是以 Bash 為環境。

如果不知道你目前使用的是哪種 shell,可以使用命令echo $SHELL來檢視。

本文內容可能會比較多,為了方面提前知道有哪些內容,這裡先作一個簡要的列表:

  • shell 變數
    --檢視變數、變數型別、變數操作、系統常見的全域性變數
  • shell 選項
    --檢視 shell 選項、設定 shell 選項
  • 元字元
    --元字元列表、引用與轉義
  • shell 內建命令
    --檢視說明、常用內建命令
  • 搜尋路徑
    --檢視搜尋路徑、修改搜尋路徑
  • 歷史列表
    --檢視歷史列表、調取歷史命令、調取並修改歷史命令、搜尋歷史命令、設定歷史列表大小
  • 別名 alias
    --語法、建立別名、檢視別名、移除別名
  • 初始化檔案
    --檔名稱、登入 shell 與 非登入 shell、初始化檔案內容

二、shell 變數

1、檢視變數

上面說的$SHELL是 shell 的一個全域性變數,shell 中還有很多其它的變數。可以使用命令env或者printenv去檢視全域性變數。

使用不帶選項或者引數的 set 命令也可以顯示所有的 shell 變數以及它們的值。

2、變數型別

根據儲存型別,shell變數幾乎總是儲存一種型別的資料,即字串。

根據變數的作用域(Scope),shell 變數可以劃分為以下三種型別:

  • 有的變數可以在當前shell程式及其子程式中使用,這叫做全域性變數
  • 有的變數僅可以在當前shell程式中使用,這叫做環境變數
  • 有的變數只能在函式內部使用,這叫做區域性變數(作用域僅為某程式碼片斷(函式上下文))

如果使用export命令將環境變數匯出,那麼它就在所有的子程式中也可以使用了,這時稱為“全域性變數”。

很多書本或網站對環境變數與全域性變數的定義都比較模糊,我這裡採用一種比較好理解的方式來定義。

3、變數操作

變數通常有4種不同型別的操作,即建立變數、檢視變數、修改變數、刪除變數。

變數的建立非常簡單,使用如下語法就行:

變數名稱=變數值  # 注意!等號前後不能有空格。

刪除變數:

unset 變數名 ...

例:

[14:59 linux1@noseeu ~]$ MYNAME=Nosee    #建立變數(當前shell可用)
[14:59 linux1@noseeu ~]$ echo $MYNAME    #檢視變數
Nosee
[14:59 linux1@noseeu ~]$ export $MYNAME     #匯出變數(當前shell及子程式可用)
[15:00 linux1@noseeu ~]$ unset MYNAME    #刪除變數
[15:00 linux1@noseeu ~]$ echo $MYNAME

[15:00 linux1@noseeu ~]$ 

注意:
通過 export 匯出的變數只對當前 Shell 程式以及所有的子程式有效,如果最頂層的父程式被關閉了,那麼這個全域性變數也就隨之消失了,其它的程式也就無法使用了,這個變數只是臨時的。
只有將變數寫入 Shell 配置檔案中才能達到永久的目的,Shell 程式每次啟動時都會執行配置檔案中的程式碼做一些初始化工作,如果將變數放在配置檔案中,那麼每次啟動程式都會定義這個變數。

4、系統常見的全域性變數

HISTFILE # 歷史列表:用來儲存歷史命令的檔名稱
HISTSIZE # 歷史列表:用來儲存歷史命令的最大數目
HOME # home目錄
HOSTNAME # 計算機名稱
LOGNAME # 當前使用者標誌(使用者名稱)
PATH # 程式搜尋目錄
PS1 # shell提示
PWD # 當前工作目錄
SHELL # shell型別
TERM # 終端型別
USER # 當前使用者標誌(使用者名稱)

三、shell 選項

在Bash shell中,當我們需要控制 shell 行為的各個方面時,則可以使用shell 選項。

shell 選項就像 on/off 開關一樣。當開啟一個選項時,就說設定了這個選項;當關閉這個選項時,就說復位了這個選項。

shell 選項或者是 off 或者是 on ,它們不需要建立。

1、檢視 shell 選項

可以使用命令set -o或者set +o來檢視所有 shell 選項,兩種檢視方式只是在顯示格式上不一樣。

set -o命令以一種易於閱讀的方式顯示(人類可讀)
set +o命令顯示的輸出適合用作 shell 指令碼的資料(機器可讀)

選項介紹:

選項 短名稱 含義
allexport -a 匯出隨後定義的所有變數和函式
braceexpand -B 啟用括號擴充套件(生成字元模式)
emacs 命令列編輯器:Emacs模式,關閉vi模式
hashall -h 查詢到命令時(記住)的命令雜湊位置
hisexpand -H 歷史列表:啟用!風格替換
history 歷史列表:啟用
ignoreeof 忽略eof訊號^D;使用exit或logout退出shell
minitor -m 作業控制:啟用
noclobber -C 不允許重定向的輸出替換某個檔案
notify -b 作業控制:當後臺作業結束時立即通知
vi vi模式:立即處理每個鍵入的字元

使用命令 shopt可以檢視更多的 shell 選項。

2、設定 shell 選項

語法:

set -o option  # 開啟shell選項(開啟)
set +o option  #復位shell選項(關閉)

注意,這裡的-o表示開啟一個選項,+o表示關閉一個選項。

1)如,我要開啟某個選項(選項noclobber表示不允許重定向的輸出替換某個檔案)
則可以:

set -o noclobber # 開啟該選項

或者:

set -C

關閉該選項則這樣:set +o noclobberset +C

2)忽略eof訊號^D

[21:34 linux1@noseeu ~]$ set -o ignoreeof
# 此時我再按 <Crlt>-D 組合鍵
[21:34 linux1@noseeu ~]$ Use "logout" to leave the shell.

設計 shell 的程式設計師們知道人們會如何使用 shell,因此在大多數情況下,預設的 shell 選項就可以滿足要求。這意味著我們極少需要雲修改 shell 選項。

四、元字元

在shell中,有許多字元擁有它特殊的含義,我們稱這樣的字元為元字元。如;(分號)、\(反斜槓)、.(點號),抽象一點的如按鍵<Space><Tab><Enter>也是使用了元字元。

1、元字元列表

字元 名稱 作用
| 管道 命令列:建立一個管道線
< 小於 命令列:重定向輸入
> 大於 命令列:重定向輸出
() 圓括號 命令列:在子shell中執行命令
# hash、pound 命令列:註釋
; 分號 命令列:用於分隔多條命令
` 反引號 命令列:命令替換
~ 波浪號 檔名擴充套件:插入home目錄的名稱
? 問號 檔名擴充套件:匹配任意一個字元
[] 方括號 檔名擴充套件:與一組字元中的字元匹配
* 星號 檔名擴充套件:匹配0個或多個字元
! 歎號、bang 歷史列表:事件標記
& 和號 作業控制:在後臺執行命令
\ 反斜槓 引用:下一個字元轉義
' 單引號 引用:取消所有的替換
" 雙引號 引用:取消大部分替換
{} 花括號 變數:確定變數名稱的界限
$ 美元符號 變數:用變數的值替換
空格符 空白符:在命令列中分隔單詞
製表符 空白符:在命令列中分隔單詞
/ 新行字元 空白符:標記一行結束

以上元字元列表展示的就是元字元在shell中的常用作用。

2、引用與轉義

有時候,我們希望按字面上的含義使用元字元(而不是使用其特殊含義),這時我們必須告訴shell按字面意思解釋字元。這樣做時,可以稱其為引用字元。
字元的引用有3種方法:使用反斜槓、一對單引號或者一對雙引號。

當使用反斜槓引用單個字元時,我們稱反斜槓為“轉義字元”。

如:

echo It is warm\; come on.

上面例子中,;(分號)在shell中有特殊的含義,如果我們想原樣輸出則必須轉義。這時可以說“使用反斜槓轉義了分號”,或者可以說“使用反斜槓引用了分號”。

當元字元少時,上面的方法沒有什麼問題。但是當元字元多的時候,我們就需要用到單引號或者雙引號了。

1)強引用(單引號)
單引號裡面的所有元字元都會被轉義,也就是所有元字元都會原樣輸出。

如,下面的$會被轉義,$NAME會原樣輸出:

linux1@noseeu:~$ echo 'My name is $NAME'
My name is $NAME

2)弱引用(雙引號)
雙引號引用時,會保留$(美元符號)、`(反引號)、\(反斜槓)的特殊含義。

如:

linux1@noseeu:~$ echo "My name is $NAME"
My name is Chan
[22:04 linux1@noseeu ~]$ echo "now is `date`."
now is Sun 26 Dec 2021 10:04:24 PM UTC.

上面的$不會被轉義,$NAME會被替換為NAME的實際值;`也不會被轉義,date會被當作嵌入命令優先執行。

注:反斜槓是所有引用中最強的,所以它甚至可以引用新行字元(<Enter>)。
如,

linux1@noseeu:~$ echo hi \
> nosee.
hi nosee.
linux1@noseeu:~$ date;\
> cal
Sun 26 Dec 2021 05:28:01 PM UTC
   December 2021      
Su Mo Tu We Th Fr Sa  
          1  2  3  4  
 5  6  7  8  9 10 11  
12 13 14 15 16 17 18  
19 20 21 22 23 24 25  
26 27 28 29 30 31 

在這裡新行字元(<Enter>)失去了它的特殊含義,所以這時它並不是一行結束的訊號了,意味著後面輸入的內容都是接著上一行的。

五、shell 內建命令

當在shell中輸入命令時,shell會將命令進行解析,然後決定如何處理命令,其中有兩種可能。一些命令在shell的內部,這意味著shell可以直接解釋它們,這些命令是內部命令,稱為內建命令。其他所有命令都是外部命令,即必須獨自執行的獨立程式。

當輸入內建命令時,shell在自己的程式內執行該命令(不建立新的程式)。當輸入外部命令時,shell將搜尋合適的程式然後以一個單獨的程式執行該命令(建立一個子程式)。

檢視某一條命令是不是內建命令的快捷方法是使用type。如:

linux1@noseeu:~$ type time set date type
time is a shell keyword
set is a shell builtin
date is hashed (/usr/bin/date)
type is a shell builtin

可以看出,time、set、type都是shell的內建命令,而date是外部命令。

1、檢視說明

幾乎所有的Unix程式在發行的時候都提供有說明明書頁,即可以用man或者info去檢視它們的說明。但內建命令不是一個單獨的程式,它們是shell的一部分,每種shell都會提供大量的命令,所以每個內建命令都開發一個單獨的說明書頁是不現實的。

其實,所有的內建命令都記錄在shell的說明書頁中,即你可以使用man bash去檢視。但是shell的說明書頁都非常長,可能需要使用搜尋才能找到所需的內容(可以使用apropos命令或者man -k命令進行搜尋)。

還有一個方法,就是使用help命令也可以檢視內建命令的說明,如:help unset

linux1@noseeu:~$ help unset
unset: unset [-f] [-v] [-n] [name ...]
    Unset values and attributes of shell variables and functions.
    ...
    ...
    Exit Status:
    Returns success unless an invalid option is given or a NAME is read-only.

使用不帶引數的help命令,可以顯示一個所有內建命令的摘要列表。如下圖:

使用help -s命令可以檢視某個內建命令的語法:

linux1@noseeu:~$ help -s set
set: set [-abefhkmnptuvxBCHP] [-o option-name] [--] [arg ...]

2、常用內建命令

alias # 為指定命令定義一個別名
echo # 將指定字串輸出到STDOUT
exit # 強制 shell 以指定的退出狀態碼退出
export  # 將變數匯出到全域性變數
fc  # 從歷史記錄中選擇命令列表
help # 顯示幫助說明
history # 顯示命令歷史記錄
kill # 向指定的程式 ID(PID) 傳送一個系統訊號
pwd  # 顯示當前工作目錄的路徑名
set # 設定並顯示環境變數的值和 shell 屬性
shopt # 開啟/關閉控制 shell 可選行為的變數值
type # 顯示指定的單詞如果作為命令將會如何被解釋
unset # 刪除指定的環境變數或 shell 屬性

六、搜尋路徑

大部分命令都不是 shell 內建的,那麼 shell 必須查詢出合適的程式來執行。那麼 shell 在哪裡查詢外部命令?

shell 通過查詢變數 PATH 來獲得一系列目錄名稱,然後在這些目錄下尋找對應的程式,這些目錄名稱就是我們所說的搜尋路徑

1、檢視搜尋路徑

搜尋路徑是包含所有外部命令的程式的目錄列表,使用命令echo $PATH可以檢視 搜尋路徑。

[21:31 linux1@noseeu ~]$ echo $PATH
/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin:/usr/games:/usr/local/games:/snap/bin

搜尋順序:
當 shell 在查詢外部程式時,它在搜尋路徑中會按指定順序逐個檢查目錄,進到找到期望的外部命令時,它就停止搜尋並執行程式。

2、修改搜尋路徑

修改搜尋路徑的基本思想就是將修改PATH變數的命令放到登入時自動執行的初始化檔案中。

PATH的值就是一個包含若干目錄名稱的字串,各個目錄名稱用:(冒號)隔開。

如我有一些自定義的程式放在 ~/bin 目錄,那麼我可以這樣設定:

export PATH="$PATH:$HOME/bin"

一般修改 PATH 的值我們都是在原有的路徑上再加上我們想要新增的路徑。

七、歷史列表

在輸入命令時,shell 會將命令儲存到所謂的歷史列表中。然後我們可以採用不同方式訪問歷史列表,調取前面的命令,然後再對命令進行修改,並重新輸入命令。

1、檢視歷史列表

最簡單的檢視歷史列表的辦法就是使用<Up><>Down鍵,但是這個方法每次只能檢視一條命令。

還有一個更強大的命令,可以檢視全部或者部分歷史命令,就是使用history或者fc命令。

在歷史列表中,每條命令稱為一個事件,而每個事件都有一個內部編號,稱為事件編號。歷史列表的主要功能就是它可以基於事件編號調取命令。

檢視歷史列表:

[23:12 linux1@noseeu ~]$ fc -l
...
931      echo "'now is `date`."
932      help fc
933      fc-l
[23:12 linux1@noseeu ~]$ history
    1  sudo -i
    2  exit
...
934  fc -l
935  history

每條命令前面的數字就是事件編號。

2、調取歷史命令

如我們想再次執行932號命令,可以使用fc -s 932或者!932

[23:14 linux1@noseeu ~]$ fc -s 932
help fc
fc: fc [-e ename] [-lnr] [first] [last] or fc -s [pat=rep] [command]
    Display or execute commands from the history list.
    ...
    Exit Status:
    Returns success or status of executed command; non-zero if an error occurs.
[23:22 linux1@noseeu ~]$ !932
help fc
fc: fc [-e ename] [-lnr] [first] [last] or fc -s [pat=rep] [command]
    Display or execute commands from the history list.
    ...
    Exit Status:
    Returns success or status of executed command; non-zero if an error occurs.

如果只是希望輸入最近上一條命令,則可以不帶事件編號:fc -s或者!!

3、調取並修改歷史命令

shell 允許在重新執行歷史命令之前對命令進行小的修改,語法如下:

fc -s pattern=replacement number
!number:s/pattern/replacement

例:

937      vim sh_test1 
[23:34 linux1@noseeu ~]$ fc -s sh_=Sh 937
vim Shtest1 
[23:35 linux1@noseeu ~]$ !937:s/sh_/Sh
vim Shtest1 

如果只是想修改上一條輸錯的命令,還可以這樣:

[23:39 linux1@noseeu ~]$ datw
Command 'datw' not found.
[23:39 linux1@noseeu ~]$ ^w^e
date
Sun 26 Dec 2021 11:40:12 PM UTC
[23:40 linux1@noseeu ~]$ 

記住,這個方法僅適用於對上一條歷史命令修改。

4、搜尋歷史命令

bash 還提供了一個非常方便的歷史命令搜尋方式,即使用<Ctrl>-R(^R)。
如我需要調取一條設定PS1變數的命令,查歷史列表又覺得麻煩。這時我就可以按下<Ctrl>-R鍵,然後輸入關鍵詞PS:

linux1@noseeu:~$ 
(reverse-i-search)`PS': PS1="\[\033[0;32m\][\A \u\[\033[0;33m\]@\H \w]$ \[\033[0m\]"

如果顯示出的命令是你想要的,直接按回車就可以執行了:

linux1@noseeu:~$ PS1="\[\033[0;32m\][\A \u\[\033[0;33m\]@\H \w]$ \[\033[0m\]"
[23:54 linux1@noseeu ~]$ 

如果看到的不是你想要的命令,可以繼續按<Ctrl>-R搜尋下一個。

5、設定歷史列表大小

Bash shell 將歷史列表儲存在一個檔案中,所以下次登入時還是可以繼續使用。為了避免歷史列表太多,shell 允許通過設定一個變數來設定歷史列表的大小。

檢視 shell 預設設定:

linux1@noseeu:~$ set | grep HIST
HISTCONTROL=ignoreboth
HISTFILE=/home/linux1/.bash_history
HISTFILESIZE=2000
HISTSIZE=1000

如我們要修改history命令所展示的歷史列表條數:

linux1@noseeu:~$ export HISTSIZE=10

八、別名 alias

別名就是賦予一條命令或者一列命令的名稱。可以將別名作為縮寫,或者使用別名建立已有命令的自定義變體。

1、語法

Define or display aliases.

alias: alias [-p] [name[=value] ... ]

2、建立別名

我們經常會使用ls -al來檢視檔案列表,為了方便可以如下定義別名:

alias ll='ls -al'

當一條命令中包含有空格或元字元時,記得要使用引號包圍。

linux1@noseeu:~$ alias mytime='date; cal'
linux1@noseeu:~$ mytime
Mon 27 Dec 2021 01:01:56 AM UTC
   December 2021      
Su Mo Tu We Th Fr Sa  
          1  2  3  4  
 5  6  7  8  9 10 11  
12 13 14 15 16 17 18  
19 20 21 22 23 24 25  
26 27 28 29 30 31  

3、檢視別名

當需要檢視某個別名的含義時,可以這樣:

linux1@noseeu:~$ alias mytime
alias mytime='date; cal'

或者使用type命令:

linux1@noseeu:~$ type mytime
mytime is aliased to `date; cal'

檢視所有別名:

linux1@noseeu:~$ alias
alias alert='notify-send --urgency=low -i "$([ $? = 0 ] && echo terminal || echo error)" "$(history|tail -n1|sed -e '\''s/^\s*[0-9]\+\s*//;s/[;&|]\s*alert$//'\'')"'
alias egrep='egrep --color=auto'
alias fgrep='fgrep --color=auto'
alias grep='grep --color=auto'
alias l='ls -CF'
alias la='ls -A'
alias ll='ls -al'
alias ls='ls --color=auto'
alias mytime='date; cal'

4、移除別名

語法:Remove each NAME from the list of defined aliases.

unalias [-a] name [name ...]

例:

linux1@noseeu:~$ unalias mytime 
linux1@noseeu:~$ mytime
mytime: command not found

5、不使用別名

ls命令,很多 linux 會預設就在配置檔案中設定了ls --color=auto,這時你就會在不知情的情況下使用了別名。如果你不想使用使用別名,可以在命令前面鍵入一個\,告訴 shell 不使用任何別名,如\ls

九、初始化檔案

以上所說的所有設定都是隻有當前shell生效,登出然後再登入之後那些設定都不存在了。如果你想在你下次登入時還可以繼續使用你的設定,則需要把你的設定新增到初始化檔案中。

Bash shell 的初始化檔案包括兩個,即登入檔案環境檔案

初始化檔案存放著所有希望在每次登入時自動執行的命令,而環境檔案存放著所有希望在新 shell 啟動時自動執行的程式。

為了提供更多的功能,Bash shell 還提供了登出檔案,用於存放登出登入時自動執行的命令。

1、檔名稱

初始化檔案的名稱在不同的系統中可能會稍有不同,但基本都是以下這種:

執行環境 檔名稱
登入檔案 .bash_profile、.profile、.bash_login
環境檔案 .bashrc
登出檔案 .bash_logout

檔案存放的目錄都為使用者目錄,即:/home/使用者名稱

注:
系統啟動的時候會先執行.profile然後執行.bashrc,所以如果這兩個檔案存在相同的配置的話,前面的會被後面的覆蓋。

2、登入 shell 與 非登入 shell

1)登入 shell
在任何時候,如通過ssh連線登入到遠端主機,或者使用登入一個 shell,等。這型別需要使用者進行登入認證才能進入的shell,我們稱之為登入 shell

登入 shell 的初始化會執行登入檔案(.profile)與環境檔案(.bashrc)。

2)非登入 shell
當開啟一個 shell 視窗不需要登入認證時,如在一個已登入的 shell 視窗輸入 bash 開啟一個新 shell,或者在桌面環境簡單地開啟一個終端視窗,等,我們稱這類 shell 為 非登入 shell

非登入 shell 只執行環境檔案。

3、初始化檔案內容

shell 的初始化檔案一般包含下述內容:
1)建立或者修改環境變數的命令
2)執行所有一次性操作的命令
3)合理的註釋
4)等

如:

# 設定環境變數
HISTSIZE=50
HISTFILESIZE=1000

# 設定檔案建立掩碼,控制新建立檔案的預設許可權
umask 077

# 設定別名
alias ll='ls -alF'
alias la='ls -A'
alias l='ls -CF'

# 設定預設的分頁程式(需要呼叫分頁程式顯示資料時就會呼叫該程式,如 man 命令)
export PAGER=less
# 設定分頁程式的預設選項,相當於:alias less='less -CMs'
export LESS='-CMs'

# 檢視命名為`.bashrc`的檔案是否存在,如果存在,則執行這個檔案
if [ -f "$HOME/.bashrc" ]; then
    . "$HOME/.bashrc"
fi

十、參考

書箱:《Unix & Linux 大學教程》第11-14章 (美)Harley Hahn 著 張傑良 譯
部落格: 命令列介面 (CLI)、終端 (Terminal)、Shell、TTY的區別
SegmentFault: Linux TTY/PTS概述

相關文章