探索 Shell 腳本的威力
![](https://linuxstory.org/wp-content/uploads/2024/06/070953kv0kdvld33h55uk5-800x400.jpg)
當我們登錄到一個 UNIX/Linux 系統時,我們首先注意到的是閃爍的游標和 $
符號之間的空格。這就是 Shell(交互界面)。多年來,它一直是一種無處不在(有時甚至是唯一的)與計算機交互的界面。在圖形用戶界面(GUI)出現和流行之前,終端和 Shell 是唯一的機制,可以讓計算機按照我們的意圖進行操作。乍一看,我們可能會想知道 Shell 的作用,除了將命令傳遞給底層操作系統以進行執行之外。我們中的大多數人熟悉像 ls
(用於列出目錄內容),cd
(用於更改當前目錄)等命令。通過 Shell,我們可以執行這些命令。Shell 理解我們輸入的文本 - 將其轉換為標記 - 然後在操作系統上執行這些標記。
不同的 Shell 變種
最初,終端使用了樸素的 Bourne Shell(即 Sh)。多年來,許多不同的 Shell 變種被開發出來和使用。其中一些流行的包括 C Shell(Csh) 和 Korn Shell(Ksh)。Sh 在一段時間內不再受歡迎,但通過其最新的化身 —— Bourne Again Shell(Bash),它再次流行起來。
Shell 實際上是做什麼的?
Shell 是操作系統(OS)和用戶之間的直接介面。通過使用命令和應用程序來使用計算機上安裝的工具,我們可以使計算機按照我們的意願工作。一些命令是安裝在操作系統上的應用程序,而某些命令則是直接內置在 Shell 中的。在 Bash 中內置的一些命令包括 clear
、cd
、eval
和 exec
,還有 ls
和 mkdir
這樣的應用程序。內置在 Shell 中的命令因 Shell 而異。
在本文中,我們將涵蓋與 Bash 相關的幾個方面。
更多關於 Shell 的內容
我們中的大多數人都用過像 ls
、cd
和 mkdir
這樣的命令。當我們在一個目錄上運行 ls -l
命令時,該目錄中的所有子目錄和文件都會在屏幕上列出。如果數量很大,屏幕會滾動。如果終端不支持滾動條(在很多年裡都是如此),則無法查看已滾動過的條目。為了克服這個問題,我們使用像 more
和 less
這樣的命令。它們允許我們逐頁查看輸出。通常使用的命令是:
ls -l | less
在這裡 Shell 是在做什麼?看起來像是單個命令,實際上是 ls
和 less
兩個命令依次執行。管道符(|
)將這兩個程序連接起來,但連接由 Shell 管理。由於有了管道符,Shell 連接了這兩個程序——它將 ls
命令的標準輸出連接到 less
的標準輸入(stdin)。管道功能使我們能夠將任何程序的輸出作為另一個程序的輸入提供,而無需對程序進行任何更改。這是許多 UNIX/Linux 應用程序的理念——保持應用程序簡單,然後將許多應用程序組合在一起以實現最終結果,而不是讓一個程序做很多事情。
如果需要,我們可以將 ls
的輸出重定向到文件中,然後使用 vi
查看它。為此,我們使用以下命令:
ls -l > /tmp/my_file.txt
vi /tmp/my_file.txt
在這種情況下,ls
的輸出被重定向到一個文件中。這由 Shell 進行管理,它理解 >
符號表示重定向。它將其後面的標記視為文件。
使用 Shell 自動化
結合命令的能力是使用 Shell 命令創建自動化腳本的關鍵要素之一。在我最近的項目中,我們使用集群模式執行 Python/Spark(PySpark)應用程序。每個應用程序執行了許多結構化查詢語言(SQL)語句 - SparkSQL。為了跟蹤應用程序的進展,我們會列印有關正在執行的 SQL 的詳細信息。這樣可以讓我們保留應用程序中正在發生的情況的日誌。由於應用程序在集群模式下執行,要查看日誌,我們必須使用以下 yarn
命令:
yarn log –applicationId [application_id]
在大多數情況下,應用程序生成的日誌非常大。因此,我們通常將日誌導入到 less
中,或將其重定向到一個文件中。我們使用的命令是:
yarn log –aplicationId [application_id] | less
我們的開發團隊有 40 人。每個人都必須記住這個命令。為了簡化操作,我將這個命令轉換成了一個 Bash 腳本。為此,我創建了一個以 .sh
為擴展名的文件。在 UNIX 和 Linux 系統上,文件擴展名並不重要。只要文件是可執行的,它就能工作。擴展名在 MS Windows 上有意義。
需要記住的重要事項
Shell 是一個解釋器。這意味著它會逐行讀取程序並執行它。這種方法的限制在於錯誤(如果有)在事先無法被識別。直到解釋器讀取和執行它們時,錯誤才會被識別出來。簡而言之,假如我們有一個在前 20 行完美執行,但在第 21 行由於語法錯誤而失敗的 Shell 程序。當腳本在第 21 行失敗時,Shell 不會回滾/撤銷之前的步驟。當發生這樣的情況時,我們必須糾正腳本並從第一行開始執行。因此,例如,如果在遇到錯誤之前刪除了幾個文件,腳本的執行將停止,而文件將永遠消失。
我創建的腳本是:
#!/bin/bash
yarn log –applicationId 123 | less
…其中 123 是應用程序的 ID。
第一行的前兩個字元是特殊字元(「釋伴」)。它們告訴腳本這是一個可執行文件,並且該行包含要用於執行的程序的名稱。腳本的其餘行傳遞給所提到的程序。在這個例子中,我們將執行 Bash。即使包含了第一行,我們仍然必須使用以下命令對文件應用執行許可權:
chmod +x my_file.sh
在給文件設置了執行許可權之後,我們可以如下執行它:
./my_file.sh
如果我們沒有給文件設置執行許可權,我們可以使用以下命令執行該腳本:
sh ./my_file.sh
傳遞參數
你很快就會意識到,這樣的腳本很方便,但立即變得無用。每次執行 Python/Spark 應用程序時,都會生成一個新的 ID。因此,對於每次運行,我們都必須編輯文件並添加新的應用程序 ID。這無疑降低了腳本的可用性。為了提高其可用性,我們應該將應用程序 ID 作為參數傳遞:
#!/bin/bash
yarn –log -applicationId ${1} | less
我們需要這樣執行腳本:
./show_log.sh 123
腳本將執行 yarn
命令,獲取應用程序的日誌並允許我們查看它。
如果我們想將輸出重定向到一個文件中怎麼辦?沒問題。我們可以將輸出重定向到一個文件而不是發送給 less
:
#!/bin/bash
ls –l ${1} > ${2}
view ${2}
要運行腳本,我們需要提供兩個參數,命令變為:
./my_file.sh /tmp /tmp/listing.txt
當執行時,$1
將綁定到 /tmp
,$2
將綁定到 /tmp/listing.txt
。對於 Shell,參數從一到九命名。這並不意味著我們不能將超過九個參數傳遞給腳本。我們可以,但這是另一篇文章的主題。你會注意到,我將參數命名為 ${1}
和 ${2}
,而不是 $1
和 $2
。將參數名稱封閉在花括弧中是一個好習慣,因為它使我們能夠無歧義地將參數作為較長變數的一部分組合起來。例如,我們可以要求用戶將文件名作為參數,並使用它來形成一個更大的文件名。例如,我們可以將 $1
作為參數,創建一個新的文件名為 ${1}_student_names.txt
。
使腳本更健壯
如果用戶忘記提供參數怎麼辦?Shell 允許我們檢查這種情況。我們將腳本修改如下:
#!/bin/bash
if [ -z "${2}" ]; then
echo "file name not provided"
exit 1
fi
if [ -z "${1}" ]; then
echo "directory name not provided"
exit 1
fi
DIR_NAME=${1}
FILE_NAME=${2}
ls -l ${DIR_NAME} > /tmp/${FILE_NAME}
view /tmp/${FILE_NAME}
在這個程序中,我們檢查是否傳遞了正確的參數。如果未傳遞參數,我們將退出腳本。你會注意到,我以相反的順序檢查參數。如果我們在檢查第一個參數存在之前檢查第二個參數的存在,如果只傳遞了一個參數,腳本將進行到下一步。雖然可以按遞增順序檢查參數的存在,但我最近意識到,從九到一檢查會更好,因為我們可以提供適當的錯誤消息。你還會注意到,參數已分配給變數。參數一到九是位置參數。將位置參數分配給具名參數可以在出現問題時更容易調試腳本。
自動化備份
我自動化的另一個任務是備份。在開發的初期階段,我們沒有使用版本控制系統。但我們需要有一個機制來定期備份。因此,最好的方法是編寫一個 Shell 腳本,在執行時將所有代碼文件複製到一個單獨的目錄中,將它們壓縮,並使用日期和時間作為後綴來上傳到 HDFS。我知道,這種方法不如使用版本控制系統那樣清晰,因為我們存儲了完整的文件,查找差異仍然需要使用像 diff
這樣的程序;但它總比沒有好。儘管我們最終沒有刪除代碼文件,但團隊確實刪除了存儲助手腳本的 bin
目錄!!!而且對於這個目錄,我沒有備份。我別無選擇,只能重新創建所有的腳本。
一旦建立了源代碼控制系統,我很容易將備份腳本擴展到除了之前上傳到 HDFS 的方法之外,還可以將文件上傳到版本控制系統。
總結
如今,像 Python、Spark、Scala 和 Java 這樣的編程語言很受歡迎,因為它們用於開發與人工智慧和機器學習相關的應用程序。儘管與 Shell 相比,這些語言更強大,但「不起眼」的 Shell 提供了一個即用即得的平台,讓我們能夠創建輔助腳本來簡化我們的日常任務。Shell 是相當強大的,尤其是因為我們可以結合操作系統上安裝的所有應用程序的功能。正如我在我的項目中發現的那樣,即使經過了幾十年,Shell 腳本仍然非常強大。我希望我已經說服你嘗試一下了。
最後一個例子
Shell 腳本確實非常方便。考慮以下命令:
spark3-submit --queue pyspark --conf "spark.yarn.principal=abcd@abcd.com" --conf "spark.yarn.keytab=/keytabs/abcd.keytab" --jars /opt/custom_jars/abcd_1.jar --deploy-mode cluster --master yarn $*
我們要求在執行 Python/Spark 應用程序時使用此命令。現在想像一下,這個命令必須每天被一個由 40 個人組成的團隊多次使用。大多數人會在記事本中複製這個命令,每次需要使用時,會將其從記事本中複製並粘貼到終端中。如果複製粘貼過程中出現錯誤怎麼辦?如果有人錯誤使用了參數怎麼辦?我們如何調試使用的是哪個命令?查看歷史記錄並沒有太多幫助。
為了讓團隊能夠簡單地執行 Python/Spark 應用程序,我們可以創建一個 Bash Shell 腳本,如下所示:
#!/bin/bash
SERVICE_PRINCIPAL=abcd@abcd.com
KEYTAB_PATH=/keytabs/abcd.keytab
MY_JARS=/opt/custom_jars/abcd_1.jar
MAX_RETRIES=128
QUEUE=pyspark
MASTER=yarn
MODE=cluster
spark3-submit --queue ${QUEUE} --conf "spark.yarn.principal=${SERVICE_PRINCIPAL}" --conf "spark.yarn.keytab=${KEYTAB_PATH}" --jars ${MY_JARS} --deploy-mode ${MODE} --master ${MASTER} $*
這展示了一個 Shell 腳本的強大之處,讓我們的生活變得簡單。根據你的需求,你可以嘗試更多的命令和腳本,並進一步探索。
(題圖:MJ/f32880e8-0cdc-4897-8a1c-242c131111bf)
via: https://www.opensourceforu.com/2022/05/shell-scripting-is-still-going-strong/
作者:Bipin Patwardhan 選題:lkxed 譯者:ChatGPT 校對:wxy
本文轉載來自 Linux 中國: https://github.com/Linux-CN/archive