shell脚本开发技术笔记1--基础概念

#创作灵感#

毕业已经五年了,学的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
#这条命令可以列出系统中所有的内置命令

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值