免费注册 查看新帖 |

Chinaunix

  平台 论坛 博客 文库
最近访问板块 发新帖
查看: 1177 | 回复: 0
打印 上一主题 下一主题

脚本编程编码风格 [复制链接]

论坛徽章:
0
跳转到指定楼层
1 [收藏(0)] [报告]
发表于 2008-08-01 02:28 |只看该作者 |倒序浏览

背景:这是一篇我参考很多资料后为原公司编写的coding style说明,个人感觉代码风格很重要,希望大家也形成自己的coding style。

1.        编写背景
“这篇简短的文章描述了Linux内核首选的编码风格。编码风格是很个人化的东西,我不会把自己的观点强加给任何人。但是,Linux内核的代码毕竟是我必须有能力维护的,因此我宁愿它的编码风格是我喜欢的。请至少考虑一下这一点。”
摘自Linux Kernel Coding Style -- Torvalds Linus

众所周知,现在的中国,是一个自由、开放的国度,而我们系统部,更是秉承这一“Open”的精神,为保障我们的系统能正常的、稳定的、高效的为用户提供服务,我们使用了各种开源界软件,这些开源软件对于我们系统的发展,是功不可没的。Shell就是开源软件星空中最灿烂的星星之一。在我们维护系统、排查故障、优化性能的过程中,无一不和shell紧密的接触。而shell编程,则是提高我们工作效率的有效方法之一。
在我们现有的系统中,存在着我们编写的很多shell脚本,但由于脚本是由不同的同事进行编写的,个人的编码风格也不尽相同,在阅读别人编写的脚本时,编码风格相差太大就会影响到我们的工作效率,例如以下的几种典型情况,就会影响到我们工作的效率了:

1.    公司以及个人的发展,必然会有老同事的离职,新同事的就职,那么,老同事所编写和维护的脚本,就会交给新同事来维护和发展。
2.    对于其他同事维护的脚本,也经常会有不同的同事来进行维护。
3.    在进行相对规模较大的脚本的开发时,经常会是几个人一起进行开发,不同编码风格会影响各个模块的合成以及之后的测试调优过程。

因此,我们迫切的需要各位同事的shell编码风格能尽量的相近,基于这样的情况,我们编写了《脚本编程编码风格》这篇文档。
2.        导读
编码规则设定之初,必定有许多不完善,我们期待后来者将其完善。本文档主要分为以下几个部分:

1.    脚本版式
2.    命名规则
3.    目录结构
4.    VI的配置
5.    其他事项

本文档参考了以下资料:
Linux Kernel Ccoding Style -- Torvalds Linus
3.        脚本版式
写脚本也是一门艺术,良好清晰的版式,能让人一目了然,让阅读者有清晰的逻辑。
3.1    Tab与对齐
Tab(制表符)通常为8个字符,Linus在他的《Linux Kernel Coding Style》里这样叙述到:“有些叛逆主张试图把缩进变成4个(甚至是2个!)字符的长度,这就好象试图把PI定义成3是一样的。……有些人说8个字符大小的缩进导致代码太偏右了,并且在一个80字符宽的终端屏幕上看着很不舒服。对这个问题的回答是:如果你有超过3个级别的缩进,你就有点犯糊涂了,应当修改你的程序。”
对于这样描述,我的看法是,Kernel是用C语言来编写的,C语言的语句不像shell,shell编程中,即使一个判断语句,都可能因为使用了管道和命令反译而变得很长,因此,我们推荐Tab的字符数为4个字符。
3.2    大括号{}
Shell中大括号可以用在变量的保护,命令的组合等地方,但是这里我们主要描述在函数里的用法,我们推荐函数的边界大括号单独占一行,缩进与函数名一致,函数体内缩进一次,例如:

func_exam()
{
commands

}
3.3    空格和空行
空行起着分隔程序段落的作用。空行不会浪费内存,所以不要舍不得用空行。空格的使用使得程序看起来很“块状”,逻辑结构更清晰。我们建议:

1.         每个函数定义结束后要加空行。
2.         逻辑块之间要有空行分隔。
3.         逻辑紧密的语句之间不加空格。

空格的使用情况较多,也不好归纳,下面仅仅举几个例子来说明:
1.         定义变量时空格用于美化视图。

firstVar=value1
secondVar=value2
thirdVar=value3
2.         管道、重定向前后建议加空格。

command1 | command2 > /dev/null 2>&1
3.         运算符、判断符前后建议加空格,有语法限制的除外。

if [[ -d dir1 ]]
then

