开发者社区> 异步社区> 正文

《UNIX/Linux 系统管理技术手册(第四版)》——2.2 bash脚本编程

简介:
+关注继续查看

本节书摘来自异步社区《UNIX/Linux 系统管理技术手册(第四版)》一书中的第2章,第2.2节,作者:【美】Evi Nemeth , Garth Snyder , Trent R.Hein , Ben Whaley著,更多章节内容可以访问云栖社区“异步社区”公众号查看

2.2 bash脚本编程

UNIX/Linux 系统管理技术手册(第四版)
bash特别适合编写简单的脚本,用来自动执行那些以往在命令行输入的操作。在命令行用的技巧也能用在bash的脚本里,反之亦然,这让用户在bash上投入的学习时间获得了最大的回报。不过,一旦bash脚本超过了100行,或者需要的特性bash没有,那么就要换到Perl或者Python上了。

bash脚本的注释以一个井号(#)开头,并且注释一直延续到行尾。和命令行中一样,可以把逻辑上的一行分成多个物理上的多行来写,每行末尾用反斜线消除换行符(newline)。还可以用分号分隔语句的办法,在一行里书写多条语句。

bash脚本可以只包含一系列的命令行,此外其他什么都没有。例如,下面的helloworld脚本就只有一条echo命令。

!/bin/bash

echo "Hello,  world!"

第一行叫做“#!”语句,它声明这个文本文件是一个脚本,要由/bin/bash来解释。内核在决定如何执行这个文件的时候,要先找这个语句。从派生出来执行这个脚本的shell的角度来看,“#!”行只是一个注释行。如果bash不在这行指定的位置那里,那么就需要调整这行的内容。

要让这个文件做好能运行的准备,只要设置它的可执行位即可(参考6.5.5节)。

$ chmod +x  helloworld

$ ./helloworld3

Hello, world!

1

还可以把shell当做解释程序直接调用:

$ bash helloworld

Hello, world!

$ source helloworld

Hello, world!

第一条命令在一个shell的新实例中运行helloworld脚本,第二条命令让当前的登录shell读取并执行这个文件的内容。当这个脚本用来设置环境变量,或者只对当前的shell做定制的时候,就采用后一种选择。在脚本编程中,这种形式常用来加入一个配置文件的内容,该文件里面写的是对一系列bash变量进行赋值2。

如果是Windows用户,那么可能已经习惯于这样的做法,即由文件的扩展名标明该文件的类型,以及是否可以执行。但在UNIX和Linux上,要由文件的权限位来指定一个文件是否可以执行,如果可执行,那么由谁可以执行。如果愿意,可以给自己的bash脚本加.sh后缀,提醒用户它们是什么文件,但在运行该命令的时候,就必须得输入.sh,因为UNIX不会对扩展名做特殊处理。参考6.5.1节了解有关权限位的更多知识。

2.2.1 从命令到脚本
在我们开始介绍bash的脚本编程特性之前,先讲一下方法。大多数人写bash脚本的时候,都按照和他们写Perl或者Python脚本一样的方式:用一个文本编辑器来写。不过,把常规的shell命令行当做一种交互式的脚本开发环境,考虑这样用的话,效果会更高。

例如,假定在一个目录层次结构中,散布着很多日志文件,它们的名字后缀为.log和.LOG,现在想把它们都改为大写的形式。首先,让我们看看是否能找到所有这样的文件。

$ find  . -name ' *log '

.do-not-touch/important.log admin.com-log/

foo.log genius/spew.log leather_flog

…

哦,看起来我们要在搜索模式中包括点号(.),而且还要排除目录。键入重新找回这条find命令,然后对它进行修改。

$ find  . -type f -name ' *.log '

.do-not-touch/important.log foo.log

genius/spew.log

…

好了,这次看上去结果更好了。不过,.do-not-touch目录看上去挺危险的;我们或许不应该让它出来捣乱。

$ find  . -type f -name ' *.log ' | grep  -v  .do-not-touch

foo.log genius/spew.log

…

好了,正好剩下需要的文件清单。让我们生成一些新的名字。

$ find  . -type f -name ' *.log ' | grep  -v  .do-not-touch | while read  fname

> do

> echo mv  $fname ${fname/.log/.LOG/}

> done

mv  foo.log foo.LOG

mv  genius/spew.log genius/spew.LOG

…

好,那几条命令就是我们想要的命令,把它们运行起来就可以执行改名操作。那么在现实中,我们该怎么做呢?我们可以把这条命令重新找回来,编辑一下把echo去掉,让bash执行mv命令,而不仅仅是打印mv命令。不过,用管道把这些命令都送到另一个shell的实例,这样更不容易出错,而且需要对前面命令做的编辑也更少。

当键入的时候,我们会发现bash考虑得很精心,它把这个小小的脚本变成了一行。对于这个紧凑的命令行,我们只要加一个管道,把输出送给bash -x就行了。

$ find  . -type f -name ' *.log ' | grep  -v  .do-not-touch | while read  fname; do echo mv  $fname  ${fname/.log/.LOG/}; done  | bash -x

+ mv  foo.log foo.LOG

+ mv  genius/spew.log genius/spew.LOG

…

给bash加了-x选项后,它在执行每条命令之前,会先打印这条命令。

我们现在已经完成了实际的改名工作,但是仍然想把整个脚本保存下来,以便可以再次使用它。bash的内置命令fc和非常像,但它不是让上次的命令重新出现在命令行,而是把该命令送到用户选择的编辑器里。再加一个“#!”行和用法说明之后,把这个文件写到一个可以执行的地方(或许是~/bin,或者/usr/local/bin),让这个文件可执行,于是就得到了最终的脚本。

上述方法总结如下:

按一个管道的方式开发脚本(或者脚本的组成部分),一次开发一步,完全都在命令行上做;
把输出送到标准输出,检查并确保结果正确;
每开发一步,用shell的history命令重新找回命令管道,用shell的编辑功能调整它们;
在得到正确输出之前,都不实际执行任何操作,所以如果命令不正确,也不需要撤销什么操作;
一旦得到正确的输出,就真正执行命令,并核对命令能按预期要求工作;
用fc命令捕获工作结果,整理后保存下来。
在上面的例子里,我们打印出数行命令,然后用管道把它们送入一个子shell去执行。这一技术并不一定行得通,但它经常还是有帮助的。另一种做法是,可以把输出重定向到一个文件,得到这个结果。无论怎样,都要预先看到正确的结果,才做任何可能有破坏性的操作。

2.2.2 输入和输出
echo命令虽然原始,但易于使用。要想对输出做更多的控制,就需要使用printf命令。因为采用printf的话,必须显式地在必要的地方加换行符(用“n”),所以它用起来稍有不便,不过它也能让用户使用制表符,而且能让输出里的数字有更好的格式。比较下面两条命令的输出。

$ echo "\taa\tbb\tcc\n"

\taa\tbb\tcc\n

$ printf  "\taa\tbb\tcc\n"

aa  bb  cc

有些系统带有操作系统级的echo和printf命令,通常分别位于/bin和/usr/bin目录下。虽然这两条命令和shell的内置命令都很相似,但是它们的细节还是稍有不同,特别是printf,差别更大一些。对此,要么坚持采用bash的语法,要么用完整路径名调用外部的printf命令。

用read命令可以提示输入。下面是一个例子:

!/bin/bash

echo -n  "Enter  your name:  " read user_name

if [ -n  "$user_name" ]; then echo "Hello  $user_name!" exit  0

 else

fi

 echo  "You did  not tell  me  your name!" exit  1

echo命令的-n选项消除了通常的换行符,但也可以在这里用printf命令。我们简要介绍一下if语句的语法,它的作用在这里很明显。if语句里的-n判断其字符串参数是否为空,不为空的话则返回真(true)。下面是这个脚本运行后的结果:

$ sh readexample Enter your name: Ron Hello Ron!
2.2.3 命令行参数和函数
给一个脚本的命令行参数可以成为变量,这些变量的名字是数字。$1是第一个命令行参数,$2是第二个,以此类推。$0是调用该脚本所采用的名字。这个名字可以是像../bin/example.sh这样的奇怪名字,所以它的取值并不固定。

变量$#是提供给脚本的命令行参数的个数,变量$*里保存有全部参数。这两个变量都不包括或者算上$0。

如果调用的脚本不带参数,或者参数不正确,那么该脚本应该打印一段用法说明,提醒用户怎样使用它。下面这个脚本的例子接受两个参数,验证这两个参数都是目录,然后显示它们。如果参数无效,那么这个脚本会打印一则用法说明,并且用一个非零的返回码退出。如果调用这个脚本的程序检查该返回码,那么它就会知道这个脚本没有正确执行。

!/bin/bash

function show_usage {

echo "Usage:  $0  source_dir dest_dir" exit  1

}

 Main  program starts here if [ $#  -ne  2 ]; then

show_usage

else  # There are two arguments if [ -d  $1  ]; then

source_dir=$1

 else

fi

 echo 'Invalid source directory' show_usage

 if [ -d  $2  ]; then dest_dir=$2

 else

fi fi

 echo 'Invalid destination  directory' show_usage

 printf "Source  directory is  ${source_dir}\n" printf "Destination directory is  ${dest_dir}\n"

我们创建了一个单独的show_usage函数,用它打印用法说明。如果这个脚本以后又做了更新,能够接受更多的参数,那么只要在一个地方修改用法说明就行了3。

$ mkdir  aaa  bbb

$ sh  showusage aaa  bbb Source directory is  aaa Destination directory is  bbb

$ sh  showusage foo  bar

Invalid source directory

Usage: showusage  source_dir dest_dir

bash函数的参数就按命令行参数那样处理。第一个参数变成$1,以此类推。正如上面的例子所示,$0是这个脚本的名字。

要让上面的例子更健壮一点儿,我们可以编写show_usage函数,让它接受一个出错码作为参数。对于执行不成功的每一种不同类型,返回一个定义好的出错码。下面的代码片段给出了该函数的样子。

function show_usage {

echo "Usage:  $0  source_dir dest_dir" if [ $#  -eq  0 ]; then

exit  99  # Exit  with arbitrary nonzero return code

 else

fi

}

 exit  $1

下面这个版本的函数,其参数可有可无。在一个函数内部,$#表明传入了多少个参数。如果没有提供更确定的出错码,那么这个脚本就返回代码99。但是如果给这个函数一个确定的出错码值,就会让脚本在打印用法说明之后以那个出错码退出,例如:

show_usage  5

(shell变量$?是上次执行的命令退出的状态,而且无论该命令是在一个脚本内部使用,还是在命令行上使用。)

在bash里,函数和命令之间很类似。用户可以在自己的~/.bash_profile文件里定义自己的函数,然后在命令行上使用它们,就好像它们是命令一样。例如,如果站点里统一将网络端口7988用于SSH协议(“不公开,即安全”的一种形式),就可以在~/.bash_profile文件里定义

function ssh {

/usr/bin/ssh -p  7988  $*

}

以保证ssh总是带选项-p7988来运行。和许多shell一样,bash也有一种别名机制,能更加简洁地再现上面这个限制端口的例子,不过采用函数的方法更通用,功能也更强。忘掉别名,采用函数吧。

2.2.4 变量的作用域
在脚本里的变量是全局变量,但是函数可以用local声明语句,创建自己的局部变量。考虑下面的代码:

#!/bin/bash

function localizer {

echo "==> In  function localizer, a starts as ' $a' " local a

echo "==> After local declaration, a is  ' $a' " a="localizer version"

echo "==> Leaving localizer, a is  ' $a' "

}

a="test"

echo "Before  calling localizer, a is  ' $a' " localizer

echo "After  calling localizer, a is  ' $a' "

下面的日志显示在localizer函数内,局部变量$a屏蔽了全局变量$a。在localizer内,在碰到local声明了局部变量$a之前,全局变量$a都可见;local实际上是一条命令,它从执行的那个地方开始,创建局部变量。

$ sh  scopetest.sh

Before calling localizer, a is  'test'

==>  In  function localizer, a starts as 'test'

==>  After local declaration, a is  ' '

==>  Leaving localizer, a is  'localizer version' After calling localizer, a is  'test'

2.2.5 控制流程
我们在本章里已经见过几种if-then和if-then-else语句的形式;它们的功能在其名字中得以体现。一条if语句的结束标识是fi。要把几条if语句串起来,可以用elif这个关键字,它的意思是“else if”。例如:

if [ $base -eq  1 ] && [ $dm -eq  1 ]; then installDMBase

elif  [ $base -ne  1 ] && [ $dm -eq  1 ]; then installBase

elif  [ $base -eq  1 ] && [ $dm -ne  1 ]; then installDM

 else

fi

 echo '==>  Installing nothing'

用[]做比较的奇特语法,以及整数比较运算符的名字(例如,-eq),都看上去像是命令行选项,它们二者都是从原来Bourne shell的/bin/test命令延续下来的。方括号实际上是调用test的一种快捷方式,而不是if语句的语法要求4。

表2.2给出了bash的数值和字符串比较运算符。bash比较数值采用文字运算符,而比较字符串采用符号运算符,这正好和Perl相反。

screenshot

bash对文件属性取值的那些选项是其出彩之处(还是其/bin/test遗留下来的特性)。bash有大量的测试文件和比较文件的运算符,表2.3列出了其中几个。
screenshot

虽然elif的形式能用,但是为了清楚起见,用case语句做选择是更好的方法。case的语法如下面的这个函数例程所示,该函数集中给一个脚本写日志。特别值得注意的是,每一选择条件之后有个右括号,而在条件符合时每个要执行的语句块之后有两个分号。case语句以esac结尾。

The log  level is  set in  the global variable LOG_LEVEL. The  choices

 are, from most to  least severe, Error, Warning, Info,  and Debug.

function logMsg  { message_level=$1 message_itself=$2
if [ $message_level -le  $LOG_LEVEL ]; then case $message_level in

0) message_level_text="Error" ;;

1) message_level_text="Warning"  ;;

2) message_level_text="Info" ;;

3) message_level_text="Debug" ;;

