随着功能丰富,脚本变得越来越庞大和复杂,如何把复杂的任务分解成小的、具体的任务?
先拆分主要步骤,再逐个步骤进行细化实现,自顶向下设计。shell编程尤其适用自顶向下设计。
shell函数
继续之前的系统信息报告生成器脚本,新增需求:需要展示系统的运行时间、磁盘空间、用户空间。
假设每个需求对应一条命令,我们可以用命令替换的方式应用实现。
#!/bin/bash
# a program to output system information report
TITLE="System Information Report For $HOSTNAME"
CURRENT_TIME=$(date +"%x %r %Z")
TIME_STAMP="Generated $CURRENT_TIME ,by $USER"
echo << _EOF_
<html>
<head>
<title>$TITLE</title>
</head>
<body>
<h1>$TITLE</h1>
<p>$TIME_STAMP</p>
$(report_uptime)
$(report_disk_space)
$(report_home_space)
</body>
</html>
_EOF_
对于以上$(report_uptime) $(report_disk_space) $(report_home_space)三个命令,如何组织实现?主要有两种方式
编写三个独立的脚本,把脚本文件放到环境变量PATH所列的目录中。或者,可以将脚本作为shell函数嵌入到程序中。
shell函数有两种语法形式,第一种,
function name{
commands
return
}
其中,name是指这个函数的名称,commands是指这个函数中的一系列命令。
第二种形式,
name(){
commands
return
}
shell函数使用演示脚本
#!/bin/bash
# shell function demo
function funct {
echo "Step 2"
return
}
# Main program starts here
echo "Step 1"
funct
echo "Step 3"
shell脚本中,shell函数的定义在脚本中的位置必须在它被调用的前面。
函数的调用直接使用函数名称即可,不需要加括号
#!/bin/bash
# Program to output a system information page
TITLE="Syatem Information Report For $HOSTNAME"
CURRENT_TIME=$(date +"%x %r %Z")
TIME_STAMPT="Generated $CURRENT_TIME,by $USER"
report_uptime () {
return
}
report_disk_space () {
return
}
report_home_space () {
return
}
cat <<- _EOF_
<html>
<head>
<title>$TITLE</title>
</head>
<body>
<h1>$TITLE</h1>
<p>$TIME_STAMPT</p>
$(report_uptime)
$(report_disk_space)
$(report_home_space)
</body>
</html>
_EOF_
shell函数命名规则和变量相同。
一个函数中必须至少包含有一条命令。return命令(可选的)可以满足该要求
局部变量
在shell函数中,经常需要使用局部变量。局部变量仅仅在定义它们的shell函数中有效,一旦shell函数终止,它们就不再存在。
为什么引入局部变量的概念呢?我总结了一下,主要包含且不限以下两点:
1、局部变量可以让程序员使用已存在的变量名称,无论是脚本中的全局变量还是其他shell函数中的变量,不用担心变量命名冲突的问题;
2、shell函数中尽量使用局部变量,可以保证shell函数相互独立,也独立于它们所在的脚本,增加shell函数的可移植性。因为减少了外部变量的使用,移植到新的脚本上下文中,不用担心shell函数的能否运行起来。(保持shell函数的独立性非常有用 - 能够直接从一个脚本中复制剪切shell函数到另一个脚本中使用运行)
#!/bin/bash
# local-variable:script to demostrate local variables
foo=0 # global variable foo
funct_1 () {
local foo # variable foo local to funct_1
foo=1
echo "funct_1: foo=$foo"
}
funct_2 () {
local foo # variable foo local to funct_2
foo=2
echo "funct_2: foo=$foo"
}
echo "global: foo=$foo"
funct_1
echo "global: foo=$foo"
funct_2
echo "global: foo=$foo"
局部变量是通过在变量名前面添加 local 来定义的。
程序编写中的一些小技巧
在程序设计中,我们需要保持编写的程序能够持续有效运行。在之前的系统信息报告生成器脚本中,定义的report_uptime、report_disk_space、report_home_space方法中没有具体的业务逻辑流程,相当于一个空函数。一般将这种类型的代码称为stub代码,即起到一个占位的作用。
在编写stub时,最好能够包含一些能够为程序员提供反馈信息的东西,以便程序员进行调试。
#!/bin/bash
# Program to output a system information page
TITLE="Syatem Information Report For $HOSTNAME"
CURRENT_TIME=$(date +"%x %r %Z")
TIME_STAMPT="Generated $CURRENT_TIME,by $USER"
report_uptime () {
echo "Function report_uptime executed." # 一些反馈的信息
return
}
report_disk_space () {
echo "Function report_disk_space executed." # 一些反馈的信息
return
}
report_home_space () {
echo "Function report_home_space executed." # 一些反馈的信息
return
}
cat <<- _EOF_
<html>
<head>
<title>$TITLE</title>
</head>
<body>
<h1>$TITLE</h1>
<p>$TIME_STAMPT</p>
$(report_uptime)
$(report_disk_space)
$(report_home_space)
</body>
</html>
_EOF_
如果成功输出以上函数中设置的反馈信息,我们可以确定,函数的编写定义和使用没有问题。可以对各个函数进行业务逻辑流程的编写了。
#!/bin/bash
# Program to output a system information page
TITLE="Syatem Information Report For $HOSTNAME"
CURRENT_TIME=$(date +"%x %r %Z")
TIME_STAMPT="Generated $CURRENT_TIME,by $USER"
report_uptime () {
cat <<- _EOF_
<h2>System Uptime</h2>
<pre>$(uptime)</pre>
_EOF_
return
}
report_disk_space () {
cat <<- _EOF_
<h2>Disk Space Utilization</h2>
<pre>$(df -h)</pre>
_EOF_
return
}
report_home_space () {
cat <<- _EOF_
<h2>Home Space Utilization</h2>
<pre>$(du -sh /home/*)</pre>
_EOF_
return
}
cat <<- _EOF_
<html>
<head>
<title>$TITLE</title>
</head>
<body>
<h1>$TITLE</h1>
<p>$TIME_STAMPT</p>
$(report_uptime)
$(report_disk_space)
$(report_home_space)
</body>
</html>
_EOF_
以上内容都属于自顶向下设计的思想过程。
编写的report_home_space函数在有些系统只有超级用户才能运行。一个更好的解决方案是根据执行脚本的用户权限来调整脚本的行为。在下一节中讨论该问题。
!!! 在.bashrc文件中通过自定义shell函数可以定义个人的专属命令
可以关注作者微信公众号,追踪更多有价值的内容!