背景(Background)
使用哪種 Shell(Which Shell to Use)
Bash是唯一被允許執(zhí)行的shell腳本語言。
可執(zhí)行文件必須以 #!/bin/bash
和最小數(shù)量的標(biāo)志開始。請使用 set
來設(shè)置 Shell 的選項(xiàng),使得用 bash <script_name>
調(diào)用你的腳本時不會破壞其功能。
限制所有的可執(zhí)行 Shell 腳本為 Bash 使得我們安裝在所有計算機(jī)中的 Shell 語言保持一致性。
對此唯一例外的是當(dāng)你被迫時可以不這么做的。例如 Solaris SVR4,需要用純 Bourne shell。
何時使用 Shell(When to use Shell)
Shell 應(yīng)該僅僅被用于小工具或者簡單的包裝腳本。
盡管 Shell 腳本不是一種開發(fā)語言,但在整個谷歌它被用于編寫多種實(shí)用工具腳本。這個風(fēng)格指南更多的是認(rèn)同它的使用,而不是一個建議,即它可被用于廣泛部署。
以下是一些準(zhǔn)則:
- 如果你主要是在調(diào)用其他的工具并且做一些相對很小數(shù)據(jù)量的操作,那么使用Shell 來完成任務(wù)是一種可接受的選擇。
- 如果你在乎性能,那么請選擇其他工具,而不是使用 Shell。
- 如果你發(fā)現(xiàn)你需要在任何地方使用數(shù)組而不是變量賦值(如
${PHPESTATUS}
),那么你應(yīng)該使用 Python 腳本。 - 如果你將要編寫的腳本會超過 100 行,那么你可能應(yīng)該使用 Python 來編寫,而不是 Shell。記住:腳本長度會增加,盡早使用另外一種語言重寫你的腳本,以避免之后花更多的時間來重寫。
Shell 文件和解釋器調(diào)用(Shell Files and Interpreter Invocation)
文件擴(kuò)展名(File Extensions)
可執(zhí)行文件應(yīng)該沒有擴(kuò)展名(強(qiáng)烈建議)或者使用 .sh 擴(kuò)展名。庫文件必須使用 .sh 作為擴(kuò)展名,而且應(yīng)該是不可執(zhí)行的。
當(dāng)執(zhí)行一個程序時,并不需要知道它是用什么語言編寫的。而且 Shell 腳本也不要求有擴(kuò)展名。所以我們更喜歡可執(zhí)行文件沒有擴(kuò)展名。
然而,對于庫文件,知道其用什么語言編寫的是很重要的,有時候會需要使用不同語言編寫的相似的庫文件。使用 .sh 這樣特定語言后綴作為擴(kuò)展名,就使得用不同語言編寫的具有相同功能的庫文件可以采用一樣的名稱。
SUID/SGID
SUID 和 SGID 在 Shell 腳本上是被禁止的。
Shell 有著很多的安全性問題以至于在 Shell 上安全啟用 SUID/SGID 幾乎是不可能的。雖然 Bash 使得使用 SUID 很困難,但是在某些平臺上還是有可能的,這也是為什么我們要明確禁止使用 SUID/SGID 的原因。
如果你需要高的存取(權(quán)限)請使用 sudo
。
環(huán)境(Environment)
標(biāo)準(zhǔn)輸出對比錯誤輸出(STDOUT vs STDERR)
所有的錯誤信息應(yīng)該輸入到標(biāo)準(zhǔn)錯誤輸出(STDERR)中。
這使得從實(shí)際問題中分離出正常狀態(tài)變得更容易。
下面這個函數(shù)是用于打印出錯誤信息以及其他狀態(tài)信息的功能,值得推薦。
err() {
echo "[$(date +'%Y-%m-%dT%H:%M:%S%z')]: $@" >&2
}
if ! do_something; then
err "Unable to do_something"
exit "${E_DID_NOTHING}"
fi
注釋(Comments)
文件頭(File Header)
在每個文件開頭處添加一段描述內(nèi)容。
每個文件必須有一個頂層注釋,內(nèi)容包含了內(nèi)容的簡短概述。一個版權(quán) (copyright) 聲明,還有作者信息時可選的。
例如:
#!/bin/bash
#
# Perform hot backups of Oracle databases.
函數(shù)注釋(Function Commets)
任何不能同時具備(功能)顯而易見且短小的函數(shù)都必須有注釋。任何庫函數(shù),無論其長度大小和復(fù)雜性都必須要有注釋。
所有的函數(shù)注釋應(yīng)該包含如下的內(nèi)容:
- 函數(shù)的描述信息
- 使用的和修改的全局變量
- 參數(shù)信息
- 返回值而不是最后一條命令的缺省退出狀態(tài)碼
例子:
#!/bin/bash
#
# Perform hot backups of Oracle databases.
export PATH='/usr/xpg4/bin:/usr/bin:/opt/csw/bin:/opt/goog/bin'
#######################################
# Cleanup files from the backup dir
# Globals:
# BACKUP_DIR
# ORACLE_SID
# Arguments:
# None
# Returns:
# None
#######################################
cleanup() {
...
}
實(shí)現(xiàn)的注釋(Implementation Comments)
對你的代碼中含有技巧的,不明顯的,有趣的,或者是一些重要的部分添加注釋。
遵循 Google 的通用編碼注釋的做法。不要所有代碼都加注釋。如果有一個復(fù)雜的算法,或者是你在做一個與眾不同的功能,在這些地方放置一個簡單的注釋即可。
TODO 的注釋(TODO Comments)
在臨時的、短期解決方案的、或者足夠好但不夠完美的代碼處添加 TODO 注釋。
這與 C++ 指南中的約定相一致。
所有的 TODO 類別的注釋,應(yīng)該包含一個全部大寫的字符串 TODO,后面用括號包含您的用戶名。冒號是可選的。這里最好把 bug 號,或者是 ticket 號放在TODO 注釋后面。
例如:
# TODO(mrmonkey): Handle the unlikely edge cases (bug ####)
格式(Formatting)
雖然你需要遵循你正在修改的文件的風(fēng)格,但是新的代碼必須要遵循下面的風(fēng)格。
縮進(jìn)(Indentation)
按照 2 個空格來縮進(jìn),不使用 tab 來縮進(jìn)。
在兩個語句塊中間使用空白行來提高可讀性。縮進(jìn)使用兩個空格。無論你做什么,不要使用制表符(tab)。對于現(xiàn)有的文件,保留現(xiàn)有使用的縮進(jìn),
行長度和長字符串(Line Length and Long Strings)
一行的長度最多是 80 個字符.
如果你必須要寫一個長于 80 個字符的字符串,如果可能的話,你應(yīng)該盡量使用 here document 或者嵌入一個新行,如果有一個文字字符串長度超過了 80 個字符,并且不能合理的分割文字字符串,但是強(qiáng)烈推薦你找到一種辦法讓它更短一點(diǎn)。
# 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."
多個管道(Pipelines)
如果一行不能容納多個管道操作,那么請將多個管道拆分成一行一個。
如果一行容得下整個管道操作,那么請將整個管道操作寫在同一行。
否則,那么應(yīng)該分割成每行一個管道,新的一行應(yīng)該縮進(jìn) 2 個空格。這條規(guī)則適用于那些通過使用”|”或者是一個邏輯運(yùn)算符”||”和”&&”等組合起來的鏈?zhǔn)矫睢?/p>
# All fits on one line
command1 | command2
# Long commands
command1 \
| command2 \
| command3 \
| command4
循環(huán)(Loops)
請將 ; do
、; then
和 while
、for
或者 if
放在同一行。
Shell 中的循環(huán)略有不同,但是我們遵循像聲明函數(shù)時用大括號同樣的原則,也就是說:; do
、; then
應(yīng)該和 if/for/while
放在同一行。 else
應(yīng)該單獨(dú)一行,結(jié)束語句應(yīng)該單獨(dú)一行并且跟開始語句垂直對齊。
例如:
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 語句(Case statement)
- 縮進(jìn)可用 2 個空格替代。
- 可用一行替代的,需要在右括號后面和
;;
號前面添加一個空格。 - 對于長的,有多個命令的,應(yīng)該分割成多行,其中匹配項(xiàng),對于匹配項(xiàng)的處理以及
;;
號各自在單獨(dú)的行。
case 和 esac 中匹配項(xiàng)的表達(dá)式應(yīng)該都在同一個縮進(jìn)級別,匹配項(xiàng)的(多行)處理也應(yīng)該在另一個縮進(jìn)級別。通常來說,沒有必要給匹配項(xiàng)的表達(dá)式添加引號。匹配項(xiàng)的表達(dá)式不應(yīng)該在前面加一個左括號,避免使用 ;&
和 ;;s&
等符號.
case "${expression}" in
a)
variable="..."
some_command "${variable}" "${other_expr}" ...
;;
absolute)
actions="relative"
another_command "${actions}" "${other_expr}" ...
;;
*)
error "Unexpected expression '${expression}'"
;;
esac
對于一些簡單的匹配項(xiàng)處理操作,可以和匹配項(xiàng)表達(dá)式以及 ;;
號在同一行,只要表達(dá)式仍然可讀。這通常適合單字符的選項(xiàng)處理,當(dāng)匹配項(xiàng)處理操作不能滿足單行的情況下,可以將匹配項(xiàng)表達(dá)式單獨(dú)放在一行,匹配項(xiàng)處理操作和 ;;
放在同一行,當(dāng)匹配項(xiàng)操作和匹配項(xiàng)表達(dá)式以及 ;;
放在同一行的時候在匹配項(xiàng)表達(dá)式右括號后面以及 ;;
前面放置一個空格。
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
don
變量擴(kuò)展(Variable expansion)
按優(yōu)先級順序:保持跟你所發(fā)現(xiàn)的一致;把你的變量用括號印起來;推薦用 "${var}"
而不是 "$var"
,詳細(xì)解釋如下。
這些僅僅是指南,因?yàn)榘礃?biāo)題作為強(qiáng)制的規(guī)定飽受爭議。
以下按照優(yōu)先順序列出。
- 與現(xiàn)存代碼中你所發(fā)現(xiàn)的保持一致。
- 把變量用(大)擴(kuò)號引起來,參閱下面一節(jié):引用。
- 除非絕對必要或者為了避免深深的困惑,否則不要用大括號將單個字符的 Shell 特殊變量或位置參數(shù)括起來。推薦將其他所有變量用大括號括起來。
# 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"
<span id='Quoting'>引用(Quoting)</span>
- 除非需要小心不帶引用的擴(kuò)展,否則總是將包含變量、命令替換符、空格或 Shell 元字符的字符串引起來。
- 優(yōu)先引用是單詞的字符串(而不是命令選項(xiàng)或者路徑名)。
- 不要對整數(shù)進(jìn)行引用。
- 千萬小心 [[ 中模式匹配的引用規(guī)則。
- 請使用
$@
除非你有特殊原因需要使用$*
。
# 'Single' quotes indicate that no substitution is desired.
# "Double" quotes indicate that substitution is required/tolerated.
# Simple examples
# "quote command substitutions"
flag="$(some_command and its args "$@" 'quoted separately')"
# "quote variables"
echo "${flag}"
# "never quote literal integers"
value=32
# "quote command substitutions", even when you expect integers
number="$(generate_number)"
# "prefer quoting words", not compulsory
readonly USE_INTEGER='true'
# "quote shell meta characters"
echo 'Hello stranger, and well met. Earn lots of $$$'
echo "Process $$: Done making \$\$\$."
# "command options or path names"
# ($1 is assumed to contain a value here)
grep -li Hugo /dev/null "$1"
# Less simple examples
# "quote variables, unless proven false": ccs might be empty
git send-email --to "${reviewers}" ${ccs:+"--cc" "${ccs}"}
# Positional parameter precautions: $1 might be unset
# Single quotes leave regex as-is.
grep -cP '([Ss]pecial|\|?characters*)$' ${1:+"$1"}
# For passing on arguments,
# "$@" is right almost everytime, and
# $* is wrong almost everytime:
#
# * $* and $@ will split on spaces, clobbering up arguments
# that contain spaces and dropping empty strings;
# * "$@" will retain arguments as-is, so no args
# provided will result in no args being passed on;
# This is in most cases what you want to use for passing
# on arguments.
# * "$*" expands to one argument, with all args joined
# by (usually) spaces,
# so no args provided will result in one empty string
# being passed on.
# (Consult 'man bash' for the nit-grits ;-)
set -- 1 "2 two" "3 three tres"; echo $# ; set -- "$*"; echo "$#, $@")
set -- 1 "2 two" "3 three tres"; echo $# ; set -- "$@"; echo "$#, $@")
特征和錯誤(Features and Bugs)
命令替換(Command Substitution)
使用 $(command)
而不是反引號。
嵌套的反引號要求用反斜杠("\"
)轉(zhuǎn)義內(nèi)部的反引號。而 $(command)
形式嵌套時不需要改變,而且更易于閱讀。
例如:
# This is preferred:
var="$(command "$(command1)")"
# This is not:
var="`command \`command1\``"
<span id='Test,[and[['>Test, [ 和 [[(Test, [ and [[)</span>
優(yōu)先使用 [[ ... ]]
,而不是 [
, test
和 /usr/bin/[
。
因?yàn)樵?[[
和 ]]
之間不會有路徑名稱擴(kuò)展或單詞分割發(fā)生,所以使用 [[ ... ]]
能夠減少錯誤。而且 [[ ... ]]
允許正則表達(dá)式匹配,而 [ ... ]
不允許。
# This ensures the string on the left is made up of characters in the
# alnum character class followed by the string name.
# Note that the RHS should not be quoted here.
# For the gory details, see
# E14 at http://tiswww.case.edu/php/chet/bash/FAQ
if [[ "filename" =~ ^[[:alnum:]]+name ]]; then
echo "Match"
fi
# This matches the exact pattern "f*" (Does not match in this case)
if [[ "filename" == "f*" ]]; then
echo "Match"
fi
# This gives a "too many arguments" error as f* is expanded to the
# contents of the current directory
if [ "filename" == f* ]; then
echo "Match"
fi
測試字符串(Testing Strings)
盡可能使用引用,而不是過濾字符串。
Bash 足以在測試中處理空字符串。所以,請使用空(非空)字符串測試,而不是過濾字符,使得代碼更易于閱讀。
# Do this:
if [[ "${my_var}" = "some_string" ]]; then
do_something
fi
# -z (string length is zero) and -n (string length is not zero) are
# preferred over testing for an empty string
if [[ -z "${my_var}" ]]; then
do_something
fi
# This is OK (ensure quotes on the empty side), but not preferred:
if [[ "${my_var}" = "" ]]; then
do_something
fi
# Not this:
if [[ "${my_var}X" = "some_stringX" ]]; then
do_something
fi
為了避免對你測試的目的產(chǎn)生困惑,請明確使用 -z
或者 -n
# Use this
if [[ -n "${my_var}" ]]; then
do_something
fi
# Instead of this as errors can occur if ${my_var} expands to a test
# flag
if [[ "${my_var}" ]]; then
do_something
fi
文件名的通配符擴(kuò)展(Wildcard Expansion of Filenames)
當(dāng)做文件名通配符擴(kuò)展的時候,使用顯式路徑。
因?yàn)槲募梢允褂?-
開頭,所以使用擴(kuò)展通配符 ./*
比 *
安全得多。
# Here's the contents of the directory:
# 當(dāng)前目錄下又-f -r somedir somefile等文件和目錄
# -f -r somedir somefile
# 使用rm -v *將會擴(kuò)展成rm -v -r -f somedir simefile,這將導(dǎo)致刪除當(dāng)前目錄所有的文件和目錄
# This deletes almost everything in the directory by force
psa@bilby$ rm -v *
removed directory: `somedir'
removed `somefile'
#相反如果你使用./*則不會,因?yàn)?r -f就不會變成rm的參數(shù)了
# As opposed to:
psa@bilby$ rm -v ./*
removed `./-f'
removed `./-r'
rm: cannot remove `./somedir': Is a directory
removed `./somefile'
Eval
eval
命令應(yīng)該被禁止執(zhí)行。
eval 用于給變量賦值的時候,可以設(shè)置變量,但是不能檢查這些變量是什么。
# What does this set?
# Did it succeed? In part or whole?
eval $(set_my_variables)
# What happens if one of the returned values has a space in it?
variable="$(eval some_function)"
管道導(dǎo)向 while 循環(huán)(Pipes to While)
優(yōu)先使用過程替換或者 for 循環(huán),而不是管道導(dǎo)向 while 循環(huán)。在 while 循環(huán)中被修改的變量是不能傳遞給父 Shell 的,因?yàn)檠h(huán)命令是在一個子 Shell 中運(yùn)行的。
管道導(dǎo)向 while 循環(huán)中的隱式子 Shell 使得追蹤 bug 變得很困難。
last_line='NULL'
your_command | while read line; do
last_line="${line}"
done
# This will output 'NULL'
echo "${last_line}"
如果你確定輸入中不包含空格或者特殊符號(通常意味著不是用戶輸入的),那么可以使用一個 for 循環(huán)。
total=0
# Only do this if there are no spaces in return values.
for value in $(command); do
total+="${value}"
done
使用過程替換允許重定向輸出,但是請將命令放入一個顯式的子 Shell 中,而不是 bash 為 while 循環(huán)創(chuàng)建的隱式子 Shell。
total=0
last_file=
while read count filename; do
total+="${count}"
last_file="${filename}"
done < <(your_command | uniq -c)
# This will output the second field of the last line of output from
# the command.
echo "Total = ${total}"
echo "Last one = ${last_file}"
當(dāng)不需要傳遞復(fù)雜的結(jié)果給父 Shell 時可以使用 while 循環(huán)。這通常需要一些更復(fù)雜的“解析”。請注意簡單的例子使用如 awk 這類工具可能更容易完成。當(dāng)你特別不希望改變父 Shell 的范圍變量時這可能也是有用的。
# Trivial implementation of awk expression:
# awk '$3 == "nfs" { print $2 " maps to " $1 }' /proc/mounts
cat /proc/mounts | while read src dest type opts rest; do
if [[ ${type} == "nfs" ]]; then
echo "NFS ${dest} maps to ${src}"
fi
done
命名約定(Naming Conventions)
函數(shù)名(Function Names)
使用小寫字母,并用下劃線分隔單詞。使用雙冒號 ::
分隔庫。函數(shù)名之后必須有圓括號。關(guān)鍵詞 function 是可選的,但必須在一個項(xiàng)目中保持一致。
如果你正在寫單個函數(shù),請用小寫字母來命名,并用下劃線分隔單詞。如果你正在寫一個包,使用雙冒號 ::
來分隔包名。大括號必須和函數(shù)名位于同一行(就像在 Google 的其他語言一樣),并且函數(shù)名和圓括號之間沒有空格。
# Single function
my_func() {
...
}
# Part of a package
mypackage::my_func() {
...
}
當(dāng)函數(shù)名后存在 ()
時,關(guān)鍵詞 function 是多余的。但是其促進(jìn)了函數(shù)的快速辨識。
變量名(Variable Names)
如函數(shù)名。
循環(huán)的變量名應(yīng)該和要循環(huán)的任何變量同樣命名。
for zone in ${zones}; do
something_with "${zone}"
done
常量和環(huán)境變量名(Constants and Environment Variable Names)
要大寫、用下劃線分割、聲明在文件的開頭。
常量和任何導(dǎo)出到環(huán)境的變量都應(yīng)該大寫。
# Constant
readonly PATH_TO_FILES='/some/path'
# Both constant and environment
# declare -r設(shè)置只讀變量,-x設(shè)置為環(huán)境變量
declare -xr ORACLE_SID='PROD'
有些第一次設(shè)置時(例如使用 getopts 情況下)就變成了常量。也就是說,可以在 getopts 中或基于條件來設(shè)定常量,但之后應(yīng)該立即設(shè)置其為只讀。需要注意的是,declare
不能在函數(shù)內(nèi)部操作全局變量,所以這時推薦使用 readonly
和 export
來代替。
VERBOSE='false'
while getopts 'v' flag; do
case "${flag}" in
v) VERBOSE='true' ;;
esac
done
readonly VERBOSE
源文件名(Source Filenames)
小寫,如果需要的話使用下劃線分隔單詞。
這是為了和在 Google 中的其他代碼風(fēng)格保持一致:maketemplate
或者 make_template
,而不是 make-template
。
只讀變量(Read-ony Variables)
使用 readonly
或者 declare -r
來確保變量只讀。
因?yàn)槿肿兞吭?Shell 中廣泛使用,所以在使用它們的過程中捕獲錯誤是很重要的。當(dāng)你聲明了一個希望其只讀的變量,那么請明確指出。
zip_version="$(dpkg --status zip | grep Version: | cut -d ' ' -f 2)"
if [[ -z "${zip_version}" ]]; then
error_message
else
readonly zip_version
fi
使用本地變量(Use Local Variables)
使用 local 聲明函數(shù)內(nèi)部變量。聲明和賦值應(yīng)該在不同行。
使用 local 來聲明局部變量以確保其只在函數(shù)內(nèi)部和子函數(shù)中可見。這避免了污染全局命名空間和不經(jīng)意間設(shè)置可能具有函數(shù)之外重要意義的變量。
當(dāng)賦值的值由命令替換提供時,聲明和賦值必須分開。因?yàn)閮?nèi)建的 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
...
}
函數(shù)位置(Function Location)
將文件中所有的函數(shù)一起放在常量下面。不要在函數(shù)之間隱藏可執(zhí)行代碼。
如果你有函數(shù),請將他們一起放在文件頭部。只有 includes
,set
語句和設(shè)置常數(shù)可能在函數(shù)定義前完成。
不要在函數(shù)之間隱藏可執(zhí)行代碼。如果那樣做,會使得代碼在調(diào)試時難以跟蹤并出現(xiàn)意想不到的討厭結(jié)果。
主函數(shù)(main)
對于足夠長的腳本來說,至少需要一個名為 main 的函數(shù)來調(diào)用其它的函數(shù)。
為了便于找到程序的起始位置,把主程序放在一個叫 main 的函數(shù)中,放在其它函數(shù)的下面,為了提供一致性你應(yīng)該定義更多的變量為本地變量(如果主程序不是一個程序,那么不能這么做),文件中最后一句非注釋行應(yīng)該是一個 main 函數(shù)的調(diào)用。
main "$@"
調(diào)用命令(Calling Commands)
檢查返回值(Checking Return Values)
總是應(yīng)該檢查返回值,給出返回值相關(guān)的信息。
對于一個未使用管道的命令,可以使用 $?
或者直接指向 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 同樣有 PIPESTATUE 變量允許檢查管道命令所有部分的返回碼,這僅僅用于檢查整個管道執(zhí)行成功與否。下面的例子是被接受的。
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
然后當(dāng)你使用任何其它命令的時候 PIPESTATUS 將會被覆蓋,如果你需要根據(jù)管道發(fā)生錯誤的地方來進(jìn)行不同的操作,那么你將需要在運(yùn)行完管道命令后立即將 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
內(nèi)置命令對比外部命令(Builtin Commands vs. External Commands)
可以在調(diào)用 Shell 內(nèi)建命令和調(diào)用另外的程序之間選擇,請選擇內(nèi)建命令。
我們更喜歡使用內(nèi)建命令,如在 bash(1) 中參數(shù)擴(kuò)展函數(shù)。因?yàn)樗鼜?qiáng)健和便攜(尤其是跟像 sed 這樣的命令比較)
例如:
# Prefer this:
addition=$((${X} + ${Y}))
substitution="${string/#foo/bar}"
# Instead of this:
addition="$(expr ${X} + ${Y})"
substitution="$(echo "${string}" | sed -e 's/^foo/bar/')"
結(jié)論(Conclusion)
引用
- 原文來自于 Google 的 Shell Style Guide
- 參照 Shell 風(fēng)格指南
- 參照 Google Style Guides-Shell Style Guide