fi
对于较长的表达式,为了结构紧凑可以适当去掉一些空格,例如在for循环中。
3.4    函数、选择、循环的规范结构
1.         函数
函数建议的规范如下:

func_exam()
{
commands

}
2.         选择
选择包括if和case两种,当然还有select,但它是一个和case结合使用的循环,用得比较少。

if condition
then
    commands

fi

if condition
then
    commands1

else
    commands2

fi

if condition1
then
    commands1

elif condition2
then
    commands2

else
    commands3

fi
3.         循环
循环主要使用while和for。

while condition
do
    commands

done

for var in value_list
do
    commands

done

for ( init var ; condition ; change var )
do
    commands

done
4.        命名规则
没有一种命名规则是所有人都赞同的,在这里,我们只是把常用的命名规则罗列出来,然后从中找出适合shell编程和便于理解的规则作为我们的命名规则。当然,我们不会只推荐一种,我们会推荐一两种,然后希望这一两种成为我们的规范。
4.1    常见命名规则
比较著名的命名规则首推匈牙利命名法,这种命名方法是由Microsoft程序员查尔斯·西蒙尼(Charles Simonyi) 提出的。其主要思想是“在变量和函数名中加入前缀以增进人们对程序的理解”。匈牙利命名法关键是:标识符的名字以一个或者多个小写字母开头作为前缀;前缀之后的是首字母大写的一个单词或多个单词组合,该单词要指明变量的用途。例如:lpszStr, 表示指向一个以'\0'结尾的字符串(sz)的长指针(lp)变量。
骆驼(Camel)命名法近年来越来越流行,在许多新的函数库和Java这样的平台下使用得当相多。骆驼命名法,正如它的名称所表示的那样,指的是混合使用大小写字母来构成标识符的名字。其中第一个单词首字母小写,余下的单词首字母大写。例如:printEmployeePaychecks(),函数名中每一个逻辑断点都有一个大写字母来标记。
帕斯卡(Pascal)命名法与骆驼命名法类似。只不过骆驼命名法是第一个单词首字母小写,而帕斯卡命名法则是第一个单词首字母大写。例如:DisplayInfo()和UserName都是采用了帕斯卡命名法。
在C#中,以帕斯卡命名法和骆驼命名法居多。事实上,很多程序设计者在实际命名时会将骆驼命名法和帕斯卡结合使用,例如变量名采用骆驼命名法,而函数采用帕斯卡命名法。
另一种流行的命名规则称为下划线命名法。
下划线法是随着C语言的出现流行起来的,在UNIX/LIUNX这样的环境,以及GNU代码中使用非常普遍。
对于以上的几种命名规则,我们认为比较对变量或函数进行描述的是骆驼命名法和下划线命名法,因此,我们推荐使用这两种命名方面。
4.2    函数的命名
函数的命名推荐使用下划线分割小写字母的方式。命名结构形如:
1.         设备名_操作名():表达的意思是要对什么设备进行什么操作,例如array_handle()表示对一个数组进行操作。
2.         操作名():一些简单的操作,可以直接用操作名来做函数名,只要不和系统命令冲突,例如email()表示发送email。
4.3    变量的命名
为区别于函数,变量的命名方法我们推荐骆驼命名法,因为unix/linux衍生于C。命名时尽量用有意义的单词,单词所写能表达意思的尽量用单词缩写。例如progRoot表示程序的根目录,logDir表示日志文件的目录。
常用的缩写词有:
原词
缩写
原词
缩写
原词
缩写
addition
add
answer
ans
array
arr
average
avg
buffer
buf/buff
check
chk
count
cnt
column
col
control
ctrl/ctl
define
def
delete
del
distination
dist
display
disp
division
div
environment
env
error
err
frequency
freq
header
hdr
index
idx
image
img
increment
inc
initialize
init
length
len
memory
mem
middle
mid
make
mk
message
msg
number
num
operator
optr
packet
pkt
position
pos
previous
pre/prev
pointer
ptr
record
rcd
receive
recv
result
res
return
ret
source
src
stack
stk
string
str
subtraction
sub
table
tab
temporary
tmp/temp
total
tot
time stamp
ts
value
val
variable
var


4.4    常量的命名
Shell编写过程中,常量和变量的区分不是太清晰,因此,其命名规则可以参照变量命名法。但如果有需要特别注意的,建议用下划线分割大写字母的方法。例如,GLOBAL_VAR1。
4.5    函数内部变量
函数中可以使用local定义仅在函数内部使用的局部变量,为区别于全局变量,建议在局部变量之前加下划线,例如local _funcLocalVar。
5.        目录结构
为便于管理,我们建议稍具规模的脚本使用如下的目录结构:

progName/     :以脚本名作为根目录名
|-- conf           :配置文件目录
|-- data           :源/目的数据目录
|-- logs           :日志存放目录
|-- modules     :子模块目录
|-- run            :脚本运行临时文件目录
`-- tools          :第三方工具目录
若有子模块,子模块也使用相同的目录结构。
6.        编辑器的配置
这里只讨论我们常用的vi配置。下面的配置使得vi能通过脚本后缀名来使用不同的语法高亮,自动缩进等功能。

set nocompatible " get out of horrible vi-compatible mode,不使用vi兼容模式
filetype on " detect the type of file,检测文件类型
filetype plugin on " load filetype plugins,加载文件类型插件

set background=dark " we are using a dark background,使用暗的背景
syntax on " syntax highlighting on,语法高亮
set nobackup " do not make backup file,不备份文件

set ruler " Always show current positions along the bottom,显示光标当前位置
set number " turn on line numbers,显示行数
set backspace=2 " make backspace work normal,
set nohlsearch " do not highlight searched for phrases,搜索时不高亮关键词
set incsearch " BUT do highlight as you type you search phrase,但在输入关键词时高亮

set ai " autoindent,自动缩进
set si " smartindent,智能缩进
set cindent " do c-style indenting,进行c风格的缩进
set tabstop=4 " tab spacing (settings below are just to unify it),Tab占4个字符
set softtabstop=4 " unify,与Tab统一
set shiftwidth=4 " unify,与Tab统一
set noexpandtab " real tabs please!,不实用扩展的Tab
set smarttab " use tabs at the start of a line, spaces elsewhere,除行开头,其余的Tab用空格替换
7.        其他事项
7.1    注释
注释是必须的,否则,你可能会忘记一个星期前写的一个脚本是用来干嘛的。但过量的注释会使得脚本看起来很复杂,也很臃肿。
注释应该放在使用之前,如果是变量的话,建议与变量处于同一行,使得结构更紧凑。函数的注释也应该放在函数体之前,并且要对函数进行简单描述和如何使用等。
例如:
变量注释:

progRoot=/var/PROGRAM/purge_cache     # Program's running path.
log=$protRoot/logs/purge_cache.log           # Log file name.
servList=$progRoot/serv.list                       # List all servers' ip.
servListRo=$progRoot/run/.serv.list.ro         # Standard servers' file.
函数的注释:

# Export an array to a file, line by line.
# Usage : array_2_file ARRAY_NAME TO_FILE_NAME
array_2_file()
{
    commands

}
7.2    脚本整体结构
脚本的整体结构,建议做成如下形式:

#!/bin/bash

#
#脚本名、作者、时间和相关描述信息
#

#函数定义区


#脚本主体
#定义目录、日志等变量

#主要逻辑区


#结束
7.3    函数/模块的重用
一旦写的脚本达到一定数量,我们就会慢慢感觉到,有很多脚本语句是重复出现的,比如,发送email、清空临时文件、日志打包等,这时候,我们就可以考虑把经常需要做的工作集中到一个函数中来处理,下次要再使用的时候,只需要给函数传递一定的参数就可以了。
借用C语言中接口和实现的定义:
一个函数/模块由两部分组成:接口和实现。接口指明模块要做什么,它声明了该模块可用的标识符、类型和例程;实现指明模块是如何完成其接口声明的目标的。客户调用程序是使用某个模块的一段代码。客户调用程序导入接口;而实现导出接口。客户调用程序只需要了解接口即可。接口与实现只需一次编写和调试就可多次使用,这就是可重用性。
接口只需要指明客户调用程序可使用的标识符即可,应尽可能地隐藏一些无关的表示细节和算法,这样客户调用程序可以不必依赖于特定的实现细节,即减少了它们之间的耦合。接口在头(.h)文件中声明,该文件声明了客户调用程序可以使用的宏、类型、数据结构、变量以及例程。用户通过预处理指令#include导入接口。
实现导出接口。它定义了必要的变量和函数,以提供接口所规定的功能。一个实现揭示了表示的细节和接口给出的特定行为的算法。理想情况是:客户调用程序根本不需要了解这些细节。实现是由一个或多个定义(.c)文件提供的。一个实现必须提供其导出的接口所指定的功能。实现应包含接口的.h文件,以保证它的定义与接口的声明是一致的。
尽管上述的接口和实现的定义是基于C语言编程的,我们仍然可以从中得到启发。我们可以在函数化/模块化之后,在脚本中使用source(或点“.“命令)来将函数引如脚本,然后就可以在脚本中自由使用了。
8.        脚本实例
下面是一个简单的脚本,用途是监控磁盘容量的变化,并报警,在此贴出来,权当做此次规范的实例。

#!/bin/bash

# script: diskUsage.sh
# author: Micheas Liao
# date: Tue Jul 31 15:12:43 CST 2007

# function

# sendMail
# Usage: sendMail
sendMail()
{
    env MAILRC=/dev/null chartset=C from=support@pconline.com.cn smtp=61.145.113.31 \
    nail -n -s "NAS/netApp diskUsage Report from $hostName($ip)" bb@pconline.com.cn
}

export PATH=/bin:/sbin:/usr/bin:/usr/sbin:/usr/local/bin:/usr/local/sbin
export LANG=C

# main
progRoot=/var/PROGRAM/diskUsage
  runDir=$progRoot/run
  logDir=$progRoot/logs
     log=$logDir/diskUsage.log
   dfLog=$logDir/df.log
   dfTmp=$runDir/df.tmp
mailTag=$run/.mail.tag

  ipList="192.168.200.3 192.168.200.4 192.168.200.5"
warnCode=90

hostName=$(hostname -s)
      ip=$(hostname -i)

cd $progRoot
[[ ! -d $runDir ]] && mkdir $runDir
[[ ! -d $logDir ]] && mkdir $logDir
[[ ! -f $log ]] && touch $log
[[ ! -f $log.tmp ]] && touch $log.tmp
[[ ! -f $dfLog ]] && touch $dfLog

# check ip
for _ip in $ipList
do
    _pingOpt="-c 4 -i 0.5"

    ping $_pingOpt $_ip > /dev/null 2>&1
    if [[ $? != "0" ]]
    then
        sleep 5
        ping $_pingOpt $_ip > /dev/null 2>&1
        if [[ $? != "0" ]]
        then
            date +%c >> $log.tmp
            echo "
----------
Ping NAS/netApp: $_ip failed!!!
Pls check!!!
Maybe NOT JUST this one!!!
----------
            " >> $log.tmp
            sendMail
            cat $log.tmp >> $log ; rm -fr $log.tmp
            exit 1
        fi
    fi
done

# check disk usage
dfOpt="-Pk -t nfs"
df $dfOpt > $dfTmp
( date +%c ; echo "----------" ; cat $dfTmp ; echo "----------" ) >> $dfLog

cat $dfTmp | grep "%" | while read line
do
    [[ "x$line" = "x" ]] && continue

    fileSys=$(echo $line | awk '{print $1}')
    useBlks=$(echo $line | awk '{print $3}')
    usePctg=$(echo $line | awk '{split($5,tmp,/%/);print tmp[1]*100}')
    mountPt=$(echo $line | awk '{print $6}')

    if (( usePctg >= warnCode*100 ))
    then
        touch $mailTag
    fi

    fmtName=$(echo $fileSys | sed -e 's@:@@' -e 's@/@.@g')
    [[ ! -f $logDir/$fmtName ]] && touch $logDir/$fmtName

    ymdHM=$(date +%Y%m%d%H%M)

    echo $ymdHM $useBlks $usePctg >> $logDir/$fmtName
done

if [[ -e $mailTag ]]
then
    date +%c >> $log.tmp
    echo "
----------
NAS/netApp disk usage: WARNING
    " >> $log.tmp
    cat $dfTmp | awk '{printf "%s\n%-25s%-25s%-15s%-10s%s\n\n",$1,$2,$3,$4,$5,$6}' >> $log.tmp
    echo "----------" >> $log.tmp
    sendMail
    rm -fr $mailTag $log.tmp $dfTmp
fi

rm -f $mailTag
# end



本文来自ChinaUnix博客,如果查看原文请点:http://blog.chinaunix.net/u2/73914/showart_1098827.html
您需要登录后才可以回帖 登录 | 注册

本版积分规则 发表回复

  

北京盛拓优讯信息技术有限公司. 版权所有 京ICP备16024965号-6 北京市公安局海淀分局网监中心备案编号:11010802020122 niuxiaotong@pcpop.com 17352615567
未成年举报专区
中国互联网协会会员  联系我们:huangweiwei@itpub.net
感谢所有关心和支持过ChinaUnix的朋友们 转载本站内容请注明原作者名及出处

清除 Cookies - ChinaUnix - Archiver - WAP - TOP