腳本基礎
參考資料:Shell Scripts (Bash Reference Manual)
不嚴謹地說,編程語言根據代碼運行的方式,可以分為兩種方式:
- 編譯運行:需要先將人類可識別的代碼檔案編譯成機器可運行的二進制程式檔案后,方可運行,例如C語言和Java語言,
- 解釋運行:需要一個編程語言的解釋器,運行時由解釋器讀取代碼檔案并運行,例如python語言(解釋器:/usr/bin/python)和shell腳本(解釋器:/bin/bash),
根據其是否呼叫OS上的其他應用程式來分來:
- 腳本語言(shell腳本):依賴于bash自身以及OS中的其他應用程式(例如:grep/sed/awk等),
- 完整編程語言:依賴自身的語法和其自身豐富的庫檔案來完成任務,對系統的依賴性很低,例如python、PHP等,
根據編程模型:
- 程序式:以指令為中心來組織代碼,資料是服務于代碼,像C語言和bash,
- 物件式:以資料為中心來組織代碼,圍繞資料組織指令,其編程的程序一般為創建類(class,例如:人類),根據類實體化出物件(例如:阿龍弟弟),物件具有類的通用屬性(例如人類有手有腳,那么阿龍弟弟也有),物件可以具備自己的方法(method,例如寫博客),像Java語言,
像C++和python是既支持面向物件又支持面向程序,
因此我們可以總結出:bash是一門解釋運行的程序式腳本語言,而bash的腳本,是一種將自身的編程邏輯和OS上的命令程式堆砌起來的待執行檔案,
在shell腳本中,第一行我們需要向內核傳達我們這個腳本是使用哪種解釋器(interpreter)來解釋運行,形如:
#!/bin/sh 或者 #!/bin/bash 或者 #!/usr/bin/python
“#!”是固定的格式,叫做shebang或者hashbang,后面跟著的是解釋器程式的路徑,如果是/bin/bash那就是bash腳本,如果是/usr/bin/python那就是python腳本,
shebang是可以添加選項的,例如可以使得腳本在執行時為登錄式(login)shell,
#!/bin/bash -l
bash腳本的檔案的后綴名(亦稱擴展名)一般為“.sh”,這個名稱主要用于讓人易識別用的,具體腳本在執行的時候使用什么解釋器,與檔案的后綴名無關,
腳本還需要具備執行權限,在執行的時候,需要使用相對路徑或者絕對路徑方可正確執行,
~]# cat alongdidi.sh #!/bin/bash ... ~]# chmod a+x alongdidi.sh ~]# ./alongdidi.sh ~]# /PATH/TO/alongdidi.sh
如果直接鍵入腳本的名稱來執行的話,bash會從內置命令、PATH等中尋找alongdidi.sh命令,如果我們的腳本當前路徑不存在于PATH中,就會報錯,
~]# alongdidi.sh bash: alongdidi.sh: command not found...
腳本也可以沒有shebang和執行權限,我們依然可以通過呼叫bash命令,將腳本作為bash的引數來執行,這樣也是可以的,
~]# bash alongdidi.sh
腳本中存在的空白行會被忽略,bash并不會將空白行列印出來,
除了shebang(#!)這種特殊的格式,其余位置出現#,其后面的字符均會被認為是腳本注釋,
Bash執行一個腳本,實際上是在當前shell下創建子shell來執行的,
組態檔
參考資料:
bash啟動時加載組態檔程序 - 駿馬金龍 - 博客園
Bash Startup Files (Bash Reference Manual)
建議英文不好、bash新手直接參考駿馬兄的博文來學習即可,直接跳過官網的參考資料,駿馬兄本人也是基于官網學習并自己反復驗證的,準確率應該很高,可放心,
無論我們直接通過連接至物理服務器/機器的物理設備(滑鼠、鍵盤和顯示幕),還是我們通過SSH客戶端(無論GUI或者CLI)連接至Linux服務器中,系統都會在我們所連接上的終端上啟用一個bash,我們通過這個shell來與OS進行互動,
即使我們執行bash腳本,系統也會創建一個子bash來完成任務,
這些bash在啟動的時候,就需要讀取其組態檔,官方也將其稱之為啟動檔案(startup files),用于使bash在啟動的時候讀取這些檔案并執行其中的指令來設定bash環境,
互動式和登錄式
Bash需要判斷自身是否具備互動式(interactive)和登錄式(login)的特性來決定自己應該讀取哪些組態檔,
判斷shell是否為互動式有兩種方法:
方法一:判斷特殊變數“$-”是否包含字母i,bash還有其他的特殊變數,有興趣的請參考Special Parameters (Bash Reference Manual),
[root@c7-server ~]# echo $- himBH [root@c7-server ~]# cat alongdidi.sh #!/bin/bash echo $- [root@c7-server ~]# bash alongdidi.sh hB
方法二:判斷變數“$PS1”是否為空,互動式登錄會設定該變數,如果變數為空,則為非互動式,否則為互動式,PS1是Prompt String,提示符字串的意思,在官網中它屬于Bourne Shell的變數之一,
[root@c7-server ~]# echo $PS1 [\u@\h \W]\$ [root@c7-server ~]# cat alongdidi.sh #!/bin/bash echo $PS1 [root@c7-server ~]# bash alongdidi.sh [root@c7-server ~]#
判斷shell是否為登錄式,使用bash的內置命令shopt來查看,它和內置命令set一起都用于修改shell的行為,Modifying Shell Behavior (Bash Reference Manual)
[root@c7-server ~]# shopt login_shell login_shell on [root@c7-server ~]# cat alongdidi.sh #!/bin/bash shopt login_shell [root@c7-server ~]# bash alongdidi.sh login_shell off [root@c7-server ~]# bash [root@c7-server ~]# shopt login_shell login_shell off
常見的bash啟動方式
在此之前需要讀者大概了解一下終端的概念,可參考【你真的知道什么是終端嗎? - Linux大神博客】,
PS:最后還把Windows給黑了一下,確實感覺windows應該算不上多用戶,以前維護Windows Server的時候,使用遠程連接只能以超管用戶連接上2或者3個而已,再多就不行了,目前也不曉得為什么,可能windows自身的限制如此,
1、通過Xshell客戶端使用SSH協議登錄,
偽終端,互動式,登錄式,
[root@c7-server ~]# tty /dev/pts/1 [root@c7-server ~]# who am i root pts/1 2019-12-12 15:39 (192.168.152.1) [root@c7-server ~]# echo $PS1; shopt login_shell [\u@\h \W]\$ login_shell on
2、在圖形界面下右擊桌面打開的終端,
偽終端,互動式,非登錄式,
[root@c7-server ~]# tty /dev/pts/0 [root@c7-server ~]# who am i root pts/0 2019-12-12 15:28 (:0) [root@c7-server ~]# echo $PS1; shopt login_shell [\u@\h \W]\$ login_shell off
可通過設定終端的屬性來使其變為登錄式,

該圖形界面,在CentOS 7上本身位于Ctrl+Alt+F1的虛擬終端上,
3、虛擬終端,
通過Ctrl+Alt+Fn來切換,n為正整數,該截圖位于Ctrl+Alt+F2,這種叫虛擬終端,互動式,登錄式,

4、su命令啟動的bash,
不使用login選項的su,互動式,非登錄式,
[root@c7-server ~]# su root [root@c7-server ~]# echo $PS1; shopt login_shell [\u@\h \W]\$ login_shell off
使用login選項的su,互動式,登錄式,
-, -l, --login:使shell為login shell,
[root@c7-server ~]# su - root Last login: Thu Dec 12 16:10:36 CST 2019 on pts/1 [root@c7-server ~]# echo $PS1; shopt login_shell [\u@\h \W]\$ login_shell on
5、通過bash命令啟動的子shell,
一定為互動式,是否登錄式看是否帶-l選項,
[root@c7-server ~]# echo $PS1; shopt login_shell [\u@\h \W]\$ login_shell off [root@c7-server ~]# exit exit [root@c7-server ~]# bash -l [root@c7-server ~]# echo $PS1; shopt login_shell [\u@\h \W]\$ login_shell on
6、命令組合時,
PS:這部分看不懂,來自駿馬金龍,
這種情況下,登錄式與互動式的情況繼承于父shell,
[root@c7-server ~]# (echo $BASH_SUBSHELL; echo $PS1; shopt login_shell) 1 [\u@\h \W]\$ login_shell on [root@c7-server ~]# su [root@c7-server ~]# (echo $BASH_SUBSHELL; echo $PS1; shopt login_shell) 1 [\u@\h \W]\$ login_shell off
7、使用ssh命令遠程執行命令,
非互動式,非登錄式,這種方式,在官網叫做遠程shell,Remote Shell Daemon,
[root@c7-server ~]# ssh localhost 'echo $PS1; shopt login_shell' root@localhost's password: login_shell off
8、運行shell腳本,
通過bash命令運行,非互動式,是否登錄式根據是否帶-l選項,
[root@c7-server ~]# cat alongdidi.sh #!/bin/bash echo $PS1 shopt login_shell [root@c7-server ~]# bash alongdidi.sh login_shell off [root@c7-server ~]# bash -l alongdidi.sh login_shell on
檔案具備執行權限后直接運行,非互動式,非登錄式,
[root@c7-server ~]# ./alongdidi.sh login_shell off
如果shebang具備了-l選項,那么直接運行為非互動式、登錄式,
通過不帶-l選項的bash執行,依然是非互動式,非登錄式,
也就是說是否為登錄式,先看CLI中的bash是否帶-l選項,再看shebang是否帶-l選項,均為非互動式,
[root@c7-server ~]# cat alongdidi.sh #!/bin/bash -l echo $PS1 shopt login_shell [root@c7-server ~]# ./alongdidi.sh login_shell on [root@c7-server ~]# bash alongdidi.sh login_shell off
組態檔的加載方式
在bash中,加載組態檔的方式是通過讀取命令來實作的,它們是bash的內置命令source和“.”,
source filename [arguments]
. filename [arguments]
注意這里是一個單獨的小數點,是一個bash內置命令,如果有arguments的話就作為位置引數,
本質上是讀取了檔案并在當前的shell下執行檔案中的命令,(不同于shell腳本的執行是需要創建子shell)
bash相關的組態檔,主要有這些:
/etc/profile ~/.bash_profile ~/.bashrc /etc/bashrc /etc/profile.d/*.sh
注意:這些組態檔,一般是都要求要具備可讀取的權限才行(雖然對于root用戶可能無所謂)
位于用戶家目錄下的組態檔,為用戶私有的組態檔,只有對應的用戶才會加載,可實作針對用戶的定制,位于/etc/目錄下的組態檔,可以理解為全域組態檔,對所有用戶生效,
為了測驗不同的bash啟動場景會加載哪些檔案,我們在上述檔案的末尾處加上一句echo陳述句,注意,我們是在檔案的末尾加的echo陳述句,bash腳本的執行是按順序自上而下執行,位置很關鍵,
echo "echo '/etc/profile goes'" >>/etc/profile echo "echo '~/.bash_profile goes'" >>~/.bash_profile echo "echo '~/.bashrc goes'" >>~/.bashrc echo "echo '/etc/bashrc goes'" >>/etc/bashrc echo "echo '/etc/profile.d/test.sh goes'" >>/etc/profile.d/test.sh
1、只要是登錄式(無論是否互動式)的bash:先讀取/etc/profile,再依次搜索~/.bash_profile、~/.bash_login和~/.profile并僅加載第一個搜索到的且可讀的檔案,在bash退出時,讀取~/.bash_logout,
在/etc/profile檔案中,有讀取指令:
for i in /etc/profile.d/*.sh /etc/profile.d/sh.local ; do if [ -r "$i" ]; then if [ "${-#*i}" != "$-" ]; then . "$i" else . "$i" >/dev/null fi fi done
判斷/etc/profile.d/目錄下的*.sh和sh.local檔案是否存在且可讀,如果是的話,則讀取,紅色粗體字的判斷,是判斷是否為互動式的bash,如果是的話在讀取組態檔時輸出STDOUT,否則不輸出,
在CentOS 6中沒有/etc/profile.d/sh.local檔案,也沒有加載該檔案的指令,在CentOS 7上,這個檔案也只有一行注釋,以我蹩腳的英文水平,我猜應該是用來填寫一些環境變數,可用于覆寫掉/etc/profile中的環境變數,
~]# cat /etc/profile.d/sh.local #Add any required envvar overrides to this file, it is sourced from /etc/profile
對于root用戶來說,由于存在~/.bash_profile檔案且可讀(在我的測驗環境中,普通用戶也具備有可讀的~/.bash_profile),因此~/.bash_login和~/.profile就被忽略了,
在~/.bash_profile中,有讀取指令:
PS:記得留意那段英文注釋,
# Get the aliases and functions if [ -f ~/.bashrc ]; then . ~/.bashrc fi
在~/.bashrc中,也有讀取指令:
# Source global definitions if [ -f /etc/bashrc ]; then . /etc/bashrc fi
在/etc/bashrc中,雖然有讀取指令,但是這部分指令是在非登錄式的情況下才執行:
if ! shopt -q login_shell ; then # We're not a login shell ... for i in /etc/profile.d/*.sh; do if [ -r "$i" ]; then if [ "$PS1" ]; then . "$i" else . "$i" >/dev/null fi fi done ... fi
圖示如下,按編號順序,首先加載第一條,加載完再加載第二條,

我們來測驗之前所述的幾種bash啟動場景來看看,注意,必須得是登錄式的才行,因為我們這個小節討論的是登錄式的,
I. Xshell客戶端,偽終端登錄,互動式登錄式,
/etc/profile.d/test.sh goes /etc/profile goes /etc/bashrc goes ~/.bashrc goes ~/.bash_profile goes
之所以后加載的先顯示,那是因為我們的echo陳述句是添加在腳本的末尾,而讀取后續組態檔是在腳本的中間段,
II. ssh遠程登陸,互動式登錄式,
[root@c7-server ~]# ssh localhost root@localhost's password: Last login: Fri Dec 13 16:01:43 2019 from 192.168.152.1 /etc/profile.d/test.sh goes /etc/profile goes /etc/bashrc goes ~/.bashrc goes ~/.bash_profile goes
III. 啟動帶有登錄選項的子shell,
~]# bash -l /etc/profile.d/test.sh goes /etc/profile goes /etc/bashrc goes ~/.bashrc goes ~/.bash_profile goes
IV. 登錄式切換用戶,
~]# su -l Last login: Fri Dec 13 16:03:20 CST 2019 from localhost on pts/3 /etc/profile.d/test.sh goes /etc/profile goes /etc/bashrc goes ~/.bashrc goes ~/.bash_profile goes
V. 執行腳本時,帶有登錄選項,
[root@c7-server ~]# cat a.sh #!/bin/bash -l echo 'haha' [root@c7-server ~]# ./a.sh /etc/profile goes /etc/bashrc goes ~/.bashrc goes ~/.bash_profile goes haha [root@c7-server ~]# bash -l a.sh /etc/profile goes /etc/bashrc goes ~/.bashrc goes ~/.bash_profile goes haha
執行腳本屬于非互動式,而在非互動式場景下讀取/etc/profile.d/*.sh檔案時,不會有輸出,(在/etc/profile檔案中有定義,可以翻上去看)
. "$i" >/dev/null 2>&1
因此就不會輸出:
/etc/profile.d/test.sh goes
注意,僅僅只是不輸出而已,但是還是有加載了組態檔的,如果涉及到比如環境變數的設定等,還是會執行的,
2、互動式但非登錄式的bash:讀取~/.bashrc檔案,不讀取/etc/profile、~/.bash_profile、~/.bash_login和~/.profile,

對應的場景為不帶登錄選項的子bash創建或者su用戶切換,
[root@c7-server ~]# bash /etc/profile.d/test.sh goes /etc/bashrc goes ~/.bashrc goes [root@c7-server ~]# su /etc/profile.d/test.sh goes /etc/bashrc goes ~/.bashrc goes
3、非互動式非登錄式的bash,
不加載任何的組態檔,嘗試展開環境變數BASH_ENV(這個變數一般是存盤了某些個組態檔的路徑),若有值則加載對應的組態檔,行為如下:
if [ -n "$BASH_ENV" ]; then . "$BASH_ENV"; fi
正常在撰寫和執行bash腳本時,都不會刻意加上登錄選項,因此幾乎所有的bash腳本的執行都屬于這種情況,
存在一種非互動式非登錄式的bash特例,不使用這種組態檔加載方式,看下一個例子,
4、非互動式非登錄式的bash特例:遠程shell(Remote Shell Daemon),
加載方式如下圖所示,

由于是非登錄式的shell,因此在讀取*.sh的時候不輸出,
[root@c7-server ~]# ssh localhost echo 'Remote Shell Daemon' root@localhost's password: /etc/bashrc goes ~/.bashrc goes Remote Shell Daemon
轉載請註明出處,本文鏈接:https://www.uj5u.com/caozuo/147623.html
標籤:Linux
上一篇:CentOS自行編譯升級Git