*)  message_level_text="Other"

esac

echo "${message_level_text}:  $message_itself" fi

}

这个函数演示了许多系统管理应用经常采取的“日志级别”方案。脚本的代码产生详尽程度不同的日志消息,但是只有那些在全局设定的阈值$LOG_LEVEL之内的消息才被真正记录到日志里,或者采取相应的行动。为了阐明每则消息的重要性,在消息文字之前用一个标签说明其关联的日志级别。

2.2.6 循环
bash的for…in结构可以让它很容易对一组值或者文件执行若干操作,尤其是和文件名通配功能(对诸如和?这样的模式匹配字符进行扩展,形成文件名或者文件名的列表)联合起来使用的时候。在下面这个for循环里,其中的.sh模式会返回当前目录下能够匹配的文件名列表。for语句则逐一遍历这个列表,接着把每个文件名赋值给变量$script。

#!/bin/bash

suffix=BACKUP--`date +%Y%m%d-%H%M`

for  script in  *.sh; do newname=”$script.$suffix”

echo  "Copying $script  to  $newname..." cp  $script $newname

done

输出结果如下:

$ sh  forexample

Copying rhel.sh to  rhel.sh.BACKUP--20091210-1708... Copying sles.sh to  sles.sh.BACKUP--20091210-1708...

