shell 编程

1.为什么要学习 Shell 编程?

1.1 什么是 Shell

Shell 是一个命令解释器,它的作用是解释执行用户输入的命令及程序。用户每输入一条命令, Shell 就解释执行一条。这种从键盘一输入命令,就可以立即得到回应的对话方式,称为交互的方式。

Shell 存在于操作系统的外层,负责与用户直接对话,把用户的输入解释给操作系统,并处理各种各样的操作系统的输出结果,然后输出到屏幕返回给用户。输入系统用户名和密码并登录到Linux后的所有操作都是由 Shell 解释与执行的。

image-20221202183110412

Shell 的英文是壳的意思,从上图可以看出,命令解释器( Shell )就像壳一样包住了系统核心。

在常用的操作系统中:

  • Linux 下默认的 Shell 是 Bourne Again shell(bash)。
  • Solaris 和 FreeBSD 下默认的是 Bourne shell(sh)。
  • AIX 下默认的是 Kom Shell (ksh)。

这里重点讲 Linux 系统环境下的 Bourne Again shell(bash)。

1.2 什么是 Shell 脚本

理解了 Shell 之后,再理解 Shell 脚本就简单了。当命令或程序语句不在命令行下执行,而是通过一个程序文件来执行时,该程序就被称为 Shell 脚本。

如果在 Shell 脚本里内置了很多条命令、语句及循环控制,然后将这些命令一次性执行完毕,这种通过文件执行脚本的方式称为非交互的方式。

Shell 脚本类似于 DOS 系统下的批处理程序(早期扩展名一般为*.bat)。用户可以在 Shell 脚本中敲入一系列的命令及命令语句组合。这些命令、变量和流程控制语句等有机地结合起来,就形成了一个功能强大的 Shell 脚本。

1.3 为什么要学习 Shell 编程

Shell 脚本语言很适合用于处理纯文本类型的数据,而Linux系统中几乎所有的配置文件、日志文件(如NFS、Rsync、Httpd、Nginx、LVS、MySQL等),以及绝大多数的启动文件都是纯文本类型的文件。因此,学好 Shell 脚本语言,就可以利用它在Linux系统中发挥巨大的作用。

Shell 脚本语言是实现Linux/UNIX系统管理及自动化运维所必备的重要工具,Linux/UNIX系统的底层及基础应用软件的核心大都涉及 Shell 脚本的内容。每一个合格的Linux系统管理员或运维工程师,都需要能够熟练地编写 Shell 脚本语言,并能够阅读系统及各类软件附带的 Shell 脚本内容。只有这样才能提升运维人员的工作效率,适应日益复杂的工作环境,减少不必要的重复工作,从而为个人的职场发展奠定较好的基础。

例如:

  • 检查多台服务器运行状态
  • 自动化部署、升级各种服务器

1.4 学好 Shell 编程所需的基础知识

本节首先来探讨一下在学习 Shell 编程之前需要掌握的基础知识,需要说明的是,并不是必须具备这些基础知识才可以学习 Shell 编程,而是,如果具备了这些基础知识,那么就可以把 Shell 编程学得更好,领悟得更深。如果只是想简单地了解 Shell 脚本语言,那么就无须掌握太多的系统基础知识,只需要会一些简单的命令行操作即可。

学好 Shell 编程并通过 Shell 脚本轻松地实现自动化管理企业生产系统的必备基础如下:

  1. **能够熟练使用vim编辑器,熟悉SSH终端及.vimrc等的配置。**在Linux下开发 Shell 脚本最常使用的编辑器是 vim,因此如果能够熟练使用并配置好vim的各种高级功能设置,就可以让开发 Shell 脚本达到事半功倍的效果。
  2. 要有一定的 Linux 命令基础,掌握 Linux 常用命令,并能够熟练使用它们。
  3. 要熟练掌握Linux正则表达式及三剑客命令(grep、sed、awk)。
  4. 熟悉常见的Linux网络服务部署、优化、日志分析及排错。

1.5 如何才能学好 Shell 编程

学好 Shell 编程的核心:多练习—多思考—多总结—再练习—再思考—再总结。

如此循环,坚持即可!

马老师建议:

  1. 掌握 Shell 脚本基本语法。
  2. 从简单做起,简单判断,简单循环。
  3. 多模仿,多练习,多思考。
  4. 编程变量名字要规范,采用驼峰语法表示。
  5. 不要拿来主义,特别是新手。
  6. 形成自己的脚本开发风格。

1.6 脚本语言的种类

Shell 脚本语言是弱类型语言(无须定义变量的类型即可使用)。

在 Unix/Linux中 主要有两大类 Shell :

  1. Bourne shell,包括Bourne shell(sh)、Kom shell(ksh)、Bourne Again Shell (bash)三种类型。

  2. C shell,包括 csh、tcsh 两种类型。

    • csh 由 Berkeley 大学开发,随 BSD UNIX 发布,它的流程控制语句很像C语言,支持很多 Bourne shell 所不支持的功能,例如:作业控制、别名、系统算术、命令历史命令行编辑等。

    • tcsh 是 csh 的增强版,加人了命令补全等功能,在 FreeBSD、MacOSX 等系统上替代了 csh。

2. 创建第一个 Shell 脚本

2.1 Shell 脚本

在 Linux 系统中, Shell 脚本通常是在编辑器 vi/vim 中编写的,由UNIX/Linux命令、bash Shell 命令、程序结构控制语句和注释等内容组成。这里推荐用Linux自带的功能更强大的vim编辑器来编写,可以事先做一个别名alias vi=’vim’,并使其永久生效,这样以后习惯输入 vi 的读者也就可以直接调用 vim 编辑器了。设置方法如下:

