shell实践
shell实践
父子shell
父shell:我们在登录某个虚拟机控制器终端的时候(连接某一个linux虚拟机)时,默认启动的交互式shell,然后等待命令输入。
ps命令参数,是否有横杠的参数作用是不一样的
-f 显示UID,PPID,C与STIME栏位。
f 用ASCII字符显示树状结构,表达进程间的相互关系。
-e 此参数的效果和指定"A"参数相同。
e 列出进程时,显示每个进程所使用的环境变量。
案例
1.于超老师登录自己的虚拟机
[yuchao@yumac Luffy_linux]$sshpyyu
Last login: Sat Sep 26 21:06:16 2020 from 221.218.215.96
[root@chaogelinux ~]#
2.一条命令,查看进程的父子关系
[root@chaogelinux ~]# ps --forest -ef
# 观察如下信息,可以清晰看出父子关系
root 1830 1 0 9月25 ? 00:00:00 /usr/sbin/sshd -D
root 15105 1830 0 21:07 ? 00:00:00 \_ sshd: root@pts/0
root 15107 15105 0 21:07 pts/0 00:00:00 \_ -bash
root 16074 15107 0 21:11 pts/0 00:00:00 \_ ps --forest -ef
子shell
当在CLI的提示符下,输入/bin/bash指令,或者其他bash指令,会创建一个新的shell程序,这就被称之为子shell(child shell)
子shell同样的拥有CLI提示符,可以输入命令。
使用如下命令,超哥教你如何查看父子的诞生
[root@chaogelinux ~]# ps -f
UID PID PPID C STIME TTY TIME CMD
root 15107 15105 0 21:07 pts/0 00:00:00 -bash
root 16893 15107 0 21:17 pts/0 00:00:00 ps -f
当前父shell 15107
[root@chaogelinux ~]# ps -f
UID PID PPID C STIME TTY TIME CMD
root 15107 15105 0 21:07 pts/0 00:00:00 -bash
root 16966 15107 1 21:18 pts/0 00:00:00 bash
root 17144 16966 0 21:18 pts/0 00:00:00 ps -f
第一次父bash的pid,15107
第二次执行bash,子shell的pid, 16966,ppid是15107,由此看出是子shell
输入bash指令之后,一个子shell就产生了。
- 第一个ps -ef命令是在父shell里执行的
- 第二个ps -ef是在子shell里执行的。
子shell生成时,父进程的部分环境变量被复制到子shell里,这个后面于超老师在给大家说。
多个子shell
1.当前shell关系
root 1830 1 0 9月25 ? 00:00:00 /usr/sbin/sshd -D
root 22206 1830 0 09:23 ? 00:00:00 \_ sshd: root@pts/2
root 22208 22206 0 09:23 pts/2 00:00:00 \_ -bash
root 22757 22208 0 09:24 pts/2 00:00:00 \_ ps -ef --forest
2.执行多个bash,开启多个子shell
连续输入四次bash之后
root 1830 1 0 9月25 ? 00:00:00 /usr/sbin/sshd -D
root 22206 1830 0 09:23 ? 00:00:00 \_ sshd: root@pts/2
root 22208 22206 0 09:23 pts/2 00:00:00 \_ -bash
root 22844 22208 0 09:25 pts/2 00:00:00 \_ bash
root 23017 22844 0 09:25 pts/2 00:00:00 \_ bash
root 23190 23017 1 09:25 pts/2 00:00:00 \_ bash
root 23363 23190 1 09:25 pts/2 00:00:00 \_ bash
root 23537 23363 0 09:25 pts/2 00:00:00 \_ ps -ef --forest
退出子shell
exit 可以退出子shell,也可以退出当前的虚拟控制台终端。
只需要在父shell里输入exit就可以退出了。
进程列表(创建子shell)
若是超哥想要执行一系列的命令,可以通过命令列表来实现,如下
[root@chaogelinux ~]# pwd;ls;cd /opt;pwd;ls
这样的写法,命令的确会依次执行,但是这并不是【进程列表】
必须如下写法才是
[root@chaogelinux opt]# (cd ~;pwd;ls ;cd /tmp;pwd;ls)
命令列表,必须写入括号里,进程列表是生成子shell去执行对应的命令。
进程列表的语法就是如上
(command1;command2)
检测子shell
通过一个环境变量,检查子shell是否存在
[root@chaogelinux opt]# echo $BASH_SUBSHELL
0
结尾为0则没有子shell,非0就是有子shell
非子shell的执行命令
[root@chaogelinux opt]# cd ~;pwd;ls ;cd /tmp;pwd;ls;echo $BASH_SUBSHELL
能够看到结果为0,表示是父shell直接执行
子shell的执行形式
[root@chaogelinux tmp]# (cd ~;pwd;ls ;cd /tmp;pwd;ls;echo $BASH_SUBSHELL)
看到结果不为0了,表示是在子shell里运行了
子shell嵌套
刚才我们是用了一个括号,开启子shell,现在可以开启多个子shell
[root@chaogelinux tmp]# (pwd;echo $BASH_SUBSHELL)
/tmp
1
细心的同学观察下,超哥这里是怎么改动的
[root@chaogelinux tmp]# (pwd;(echo $BASH_SUBSHELL))
/tmp
2
观察到环境变量的数字已经发生了变化,其实是通过两个括号,创建了2个子shell。
shell脚本开发里,经常会使用子shell进行多进程处理。
后台执行与子shell
在我们日常shell命令执行里,很多地方都有用到子shell,如进程列表、协程、管道等。
一个高效的子shell用法是和后台结合使用。
使用sleep
命令
sleep 3
sleep将你会话暂停3秒,然后返回shell
不希望sleep卡住会话,将它放在后台
[root@chaogelinux tmp]# sleep 300&
[1] 27520
显示的是后台作业的id号(background job 1),以及后台进程的PID(27520)
[root@chaogelinux tmp]# ps -f
UID PID PPID C STIME TTY TIME CMD
root 24734 24731 0 09:36 pts/2 00:00:00 -bash
root 27520 24734 0 09:57 pts/2 00:00:00 sleep 300
root 27557 24734 0 09:57 pts/2 00:00:00 ps -f
我们发现是基于bash父shell的24734生成的27520
jobs命令
[root@chaogelinux tmp]# jobs
[1]+ 运行中 sleep 300 &
[root@chaogelinux tmp]#
jobs命令可以显示后台作业信息
[root@chaogelinux tmp]# jobs -l
[1]+ 27520 运行中 sleep 300 &
显示pid信息
一旦后台jobs完成,就会显示出结束状态。
[1]+ 完成 sleep 300
进程列表放入后台
先看一个事例
[root@chaogelinux tmp]# (sleep 2;echo $BASH_SUBSHELL;sleep 2)
1
这个案例,会有2秒的暂停,显示数字,表示只有一个子shell,然后又暂停了2秒,最终返回提示符
在看下进程列表,结合后台模式的效果
[root@chaogelinux tmp]# (sleep 2;echo $BASH_SUBSHELL;sleep 2)&
[1] 29308
[root@chaogelinux tmp]# 1
[1]+ 完成 ( sleep 2; echo $BASH_SUBSHELL; sleep 2 )
这样的子shell用法,目的是在于,开辟子shell处理繁琐的工作,同时保证不会让子shell限制终端的使用。
我们会在后面学习结合tar命令进行后台压缩的实用案例。
# 这里注意,tar压缩的时候,会有报警信息,原因是绝对路径的问题,可以忽略,是系统为了保护文件的操作
[root@chaogelinux tmp]# (tar -cf Tmp.tar /tmp;tar -Pcf Home.tar /home)&
[1] 29931
此时可以通过命令检查,父子shell的执行方式
[root@chaogelinux tmp]# ps -ef --forest
root 1830 1 0 9月25 ? 00:00:00 /usr/sbin/sshd -D
root 24731 1830 0 09:36 ? 00:00:00 \_ sshd: root@pts/2
root 24734 24731 0 09:36 pts/2 00:00:00 \_ -bash
root 30337 24734 0 10:28 pts/2 00:00:00 \_ -bash
root 30341 30337 16 10:28 pts/2 00:00:02 | \_ tar -cf Tmp.tar /tmp
root 30452 24734 1 10:28 pts/2 00:00:00 \_ ps -ef --forest
协程与子shell
协程也是在后台创建子shell,然后在子shell中执行命令
# 使用coproc命令
[root@chaogelinux tmp]# coproc sleep 10
[1] 31253
[root@chaogelinux tmp]#
[root@chaogelinux tmp]#
[root@chaogelinux tmp]#
[1]+ 完成 coproc COPROC sleep 10
[root@chaogelinux tmp]# ps -ef --forest
root 1830 1 0 9月25 ? 00:00:00 /usr/sbin/sshd -D
root 24731 1830 0 09:36 ? 00:00:00 \_ sshd: root@pts/2
root 24734 24731 0 09:36 pts/2 00:00:00 \_ -bash
root 31404 24734 0 10:34 pts/2 00:00:00 \_ sleep 10
root 31440 24734 0 10:34 pts/2 00:00:00 \_ ps -ef --forest
协程是将命令放在后台执行,也可以通过jobs命令看到
[root@chaogelinux tmp]# coproc sleep 10
[1] 31610
[root@chaogelinux tmp]# jobs
[1]+ 运行中 coproc COPROC sleep 10 &
协程给任务起了个名字,COPROC
,也可以自己指定名字
[root@chaogelinux tmp]# coproc Chao_ge_job { sleep 10; }
[1] 31840
[root@chaogelinux tmp]# jobs
[1]+ 运行中 coproc Chao_ge_job { sleep 10; } &
通过这种写法,协程的名字指定了,注意扩展语法{ 任务 }
花括号里面的空格。
内建命令
这里超哥曾经在15年在上海面试运维的时候,面试官问过这个问题:你知道linux内置命令,外置命令吗?
答:
> >
内置命令:在系统启动时就加载入内存,常驻内存,执行效率更高,但是占用资源
外置命令:用户需要从硬盘中读取程序文件,再读入内存加载
外部命令
外部命令也称作文件系统命令,存在于bash shell之外的程序,一般存在的路径是
/bin
/usr/bin
/sbin/
/usr/sbin
例如ps就是外部命令
[root@chaogelinux tmp]# which ps
/usr/bin/ps
[root@chaogelinux tmp]# type -a ps
ps 是 /usr/bin/ps
[root@chaogelinux tmp]# ls -l /usr/bin/ps
-rwxr-xr-x 1 root root 100112 10月 19 2019 /usr/bin/ps
外部命令在执行时,会创建一个子进程,我们还是可以通过ps命令查看,进程id号
[root@chaogelinux tmp]# ps -f
UID PID PPID C STIME TTY TIME CMD
root 750 24734 0 10:45 pts/2 00:00:00 ps -f
root 24734 24731 0 09:36 pts/2 00:00:00 -bash
ps命令是父bash,创建新的进程750执行的。
无论是子进程,还是子shell,我们都可以通过发送signaling信号
和其沟通。
内置命令
内置命令和外置命令的区别,就在于是否会创建子进程去执行
。
内置命令和shell编译为一体,是shell的一部分,不需要外部程序文件执行。
还是可以通过type
了解命令是否是内建的。
[root@chaogelinux tmp]# type cd
cd 是 shell 内嵌
[root@chaogelinux tmp]# type exit
exit 是 shell 内嵌
因为内置命令不需要衍生子进程执行,也不用打开程序文件,执行速度更快,效率也更高。
查看内置命令
# 该命令列出所有的bash shell可以用的内置命令
[root@web01 ~ 11:33:33]$compgen -b
查看外置命令
除了以上的内置命令,日常使用的大部分命令都是外部命令啦。
可以用type验证下即可。
Linux环境变量
变量的概念,超哥前面已经给大家介绍了。
Linux环境变量可以提升shell使用体验,很多程序和脚本通过环境变量来获取系统信息,存储的临时数据和配置信息。
什么是环境变量
environment variable
的作用是存储有关shell会话和工作环境的信息,因此也称之为环境变量。
它允许你在内存里存储临时数据,便于程序或者shell能够轻松的访问。
bash shell里,环境变量分为两类:
- 全局变量
- 局部变量
全局环境变量
全局环境变量对于shell会话和所有的子shell都是可以访问到的。
局部环境变量是只针对创建他们的shell可见。
Linux在bash会话启动时就设定里全局环境变量:
- 系统环境变量,区别在于纯大写字母
- 用户配置的环境变量
1.查看全局环境变量
env
printenv
要想显示某个环境变量的值
[root@web01 ~ 12:02:44]$printenv HOME
/root
# 也可以用echo命令
[root@web01 ~ 12:03:13]$echo $HOME
/root
# 也可以利用变量的值,作为参数使用
[root@web01 ~ 12:04:02]$ls $HOME
# 既然是全局变量,进入子shell,也是可以看到,和父shell是一样的结果
[root@web01 ~ 12:04:53]$bash
[root@web01 ~]# echo $HOME
/root
局部环境变量
局部变量只能在定义他们的进程里可见,局部变量无法单独查看,可以用set命令查到所有的环境变量,包含局部变量,全局变量,以及用户自定义变量。
> >
env、printenv、set之间的差异微小
set显示全局变量,局部变量,用户自定义变量,以及按照字母顺序排序
env、printenv命令和set的区别在于不会排序,也不会输出局部变量和自定义变量。
局部用户定义变量
注意,加上引号
自定义的变量,尽量用小写字母,进行和系统变量区分开,防止修改系统变量导致灾难。
[root@web01 ~]# echo $my_name
[root@web01 ~]# my_name="超哥"
[root@web01 ~]#
[root@web01 ~]# echo $my_name
超哥
[root@web01 ~]# set |grep my_name
my_name=超哥
局部变量,在父子shell是不可见的
[root@web01 ~]# echo $my_name
超哥
[root@web01 ~]#
[root@web01 ~]# bash
[root@web01 ~]# echo $my_name
# 退回父shell
[root@web01 ~]# my_age=18
[root@web01 ~]# echo $my_age
18
[root@web01 ~]# exit
exit
[root@web01 ~]# echo $my_age
想要解决这个问题,就可以设置全局变量来改变这个情况。
设置全局变量
[root@web01 ~]# export name='超哥带你学shell'
[root@web01 ~]#
[root@web01 ~]# printenv name
超哥带你学shell
[root@web01 ~]#
[root@web01 ~]# bash
[root@web01 ~]#
[root@web01 ~]# printenv name
超哥带你学shell
通过export
命令设置全局变量,在子shell里也都是可见的。
作用域优先级
父shell的环境变量优先级,是高于子shell的,也就是:
- 子shell里修改了全局变量,也不回影响到父shell
通过如下过程,看出子shell不会影响到父shell的变量
1.当前的父shell
[root@web01 ~]# name='我是超哥,这里是全局变量'
[root@web01 ~]#
[root@web01 ~]# export name='我是超哥,这里是全局变量'
[root@web01 ~]#
[root@web01 ~]#
[root@web01 ~]# bash
[root@web01 ~]#
[root@web01 ~]# name='我是子shell,我也是超哥'
[root@web01 ~]#
[root@web01 ~]#
[root@web01 ~]# printenv name
我是子shell,我也是超哥
[root@web01 ~]#
[root@web01 ~]# exit
exit
[root@web01 ~]#
[root@web01 ~]# printenv name
我是超哥,这里是全局变量
2.子shell即使用export也无法修改父shell的变量值
删除变量
[root@web01 ~]# unset name
[root@web01 ~]# echo $name
要注意的还是,在子shell里删除变量,也不回影响父shell
查找变量
小技巧,过滤出部分系统的环境变量
[root@web01 ~]# set |grep '^[A-Z]' |wc -l
查找出部分自定义变量
[root@web01 ~]# set |grep '^[a-z]'
colors=/root/.dircolors
name=age
dequote ()
quote ()
quote_readline ()
PATH变量
PATH变量的作用,超哥已经在其他章节给大家讲解过了。
登录Shell
登录Linux时,bash shell是默认的shell启动,shell会从5个文件中读取,是否定义了环境变量
- /etc/profile
- $HOME/.bash_profile
- $HOME/.bashrc
- $HOME/.bash_login
- $HOME/.profile
/etc/profile
文件是系统上默认的bash主启动文件,每个用户启动都会执行该文件。
该文件利用了for
语句,进行配置文件循环读取,遍历执行/etc/profile.d
目录下所有的文件
[root@web01 ~]# ls /etc/profile.d/
交互式shell
刚才说的登录shell,是系统启动,首次登录时启动的shell。
交互式shell,指的是当你手动输入bash指令,进入的子shell
,这个称之为交互式shell,提供命令行提示符,用户进行输入命令交互。
如果bash是交互式shell启动的,不会访问/etc/profile,只会检查HOME目录下的.bashrc文件
[root@web01 ~]# cat ~/.bashrc
这个文件有两个作用:
1.执行/etc/bashrc文件
2.为用户提供自定义的命令别名,自定义变量,以及shell函数执行。
非交互式shell
说了上面的两种shell,就是因为还存在非交互式shell。
这种形式是用来执行shell脚本,它没有命令行提示符。
[root@web01 ~]# cat hello.sh
#!/bin/bash
echo 'hello'
[root@web01 ~]# bash hello.sh
hello
永久性环境变量
对于想要设置永久的环境变量,也就是每次开机都能够生效,大多数运维的习惯是写入/etc/profile
但是要注意,如果系统某天升级了,这个文件也会更新,所定制的环境变量也就消失了。
因此正确的操作是
在/etc/profile.d/目录创建.sh文件,在该脚本文件中定义环境变量。