#创作灵感#
毕业已经五年了,学的shell已经在工作之中逐渐淡忘,很多脚本都能在网上搜到,加上chatgpt问世稍加改动就能套用,但是作为运维的基础,我觉得还是要把对应的知识点整理一下,大道至简,一些简单的内容也会详细写出来,作为能力的巩固
1、基础知识点
1.sheban的定义
在 Shell 脚本中,shebang(也称为 hashbang)是指脚本文件的第一行,用于指定解释器路径。它以井号 #
开头,后面紧跟着一个叹号 !
然后是解释器的路径。通常情况下,shebang 的格式如下:
#!/path/to/interpreter
例如,在 Bash 脚本中,常见的 shebang 是:
#!/bin/bash
这表示脚本将由 Bash 解释器执行。当你运行这样的脚本时,操作系统会根据 shebang 指定的路径找到对应的解释器,并用该解释器执行脚本。
#!/usr/bin/env
是一个 shebang(hashbang)行,但它与直接指定解释器路径的形式略有不同。在这种形式中,env
是一个命令,用于在当前环境中查找并执行给定的命令。因此,#!/usr/bin/env
实际上是告诉操作系统使用 /usr/bin/env
命令来寻找后续指定的解释器,然后使用找到的解释器来执行脚本。
在未指定的情况下默认使用shell去解释
同时可以手动指定解释器,如:
[root@bogon ~]# bash Example.sh
[root@bogon ~]# python3 Example.py
2.shell脚本执行方式
1、bash Example.sh 指定解释器执行,无需脚本自身有可执行权限。
2、./Example.sh 相对路径执行,需要本身具有执行权限。
3、source Example.sh与. Example.sh此种方法执行不产生子shell,变量可以保存在当前shell。
4、sh < Example.sh 传递执行,效果貌似和1没有什么不同。
3.变量操作
1、注意事项
变量赋值时与其他编程语言不同,无需声明变量形式,默认为字符型;
变量赋值时变量名,等号,变量值之间无空格;
变量名称尽量见名知意,哪怕用拼音都行;
变量名称只能包含数字、字母、下划线三种,其中数字不能用在变量名称开头,严格区分大小写;
2、变量作用域
在 Shell 脚本中,变量的作用域决定了该变量在脚本中的可见性和生命周期。Shell 中的变量作用域主要分为两种:全局作用域和局部作用域。
- 全局变量在整个脚本中都可见和可用。
- 局部变量只在定义它的函数内部可见和可用,并且在函数执行完毕后会被销毁。
父子shell概念:父子 shell 的关系通常用于在 Shell 环境中执行并行任务、控制作业流程等场景。需要注意的是,虽然子 shell 可以继承父 shell 的环境变量和当前工作目录,但它们是独立的进程,彼此之间的变量和状态不会直接共享。
每次调用bash/sh解释器都会打开一个子shell,因此不保留当前的变量,可以通过pstree命令查看;
source Example.sh与. Example.sh此种方法执行不产生子shell,变量可以保存在当前shell。
反引号示例:
[root@bogon ~]# mkdir 1.txt 2.txt
[root@bogon ~]# name=`ls`
[root@bogon ~]# echo $name
1.txt 2.txt anaconda-ks.cfg
3、变量类型
1、环境变量(全局变量)
用户个人配置文件(这里是rhcsa的一个考点)
~/.bash_profile和~/.bashrc 可以使用export定义,效果演示:
[root@bogon ~]# su - test1
[test1@bogon ~]$ vim ~/.bashrc
#在最后一行添加
export name="Liumuquan"
#保存退出
[test1@bogon ~]$ source ~/.bashrc
#重新加载配置文件
[test1@bogon ~]$ echo $name
Liumuquan
全局配置文件
/etc/profile和/etc/bashrc,一般用于java等环境配置,仅以不要随意改动
环境变量的操作:
set/declare:输出当前环境所有变量,包括全局变量,局部变量。
env:只显示全局变量。
export:显示和定义环境变量的值。
unset:删除变量
环境变量的初始化和加载顺序:
ssh登录linux,系统启动一个bash/shell
/etc/profile > /etc/profile.d > /.bash_profile > /.bashrc > /etc/bashrc
2、只读变量
演示如下:
[root@bogon ~]# readonly name="Liumuquan"
[root@bogon ~]# echo $name
Liumuquan
[root@bogon ~]# name="Liumuquan@123"
-bash: name:只读变量
[root@bogon ~]#
3、特殊参数变量
$0:获取文件名
$1 $2 $3:获取脚本第一二三个参数,编号大于9需写成${10}
$#:shell脚本后面参数个数
$*与$@:获取脚本所有参数,不加" "情况下两者相同,加引号$*将所有字符串合并显示 $@加引号为逐个显示
演示代码:
#!/bin/bash
# 输出脚本本身的名称
echo "脚本名称:$0"
# 输出传递给脚本的参数
echo "第一个参数:$1"
echo "第二个参数:$2"
echo "第三个参数:$3"
# 输出传递给脚本的参数的数量
echo "参数个数:$#"
# 使用 "$*" 打印所有参数
echo "使用 \$* 打印所有参数:$*"
# 使用 "$@" 打印所有参数
echo "使用 \$@ 打印所有参数:$@"
# 使用 "$*" 循环打印所有参数
echo "使用 \"\$*\" 循环打印所有参数:"
for var in "$*"; do
echo "$var"
done
# 使用 "$@" 循环打印所有参数
echo "使用 \"\$@\" 循环打印所有参数:"
for var in "$@"; do
echo "$var"
done
演示结果:
4、特殊状态变量
$?:上次命令执行的返回值,0正确执行,1-255错误
[root@bogon ~]# ls
1.txt 2.txt anaconda-ks.cfg SpecialVar.sh
[root@bogon ~]# echo $?
0
[root@bogon ~]# ls /abcdefg
ls: 无法访问 '/abcdefg': 没有那个文件或目录
[root@bogon ~]# echo $?
2
$$:当前shell脚本进程号
[root@bogon ~]# echo $$
1562
[root@bogon ~]# ps -aux | grep 1562
root 1562 0.0 0.7 9052 5376 pts/0 Ss 09:20 0:00 -bash
$!:上一次后台进程的pid
[root@bogon ~]# nohup ping www.baidu.com & 1 > /dev/null
[1] 1600
-bash: 1:未找到命令
[root@bogon ~]# nohup: 忽略输入并把输出追加到'nohup.out'
[root@bogon ~]# echo $!
1600
[root@bogon ~]# ps -aux | grep 1600
root 1600 0.0 0.3 7596 2560 pts/0 S 09:26 0:00 ping www.baidu.com
$_:上一次命令传入的最后一个参数
[root@bogon ~]# bash SpecialVar.sh one two three four
脚本名称:SpecialVar.sh
第一个参数:one
第二个参数:two
第三个参数:three
参数个数:4
使用 $* 打印所有参数:one two three four
使用 $@ 打印所有参数:one two three four
使用 "$*" 循环打印所有参数:
one two three four
使用 "$@" 循环打印所有参数:
one
two
three
four
[root@bogon ~]# echo $_
four
可以使用man bash查看手册
/Special Parameters内有详细的特殊变量详解
5、shell子串(操作字符串)
这里有一个脚本通过阅读脚本和查看执行结果,可以初步了解子串的使用方法
#!/bin/bash
name="abc123ABC123ABC123abc"
echo "这是\${变量}的效果。"
echo ${name}
echo "这是\${#变量}的效果,用来获取变量内容的长度。"
echo ${#name}
echo "截取字符串\${变量:截取位置}的效果,start的值是3。"
echo ${name:3}
echo "截取字符串\${变量:截取位置:截取长度}的效果,位置是3,长度是4。"
echo ${name:3:4}
echo "截取字符串\${变量#截取词}的效果,截取内容可以使用通配符,截取词是'a*c',删除从a开始能匹配到的最长aXXXXXXc字符串。"
echo ${name#a*c}
echo "截取字符串\${变量##截取词}的效果,截取内容可以使用通配符,截取词是'a*c',删除从a开始能匹配到的最长aXXXXXXc字符串,就是全部内容。"
echo ${name##a*c}
echo "截取字符串\${变量%截取词}的效果,从后往前找截取字段。"
echo ${name%a*c}
echo "截取字符串\${变量%%截取词}的效果,从后往前找截取字段,字段中有两个a,匹配的aXXXXc字段有三个,%%删除最长的匹配结果。"
echo ${name%%a*c}
echo "这是替换字符串\${变量/谁要被替换/替换成什么}的效果。"
echo ${name/abc/666}
echo "这是全替换字符串\${变量//谁要被替换/替换成什么}的效果。"
echo ${name//abc/666}
shell子串练习:
创造一个这样的文件夹,要求修改将文件内所有gif文件,文件名中的"_mu"去掉
[root@bogon ~]# mkdir sub_str1
[root@bogon ~]# ls
anaconda-ks.cfg sub_str1
[root@bogon ~]# touch sub_str1/LIU_{1..5}_mu.jpg
[root@bogon ~]# touch sub_str1/LIU_{1..5}_mu.png
[root@bogon ~]# touch sub_str1/LIU_{1..5}_mu.gif
[root@bogon ~]# ls sub_str1/
LIU_1_mu.gif LIU_2_mu.gif LIU_3_mu.gif LIU_4_mu.gif LIU_5_mu.gif
LIU_1_mu.jpg LIU_2_mu.jpg LIU_3_mu.jpg LIU_4_mu.jpg LIU_5_mu.jpg
LIU_1_mu.png LIU_2_mu.png LIU_3_mu.png LIU_4_mu.png LIU_5_mu.png
答案脚本:
#!/bin/bash
cd /root/sub_str1
for filename in `ls *_mu.gif`
do
mv $filename `echo ${filename//_mu/}`
done
6、特殊shell拓展变量
${name:-word}
如果name为空,则调用变量返回word
${name:=word}
如果name为空,则将word的值赋予name,并返回赋值后的name的值
${name:?word}
如果name为空,则将word当作错误信息输出
${name:-word}
如果name为空,不进行任何操作,如果name不为空则返回word
实践:删除sub_str1文件夹下的,以.jpg结尾的,超过五天没修改的文件,使用拓展变量表示路径
实验环境:
find ${dir_path:=/rootsub_str1/} -name '*.jpg' -type f -mtime +7 | xargs rm -rf
如果没有添加拓展变量,同时忘记定义dir_path会发生什么
4.父子shell详解
在变量作用域中提到了父子shell的概念,因为这个概念十分重要,后续的一些并发脚本中会使用到其中内容,所以这里再剖析一下。
1、运行方式与查看方式
1、bash Example.sh 指定解释器执行,会开启子shell运行。
2、./Example.sh 相对路径执行,是通过shebang确定的解释器执行,同样会开启子shell。
3、source Example.sh与. Example.sh此种方法执行不产生子shell。
我们可以通过两个命令查看当前的shell位置
命令1:pstree
命令2:ps -ef --forest(此处 可以观察pid与ppid关系确定父子shell)
在子shell中输入exit可以返回至当前shell的父shell
2、BASH_SUBSHELL变量
在Bash中,BASH_SUBSHELL
是一个环境变量,用于表示当前子Shell的嵌套级别。每当你启动一个新的子Shell时,该变量的值就会增加。当你退出一个子Shell时,该值会减少。
方便引入官方默认shell变量BASH_SUBSHELL,需要创建一个进程列表,步骤如下:
需要执行多条命令的做法一般是使用分号隔开
[root@bogon ~]# cd /root;pwd;ls;cd /tmp ;pwd
/root
anaconda-ks.cfg sub_str1
/tmp
[root@bogon tmp]#
将这些命令使用小括号括起来,就形成了最简单的进程列表
[root@bogon tmp]# (cd /root;pwd;ls;cd /tmp ;pwd)
/root
anaconda-ks.cfg sub_str1
/tmp
[root@bogon tmp]#
当在环境中直接调用BASH_SUBSHELL会显示当前shell所在的父子层数
[root@bogon tmp]# cd /root;pwd;ls;cd /tmp ;pwd;echo $BASH_SUBSHELL
/root
anaconda-ks.cfg sub_str1
/tmp
0
[root@bogon tmp]#
进程列表会调用bash解释器,所以会自动进入一个子shell执行命令,此时变量值发生改变
[root@bogon tmp]# (cd /root;pwd;ls;cd /tmp ;pwd;echo $BASH_SUBSHELL)
/root
anaconda-ks.cfg sub_str1
/tmp
1
[root@bogon tmp]#
子shell嵌套运行
#!/bin/bash
echo "当前Shell嵌套级别为: $BASH_SUBSHELL"
# 在子Shell中执行命令
(
echo "进入子Shell,当前Shell嵌套级别为: $BASH_SUBSHELL"
# 在子Shell中再次启动一个子Shell
(
echo "再次进入子Shell,当前Shell嵌套级别为: $BASH_SUBSHELL"
)
)
echo "退出子Shell,当前Shell嵌套级别为: $BASH_SUBSHELL"
运行结果
[root@bogon tmp]# source subshell.sh
当前Shell嵌套级别为: 0
进入子Shell,当前Shell嵌套级别为: 1
再次进入子Shell,当前Shell嵌套级别为: 2
退出子Shell,当前Shell嵌套级别为: 0
[root@bogon tmp]#
这个变量对于脚本编写和处理逻辑上的区分子shell和父shell之间的执行环境非常有用。你可以在脚本中根据BASH_SUBSHELL
的值来执行不同的操作或逻辑,提高并发效率。
5.内置命令与外置命令
此处是一个纯学术概念,目前没有发现这个命令在实际中的应用,基本已经忘干净了,这里作为一个知识点记录一下
在Linux系统中,命令可以分为内置命令和外置命令,它们之间的区别在于:
内置命令(Built-in Commands):
是shell的一部分,在系统启动时随系统启动,常驻内存,执行效率高,占用资源,常见的有cd,pwd等
外置命令(External Commands):
用户需要从硬盘中读取程序,也就会创建一个子进程,再读入内存加载,常见的有ls,git等
使用type可以查看一个命令是内置命令还是外置命令
[root@bogon tmp]# type cd
cd 是 shell 内建
[root@bogon tmp]# type ls
ls 是“ls --color=auto”的别名
[root@bogon tmp]# compgen -b
#这条命令可以列出系统中所有的内置命令