[root@shell ~]# echo "alias vi='vim'" >> /etc/bashrc
[root@shell ~]# source /etc/bashrc
2.1.1 脚本开头

一个规范的 Shell 脚本在第一行会指出由哪个程序(解释器)来执行脚本中的内容,这一行内容在Linux bash 的编程一般为:

#!/bin/bash

#!/bin/sh

其中,开头的"#!“字符又称为幻数(其实叫什么都无所谓,知道它的作用就好),在执行bash脚本的时候,内核会根据”#!"字符后的解释器来确定该用哪个程序解释这个脚本中的内容。

注意,这一行必须位于每个脚本顶端的第一行,如果不是第一行则为脚本注释行,

例如下面的例子:

[dcr@shell ~]$ vim hello.sh 
#!/bin/bash
echo hello world

CentOS 和 RHEL下默认的 Shell 均为 bash。因此,在写 Shell 脚本的时候,脚本的开头即使不加幻数,也会交给bash解释。如果写脚本不希望使用系统默认的 Shell 解释,那么就必须要指定解释器了,否则脚本文件执行后的结果可能就不是你所要的。

建议读者养成好的编程习惯,不管采用什么脚本,最好都加上相应的开头解释器语言标识,遵守 Shell 编程规范。

2.1.2 脚本注释

在 Shell 脚本中,跟在# 后面的内容表示注释,用来对脚本进行注释说明,注释部分不会被当作程序来执行,仅仅是给开发者和使用者看的,系统解释器是看不到的,更不会执行。

注释可自成一行,也可以跟在脚本命令的后面与命令在同一行。

开发脚本时,如果没有注释,那么团队里的其他人就会很难理解脚本对应内容的用途,而且若时间长了,自己也会忘记。因此,我们要尽量养成为所开发的 Shell 脚本书写关键注释的习惯,书写注释不光是为了方便别人,更是为了方便自己,避免影响团队的协作效率,以及给后来接手的人带来维护困难。

特别提示一下,注释尽量不要用中文,在脚本中最好也不要有中文。

2.1.3 bash 与 sh 的区别

早期的bash与sh稍有不同,但大多数脚本都可以不加修改地在sh上运行。

sh为bash的软链接,大多数情况下,脚本的开头使用 #!/bin/bash#!/bin/sh 是没有区别的,但更规范的写法是在脚本的开头使用 #!/bin/bash

[dcr@shell ~]$ ls -l /bin/sh
lrwxrwxrwx. 1 root root 4 112 17:33 /bin/sh -> bash

2.2 Shell 脚本的执行

当 Shell 脚本运行时,它会先查找系统环境变量ENV,该变量指定了环境文件,再加载了上述环境变量文件后, Shell 就开始执行 Shell 脚本中的内容。

Shell 脚本是从上至下、从左至右依次执行每一行的命令及语句的,即执行完了一个命令后再执行下一个,如果在 Shell 脚本中遇到子脚本(即脚本嵌套)时,就会先执行子脚本的内容,完成后再返回父脚本继续执行父脚本内后续的命令及语句。通常情况下,在执行 Shell 脚本时,会向系统内核请求启动一个新的进程,以便在该进程中执行脚本的命令及子 Shell 脚本,基本流程如下。

在这里插入图片描述

Shell 脚本的执行通常可以采用以下几种方式:

  1. bash hello.sh 或sh hello.sh:这是当脚本文件本身没有可执行权限(即文件权限属性x位为-号)时常使用的方法,或者脚本文件开头没有指定解释器时需要使用的方法。

    [dcr@server bin 12:40:43]$ bash hello.sh
    hello world
    
  2. /path/hello.sh 或 ./hello.sh:指在当前路径下执行脚本(脚本需要有执行权限),需要将脚本文件的权限先改为可执行,然后通过脚本绝对路径或相对路径就可以直接执行脚本了。

    [dcr@server bin 12:40:52]$ chmod +x hello.sh
    # 增加文件执行权限
    
    # 绝对路径
    [dcr@server bin 12:41:24]$ /home/dcr/bin/hello.sh 
    hello world
    
    # 相对路径
    [dcr@server bin 12:41:44]$ ./hello.sh 
    hello world
    

    在企业生产环境中,不少运维人员在写完 Shell 脚本之后,由于忘记为该脚本设置执行权限,然后就直接应用了,结果导致脚本没有按照自己的意愿手动或定时执行,对于这一点,避免出现该问题的方法就是用第1种方法替代第2种。

  3. source hello.sh 或 . hello.sh:这种方法通常是使用 source. 点号读入或加载指定的 Shell 脚本文件,然后,依次执行指定的 Shell 脚本文件中所有语句。这些语句将在当前父 Shell 脚本进程中运行(其他几种模式都会启动新的进程执行子脚本)。因此,**使用 source 可以将子脚本中的变量值、函数值等传递到当前父 Shell 脚本中使用。**这是它和其他几种方法最大的区别,也是值得读者特别注意的地方。

    [dcr@server bin 12:41:53]$ source hello.sh
    hello world
    
    # 等效于
    [dcr@server bin 12:41:44]$ ./hello.sh 
    hello world
    
  4. bash < hello.sh 或 cat scripts-name|sh:同样适用于bash,这种用法也很常见。

    [dcr@server bin 12:43:25]$ bash < hello.sh
    hello world
    
    [dcr@server bin 12:44:14]$ cat hello.sh | bash
    hello world
    
