鳥哥的 Linux 私房菜
<<

第 7 堂課:認識 bash 基礎與系統救援

最近更新日期:2016/07/14

前一節課談到檔案系統且最終有一個簡單的檔案系統錯誤救援。但如果發生嚴重問題時該如何是好?此時可能需要一個簡易的救援模式, 包括透過 systemd 以及直接取得一個 bash 來處理。那如何使用 bash ?這就需要了解一下 bash shell 的功能了。

7.1:bash shell 基礎認識

之前課程講過登入系統取得的文字型互動界面就稱為 shell,shell 的操作環境能夠依據使用者的喜好來設定,使用者也能夠切換不同的 shell。 而 shell 最重要的就是變數,這在許多的程式語言裡面都是需要注意到的部份。

7.1.1:系統與使用者的 shell

系統所有合法的 shell 都在 /etc/shells 這個檔案內,讀者可以查詢該檔案的內容。在 /etc/shells 常見的合法 shell 如下:

  • /bin/sh (已經被 /bin/bash 所取代)
  • /bin/bash (就是 Linux 預設的 shell)
  • /bin/tcsh (整合 C Shell ,提供更多的功能)
  • /bin/csh (已經被 /bin/tcsh 所取代)

因為有許多軟體會使用到系統上的 shell,但又擔心使用者或者是惡意攻擊者會使用怪異的有問題的 shell 來操作軟體。因此某些軟體在判斷 shell 的合法性, 亦即直接參考 /etc/shells 的規範,來判斷所謂的合法與不合法。

讀者從之前的系統登入行為中,應該知道在文字界面登入後系統會給予一個 shell,而在圖形界面時,也能夠藉由按下『終端機』來取得 shell 的操作。 但取得的預設 shell 是哪一個?需要從使用者的設定資料裡面搜尋。請參考 /etc/passwd 裡面,使用冒號 (:) 分隔的第 7 個欄位,就是該帳號預設取得的 shell。

例題:
  1. 請使用 cut 這個指令,將 /etc/passwd 這個檔案的內容中,以冒號 (:) 為分隔字元 (delimiter),將第 1 及第 7 欄位 (field) 輸出到螢幕上
  2. 承上,找到關鍵字為 daemon 的那一行,daemon 用戶所使用的 shell 是什麼?

使用者可以自由的切換所需要的 shell,不過不同的 shell 使用的方式、語法都有點差異。舉例來說, bash 使用的變數設定方式為 『var='content'』, 但是 csh 使用的是『 set var = 'content' 』,csh 需要有 set ,不過等號兩邊可以有空格。但是 bash 雖然不用 set ,但是等號兩邊不可以直接加空格, 這就有不一樣的的地方。

例題:練習不同 shell 的切換
  1. 請使用 student 身份登入系統,取得終端機後,使用『 echo $BASH 』的方式查閱有沒有這個變數以及其輸出的內容
  2. 請輸入『 echo $shell 』觀察有沒有資料輸出?
  3. 使用『 /bin/csh 』切換 shell 成為 c shell
  4. 分別使用『 echo $BASH 』與『 echo $shell 』觀察輸出的資料為何?
  5. 使用『 echo $0 』觀察輸出的資料是什麼?
  6. 先透過『 exit 』離開 c shell 之後,再次以 echo $0 觀察目前的 shell 名稱為何?
  7. 執行『 /sbin/nologin 』看看輸出的資料為何?

使用者可以透過直接輸入 shell 的執行檔 (例如上述的 /bin/csh) 來直接切換到新的 shell 去工作,而想要確認目前的 shell 是什麼, 最簡單的方式就是使用『 echo $0 』列出目前的執行檔。另外,寫入在 /etc/shells 內有個名為 /sbin/nologin 的 shell,就是給系統帳號預設使用的不可互動的合法 shell 。

例題:
  1. 請使用 usermod 來修改 student 的 shell 變成 /sbin/nologin
  2. 修改完畢後,請到 tty3 的終端機,嘗試使用 student 的帳號登入,看看會出現什麼情況
  3. 請再次以 usermod 的方式將 student 的 shell 改回來 /bin/bash

