认识Shell

之前我们一直在说shell,也一直在使用shell。那么shell到底是什么?

实际上,shell自身也是一个应用程序,它负责用户与操作系统之间的通信。当用户登录到shell时,系统会将一个shell程序调入内存——每个用户都有一个默认的shell,记录在/etc/passwd文件(口令文件)中。

如我(root用户)现在默认的shell为zsh,那么/etc/passwd中的记录就为:

image-20220621142626630

有些特殊用户的记录可以不是shell程序,如上图中的第二个。

zsh是mac os自带的一个shell,以高度定制化和有众多的主题闻名,Linux需要安装。

用户登录、shell执行和用户退出的过程如下:

image-20220621141543783

  1. 用户登录init进程fork/exec一个login程序,输入用户名和密码。如果验证成功,则按照口令文件为用户fork/exec默认的shell程序。
  2. shell执行:shell内置了解释器如sh,它可以解释用户输入的正确的命令。如果命令为shell内置命令,那么直接由shell完成即可;如果命令为可执行程序,那么fork/exec一个新的进程来执行该程序;如果命令为脚本文本,那么shell先解析脚本,再按照相应的命令选择执行方式。
  3. 用户退出:发送退出信号,shelllogin程序依次退出。

shell对各种命令的解析和执行依赖于shell内置的解释器sh/bash。它和python的解释器一样,可以处理脚本程序——shell提供了一种比较简单和实用的脚本语言供用户使用,我们可以把这些命令整合起来编写为脚本程序。


shell编程

shell脚本

shell是一种语言,就和python一样。一个shell脚本如下:

1
2
3
#!/bin/bash
echo "Hello,master!"
echo "Have a nice day~"

第一行的#!是一个约定的标记,它告诉系统这个脚本需要什么解释器来执行。这里指定使用bash作为解释器来执行。

如果我们指定了文件的脚本的解释器,那么使用./a.sh的方式即可运行它,但前提是给文件添加了可执行权限。

image-20220621150030043

也可以不在文件中指定解释器,而是在shell中指定解释器来执行它:bash a.sh

1
2
echo "Hello,master!"
echo "Have a nice day~"

image-20220621150241475

同理,我们也可以为python程序在文件中指定解释器,然后通过./的方式运行。(平时都是使用python3.9 b.py的方式运行)

image-20220621150635253

在文件开始指定解释器,也便于shell识别文件类型

image-20220621151707522

变量定义与使用

变量的定义

使用[变量名]=[值]的方式来创建一个变量,需要注意=两边不能由空格,比如下面的情况就是=两边加了空格的BUG。

image-20220621153143375

总之变量的定义还是太简单的。也可以定义一个数组:[数组名]={元素1 元素2 元素3 …},如nums={0 1 2 3 4}

上面的方式创建的变量是一个全局变量,它在当前的整个 Shell 进程中都有效。但是不同的shell进程间的全局变量是相互独立的。

如果想要让一个变量在其他进程中生效,就要使用export命令将它导出,那么它就在所有的子进程中也有效了。这称为“环境变量”。

此外还有局部变量,它涉及到shell中的函数,且只在函数内部生效。需要使用local加以限定,不然它也是全局变量:

image-20220621161033638

变量的使用

定义了变量后可以通过${变量名}的方式来使用变量:

image-20220621154308032

使用上面的方式来访问数组所有的元素还是有点繁琐,可以使用直接打印所有元素:

image-20220621154609899

一些常用的操作总结如下:

image-20220621155409987

变量的删除

使用unset var可以删除某个变量。

我们可以把变量设置为只读变量,使用关键字readonly。只读变量不能修改,也不能删除。

image-20220621155909307

特殊变量

shell还有一种特殊的变量:位置参数。它是一种特殊的变量,用于表示命令行参数。使用set命令可以设置命令行参数,不同参数用空格隔开:

1
2
3
4
set hello “I love bash” world
echo $1
echo $2
echo $*

image-20220621162105044

运算符