[dcr@server bin 12:44:25]$ echo $PATH
/usr/local/bin:/usr/bin:/usr/local/sbin:/usr/sbin:/home/dcr/.local/bin:/home/dcr/bin

因为脚本写在PATH中的路径bin中,所以可以在其他目录下直接执行

[dcr@server bin 12:44:54]$ cd /tmp
[dcr@server tmp 12:46:28]$ hello.sh 
hello world

2.3 Shell 脚本开发的基本规范及习惯

Shell 脚本的开发规范及习惯非常重要,虽然这些规范不是必须要遵守的,但有了好的规范和习惯,可以大大提升开发效率,并能在后期降低对脚本的维护成本。

当多人协作开发时,大家有一个互相遵守的规范就显得更重要了。即使只是一个人开发,最好也采取一套固定的规范,这样脚本将会更易读、更易于后期维护,最重要的是要让自己养成一个一出手就很专业和规范的习惯。

下面来看看有哪些规范:

  1. Shell 脚本的第一行是指定脚本解释。

    #!/bin/bash
    
  2. Shell 脚本的开头加版本、版权等信息。(暂时不用)

    #!/bin/bash
    # Date:16:29 2022-3-30
    # Author:Created by dcr
    # Description:This script is used to...
    # Version:2.0
    

    可修改 ~/.vimrc 配置文件配置 vim 编辑文件时自动加上以上信息的功能。

  3. 在 Shell 脚本中尽量不用中文(不限于注释)。

    尽量用英文注释,防止本机或切换系统环境后中文乱码的困扰。如果非要加中文,请根据自身的客户端对系统进行字符集调整,如:export LANG="zh_CN.UTF-8",并在脚本中,重新定义字符集设置,和系统保持一致。

  4. Shell 脚本的命名应以.sh为扩展名。例如:hello.sh.sh

  5. Shell 脚本应存放在固定的路径下。

以下则是 Shell 脚本代码书写的良好习惯

  1. 成对的符号应尽量一次性写出来,然后退格在符号里增加内容,以防止遗漏。

    这些成对的符号包括:

    {} [] '' `` ""
    
  2. 中括号([])两端至少要有1个空格,因此,键入中括号时即可留出空格[],然后再退格键入中间的内容,并确保两端都至少有一个空格,即先键入一对中括号,然后退1格,输入两个空格,再退1格,双中括号([[]])的写法也是如此。

  3. 对于流程控制语句,应一次性将格式写完,再添加内容。

    比如,一次性完成if吾句的格式,应为:

    if 条件内容;then
      内容
    fi
    

    一次性完成for循环语句的格式,应为:

    for
    do
      内容
    done
    

    提示:while和until,case等语句也是一样。

  4. 通过缩进让代码更易读,比如:

    if 条件内容;then
      内容
    fi
    
  5. 对于常规变量的字符串定义变量值应加引号,并且等号前后不能有空格,需要强引用的(指所见即所得的字符引用),则用单引号(’’),如果是命令的引用,则用反引号(‘’)。

    例如:

    dcr_file="test.txt"
    week_day="$(date +%A)"
    welcome_string='******'
    
  6. 脚本中的单引号、双引号及反引号必须为英文状态下的符号,其实所有的Linux字符及符号都应该是英文状态下的符号,这点需要特别注意。

说明:好的习惯可以让我们避免很多不必要的麻烦,提升工作效率。

示例:

[dcr@server bin 12:49:46]$ cat gather_os_info.sh 
#!/bin/bash

# 收集系统块设备信息
echo ========系统块设备信息========
lsblk
echo ========系统块设备信息========
echo

#收集文件系统信息
echo ========文件系统信息========
df -h | grep -v tmpfs
echo ========文件系统信息========
echo 

#收集系统中cpu和内存使用情况,使用top命令
echo ========cpu和内存使用信息========
os_info_file=/tmp/tasks.info
#设置一个变量名,用来表示储存信息的文件路径

top -n 2 | head -n 5 > ${os_info_file}
#${}用来限制变量名 表示变量

echo 系统总任务数量:$(cat ${os_info_file} | awk '/^Tasks/ {print $2}')
#$()解析表达式

echo 系统正在运行的进程数量:$(cat ${os_info_file} | awk '/^Tasks/ {print $4}')
echo 系统中僵尸进程数量:$(cat ${os_info_file} | awk '/^Tasks/ {print $10}')
echo 系统中内存数量:$(cat ${os_info_file} | awk 'NR==4 {print $4}') K 
#NR==4 表示取第四行  K单位

echo 系统可用内存数量:$(cat ${os_info_file} | awk 'NR==4 {print $6}') K
rm  -f ${os_info_file}
#结束后可以删除生成文件
echo ========cpu和内存使用信息========
echo


#另一个窗口查看
[dcr@server ~ 10:16:37]$ bash gather_os_info.sh
========系统块设备信息========
NAME            MAJ:MIN RM  SIZE RO TYPE MOUNTPOINT
sda               8:0    0  100G  0 disk 
├─sda1            8:1    0    1G  0 part /boot
└─sda2            8:2    0   99G  0 part 
  ├─centos-root 253:0    0   50G  0 lvm  /
  ├─centos-swap 253:1    0    2G  0 lvm  [SWAP]
  └─centos-home 253:2    0   47G  0 lvm  /home
sr0              11:0    1  4.4G  0 rom  
========系统块设备信息========

