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

来源:https://www.cnblogs.com/alongdidi/archive/2019/12/13/bash_basic_and_startup_files.html
-Advertisement-
Play Games

腳本基礎 參考資料:Shell Scripts (Bash Reference Manual) 不嚴謹地說,編程語言根據代碼運行的方式,可以分為兩種方式: 編譯運行:需要先將人類可識別的代碼文件編譯成機器可運行的二進位程式文件後,方可運行。例如C語言和Java語言。 解釋運行:需要一個編程語言的解釋 ...


腳本基礎

參考資料: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

您的分享是我們最大的動力!

-Advertisement-
Play Games
更多相關文章
  • 場景 效果 註: 博客主頁: https://blog.csdn.net/badao_liumang_qizhi 關註公眾號 霸道的程式猿 獲取編程相關電子書、教程推送與免費下載。 實現 新建form窗體,窗體中設計佈局如下,一個Panel中拖拽四個label,然後添加兩個Button,再添加一個T ...
  • 場景 向窗體中拖拽照片並顯示效果 向窗體中拖拽文件夾並顯示樹形結構效果 註: 博客主頁: https://blog.csdn.net/badao_liumang_qizhi 關註公眾號 霸道的程式猿 獲取編程相關電子書、教程推送與免費下載。 實現 新建一個窗體,在窗體中拖拽一個Panel控制項,再在P ...
  • 認識Swagger Swagger 是一個規範和完整的框架,用於生成、描述、調用和可視化 RESTful 風格的 Web 服務。總體目標是使客戶端和文件系統作為伺服器以同樣的速度來更新。文件的方法,參數和模型緊密集成到伺服器端的代碼,允許API來始終保持同步。作用: 介面的文檔線上自動生成。 功能測 ...
  • 頁面跳轉加密URL地址參數傳遞 window.location.href="foot.html?"+"good="+encodeURI(encodeURI(goodid)); 頁面跳轉解密 URL參數 ...
  • 場景 效果 註: 博客主頁: https://blog.csdn.net/badao_liumang_qizhi 關註公眾號 霸道的程式猿 獲取編程相關電子書、教程推送與免費下載。 實現 新建一個form窗體,然後在窗體中拖拽一個ListView。 在form的shown事件中進行窗體的初始化。 p ...
  • 問題描述:Ubuntu 1604 新環境下使用apt安裝的mariadb10版本,結果第二天就起不來了,很是鬱悶 啟動時會卡住,當時就慌了,這什麼情況啊,昨天好好的今天就起不來了,過了一會兒就有返回信息了 啟動失敗,先查看一下狀態 肯定也是不正常的,看一下有沒有什麼錯誤輸出信息 然後再使用這個查看詳 ...
  • 背景 By 魯迅 By 高爾基 說明: 1. Kernel版本:4.14 2. ARM64處理器,Contex A53,雙核 3. 使用工具:Source Insight 3.5, Visio 1. 概述 這篇文章,讓我們來看看用戶態進程的地址空間情況,主要會包括以下: ; ; ; 進程地址空間中, ...
  • 蜂鳴器從結構區分分為壓電式蜂鳴器和電磁式蜂鳴器。壓電式為壓電陶瓷片發音,電流比較小一些,電磁式蜂鳴器為線圈通電震動發音,體積比較小。按照驅動方式分為有源蜂鳴器和無源蜂鳴器。這裡的有源和無源不是指電源,而是振蕩源。有源蜂鳴器內部帶了振蕩源,如圖1中所示,給了 BUZZ 引腳一個低電平,蜂鳴器就會直接響 ...
一周排行
    -Advertisement-
    Play Games
  • 移動開發(一):使用.NET MAUI開發第一個安卓APP 對於工作多年的C#程式員來說,近來想嘗試開發一款安卓APP,考慮了很久最終選擇使用.NET MAUI這個微軟官方的框架來嘗試體驗開發安卓APP,畢竟是使用Visual Studio開發工具,使用起來也比較的順手,結合微軟官方的教程進行了安卓 ...
  • 前言 QuestPDF 是一個開源 .NET 庫,用於生成 PDF 文檔。使用了C# Fluent API方式可簡化開發、減少錯誤並提高工作效率。利用它可以輕鬆生成 PDF 報告、發票、導出文件等。 項目介紹 QuestPDF 是一個革命性的開源 .NET 庫,它徹底改變了我們生成 PDF 文檔的方 ...
  • 項目地址 項目後端地址: https://github.com/ZyPLJ/ZYTteeHole 項目前端頁面地址: ZyPLJ/TreeHoleVue (github.com) https://github.com/ZyPLJ/TreeHoleVue 目前項目測試訪問地址: http://tree ...
  • 話不多說,直接開乾 一.下載 1.官方鏈接下載: https://www.microsoft.com/zh-cn/sql-server/sql-server-downloads 2.在下載目錄中找到下麵這個小的安裝包 SQL2022-SSEI-Dev.exe,運行開始下載SQL server; 二. ...
  • 前言 隨著物聯網(IoT)技術的迅猛發展,MQTT(消息隊列遙測傳輸)協議憑藉其輕量級和高效性,已成為眾多物聯網應用的首選通信標準。 MQTTnet 作為一個高性能的 .NET 開源庫,為 .NET 平臺上的 MQTT 客戶端與伺服器開發提供了強大的支持。 本文將全面介紹 MQTTnet 的核心功能 ...
  • Serilog支持多種接收器用於日誌存儲,增強器用於添加屬性,LogContext管理動態屬性,支持多種輸出格式包括純文本、JSON及ExpressionTemplate。還提供了自定義格式化選項,適用於不同需求。 ...
  • 目錄簡介獲取 HTML 文檔解析 HTML 文檔測試參考文章 簡介 動態內容網站使用 JavaScript 腳本動態檢索和渲染數據,爬取信息時需要模擬瀏覽器行為,否則獲取到的源碼基本是空的。 本文使用的爬取步驟如下: 使用 Selenium 獲取渲染後的 HTML 文檔 使用 HtmlAgility ...
  • 1.前言 什麼是熱更新 游戲或者軟體更新時,無需重新下載客戶端進行安裝,而是在應用程式啟動的情況下,在內部進行資源或者代碼更新 Unity目前常用熱更新解決方案 HybridCLR,Xlua,ILRuntime等 Unity目前常用資源管理解決方案 AssetBundles,Addressable, ...
  • 本文章主要是在C# ASP.NET Core Web API框架實現向手機發送驗證碼簡訊功能。這裡我選擇是一個互億無線簡訊驗證碼平臺,其實像阿裡雲,騰訊雲上面也可以。 首先我們先去 互億無線 https://www.ihuyi.com/api/sms.html 去註冊一個賬號 註冊完成賬號後,它會送 ...
  • 通過以下方式可以高效,並保證數據同步的可靠性 1.API設計 使用RESTful設計,確保API端點明確,並使用適當的HTTP方法(如POST用於創建,PUT用於更新)。 設計清晰的請求和響應模型,以確保客戶端能夠理解預期格式。 2.數據驗證 在伺服器端進行嚴格的數據驗證,確保接收到的數據符合預期格 ...