shell脚本编写语法详解
1 基本语法
1.1 变量
变量:使用一串固定的字符来表示不固定的目标。
1.1.1 变量类型
在shell中会同时存在三种类型变量。
-
局部变量:局部变量在脚本或命令中定义,仅在当前shell实例中有效,其他shell启动的程序不能访问局部变量。
-
环境变量:所有的程序,包括shell启动的程序,都能访问环境变量,有些程序需要环境变量来保证其正常运行。
必要的时候shell脚本也可以定义环境变量。
-
shell变量:shell变量是由shell程序设置的特殊变量。
shell变量中有一部分是环境变量,有一部分是局部变量,这些变量保证了shell的正常运行
1.1.2 变量操作
- 创建普通变量:name=“test”,注意:等号两边不能有空格。
- 创建局部变量:local name=“test”,使用
local
修饰的变量在函数体外无法访问,只能在函数体中使用。 - 创建只读变量:name=“only_read” -> readonly name,这种变量不可以被修改。
- 使用变量:echo $name 或者 echo ${name}
- 删除变量:unset name,删除之后的变量无法被访问,注意:无法删除只读变量。
1.1.3 字符串变量
1.1.3.1 字符串变量的创建
-
使用单引号创建:var=‘test’
这种方式创建的变量只能原样输出,变量无效,我们可以借用c中的“字符串常量”的定义理解这种特性。
除此以外,单引号中不能出现单独的单引号,转义也是不可以的。
-
使用双引号创建:var=“my name is ${name}”
这种方式创建的字符串变量有效,也可以出现转义符。
1.1.3.2 拼接字符串
-
字面量拼接
str="1""2"或者str01=“1”‘2’,这样就将1和2两个字符拼接在了一起。需要注意的是两个串之间不可以有空格。 -
变量拼接:下面这三种方式都可以拼接字符串变量。
- str=${part01}${part02}
- str02=${part01}“end”
- str03=“${part01} ${part02}”
-
命令拼接:这里的date是一个shell命令,需要使用引用
str= date“end”str02=`date`"end"
1.1.3.3 获取字符串长度
- 使用
wc -L
命令
wc -L
可以获取到当前行的长度,因此对于单独行的字符串可以用这个简单的方法获取,另外wc -l
则是获取当前字符串内容的行数。
echo "abc" |wc -L
- 使用
expr length
可以获取string的长度
expr length ${<!--{C}%3C!%2D%2D%20%2D%2D%3E-->str}
- awk获取域的个数
但是如果大于10个字符的长度时是否存在问题需要后面确认
echo "abc" |awk -F "" '{print NF}'
- 通过
awk+length
的方式获取字符串长度
echo "Alex"|awk '{print length($0)}'
- 通过
echo ${#name}
的方式
echo "Alex"|awk '{print length($0)}'
1.1.3.4 提取子字符串
代码 | 意义 |
---|---|
${varible##*string} | 从左向右截取最后一个string后的字符串 |
${varible#*string} | 从左向右截取第一个string后的字符串 |
${varible%%string*} | 从右向左截取最后一个string后的字符串 |
${varible%string*} | 从右向左截取第一个string后的字符串 |
$ var=firenation.jpg
$ echo ${var##*i}
运行结果为on.jpg
2.使用${varible:n1:n2}
截取变量varible从n1到n2之间的字符串,可以根据特定字符偏移和长度,来选择特定子字符串,如下代码:
$ var=azula
$ echo ${var:0:3}
运行结果最终显示azu
。
1.1.4 数组
数组是多个变量的集合,它存储多个元素在一片连续的内存空间中。
在bash中,只支持一维数组,不支持多维数组。
1.1.4.1 数组定义与引用
定义一个数组方式如下:
数组名=(元素1 元素2 元素1 ... 元素n)
指定数组对应下标的元素进行赋值:
数组名[下标]=值
同时指定多个数组元素进行赋值:
数组名=([下标1]=值1 [下标2]=值2 ... [下标n]=值n)
引用数组对应下标的元素:
${数组名[下标]}
1.1.4.2 遍历数组元素
使用for
(或while
循环)循环遍历数组元素:
#!/bin/bash
a=(1 2 3 4 5 6)
for((i=0; i<10; i++))
do
echo "a[$i]=${a[$i]}"
done
除此以外我们还可以使用${a[*]}
或者${a[@]}
来遍历数组元素,具体代码如下:
#!/bin/bash
a=(1 2 3 4 5 6)
echo ${a[*]}
echo ${a[@]}
1.1.4.3 获取数组长度
我们可以使用#
来获取数组长度,需要注意的是在shell脚本中我们越界访问数组时是不会报错的。
#!/bin/bash
a=(1 2 3 4 5 6)
echo ${a[*]}
echo "a len: ${#a[*]}"
我们先使用其获取数组中的元素后使用#
获取元素个数即可。
1.1.4.4 合并数组
我们可以如下进行拼接:
#!/bin/bash
a=(1 2 3 4 5 6)
b=("hello" "zhaixue.cc")
c=(${a[*]} ${b[*]})
这样我们就将两个数组拼接起来了。
1.1.4.5 删除数组元素
如果我们想要删除某个数组元素,具体代码如下:
#!/bin/bash
a=(1 2 3 4 5 6)
echo ${a[*]}
echo "a len: ${#a[*]}"
unset a[5]
echo ${a[*]}
echo "a len: ${#a[*]}"
我们如果要删除整个数组,可以执行unset a
,举例代码如下:
#!/bin/bash
a=(1 2 3 4 5 6)
echo ${a[*]}
echo "a len: ${#a[*]}"
unset a
echo ${a[*]}
echo "a len: ${#a[*]}"
1.1.3 变量传参
相关的变量含义为:
变量 | 含义 |
---|---|
$0 | 代表执行的文件名 |
$1 | 代表传入的第1个参数 |
$n | 代表传入的第n个参数 |
$# | 参数个数 |
$* | 以一个单字符串显示所有向脚本传递的参数。 |
$@ | 与$*相同,但是使用时加引号,并在引号中返回每个参数 |
$$ | 脚本运行的当前进程号 |
$! | 后台运行的最后一个进程的ID |
$? | 显示最后命令的退出状态。0表示没有错误,其他任何值表明有错误。 |
1.2 运算符
原生的bash并不支持简单的数学运算,通常要通过其它命令来实现。
1.2.1 算数运算符
以下表格中的a
和b
都是变量。
运算 | shell中格式 |
---|---|
加法 | expr $a + $b |
减法 | expr $a - $b |
乘法 | expr $a \* $b |
除法 | expr $b / $a |
取余 | expr $b % $a |
赋值 | a=$b |
相等 | [ $a == $b ] |
不相等 | [ $a != $b ] |
需注意:条件表法式需要放在方括号之间,并且要有空格。使用
expr
进行计算时需要使用反引号,为了让读者更容易理解,给出下列示例代码。
#!/bin/bash
a=10
b=20
val=`expr $a + $b`
echo "a + b : $val"
1.2.2 关系运算符
关系运算符只支持数字,不支持字符串,除非字符串的值是数字。
运算 | 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 |
举例代码如下:
#!/bin/bash
a=1
b=2
if [ $a != $b ]
then
echo "$a != $b : a 不等于 b"
else
echo "$a == $b: a 等于 b"
1.2.1 布尔运算符
具体如下:
运算 | shell中的实现 | 主要符号 |
---|---|---|
非运算 | [ ! false ] | ! |
或运算 | [ $a -lt 20 -o $b -gt 100 ] | -o |
与运算 | [ $a -lt 20 -a $b -gt 100 ] | -a |
1.2.2 逻辑运算符
具体如下:
运算 | shell中的实现 | 主要符号 |
---|---|---|
逻辑的 AND | [[ $a -lt 100 && $b -gt 100 ]] | && |
逻辑的 OR | [[ $a -lt 100 ` |
布尔运算符和逻辑运算符的区别:
语法上,逻辑运算需要双括弧,布尔运算只需要单大括弧功能上,逻辑运算具有特殊的短路功能,即是在AND运算中第一个表达式为false时则不执行第二个表达式,在OR运算中第一个表达式为true时不执行第二个表达式。
1.2.3 字符串运算符
下表列出了常用的字符串运算符:
运算 | shell中的实现 | 主要符号 |
---|---|---|
检测两个字符串是否相等 | [ $a = $b ] | = |
检测两个字符串是否不相等 | [ $a != $b ] | != |
检测字符串长度是否为0 | [ -z $a ] | -z |
检测字符串长度是否不为 0 | [ -n “$a” ] | -n |
检测字符串是否为空 | [ $a ] | $ |
1.2.6 文件测试运算符
主要用于检测unix文件的各种属性:
运算 | 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 |
举例如下:
#!/bin/bash
file="/home/westos/Desktop/textcpp/test.sh"
if [ -r $file ]
then
echo "文件可读"
else
echo "文件不可读"
fi
1.2.7 运算指令
1.(( ))
我们可以直接使用双圆括弧计算其中的内容,如((var=a+b))
该指令经常在if/while等条件判断中需要计算时使用。
2.let
在计算表达式的时候我们可以直接使用let
如:let var=a+b。
3.expr
在前面的内容中我们也提到了它,是非常常用的计算指令,使用时需要在外部增反引号
var=`expr a+b`
4.bc计算器
bc计算器支持shell中的小数进行运算,并且可以交互式或者非交互式的使用。
基本使用方式为
var=$(echo "(1.1+2.1)"|bc)
;
5.$[]
我们可以直接使用这种方式计算中括弧中的内容,如echo $[1+2]
1.3 控制语句
和其他语句不同,shell的流传呢个控制不可为空。接下来我们为大家介绍sehll中常用的语法。
1.3.1 if语句结构
1.3.1.1 if-fi
就类似于c中的if条件判断,如下:
if condition
then
command1
command2
...
commandN
fi
1.3.1.2 if-else-fi
代码如下:
if condition
then
command1
else
command2
fi
若condition成立则执行command1,否则执行command2。
1.3.1.3 if else-if else
代码如下:
if condition1
then
command1
elif condition2
then
command2
else
command3
fi
1.3.2 循环结构
1.3.2.1 for循环
格式为:
for var in item1 item2 ... itemN
do
command1
command2
...
commandN
done
以上也可以写做一行,若变量var
在列表中,则for循环执行一次所有命令。以以下代码作为测试:
#!/bin/bash
for loop in 1 2 3 4 5
do
echo "The value is: $loop"
done
1.3.2.2 while循环
格式如下:
while condition
do
command
done
我们运行如下代码:
#!/bin/bash
int=1
while(( $int<=5 ))
do
echo $int
let "int++"
done
1.3.2.3 无限循环
我们可以以上两种语句给出无限循环的实现,首先看一下for循环如何实现:
for (( ; ; ))
除此以外我们也可以使用while循环实现如下:
while :
do
command
done
或者直接将while中的判断语句置为真:
while true
do
command
done
1.3.2.4 until循环
until 循环执行一系列命令直至条件为 true 时停止。语法格式如下:
until condition
do
command
done
1.3.2.5 跳出循环
在循环过程中,有时候需要在未达到循环结束条件时强制跳出循环,Shell使用两个命令来实现该功能:break
和continue
。
1.break跳出循环
当我们需要跳出当前循环,或者终止死循环时,我们就可以使用break来跳出循环。接下来我们运行如下代码:
#!/bin/bash
var=1
while(( $var < 5 ))
do
if(( $var>3 ))
then
echo "跳出循环"
break
fi
echo "$var"
var=`expr $var + 1`
done
在该循环中var>1
时break,而是直接跳出循环。
2.continue跳出循环
continue命令与break命令类似,只有一点差别,它不会跳出所有循环,仅仅跳出当前循环。 接下来我们运行如下代码:
#!/bin/bash
var=1
while(( $var < 5 ))
do
if(( $var>3 ))
then
echo "跳出循环"
continue
fi
echo "$var"
var=`expr $var + 1`
done
使用continue跳出的循环只是当前循环,无法跳出整个循环,由于在该代码中我们每次执行到continue就会跳出当前循环,无法执行 var=expr $var + 1
,所以循环条件一直成立,就成了死循环。
1.3.3 case-esac多选择语句
case ... esac
为多选择语句。- 与其他语言中的
switch ... case
语句类似,是一种多分支选择结构- 每个
case
分支用右圆括号开始 - 用两个分号
;;
表示 break,跳出整个 case … esac 语句 esac
(就是 case 反过来)作为结束标记。
- 每个
- case 要求取值后面必须为单词 in,每一模式必须以右括号结束。取值可以为变量或常数,匹配发现取值符合某一模式后,其间所有命令开始执行直至 ;;。
- 若检测匹配时无一匹配,使用
*
捕获该值,再执行后续命令。
语法格式如下:
case 值 in
模式1)
command1
command2
...
commandN
;;
模式2)
command1
command2
...
commandN
;;
*)
command1
esac
1.3.4 select-in语句
select ... in ...
非常适合终端的交互场景,它可以显示出带编号的菜单,用户出入不同编号就可以选择不同的菜单,并执行不同的功能。
语法格式如下:
select var in seq
do
action
done
我们执行如下代码:
#!/bin/bash
echo "What is your favourite OS?"
select var in "Linux" "Mac" "Win" "Other"; do
break;
done
echo "You have selected $var"
2 函数
函数其实就是将一段代码组合封装在一起实现某个特定的功能或返回某个特定的值。我们在定义函数时需要先起一个函数名,在使用的时候直接调用函数名即可
2.1 定义函数
shell中定义函数格式如下:
[ function ] funname [()]
{
action;
[return int;]
}
注意:
[ function ]
可以省略- 当函数没有
return
时,默认返回最后一个命令的运行结果作为返回值(有点像Scala?)
2.2 函数参数
在shell中,调用函数时可以向其传递参数。在函数内部直接通过$n
获取参数的值。我们给出示例如下:
#!/bin/bash
funWithParam(){
echo "第一个参数为 $1 !"
echo "第十个参数为 ${10} !"
}
funWithParam 1 2 3 4 5 6 7 8 9 34 73
需要注意$10
不能返回第十个参数,当n>10
的时候,需要使用${n}
来获取参数。
2.3 函数作用域
Shell脚本中执行函数时并不会开启子进程,默认在函数外部或函数内部定义和使用变量的效果相同。函数外部的变量在函数内部可以直接调用,反之函数内部的变量也可以在函数外部直接调用。
为了更好区分,系统为我们提供了local
语句,该语句可以使在函数内部定义的变量仅在函数内部有效。定义时直接在变量前加local
即可。
3 重定向
一个命令通常从一个叫标准输入的地方读取输入,默认情况下,这恰好是你的终端。同样,一个命令通常将其输出写入到标准输出,默认情况下,这也是你的终端。
一般情况下,每个 Unix/Linux 命令运行时都会打开三个文件:
- 标准输入文件(stdin):stdin的文件描述符为0,Unix程序默认从stdin读取数据。
- 标准输出文件(stdout):stdout 的文件描述符为1,Unix程序默认向stdout输出数据。
- 标准错误文件(stderr):stderr的文件描述符为2,Unix程序会向stderr流中写入错误信息。
但有些时候我们可能需要将数据从其它文件读入或读出,这就需要我们重定向。
3.1 输入重定向
我们可以让命令从文件中获取,这样本来的命令需要从标准输入stdin
中获取,转换为从我们的指定文件中获取。这样本来需要从键盘输入的命令就会转移到文件读取内容。语法如下:
command1 < file
3.2 输出重定向
同输入重定向很相似,输出重定向也是将本来需要输出标准输出文件stdout
中转化为我们的指定文件中。语法如下:
command1 > file
3.1 标准错误文件重定向
我们可以直接借助标准错误文件的文件描述符来重定向stderr
,语法如下:
$ command 2>file
扩充一点,如果我们想将stdout
标准输出文件和stderr
标准错误文件合并重定向到一个指定文件中,语法如下:
$ command > file 2>&1
3.2 Here Document
Here Document 是 Shell 中的一种特殊的重定向方式,用来将输入重定向到一个交互式 Shell 脚本或程序。它的作用是将两个 delimiter 之间的内容(document) 作为输入传递给 command。基本语法如下:
command << delimiter documentdelimiter
注意:结尾的delimiter 一定要顶格写,前面不能有任何字符,后面也不能有任何字符,包括空格和 tab 缩进。开始的delimiter前后的空格会被忽略掉。
3.3 /dev/null 文件
- 如果希望执行某个命令,但又不希望在屏幕上显示输出结果,那么可以将输出重定向到 /dev/null中,/dev/null 是一个特殊的文件,写入到它的内容都会被丢弃;
- 如果尝试从该文件读取内容,那么什么也读不到。但是 /dev/null 文件非常有用,将命令的输出重定向到它,会起到"禁止输出"的效果。语法如下:
$ command > /dev/null