為何需要設定 /sbin/nologin 呢?

  • 許多系統預設要執行的軟體,例如 mail 的郵件分析、WWW 的網頁回應等等,系統不希望該軟體使用 root 的權限,因為擔心網路軟體會被惡意人士所攻擊。 因此系統就會依據該軟體的特性給予『系統帳號』,這些系統帳號就是有特殊的任務(執行某軟體)而產生的,並不是要讓一般用戶透過該帳號來登入系統互動。 因此系統帳號通常就是使用 /sbin/nologin 作為預設 shell
  • 某些伺服器的帳號,例如郵件伺服器、FTP 伺服器等,這些伺服器的帳號本收就在收發 email 或者是傳輸檔案而已,這些帳號無須登入系統來取得互動 shell, 因此這些帳號就不需要可互動的 shell,此時就能給 /sbin/nologin。
例題:
  1. 使用 id 這個指令檢查系統有無 bin 與 student 這兩個帳號的存在?
  2. 能不能在不知道密碼的情況下,使用 root 切換成 student 這個帳號?為什麼?
  3. 能不能在不知道密碼的情況下,使用 root 切換成 bin 這個帳號?為什麼?
  4. 建立一個不可登入系統取得互動 shell 的帳號,帳號名稱為 puser1,密碼為 MyPuser1
  5. 嘗試在 tty3 登入該帳號,結果是?

7.1.2:變數設定規則

上一小節談到的『 echo $BASH 』就是變數的功能,bash shell 會主動的建立 BASH 這個變數,且其內容就是 /bin/bash。 方便讀者了解到目前的 shell 是哪隻程式達成的。如何設定變數呢?簡單的設定方式與呼叫方式為:

[student@localhost ~]$ 變數="變數內容"
[student@localhost ~]$ echo $變數
[student@localhost ~]$ echo ${變數}