========文件系统信息========
文件系统                 容量  已用  可用 已用% 挂载点
/dev/mapper/centos-root   50G  2.3G   48G    5% /
/dev/sda1               1014M  139M  876M   14% /boot
/dev/mapper/centos-home   47G   33M   47G    1% /home
========文件系统信息========

========cpu和内存使用信息========
系统总任务数量:197
系统正在运行的进程数量:1
系统中僵尸进程数量:0
系统中内存数量:4026124 K
系统可用内存数量:2541160 K
========cpu和内存使用信息========

3. Shell 变量基础知识

3.1 什么是 Shell 变量

3.1.1 什么是变量

简单地说,变量名是用一个固定的字符串(字符、数字和下划线的组合,不能以数字开头)代替更多、更复杂的内容,该内容里可能还会包含变量、路径、字符串等其他的内容。

变量是暂时存储数据的地方及数据标记,所存储的数据存在于内存空间中,通过正确地调用内存空间中变量的名字就可以取出与变量对应的数据。使用变量的最大好处就是使程序开发更为方便,当然,在编程中使用变量也是必须的,否则就很难完成相关的程序开发工作。

下面是定义变量和打印变量的示例:

[dcr@server ~ 10:20:49]$ username=zhangsan
[dcr@server ~ 10:34:46]$ first_name=zhang
[dcr@server ~ 10:35:05]$ last_name=san

[dcr@server ~ 10:35:12]$ 1_name=zhang
-bash: 1_name=zhang: 未找到命令
[dcr@server ~ 10:35:25]$ first-name=zhang
-bash: first-name=zhang: 未找到命令

[dcr@server ~ 10:35:41]$ echo $first_name $last_name
zhang san
[dcr@server ~ 10:36:06]$ echo $first_name    $last_name
zhang san
[dcr@server ~ 10:36:11]$ echo "$first_name    $last_name"
zhang    san
[dcr@server ~ 10:36:20]$ echo $first_name-$last_name
zhang-san
[dcr@server ~ 10:36:34]$ echo $first_name_$last_name
san
[dcr@server ~ 10:36:39]$ echo ${first_name}_${last_name}
zhang_san

变量的赋值方式为:先写变量名称,紧接着是这个字符,最后是值,中间无任何空格。

通过 echo 命令加上 $username 即可输出 username 变量的值。变量的内容一般要加双引号,以防止出错,特别是当值里的内容之间有空格时。

3.1.2 Shell 变量的特性

默认情况下,在bash Shell 中是不会区分变量类型的。例如:常见的变量类型为整数、字符串、小数等都当做字符串变量。这和其他强类型语言(例如:Java/C语言)是有区别的,当然,如果需要指定 Shell 变量的类型,也可以使用 declare 命令定义变量的类型,但在一般情况下没有这个需求。

3.1.3 变量类型

变量根据范围可分为两类:

  • 全局变量,在创建它们的 Shell 及其派生出来的任意子进程 Shell 中使用

  • 局部变量,只能在创建它们的 Shell 函数或 Shell 脚本中使用。

变量根据是否是用户自定义也可分为两类:

  • 普通变量:也称为常规变量,由开发者在开发脚本程序时创建。
  • 环境变量:定义shell 执行环境。环境变量又可分为自定义环境变量和bash内置的环境变量。

3.2 环境变量

环境变量一般是指用export内置命令导出的变量,用于定义 Shell 的运行环境,保证 Shell 命令的正确执行。 Shell 通过环境变量来确定登录用户名、命令路径、终端类型、登录目录等,所有的环境变量都是系统全局变量,可用于所有子进程中,这包括编辑器、 Shell 脚本和各类应用。

环境变量可以在命令行中设置和创建,但用户退出命令行时这些变量值就会丢失,因此,如果希望永久保存环境变量,可在用户家目录下的 .bash_profile.bashrc(非用户登录模式特有,例如远程SSH)文件中,或者全局配置 /etc/bashrc (非用户登录模式特有,例如远程SSH)或 /etc/profile 文件中定义。在将环境变量放入上述的文件中后,每次用户登录时这些变量都将被初始化。

按照系统规范,所有环境变量的名字均采用大写形式。在将环境变量应用于用户进程程序之前,都应该用 export 命令导出定义。有一些环境变量,比如HOMEPATHUSER等,在用户登录之前就已经被/bin/login程序设置好了。通常环境变量被定义并保存在用户家目录下的 .bash_profile 文件或全局的配置文件 /etc/profile 中。

部分bash环境变量展示:

  • EDITOR,默认编辑器。
  • HISTFILE,历史文件位置。
  • HISTSIZE,历史命令个数。
  • PS1,命令行提示符。
  • LANG,Shell 环境语言。
  • PATH,命令搜素路径。
  • HOME,当前用户家目录。
  • USER,当前用户。
  • PWD,当前 Shell 路径。
  • IFS,字符串分隔符。
[root@server ~ 13:09:04]# echo $USER
root
[root@server ~ 13:19:22]# echo $UID
0

在查看设置的变量时,有3个命令可以显示变量的值:

  • set 命令,输出所有的变量,包括全局变量和局部变量。
  • env 命令,只显示全局变量,包括shell的环境。
  • declare 命令输出所有的变量、函数、整数和已经导出的变量set -o 命令显示bash Shell 的所有参数配置信息。
