shell 脚本教程
指定解释器
#!/bin/bash
基本语法
变量
变量类型
- 局部变量:局部变量在脚本或命令中定义,仅在当前shell实例中有效,其他shell启动的程序不能访问局部变量
- 环境变量:所有的程序,包括shell启动的程序,都能访问环境变量,有些程序需要环境变量来保证其正常运行。必要的时候shell脚本也可以定义环境变量
- shell变量:shell变量是由shell程序设置的特殊变量。shell变量中有一部分是环境变量,有一部分是局部变量,这些变量保证了shell的正常运行
变量操作
- 创建普通变量:name=“test”,组要注意的是等号两边不能有空格。执行这一行后,被删除前全局都可以访问
- 创建局部变量:local name=“test”,使用local修饰的变量在函数体外无法访问,只能在函数体中使用
- 创建只读变量:name=“only_read” -> readonly name,这种变量不可以被修改
- 使用变量:echo $name或者echo $
- 删除变量:unset name,删除之后的变量无法被访问,需要注意无法删除只读变量
字符串变量
var='str'
单引号中不能出现单独的单引号,转义也是不可以的var="my name is ${name}"
这种方式创建的字符串变量有效,也可以出现转义符str=('cascc \(s" 'ac'\)')
‘
符号需要成对出现
拼接字符串
- 字面量拼接
str01="1""2"
或者str01="1"'2'
,这样就将1和2两个字符拼接在了一起。需要注意的是两个串之间不可以有空格 str03=${part01}${part02}
或str04=${part01}"end"
或str05="${part01} ${part02}"
这三种方式都可以拼接字符串变量str02= `date`“end”
,这里的date是一个shell命令,需要使用引用
获取字符串长度
- 使用
wc -L
命令:echo "abc" |wc -L
- 使用
expr length
可以获取string的长度expr length '1234567'
- 通过
echo ${#name}
的方式a='abc' echo ${#a}
取子字符串
-
代码 意义 ${varible##*string}
从左向右截取最后一个string后的字符串 ${varible#*string}
从左向右截取第一个string后的字符串 ${varible%%string*}
从右向左截取最后一个string后的字符串 ${varible%string*}
从右向左截取第一个string后的字符串 MYVAR=foodforthought.jpg echo ${MYVAR##*fo} >rthought.jpg
${varible:n1:n2}
截取变量varible从n1到n2之间的字符串,可以根据特定字符偏移和长度,来选择特定子字符串EXCLAIM=cowabunga echo ${EXCLAIM:0:3} >cow
数组
如果说变量是存储单个变量的内存空间,那么数组就是多个变量的集合,它存储多个元素在一片连续的内存空间中。在bash中,只支持一维数组,不支持多维数组
数组定义与引用
数组名=(元素1 元素2 元素3 ... 元素n)
数组名[下标]=值
数组名=([下标1]=值1 [下标2]=值2 ... [下标n]=值n)
${数组名[下标]}
遍历数组元素
使用 for
或 while
循环便利数组元素
a=(1 2 3 4 5 6)
for((i=0; i<10; i++))
do
echo "a[$i]=${a[$i]}"
done
除此以外我们还可以使用 ${a[*]}
或者 ${a[@]}
来遍历数组元素
a=(1 2 3 4 5 6)
echo "${a[*]}"
echo "${a[@]}"
获取数组长度
我们可以使用 #
来获取数组长度,需要注意的是在shell脚本中越界访问数组时不会报错
a=(1 2 3 4 5 6)
echo "${a[*]}"
echo "a len: ${#a[*]}"
>1 2 3 4 5 6
>a len: 6
先使用其获取数组中的元素后使用#获取元素个数即可
合并数组
a=(1 2 3 4 5 6)
b=("hello" "zhaixue.cc")
c=("${a[*]}" "${b[*]}")
echo "${c[@]}"
>1 2 3 4 5 6 hello zhaixue.cc
删除数组元素
a=(1 2 3 4 5 6)
echo "${a[*]}"
echo "a len: ${#a[*]}"
unset "a[5]"
echo "${a[*]}"
echo "a len: ${#a[*]}"
unset a
>1 2 3 4 5 6
>a len: 6
>1 2 3 4 5
>a len: 5
变量传参
变量 | 含义 |
---|---|
$0 | 代表执行的文件名 |
$1 | 代表传入的第1个参数 |
$n | 代表传入的第n个参数 |
$# | 参数个数 |
$* | 以一个单字符串显示所有向脚本传递的参数。 |
$@ | 与$*相同,但是使用时加引号,并在引号中返回每个参数 |
$$ | 脚本运行的当前进程号 |
$! | 后台运行的最后一个进程的ID |
$? | 显示最后命令的退出状态。0表示没有错误,其他任何值表明有错误。 |
运算符
算术运算符
条件表法式需要放在方括号之间,并且要有空格。使用expr进行计算时需要使用反引号
+ - * / % =
[ $a == $b ]
[ $a != $b ]
a=10
b=20
val=$(expr $a + $b)
echo "a + b : $val"
关系运算符
只支持数字,不支持字符串,除非字符串的值是数字
运算 | shell中的实现 | 主要符号 |
---|---|---|
检测两个数是否相等 | [ \$a -eq \$b ] | -eq |
检测两个数是否不相等 | [ \$a -ne \$b ] | -ne |
检测左边的数是否大于右边的 | [ \$a -gt \$b ] | -gt |
检测左边的数是否小于右边的 | [ \$a -lt \$b ] | -lt |
检测左边的数是否大于等于右边的 | [ \$a -ge \$b ] | -ge |
检测左边的数是否小于等于右边的 | [ \$a -le \$b ] | -le |
a=1
b=2
if [ $a != $b ]; then
echo "$a != $b : a 不等于 b"
else
echo "$a == $b: a 等于 b"
fi
>1 != 2 : a 不等于 b
布尔运算符
!
[ ! false ]
==注意!
与false
中间的空格- 或运算
-o
[ $a -lt 20 -o $b -gt 100 ]
- 与运算
-a
[ $a -lt 20 -a $b -gt 100 ]
逻辑运算符
- 逻辑 AND
&&
[[ $a -lt 100 && $b -gt 100 ]]
- 逻辑 OR
||
[[ $a -lt 100 || $b -gt 100 ]]
字符串运算符
运算 | shell中的实现 | 主要符号 |
---|---|---|
检测两个字符串是否相等 | [ \$a = \$b ] | = |
检测两个字符串是否不相等 | [ \$a != \$b ] | != |
检测字符串长度是否为0 | [ -z \$a ] | -z |
检测字符串长度是否不为 0 | [ -n “\$a” ] | -n |
检测字符串是否为空 | [ \$a ] | \$ |
文件测试运算符
运算 | shell中的实现 | 主要符号 |
---|---|---|
检测文件是否是块设备文件 | [ -b \$file ] | -b file |
检测文件是否是字符设备文件 | [ -c \$file ] | -c file |
检测文件是否是目录 | [ -d \$file ] | -d file |
检测文件是否是普通文件(既不是目录,也不是设备文件) | [ -f \$file ] 返回 true | -f file |
检测文件是否设置了 SGID 位 | [ -g \$file ] | -g file |
检测文件是否设置了粘着位(Sticky Bit) | [ -k \$file ] | -k file |
检测文件是否是有名管道 | [ -p \$file ] | -p file |
检测文件是否设置了 SUID 位 | [ -u \$file ] | -u file |
检测文件是否可读 | [ -r \$file ] | -r file |
检测文件是否可写 | [ -w \$file ] | -w file |
检测文件是否可执行 | [ -x \$file ] | -x file |
检测文件是否为空(文件大小是否大于0) | [ -s \$file ] | -s file |
检测文件(包括目录)是否存在 | [ -e \$file ] | -e file |
运算指令
(())
可以直接使用双圆括弧计算其中的内容,如((var=a+b)),该指令经常在if/while等条件判断中需要计算时使用let
在计算表达式的时候可以直接使用let,如let var=a+bexpr
```var=`expr a+b````- bc计算器
bc计算器支持shell中的小数进行运算,并且可以交互式或者非交互式的使用。基本使用方式为var=$(echo "(1.1+2.1)"|bc)
$[]
可以直接使用这种方式计算中括弧中的内容,如echo $[1+2]
控制语句
if
# if-fi
if condition
then
command1
command2
...
commandN
fi
# if-else-fi
if condition
then
command1
else
command2
fi
# if else-if else
if condition1
then
command1
elif condition2
then
command2
else
command3
fi
for
for var in item1 item2 ... itemN
do
command1
command2
...
commandN
done
例如:
for loop in 1 2 3 4 5
do
echo "The value is: $loop"
done
while
while condition
do
command
done
例如:
int=1
while(( $int<=5 ))
do
echo $int
let "int++"
done
无限循环:
for (( ; ; ))
while :
do
command
done
while true
do
command
done
until循环
until condition
do
command
done
跳出循环
break
continue
case-esac多选择语句
类似其他语言的
switch
格式如下
case 值 in
模式1)
command1
command2
...
commandN
;;
模式2)
command1
command2
...
commandN
;;
*)
command1
esac
select-in语句
select in是shell中独有的一种循环,非常适合终端的交互场景,它可以显示出带编号的菜单,用户出入不同编号就可以选择不同的菜单,并执行不同的功能
select var in seq
do
action
done
echo "What is your favourite OS?"
select var in "Linux" "Gnu Hurd" "Free BSD" "Other"; do
break;
done
echo "You have selected $var"
函数
定义函数
[ function ] funname [()]
{
action;
[return int;]
}
注意
1. 以上的[ function ]也可以省略
2. 当函数没有return时,默认返回最后一个命令的运行结果作为返回值
函数参数
在shell中,调用函数时可以向其传递参数。在函数内部直接通过 $n
获取参数的值
funWithParam(){
echo "第一个参数为 $1 !"
echo "第十个参数为 ${10} !"
}
funWithParam 1 2 3 4 5 6 7 8 9 34 73
需要注意\(10不能返回第十个参数,当n>10的时候,需要使用\)(n)来获取参数。
函数作用域
Shell脚本中执行函数时并不会开启子进程,默认在函数外部或函数内部定义和使用变量的效果相同。函数外部的变量在函数内部可以直接调用,反之函数内部的变量也可以在函数外部直接调用。但是这样会导致变量混淆、数据可能被错误地修改等等问题,那么如何解决这些问题呢?
系统为我们提供了一个local语句,该语句可以使在函数内部定义的变量仅在函数内部有效。定义时直接在变量前加 local
即可
重定向
一个命令通常从一个叫标准输入的地方读取输入,默认情况下,这恰好是你的终端。同样,一个命令通常将其输出写入到标准输出,默认情况下,这也是你的终端。
一般情况下,每个 Unix/Linux 命令运行时都会打开三个文件:
标准输入文件(stdin):stdin的文件描述符为0,Unix程序默认从stdin读取数据。
标准输出文件(stdout):stdout 的文件描述符为1,Unix程序默认向stdout输出数据。
标准错误文件(stderr):stderr的文件描述符为2,Unix程序会向stderr流中写入错误信息。
但有些时候我们可能需要将数据从其它文件读入或读出,这就需要重定向。
输入重定向
command1 < file
输出重定向
command1 > file
Here Document
Here Document 是 Shell 中的一种特殊的重定向方式,用来将输入重定向到一个交互式 Shell 脚本或程序。它的作用是将两个 delimiter 之间的内容(document) 作为输入传递给 command。基本语法如下:
command << delimiter documentdelimiter
注意:
结尾的delimiter 一定要顶格写,前面不能有任何字符,后面也不能有任何字符,包括空格和 tab 缩进。开始的delimiter前后的空格会被忽略掉。
/dev/null 文件
如果希望执行某个命令,但又不希望在屏幕上显示输出结果,那么可以将输出重定向到 /dev/null中,/dev/null 是一个特殊的文件,写入到它的内容都会被丢弃;如果尝试从该文件读取内容,那么什么也读不到。但是 /dev/null 文件非常有用,将命令的输出重定向到它,会起到"禁止输出"的效果。语法如下
command > /dev/null