- ver:1.0
- 博客:https://www.cnblogs.com/Rohn
- 本文介紹了Shell編程的一些語法規范,主要參考依據為谷歌的Shell語法風格,
目錄
- 背景
- 使用哪一種Shell
- 什么時候使用Shell
- 注釋
- 頂層注釋
- 功能注釋
- TODO注釋
- 格式
- 縮進
- 行的長度和長字串
- 管道
- 回圈
- if-else陳述句
- for-do和while-do陳述句
- case陳述句
- 變數擴展
- 特性
- 命令替換
- 檔案名的通配符擴展
- 命名約定
- 函式名
- 變數名
- 常量和環境變數名
- 源檔案名
- 只讀變數
- 使用本地變數
- 呼叫命令
- 檢查回傳值
背景
博客:https://www.cnblogs.com/Rohn
使用哪一種Shell
可執行檔案必須以 #!/bin/bash 和最小數量的標志開始,請使用 set 來設定shell的選項,使得用 <script_name>呼叫你的腳本時不會破壞其功能,
推薦使用:
#!/usr/bin/env bash
env一般固定在/usr/bin目錄下,而其余解釋器的安裝位置就相對不那么固定,
限制所有的可執行Shell腳本為bash使得我們安裝在所有計算機中的shell語言保持一致性,
無論你是為什么而編碼,對此唯一例外的是當你被迫時可以不這么做的,其中一個例子是Solaris SVR4包,撰寫任何腳本都需要用純Bourne shell,
[root@test ~]# echo $SHELL
/bin/bash
什么時候使用Shell
使用Shell需要遵守的一些準則:
- 如果你主要是在呼叫其他的工具并且做一些相對很小資料量的操作,那么使用Shell來完成任務是一種可接受的選擇,
- 如果你在乎性能,那么請選擇其他工具,而不是使用Shell,
- 如果你發現你需要使用資料而不是變數賦值(如 ${PHPESTATUS} ),那么你應該使用Python腳本,
- 如果你將要撰寫的腳本會超過100行,那么你可能應該使用Python來撰寫,而不是Shell,
請記住,當腳本行數增加,盡早使用另外一種語言重寫你的腳本,以避免之后花更多的時間來重寫,
注釋
博客:https://www.cnblogs.com/Rohn
Bash只支持單行注釋,使用#開頭的都被當作注釋陳述句,
頂層注釋
每個檔案必須包含一個頂層注釋,對其內容進行簡要概述,著作權宣告和作者資訊是可選的,
例如:
#!/usr/bin/env bash
# Author: Rohn
# Version: 1.0
# Created Time: 2020/06/06
# Perform hot backups of MySQL databases.
- 第1行,指明解釋器,使用
bash
#!叫做"Shebang"或者"Sha-bang"(Unix術語中,#號通常稱為sharp,hash或mesh;而!則常常稱為bang),指明了執行這個腳本檔案的解釋程式,當然,如果使用bash test.sh這樣的命令來執行腳本,那么#!這一行將會被忽略掉,
- 第2-5行,分別為作者、版本號、創建時間、功能說明,
功能注釋
任何不是既明顯又短的函式都必須被注釋,任何庫函式無論其長短和復雜性都必須被注釋,
其他人通過閱讀注釋(和幫助資訊,如果有的話)就能夠學會如何使用你的程式或庫函式,而不需要閱讀代碼,
所有的函式注釋應該包含:
- 函式的描述
- 全域變數的使用和修改
- 使用的引數說明
- 回傳值,而不是上一條命令運行后默認的退出狀態
例如:
#!/usr/bin/env bash
# Author: Rohn
# Version: 1.0
# Created Time: 2020/06/06
# Perform hot backups of Oracle databases.
export PATH='/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/root/bin'
#######################################
# Cleanup files from the backup dir
# Globals:
# BACKUP_DIR
# ORACLE_SID
# Arguments:
# None
# Returns:
# None
#######################################
cleanup() {
...
}
TODO注釋
TODOs應該包含全部大寫的字串TODO,接著是括號中你的用戶名,冒號是可選的,最好在TODO條目之后加上bug或者ticket的序號,
例如:
# TODO(mrmonkey): Handle the unlikely edge cases (bug ####)
格式
博客:https://www.cnblogs.com/Rohn
縮進
縮進兩個空格,沒有制表符,例如:
if [ a > 1 ];then
echo '${a} > 1'
fi
行的長度和長字串
行的最大長度為80個字符,例如:
# DO use 'here document's
cat <<END;
I am an exceptionally long
string.
END
# Embedded newlines are ok too
long_string="I am an exceptionally
long string."
管道
如果一行容不下整個管道操作,那么請將整個管道操作分割成每行一個管段,
應該將整個管道操作分割成每行一個管段,管道操作的下一部分應該將管道符放在新行并且縮進2個空格,這適用于使用管道符|的合并命令鏈以及使用||和&&的邏輯運算鏈,
例如:
# All fits on one line
command1 | command2
# Long commands
command1 \
| command2 \
| command3 \
| command4
回圈
if-else陳述句
if和; then放在同一行,;后空一格,else單獨一行,fi單獨一行,并與if垂直對齊,即:
if condition; then
statement(s)
else
statement(s)
fi
for-do和while-do陳述句
while/for和; do放在同一行,done與while/for垂直對齊,即:
# while structure
while condition; do
statement(s)
done
# for structure
for condition; do
statement(s)
done
例如:
for dir in ${dirs_to_cleanup}; do
if [[ -d "${dir}/${ORACLE_SID}" ]]; then
log_date "Cleaning up old files in ${dir}/${ORACLE_SID}"
rm "${dir}/${ORACLE_SID}/"*
if [[ "$?" -ne 0 ]]; then
error_message
fi
else
mkdir -p "${dir}/${ORACLE_SID}"
if [[ "$?" -ne 0 ]]; then
error_message
fi
fi
done
case陳述句
- 通過2個空格縮進可選項,
- 在同一行可選項的模式右圓括號之后和結束符
;;之前各需要一個空格, - 長可選項或者多命令可選項應該被拆分成多行,模式、操作和結束符
;;在不同的行,
匹配運算式比case和esac 縮進一級,多行操作要再縮進一級,一般情況下,不需要參考匹配運算式,模式運算式前面不應該出現左括號,避免使用;&和;;&符號,即:
# case structure
case in expression in
pattern1)
statement1
;;
pattern2)
statement2
;;
...
*)
statementn
;;
esac
例如:
case "${expression}" in
a)
variable="..."
some_command "${variable}" "${other_expr}" ...
;;
absolute)
actions="relative"
another_command "${actions}" "${other_expr}" ...
;;
*)
error "Unexpected expression '${expression}'"
;;
esac
只要整個運算式可讀,簡單的命令可以跟模式和;; 寫在同一行,這通常適用于單字母選項的處理,當單行容不下操作時,請將模式單獨放一行,然后是操作,最后結束符;; 也單獨一行,當操作在同一行時,模式的右括號之后和結束符;;之前請使用一個空格分隔,
verbose='false'
aflag=''
bflag=''
files=''
while getopts 'abf:v' flag; do
case "${flag}" in
a) aflag='true' ;;
b) bflag='true' ;;
f) files="${OPTARG}" ;;
v) verbose='true' ;;
*) error "Unexpected option ${flag}" ;;
esac
done
變數擴展
按優先級順序:保持跟你所發現的一致;參考你的變數;推薦用
${var}而不是$var,
例如
# Section of recommended cases.
# Preferred style for 'special' variables:
echo "Positional: $1" "$5" "$3"
echo "Specials: !=$!, -=$-, _=$_. ?=$?, #=$# *=$* @=$@ \$=$$ ..."
# Braces necessary:
echo "many parameters: ${10}"
# Braces avoiding confusion:
# Output is "a0b0c0"
set -- a b c
echo "${1}0${2}0${3}0"
# Preferred style for other variables:
echo "PATH=${PATH}, PWD=${PWD}, mine=${some_var}"
while read f; do
echo "file=${f}"
done < <(ls -l /tmp)
# Section of discouraged cases
# Unquoted vars, unbraced vars, brace-quoted single letter
# shell specials.
echo a=$avar "b=$bvar" "PID=${$}" "${1}"
# Confusing use: this is expanded as "${1}0${2}0${3}0",
# not "${10}${20}${30}
set -- a b c
echo "$10$20$30"
特性
博客:https://www.cnblogs.com/Rohn
命令替換
使用
$(command)而不是反引號,
嵌套的反引號要求用反斜杠轉義內部的反引號,而$(command) 形式嵌套時不需要改變,而且更易于閱讀,
例如:
# This is preferred:
var="$(command "$(command1)")"
# This is not:
var="`command \`command1\``"
檔案名的通配符擴展
當進行檔案名的通配符擴展時,請使用明確的路徑,
因為檔案名可能以-開頭,所以使用擴展通配符./*比*來得安全得多,
# Here's the contents of the directory:
# -f -r somedir somefile
# This deletes almost everything in the directory by force
psa@bilby$ rm -v *
removed directory: `somedir'
removed `somefile'
# As opposed to:
psa@bilby$ rm -v ./*
removed `./-f'
removed `./-r'
rm: cannot remove `./somedir': Is a directory
removed `./somefile'
命名約定
博客:https://www.cnblogs.com/Rohn
函式名
使用小寫字母,并用下劃線分隔單詞,使用雙冒號
::分隔庫,函式名之后必須有圓括號,關鍵詞function是可選的,但必須在一個專案中保持一致,
如果你正在寫單個函式,請用小寫字母來命名,并用下劃線分隔單詞,如果你正在寫一個包,使用雙冒號 :: 來分隔包名,大括號必須和函式名位于同一行(就像在Google的其他語言一樣),并且函式名和圓括號之間沒有空格,
# Single function
my_func() {
...
}
# Part of a package
mypackage::my_func() {
...
}
當函式名后存在 () 時,關鍵詞 function 是多余的,但是其促進了函式的快速辨識,
變數名
使用小寫字母,回圈的變數名應該和回圈的任何變數同樣命名,例如:
for zone in ${zones}; do
something_with "${zone}"
done
常量和環境變數名
全部使用大寫字母,用下劃線分隔,宣告在檔案的頂部,例如:
# Constant
readonly PATH_TO_FILES='/some/path'
# Both constant and environment
declare -xr ORACLE_SID='PROD'
源檔案名
使用小寫字母,如果需要的話使用下劃線分隔單詞,例如: maketemplate 或者 make_template ,而不是 make-template ,
只讀變數
使用小寫字母,使用 readonly 或者 declare -r 來確保變數只讀,
因為全域變數在Shell中廣泛使用,所以在使用它們的程序中捕獲錯誤是很重要的,當你宣告了一個變數,希望其只讀,那么請明確指出,
zip_version="$(dpkg --status zip | grep Version: | cut -d ' ' -f 2)"
if [[ -z "${zip_version}" ]]; then
error_message
else
readonly zip_version
fi
使用本地變數
使用小寫字母,使用 local 宣告特定功能的變數,宣告和賦值應該在不同行,
使用 local 來宣告區域變數以確保其只在函數內部和子函式中可見,這避免了污染全域命名空間和不經意間設定可能具有函式之外重要性的變數,
當賦值的值由命令替換提供時,宣告和賦值必須分開,因為內建的 local 不會從命令替換中傳遞退出碼,
my_func2() {
local name="$1"
# Separate lines for declaration and assignment:
local my_var
my_var="$(my_func)" || return
# DO NOT do this: $? contains the exit code of 'local', not my_func
local my_var="$(my_func)"
[[ $? -eq 0 ]] || return
...
}
呼叫命令
博客:https://www.cnblogs.com/Rohn
檢查回傳值
對于非管道命令,使用$?或直接通過一個if陳述句來檢查以保持其簡潔,例如:
if ! mv "${file_list}" "${dest_dir}/" ; then
echo "Unable to move ${file_list} to ${dest_dir}" >&2
exit "${E_BAD_MOVE}"
fi
# Or
mv "${file_list}" "${dest_dir}/"
if [[ "$?" -ne 0 ]]; then
echo "Unable to move ${file_list} to ${dest_dir}" >&2
exit "${E_BAD_MOVE}"
fi
Bash也有 PIPESTATUS 變數,允許檢查從管道所有部分回傳的代碼,如果僅僅需要檢查整個管道是成功還是失敗,以下的方法是可以接受的:
tar -cf - ./* | ( cd "${dir}" && tar -xf - )
if [[ "${PIPESTATUS[0]}" -ne 0 || "${PIPESTATUS[1]}" -ne 0 ]]; then
echo "Unable to tar files to ${dir}" >&2
fi
可是,只要你運行任何其他命令, PIPESTATUS 將會被覆寫,如果你需要基于管道中發生的錯誤執行不同的操作,那么你需要在運行命令后立即將 PIPESTATUS 賦值給另一個變數(別忘了 [ 是一個會將 PIPESTATUS 擦除的命令),
tar -cf - ./* | ( cd "${DIR}" && tar -xf - )
return_codes=(${PIPESTATUS[*]})
if [[ "${return_codes[0]}" -ne 0 ]]; then
do_something
fi
if [[ "${return_codes[1]}" -ne 0 ]]; then
do_something_else
fi
轉載請註明出處,本文鏈接:https://www.uj5u.com/caozuo/56812.html
標籤:Linux
