四.shell 的语法
接下来我们讲学习一下内容:
& 变量:字符串、数字、环境和参数
&条件:shell 中的布尔值
&程序控制:if 、elif 、for、while、until、case
&命令列表
&函数
&shell 内置命令
&获取命令执行结果
&here文档
注释:
在进行shell编程时,以#开头的句子表示注释,直到这一行的结束。如果使用了注释,即使相当长的时间内没有使用该脚本,也能在很短的时间内明白该脚本的作用及工作原理。
(一)变量
1.字符串
在shell编程中,所有的变量都由字符串组成,并且不需要预先对变量进行声明,例s1:
#!/bin/sh
#set variable a
a =”hello world”
#print a
echo “A is:”
echo $a
注意:有时候变量名很容易与其他文字混淆,比如:
S13:
Num=2
Echo “this is the $numnd”
思考:输出?why?
答案:num=2
Echo “this is the $numnd”
这并不会打印出““this is the2nd”,而仅仅打印“this is the”,因为shell会去搜索变量numnd的值,但是这个变量是没有的。可以使用花括号来告诉shell我们要打印的是num变量:
Num=2
Echo “this is the ${num}nd”
这将打印:this is the 2nd
2.参数变量
默认变量
$#:传入脚本的命令行参数个数(不算命令名本身)
$*:所有命令行参数值,(在各个参数值之间留有空格)(不算命令名本身)
$0:命令行本身
$1:第一个命令行参数 (不包括命令本身)
$2:第二个命令行参数
3.局部变量
在变量首次赋值加上local关键字可以声明一个局部变量,例s3:
#!/bin/sh
Hello=”var1”
echo $hello
function fun1
{
Local hello=”var2”
echo $hello
}
Func1
Echo $hello
输出:?
答案:var1
Var2
Var1
注意:
1. 变量赋值时,“=”左右两边都不能有空格(在脚本编程里面不能这样)
2. BASH 中的语句结尾不需要分号
***使用引号***
在继续学习之前,我们要弄清楚shell的一个特点:引号的使用。
一般情况下,脚本文件中的参数一空白字符分隔,如果想在一个参数里包含一个或者多个空白字符,就必须给参数加上引号。
变量在引号中的行为取决于你所使用的引号类型, 如果你把一个带有$字符的变量放在
双引号中,程序执行到这一行时就会把变量替换为它的值;
单引号中,就不会发生替换现象;
在$字符前面加上一个\字符取消它的特殊含义。
看下面的例子:---显示了引号在变量中的输出的作用:
#! /bin/sh
myvar=“Hi there”
echo $myvar
echo "$myvar"
echo '$myvar'
echo \$myvar
echo Enter some text
read myvar
echo '$myvar' now equals $myvar
exit 0
输出结果:
Hi there
Hi there
$myvar
$myvar
Enter some text
hello world
$myvar now equals $myvar
实验解析: 变量myvar在创建时被赋值为字符串Hi there .我们在用echo命令显示该变量的内容,同时显示了变量名前面加一个$符号就能得到变量的内容。我们看到使用双引号并不影响变量的替换,但是用单引号和反斜杠就不进行变量的替换,我们还是用read命令从用户那里读入一个字符串。 |
(二)条件
test 或 [ ]
注意: 因为test命令在shell脚本程序以外用得很少,所以那些很少编写shell脚本的Linux用户往往会自己编写一个简单的程序并将这个文件命名为test.如果这个程序不能正常工作,很可能是因为它与shell中的test命令发生了冲突。(想要查看命令来检查执行的是哪一个test命令,或者可以使用./test这种执行方式,以确保你执行的是当前目录下的脚本程序) |
我们以一个简单的条件为例来介绍test命令的用法:检查一个文件是否存在。
用于实现这一作用的命令是test -f <filename>,所以在脚本程序里我们可以写出如下所示的代码:
if test -f fred.c
then
...
fi
我们还可以写出:
if [ -f fred.c ]
then
...
fi
注意:你必须在[ 符号和被检查的条件之间留出空格。要记住这一点,你可以把[符号看成和test命令一样,而test命令之后总是应该有一个空格。 |
如果你喜欢把then和if放在同一行上,就必须要用一个分号把test语句和then分隔开。如下所示:
if [ -f fred.c ] ; then
...
fi
test命令可以使用的条件类型可以归为三类:1.字符串比较;2.算术比较;3.文件有关的条件测试
比较操作 整数操作 字符串操作
相同 -eq =
不同 -ne !=
大于 -gt >
小于 -lt <
大于或等于 –ge
小于或等于 -le
为空 -z
不为空 -n
例:
比较整数a和b是否相等:if[ $a = $b ](也可以eq)(注意:赋值“=“号两边是没有空格的,比较”=“号两边是有空格的)
判断整数a是否大于整数b: if[ $a > $b ]
比较字符串a和b是否相等:if[ $a = $b]
判断字符串a是否为空:if[ -z $a ]
判断整数变量a是否大于b: if[ $a > $b ]
注意:
1. 在”[” 和 ”]”符号的左右都留有空格
2. “=”左右都有空格
判断:
-e : 文件已经存在
-f :文件时普通文件
-s : 文件大小不为0
-d: 文件是一个目录
-r : 文件对当前用户可以读取
-w :文件对当前用户可以写入
-x : 文件对当前用户可以执行
例s5:
#!/bin/sh
Folder=/home
[-r “$folder”]&& echo “can read $folder” (&&前的条件为真时才执行&&后的语句)
[-f “$folder”] || echo “this is not file”(||前的条件为假时才执行||后的语句)
(三)控制结构
1.if 语句
(1) if [expression]
then
#code block
fi
(2) if [expression]
then
#code block
else
#code block
fi
(3) if语句(fi表示if的结束)
if [expression]
then
#code block
else if[expression]
then
#code block
else
#code block
fi(第一个fi对应第二个if)
fi(对应第一个if)
看一下下面例子:
#!/bin/sh
echo "Is it morning? Please answer yes or no"
read timeofday
if [ $timeofday = "yes" ]; then
echo "Good morning"
else
echo "Good afternoon"
fi
exit 0
这将给出如下的输出:
Is it morning? Please answer yes or no
yes
Good morning
这个脚本程序用[命令对变量timeofday的内容进行测试,测试结果由if命令判断,由它来决定执行哪部分代码。
两个小知识点总结: read /*读输入的值并显示*/ read timeof /*读输入的值并赋给timeof并显示*/ |
2.elif语句
上面脚本程序存在几个问题,把不是yes的回答都看做是no.我们可以通过elif结构来避免这种情况。
实验:用elif 结构做进一步的测试
在刚才的脚本进行修改,让它在用户输入yes或no以外的其他任何东西时报告一条出错信息。我们通过将else替换为elif并且增加其他测试条件的方法来实现它。
#!/bin/sh
echo "Is it morning? Please answer yes or no"
read timeofday
if [ $timeofday = "yes" ]
then
echo "Good morning"
elif [ $timeofday = "no" ]; then
echo "Good afternoon"
else
echo "Sorry, $timeofday not recognized. Enter yes or no"
exit 1
fi
exit 0
3.一个与变量有关的问题
刚才修改弥补了一个非常明显的缺陷,但这个脚本程序还存在一些更隐蔽的问题。运行这个上面新的脚本我们不回答问题之间按回车键(或是某些return键)
我们将看到如下所示的出错信息:
[: =: unary operator expected ]
哪里出了问题?问题就在第一个if语句。在对变量timeofday进行测试的时候,它包含一个字符串,这使得if语句成为下面这个样子:
if [ = "yes" ]
而这不是一个合法的条件,为了避免出现这种情况,我们必须给变量加上引号,如下:
if [ “$timeofday” = "yes" ]
这样,一个空变量提供给我们一个合法的测试:
if [ “ “ = "yes" ]
我们的新脚本程序如下所示:
#!/bin/sh
echo "Is it morning? Please answer yes or no"
read timeofday
if [ "$timeofday" = "yes" ]
then
echo "Good morning"
elif [ "$timeofday" = "no" ]; then
echo "Good afternoon"
else
echo "Sorry, $timeofday not recognized. Enter yes or no"
exit 1
fi
exit 0
它对用户直接按下回车键来回答问题的情况能应付自如
如果你想让echo命令去掉每一行后面的换行符,最好的可移植办法是使用printf命令,而不是echo命令。 1.换行:echo 2.不换行:printf 不换行:echo -e |
4.for语句
for 循环结构与C语言有所不同,在bash中for 循环的基本结构是:
for var in [list]
do
#code block
Done
其中$var 是循环控制变量, [list ] 是var 需要遍历的一个集合,do /done 对包含了循环体,相当于C语言中的一对大括号。另外如果do和 for 被写在同一行,必须在do前面加上”;” 。如:for $ var in [list] ; do
例:
#!/bin/bash
for day in Sun Mon Tue Wed Thu Sat
do
Echo $day
done
打印结果:
Sun
Mon
Tue
Wed
Thu
Sat
如果列表被包含在一对双引号中,则被认为是一个元素,如:s8 :
#!/bin/bash
For day in “Sun Mon Tue Wed Thu Fri Sat”
do
echo $day
done
在for所在的那一行,变量day是没有加“$“符号的,而循环体内,echo所在行变量 $day是必须加上“$”符号的
打印结果:Sun Mon Tue Wed Thu Fri Sat
***使用通配符扩展for循环****
实例--打印当前目录中所有以字母f开头的脚本文件,并且这些脚本都以.sh结
!/bin/sh
for file in $(ls f*); do
echo $file
done
exit 0
5.while语句
for特别适合于对一系列字符进行循环处理,但在需要执行特定次数命令的场合就显得有些笨拙了。
如果我们想要执行20次,用for循环相当冗长:
#!/bin/sh
for foo in 1 2 3..... 19 20
do
echo "here we go again"
done
exit 0
这种情况下,我们可以使用一个while循环,它的语法如下:
while condition do
statements
done
看下面例子--一个比较简陋的密码检查程序:
#!/bin/sh
echo "Enter password"
read trythis
while [ "$trythis" != "secret" ]; do
echo "Sorry, try again"
read trythis
done
exit 0
这个脚本程序的一个输出示例如下:
Enter password
123456(输入的密码)
Sorry, try again
secret(再次输入的密码)
实验:循环、循环、再循环
通过将while结构和数值替换结合在一起,我们就可以让某个命令执行特定的次数,这比我们前面见过的for循环要简化多了
#!/bin/sh
foo=1
while [ "$foo" -le 20 ]
do
echo "Here we go again"
foo=$(($foo+1))
done
exit 0
实验解析:
这个脚本程序用[命令来测试foo的值,如果它小于等于20,就执行循环体。在while循环的内部,语法(($(foo+1)))用来对括号内的表达式进行算术赋值,所以foo的值会在每次循环中递增。
因为foo不可能变成空字符串,所以我们在对它进行测试时不需要把它放在双引号内加以保护。我们这样做只是因为这是一种良好的变成习惯。
6.until 语句
Until 循环的基本结构是:
Until [condition]
do
#code block
done
while 和until的区别是: while是为真时执行,until是为假时执行
7.case 语句
Case 语句
case “var” in
condition1)
;;
condition2)
;;
*)
default statements;;
esac
请注意,每个模式行都以双分号(;;)结尾。因为你可以在前后模式之间放置多条语句,所以需要用一个双分号来标记前一个语句的结束和后一个语句的开始。
我们将通过3个例子逐步深入地对它进行介绍,每次都对模式匹配进行改进。
你在case结构中使用如*这样的通配符要小心,因为case将使用第一个匹配的模式,即使后面的模式更加精确的匹配也是如此。 |
实验:case示例一:用户输入
我们可以用case结构编写一个新版的输入测试脚本程序,让它更具选择性并且对非预期输入也更宽容。
#!/bin/sh
echo "Is it morning? Please answer yes or no"
read timeofday
case "$timeofday" in
"yes") echo "Good Morning";;
"no" ) echo "Good Afternoon";;
"y" ) echo "Good Morning";;
"n" ) echo "Good Afternoon";;
* ) echo "Sorry, answer not recognised";;
esac
exit 0
实验解析:
当case语句被执行时,它会把变量timeofday的内容与各字符串依次进行比较。一旦某个字符串与输入匹配成功,case命令就会执行紧随右括号)后面的代码,然后结束。
实验:case示例二:合并匹配模式
我们通过合并匹配模式,编写一个更清晰的版本。如下:
#!/bin/sh
echo "Is it morning? Please answer yes or no"
read timeofday
case "$timeofday" in
yes | y | Yes | YES ) echo "Good Morning";;
n* | N* ) echo "Good Afternoon";;
* ) echo "Sorry, answer not recognised";;
esac
exit 0
实验解析:
我们还显示了*通配符的用法,但这样做有可能匹配我们意料之外的模式,例如,如果用户输入never,它就会匹配n* 并显示Good Afternoon 而不是我们希望的行为,另一需要注意的地方*通配符在扩展单引号中不起作用。
实验:case示例三:执行多条语句
为了让这个脚本具备重用性,我们需要再使用默认的模式时给出另外一个退出码。如:
#!/bin/sh
echo "Is it morning? Please answer yes or no"
read timeofday
case "$timeofday" in
yes | y | Yes | YES )
echo "Good Morning"
echo "Up bright and early this morning?"
;;
[nN]* )
echo "Good Afternoon"
;;
* )
echo "Sorry, answer not recognised"
echo "Please answer yes or no"
exit 1
;;
esac
exit 0
注意:我们把最精确的匹配放在最开始,把最含糊的匹配放在最后。
为了让case功能更强大,我们使用如下模式:
[yY] | [ Yy ] [ Ee ] [Ss] )