#env查看所有环境变量
[root@server ~ 13:09:00]# env
XDG_SESSION_ID=83
HOSTNAME=server.dcr.cloud
TERM=xterm
SHELL=/bin/bash
HISTSIZE=1000
SSH_CLIENT=10.1.8.1 56912 22
SSH_TTY=/dev/pts/5
USER=root
LS_COLORS=rs=0:di=01;34:ln=01;36:mh=00:pi=40;33:so=01;35:do=01;35:bd=40;33;01:cd=40;33;01:or=40;31;01:mi=01;05;37;41:su=37;41:sg=30;43:ca=30;41:tw=30;42:ow=34;42:st=37;44:ex=01;32:*.tar=01;31:*.tgz=01;31:*.arc=01;31:*.arj=01;31:*.taz=01;31:*.lha=01;31:*.lz4=01;31:*.lzh=01;31:*.lzma=01;31:*.tlz=01;31:*.txz=01;31:*.tzo=01;31:*.t7z=01;31:*.zip=01;31:*.z=01;31:*.Z=01;31:*.dz=01;31:*.gz=01;31:*.lrz=01;31:*.lz=01;31:*.lzo=01;31:*.xz=01;31:*.bz2=01;31:*.bz=01;31:*.tbz=01;31:*.tbz2=01;31:*.tz=01;31:*.deb=01;31:*.rpm=01;31:*.jar=01;31:*.war=01;31:*.ear=01;31:*.sar=01;31:*.rar=01;31:*.alz=01;31:*.ace=01;31:*.zoo=01;31:*.cpio=01;31:*.7z=01;31:*.rz=01;31:*.cab=01;31:*.jpg=01;35:*.jpeg=01;35:*.gif=01;35:*.bmp=01;35:*.pbm=01;35:*.pgm=01;35:*.ppm=01;35:*.tga=01;35:*.xbm=01;35:*.xpm=01;35:*.tif=01;35:*.tiff=01;35:*.png=01;35:*.svg=01;35:*.svgz=01;35:*.mng=01;35:*.pcx=01;35:*.mov=01;35:*.mpg=01;35:*.mpeg=01;35:*.m2v=01;35:*.mkv=01;35:*.webm=01;35:*.ogm=01;35:*.mp4=01;35:*.m4v=01;35:*.mp4v=01;35:*.vob=01;35:*.qt=01;35:*.nuv=01;35:*.wmv=01;35:*.asf=01;35:*.rm=01;35:*.rmvb=01;35:*.flc=01;35:*.avi=01;35:*.fli=01;35:*.flv=01;35:*.gl=01;35:*.dl=01;35:*.xcf=01;35:*.xwd=01;35:*.yuv=01;35:*.cgm=01;35:*.emf=01;35:*.axv=01;35:*.anx=01;35:*.ogv=01;35:*.ogx=01;35:*.aac=01;36:*.au=01;36:*.flac=01;36:*.mid=01;36:*.midi=01;36:*.mka=01;36:*.mp3=01;36:*.mpc=01;36:*.ogg=01;36:*.ra=01;36:*.wav=01;36:*.axa=01;36:*.oga=01;36:*.spx=01;36:*.xspf=01;36:
MAIL=/var/spool/mail/root
PATH=/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/root/bin
PWD=/root
LANG=zh_CN.UTF-8
HISTCONTROL=ignoredups
SHLVL=1
HOME=/root
LOGNAME=root
SSH_CONNECTION=10.1.8.1 56912 10.1.8.10 22
LESSOPEN=||/usr/bin/lesspipe.sh %s
XDG_RUNTIME_DIR=/run/user/0
_=/usr/bin/env

#set查看所有变量,包括环境变量 
3.2.1 设置环境变量
设置环境变量

如果想要设置环境变量,就要在给变量赋值之后或在设置变量时使用export命令,具体设置见下文的示例。其实,除了 export 命令,带-x选项的declare内置命令也可以完成同样的功能(注意:此处不要在变量名前面加$)。

export 命令和 declare 命令的格式如下:

  • export 变量名=value
  • 变量名=value ; export 变量名
  • declare -x 变量名=value

示例:

[dcr@server ~ 10:36:51]$ echo $username
zhangsan
[dcr@server ~ 10:48:30]$ bash
[dcr@server ~ 10:48:38]$ echo $username

#没有设为环境变量,所以bash之后就无法保留

[dcr@server ~ 10:49:18]$ export username=zhangsan
[dcr@server ~ 10:49:37]$ echo $username
zhangsan
[dcr@server ~ 10:49:41]$ bash
[dcr@server ~ 10:49:46]$ echo $username
zhangsan
#export 设置环境变量

下面来看看让环境变量永久生效的常用设置文件。

  • 用户的环境变量配置文件~/.bash_profile~/.bashrc推荐在(~/.bashrc)文件中设置。

  • 全局环境变量的配置文件/etc/profile/etc/bashrc/etc/profile.d/。**推荐在 /etc/bashrc 文件中设置。**若要在登录后初始化或显示加载内容,则把脚本文件放在/etc/profile.d/下即可(无须加执行权限)。

3.2.2 显示与取消环境变量
  1. 通过echoprintf命令打印环境变量。
  2. envset显示默认的环境变量。
  3. unset消除本地变量和环境变量。

4. Shell 变量进阶知识

4.1 Shell 中特殊变量

4.1.1 Shell 位置参数变量

在 Shell 中存在一些特殊且重要的变量,例如:$0$1,我们称之为位置参数变量。要从命令行、函数或脚本执行等处传递参数时,就需要在 Shell 脚本中使用位置参数变量。

