这里写目录标题
概述
shell是什么
shell是我们的linux所使用的命令行解释器,他负责程序员与Linux内核的沟通,所以可以认为shell是一个程序员与linux内核沟通的编程语言
我们将命令交给shell,shell将我们的命令翻译为机器语言,交给内核,去调动内核
我们还可以将多个shell命令写成在一起,存入一个文件,该文件就叫做脚本
从2)可以看到,sh是当前系统的默认解释器,他默认链接到了bash上,说明centos默认的shell解析器是bash
shell脚本入门
内容
注意,按理说,我们的脚本文件应该是(.sh)结尾,但是shell解释器可以解释任何一个可执行文件,所以,有没有.sh结尾都可以
代码
第一行:是一个注释,指定当前脚本的shell解释器(一般是系统解释器)
之后就是一行一行的shell命令
执行:
sh是一个命令,表示执行sh文件
(使用该命令无需要求脚本文件有可执行权限)
直接执行脚本文件
执行脚本文件还有另外一种方式,也就是不使用sh,而是直接执行可执行文件
注意,使用该方式执行脚本,就要先让该脚本具有可执行权限,如上图,使用chmod +x 文件,就为该文件所有的用户赋予了可执行权限
之后可执行文件就在当前目录内,我们直接键入文件名,是无法执行的,需要在其前面加一个./,
如果不在当前目录,那么相对路径来执行的话,前面无需加./
补充
还可以使用“source”、“.”,两个命令,可以代替第一种方式中的“sh”(“bash”)
对比之前的方式:
如果使用之前的两种方式来进行脚本的执行,他会在当前的shell环境下,启动一个子shell,在子shell中执行shell脚本
而如果使用“source”或者“.”来启动shell,则会直接在当前的父shell下启动脚本,不会开启子shell
这样做的好处是:可以避免在脚本中的一些设置由于执行在子shell中,而没有对全局起作用。所以,如果脚本中有一些全局设置,最好使用“source”或者“.”来进行脚本的执行
总结:
我们可以使用ps来查看后台进程,ps -f,表示只查看与控制台相关的后台进程
第一次ps,看到后台的进程
之后键入bash
再次查看ps,可以看到,后台多了一个“bash”进程,该进程就是子shell环境(且ppid就是-bash进程,说明父进程是“-bash”)
我们之后的命令,都被执行在子shell中,
之后exit,就退出了子shell(因为我们在子shell中,所以exit之后,并没有退出系统)
但是这有什么影响呢,最大的影响就是环境变量的继承关系,下章详解
变量
环境变量
在系统中,shell的变量就包括我们平时说的环境变量
环境变量,可以分为系统变量和用户自定义变量,系统变量的变量,我们只能对其value进行操作,无法修改其变量名。
而用户自定义变量,则可以随意操作
(环境变量,就是特殊的全局变量,可以是系统定义的,也可以是用户定义的,只不过环境变量是具有最高权限的全局变量,他是被配置到系统文件内的全局变量,对当前系统的所有shell都向下兼容;而普通的全局变量就不是系统级,因为没有被配置到系统文件,他只是基于他所在的shell向下兼容子shell,有继承性但不是系统级)
而从另一个维度,变量还可以分为全局变量和局部变量,
全局变量:在当前shell以及其内部嵌入的shell都可见(嵌入多深都可见)
局部变量:只在当前shell可见(这一点与c++略有不同,c++是在当前以及其内部可见,外部不可见)
补充:
直接键入env,可以查看当前系统的全部环境变量(即系统级全局变量)
而键入set,可以看到当前定义的所有变量(包含所有的系统级全局变量(即环境变量),还有普通全局变量、以及默认的局部变量)
变量定义
规则
代码
需要注意的:
1、=前后不可有空格
2、变量默认字符串类型,无法直接计算
3、value若有空格,则加上引号
局部和全局变量
局部变量
由上图可以看出,
查找my_var
env系统的全局变量是没有的
set中有
打开一个子shell
发现在子shell中没有
退出之后,又可以访问
所以,足以证明,单纯定义一个变量的话,默认是一个局部变量
全局变量
全局变量不能直接定义,而是需要使用默认的方式定义出来局部变量之后,将其提升为全局变量,
语法:
export (已经定义出来的局部变量的变量名),此时该变量就是全局变量
但是:
虽然全局变量在子shell中是可见的,但是在子shell中更改这个全局变量,他是不会影响到父shell的,哪怕在子shell中将其提升为了全局变量,也是以子shell为基础的全局变量,父shell不可见
补充:
声明全局变量,是以当前声明的shell为基础的,不会影响外层shell,如上图,在子shell中声明一个全局变量,在父shell中访问不到
反映到脚本文件中
如果在当前shell中定义了一个变量,那么在脚本中写入“echo $new_var”,是访问不到的,因为使用./,就会新开一个子shell,而默认定义的变量是局部变量,所以在子shell中访问不到
解决方案:
1、将new_var提升为全局变量
2、将new_var的定义也写入脚本文件,这样,new_var就是子shell中的局部变量了
常量
在变量的定义之前加一个readonly
就是定义了一个常量
该常量定义之后,不可修改,不可撤销
特殊变量
补充
如果我们想要不使用./,就可以直接指向可执行文件,需要将其路径加入到系统变量PATH的后面,加入之后,就可以直接键入文件名进行执行了,而且是在任何目录下都可以执行(至于是当前用户还是所有用户,得看是使用~/.bashrc还是使用/etc/profile)
$n
$n,n为数字,表示第几个参数,(0表示脚本的名称,如果不止9个参数,则用花括号括起来)
我们可以在脚本中使用 $n来拿到调用脚本所输入的参数
补充:
如上图第一行,如果使用双引号,shell会认为n是一个变量(会想办法获取n的值,拼接进去),但是我们的意思只是想要输出字符串,所以,使用单引号括起来
$#
$#可以统计到参数的个数
代码
可以看到,echo本身输出字符串的话,是不需要加引号的(空格也可以输出),所以,引号主要用在:
1、变量的定义:value有空格
2、echo输出时,将$不再作为取值,而是字符串(使用单引号)
但是加上引号更保险,所以:
3、echo输出时, $进行取值,则加双引号
$*、 $@
这两个的作用都是获取到参数,只不过,*是将所有的参数封装成一个整体,而@则是将每个参数拿到以后,相当于放入了一个数组
$?
他可以返回最后一次命令的执行状态
运算符(在Unix中只能使用expr)
内容
因为变量默认是字符串类型,
所以,如果我们直接1+2,他的输出还是1+2:
我们可以使用expr进行表达式的计算:
但是注意,运算符要前后要加上空格,且乘号需要正斜杠(回车上面的斜杠)来进行转义
且表达式还包括逻辑表达式:
可以使用expr,输出两个操作数比较的结果,0表示结果为真,1表示结果为假
可以操作等号、>、<、>=、<=、!等
但是这样还是有点繁琐,所以,我们使用上面第一张图那种方式,如下图:
使用中括号或者双括号,将表达式引起来,此时运算符前后的空格已经无所谓了
那如何使用expr来进行变量的赋值呢:
将命令用$()括起来,就可以拿到他的执行结果了,之后就可以复制给a
或者
使用反引号,esc下面的符号将其引起来:
代码
注意点:
1、使用中括号进行计算时,对于乘除无需转义
2、echo 中,第一个sum=,这是字符串,后面是对sum的取值
条件判断
字符串之间相等/不相等
使用test,
语法:test+空格+判断式
判断式:$变量(空格)=(空格)字符串
使用echo $?来判断执行结果,如果返回0,则表示式子成立,返回1,表示式子不成立
可以看到,返回了0,表示a等于hello
或者使用中括号:
语法:【(空格)判断式(空格)】
使用echo $?来判断执行结果,如果返回0,则表示式子成立,返回1,表示式子不成立
对于不等于,将表达式的等号换成不等于即可:
可以看到,执行状态都是0,说明判断式成立,a也确实不等于1
整数比较、文件权限判断、文件类型判断
整数比较:
权限判断:
这里可以看到,这里所依据的,只是第一组的权限
文件类型判断:
多条件判断
我们知道&& 与 || 一方面是条件判断,一方面,他们也被称为开关:
对于&& ,前面为真时,才执行后面的命令
对于|| ,前面为假时,才执行后面的命令
而&& 和 || 结合着使用,就会等效于一个三元运算符,如下图:
当$a真的小于20,那么&&就会生效,就会执行第二个命令,且因为第二个命令执行了,所以||就不会生效,第三个命令就不执行。
如果大于等于20,那么第二个命令不会执行,那么||就会生效,第三个命令执行
而对于上二张图,括号内有东西,就会执行第二条命令,如果括号内没东西,就会执行第三条命令
流程控制
if
内容
代码
注意点:if前后有空格
且
then之后的代码块,是需要一个tab缩进的
tips:
按照我们上面那种写法,程序需要传入一个参数,如果我们没有参数的话,命令行会报错,如上图第二行,那么如何能保证当我们没有参数的时候,不进行任何输出,也不报错呢
如下图:
我们可以在条件判断语句的字符串后面都加上一个“x”,相当于字符拼接,这样,哪怕没有输入,等式左边也有一个x,这样等式就不会成立,代码块也不会执行
补充:
在if中的多条件,在if中,同样是使用&&,只不过是两个中括号的&&,或者||
如果想要只使用一个中括号,那么是:
在一个中括号里,就不能直接使用&& 或者 || 了,而是用-a(and)表示&&,用-o(or)表示||
双分支:
多分支:
case
内容
最后的“*)”相当于defult,其实可以理解为星号是通配符,当上面所有情况都不成立,就可以进入星号的分支
最后的esac,是case的倒序
代码
for
内容
代码
总结:
1、在(())中,代码跟c++一样,只是一些特殊变量(如占位符等)需要使用$取值,且如果用到了for循环外部的变量也需要 $,其他的不需要 $取值,基本跟c++一样
2、单纯一个【】,是条件判断,而 $【】,是得到表达式的值,所以,此处是将表达式的值赋值给sum,而【】里所用到的变量是统统需要 $来取
3、(())与【】除了最两边的空格之外,都不用在乎其内部的其他空格
(实际上,只有在用于条件判断时才会要求最两边的空格,要是只是 $[],最两边的空格也可以不要,但是为了方便记忆,统一记为他们最两边都需要空格)
4、再次强调,在(())、【】中,乘除都不需要转义
关于总结1:
内容2
代码2
花括号,表示一个序列,如上图,{1…100},表示这是一个1到100的序列,所以,使用{}再搭配增强for循环,也可以实现1到100的和
增强for搭配$*/ $@
$*/ $@不加引号
可以看到,使用这两个变量,可以获取到所有的输入参数,并遍历,而不加引号的话,$*和 $@都可以依次拿到参数
加引号
而加了引号之后,$*就变成了拿到一整个参数, $@则是将参数放入数组
while
内容
代码
应用(数组)
1、从IO读入数据,保存为数组,再遍历数组的每一个元素:
read -a number
(将数据存入一个数组)
for num in “${numbers[@]}”; do
echo " $num"
done
(增强for循环固定写法,使用双引号加取值加花括号,花括号内是数组名,之后使用@当占位符)
2、自己定义一个数组:
numbers=(1 2 3 4 5)
3、求数组的长度:
length=${#numbers[@]}
4、while中遍历数组:
counter=0
while [ $counter -lt $length ]; do
num= ${numbers[ $counter]}
echo " $num"
((counter++))
done
从控制台拿到输入
使用场景
有一些程序,并不是启动的时候就带有参数,而是会输出让用户进行输入,然后再进行数据的读取
这时,使用特殊变量获取参数就不能用了
就要使用IO设备的读入
内容
代码
总结
对于Unix:
1、计算表达式的值时,要使用expr,且符号需要转义(乘除需要转义,\加上乘除号),且运算符左右两边需要留空格
2、要想把expr计算来的值赋给其他变量,则需要在expr a + b,的外面加上一对反引号,或者$()
3、在条件判断时,使用[ ],来进行条件判断,且在[ ]里面,若不是字符串比较或者常量数值,都要加 $进行取值
4、在条件判断时,使用[ ],在[ ]的最两边要留空格
5、逻辑符(if、while等)后面要加空格
6、变量定义或者赋值时,在等号左边不需要 $,而等号右边,只要不是字符串,想要用变量的值,都要使用 $取值
7、continue、break直接用即可