在上一篇文章《不过时的技术-Bash脚本》中,我们简单介绍了Bash脚本,并且学会如何编写、运行一个Bash脚本。这篇文章我们将目光转向Bash脚本的一些基础知识:
- 如何定义、使用变量
- export和source
- 特殊字符及转义
- 脚本参数
- 命令替换
- 字符串验证
- Here文档
变量
有三种方式定义变量:
- 在命令行或脚本中直接显式定义
COLOR=red
千万不要像其他编程语言一样在变量和=
之间加空格!!! - 使用
read
命令从标准输入给变量赋值
read COLOR
- 第三种变量比较特殊,由参数直接传入:
$0
、$1
、$2
……其中$0
为脚本名,$1
是第一个参数,以此类推。
在变量名前加$
符引用变量,比如$COLOR
。下面这段脚本让用户输入进程名,保存到一个变量里,然后查找相关进程并杀掉。
#!/bin/sh
# Ask what to stop using the kill command and then kill it
echo which process do you want to kill?
read TOKILL
kill $(ps aux | grep $TOKILL | grep -v grep | awk '{print $2}')
这个脚本的最后一句看起来有点复杂,用到了命令替换、管道、awk等知识。如果你是那种不怕麻烦的人,可在命令行中一点一点去执行,看看发生了什么:比如ps aux
返回当前进程,ps aux | grep http
在当前进程中找出http
相关进程,以此类推……事实上这是编写脚本时常用的方法,先在命令行中一部分一部分实验,成功后再组织成一段脚本。
如果执行脚本时出错怎么办?比如上面这段脚本运行第二次时,如果输入的值一样,就会报错。此时运行bash -x tokill.sh
可以打印出详细的诊断信息:
foo:bin muxi$ bash -x tokill.sh
+ echo which process do you want to 'kill?'
which process do you want to kill?
+ read TOKILL
http
++ ps aux
++ grep -v grep
++ grep http
++ awk '{print $2}'
+ kill
kill: usage: kill [-s sigspec | -n signum | -sigspec] pid | jobspec ... or kill -l [sigspec]
相信你已经看出了问题所在,最后一步kill进程时传入的参数不对(为空)。这点不难想象,因为我们已经在上一次运行时杀掉所有相关进程了。
需要注意的是变量只在定义它的脚本中起效,如果在当前脚本中进入子脚本,那么该变量在子脚本中就不存在了。这点可以在命令行中得到验证:
foo:bin muxi$ COLOR=red
foo:bin muxi$ echo $COLOR
red
foo:bin muxi$ bash
bash-3.2$ echo $COLOR
bash-3.2$ COLOR=green
bash-3.2$ echo $COLOR
green
bash-3.2$ exit
exit
foo:bin muxi$ echo $COLOR
red
细心的读者可能会注意到,定义变量时我一直用的大写,这是一个好的编程习惯,人家一看就知道这是个变量。
export 和 source
如果想使用父脚本中定义的变量该怎么办?答案是使用export
将变量导入子脚本:
foo:bin muxi$ export COLOR=red
foo:bin muxi$ echo $COLOR
red
foo:bin muxi$ bash
bash-3.2$ echo $COLOR
red
source
更厉害,它能将一个脚本中的所有内容导入到另外一个脚本中。
#!/bin/sh
. ./slave.sh
echo the value of variable '$COLOR' is $COLOR
exit 0
#!/bin/sh
COLOR=yellow
source
的厉害之处在于它能将变的东西和不变的东西分离开,听着是不是很熟悉?这是计算机世界里一个基本的原则之一。以后大家会经常在脚本中看到export
和source
。
特殊字符和转义
在Shell脚本中,很多字符有特殊的含义:~
代表当前用户主目录, #
注释等,更多内容可参考特殊字符列表。比如下面这段脚本,你觉得输出会是什么?
echo 2 * 3 > 5
上面的脚本什么也没输出,它将一段文本重定向到文件5
中。为了输出2 * 3 > 5
,需要这样:echo '2 * 3 > 5'
脚本参数
执行脚本时可传入参数,在脚本中使用$0, $1, $2……${10}, ${11}……引用参数,其中$0代表脚本名。还有三个特殊变量:$#
表示传入参数个数,$@
返回所有参数列表,$*
将所有参数以一个字符串的形式返回。下面这段程序用来遍历参数列表,你能猜出为什么"$@"
要加双引号吗?
for i in "$@"
do
echo $i
done
命令替换
编写脚本时,经常一个命令需要使用另一个命令执行完成后的结果,此时就用到了命令替换,比如ls -l $(which passwd)
列出了passwd
命令的文件属性。
字符串验证
test -z $1 && exit 1 #如果第一个参数为空,返回状态1退出
[[ $1=='[a-z]*' ]] || echo $1 does not start with a letter
恐怖的空格!!!$1之前必须有空格,之后必须没空格!!!
Here文档
wall <<End-of-message
----------------------------
This is line 1 of the message.
This is line 2 of the message.
This is the last line of the message.
------------------------------
End-of-message
#!/bin/bash
# example of a scripted FTP session
lftp localhost <<EndOfSession
ls
get hosts
bye
EndOfSession
echo the file is now downloaded