shell基础语法
指定脚本要使用的 Shell
#!/bin/bash
上面这句代码中, /bin/bash 是 Bash 程序在大多数 Linux 系统中的存放路径,而最前面的 #! 被称作 Sha-bang,或者 Shebang。
在计算机科学中,Shebang(也称为 Hashbang )是一个由井号和叹号构成的字符串
#!
,其出现在文本文档的第一行的前两个字符。
在文档中存在 Shebang 的情况下,类 Unix 操作系统的进程载入器会分析 Shebang 后的内容,将这些内容作为解释器指令,并调用该指令,并将载有 Shebang 的文档路径作为该解释器的参数。
给脚本文件添加可执行的权限
chmod +x test.sh
以调试模式运行
随着我们渐渐深入 Shell 编程,你也许会写出很长的 Shell 脚本,代码一多很可能就会有 Bug。
因此,我们需要学习如何调试一个脚本程序。用法如下:
bash -x test.sh
我们直接调用 Bash 这个 Shell 程序,并且给它一个参数 -x (表示以调试模式运行),后面再跟上要调试运行的脚本文件。
shell变量
定义变量的时候=左右不要加空格!
引号
我们可以用引号来界定包含空格的字符串。
引号一共有三种:
类型 | 表示 |
---|---|
单引号 | ’ |
双引号 | " |
反引号 | ` |
根据引号类型不同,Bash 的处理方式也会不同。
单引号
我们从单引号开始学习吧。美式键盘中,单引号( ’ ) 位于 Enter 键(回车键)的左方。
之前的例子里,我们也已经使用过单引号了。
#!/bin/bash
message='Hello World'
echo 'The message is $message'
正如之前我们测试的一样,如果变量被包含在单引号里面,那么变量不会被解析,美元符号( $ )保持原样输出。
显示:
The message is $message
因为:单引号忽略被它括起来的所有特殊字符。
双引号
一般来说,要输入双引号,需要用 “Shift键 + 单引号的按键”。
不同于单引号忽略所有特殊字符,双引号忽略大多数特殊字符,但不包括:美元符号( $ )、反引号( ` )、反斜杠( \ ),这 3 种特殊字符将不被忽略。 不忽略美元符号意味着 Shell 在双引号内部可进行变量名替换。
例如:
#!/bin/bash
message='Hello World'
echo "The message is $message"
执行以上脚本,显示:
The message is Hello World
反引号
反引号 ( ` ) 不太常用,位于键盘的 Tab 键的上方、数字键 1 的左方。
反引号要求 Shell 执行被它括起来的内容。 什么意思呢?我们来看一个例子你就懂了:
#!/bin/bash
message=`pwd`
echo "You are in the directory $message"
运行这个脚本,显示:
You are in the directory /home/pearfl
read : 请求输入
-p :显示提示信息
目前来说,read 命令提供了 -p 参数,p 是 prompt 的首字母,表示“提示”。
-n :限制字符数目
用 -n 参数,我们可以限制用户输入的字符串的最大长度(字符数)。n 是 number 的首字母,是英语“数目”的意思。
-t :限制输入时间
用 -t 参数,我们可以限定用户的输入时间(以秒为单位),也就是说超过这个时间,就不读取输入了。t 是 time 的首字母,是英语“时间”的意思。
-s :隐藏输入内容
用 -s 参数,我们可以隐藏输入内容。一般用不到,但是如果你想要用户输入的是一个密码,那 -s 参数还是有用的。
数学运算
在 Bash 中,所有的变量都是字符串
let 命令可以用于赋值
可用的运算符是以下几种:
运算 | 符号 |
---|---|
加法 | + |
减法 | - |
乘法 | * |
除法 | / |
幂(乘方) | ** |
余(整数除法的余数) | % |
如果你要做带小数的运算,那么需要用到 bc 命令
参数变量
假设,我们可以这样调用我们的脚本文件:
./variable.sh 参数1 参数2 参数3 ...
这些个 参数1,参数2,参数3 … 被称为“参数变量”。
但问题是我们还不知道如何接收这些参数到我们的脚本中。
其实不难,因为这些变量是被自动创建的。
$#
:包含参数的数目。$0
:包含被运行的脚本的名称 (我们的示例中就是 variable.sh )。$1
:包含第一个参数。$2
:包含第二个参数。
…$8
:包含第八个参数。
…
以此类推。
数组
例子:
array=('value0' 'value1' 'value2')
上面的语句定义了一个数组变量,名叫 array(array 是英语“数组”的意思),其中包含三个值:value0,value1,value2。
如果要访问其中一个格子的内容,要用到这样的语法:
${array[2]}
以上语句表示数组中编号为 2 的元素(在我们的情况就是 value2 )。
注意:和大多数编程语言一样,Shell 中的数组的下标(index)也基本是从 0 开始的,而不是从 1 开始。因此,第一个元素的编号(下标)就是 0,第二个元素的下标就是 1,以此类推。
不过,也不是所有 Shell 语言的数组下标都是从 0 开始,不少 Shell 语言(例如 Csh,Tcsh,Zsh,等等)的数组下标是从 1 开始的。
判断条件
if : 最简单的条件
if 条件语句的基本格式是这样的:
if [ 条件测试 ]
then
做这个
fi
fi 是 if 的反转写法,表示“if 语句结束”。then 是英语“那么”的意思。
“做这个”只有在“条件测试”为真时,才会被执行。
注意:方括号
[]
中的条件测试
两边必须要空一格。不能写成[test]
,而要写成[ test ]
。
当然了,if 语句的基本写法还有一种,那就是把 then 写在 if [ 条件测试 ]
后面,如下:
if [ 条件测试 ]; then
做这个
fi
用这种写法时,在 if 条件判断和 then 之间要加一个分号。
需要注意在 Shell 语言中,“等于”是用一个等号( = )来表示的,这和大多数主流编程语言不同。
else : 否则
既然有“如果”的条件判断,那么也会存在条件不成立的时候
if 和 else 两者配合的逻辑是这样的:
if [ 条件测试 ]
then
做这个
else
做那个
fi
也就是:如果“条件测试”为真,那么“做这个”被执行;否则,“做那个”被执行。
elif : 否则,如果
一般来说 if 和 else 已经能满足我们的大部分条件判断需要了,但有些时候,存在好几种情况。
光是 if 和 else 表示的两种对立的情况已经不足以满意要求了,因此我们再来一个关键字:elif 。
elif 是 else if 的缩写,表示“否则 - 如果”。
if, elif 和 else 三者配合的逻辑是这样的:
if [ 条件测试 1 ]
then
做事情 1
elif [ 条件测试 2 ]
then
做事情 2
elif [ 条件测试 3 ]
then
做事情 3
else
做其他事情
fi
不同的测试类型
在 Bash 中我们可以做三种测试:
- 测试字符串
- 测试数字
- 测试文件
测试字符串
我们之前的课程已经说过:在 Shell 中,所有的变量都是字符串。
因此,要做字符串的测试非常简单。记住以下表格:
条件 | 意义 |
---|---|
$string1 = $string2 | 两个字符串是否相等。Shell 大小写敏感,因此 A 和 a 是不一样的。 |
$string1 != $string2 | 两个字符串是否不同。 |
-z $string | 字符串 string 是否为空。z 是 zero 的首字母,是英语“零”的意思。 |
-n $string | 字符串 string 是否不为空。n 是英语 not 的首字母,是英语“不”的意思。 |
测试数字
尽管 Shell 把所有变量都看成字符串,但是我们还是可以做数字的条件测试。记住以下表格:
条件 | 意义 |
---|---|
$num1 -eq $num2 | 两个数字是否相等。和判断字符串所用的符号( = )不一样。eq 是 equal 的缩写,是英语“等于”的意思。 |
$num1 -ne $num2 | 两个数字是否不同。ne 是 not equal 的缩写,是英语“不等于”的意思。 |
$num1 -lt $num2 | 数字 num1 是否小于 num2。lt 是 lower than 的缩写,是英语“小于”的意思。 |
$num1 -le $num2 | 数字 num1 是否小于或等于 num2。le 是 lower or equal 的缩写,是英语“小于或等于”的意思。 |
$num1 -gt $num2 | 数字 num1 是否大于 num2。gt 是 greater than 的缩写,是英语“大于”的意思。 |
$num1 -ge $num2 | 数字 num1 是否大于或等于 num2。ge 是 greater or equal 的缩写,是英语“大于或等于”的意思。 |
测试文件
相比于主流编程语言,Shell 的一大优势就是可以非常方便地测试文件:文件存在吗?我们可以写入文件吗?这个文件比那个文件修改时间更早还是更晚?等等。
下表非常丰富:
条件 | 意义 |
---|---|
-e $file | 文件是否存在。e 是 exist 的首字母,表示“存在”。 |
-d $file | 文件是否是一个目录。因为 Linux 中一切都是文件,目录也是文件的一种。d 是 directory 的首字母,表示“目录”。 |
-f $file | 文件是否是一个文件。f 是 file 的首字母,表示“文件”。 |
-L $file | 文件是否是一个符号链接文件。L 是 link 的首字母,表示“链接”。 |
-r $file | 文件是否可读。r 是 readable 的首字母,表示“可读的”。 |
-w $file | 文件是否可写。w 是 writable 的首字母,表示“可写的”。 |
-x $file | 文件是否可执行。x 是 executable 的首字母,表示“可执行的”。 |
$file1 -nt $file2 | 文件 file1 是否比 file2 更新。nt 是 newer than 的缩写,表示“更新的”。 |
$file1 -ot $file2 | 文件 file1 是否比 file2 更旧。ot 是 older than 的缩写,表示“更旧的”。 |
一次测试多个条件
在一个条件测试中,我们可以同时测试多个条件。需要用到两种符号:
符号 | 意义 |
---|---|
&& | 两个 &。表示“逻辑与”。此符号两端的条件必须全为真,整个条件测试才为真;只要有一个不为真,整个条件测试为假。 |
II | 两个竖线。表示“逻辑或”。此符号两端的条件只要有一个为真,整个条件测试就为真;只有两个都为假,整个条件测试才为假。 |
反转测试
我们可以用“否定”来反转测试条件,要用到感叹号( !
)。
case : 测试多个条件
#!/bin/bash
case $1 in
"Matthew")
echo "Hello Matthew !"
;;
"Mark")
echo "Hello Mark !"
;;
"Luke")
echo "Hello Luke !"
;;
"John")
echo "Hello John !"
;;
*)
echo "Sorry, I do not know you."
;;
esac
来分析一下上面的程序,因为有很多新的内容:
case $1 in
:$1
表示我们要测试的变量是输入的第一个参数。in 是英语“在…之中”的意思。"Matthew")
:测试其中一个 case,也就是$1
是否等于"Matthew"
。当然,我们也可以用星号来做通配符来匹配多个字符,例如"M*")
可以匹配所有以 M 开头的字符串。;;
:类似于主流编程语言中的break;
,表示结束 case 的读取,程序跳转到 esac 后面执行。*)
:相当于 if 条件语句的 else,表示“否则”,就是“假如不等于上面任何一种情况”。esac
:是 case 的反写,表示 case 语句的结束。
循环
Shell 中,主要的循环语句有三种:
- while 循环
- until 循环
- for 循环
while 循环
在 Shell 中,我们最常用的循环是 while 循环。
while 循环的逻辑是这样的:
while [ 条件测试 ]
do
做某些事
done
当然了,我们也可以像在 if 语句中那样,把关键字 do 放到与条件测试同一行上,但是之间要加分号,如下:
while [ 条件测试 ]; do
做某些事
done
until 循环
与 while 这个关键字相反的有一个 until 关键字,until 在英语中是“到…为止,直到…时”的意思。
把while改成until就一样了
for 循环
遍历列表
for 循环可以遍历一个“取值列表”,基本的逻辑如下:
for 变量 in '值1' '值2' '值3' ... '值n'
do
做某些事
done
更常规的 for 循环
刚才我们看到的 for 循环,和主流编程语言中的语法略有不同,不过我们可以借助 seq 命令,来实现类似主流编程语言中的 for 循环的语法。
seq 是 sequence 的缩写,是英语“序列”的意思。
来写一个例子:
#!/bin/bash
for i in `seq 1 10`
do
echo $i
done
函数
函数的定义
定义(或创建) Shell 函数是非常容易的。有两种方式:
函数名 () {
函数体
}
或:
function 函数名 {
函数体
}
-
这两种方式都是可行的。看你个人喜好用哪一种方式。
-
函数名后面跟着的圆括号里不加任何参数:这一点与主流编程语言很不相同。C 语言,Java,C++ 等语言中,函数的圆括号中是可以放置参数的(也就是函数的一部分输入),但是 Shell 中的函数的圆括号里不能放置参数。
-
函数的完整定义必须置于函数的调用之前。
传递参数
在 Shell 函数中,我们给它传递参数的方式其实很像给 Shell 脚本传递命令行参数。我们把参数直接置于函数名字后面,然后就像我们之前 Shell 脚本的参数那样:$1
,$2
,$3
等等
我们来看一个例子:
#!/bin/bash
print_something () {
echo Hello $1
}
print_something Matthew
print_something Mark
print_something Luke
print_something John
返回值
Shell 的函数却没办法做到。但是 Shell 的函数可以返回一个状态,有点类似一个程序或命令退出时会有一个退出状态,表明是否成功。
Shell 函数要返回状态,也用 return 这个关键字( return 是英语“返回”的意思)。
一般来说,返回状态 0 表示一切顺利;一个非零值表示有错误。
值得一提的是exit和return有很多有意思的相同点,但exit是系统级的比return更强大,return仅在函数中使用,exit可以在任何地方使用,有兴趣的可以了解其中的区别
变量作用范围
变量的作用范围意味着一个 Shell 脚本的哪些部分可以访问到这个变量。
默认来说,一个变量是“全局的”(global),意味着在脚本的任何地方都可以访问它。
我们也可以创建局部(local)变量。当我们在函数中创建局部变量时,这个变量就只能在这个函数中被访问。
要定义一个局部变量,我们只要在第一次给这个变量赋值时在变量名前加上关键字 local 即可( local 是英语“本地的”的意思)。
#!/bin/bash
local_global () {
local var1='local 1'
echo Inside function: var1 is $var1 : var2 is $var2
var1='changed again' # 这里的 var1 是函数中定义的局部变量
var2='2 changed again' # 这里的 var2 是函数外定义的全局变量
}
定义局部变量有一个好处,就是可以防止被脚本的其它地方的代码意外改变数值。
在函数中,尽量用局部变量。只有实在不行才用全局变量,毕竟全局变量不太安全。