文章首发公众号:Java架构师联盟
*基础引入*
执行sh01.sh
*文件流和重定向*
文件流:我们大家应该对一个东西不陌生,编程,那编程这个东西简化一下应该怎么分步骤呢?赵本山老师说过,是不是大象关进冰箱分几步 啊,三步是不是,打开冰箱,大象放进去,关上冰箱,那好,其实我们的编程也就是这样,无非就是一个input一个output,中间的实现过程是不是就可以按照你的习惯自己去书写了啊,对吧,所以文件流或者说IO流其实就是这样的一个概念
那好,我们为什么要再linux中强调这个东西,你们之前学过linux,那你们应该知道linux区别于windows 的一个点是不是就是文件至上 啊,在linux的认知中的,一切皆文件,可能你们用的是图形化界面的linux,那大家可以看一下老师的这一个,是不是里面所有的东西都是文件啊,就是以文件的形式存在的。
重定向:(重定向不是命令)
程序自身都有IO
0:标准输入
1:标准输出
2:错误输出
输出重定向
控制程序IO位置:本身我们的文件是不是一对一的,一一对应,一个输入就是完整的输入到一个输出中啊,然后呢,我们linux干了一件什么事 啊,他找了一个第三者(fd),中间人的意思啊,然后呢,我们的输入连接到第三者,然后由第三者将数据进行相应的转发,转发到我们想要他去的位置
cd /prof/$$/fd
Ps -ef 查看进程 ps -ef | grep 13175
举个例子,ls这个命令大家都很熟悉了对吧,我反正拿到一个linux系统,就跟拿到数据库管他怎么样,先来一个select一样,我就先来一个ls,然后再去做别的,那好,那我们知道,ls是不是就是输出显示啊,ll命令就是ls -l的缩写,那他是将当前目录的内容展现到控制台也就是我们的页面上进行相应的显示,对不对,那好,我们前面也说过012这三个属性对吧,对应输出的是不是就是我们的1啊,那我们可不可以通过1这个属性将我们的这个输出位置进行相应的改变啊,好,我们来看一下
ls 1> aaa 在这其中有两个点,
一个是覆盖(ls /tmp 1> aaa)
一个是追加(ls /tmp 1>>aaa)
那如果我们换一下呢 ls / /tmp 1> aaa 这个命令是不是这两个文件夹中的目录都进入我的aaa中了啊,那他的顺序是怎么样的啊,那好,这个地方分成两种情况讨论,
一同级(按照字典序排列),
而上下级(按照上下级排列,也就是我们的目录深度,为什么,因为我们的linux是不是一个目录树的结构 啊 tree -L n)
组合使用
新建 mkdir out
ls / /aabb 1>ls01.out 2>ls02.out(aabb不存在)
那这样写是不是很麻烦啊,可以不可以联系结合到一起啊
ls / /aabb 1>ls03.out 2>ls03.out
按照常理来说是不是从左向右执行,对吧,但是这里为什么没有我们的错误输出呢?好,我们来看一下
ls / /aabb 或者ls /aabb /
我们发现,无论怎么处理,是不是最先输出的是我们的错误日志啊,那也就是说,我的命令
ls / /aabb 1>ls03.out 2>ls03.out
先将错误日志写入到ls03.out中啊,然后当我们再次写入标准输出的时候,会被覆盖掉啊,
ls / /aabb 1>>ls04.out 2>>ls04.out
这样是不是还是很麻烦啊,人比较懒,我不想重写两遍,那我该怎么处理呢?
这里就有一个新的符号&
ls / /aabb 2>&1 1>ls05.out
这里有问题啊,是不是啪啪打脸 啊,我说这里可以用一个&符号,但是为什么会有问题啊?
大家想一下这条命令,我是不是按照顺序进行,是不是有一个2,一个1 啊,那2指向1了,但是1是不是指向控制台啊,也就是说我的错误日志是不是还是在控制台上进行展示啊,然后这个进行完了之后,1又重新指向了ls05.out,那我们这地方考虑一下,换个顺序怎么样?
ls / /aabb 1>ls06.out 2>&1
这样是不是就可以了啊。但是啊,这里注意啊(符号左边不能有空格,,但是右边没问题,是可以的)
ls / /aabb >& ls07.out 和ls / /aabb >& ls07.out这是两个特殊写法 是最简单的特殊写法,记住就可以了)
输入重定向
之前我们写的都是输出重定向,然后接下来就是我们的输入重定向
read aaa <<< "hello world"
read aaa<<AABB(对换行符特别敏感,当遇到换行之后,后面的内容全部舍弃,当遇到AABB的时候会自动停止)
cat 0</etc/init/tab
*shell变量*
exec ls---展示当前目录并退出
exec 8<> /dev/tcp/www.baidu.com/80
[root@linuxStudy fd]# echo -e "GET / HTTP/1.0\n" 1>&8
[root@linuxStudy fd]# cat 0<&8
注意点:我们刚才是不是echo发送了一个数据啊,但是我们再次cat的时候他不会发送消息了,为什么,因为我们用的是http1.0的版本,它是一个只会发送一次请求的短连接,我们如果要再次获取数据的话需要再次重新定义,那我们创建了这么多的链接我们该如何删除啊,可以rm -rf吗?不行,我们只能exit退出,没办法,他就是这么任性
变量
那好,我们现在要来定义变量
本地:
当前shell拥有,生命周期随shell进行改变
假设我们定义一个变量aaa=bawei
Echo $aaa 是不是可以展现啊,
但是如果我重新开一个链接,是不是就不行了啊
如果是创建本地函数呢,同样的clear
qwe没有输出值,为啥 啊,是不是我们就没有给他赋值 啊,然后呢qwe=123,我给他赋值,然后我们在使用bawei看一下是不是就有了 啊
局部:
这一个我们的区别就是我们直接bawei可以取得bbb的值,但是我们直接echo $bbb,就不能获取我们的值
位置
但是Vi sh03.sh
Source sh03.sh a b c d e f g h 执行的话没有办法执行啊,怎么办呢,这个地方,当发生重复的时候,我们可以将其括起来echo ${12}
Abc=hello
Echo $abcverygood(不行)======>echo ${abc}verygood
awk -F':' '{print $1}' passwd
这个地方的注意点就是这个{}需不需要单引号,当我们要将其传给bash的话不需要单引号,传给命令的话需要单引号
再说一个简单的例子----数组
[root@linuxStudy sh]# msb=(1,2,3)
[root@linuxStudy sh]# echo $msb
1,2,3
[root@linuxStudy sh]# echo ${msb[1]}
[root@linuxStudy sh]# echo $msb[1]
1,2,3[1]
我们除了可以读取整个数组之外,是不是无法根据下标来进行读取啊,那这个地方怎么办啊,这里就涉及到一个东西,就到分隔符,我们看一下,刚才我们定义的数组是不是通过逗号进行分割的啊,但是linux中,他对于分隔符比较敏感的是不是空格啊,那我们用空格尝试一下
[root@linuxStudy sh]# msb=(1 2 3)
[root@linuxStudy sh]# echo ${msb[1]}
2
特殊
$BASHPID
执行source sh03.sh a b c d e f g h i j k l m n
$* 与 $@ 区别:
·
相同点:都是引用所有参数。
·
·
不同点:只有在双引号中体现出来。假设在脚本运行时写了三个参数 1、2、3,,则 " * " 等价于 "1 2 3"(传递了一个参数),而 "@" 等价于 "1" "2" "3"(传递了三个参数)。
·
注意点:管道
[root@linuxStudy sh]# a=9
[root@linuxStudy sh]# a=22 | echo grep
grep
[root@linuxStudy sh]# echo $a
9
为什么呢?我们不是设置了a=22 啊,为什么还是现实为9 啊,这就是管道 的一个原理
管道就是把左边命令的输出作为右边命令的输入,这是他最基础的认知 ,那这边,我们的bash和管道是干嘛了呢,他是在左边和右边各启动一个bash,当左边的bash执行完毕后,是不是去执行右边的bash 啊 ,但是我们说了,变量的值是不是执行当前的shell的生命周期啊,对吧,那这样的话当这个bash结束之后是不是他的生命周期就结束了 啊,那他跟我们外面的这个bash有影响吗?没有的,那这个地方有什么用处呢?
这个就是我们的环境变量的问题?
环境
我们之前用过jdk吧,那我们知道jdk配置完成之后要在环境变量中定义
那这个地方就涉及到一个问题,就是bash是如何创建的,bash在管道中的创建,子bash会继承父bash的值,但是在其他里面就不可以了
但是在其他的里面呢,
这里是不是就看出来了,在管道中,是不是无论是否export,我的数值都是可以调用的啊,因为他直接继承父bash的值,但是呢,当我们将其写入到文件中的时候,如果没有export,是不是没有办法找到 啊,因为他无法继承父bash,而且,这里是不能跨bash进行继承,例如我们在创建一个连接就无法调用我这个bash的值
*逻辑判断*
我们刚才讲过一个export,那好,这里在讲解一下
[root@linuxStudy sh]# vi sh05.sh
[root@linuxStudy sh]# chmod +x sh05.sh
[root@linuxStudy sh]# ls
a.out passwd sh01.sh sh02.sh sh04.sh sh05.sh
[root@linuxStudy sh]# ./sh05.sh
.............
99
[root@linuxStudy sh]# a=88
[root@linuxStudy sh]# ./sh05.sh
.............
99
[root@linuxStudy sh]# export a
[root@linuxStudy sh]# ./sh05.sh
88
.............
99
export的一个特性,叫做导出非共享,
[root@linuxStudy sh]# echo $a
88
fork():创建子进程,一个是管道(创建子进程完整复制了父进程的值,可以直接访问),一个是文件(需要export,如果不export的话,无法获取父的值),
Copy on Write
引用和命令替换
单引号:强制引用
双引号:弱引用,不会产生覆盖
[root@linuxStudy sh]# a=99
[root@linuxStudy sh]# echo "$a"
99
[root@linuxStudy sh]# echo '$a'
$a
花括号扩展不会被引用
[root@linuxStudy sh]# cp /etc/inittab /etc/passwd ./
cp:是否覆盖"./passwd"? y
[root@linuxStudy sh]# cp /etc/{inittab,passwd} ./
cp:是否覆盖"./inittab"? y
cp:是否覆盖"./passwd"? y
[root@linuxStudy sh]# cp "/etc/{inittab,passwd}" ./
cp: 无法获取"/etc/{inittab,passwd}" 的文件状态(stat): 没有那
个文件或目录
为什么呢?因为{}外如果添加引用符号,他会默认的将引号之中的路径当做一个文件,这是肯定不行的
Bash执行前删除引用以及反向引用
退出状态和逻辑判断
退出状态就是我之前写的echo $?
逻辑判断 && (一假全假)||(一真全真 )
*表达式:*
Let命令:算术表达式
主要使用Let c=$a+$b和C=$(($a +$b))(注:可以不加$)
条件表达式
Test
简化的条件表达式
[]
*接下来我们要开始书写shell脚本*
1、
2、判断是否有参数
[ ! $# -eq 1 ] && echo "args error!!!" && exit 2
3、判断用户是否存在
id $1 >&/dev/null && echo "user exist" && exit 3
为什么啊。因为id这个命令大家可以看一下 id 用户名 可以进行相应的查看
4、
`#! /bin/bash`
[ ! $# -eq 1 ] && echo "args error!!!" && exit 2
id $1 >&/dev/null && echo "user exist" && exit 3
useradd $1 >&/dev/null && echo $1 | passwd --stdin $1 >&/dev/null && echo "user add success" && exit 4
echo "i don't know ,user add fail" && exit 5
*流程控制*
If
Help if.查看帮助文档
If test 3 -gt 2; then echo ok ;fi
If test 3 -gt 2;then echo ok;else echo error ;fi
If test 3 -eq 4;then echo ok;elif test 3 -eq 3 ;then echo 3;fi
While
While ls /share;
do
echo ok;
rm -rf /share;
done
注:while后面必须跟一个命令
For
那说起for来,我们大家应该都不陌生,它有两种不同的方案,是不是普通的for循环和增强for循环啊
For((a=1;a<=5;a++));do echo $a;done
For i in 1 2 3 4 5 ;do echo $i;((i++));done
这里面补充一个简单的seq n它就是打开一个n个数列的循环