部分位置参数变量如下:

  1. $0,获取当前执行的 Shell 脚本的文件名,如果执行脚本包含了路径,那么就包括脚本路径。
  2. ** n ∗ ∗ ,获取当前执行的 S h e l l 脚本的 ∗ ∗ 第 n 个参数值 ∗ ∗ 。如果 n 大于 9 ,则用大括号括起来,例如 n**,获取当前执行的 Shell 脚本的**第n个参数值**。如果n大于9,则用大括号括起来,例如 n,获取当前执行的Shell脚本的n个参数值。如果n大于9,则用大括号括起来,例如{10},接的参数以空格隔开。
  3. $#,获取当前执行的 Shell 脚本后面接的参数数量
  4. ** ∗ ∗ ∗ ,获取当前 S h e l l 脚本所有传参的参数,不加引号和 ‘ ***,获取当前 Shell 脚本所有传参的参数,不加引号和` ,获取当前Shell脚本所有传参的参数,不加引号和@相同;如果给 ∗ ‘ 加上双引号,例如: ‘ " *`加上双引号,例如:`" 加上双引号,例如:‘"*",则表示将所有的参数视为单个字符串,相当于$1 $2 $3`。
  5. ** @ ∗ ∗ ,获取当前 S h e l l 脚本所有传参的参数,不加引号和 @**,获取当前 Shell 脚本所有传参的参数,不加引号和 @,获取当前Shell脚本所有传参的参数,不加引号和*相同;如果给 @ 加上双引号,例如: ‘ @加上双引号,例如:` @加上双引号,例如:@,则表示将所有的参数视为不同的独立字符串,相当于"$1"、“$2”、“$3”…`,这是将多参数传递给其他程序的最佳方式,因为它会保留所有的内嵌在每个参数里的任何空白。
[dcr@server bin 11:32:07]$ vim show_args.sh 
[dcr@server bin 11:35:38]$ cat show_args.sh 
#!/bin/bash

echo $1
echo $2
echo $10#表示$1$0
[dcr@server bin 11:35:44]$ show_args.sh args{1..9} ding
args1
args2
args10

[dcr@server bin 11:37:03]$ cat show_args.sh
#!/bin/bash

echo $1
echo $2
echo ${10}#大于9要用大括号括起来
[dcr@server bin 11:37:06]$ show_args.sh args{1..9} ding
args1
args2
ding

[dcr@server bin 11:30:14]$ cat show_args.sh 
#!/bin/bash

echo $1
echo $2
echo $#  # $# 显示变量数
[dcr@server bin 11:30:20]$ show_args.sh args{1..9} ding
args1
args2
10
[dcr@server bin 10:49:56]$ vim show_args.sh
#!/bin/ bash

echo $1
echo $2
[dcr@server bin 11:13:31]$ chmod +x show_args.sh 
[dcr@server bin 11:13:41]$ show_args.sh start stop
start
stop
#将start stop两个值分别传给$1  $2

示例1:

[dcr@server bin 11:15:51]$ vim ssh_ctl
[dcr@server bin 11:18:06]$ cat ssh_ctl 
#!/bin/bash
systemctl $1 sshd


[dcr@server bin 11:15:57]$ chmod +x ssh_ctl 

[dcr@server bin 11:16:11]$ ssh_ctl status
● sshd.service - OpenSSH server daemon
   Loaded: loaded (/usr/lib/systemd/system/sshd.service; enabled; vendor preset: enabled)
   Active: active (running) since 一 2025-09-22 15:45:28 CST; 2 weeks 2 days ago
     Docs: man:sshd(8)
           man:sshd_config(5)
 Main PID: 1100 (sshd)
   CGroup: /system.slice/sshd.service
           └─1100 /usr/sbin/sshd -D
[dcr@server bin 11:17:06]$ ssh_ctl stop
==== AUTHENTICATING FOR org.freedesktop.systemd1.manage-units ===
Authentication is required to manage system services or units.
Authenticating as: root
Password: 
==== AUTHENTICATION COMPLETE ===
[dcr@server bin 11:17:21]$ ssh_ctl status
● sshd.service - OpenSSH server daemon
   Loaded: loaded (/usr/lib/systemd/system/sshd.service; enabled; vendor preset: enabled)
   Active: inactive (dead) since 四 2025-10-09 11:17:21 CST; 7s ago
     Docs: man:sshd(8)
           man:sshd_config(5)
  Process: 1100 ExecStart=/usr/sbin/sshd -D $OPTIONS (code=exited, status=0/SUCCESS)
 Main PID: 1100 (code=exited, status=0/SUCCESS)
[dcr@server bin 11:17:29]$ ssh_ctl start
==== AUTHENTICATING FOR org.freedesktop.systemd1.manage-units ===
Authentication is required to manage system services or units.
Authenticating as: root
Password: 
==== AUTHENTICATION COMPLETE ===
[dcr@server bin 11:18:04]$ ssh_ctl status
● sshd.service - OpenSSH server daemon
   Loaded: loaded (/usr/lib/systemd/system/sshd.service; enabled; vendor preset: enabled)
   Active: active (running) since 四 2025-10-09 11:18:04 CST; 1s ago
     Docs: man:sshd(8)
           man:sshd_config(5)
 Main PID: 20808 (sshd)
   CGroup: /system.slice/sshd.service
           └─20808 /usr/sbin/sshd -D

示例2:

[dcr@server bin 11:19:56]$ vim service_ctl
[dcr@server bin 11:20:16]$ cat service_ctl 
#!/bin/bash
systemctl $1 $2
[dcr@server bin 11:20:27]$ ./service_ctl status sshd
● sshd.service - OpenSSH server daemon
   Loaded: loaded (/usr/lib/systemd/system/sshd.service; enabled; vendor preset: enabled)
   Active: active (running) since 四 2025-10-09 11:18:04 CST; 2min 55s ago
     Docs: man:sshd(8)
           man:sshd_config(5)
 Main PID: 20808 (sshd)
   CGroup: /system.slice/sshd.service
           └─20808 /usr/sbin/sshd -D
4.1.2 Shell 进程中的特殊状态变量
$?

作用:获取执行上一个指令的执行状态返回值:0为成功,非零为失败,这个变量最常用。

[dcr@server bin 11:30:48]$ ls 
gather_os_info.sh  hello.sh  service_ctl  show_args.sh  ssh_ctl
[dcr@server bin 11:30:55]$ echo $?
0
#echo $? 值为0 则代表命令执行成功且有结果

[dcr@server bin 11:31:06]$ ls dgaugfj
ls: 无法访问dgaugfj: 没有那个文件或目录
[dcr@server bin 11:31:11]$ echo $?
2
#echo $? 值为2 代表命令执行失败

[dcr@server bin 11:31:15]$ grep selinux /etc/selinux/config
[dcr@server bin 11:31:45]$ echo $?
1
#echo $? 值为1 代表命令执行成功且没有结果

[dcr@server bin 11:31:52]$ grep -i selinux /etc/selinux/config
# This file controls the state of SELinux on the system.
# SELINUX= can take one of these three values:
#     enforcing - SELinux security policy is enforced.
#     permissive - SELinux prints warnings instead of enforcing.
#     disabled - No SELinux policy is loaded.
SELINUX=disabled
# SELINUXTYPE= can take one of three values:
SELINUXTYPE=targeted 
[dcr@server bin 11:32:04]$ echo $?
0

4.2 Shell 内置变量命令

bash Shell 包含一些内置命令。 这些内置命令在目录列表里是看不见的,它们由 Shell 本身提供。常用的内部命令有: echoevalexecreadshift等。

下面简单介绍几个最常用的内置命令的格式和功能。

4.2.1 read
[dcr@server bin 11:42:37]$ vim prepare_os.sh
#!/bin/bash 

#设置防火墙
read -p "请输入start或者stop控制防火墙状态" status
systemctl $status firewalld

#设置ssh
read -p "请输入start或者stop控制sshd服务状态" status1
systemctl $status1 sshd


[dcr@server bin 11:50:35]$ chmod +x prepare_os.sh
[dcr@server bin 11:50:46]$ prepare_os.sh 
请输入start或者stop控制防火墙状态start   
==== AUTHENTICATING FOR org.freedesktop.systemd1.manage-units ===
Authentication is required to manage system services or units.
Authenticating as: root
Password: 
==== AUTHENTICATION COMPLETE ===
请输入start或者stop控制sshd服务状态stop
==== AUTHENTICATING FOR org.freedesktop.systemd1.manage-units ===
Authentication is required to manage system services or units.
Authenticating as: root
Password: 
==== AUTHENTICATION COMPLETE ===
#read 命令实践,添加用户
[root@server ~ 13:37:24]# vim useradd.sh
[root@server ~ 13:56:53]# cat useradd.sh
#!/bin/bash

#提供用户名
read -p  "请输入用户名:" user_name

#提供用户性别
read -p  "请输入用户性别:" user_sex

#提供用户年龄
read -p  "请输入用户年龄:" user_age

#提供用户密码
read -sp  "请输入用户密码:" user_pass
echo

#添加用户
useradd ${user_name}

#设置用户密码
echo ${user_pass} | passwd --stdin ${user_name}

#将以上用户信息存储到/etc/users.info中
echo ${user_name}:x:${user_sex}:${user_age} >> /etc/users.info

#打印用户信息存储到/etc/users.info中
echo "用户信息已经存储到/etc/users.info中。"


[root@server ~ 13:56:56]# bash useradd.sh
请输入用户名:zhangsan
请输入用户性别:male
请输入用户年龄:18
请输入用户密码:
更改用户 zhangsan 的密码 。
passwd:所有的身份验证令牌已经成功更新。
用户信息已经存储到/etc/users.info中。



#或者使用文件,将文件里的内容读取传输给shell
[root@server ~ 13:57:23]# cat /etc/users.info
zhangsan:x:male:18
[root@server ~ 13:57:40]# ls /home/
dcr  zhangsan
[root@server ~ 13:59:44]# userdel -r zhangsan

[root@server ~ 14:20:46]# vim /etc/users.info
[root@server ~ 14:20:57]# cat /etc/users.info
user_name=zhangsan
user_sex=male
user_age=18
user_pass=123
[root@server ~ 14:16:11]# vim useradd-2.sh
[root@server ~ 16:46:27]# cat useradd-2.sh
#!/bin/bash

source /etc/users.info #读取文件里的内容将变量传输给shell
#添加用户
useradd ${user_name}

#设置用户密码
echo ${user_pass} | passwd --stdin ${user_name}

#将以上用户信息存储到/etc/users.info中
echo ${user_name}:x:${user_sex}:${user_age} >> /etc/users.info

#打印用户信息存储到/etc/users.info中
echo "用户信息已经存储到/etc/users.info中。"


[root@server ~ 14:20:54]# bash useradd-2.sh
更改用户 zhangsan 的密码 。
passwd:所有的身份验证令牌已经成功更新。
用户信息已经存储到/etc/users.info中。
[root@server ~ 14:11:43]# cat /etc/users.info
user_name=zhangsan
user_sex=male
user_age=18
[root@server ~ 14:11:46]# source /etc/users.info
[root@server ~ 14:12:05]# echo $user_name
zhangsan

