手機版
你好,游客 登錄 注冊
背景:
閱讀新聞

Bash腳本編程之腳本基礎和bash配置文件

[日期:2020-01-06] 來源:cnblogs.com/alongdidi  作者: 阿龍弟弟 [字體: ]

腳本基礎

參考資料: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來執行的。

配置文件

無論我們直接通過連接至物理服務器/機器的物理設備(鼠標、鍵盤和顯示器),還是我們通過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)。

[[email protected] ~]# echo $-
himBH
[[email protected]-server ~]# cat alongdidi.sh 
#!/bin/bash
echo $-
[[email protected]-server ~]# bash alongdidi.sh
hB

方法二:判斷變量“$PS1”是否為空。交互式登錄會設置該變量,如果變量為空,則為非交互式,否則為交互式。PS1是Prompt String,提示符字符串的意思,在官網中它屬于Bourne Shell的變量之一。

[[email protected] ~]# echo $PS1
[\[email protected]\h \W]\$
[[email protected]-server ~]# cat alongdidi.sh 
#!/bin/bash
echo $PS1
[[email protected]-server ~]# bash alongdidi.sh

[[email protected]-server ~]#

判斷shell是否為登錄式,使用bash的內置命令shopt來查看。它和內置命令set一起都用于修改shell的行為。Modifying Shell Behavior (Bash Reference Manual)

[[email protected] ~]# shopt login_shell 
login_shell        on
[[email protected]-server ~]# cat alongdidi.sh 
#!/bin/bash
shopt login_shell
[[email protected]-server ~]# bash alongdidi.sh
login_shell        off
[[email protected]-server ~]# bash
[[email protected]-server ~]# shopt login_shell 
login_shell        off

常見的bash啟動方式

PS:最后還把Windows給黑了一下。確實感覺windows應該算不上多用戶,以前維護Windows Server的時候,使用遠程連接只能以超管用戶連接上2或者3個而已,再多就不行了。目前也不曉得為什么,可能windows自身的限制如此。

1、通過Xshell客戶端使用SSH協議登錄。

偽終端。交互式,登錄式。

[[email protected] ~]# tty
/dev/pts/1
[[email protected]-server ~]# who am i
root     pts/1        2019-12-12 15:39 (192.168.152.1)
[[email protected]-server ~]# echo $PS1; shopt login_shell 
[\[email protected]\h \W]\$
login_shell        on

2、在圖形界面下右擊桌面打開的終端。

 偽終端。交互式,非登錄式。

[[email protected] ~]# tty
/dev/pts/0
[[email protected]-server ~]# who am i
root     pts/0        2019-12-12 15:28 (:0)
[[email protected]-server ~]# echo $PS1; shopt login_shell 
[\[email protected]\h \W]\$
login_shell        off

可通過設置終端的屬性來使其變為登錄式。

 

該圖形界面,在CentOS 7上本身位于Ctrl+Alt+F1的虛擬終端上。

3、虛擬終端。

通過Ctrl+Alt+Fn來切換,n為正整數,該截圖位于Ctrl+Alt+F2,這種叫虛擬終端。交互式,登錄式。

 

4、su命令啟動的bash。

不使用login選項的su。交互式,非登錄式。

[[email protected] ~]# su root
[[email protected]-server ~]# echo $PS1; shopt login_shell 
[\[email protected]\h \W]\$
login_shell        off

使用login選項的su。交互式,登錄式。

-, -l, --login:使shell為login shell。

[[email protected] ~]# su - root
Last login: Thu Dec 12 16:10:36 CST 2019 on pts/1
[[email protected]-server ~]# echo $PS1; shopt login_shell 
[\[email protected]\h \W]\$
login_shell        on

5、通過bash命令啟動的子shell。

一定為交互式,是否登錄式看是否帶-l選項。

[[email protected] ~]# echo $PS1; shopt login_shell 
[\[email protected]\h \W]\$
login_shell        off
[[email protected]-server ~]# exit
exit
[[email protected]-server ~]# bash -l
[[email protected]-server ~]# echo $PS1; shopt login_shell 
[\[email protected]\h \W]\$
login_shell        on

6、命令組合時。

PS:這部分看不懂,來自駿馬金龍。

這種情況下,登錄式與交互式的情況繼承于父shell。

[[email protected] ~]# (echo $BASH_SUBSHELL; echo $PS1; shopt login_shell)
1
[\[email protected]\h \W]\$
login_shell        on
[[email protected]-server ~]# su
[[email protected]-server ~]# (echo $BASH_SUBSHELL; echo $PS1; shopt login_shell)
1
[\[email protected]\h \W]\$
login_shell        off

7、使用ssh命令遠程執行命令。

非交互式,非登錄式。這種方式,在官網叫做遠程shell,Remote Shell Daemon。

[[email protected] ~]# ssh localhost 'echo $PS1; shopt login_shell'
[email protected]'s password: 

login_shell        off

8、運行shell腳本。

通過bash命令運行。非交互式,是否登錄式根據是否帶-l選項。

[[email protected] ~]# cat alongdidi.sh
#!/bin/bash
echo $PS1
shopt login_shell
[[email protected]-server ~]# bash alongdidi.sh

login_shell        off
[[email protected]-server ~]# bash -l alongdidi.sh

login_shell        on

文件具備執行權限后直接運行。非交互式,非登錄式。

[[email protected] ~]# ./alongdidi.sh 

login_shell        off

如果shebang具備了-l選項,那么直接運行為非交互式、登錄式。

通過不帶-l選項的bash執行,依然是非交互式,非登錄式。

也就是說是否為登錄式,先看CLI中的bash是否帶-l選項,再看shebang是否帶-l選項。均為非交互式。

[[email protected] ~]# cat alongdidi.sh 
#!/bin/bash -l
echo $PS1
shopt login_shell
[[email protected]-server ~]# ./alongdidi.sh 

login_shell        on
[[email protected]-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遠程登陸。交互式登錄式。

[[email protected] ~]# ssh localhost
[email protected]'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. 執行腳本時,帶有登錄選項。

[[email protected] ~]# cat a.sh 
#!/bin/bash -l
echo 'haha'
[[email protected]-server ~]# ./a.sh 
/etc/profile goes
/etc/bashrc goes
~/.bashrc goes
~/.bash_profile goes
haha
[[email protected]-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用戶切換。

[[email protected] ~]# bash
/etc/profile.d/test.sh goes
/etc/bashrc goes
~/.bashrc goes
[[email protected]-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的時候不輸出。

[[email protected] ~]# ssh localhost echo 'Remote Shell Daemon'
[email protected]'s password: 
/etc/bashrc goes
~/.bashrc goes
Remote Shell Daemon
linux
本文評論   查看全部評論 (0)
表情: 表情 姓名: 字數

       

評論聲明
  • 尊重網上道德,遵守中華人民共和國的各項有關法律法規
  • 承擔一切因您的行為而直接或間接導致的民事或刑事法律責任
  • 本站管理人員有權保留或刪除其管轄留言中的任意內容
  • 本站有權在網站內轉載或引用您的評論
  • 參與本評論即表明您已經閱讀并接受上述條款
彩票投注骗局