其中變數有許多的設定規則需要遵守:

  • 變數與變數內容以一個等號『=』來連結
  • 等號兩邊不能直接接空白字元
  • 變數名稱只能是英文字母與數字,但是開頭字元不能是數字
  • 變數內容若有空白字元可使用雙引號『"』或單引號『'』將變數內容結合起來
  • 承上,雙引號內的特殊字元如 $ 等,可以保有原本的特性
  • 承上,單引號內的特殊字元則僅為一般字元 (純文字)
  • 可用跳脫字元『 \ 』將特殊符號(如 [Enter], $, \, 空白字元, ' 等)變成一般字元
  • 在一串指令的執行中,還需要藉由其他額外的指令所提供的資訊時,可以使用反單引號『`指令`』或 『$(指令)』。
  • 若該變數為擴增變數內容時,則可用 "$變數名稱" 或 ${變數} 累加內容
  • 若該變數需要在其他子程序執行,則需要以 export 來使變數變成環境變數
  • 通常大寫字元為系統預設變數,自行設定變數可以使用小寫字元,方便判斷 (純粹依照使用者興趣與嗜好)
  • 取消變數的方法為使用 unset :『unset 變數名稱』
例題:
  1. 設定一個名為 myname 的變數,變數的內容為『 peter pan 』
  2. 使用 echo 呼叫出 myname 的內容
  3. 是否能夠設定 2myname 的內容為『 peter pan 』呢?
  4. 設定 varsymbo 變數的內容為『 $var 』,$var 就是純文字資料不是變數。設定完畢後呼叫出來
  5. 設定 hero 變數的內容為『 I am $myname 』,其中 $myname 會依據 myname 變數的內容而變化。設定完畢請呼叫出來。
  6. 使用 uname -r 秀出目前的核心版本
  7. 設定 kver 變數,內容為『 my kernel version is 3.xx 』,其中 3.xx 為 uname -r 輸出的資訊。請注意, kver 變數設定過程中,需要用到 uname -r 這個指令的協助。

變數設定的過程當中,使用子指令『 $(command) 』的操作為相當重要的。例如底下的案例中,管理員可以很快速的找到前一堂課談到的特殊權限檔案並列出該檔案的權限:

例題:
  1. 使用『 find /usr/bin /usr/sbin -perm /6000 』找出所有含有特殊權限的檔名
  2. 使用 man find 找出 -perm 的功能為何?
  3. 使用『 ls -l $(find /usr/bin /usr/sbin -perm /6000) 』將所有檔名的權限列出

如上的第 3 個範例,讀者可以將第 1 個範例的檔名找到後,一個一個以 ls -l 去查詢權限,不過效能與時間花費太多。 此時透過子指令的功能即可快速的找到相對應的資料。底下亦為常見的操作功能:

例題:
  1. 使用 find 的功能,找出在 /usr/sbin 及 /usr/bin 底下權限為 4755 的檔案
  2. 建立 /root/findfile 目錄
  3. 將步驟 1 找到的檔案連同權限複製到 /root/findfile 目錄下。

7.1.3:影響操作行為的變數

某些變數會影響到使用者的操作行為,許多變數之前曾經提及,本節集中說明。

變數功能
LANG
LC_ALL
語系資料,例如使用 date 輸出資訊時,透過 LANG 可以修改輸出的訊息資料。
PATH 執行檔搜尋的路徑~目錄與目錄中間以冒號(:)分隔,由於執行檔/指令的搜尋是依序由 PATH 的變數內的目錄來查詢,所以,目錄的順序也是重要的。
HOME 代表使用者的家目錄,亦即使用者看到的 ~ 代表的目錄。
MAIL 當我們使用 mail 這個指令在收信時,系統會去讀取的郵件信箱檔案 (mailbox)。
HISTSIZE 這個與『歷史命令』有關。我們曾經下達過的指令可以被系統記錄下來,而記錄的『筆數』則是由這個值來設定的。
RANDOM 『隨機亂數』的變數。目前大多數的 distributions 都會有亂數產生器,亦即 /dev/random 檔案。讀者可以透過這個亂數檔案相關的變數 ($RANDOM) 來隨機取得亂數。在 BASH 的環境下,RANDOM 變數的內容介於 0~32767 之間,所以,你只要 echo $RANDOM 時,系統就會主動的隨機取出一個介於 0~32767 的數值。
PS1 命令提示字元,可使用 man bash 搜尋 PS1 關鍵字,即可了解提示字元的設定方式。
? $? 這個變數內容為指令的回傳值,當回傳值為 0 代表指令正常運作結束,當不為 0 則代表指令有錯誤。

比較需要注意到的變數是 PATH 路徑搜尋變數,他會影響到使用者操作的行為,設定錯誤會有相當嚴重的後果。

例題:關於 PATH 的重要性
  1. 印出 PATH 這個變數的內容,並觀察每個項目中間的分隔符號為何?
  2. 設定一個名為 oldpath 的變數,內容就是 ${PATH}
  3. 設定 PATH 的內容成為 /sbin 而已
  4. 此時輸入 ls 或 ll 或 cat /etc/hosts 等,螢幕顯示的訊息為何?
  5. 若使用 /bin/ls 或 /bin/ls -l 或 /bin/cat /etc/hosts ,可以正常顯示嘛?
  6. 請設定 PATH 的內容成為 ${oldpath} ,恢復正常的路徑資料。
  7. 建立 ~student/cmd/ 目錄,且將 /bin/cat 複製成為 ~student/cmd/scat
  8. 輸入『 ~student/cmd/scat /etc/hosts 』確認指令正常無誤。
  9. 輸入『 scat /etc/hosts 』會發生什麼問題?
  10. 如何讓 student 用戶直接使用 scat 而不須使用 ~student/cmd/scat 來執行?

命令提示字元在每個系統中都不一樣,但那是可以修改的,就透過 PS1 這個變數來修改即可。

例題:
  1. 呼叫出 PS1 這個變數的內容
  2. 請查詢上述變數內容當中 \W 及 \$ 的意義為何 (請 man bash 透過 PS1 關鍵字查詢)
  3. 假設操作者已經做了 15 個指令,則命令提示字元輸出如:『 [student@localhost 15 ~]$ 』該如何設定 PS1?

7.1.4:區域/全域變數、父程序與子程序

變數是有使用範圍的,一般來說變數的使用範圍分為:

  • 區域變數:變數只能在目前這個 shell 當中存在,不會被子程序所沿用
  • 全域變數:變數會儲存在一個共用的記憶體空間,可以讓子程序繼承使用。

如 7.1.2 當中提到的將變數提昇成為全域變數的方式為透過 export,觀察可用 env 或 export 來觀察。

例題:
  1. 使用 set 或 env 或 export 觀察是否存在 mypp 這個變數?
  2. 設定 mypp 的內容為『 from_ppid 』,並且呼叫出來
  3. 使用 set 或 env 或 export 觀察是否存在 mypp 這個變數?
  4. 執行『 /bin/bash 』進入下一個 bash 的子程序環境中
  5. 使用 set 或 env 或 export 觀察是否存在 mypp 這個變數?同時說明為什麼?
  6. 設定 mypp2 的內容為『 from_cpid 』,並且呼叫出來
  7. 使用『 exit 』離開子程序回到原本的父程序
  8. 觀察是否存在 mypp2 這個變數?為什麼?
  9. 使用『 export mypp 』後,使用 env 或 export 觀察是否存在?
  10. 執行『 /bin/bash 』進入下一個 bash 的子程序環境中
  11. 使用 set 或 env 或 export 觀察是否存在 mypp 這個變數?同時說明為什麼?
  12. 回到原本的父程序中。

基本上,由原本的 bash 衍生出來的程序都是該 bash 的子程序,而 bash 可以執行 bash 產生一隻 bash 的子程序,兩隻 bash 之間僅有全域變數 (環境變數) 會帶給子程序, 而子程序的變數,基本上是不會回傳給父程序的。


7.1.5:使用 kill 管理程序

某些時刻管理員會有想要手動移除某些特定程序的時刻發生,例如某些很佔資源的 bash 程序的管理等等,此時就可以透過使用 kill 這個指令來處理。 基本上,kill 並不是『刪除』程序,而是給予程序一個『訊號 (signal) 』來管理,預設的訊號為 15 號,該訊號的功能為『正常關閉程序』的意思。 而想要強制關閉該程式,就得要使用 -9 這個號碼來處理了。

例題:
  1. 使用 vim & 將 vim 程序放進背景中暫停
  2. 使用 jobs -l 進一步列出該程序的 PID 號碼
  3. 使用『 kill PID號碼 』嘗試刪除該工作,是否能夠生效?
  4. 若無法刪除,請使用『 kill -9 PID號碼 』的方式來刪除,是否能夠生效?

若使用者有特別的需求需要刪除掉某些特定的程序,就可以透過這樣的機制來處理。


7.1.6:login shell and non-login shell

當讀者下達『 echo ${PS1} 』時,應該有發現 PS1 這個影響操作行為的變數已經設定好了,故應該理解為已經有設定檔在協助使用者登入時規劃好操作環境的流程。 而讀者應該也會發現到,取得 bash 的情況有很多種,但大致可分為兩大類:

  • 一種是需要輸入帳號與密碼才能夠取得 bash 的行為,例如從 tty2 登入,或者是輸入『 su - 』來取得某個帳號的使用權,這種情況被稱為是 login shell 的變數設定檔讀取方式。
  • 一種是使用者已經取得 bash 或者是其他的互動界面,然後透過該次登入後執行 bash ,例如從圖形界面按下終端機、直接在文字界面輸入 bash 來取得 bash 子程序、輸入『 su 』來切換身份等等, 這種方式通常不需要重新輸入帳號與密碼,因此稱為 non-login shell 的變數設定檔讀取方式。

通常 login shell 讀取設定檔的流程是:

  1. /etc/profile:這是系統整體的設定,你最好不要修改這個檔案;
  2. ~/.bash_profile 或 ~/.bash_login 或 ~/.profile (只會讀 1 個,依據優先順序決定):屬於使用者個人設定,你要改自己的資料,就寫入這裡!

由於 login shell 已經讀取了 /etc/profile 因此已經設定了大部分的全域變數設定,所以 non-login shell 只需要少部份的設定即可。 故 non-login shell 只會讀取一個個人設定檔,亦即是:

  • ~/.bashrc
例題:
  1. 觀察一下 ~/.bash_profile 的內容,說明該檔案設定了什麼項目?
  2. 觀察 ~/.bashrc 的內容,說明該檔案設定了什麼項目?

由於 ~/.bash_profile 也是讀取 ~/.bashrc ,因此使用者只需要將設定放置於家目錄下的 .bashrc 就可以讓兩者讀取了。

例題:嘗試設定 student 的操作環境
  1. 請在 student 的家目錄編輯 .bashrc ,增加底下的項目:
    • 設定 history 可以輸出 10000 筆資料
    • 設定執行 cp 時,其實會主動加入 cp -i 的選項
    • 設定執行 rm 時,其實會主動加入 rm -i 的選項
    • 設定執行 mv 時,其實會主動加入 mv -i 的選項
    • 增加 PATH 的搜尋目錄在 /home/student/cmd/ 目錄
    • 設定一個變數名稱為 kver,其內容是目前的核心版本
    • 強迫語系使用 zh_TW.utf8 這個項目,且必須要設定為全域變數
    • 讓提示字元項目中,增加時間與操作指令次數的項目。
    • 使用 wc 指令分析 ~/.bash_history 的行數,將該行數紀錄於 h_start 的變數中
  2. 設定完畢後,如何在不登出的情況下,讓設定生效?

當使用者登出 bash 時,bash 會依據家目錄下的 .bash_logout 來進行後續的動作,因此若使用者有需要額外進行某些工作時, 可以在此檔案中設定。

例題:
  1. 每次登出 bash 時,都會:
    • (1)使用 date 取得『 YYYY/MM/DD HH:MM 』的格式,並且轉存到家目錄的 history.log 檔案中
    • (2)使用 history 加上管線命令與 wc 來分析結束時的 history 行數,將該數值設定為 h_end,搭配之前設定的 h_start 開始的行數, 計算出這次執行指令的行數號碼 (應該是 h_end - h_start +1 ),設定為 h_now,透過 history ${h_now} 將最新的指令轉存到 history.log 當中。
  2. 嘗試使用 su - student 來登入 student ,再隨意進行數個指令,之後登出 bash 回到原本的 bash 當中,觀察 ~/history.log 是否有資訊紀錄?

7.2:系統救援

我們在前一堂課提到過簡易的系統救援,直接以 root 的密碼與身份來進入救援模式,然後處理好了檔案系統。但萬一 root 的 shell 被不小心修改了, 導致無法使用 root 的密碼進入系統時,該如何處理?底下的動作比較嚴重,請讀者務必要學會救援的方式。

7.2.1:透過正規的 systemd 方式救援

要處理這個練習,請先進行如下的動作之後,讓系統被破壞,才能夠加以練習:

[root@localhost ~]# vim /etc/fstab
/dev/mapper/centos-home1 /home  xfs  defaults  0 0

[root@localhost ~]# usermod -s /sbin/nologin root
[root@localhost ~]# su -
This account is currently not available.

如此才能夠確認 1. 檔案系統被破壞以及 2. root 的身份設定錯誤 (shell 無法使用)。接下來執行 reboot 來看看會出現什麼問題。

因為檔案系統與 root 身份出問題的狀態
圖7.2.1、因為檔案系統與 root 身份出問題的狀態

如上圖所示,開機後出現檔案系統狀態,系統要求輸入 root 密碼,雖然用戶輸入正確的密碼了,卻無法取得 root 的 bash 操作界面, 因為設定到錯誤的 shell 了。此時正規的救援模式無法使用,需要用到 systemd 開機流程裡面,讓系統進入到一個小小的救援作業系統, 該作業系統是模擬出來的,僅僅用來作為掛載檔案系統的功能。處理的流程為:

  1. 重新開機,且進入選單後的五秒內按下方向鍵,並將光棒選擇到第一個開機選單項目上面
    光棒的移動與進入編輯模式的參考按鈕 (e)
    圖7.2.2、光棒的移動與進入編輯模式的參考按鈕 (e)
  2. 按下『 e 』來進入選單編輯畫面,如下所示,並且在 linux16 那一行的最後面增加『 rd.break 』的項目,之後按下 [Ctrl]+x 來進入救援模式
    互動編輯核心參數,加入進入救援模式的方案
    圖7.2.3、互動編輯核心參數,加入進入救援模式的方案
  3. 救援模式會將根目錄掛載到 /sysroot 這個目錄下,不過是預設掛載成唯讀,因此管理員請用『 mount -o remount,rw /sysroot 』將該目錄重新掛載成可讀寫, 然後使用『 chroot /sysroot 』將根目錄切換到 /sysroot 底下,就可以成功的取用原本的作業系統了。
    重新掛載為可讀寫,並進行更換根目錄的行為 (chroot)
    圖7.2.4、重新掛載為可讀寫,並進行更換根目錄的行為 (chroot)
  4. 此時系統提示字元只有『 sh-4.2# 』也是正常的,請執行『 mount -a 』之後就可以進行『 usermod -s /bin/bash root 』等動作!一般建議動作流程如下:
    [root@localhost ~]# mount -a
    [root@localhost ~]# usermod -s /bin/bash root
    [root@localhost ~]# vim /etc/fstab
    /dev/mapper/centos-home /home  xfs  defaults  0 0
    
    [root@localhost ~]# touch /.autorelabel 
    [root@localhost ~]# exit
    
  5. 最後使用 reboot 重新開機,就能夠正常的開機進入系統環境了。

最後一個步驟之所以要處理 /.autorelabel 這個情況,是因為 CentOS 7 預設會啟用 SELinux 這個安全強化模組,但是此模組在救援模式並沒有開啟, 所以被更動到的檔案在下次開機後,可能會產生無法讀取的問題。系統開機會去找 /.autorelabel ,若發現有此檔案則會重新寫入 SELinux 的相關設定, 因此系統在重新開機的流程中共會啟動兩次,原因為第一次會重新寫入 SELinux 設定,第二次才是正常開機。


7.2.2:透過 bash 直接救援

若讀者以前有接觸過 Linux 的話,應該知道開機流程中,可以使用 init=/bin/bash 直接讓核心呼叫 bash 來操作系統。 CentOS 7 的 grub2 與 systemd 也保留此功能,此操作行為與 rd.break 相當接近。

  1. 同樣在開機過程中,使用第一個開機選單,然後按下 e 來進入互動編輯模式
  2. 在 linux16 那一行的最後面加入 init=/bin/bash 然後按下 [ctrl]+x 來啟動
  3. 出現『 bash-4.2# 』之後,進行如下的動作來處理:
    [root@localhost ~]# mount -o remount,rw /
    [root@localhost ~]# mount -a
    [root@localhost ~]# /usr/sbin/usermod -s /bin/bash root
    [root@localhost ~]# /usr/bin/vim /etc/fstab
    [root@localhost ~]# /usr/bin/touch /.autorelabel 
    [root@localhost ~]# reboot
    

因為這個方案是 bash 直接控管的,與 systemd 的管理機制無關,因此使用者無法使用 reboot 來重新開機。此時請按下電腦的 reset 按鈕或強制關機再次重新開機。 之後應該就可以順利的開機了。


7.3:課後練習操作

  1. 請使用系統救援方式將目前的練習機問題解決。問題解決之後請順利進入系統,開始進行底下的實做訓練。
  2. 請將 /bin/false 與 /bin/true 變成合法的 shell
  3. 請將 examuser1 的 shell 變成 /bin/true
  4. 找出系統中檔案擁有者為 examuser5 的檔名,並將這些找到的檔名(含權限)複製到 /root/findout/ 目錄內
  5. 找出在 /usr/sbin 及 /usr/bin 底下權限為 4755 的檔案,並將這些檔案複製到 /root/findperm/ 目錄內
  6. 建立三個名為 myuser1, myuser2, myuser3 的帳號,這三個帳號基本上無法使用互動界面操作系統,且密碼為 MyPassWordhehe
  7. 建立名為 examuser10 的帳號,密碼為 MyPassWordhehe,且這個帳號登入後,預設會有底下的設定:
    • 預設使用 bash 作為 shell
    • 會讀入 /etc/examvar 設定檔
    • 擁有一個名為 myip 的變數,變數內容為『 ifconfig eth0 | grep 'inet ' | cut -d 't' -f 2 | cut -d ' ' -f 2 』 的執行成果 (每個同學操作指令的結果都不會相同, 但是指令會是一樣的)
    • 使用 zh_TW.utf8 語系資料
    • 增加 ~examuser10/scripts/ 目錄作為指令執行時所尋找的目錄位置
    • 命令提示字元增加時間項目在裡面
    • 預設歷史命令紀錄 5000 筆
    • 操作 cp 時,自動給予 cp -i 的選項
    • 每次離開 bash 時,會自動的將最後 20 筆資料累加儲存到 ~examuser10/myhistory 檔案中
2016/07/14:這一堂課講比較多的 bash 以及前一節課的進階救援問題!
2016/07/14以來統計人數
計數器
>>
HOME
PrePage
NextPage
   http://linux.vbird.org is designed by VBird during 2001-2015.