Shell 教程
1 Shell概论
Shell是一个用C语言编写的程序,它诞生于Unix,是我们通过命令行与Unix/Linux交互的工具。笼统地说:Shell既是一种命令语言,又是一种程序设计语言。
而Shell脚本是一种为Shell编写的脚本程序,有的时候也被称为Shell(但二者是两个完全不同的概念!),它可以直接在命令行中执行,也可以将一套逻辑组织成一个文件,方便复用。 Acwing网站提供给的AC Terminal中的命令行就可以看成是一个“Shell脚本在逐行执行”。
Unix/Linux系统中常见的shell脚本有很多:
- Bourne Shell(
/usr/bin/sh
或/bin/sh
) - Bourne Again Shell(
/bin/bash
) - C Shell(
/usr/bin/csh
) - K Shell(
/usr/bin/ksh
) - Shell for Root(
/sbin/sh
)
由于Linux系统中一般默认使用bash,而且其易用免费,所以我们接下来学习bash中的语法。
脚本示例
进入终端,新建一个hello.sh
文件,内容如下:
|
|
说明:
sh
后缀则表示该文件是Shell脚本文件。#!
告诉系统这个脚本用什么解释器来执行,后面所跟的就是你所需要用的解释器。这个一般都需要添加上,具体解释见运行。echo
指令用于字符串的输出,所以运行该文件会输出Hello,World
。
运行方式:
作为可执行文件
该方法将
hello.sh
作为可执行程序运行,由于未指定解释器,所以使用该方法第一行一定要指定解释器。
|
|
用解释器执行
该方法直接运行解释器,此时
hello.sh
作为Shell解释器的参数。此时Shell脚本就不需要指定解释器信息,则不需要第一行的注释了。1
bash hello.sh #当然也可以用缩写 sh hello.sh
输出:
2 Shell语法
2.1 注释
单行注释
每一行中
#
之后的内容均是注释。1 2 3
# 这是一行注释 echo "Hello, World" # 这也是一行注释
多行注释
Shell中的多行注释有点特别,格式为:
1 2 3 4 5
:<<EOF 第一行注释 ... 第n行注释 EOF
其中
EOF
可以替换成其他任意字符串,如:1 2 3 4 5
:<<! 第一行注释 ... 第n行注释 !
2.2 变量
定义变量
定义变量时,变量名不加美元符号
$
,同时特别需要注意的一点就是变量名与等号之间不能有空格(如果有自动打空格的习惯在这里最好克制)。shell中的变量命名同样须遵循如下规则:- 命名只能使用英文字母,数字和下划线,首个字符不能以数字开头。
- 中间不能有空格,可以使用下划线 _。
- 不能使用标点符号。
- 不能使用bash里的关键字(可用help命令查看保留关键字)。
如下:
1 2 3 4 5 6 7 8 9
# 有效的Shell变量名称 _var var123 LF_DDFHI_X # 无效的Shell变量名称 ?var 123abc echo
和现在大多数的语言一样,Shell定义变量不需要指定变量类型,如下:
1 2 3
name1="a" #单引号定义字符串 name2='a' #双引号定义字符串 name3=a #也可以不加引号,同样表示字符串
在Bash shell中,每一个变量的值都是字符串,无论你给变量赋值用的时单引号双引号还是没有使用引号,都是使用字符串的i形式存储的,这很特殊。串的i形式存储的,这很特殊。串的i形式存储的,这很特殊。串的i形式存储的,这很特殊。
使用变量
使用变量我们需要加上
$
符号或者${}
符号。花括号时可选的,主要是为了帮助解释器识别变量边界。一定要注意:只有使用变量的时候才加美元符号
$
变量的时候才加美元符号$
变量的时候才加美元符号$
1 2 3 4
name=hzf echo $name echo ${name} echo ${name}123
只读变量
我们可以使用
readOnly
或者declare
将变量设置为只读。1 2 3 4
name=hzf readOnly name #declare -r name #两种写法均可。 name=ylf #此时会报错,因为已经设置成了只读变量。
删除变量
unset
可以删除变量。1 2 3
name=hzf unset name echo $name
变量类型
根据访问权限划分可以分为:
自定义变量(局部变量)
在脚本或命令中定义,仅在当前Shell示例中有效,其他Shell启动的程序不能访问局部变量。即为子进程不能访问的变量。
环境变量(全局变量)
所有程序,包括shell启动程序,都能访问环境变量,有些程序需要环境变量来保证其正常运行。即为子进程可以访问的变量。
自定义变量转换为环境变量:
1 2 3
name=hzf#定义自定义变量 export name delcare -x name#两种方法均可
环境变量转化为自定义变量
1 2
export name=hzf#定义环境变量。 delcare +x name#改为自定义变量。
Shell字符串
字符串可以用单引号,也可以用双引号,也可以不用引号。但其中是有区别的:
使用单引号字符串,其中的变量名不会输出,但可以输出转义字符。单引号字串中不能出现单独一个的单引号(对单引号使用转义符后也不行),但可成对出现,作为字符串拼接使用。
不用引号的字符串,其中变量可以输出,但是不能输出转移字符。
使用双引号的字符串,既可以输出变量也可以输出转义字符。
示例:
1 2 3 4
name=hzf echo 123$name\n echo "123$name\n" echo '123$name\n'
输出:
字符串操作
拼接字符串
1 2 3 4 5
name=xyz #双引号字符串拼接 s1="Hello, $ {name} !” s2="Hello, "$ {name}"!" echo $s1 $s2
获取字符串长度
1 2
name=hzf echo ${#name}
提取字符串
注意,第一个字符的索引为$0$,给出的两个参数第一个为起始位置,第二个为截取长度。
1 2 3 4 5
name="Hello, World!" echo ${name} echo ${name:0:4} echo ${name:0} echo ${name:1}
文件参数变量
在执行Shell脚本时,可以向脚本传递参数。
$1
是第一个参数,$2
是第二个参数,以此类推。特殊的,$0
是文件名(包含路径)。例如:1 2 3 4 5
echo "文件名:$0" echo "第一个参数:$1" echo "第二个参数:$2" echo "$*" echo "$@"
然后我们执行的时候在后面添加参数即可。如果没有给出,那么则为空字符。
- 其他参数相关变量
参数 说明 $#` | 表文件传入的参数个数,如上例中值为2 | $*` | 所有参数构成的用空格隔开的字符串,如上例中值为 "$1 $2"
|$@` | 个参数分别用双引号括起来的字符串,如上例中值为 "$1" "$2"
|$$` | 本当前运行的进程ID | $?` | 一条命令的退出状态(注意不是stdout,而是exit code)。0表示正常退出,其他值表示错误 | $(command)` | 回 command
这条命令的stdout(可嵌套) |command` | 回 command
这条命令的stdout(不可嵌套) |
2.3 数组
数组中可以存放多个值。Bash Shell 只支持一维数组(不支持多维数组),初始化时不需要定义数组大小(与 PHP 类似)。与大部分编程语言类似,数组元素的下标由 0 开始。
定义
Shell 数组用括号来表示,元素用"空格"符号分割开,语法格式如下:
1
array_name=(value1 value2 ... valuen)
如:
1
name=(张三 "李四" '王五')
也可以直接通过定义数组中元素的值来创建数组,如:
1
name[0]="张三"
这样就创建了name数组。
访问数组元素
访问单个元素
语法格式为:
${array_name[index]}
如:
1 2 3 4
name[0]="123" name[3]="124" echo "${name[0]}" echo "${name[3]}"
2.4 expr命令与基本运算符
Shell 和其他编程语言一样,支持多种运算符,包括:
- 算数运算符
- 关系运算符
- 布尔运算符
- 字符串运算符
- 文件测试运算符
但是原生Bash不支持简单的数学运算,我们需要通过其他命令来实现,如awk
,expr
,expr
命令最常用,所以这里介绍expr
。
expr
是一款表达式计算工具,使用它能完成表达式的求值操作。格式为:
expr 表达式
表达式说明
- 用空格隔开每一项
- 用反斜杠放在shell特定的字符前面(发现表达式运行错误时,可以试试转义)
- 对包含空格和其他特殊字符的字符串要用引号括起来
expr
会在stdout
中输出结果。如果为逻辑关系表达式,则结果为真,stdout
为1,否则为0。expr
的exit code
:如果为逻辑关系表达式,则结果为真,exit code
为0,否则为1。- 完整的表达式要被 ` ` 包含,注意这个字符不是常用的单引号,而是反引号,代表执行该命令。通常在 Esc 键下边。
算数表达式
下表列出了常用的算术运算符,假定变量 a 为 10,变量 b 为 20:
运算符 说明 举例 | 法 | expr $a + $b` 结果为 30。 | | 法 | expr $a - $b` 结果为 -10。 | | 法 | expr $a * $b` 结果为 200。 | | 法 | expr $b / $a` 结果为 2。 | | 余 | expr $b % $a` 结果为 0。 | | 值 | a=$b` 将把变量 b 的值赋给 a。 | font color=“red”> | 等。用于比较两个数字,相同则返回 true。 | [ $a $b ]` 返回 false。 | = | 相等。用于比较两个数字,不相同则返回 true。 | [ $a != $b ]` 返回 true。 | **注意:**条件表达式要放在方括号之间,并且要有空格,例如:
[$a<font color="red">$b]
是错误的,必须写成[ $a </font> $b ]
。注意乘号等特殊符号需要转义。()
可表优先级,但同样需要反斜杠转移。实例:
1 2 3 4 5 6 7 8 9 10 11 12
a=10 b=20 echo "a + b = `expr $a + $b`" echo "a - b = `expr $a - $b`" echo "a * b = `expr $a \* $b`" echo "a / b = `expr $a / $b`" echo "a % b = `expr $a % $b`" echo "a <font color="red"> b = `expr [$a </font> $b]`" echo "a != b = `expr [$a != $b]`" a=$b echo "$a"
关系运算符
Shell支持正常的关系比较运算符,即
<,<=,>,>=
等。其会返回01代表结果。实例:
1 2 3 4 5
a=10 b=20 echo "a < b : `expr $a \< $b`" #需要转义 echo "a > b : `expr $a '>' $b`" #也可以用引号括起来。 echo "a >= b : `expr $a '>=' $b`"
同时,Shell也给出了自己特定的比较命令,如下表:
运算符 | 说明 | 举例 |
---|---|---|
-eq | 检测两个数是否相等,相等返回 true。 | [ $a -eq $b ] 返回 false。 |
-ne | 检测两个数是否不相等,不相等返回 true。 | [ $a -ne $b ] 返回 true。 |
-gt | 检测左边的数是否大于右边的,如果是,则返回 true。 | [ $a -gt $b ] 返回 false。 |
-lt | 检测左边的数是否小于右边的,如果是,则返回 true。 | [ $a -lt $b ] 返回 true。 |
-ge | 检测左边的数是否大于等于右边的,如果是,则返回 true。 | [ $a -ge $b ] 返回 false。 |
-le | 检测左边的数是否小于等于右边的,如果是,则返回 true。 | [ $a -le $b ] 返回 true。 |
实例(注:if…then是条件语句,之后会讲解):
|
|
字符串表达式
即结合
expr
命令实现对字符串的操作,常见有以下:length str
:返回str的长度。index str charSet
:返回字符集charSet中任意一个字符在str中最前面字符的位置。下标从1开始,如果不存在,则返回0。substr str st len
:截取字符串str,从st位置开始,长度最大为len
的子串。如果截取不成功,则返回空字符串。
实例:
1 2 3 4
str="Hello,World!" echo `expr length "$str"` #`不是单引号,而是反引号,代表执行该命令。 echo `expr index "$str" llo` echo `expr substr "$str" 1 4`
文件测试运算符
文件测试运算符用于检测 Unix 文件的各种属性。
属性检测描述如下:
操作符 说明 举例 b file | 测文件是否是块设备文件,如果是,则返回 true。 | -b $file ] 返回 false。 | c file | 测文件是否是字符设备文件,如果是,则返回 true。 | -c $file ] 返回 false。 | d file | 测文件是否是目录,如果是,则返回 true。 | -d $file ] 返回 false。 | f file | 测文件是否是普通文件(既不是目录,也不是设备文件),如果是,则返回 true。 | -f $file ] 返回 true。 | g file | 测文件是否设置了 SGID 位,如果是,则返回 true。 | -g $file ] 返回 false。 | k file | 测文件是否设置了粘着位(Sticky Bit),如果是,则返回 true。 | -k $file ] 返回 false。 | p file | 测文件是否是有名管道,如果是,则返回 true。 | -p $file ] 返回 false。 | u file | 测文件是否设置了 SUID 位,如果是,则返回 true。 | -u $file ] 返回 false。 | r file | 测文件是否可读,如果是,则返回 true。 | -r $file ] 返回 true。 | w file | 测文件是否可写,如果是,则返回 true。 | -w $file ] 返回 true。 | x file | 测文件是否可执行,如果是,则返回 true。 | -x $file ] 返回 true。 | s file | 测文件是否为空(文件大小是否大于0),不为空返回 true。 | -s $file ] 返回 true。 | e file | 测文件(包括目录)是否存在,如果是,则返回 true。 | -e $file ] 返回 true。 |
2.5 read命令
read命令可用于从标准输入中读取单行数据,当读到文件结束符时,exit code
为1,否则为0。
参数说明:
-p
:后面可以接提示信息。-t
:后面跟秒数,定义输入字符的等待时间,超过等待时间后会自动忽略此命令
实例:
|
|
2.6 echo命令
echo命令用于字符串的输出,在前面的过程中我们已经接触到了,其命令格式为:
|
|
我们可以用echo实现更复杂的输出格式控制。
显示普通字符串
1
echo "Hello, World!"
这种情况下,我们不加双引号也是可以的。
显示转义字符
1
echo "\"Hello,World!\""
同样,这里的双引号也可以省略。
显示变量
1 2
name=hzf echo "My name is $name"
显示换行等特殊字符
注意,这些字符都需要通过
-e
命令开启转义才能起作用的,如:\\ \a \b \c \d \e \f \n \r \t \v
这些是要在有-e
的时候才能起作用, 其他时候的转义是不用- e
也能转义的。1 2
echo -e "Hello\n" #-e开启转义。 echo "World!"
显示结果定向至文件
1
echo "Hello, World!" > output.txt
原样输出字符串
前面有提及,如果想原样输出,不进行转义或者取变量,用单引号。
1 2
name=hzf echo '$name'
显示命令执行结果
使用反引号。
1 2
echo `date` echo `ls`
2.7 printf命令
Shell的printf命令和C语言中的printf差不多,用于格式化输出。该命令不会像echo命令一样自动添加换行。其语法为:
|
|
其中format-string为格式控制字符串,[args]为参数列表。
实例:
|
|
- 命令格式指示符
符号 说明 c | SCII字符.显示相对应参数的第一个字符 | d,%i | 进制整数 | E | 点格式([-d].precisionE [+-dd]) | e | 点格式([-d].precisione [+-dd]) | g | e或%f转换,看哪一个较短,则删除结尾的零 | G | E或%f转换,看哪一个较短,则删除结尾的零 | s | 符串 | u | 带正负号的十进制值 | x | 带正负号的十六进制.使用a至f表示10至15 | % | 面意义的% | X | 带正负号的十六进制.使用A至F表示10至15 |
2.8 test命令与判断符号[]
在命令行中输入man test
,即可查看test
命令的用法。
test
命令可以用于判断文件类型,以及对变量做比较。test
命令用exit code
返回结果,而不是使用stdout
。0表示真,非0表示假。这里可以通过echo $?
来输出上一条命令的结果。
- 文件判断
命令格式:
test 测试参数 filename
其中常用测试参数如下表:
测试参数 | 代表意义 |
---|---|
-e | 文件是否存在 |
-f | 是否为文件 |
-d | 是否为目录 |
-r | 文件是否可读 |
-w | 文件是否可写 |
-x | 文件是否可执行 |
-s | 是否为非空文件 |
测试 | |
- 整数之间的比较
命令格式:
test $a 关系运算符 $b
其中关系运算符为上文所提及的,这里不作列举。 - 字符串比较 相关操作如表所示:
测试参数 | 代表意义 |
---|---|
test -z str | 判断str是否为空,如果为空,返回true,否则false |
test -n str | 判断str是否为非空,如果非空,返回true,否则false.其中-n可以省略 |
test str1 == str2 | 判断str1是否等于str2 |
test str1 != str2 | 判断str1是否不等于str2 |
` |
- 多重条件判定 即判断多个条件是否符合要求,可以嵌套多层。具体操作图表所示:
测试参数 | 代表意义 |
---|---|
-a | 两条件是否同时成立 |
-o | 两条件是否至少一个成立 |
! | 取反。对返回结果取反 |
- 判断符号
[]
和test
命令用法几乎一模一样,只是将需要判断的内容放入括号中。 更常用于if
语句中,且[[]]
是[]
的加强版,支持的特性也更多。 值得注意的是一些特性:[]
内的每一项都必须用空格隔开;中括号内的变量,最好用双引号括起来;中括号内的常数,最好用单或双引号括起来。
2.9 判断语句
if
语句 和python的语法有点像,命令格式如下:
|
|
case...esac
形式 命令格式如下:
|
|
2.10 循环语句
for...in...do...done
语句 命令格式:
|
|
循环规则为:从左到右遍历,当变量值为列表时,则一次遍历完列表。 实例1:输出a 2 cc,每个元素一行:
|
|
for((...;...;...)) do...done
语句 命令格式:
|
|
测试:输出1-10。
|
|
while...do...done
语句 命令格式:
|
|
实例:文件结束符为ctrl+d
,输入文件结束符后read
指令返回false。
|
|
unti...do..done
语句 和while语句相同,while能实现的脚本until同样可以实现。但区别是until循环的退出状态为0,与while刚好相反,即while循环在条件为真时继续执行循环而until在条件为假时继续执行循环。break
语句- 跳出当前的一层循环,break不能跳出case语句。
continue
语句 跳出当前循环。- 死循环的处理方式
如果Terminal可以打开该程序,则输入
Ctrl+c
即可。否则可以直接关闭进程:使用top
命令找到该进程的PID;输入kill -9 PID
即可关掉此进程。
2.11 函数
定义格式:
|
|
说明:
- 可以带function func()定义,也可以直接fun()定义,不带任何参数。
- 参数返回,可以显示加:return返回,如果不加,将以最后一条命令运行结果作为返回值。return后跟数值n(0-255,不能超过该范围)。 其中函数返回值通过
$?
获取。 - 函数体声明局部变量可以用local关键字声明。
实例:
|
|
- 函数参数
在shell中,调用函数可以向其传递参数。在函数体内部,通过
$n
的形式来获取参数的值。例如,$1
表示第一个参数,$2
表示第二个参数。这个规则和上文说的文件参数相同。也是在执行的后面添加参数。
2.12 exit命令
exit
命令用来退出当前的shell进程,并返回一个退出状态(0-255,只有0表示成功,其他都表示失败);使用$?
即可接收这个退出状态。
exit
命令可以接收一个整数值作为参数,代表退出状态。如果不指定,默认值为0。
2.13 文件重定向
每个进程默认打开3个文件描述符:
stdin
:标准输入,从命令行读取数据,文件描述符为0。stdout
:标准输出,向命令行输出数据,文件描述符为1。stderr
:标准错误输出,向命令行输出数据,文件描述符为2。 可以用文件重定向将这三个文件重定向到其他文件中去。重定向命令列表如下:
命令 | 说明 |
---|---|
command > file | 将输出重定向到 file。 |
command < file | 将输入重定向到 file。 |
command >> file | 将输出以追加的方式重定向到 file。 |
n > file | 将文件描述符为 n 的文件重定向到 file。 |
n >> file | 将文件描述符为 n 的文件以追加的方式重定向到 file。 |
n >& m | 将输出文件 m 和 n 合并。 |
n <& m | 将输入文件 m 和 n 合并。 |
<< tag | 将开始标记 tag 和结束标记 tag 之间的内容作为输入。 |
- 输入输出重定向实例
|
|
- 同时重定向stdin和stdout
创建main.sh编写脚本:
|
|
创建input.txt,填写内容为:
|
|
2.14 引入外部脚本
类似C/C++
中的include操作,bash
也可以引入其他文件中的代码。
语法格式为:
|
|