这是我写BASH程序的招式。这里本没有什么新的内容,但是从我的经验来看,人们爱滥用BASH。他们忽略了计算机科学,而从他们的程序中创造的是“大泥球”(译注:指架构不清晰的软件系统)。
在此我告诉你方法,以保护你的程序免于障碍,并保持代码的整洁。
不可改变的全局变量
-
尽量少用全局变量
-
以大写命名
-
只读声明
-
用全局变量来代替隐晦的$0,$1等
-
在我的程序中常使用的全局变量:
readonly PROGNAME=$(basename $0)
|
readonly PROGDIR=$(readlink -m $(dirname $0))
|
一切皆是局部的
所有变量都应为局部的。
chown $user:$group $filename
|
change_owner_of_files() {
|
-
自注释(self documenting)的参数
-
通常作为循环用的变量i,把它声明为局部变量是很重要的。
-
局部变量不作用于全局域。
bash: local: can only be used in a function
|
main()
-
有助于保持所有变量的局部性
-
直观的函数式编程
-
代码中唯一的全局命令是:main
local files="/tmp/a /tmp/b"
|
change_owner_of_file kfir users $i
|
一切皆是函数
- 不可变的全局变量声明
- main()函数
local files=$(ls /tmp | grep pid | grep -v daemon)
|
local files=$(temporary_files /tmp)
|
touch $dir/a-pid1232-daemon.tmp
|
returns "$dir/a-pid1232.tmp" temporary_files $dir
|
returns "$dir/a-pid1232.tmp $dir/b-pid1534.tmp" temporary_files $dir
|
如你所见,这个测试不关心main()。
调试函数
只调试一小段代码,使用set-x和set+x,会只对被set -x和set +x包含的当前代码打印调试信息。
打印函数名和它的参数:
调用函数:
会打印到标准输出:
代码的清晰度
这段代码做了什么?
让你的代码说话:
每一行只做一件事
ls $dir | grep pid | grep -v daemon
|
可以写得简洁得多:
符号在行末的坏例子:(译注:原文在此例中用了temporary_files()代码段,疑似是贴错了。结合上下文,应为print_dir_if_not_empty())
print_dir_if_not_empty() {
|
好的例子:我们可以清晰看到行和连接符号之间的联系。
print_dir_if_not_empty() {
|
打印用法
不要这样做:
echo "this prog does:..."
|
它应该是个函数:
echo "this prog does:..."
|
echo在每一行重复。因此我们得到了这个文档:
Program deletes files from filesystems to release space.
|
It gets config file that define fileystem paths to work on, and whitelist rules to
|
-c --config configuration file containing the rules. use --help-config to see the syntax.
|
-n --pretend do not really delete, just how what you are going to do.
|
-t --test run unit test to check the program
|
-v --verbose Verbose. You can specify more then one -v to have more verbose
|
--help-config configuration help
|
$PROGNAME --test test_string.sh
|
$PROGNAME --config /path/to/config/$PROGNAME.conf
|
Just show what you are going to do:
|
$PROGNAME -vn -c /path/to/config/$PROGNAME.conf
|
注意在每一行的行首应该有一个真正的制表符‘\t’。
在vim里,如果你的tab是4个空格,你可以用这个替换命令:
命令行参数
这里是一个例子,完成了上面usage函数的用法。我从Kirk’s blog post – bash shell script to use getopts with gnu style long positional parameters得到这段代码
# got this idea from here:
|
#translate --gnu-long-options to -g (short options)
|
--config) args="${args}-c ";;
|
--pretend) args="${args}-n ";;
|
--test) args="${args}-t ";;
|
--help-config) usage_config && exit 0;;
|
--help) args="${args}-h ";;
|
--verbose) args="${args}-v ";;
|
--debug) args="${args}-x ";;
|
#pass through anything else
|
*) [[ "${arg:0:1}" == "-" ]] || delim="\""
|
args="${args}${delim}${arg}${delim} ";;
|
#Reset the positional parameters to the short options
|
while getopts "nvhxt:c:" OPTION
|
verbose VINFO "Running tests"
|
readonly CONFIG_FILE=$OPTARG
|
if [[ $recursive_testing || -z $RUN_TESTS ]]; then
|
[[ ! -f $CONFIG_FILE ]] \
|
&& eexit "You must provide --config file"
|
你像这样,使用我们在头上定义的不可变的ARGS变量:
单元测试
-
在更高级的语言中很重要。
-
使用shunit2做单元测试
test_config_line_paths() {
|
local s='partition cpm-all, 80-90,'
|
returns "/a" "config_line_paths '$s /a, '"
|
returns "/a /b/c" "config_line_paths '$s /a:/b/c, '"
|
returns "/a /b /c" "config_line_paths '$s /a : /b : /c, '"
|
local partition_line="$@"
|
这里是另一个使用df命令的例子:
Filesystem 1K-blocks Used Available Use% Mounted on
|
124628916 23063572 100299192 19% /
|
returns 1000 "disk_size /dev/sda1"
|
returns 124628916 "disk_size /very/long/device/path"
|
| awk "{print \$$column}"
|
这里我有个例外,为了测试,我在全局域中声明了DF为非只读。这是因为shunit2不允许改变全局域函数
阅读(971) | 评论(0) | 转发(0) |