…

在这里的上下文关系中,对文件名做扩展并没有什么玄妙之处;它的做法就和在命令行上一模一样。也就是说,先扩展,然后再由解释器处理已经扩展过的这一行5。也可以静态地输入文件名,就像下面这行一样。

for  script in  rhel.sh sles.sh; do

实际上,任何以空白分隔的对象列表,包括一个变量的内容,都可以充当for …in语句的目标体。

bash也有从传统编程语言看来更为熟悉的for循环,在这种for循环里,可以指定起始、增量和终止子句。例如:

for  (( i=0  ; i < $CPU_COUNT  ; i++  )); do

CPU_LIST="$CPU_LIST  $i" done

接下来的例子演示了bash的while循环,这种循环也能用于处理命令行参数,以及读取一个文件里的各行。

#!/bin/bash exec 0<$1

counter=1

while read line;  do

echo "$counter: $line"

$((counter++))

done

下面是输出结果:

ubuntu$ sh  whileexample /etc/passwd

1: root:x:0:0:Superuser:/root:/bin/bash

2: bin:x:1:1:bin:/bin:/bin/bash

3: daemon:x:2:2:Daemon:/sbin:/bin/bash

…

这个脚本片段有两个有趣的功能。exec语句重新定义了该脚本的标准输入,变成由第一个命令行参数指定的任何文件6。这个文件必须要有,否则脚本就会出错。

