關(guān)于Shell
為了能對(duì)shell能夠有整體的認(rèn)識(shí),我們需要先簡單介紹下Linux系統(tǒng) 。
Linux系統(tǒng)
Linux系統(tǒng)主要分四部分:
-
Linux內(nèi)核 -
GNU工具 - 圖形桌面化環(huán)境
- 應(yīng)用軟件
<img src="https://p3-juejin.byteimg.com/tos-cn-i-k3u1fbpfcp/c4ac43fb0199425cbaf1220e34378384~tplv-k3u1fbpfcp-zoom-1.image" alt="Linux系統(tǒng)" style="zoom:50%;" />
Linux內(nèi)核
Linux內(nèi)核主要負(fù)責(zé)以下四種功能:
- 系統(tǒng)內(nèi)存管理:物理內(nèi)存、虛擬內(nèi)存
- 軟件程序管理:
Linux操作系統(tǒng)將運(yùn)行中的程序稱為進(jìn)程。內(nèi)核控制著Linux系統(tǒng)如何管理運(yùn)行在系統(tǒng)上的所有進(jìn)程。內(nèi)核創(chuàng)建了第一個(gè)進(jìn)程(稱為init進(jìn)程)來啟動(dòng)系統(tǒng)上所有其他進(jìn)程,Linux使用一個(gè)表來管理在系統(tǒng)開機(jī)時(shí)要自動(dòng)啟動(dòng)的進(jìn)程。Linux操作系統(tǒng)的init系統(tǒng)采用了運(yùn)行級(jí)。運(yùn)行級(jí)決定了init進(jìn)程運(yùn)行/etc/inittab文件或/etc/rcX.d目錄中定義好的某些特定類型的進(jìn)程(X代表運(yùn)行級(jí))。Linux操作系統(tǒng)有5個(gè)啟動(dòng)運(yùn)行級(jí)。每個(gè)啟動(dòng)運(yùn)行級(jí)便是一種啟動(dòng)模式。 - 硬件設(shè)備管理:內(nèi)核的另一職責(zé)是管理硬件設(shè)備。任何
Linux系統(tǒng)需要與之通信的設(shè)備,都需要在內(nèi)核代碼中加入其驅(qū)動(dòng)程序代碼。驅(qū)動(dòng)程序代碼相當(dāng)于應(yīng)用程序和硬件設(shè)備的中間人,允許內(nèi)核與設(shè)備之 間交換數(shù)據(jù)。 - 文件系統(tǒng)管理:不同于其他一些操作系統(tǒng),
Linux內(nèi)核支持通過不同類型的文件系統(tǒng)從硬盤中讀寫數(shù)據(jù)。
GNU
操作系統(tǒng)用以執(zhí)行一些標(biāo)準(zhǔn)功能,比如控制文件和程序的工具。Linus在創(chuàng)建Linux系統(tǒng)內(nèi)核時(shí),沒有可用的系統(tǒng)工具。GNU是由GNU組織(GNU是GNU’s Not Unix的縮寫)開發(fā)了一套完整的Unix工具,是開源的,但沒有運(yùn)行它們的內(nèi)核系統(tǒng)。于是將Linus的Linux內(nèi)核和GNU操作系統(tǒng)工具整合起來,就產(chǎn)生了一款完整的、功能豐富的免費(fèi)操作系統(tǒng):GNU/Linux系統(tǒng)(為了感謝GNU組織)也稱:Linux系統(tǒng)。
GNU分兩部分,一部分為核心GNU工具(core utilities),由處理文件、操作文本、管理進(jìn)程三部分工具包組成;另一部分便是Shell。
Shell簡介
Shell是一種特殊的交互式工具。它為用戶提供了啟動(dòng)程序、管理文件系統(tǒng)中的文件以及運(yùn)行在Linux系統(tǒng)上的進(jìn)程的途徑。也就是Shell負(fù)責(zé)將命令行中輸入的文本命令,進(jìn)行解釋,并傳遞到內(nèi)核進(jìn)行執(zhí)行的工具,也可稱解釋器。
Shell的核心是命令行提示符。命令行提示符是Shell負(fù)責(zé)交互的部分,它允許你輸入文本命令,然后解釋命令,并在內(nèi)核中執(zhí)行。將多個(gè)shell命令放入文件中作為程序執(zhí)行,這個(gè)文件便被稱為Shell 腳本。
在Linux系統(tǒng)上,通常有好幾種Linux shell可用。不同的shell有不同的特性,有些更利于創(chuàng)建腳本,有些則更利于管理進(jìn)程。所有Linux發(fā)行版(完整的Linux系統(tǒng)包)默認(rèn)的shell都是bash shell。
bash shell由GNU組織開發(fā),被當(dāng)作標(biāo)準(zhǔn)Unix shell——Bourne shell(以創(chuàng)建者的名字命名)的替代品。bash shell的名稱就是針對(duì)Bourne shell的拼寫所玩的一個(gè)文字游戲,稱為Bourne again shell。總結(jié):sh是標(biāo)準(zhǔn),bash是sh的替代品。除了bash shell,Linux中常見的幾種不同shell有:
-
ash:一種運(yùn)行在內(nèi)存受限環(huán)境中簡單的輕量級(jí)shell,但與bash shell完全兼容。 -
korn:一種與Bourne shell兼容的編程shell,但支持如關(guān)聯(lián)數(shù)組和浮點(diǎn)運(yùn)算等一些高級(jí)的編程特性。 -
tcsh:一種將C語言中的一些元素引入到shell腳本中的shell。 -
zsh:一種結(jié)合了bash、tcsh和korn的特性,同時(shí)提供高級(jí)編程特性、共享歷史文件和主題化提示符的高級(jí)shell。
從 macOS Catalina 版開始,蘋果的Mac系統(tǒng)將使用zsh作為默認(rèn)登錄Shell 和交互式 Shell。具體請(qǐng)看官網(wǎng)。
環(huán)境變量
這一部分將基于bash shell展開陳述。
bash shell中使用環(huán)境變量在內(nèi)存中存儲(chǔ)有關(guān)shell會(huì)話和工作環(huán)境的數(shù)據(jù),以便程序或shell中運(yùn)行的腳本能夠訪問到它們。
bash shell中的環(huán)境變量主要有兩種:全局變量與局部變量。查看系統(tǒng)中所有全局變量,可以使用env或printenv命令;要顯示個(gè)別環(huán)境變量的值,可以使用printenv命令,但是不能用env命令。
#查看shell個(gè)別全局變量
printenv HOME
#查看shell個(gè)別全局變量:通過echo 以變量的形式輸出
echo $HOME
系統(tǒng)的環(huán)境都是大寫,定義屬于用戶自己局部變量時(shí)統(tǒng)一使用小寫,避免沖突。
定義局部變量
定義形式如下:
variable=Hello
echo $variable #輸出:Hello
重要:變量名、等號(hào)和值之間沒有空格。如果在賦值表達(dá)式中加上了空格, bash shell就會(huì)把值當(dāng)成一個(gè)單獨(dú)的命令,變量值有空格需要使用引號(hào)。
variable="Hello word"
echo $variable #輸出:Hello word
定義全局變量
在設(shè)定全局環(huán)境變量的進(jìn)程所創(chuàng)建的子進(jìn)程中,該全局變量都是可見的;創(chuàng)建全局環(huán)境變量的方法是先創(chuàng)建一個(gè)局部環(huán)境變量,然后再把它導(dǎo)出到全局環(huán)境中;父shell定義的全局變量,子shell的修改不會(huì)影響父shell的值。示例如下:
variable="global variable ~~~~"
export variable
#開啟子shell
bash
#子shell 輸出一下
echo $variable #global variable ~~~~
#子shell修改
variable="子shell修改"
#子shell 輸出一下
echo $variable #輸出:子shell修改
#導(dǎo)出
export variable
#退出子shell
exit
#父shell輸出
echo $variable #子shell的修改不會(huì)影響父shell:global variable ~~~~
刪除全局變量
使用如下命令:
#刪除
unset variable
需要注意的是在子shell中是無法刪除父shell創(chuàng)建的全局變量。
默認(rèn)全局變量
默認(rèn)情況下,bash shell會(huì)用一些特定的環(huán)境變量來定義系統(tǒng)環(huán)境。這些變量在你的Linux系統(tǒng)上都已經(jīng)設(shè)置好了,只管放心使用。bash shell源自當(dāng)初的Unix Bourne shell,因此也保留了Unix Bourne shell里定義的那些環(huán)境變量。
列舉幾個(gè)比較常見的環(huán)境變量:
-
CDPATH:冒號(hào)分隔的目錄列表,作為cd命令的搜索路徑 -
HOME:當(dāng)前用戶的主目錄 -
PATH:shell查找命令的目錄列表,由冒號(hào)分隔 -
BASH:當(dāng)前shell實(shí)例的全路徑名 -
PWD:當(dāng)前工作目錄
設(shè)置PATH變量
當(dāng)我們?cè)?code>shell命令行界面中輸入一個(gè)外部命令時(shí),shell必須搜索系統(tǒng)來找到對(duì)應(yīng)的程序。PATH環(huán)境變量定義了用于進(jìn)行命令和程序查找的目錄:
#輸出下
echo $PATH
#結(jié)果
/Users/*/.rvm/gems/ruby-2.3.0/bin:
/Users/*/.rvm/gems/ruby-2.3.0@global/bin:
/Users/*/.rvm/rubies/ruby-2.3.0/bin:
/Users/*/Desktop/development/flutter/bin:
/usr/local/bin:
/usr/bin:
/bin:
/usr/sbin:
/sbin:
/Users/*/.rvm/bin
輸出的結(jié)果中顯示了有10個(gè)可供shell用來查找命令和程序的路徑。PATH中的目錄使用冒號(hào)分隔。這些路徑下分別都存放了不同的命令和程序,舉個(gè)/bin的示例:
如果命令或者程序的路徑?jīng)]有包括在PATH變量中,則不使用絕對(duì)路徑的情況下,shell是沒法找到該程序的。
問題:應(yīng)用程序放置可執(zhí)行文件的目錄常常不在PATH環(huán)境變量所包含的目錄中。
解決:是保證PATH環(huán)境變量包含了所有存放應(yīng)用程序的目錄??梢园研碌乃阉髂夸浱砑拥浆F(xiàn)有的PATH環(huán)境變量中,無需從頭定義。PATH中各個(gè)目錄之間是用冒號(hào)分隔的,我們只需要引用原來的PATH值,然后再給這個(gè)字符串添加新目錄就行了。
舉例終端啟用flutter命令:
#查看path
echo $PATH
#結(jié)果不包含:/Users/*/Desktop/development/flutter/bin:
#通過終端啟用
PATH=$PATH:~/Desktop/development/flutter/bin
#再次查看
echo $PATH
#結(jié)果包含:/Users/*/Desktop/development/flutter/bin:
#執(zhí)行flutter
flutter -v
#不再提示找不到命令。
值得注意的是:對(duì)PATH變量的修改只能持續(xù)到退出或重啟終端系統(tǒng)。這種效果并不能一直持續(xù)。如何讓這種效果持續(xù)?
為了解決這個(gè)問題,我們需要了解一些關(guān)于定位環(huán)境變量的知識(shí)。
在我們登入Linux系統(tǒng)啟動(dòng)一個(gè)bash shell時(shí),默認(rèn)情況下bash會(huì)在幾個(gè)文件中查找命令。這些文件叫作啟動(dòng)文件或環(huán)境文件。bash檢查的啟動(dòng)文件取決于你啟動(dòng)bash shell的方式。啟動(dòng)bash shell有3種方式:
- 登錄時(shí)作為默認(rèn)登錄
shell - 作為非登錄
shell的交互式shell - 作為運(yùn)行腳本的非交互
shell
默認(rèn)登錄shell
當(dāng)我們登錄Linux系統(tǒng)時(shí),bash shell會(huì)作為登錄shell啟動(dòng)。登錄shell會(huì)從5個(gè)不同的啟動(dòng)文件里讀取命令:
/etc/profile
#/etc/profile啟動(dòng)文件內(nèi)容
# System-wide .profile for sh(1)
if [ -x /usr/libexec/path_helper ]; then
eval `/usr/libexec/path_helper -s`
fi
if [ "${BASH-no}" != "no" ]; then
[ -r /etc/bashrc ] && . /etc/bashrc
fi
$HOME/.bash_profile$HOME/.bashrc
#macOS系統(tǒng)中.bashrc啟動(dòng)文件內(nèi)容,非登錄的交互式shell會(huì)以此為啟動(dòng)文件的。
export PATH="$PATH:$HOME/.rvm/bin"
$HOME/.bash_login$HOME/.profile
/etc/profile文件是系統(tǒng)上默認(rèn)的bash shell的主啟動(dòng)文件。系統(tǒng)上的每個(gè)用戶登錄時(shí)都會(huì)執(zhí)行這個(gè)啟動(dòng)文件。另外4個(gè)啟動(dòng)文件是針對(duì)用戶的,提供一個(gè)用戶專屬的啟動(dòng)文件來定義該用戶所用到的環(huán)境變量,可根據(jù)個(gè)人需求定制,且都是隱藏文件。它們位于用戶的HOME目錄下,所以每個(gè)用戶都可以編輯這些文件并添加自己的環(huán)境變 量,這些環(huán)境變量會(huì)在每次啟動(dòng)bash shell會(huì)話時(shí)生效。其中2和5我們?cè)?code>macOS中是比較熟悉的。
shell會(huì)按照下列順序,運(yùn)行第一個(gè)被找到的文件,余下的則被忽略(不會(huì)重復(fù)):
$HOME/.bash_profile=>$HOME/.bash_login=>$HOME/.profile 注:$HOME和波浪號(hào)~作用一樣,都代表用戶目錄。正是因?yàn)榇艘?guī)則的存在,我們有些時(shí)候只需要在$HOME/.profile中配置我們的PATH即可。
非登錄的交互式shell
不是登錄時(shí)啟動(dòng)的shell稱為交互式shell。比如:在命令行提示符下敲入bash時(shí)啟動(dòng)。
當(dāng)bash是作為交互式shell啟動(dòng),則不會(huì)訪問/etc/profile文件,只會(huì)檢查用戶目錄$HOME下的.bashrc文件。
運(yùn)行腳本的非交互shell
非交互shell,系統(tǒng)執(zhí)行shell腳本時(shí)會(huì)使用。不同的地方在于它沒有命令行提示符。但是當(dāng)我們?cè)谙到y(tǒng)上運(yùn)行腳本時(shí),可能希望運(yùn)行一些特定啟動(dòng)的命令。為了處理這種情況,bash shell提供了BASH_ENV環(huán)境變量。當(dāng)shell啟動(dòng)一個(gè)非交互式shell進(jìn)程時(shí),它會(huì)檢查這個(gè)環(huán)境變量來查看要執(zhí)行的啟動(dòng)文件。
在macOS系統(tǒng)下運(yùn)行echo $BASH_ENV查看,這個(gè)環(huán)境變量并未被設(shè)置。如果BASH_ENV變量沒有設(shè)置,shell腳本如何獲得它們的環(huán)境變量呢?
- 子
shell可以繼承父shell到處的環(huán)境變量;但需要注意的是父shell中設(shè)置但卻未export的變量,屬于局部變量,子shell是無法獲取的。也就是說執(zhí)行腳本時(shí),采用bash命令開啟子shell便可以解決這個(gè)問題。 - 不啟動(dòng)子
shell的腳本,變量已經(jīng)存在于當(dāng)前shell中。
環(huán)境變量持久化
在Linux在大多數(shù)發(fā)行版中,存儲(chǔ)個(gè)人用戶永久性bash shell變量的地方是$HOME/.bashrc文件。這一 點(diǎn)適用于所有類型的shell進(jìn)程。但如果設(shè)置了BASH_ENV變量,那么除非BASH_ENV指向的是 $HOME/.bashrc,否則應(yīng)該將非交互式shell的用戶變量放在別的地方。
在macOS系統(tǒng)中,存儲(chǔ)個(gè)人用戶永久性bash shell變量的地方,便是對(duì)應(yīng)的環(huán)境文件:~/.profile、~/.bash_profile、~/.bashrc(交互式shell生效)。其中~/.profile和~/.bash_profile任意一個(gè)都可以定義我們的永久性bash shell變量。
#在`~/.profile`文件中定義
export LOVE="全局可用的環(huán)境變量love"
PEACE="局部環(huán)境變量,子shell不可用"
#在`~/.bash_profile`文件中定義
export SHARE="全局可用的環(huán)境變量share"
QI="局部環(huán)境變量,子shell不可用"
#終端shell,查看全局變量
env
#輸出
LOVE=全局可用的環(huán)境變量love
SHARE=全局可用的環(huán)境變量share
#終端shell,查看永久局部變量
echo $PEACE
echo $QI
#輸出
局部環(huán)境變量,子shell不可用
#開啟非登陸交互式shell
bash
#子shell,查看可用全局變量
env
#輸出
LOVE=全局可用的環(huán)境變量love
SHARE=全局可用的環(huán)境變量share
#子shell,查看可用的父Shell的局部變量
echo $PEACE
echo $QI
#輸出為空
關(guān)于~/.bashrc,作為非登錄式交互shell的啟動(dòng)文件,僅對(duì)非登錄式交互shell生效:
#在`~/.bashrc`配置
export CHILD="子shell可用"
#重新打開終端,輸入
env
#or
echo $CHILD
#都輸出:空
#開啟子shell(交互式shell)
bash
#查看子shell的全局變量
env
#or
echo $CHILD
#輸出
CHILD=子shell可用
子shell可用
#再次開啟子shell
bash
#輸入
env
#or
echo $CHILD
#輸出
CHILD=子shell可用
子shell可用
#如果`~/.bashrc`文件中配置
CHILD="子shell可用"
#輸入
env
#輸出:空
#輸入
echo $CHILD
#輸出
子shell可用
#再次開啟子shell,`CHILD`變量將不再生效
數(shù)組變量
數(shù)組變量:環(huán)境變量設(shè)置多個(gè)值,放置在括號(hào)中,且值與值之間用空格隔開。
#定義數(shù)組變量
ARRAY_VAR=(one two three)
#輸出數(shù)組變量
echo $ARRAY_VAR
#不會(huì)全部輸出,只會(huì)輸出數(shù)組變量中第一個(gè)元素
one
要引用數(shù)組變量中的某個(gè)元素,就必須用代表它在數(shù)組中位置的數(shù)值索引值。索引值要用方括號(hào)括起來:
echo ${ARRAY_VAR[2]}
#輸出
three
#顯示整個(gè)數(shù)組
echo ${ARRAY_VAR[*]}
可以用unset命令刪除數(shù)組中的某個(gè)值,但是要小心。比如:
#刪除索引為1的元素
unset ARRAY_VAR[1]
#輸出整個(gè)數(shù)組
echo ${ARRAY_VAR[*]}
#刪除成功
one three
#輸出數(shù)組中索引為1的元素
echo ${ARRAY_VAR[1]}
#結(jié)果為:空
#輸出索引為2的元素
echo ${ARRAY_VAR[2]}
#結(jié)果
3
參考資料:
Linux命令行與shell腳本編程大全