1. 程式人生 > 實用技巧 >shell腳本系列:程式設計風格

shell腳本系列:程式設計風格

函式名

小寫字母、下劃線:

# Single function
my_func() {
  ...
}

# Part of a package
mypackage::my_func() {
  ...
}

變數名

小寫字母、下劃線、迴圈變數:

for zone in ${zones}; do
    something_with "${zone}"
done

只讀變數

使用readonly或者declare -r宣告:

zip_version="$(dpkg --status zip | grep Version: | cut -d ' ' -f 2)"
if [[ -z "${zip_version}" ]]; then
    error_message
else
    readonly zip_version
fi

常量和環境變數

大寫字母:

# Constant
readonly PATH_TO_FILES='/some/path'

# Both constant and environment
declare -xr ORACLE_SID='PROD'

VERBOSE='false'
while getopts 'v' flag; do
    case "${flag}" in
        v) VERBOSE='true' ;;
    esac
done
readonly VERBOSE

使用本地變數

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

...
}

原始檔名

小寫字母、下劃線:

make_template.sh

列印錯誤資訊

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

基本語句格式

縮排四個空格,或者一個製表符,一個製表符設定為四個空格。

行的長度最大為80字元,使用\換行。

管道:如果一行容得下整個管道操作,那麼請將整個管道操作寫在同一行。否則,應該將整個管道操作分割成每行一個管段,管道操作的下一部分應該將管道符放在新行並且縮排2個空格。這適用於使用管道符’|’的合併命令鏈以及使用’||’和’&&’的邏輯運算鏈。

# All fits on one line
command1 | command2

# Long commands
command1 \
  | command2 \
  | command3 \
  | command4

for迴圈

請將 ; do , ; then 和 while , for , if 放在同一行。

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 "${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 ,詳細解釋如下:

  • 與現存程式碼中你所發現的保持一致。
  • 引用變數參閱下面一節,引用。
  • 除非絕對必要或者為了避免深深的困惑,否則不要用大括號將單個字元的shell特殊變數或定位變數括起來。推薦將其他所有變數用大括號括起來。

引用

set -- 1 "2 two" "3 three tres"; echo $# ; set -- "$*"; echo "$#, $@"
set -- 1 "2 two" "3 three tres"; echo $# ; set -- "$@"; echo "$#, $@"

命令替換

使用 $(command) 而不是反引號`command`。

test、[和[[

推薦使用 [[ ... ]] ,而不是 [ , test , 和 /usr/bin/ [ 。

if [[ "filename" =~ ^[[:alnum:]]+name ]]; then
    echo "Match"
fi

if [[ "filename" == "f*" ]]; then
    echo "Match"
fi

測試字串

if [[ "${my_var}" = "some_string" ]]; then
    do_something
fi

if [[ -n "${my_var}" ]]; then
    do_something
fi
# string不空時為真

if [[ -z "${my_var}" ]]; then
    do_something
fi
# string空時為真

if [[ "${my_var}" = "" ]]; then
    do_something
fi
# string空時為真

檔名的萬用字元擴充套件

因為檔名可能以 - 開頭,所以使用擴充套件萬用字元 ./* 比 * 來得安全得多。

rm -v ./*    # 好
rm -v *    # 不好

管道導向while迴圈

管道導向while迴圈中的隱式子shell使得追蹤bug變得很困難。

last_line='NULL'
your_command | while read line; do
    last_line="${line}"
done

echo "${last_line}"

如果你確定輸入中不包含空格或者特殊符號(通常意味著不是使用者輸入的),那麼可以使用一個for迴圈。

total=0
for value in $(command); do
    total+="${value}"
done

使用過程替換允許重定向輸出,但是請將命令放入一個顯式的子shell中,而不是bash為while迴圈建立的隱式子shell。

total=0
last_file=
while read count filename; do
    total+="${count}"
    last_file="${filename}"
done < <(your_command | uniq -c)

echo "Total = ${total}"
echo "Last one = ${last_file}"

當不需要傳遞複雜的結果給父shell時可以使用while迴圈。這通常需要一些更復雜的“解析”。請注意簡單的例子使用如awk這類工具可能更容易完成。當你特別不希望改變父shell的範圍變數時這可能也是有用的。

cat /proc/mounts | while read src dest type opts rest; do
    if [[ ${type} == "nfs" ]]; then
        echo "NFS ${dest} maps to ${src}"
    fi
done

檢查返回值

mv "${file_list}" "${dest_dir}/"
if [[ "$?" -ne 0 ]]; then
    echo "Unable to move ${file_list} to ${dest_dir}" >&2
    exit "${E_BAD_MOVE}"
fi