5.变量的数值计算实践

5.1 算术运算符

如果要执行算术运算,就会离不开各种运算符号,和其他编程语言类似,Shell 也有很多算术运算符。

下面就给大家介绍一下常见的 Shell 算术运算符:

  • +、-一元正号和负号。
  • +、-,加法和减法。
  • *、/、%,乘法、除法、取余(取模)。
  • **,幂运算。
  • ++、–,增加及减少,可前置也可放在变量结尾。
  • !、&&、||,逻辑非(取反)、逻辑与(and)、逻辑或(or)。
  • <、<=、>、>=,比较符号(小于、小于等于、大于、大于等于)。
  • ==、!=、=,比较符号(相等、不相等,对于字符串也可以表示相当于)。
  • <<、>>,向左移位、向右移位。
  • ~、|、&、^,按位取反、按位异或、按位与、按位。
  • =、+=、-=、*=、/=、%=,赋值运算符,例如 a+=1 相当于 a=a+1a-=1 相当于 a=a-1

Shell 中常见的算术运算命令:

  • (()),用于整数运算的常用运算符,效率很高。
  • let,用于整数运算,类似于(())
  • expr,可用于整数运算,但还有很多其他的额外功能。(暂不学习)
  • bc,Linux下的一个计算器程序(适合整数及小数运算)。
  • $[],用于整数运算。
  • awk,awk 既可以用于整数运算,也可以用于小数运算。(暂不学习)
  • declare,定义变量值和属性,-i参数可以用于定义整形变量,做运算。(暂不学习)

5.2 (()) 双小括号数值运算命令

双小括号 (()) 的作用是进行数值运算与数值比较,它的效率很高,用法灵活,是企业场景运维人员经常采用的运算操作符。

5.2.1 (()) 双小括号数值运算的基础语法

双小括号 (()) 的操作方法:

  • ((i=i+1)),此种书写方法为运算后赋值法,即将i+1的运算结果赋值给变量i。

    注意:不能用 echo ((i=i+l))输出表达式的值,可以用echo $((i=i+l))输出其值。

  • **i= ( ( i + 1 ) ) ∗ ∗ ,可以在 ‘ ( ( ) ) ‘ 前加 ‘ ((i+1))**,可以在 `(())` 前加 ` ((i+1)),可以在(())前加` 符,表示将表达式运算后赋值给i。

  • (( 8>7 && 5==5)),可以进行比较操作,还可以加入逻辑与和逻辑或,用于条件判断。

  • **echo ( ( 2 + 1 ) ) ∗ ∗ ,需要直接输出运算表达式的运算结果时,可以在 ‘ ( ( ) ) ‘ 前加 ‘ ((2+1))**,需要直接输出运算表达式的运算结果时,可以在 `(())` 前加 ` ((2+1)),需要直接输出运算表达式的运算结果时,可以在(())前加` 符。

5.2.2 (()) 双小括号数运算实践

示例1计算:

[dcr@server ~ 14:41:20]$ ((1+2))
[dcr@server ~ 14:41:30]$ echo $?
0
[dcr@server ~ 14:41:37]$ echo $((1+2))
3

[root@server ~ 16:57:30]# ((i=5))
[root@server ~ 16:57:50]# ((i=i*2))
[root@server ~ 16:58:00]# echo $i
10
[root@server ~ 16:58:06]# echo $((i=i*2))
20

示例2比较:

[root@server ~ 16:58:37]# ((3<8))
[root@server ~ 17:00:26]# echo $?
0 #正确输出0
[root@server ~ 17:00:32]# ((3>8))
[root@server ~ 17:00:40]# echo $?
1 #错误输出1

5.3 let 命令

let运算命令的语法格式为:let 表达式

let表达式的功能等同于:((表达式))

示例:

[dcr@server ~ 14:41:49]$ let 1+2
[dcr@server ~ 14:41:56]$ echo $?
0 #说明已经计算了
[dcr@server ~ 14:41:59]$ let sum=1+2
[dcr@server ~ 14:42:08]$ echo $sum
3 #想要显示必须赋值

5.4 bc 命令

bc 是UNIX/Linux下的计算器,因此,除了可以作为计算器来使用,还可以作为命令行计算工具使用。

示例:

[dcr@server ~ 14:42:15]$ echo $[1.1+2.2]
-bash: 1.1+2.2: 语法错误: 无效的算术运算符 (错误符号是 ".1+2.2"#无法进行小数运算

[dcr@server ~ 14:42:33]$ bc
bc 1.06.95
Copyright 1991-1994, 1997, 1998, 2000, 2004, 2006 Free Software Foundation, Inc.
This is free software with ABSOLUTELY NO WARRANTY.
For details type `warranty'. 
1.1+2.2
3.3
^C
(interrupt) Exiting bc.

#或者利用标准输出
[dcr@server ~ 14:42:44]$ echo 1.1+2.2 | bc
3.3

5.5 $[] 符号的运算

#计算1到100的值和
[dcr@server ~ 14:39:09]$ echo $[$(echo {1..100} | sed 's/ /+/g')]
5050
[dcr@server ~ 14:40:56]$ echo $(($(echo {1..100} | sed 's/ /+/g')))
5050
Logo

葡萄城是专业的软件开发技术和低代码平台提供商,聚焦软件开发技术,以“赋能开发者”为使命,致力于通过表格控件、低代码和BI等各类软件开发工具和服务

更多推荐