Shell脚本基础教程
Shell参数定义
定义变量
想要定义变量,只需要使用如下命令即可。
variable_name=variable_value
variable_name
表示变量名,variable_value
表示变量值。注意,等号与变量名和变量值之间不能有空格。
变量名的命名需要遵循如下规则:
- 变量名的组成只能是英文字母、数字和下划线
- 首字母只能是英文字母和下划线,不能是数字
正确示例:
test="test"
_test=123
错误示例:
1test="test"
test%test=123
test = "test"
使用变量
定义了变量,就要使用变量。使用变量的时候,需要在变量名称前加上符号$
。
# 定义变量
test="hello world"
# echo是用于字符串输出的命令,这里将变量test输出到控制台
echo $test
echo ${test}!
# 输出结果
hello world
hello world!
由上面的示例,我们看到了变量名外面的花括号是可选的,加花括号是为了帮助识别变量的边界。
推荐给所有变量加上花括号,这是个好的编程习惯。
修改变量
定义的变量是可以修改的,再设置变量的值即可。
# 设置变量test的值为hello world,并打印
test="hello world"
echo ${test}
# 修改变量test的值为hello shell,并打印
test="hello shell"
echo ${test}
# 输出结果
hello world
hello shell
只读变量
使用readonly
命令可以将变量定义为只读变量,只读变量的值不能被改变。
# 定义一个变量test
test="222"
# 设置test变量为只读
readonly test
# 修改test变量值为111,修改失败
test="111"
# 输出
test: readonly variable
提示信息告诉,test
变量是一个只读的变量,不允许修改。
删除变量
使用unset
命令可以删除变量。
# 定一个一个变量test,并打印
test="222"
echo "test的值为:$test"
# 删除变量test,再打印,发现没有了
unset test
echo "test的值为:$test"
# 输出
test的值为:222
test的值为:
刚才我们说使用readonly
命令可以将变量定义为只读变量,那只读变量是否允许删除呢?
# 定一个一个变量test,并打印
test="222"
echo "test的值为:$test"
# 设置变量test为只读
readonly test
# 删除变量test,再打印,发现还有,说明没有删除成功
unset test
echo "test的值为:$test"
# 输出
test的值为:222
./test.sh: line 6: unset: test: cannot unset: readonly variable
test的值为:222
根据输出,我们发现了,只读变量是不允许删除的。
变量的作用域
变量的作用域也就是变量可以使用的范围。根据变量的作用域,可以将变量分为三种。
- 局部变量
- 全局变量
- 环境变量
局部变量
有的变量只能在函数内部使用,这叫做局部变量。
为了演示局部变量,使用了Shell函数的概念,若不清楚,可点击跳转先学习下函数。
# 定义了一个名为hello的函数
function hello()
{
local name="Shell"
echo "函数内获取:$name"
}
# 调用hello函数
hello
echo "函数外获取变量:$name"
# 输出
$ sh variables-scope.sh
函数内获取:Shell
函数外获取变量:
通过输出我们可以看到,使用了local
定义的变量成为了局部变量,在函数内可以获取到此变量,但是在函数外获取不到这个变量。
全局变量
全局变量是指在当前的整个Shell进程中都有效的变量。每个Shell进程都有自己的作用域,彼此之间互不影响。在Shell脚本中定义的变量,默认就是全局变量。
#!/bin/bash
# 在脚本中定义了一个变量name,这个变量默认就是全局变量,在整个shell脚本中都可以访问
name="shell"
# 函数内获取变量
function printName()
{
echo "函数内获取名字为:$name"
}
# 函数外获取变量
echo "函数外获取姓名为:$name"
printName
# 输出
$ sh variables.sh
函数外获取姓名为:shell
函数内获取名字为:shell
由此可以知道,当我们直接定义一个变量的时候,默认是全局变量,整个shell脚本中都可以访问。
环境变量
全局变量只在当前 Shell 进程中有效,对其它 Shell 进程和子进程都无效。我们可以使用export
命令,将变量转变为环境变量,这样Shell子进程中就可以访问到Shell父进程的变量了。当然两个没有父子关系的 Shell 进程是不能传递环境变量的。
# 以下操作在命令行中直接进行,才可以看到输出结果
name="shell"
echo "父进行中访问姓名为:$name"
bash
echo "子进程中访问姓名为:$name"
exit
export name
bash
echo "子进程中访问姓名为:$name"
age=20
echo "子进程中访问年龄为:$age"
export age
exit
echo "父进程中访问年龄为:$age"
# 输出
父进行中访问姓名为:shell
子进程中访问姓名为:
子进程中访问姓名为:shell
子进程中访问年龄为:20
父进程中访问年龄为:
由输出结果可以看出,使用export
命令之后,子进程中已经可以获取到name
变量了。但是子进程export
的变量,父进程也是访问不到的。
变量的类型
字符串
字符串是shell编程中最常用最有用的数据类型。字符串可以用单引号,也可以用双引号,也可以不用引号。
- 单引号:任何字符都会原样输出,单引号字符串中的变量是无效的
- 双引号:双引号里可以有变量,双引号里可以出现转义字符
字符串还有一些常用的操作,请继续学习。
获取字符串长度
# 定义一个字符串变量,并输出字符串的长度
str="Hello World!"
echo 字符串的长度为:${#str}
# 输出
字符串的长度为:12
我们知道${variable_name}
便是使用一个变量,由上面的例子,我们知道了,在变量名之前,加上#
符号即${#variable_name}
,便可获取字符串变量的长度。
获取子字符串
str="Hello World!"
echo 位置1之后的5位字符串为:${str:0:5}
# 输出
位置1之后的5位字符串为:Hello
${variable_name:index:length}
在变量名后加上子字符串开始的位置以及长度,即可获取子字符串。这里需要注意的是,索引位置是以0开始,所以index为0表示第一个字符。
查找子字符串位置
str="Hello World!"
echo o字符在字符${str}上的位置为:`expr index "${str}" o`
# 输出
o字符在字符Hello World!上的位置为:5
括住expr index "${str}" o
的是反引号,表示要执行里面的内容,这里需要注意。
这里使用了expr
命令来取得子字符串的位置。expr index $string $substring
表示要在字符串string
中找到字符串substring
的位置。
更多expr
命令的用法读者可以继续深入了解。
数组
定义数组
想要定义数组,可以用括号来表示数组,数组元素用"空格"符号分割。
array_name=(value0 value1 value2 value3)
或者
array_name=(
value0
value1
value2
value3
)
读取数组内元素
想要读取数组,可以使用如下命令:
# 获取数组array_name的索引index的元素
${array_name[index]}
# 获取数组array_name的索引index的元素,并赋值给value
value=${array_name[index]}
# 获取数组中的所有元素
${array_name[@]}
使用示例如下:
arr=("张三" "李四" "王五")
value=${arr[0]}
echo $value
# 输出
张三
获取数组长度
获取数组长度的方法与获取字符串长度的方法类似。
${#array_name[@]}
Shell注释
单行注释
以#
开头的行就是注释。就像下面这样:
#--------------------------------------------
# 这是一行注释
#--------------------------------------------
多行注释
以:<<EOF
开头,EOF
结尾的便是多行注释。其中EOF
可以替换成其他的字符,下面是两个多行注释的例子
:<<EOF
第一行注释
第二行注释
第三行注释
EOF
:<<HAHA
第一行注释
第二行注释
第三行注释
HAHA
Shell流程控制
分支语句
if else语句
if语句
的语法如下:
if condition
then
command1
command2
...
commandN
fi
也可以写成一行,关键点在于使用分号;
作为语句的分割
if condition; then command; fi
意思是:如果condition
语句成立,则执行then
之后的语句,直到出现fi
。
使用示例演示下if语句
的使用。
# 使用test命令判断脚本输入第一个参数和第二个参数的字符是否相等
if test $1 = $2
then
echo "相等"
fi
if test $1 != $2
then
echo "不相等"
fi
$ sh test.sh aaa aaa
相等
$ sh test.sh aaa zzz
不相等
# 将if语句写成一行
$ if test 1 -eq 1;then echo "相等";fi
相等
if else 语法`格式:
if condition
then
command1
command2
...
commandN
else
command
fi
意思是:如果condition
语句成立,则执行then
之后的语句。若是不成立,则执行else
之后的语句,直到出现fi
。
使用示例演示如下所示:
# 使用test命令判断脚本输入第一个参数指定的文件是否存在
if test -e $1
then
echo "文件存在"
else
echo "文件不存在"
fi
# 输出
$ sh test.sh "test.sh"
文件存在
$ sh test.sh "test.sh1"
文件不存在
if else-if else 语法
格式:
if condition1
then
command1
elif condition2
then
command2
else
commandN
fi
意思是:如果condition1
语句成立,则执行then
之后的语句。若是不成立,则继续判断condition2
是否成立,若成立,则执行then
之后的语句。若还是不成立,则执行else
之后的语句,直到出现fi
。
使用示例演示如下所示:
# 使用test命令判断脚本输入第一个参数和第二个参数的大小比较
if test $1 -eq $2
then
echo "数值相等"
elif test $1 -gt $2
then
echo "$1大于$2"
else
echo "${1}小于${2}"
fi
# 输出
$ sh test.sh 10 10
数值相等
$ sh test.sh 10 11
10小于11
$ sh test.sh 12 11
12大于11
case语句
case语句为多选择语句。可以用case语句匹配一个值与一个模式,如果匹配成功,执行相匹配的命令。
case语句格式如下:
case 值 in
模式1)
command1
command2
...
commandN
;;
模式2)
command1
command2
...
commandN
;;
esac
模式取值可以为变量或常数,每一模式必须以右括号结束。匹配发现取值符合某一模式后,其间所有命令开始执行直至 ;;
。
取值将检测匹配的每一个模式。
一旦模式匹配,则执行完匹配模式相应命令后不再继续其他模式。如果无一匹配模式,使用星号 *
捕获该值,再执行后面的命令。
循环语句
for循环
for循环语句格式为:
for var in item1 item2 ... itemN
do
command1
command2
...
commandN
done
也可以写成一行
for var in item1 item2 ... itemN; do command1; command2… done;
示例如下:
for value in 1 2 3 4 5
do
echo "The value is: $value"
done
# 输出
The value is: 1
The value is: 2
The value is: 3
The value is: 4
The value is: 5
while循环
while循环的语句格式如下:
while condition
do
command
done
当condition
语句判断为true
时,执行command
语句;执行结束之后,继续判断condition
语句,直到为false
,结束执行。
示例如下:
value=1
while(( $value<=5 ))
do
echo 值为:$value
let "value++"
done
# 输出
值为:1
值为:2
值为:3
值为:4
值为:5
跳出循环
在循环的过程中,经常需要做出跳出循环的操作。这里又包含了两种情况,一种是跳出当前循环,进入下一次循环;另一种是,直接跳出循环语句,执行循环语句后面的代码。
第一种,跳出当前循环,进入下一次循环,使用continue
命令。
第二种,跳出循环,使用break
命令。
Shell函数
定义函数
可以在Shell脚本内定义函数。格式如下所示:
[ function ] functioName [()]
{
action;
[return int;]
}
- function关键字是可选的,可以设置,也可以不设置。
若对这个格式不是很理解,我们接下来就详细来说明。
我们先来尝试简单地使用函数。我们先定义一个函数,然后再调用它。如下所示:
# 定义一个函数,名为getName,其作用为打印字符串Shell
function getName(){
echo "Shell"
}
# 调用函数getName
getName
# 输出
Shell
调用函数
直接使用函数名称即可调用函数。
在函数名称后面,再带上一些参数,便是调用函数并给函数传递了一些参数。
# 定义一个函数getName
function getName(){
echo "Shell"
}
# 直接使用函数名调用函数
getName
# 定义一个函数hello
function hello(){
echo "Hello $1"
}
# 直接使用函数名调用函数,并传递了一个字符串作为参数
hello "孙悟空"
# 输出
Shell
Hello 孙悟空
函数参数
函数是支持传递参数的,在函数体内部,通过$n
的形式来获取参数的值,例如,$1
表示第一个参数,$2
表示第二个参数,以此类推。注意,$10
不能获取第十个参数,获取第十个参数需要${10}
。当n>=10时,需要使用${n}来获取参数。
# 定义一个函数hello
function hello(){
echo "Hello $1"
}
# 直接使用函数名调用函数,并传递了一个字符串作为参数
hello "孙悟空"
# 输出
Hello 孙悟空
另外,还有几个特殊字符用来处理参数:
参数处理 | 说明 |
---|---|
$# | 传递到脚本的参数个数 |
$* | 以一个单字符串显示所有向脚本传递的参数 |
$$ | 脚本运行的当前进程ID号 |
$! | 后台运行的最后一个进程的ID号 |
$@ | 与$*相同,但是使用时加引号,并在引号中返回每个参数。 |
$- | 显示Shell使用的当前选项,与set命令功能相同。 |
$? | 显示最后命令的退出状态。0表示没有错误,其他任何值表明有错误。 |
函数返回值
函数的返回值,可以显式使用关键字return
返回,如果不加,将以最后一条命令运行结果,作为返回值。 return
后跟数值,可选值为0-255
。
return
表示退出函数并返回一个退出值,脚本中可以在调用函数后,用$?
获取到该退出值。
# 定义了一个函数sum,read命令用于在执行中输入数字,并设置到参数firstNum和secondNum
function sum(){
echo "这个函数会对输入的两个数字进行相加运算..."
echo "输入第一个数字: "
read firstNum
echo "输入第二个数字: "
read secondNum
echo "两个数字分别为 $firstNum 和 $secondNum"
return $(($firstNum+$secondNum))
}
# 运行函数sum,并通过$?获取到函数的返回值进行打印
sum
echo "输入的两个数字之和为$?"
# 输出
这个函数会对输入的两个数字进行相加运算...
输入第一个数字:
1
输入第二个数字:
2
两个数字分别为 1 和 2
输入的两个数字之和为3
函数返回值在调用该函数后通过$?
来获得。
递归调用函数
什么叫做递归调用,便是函数自己调用自己。
函数库
一些注意事项
- 所有函数在使用前必须定义。这意味着必须将函数放在脚本开始部分,直至shell解释器首次发现它时,才可以使用。
- Shell函数中定义的变量默认也是全局变量,它和在函数外部定义变量拥有一样的效果。
常见的命令
echo
echo命令用于字符串的输出。命令格式:
echo string
printf
printf也是一个输出命令,但是可以格式化字符串。命令格式:
printf format-string [arguments...]
test命令
Shell中的test
命令用于检查某个条件是否成立,它可以进行数值、字符和文件三个方面的测试。
数值测试
参数 | 说明 |
---|---|
-eq | 等于则为真 |
-ne | 不等于则为真 |
-gt | 大于则为真 |
-ge | 大于等于则为真 |
-lt | 小于则为真 |
-le | 小于等于则为真 |
# 使用test命令判断脚本输入第一个参数和第二个参数的大小比较
if test $1 -eq $2
then
echo "数值相等"
elif test $1 -gt $2
then
echo "$1大于$2"
else
echo "${1}小于${2}"
fi
# 输出
$ sh test.sh 10 10
数值相等
$ sh test.sh 10 11
10小于11
$ sh test.sh 12 11
12大于11
字符串测试
参数 | 说明 |
---|---|
= | 等于则为真 |
!= | 不相等则为真 |
-z 字符串 | 字符串的长度为零则为真 |
-n 字符串 | 字符串的长度不为零则为真 |
# 使用test命令判断脚本输入第一个参数和第二个参数的字符是否相等
if test $1 = $2
then
echo "相等"
fi
if test $1 != $2
then
echo "不相等"
fi
$ sh test.sh aaa aaa
相等
$ sh test.sh aaa zzz
不相等
文件测试
参数 | 说明 |
---|---|
-e 文件名 | 如果文件存在则为真 |
-r 文件名 | 如果文件存在且可读则为真 |
-w 文件名 | 如果文件存在且可写则为真 |
-x 文件名 | 如果文件存在且可执行则为真 |
-s 文件名 | 如果文件存在且至少有一个字符则为真 |
-d 文件名 | 如果文件存在且为目录则为真 |
-f 文件名 | 如果文件存在且为普通文件则为真 |
-c 文件名 | 如果文件存在且为字符型特殊文件则为真 |
-b 文件名 | 如果文件存在且为块特殊文件则为真 |
# 使用test命令判断脚本输入第一个参数指定的文件是否存在
if test -e $1
then
echo "文件存在"
else
echo "文件不存在"
fi
# 输出
$ sh test.sh "test.sh"
文件存在
$ sh test.sh "test.sh1"
文件不存在
其他
Shell中$各种含义
符 号 | 含 义 |
---|---|
$0 | 脚本名 |
$# | 参数个数 |
$n | 传递给脚本的参数值,$1表示第1参数、$2表示第2参数 |
$? | 上次退出的状态(返回值),0没有错误,1错误 |
$* | 所有参数列表。"$*“时,是”$1 $2 … $n"的形式 |
$@ | 所有参数列表。“$@“时,是”$1” “ 2 " … " 2" … " 2"…"n” 的形式 |
$$ | 当前进程的编号(ProcessID) |
$! | shell最后运行的后台Process的PID |
$var | 变量,会与后面的连接,如$var_a,会当做变量var_a |
${var} | 变量,界定范围 |
$() | 与(反引号)类似,里面执行完再返回值, 所有shell通用 |
$[] | 可进行算术运算和逻辑运算,不支持浮点和字符串 |
$(()) | 可进行算术运算和逻辑运算,不支持浮点和字符串。里面的变量可以省略$ |