在while子句里的read语句实际上是shell的内置命令,但它的作用就和一条外部命令一样。外部命令也可以放在while子句里;在这种形式下,当外部命令返回一个非零的退出状态时,就会结束while循环。

表达式$((counter++))实际上是个丑小鸭。$((…))这样的写法要求强制进行数值计算。它还可以利用$来标记变量名。++是人们在C和其他语言中熟悉的后置递增运算符。它返回它前面的那个变量的值,但返回之后还要把这个变量的值再加1。

$((…))的技巧在双引号里也起作用,所以可以把整个循环体紧凑地写到一行里。

while read line;  do

echo "$((counter++)):  $line" done

2.2.7 数组和算术运算
复杂的数据结构和计算不是bash的特长。但它的确至少提供了数组和算术运算。

所有bash变量的值都是字符串,所以bash在赋值的时候并不区分数字1和字符串“1”。不同之处在于如何使用变量。下面几行代码展示出了其中的差异:

#!/bin/bash

a=1 b=$((2))

c=$a+$b d=$(($a+$b))

echo "$a  + $b  = $c  \t(plus sign as string literal)"

echo "$a  + $b  = $d  \t(plus sign as arithmetic addition)"

This script produces the output

这个脚本产生的输出如下:

1 + 2 = 1+2   (plus  sign  as string literal)

