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

shell脚本系列:编程风格

上一篇:Linux系统启动


下一篇:克里斯坦森的破坏性创新—《可以量化的管理学》