shell中的运算符和C语言的很相似,所以简要介绍:

  1. 算数运算符:++--+-\*/\%等(*%需要转义)
  2. 关系运算符:=!=<<=>>=
  3. 逻辑运算符:&&||

但是,运算符的使用却和C语言大相径庭,如下面的程序,运行结果就和我们想的不一样:

1
2
3
x=100
x=$x+1
echo $x

image-20220621163825906

是不是感觉很头疼?bash默认把变量当成了字符串处理。要进行算术、逻辑运算,需要使用特定的命令:

  1. 使用let命令处理算数运算:let x=x+1,再echo $x输出的就是101。需要注意let命令会自动使用变量的值,所以直接键入x即可,且let后面的表达式不能有空格!
  2. 使用expr命令处理算数、关系和逻辑运算,下面详细阐述。

首先expr可以当作计算器一样使用,注意运算符两边需要空格隔开:

1
2
3
4
5
expr 100 + 2 # 加
expr 100 - 2 # 减
expr 100 \* 2 # 乘
expr 100 / 2 # 除
expr 100 \% 2 # 模

然后expr可以返回运算结果,这时需要使用``将expr命令包起来:

1
2
x=`expr 100 + 2`
echo $x # 输出102

关系和逻辑运算同理。返回1表示真,返回0表示假。

分支与循环

和其他语言一样,shell也提供了循环和分支语句,包括if语句、for循环、while循环和until循环。

if条件分支

基本格式

if语句的基本格式为:

1
2
3
4
5
6
7
if [ 测试条件 ];then
执行的命令
elif [测试条件];then
执行的命令
else
执行的命令
fi

需要注意:

  1. 测试条件表达式需要用[]围起来,且它内部的前后都要留空格
  2. 代码缩进不是必要的,但可以提高可读性。
  3. 结尾的fi就是if的反写,表示if语句结束。
  4. 如果要将thenifelif写在同一行,必须在]后面加;!换行则不用。
  5. 测试条件中的运算符记得要在两边加上空格

image-20220621173615817

使用test命令

实际上,我们判断条件是否成立更常用的是test命令。test命令是shell环境中测试条件表达式的工具,基本格式为:test $value1 -op $value2。如:

1
2
3
4
5
6
echo "Are you ok?"
read ans
if test ans = y;then
echo "Glad to hear that!"
else echo "Sorry to hear that."
fi

上面的代码使用read来获取输入,送入ans变量中。

image-20220621175322721

test可以对数字、字符和文件进行测试

  1. 数字测试

    image-20220621175708258

  2. 字符测试

    image-20220621175802781

  3. 文件测试

    image-20220621175931894

需要记住常用的测试参数,包括数字测试的-eq-ne-gt-lt-ge-le;字符测试的=!=;文件测试的-e-r-w-x

for循环

for循环的结构可简单描述为for-in-done

1
2
3
for var in vars;do
循环体
done

image-20220621181045866

while循环

while循环的结构可简单描述为while-do-done结构:

1
2
3
while [ 条件表达式 ];do
循环体
done

同理,可以使用test命令来代替条件表达式:

image-20220621182119880

until循环

until循环与while循环类似,只是它在条件为假时执行循环体。它的结构可简单描述为:

1
2
3
until [ 条件表达式 ];do
循环体
done

注意将until循环的条件表达式与while循环进行对比(实际上就是取反)

image-20220621182654586

函数

前面提到过,shell也支持函数。函数的定义方式如下:

1
2
3
4
function func(){
函数体
return 返回值
}
  1. 调用的时候,可以给函数提供若干参数func arg1 arg2 …

  2. 函数要访问到这些参数,使用位置参数$1$2……即可。

  3. 函数外面要访问到函数的返回值,使用$?即可得到上一个函数返回的值。

如下面的脚本实现了函数对提供的参数求和:

1
2
3
4
5
6
7
8
9
10
#!/bin/bash
function add(){
sum=0
for num in $*;do
let sum=sum+num
done
return $sum
}
add 1 8 9
echo "加法的结果为$?"

image-20220621184050428