1 + 2 = 3  (plus  sign  as arithmetic addition)

注意给$c赋值的语句,其中的加号(+)连字符串的连接运行符都不是。它仅仅就是一个字符而已。那行代码等价于

c="$a+$b"

为了强制进行数值计算,要把这个表达式放在$((…))里面,就像上面给$d赋值那样。但即便如此,也不会让$d获得一个数值;它的值仍然保存为字符串“3”。

bash通常能够混合使用算术、逻辑和关系运算符;参考手册页了解详情。

bash中的数组有点儿怪,所以不常用到它们。然而,如果需要它们也依然可以用。数组用括号括起来,数组元素之间用空白隔开。数组元素中的空白要用引号引起来。

ample=(aa 'bb  cc'  dd)

单个数组元素用${array name [subscript]}来访问。下标从0开始。下标和@指整个数组,${#array name[]}和${#array name[@]}这两种特殊形式表示数组里元素的个数。不要把它们和似乎更合乎逻辑的${#array name}搞混了;后者实际上是数组第一个元素的长度(等价于${#array name[0]})。

读者可能会以为$example[1]是指数组的第二个元素,这一点无可争议,但bash对这个字符串的分析结果却是:$example(即$example[0]的简洁引用形式)加上一个字符串[1]。在访问数组变量的时候,一定要带花括号——这一点无一例外。

下面是一个快速脚本,它演示了bash中数组管理的一些功能和缺陷:

#!/bin/bash

example=(aa 'bb  cc'  dd)

example[3]=ee

echo "example[@] = $\{example[@]}"

echo "example array contains $\{#example[@]} elements"

for  elt  in  "$\{example[@]}";  do echo "    Element  = $elt"

done

这个脚本的输出如下:

$ sh arrays

example[@] = aa bb  cc  dd  ee example array contains 4 elements

Element = aa Element = bb  cc Element = dd Element  = ee

这个例子似乎很直观易懂,但只是因为我们已经把这个脚本构造得循规蹈矩了。人们一不小心就会犯错误。例如,用下面这一句替换for语句那行代码:

for elt  in  $\{example[@]};  do

(在数组表达式外面没有用引号引起来)也能行,但它却不是输出4个数组元素,而是5个:aa、bb、cc、dd和ee。

这背后的问题是,因为所有bash变量实质上仍然是字符串,所以数组的表象充其量还是不确定的。字符串什么时候分割成数字元素,怎样分割成数组元素,都有很多细微变化。读者可以使用Perl或者Python,或是用谷歌搜索Mendel Cooper的Advanced Bash-Scripting Guide来研究这些细微差别。

版权声明:本文内容由阿里云实名注册用户自发贡献,版权归原作者所有,阿里云开发者社区不拥有其著作权,亦不承担相应法律责任。具体规则请查看《阿里云开发者社区用户服务协议》和《阿里云开发者社区知识产权保护指引》。如果您发现本社区中有涉嫌抄袭的内容,填写侵权投诉表单进行举报,一经查实,本社区将立刻删除涉嫌侵权内容。

相关文章
编程实战——电影管理器之界面UI及动画切换
在前文“编程实战——电影管理器之利用MediaInfo获取高清视频文件的相关信息”中提到电影管理器的目的是方便播放影片,在想看影片时不需要在茫茫的文件夹下找寻。   我对电影管理器的想法如下: 1、可以全键盘操作(不依赖鼠标),最好是利用键盘上的小数字键区就能完成全部操作。
887 0
软考新思维--2017年上半年信息系统项目管理师上午试题分析与答案(试题6-10题)
         2017年上半年信息系统项目管理师上午试题分析与答案(试题1-5题) 6.()不是获取需求的方法。A、问卷调查B、会议讨论C、获取原型D、决策分析【软考新思维】需求是获取的得来的,不是决策得来的。
829 0
软考新思维--2017年上半年信息系统项目管理师上午试题分析与答案(试题21-25题)
2017年上半年信息系统项目管理师上午试题分析与答案(试题21-25题) 21.以下关于综合布线的叙述中,正确的是:()A、综合布线系统只适用于企业、学校、团体,不适合家庭B、垂直干线子系统只能用光纤介质传输C、处于安全考虑,大型楼宇的设备间和管理间必须单独设置D、楼层配线架不一定在每一楼层都要设置考答案: 21. (D) 22.在进行网络规划时,应制定全网统一的网络架构,并遵循统一的通信协议标准,使符合标准的计算机系统很容易进行网络互联,这体现了网络规划的()原则。
1179 0
Linux内核分析(四)----进程管理|网络子系统|虚拟文件系统|驱动简介
原文:Linux内核分析(四)----进程管理|网络子系统|虚拟文件系统|驱动简介 Linux内核分析(四) 两天没有更新了,上次博文我们分析了linux的内存管理子系统,本来我不想对接下来的进程管理子系统、网络子系统、虚拟文件系统在这个阶段进行分析的,但是为了让大家对内核有个整体的把握,今天还是简单的介绍一下剩余的几个子系统,我们对这几个子系统的分析,只要了解其作用和部分内容即可,不必深究,等我们写上几个驱动,到时候按照驱动再来分析这几个子系统我们就清晰多了。
1058 0
SAS学习笔记之《SAS编程与数据挖掘商业案例》(3)变量操作、观测值操作、SAS数据集管理
SAS学习笔记之《SAS编程与数据挖掘商业案例》(3)变量操作、观测值操作、SAS数据集管理 1. SAS变量操作的常用语句 ASSIGNMENT 创建或修改变量 SUM 累加变量或表达式 KEEP 规定在数据集中保留的变量 DROP 规定在数据集中删除的变量 ARRAY 定义一个数组 RENAME
1507 0
软考新思维--2017年上半年信息系统项目管理师上午试题分析与答案(试题41-45题)
2017年上半年信息系统项目管理师上午试题分析与答案(试题41-45题) 41.以下关于项目沟通管理的叙述中,不正确的是:()A、对于大多数项目而言,沟通管理计划应在项目初期就完成B、基本的项目沟通内容信息可以从项目工作分解结构中获得C、制定合理的工作分解结构与项目沟通是否充分无关D、项目的组织结构在很大程度上影响项目的沟通需求参考答案: 41. (C) 42.沟通管理计划包括确定项目干系人的信息和沟通需求,在编制沟通计划时,()不是沟通计划编制的输入。
846 0
软考新思维--2017年上半年信息系统项目管理师上午试题分析与答案(试题46-50题)
软考新思维--2017年上半年信息系统项目管理师上午试题分析与答案(试题46-50题) 46.()不属于风险管理计划编制的成果A、风险类别B、风险概率C、风险影响力的定义D、风险记录参考答案: 46. (D) 47.赫兹伯格的双因素激励理论中的激励因素类似于马斯洛的需求层次理论中的“()”。
945 0
软考新思维--2017年上半年信息系统项目管理师上午试题分析与答案(试题11-15题)
2017年上半年信息系统项目管理师上午试题分析与答案(试题11-15题) 11.以下关于软件测试的描述,不正确的是:()A、为评价和改进产品质量进行的活动B、必须在编码阶段完成后才开始的活动C、是为识别产品的缺陷而进行的活动D、一般分为单元测试、集成测试、系统测试等阶段参考答案: 11. (B) 12.依据GB/T 11457-2006《信息技术 软件工程术语》,()是一种静态分析技术或评审过程,在此过程中,设计者或程序员引导开发组的成员通读已书写的设计或者代码,其他成员负责提出问题,并对有关技术风格、风格、可能的错误、是否违背开发标准等方面进行评论。
982 0
+关注
异步社区
异步社区(www.epubit.com)是人民邮电出版社旗下IT专业图书旗舰社区,也是国内领先的IT专业图书社区,致力于优质学习内容的出版和分享,实现了纸书电子书的同步上架,于2015年8月上线运营。公众号【异步图书】,每日赠送异步新书。
12049
文章
0
问答
文章排行榜
最热
最新
相关电子书
更多
JS零基础入门教程(上册)
立即下载
性能优化方法论
立即下载
手把手学习日志服务SLS,云启实验室实战指南
立即下载