Linux shell编程初步认知与变量学习
为什么要学习 Shell 编程?
1 什么是 Shell
Shell 是一个命令解释器,它的作用是解释执行用户输入的命令及程序。用户每输入一条命令, Shell 就解释执行一条。这种从键盘一输入命令,就可以立即得到回应的对话方式,称为交互的方式。
Shell 存在于操作系统的外层,负责与用户直接对话,把用户的输入解释给操作系统,并处理各种各样的操作系统的输出结果,然后输出到屏幕返回给用户。输入系统用户名和密码并登录到Linux后的所有操作都是由 Shell 解释与执行的。
Shell 的英文是壳的意思,从上图可以看出,命令解释器( Shell )就像壳一样包住了系统核心。
在常用的操作系统中:
- Linux 下默认的 Shell 是 Bourne Again shell(bash)。
- Solaris 和 FreeBSD 下默认的是 Bourne shell(sh)。
- AIX 下默认的是 Kom Shell (ksh)。
这里重点讲 Linux 系统环境下的 Bourne Again shell(bash)。
2 什么是 Shell 脚本
理解了 Shell 之后,再理解 Shell 脚本就简单了。当命令或程序语句不在命令行下执行,而是通过一个程序文件来执行时,该程序就被称为 Shell 脚本。
如果在 Shell 脚本里内置了很多条命令、语句及循环控制,然后将这些命令一次性执行完毕,这种通过文件执行脚本的方式称为非交互的方式。
Shell 脚本类似于 DOS 系统下的批处理程序(早期扩展名一般为*.bat
)。用户可以在 Shell 脚本中敲入一系列的命令及命令语句组合。这些命令、变量和流程控制语句等有机地结合起来,就形成了一个功能强大的 Shell 脚本。
3 为什么要学习 Shell 编程
Shell 脚本语言很适合用于处理纯文本类型的数据,而Linux系统中几乎所有的配置文件、日志文件(如NFS、Rsync、Httpd、Nginx、LVS、MySQL等),以及绝大多数的启动文件都是纯文本类型的文件。因此,学好 Shell 脚本语言,就可以利用它在Linux系统中发挥巨大的作用。
Shell 脚本语言是实现Linux/UNIX系统管理及自动化运维所必备的重要工具,Linux/UNIX系统的底层及基础应用软件的核心大都涉及 Shell 脚本的内容。每一个合格的Linux系统管理员或运维工程师,都需要能够熟练地编写 Shell 脚本语言,并能够阅读系统及各类软件附带的 Shell 脚本内容。只有这样才能提升运维人员的工作效率,适应日益复杂的工作环境,减少不必要的重复工作,从而为个人的职场发展奠定较好的基础。
例如:
- 检查多台服务器运行状态
- 自动化部署、升级各种服务器
4 学好 Shell 编程所需的基础知识
本节首先来探讨一下在学习 Shell 编程之前需要掌握的基础知识,需要说明的是,并不是必须具备这些基础知识才可以学习 Shell 编程,而是,如果具备了这些基础知识,那么就可以把 Shell 编程学得更好,领悟得更深。如果只是想简单地了解 Shell 脚本语言,那么就无须掌握太多的系统基础知识,只需要会一些简单的命令行操作即可。
学好 Shell 编程并通过 Shell 脚本轻松地实现自动化管理企业生产系统的必备基础如下:
- 能够熟练使用vim编辑器,熟悉SSH终端及
.vimrc
等的配置。 在Linux下开发 Shell 脚本最常使用的编辑器是 vim,因此如果能够熟练使用并配置好vim的各种高级功能设置,就可以让开发 Shell 脚本达到事半功倍的效果。 - 要有一定的 Linux 命令基础,掌握 Linux 常用命令,并能够熟练使用它们。
- 要熟练掌握Linux正则表达式及三剑客命令(grep、sed、awk)。
- 熟悉常见的Linux网络服务部署、优化、日志分析及排错。
5 如何才能学好 Shell 编程
学好 Shell 编程的核心:多练习—多思考—多总结—再练习—再思考—再总结。
如此循环,坚持即可!
马老师建议:
- 掌握 Shell 脚本基本语法。
- 从简单做起,简单判断,简单循环。
- 多模仿,多练习,多思考。
- 编程变量名字要规范,采用驼峰语法表示。
- 不要拿来主义,特别是新手。
- 形成自己的脚本开发风格。
6 脚本语言的种类
Shell 脚本语言是弱类型语言(无须定义变量的类型即可使用)。
在 Unix/Linux中 主要有两大类 Shell :
-
Bourne shell,包括Bourne shell(sh)、Kom shell(ksh)、Bourne Again Shell (bash)三种类型。
-
C shell,包括 csh、tcsh 两种类型。
-
csh 由 Berkeley 大学开发,随 BSD UNIX 发布,它的流程控制语句很像C语言,支持很多 Bourne shell 所不支持的功能,例如:作业控制、别名、系统算术、命令历史命令行编辑等。
-
tcsh 是 csh 的增强版,加人了命令补全等功能,在 FreeBSD、MacOSX 等系统上替代了 csh。
-
创建第一个 Shell 脚本
1 Shell 脚本
在 Linux 系统中, Shell 脚本通常是在编辑器 vi/vim 中编写的,由UNIX/Linux命令、bash Shell 命令、程序结构控制语句和注释等内容组成。这里推荐用Linux自带的功能更强大的vim编辑器来编写,可以事先做一个别名alias vi=’vim’
,并使其永久生效,这样以后习惯输入 vi 的读者也就可以直接调用 vim
编辑器了。设置方法如下:
[root@test ~ ✔]# echo "alias vi='vim'" >> /etc/bashrc
[root@test ~ ✔]# source /etc/bashrc
1.1 脚本开头
一个规范的 Shell 脚本在第一行会指出由哪个程序(解释器)来执行脚本中的内容,这一行内容在Linux bash 的编程一般为:
#!/bin/bash
或
#!/bin/sh
其中,开头的"#!“字符又称为幻数(其实叫什么都无所谓,知道它的作用就好),在执行bash脚本的时候,内核会根据”#!"字符后的解释器来确定该用哪个程序解释这个脚本中的内容。
注意,这一行必须位于每个脚本顶端的第一行,如果不是第一行则为脚本注释行,
例如下面的例子:
[wsh@test ~ ✔]$ vim script.sh
#!/bin/bash
echo "Hello World !"
CentOS 和 RHEL下默认的 Shell 均为 bash。因此,在写 Shell 脚本的时候,脚本的开头即使不加幻数,也会交给bash解释。如果写脚本不希望使用系统默认的 Shell 解释,那么就必须要指定解释器了,否则脚本文件执行后的结果可能就不是你所要的。
建议读者养成好的编程习惯,不管采用什么脚本,最好都加上相应的开头解释器语言标识,遵守 Shell 编程规范。
1.2 脚本注释
在 Shell 脚本中,跟在# 后面的内容表示注释,用来对脚本进行注释说明,注释部分不会被当作程序来执行,仅仅是给开发者和使用者看的,系统解释器是看不到的,更不会执行。
注释可自成一行,也可以跟在脚本命令的后面与命令在同一行。
开发脚本时,如果没有注释,那么团队里的其他人就会很难理解脚本对应内容的用途,而且若时间长了,自己也会忘记。因此,我们要尽量养成为所开发的 Shell 脚本书写关键注释的习惯,书写注释不光是为了方便别人,更是为了方便自己,避免影响团队的协作效率,以及给后来接手的人带来维护困难。
特别提示一下,注释尽量不要用中文,在脚本中最好也不要有中文。
1.3 bash 与 sh 的区别
早期的bash与sh稍有不同,但大多数脚本都可以不加修改地在sh上运行。
sh为bash的软链接,大多数情况下,脚本的开头使用 #!/bin/bash
和 #!/bin/sh
是没有区别的,但更规范的写法是在脚本的开头使用 #!/bin/bash
。
[wsh@test ~ ✔]$ ls -l /bin/sh
lrwxrwxrwx. 1 root root 4 Aug 1 15:45 /bin/sh -> bash
2 Shell 脚本的执行
当 Shell 脚本运行时,它会先查找系统环境变量ENV,该变量指定了环境文件,再加载了上述环境变量文件后, Shell 就开始执行 Shell 脚本中的内容。
Shell 脚本是从上至下、从左至右依次执行每一行的命令及语句的,即执行完了一个命令后再执行下一个,如果在 Shell 脚本中遇到子脚本(即脚本嵌套)时,就会先执行子脚本的内容,完成后再返回父脚本继续执行父脚本内后续的命令及语句。通常情况下,在执行 Shell 脚本时,会向系统内核请求启动一个新的进程,以便在该进程中执行脚本的命令及子 Shell 脚本,基本流程如下。
Shell 脚本的执行通常可以采用以下几种方式:
-
bash script-name 或sh script-name:这是当脚本文件本身没有可执行权限(即文件权限属性x位为-号)时常使用的方法,或者脚本文件开头没有指定解释器时需要使用的方法。
[wsh@test ~ ✔]$ bash script.sh Hello World!
-
/path/script-name 或 ./script-name:指在当前路径下执行脚本(脚本需要有执行权限),需要将脚本文件的权限先改为可执行,然后通过脚本绝对路径或相对路径就可以直接执行脚本了。
[wsh@test ~ ✔]$ chmod +x script.sh# 绝对路径 [wsh@test ~ ✔]$ /home/wsh/script.sh# 相对路径 [wsh@test ~ ✔]$ ./script.sh Hello World !
在企业生产环境中,不少运维人员在写完 Shell 脚本之后,由于忘记为该脚本设置执行权限,然后就直接应用了,结果导致脚本没有按照自己的意愿手动或定时执行,对于这一点,避免出现该问题的方法就是用第1种方法替代第2种。
-
source script-name 或 . script-name:这种方法通常是使用
source
或.
点号读入或加载指定的 Shell 脚本文件,然后,依次执行指定的 Shell 脚本文件中所有语句。这些语句将在当前父 Shell 脚本进程中运行(其他几种模式都会启动新的进程执行子脚本)。因此,使用source
可以将子脚本中的变量值、函数值等传递到当前父 Shell 脚本中使用。 这是它和其他几种方法最大的区别,也是值得读者特别注意的地方。[wsh@test ~ ✔]$ source script.sh Hello World ! # 等效于 [wsh@test ~ ✔]$ . script.sh Hello World !
-
bash < script-name 或 cat scripts-name|sh:同样适用于bash,这种用法也很常见。
[wsh@test ~ ✔]$ bash < script.sh Hello World ![wsh@test ~ ✔]$ cat script.sh | bash Hello World !
3 Shell 脚本开发的基本规范及习惯
Shell 脚本的开发规范及习惯非常重要,虽然这些规范不是必须要遵守的,但有了好的规范和习惯,可以大大提升开发效率,并能在后期降低对脚本的维护成本。
当多人协作开发时,大家有一个互相遵守的规范就显得更重要了。即使只是一个人开发,最好也采取一套固定的规范,这样脚本将会更易读、更易于后期维护,最重要的是要让自己养成一个一出手就很专业和规范的习惯。
下面来看看有哪些规范:
-
Shell 脚本的第一行是指定脚本解释。
#!/bin/bash
-
Shell 脚本的开头加版本、版权等信息。
#!/bin/bash # Date:Sun Aug 24 16:18:00 CST 2025 # Author:Created by wsh # Description:This script is used to... # Version:2.0
可修改
~/.vimrc
配置文件配置vim
编辑文件时自动加上以上信息的功能。 -
在 Shell 脚本中尽量不用中文(不限于注释)。
尽量用英文注释,防止本机或切换系统环境后中文乱码的困扰。如果非要加中文,请根据自身的客户端对系统进行字符集调整,如:
export LANG="zh_CN.UTF-8"
,并在脚本中,重新定义字符集设置,和系统保持一致。 -
Shell 脚本的命名应以.sh为扩展名。例如:
script-name.sh
。 -
Shell 脚本应存放在固定的路径下。
以下则是 Shell 脚本代码书写的良好习惯:
-
成对的符号应尽量一次性写出来,然后退格在符号里增加内容,以防止遗漏。
这些成对的符号包括:
{} [] '' `` ""
-
中括号([])两端至少要有1个空格,因此,键入中括号时即可留出空格[],然后再退格键入中间的内容,并确保两端都至少有一个空格,即先键入一对中括号,然后退1格,输入两个空格,再退1格,双中括号([[]])的写法也是如此。
-
对于流程控制语句,应一次性将格式写完,再添加内容。
比如,一次性完成if吾句的格式,应为:
if 条件内容;then内容 fi
一次性完成for循环语句的格式,应为:
for do内容 done
提示:while和until,case等语句也是一样。
-
通过缩进让代码更易读,比如:
if 条件内容;then内容 fi
-
对于常规变量的字符串定义变量值应加引号,并且等号前后不能有空格,需要强引用的(指所见即所得的字符引用),则用单引号(’’),如果是命令的引用,则用反引号(‘’)。
例如:
wsh_file="test.txt" week_day="$(date +%A)" welcome_string='******'
-
脚本中的单引号、双引号及反引号必须为英文状态下的符号,其实所有的Linux字符及符号都应该是英文状态下的符号,这点需要特别注意。
说明:好的习惯可以让我们避免很多不必要的麻烦,提升工作效率。
Shell 变量基础知识
1 什么是 Shell 变量
1.1 什么是变量
简单地说,变量名是用一个固定的字符串(字符、数字和下划线的组合,不能以数字开头)代替更多、更复杂的内容,该内容里可能还会包含变量、路径、字符串等其他的内容。
变量是暂时存储数据的地方及数据标记,所存储的数据存在于内存空间中,通过正确地调用内存空间中变量的名字就可以取出与变量对应的数据。使用变量的最大好处就是使程序开发更为方便,当然,在编程中使用变量也是必须的,否则就很难完成相关的程序开发工作。
下面是定义变量和打印变量的示例:
# 定义变量,名字为username,对应的内容wsh
[wsh@test ~ ✔]$$ username="wsh"# 打印变量的值
[wsh@test ~ ✔]$$ echo $username
wsh
变量的赋值方式为:先写变量名称,紧接着是这个字符,最后是值,中间无任何空格。
通过 echo
命令加上 $username
即可输出 username
变量的值。变量的内容一般要加双引号,以防止出错,特别是当值里的内容之间有空格时。
1.2 Shell 变量的特性
默认情况下,在bash Shell 中是不会区分变量类型的。例如:常见的变量类型为整数、字符串、小数等都当做字符串变量。这和其他强类型语言(例如:Java/C语言)是有区别的,当然,如果需要指定 Shell 变量的类型,也可以使用 declare
命令定义变量的类型,但在一般情况下没有这个需求。
1.3 变量类型
变量根据范围可分为两类:
-
全局变量,在创建它们的 Shell 及其派生出来的任意子进程 Shell 中使用。
-
局部变量,只能在创建它们的 Shell 函数或 Shell 脚本中使用。
变量根据是否是用户自定义也可分为两类:
- 普通变量:也称为常规变量,由开发者在开发脚本程序时创建。
- 环境变量:定义shell 执行环境。环境变量又可分为自定义环境变量和bash内置的环境变量。
2 环境变量
环境变量一般是指用export内置命令导出的变量,用于定义 Shell 的运行环境,保证 Shell 命令的正确执行。 Shell 通过环境变量来确定登录用户名、命令路径、终端类型、登录目录等,所有的环境变量都是系统全局变量,可用于所有子进程中,这包括编辑器、 Shell 脚本和各类应用。
环境变量可以在命令行中设置和创建,但用户退出命令行时这些变量值就会丢失,因此,如果希望永久保存环境变量,可在用户家目录下的 .bash_profile
或 .bashrc
(非用户登录模式特有,例如远程SSH)文件中,或者全局配置 /etc/bashrc
(非用户登录模式特有,例如远程SSH)或 /etc/profile
文件中定义。在将环境变量放入上述的文件中后,每次用户登录时这些变量都将被初始化。
按照系统规范,所有环境变量的名字均采用大写形式。在将环境变量应用于用户进程程序之前,都应该用 export
命令导出定义。有一些环境变量,比如HOME
、PATH
、USER
等,在用户登录之前就已经被/bin/login程序设置好了。通常环境变量被定义并保存在用户家目录下的 .bash_profile
文件或全局的配置文件 /etc/profile
中。
部分bash环境变量展示:
- EDITOR,默认编辑器。
- HISTFILE,历史文件位置。
- HISTSIZE,历史命令个数。
- PS1,命令行提示符。
- LANG,Shell 环境语言。
- PATH,命令搜素路径。
- HOME,当前用户家目录。
- USER,当前用户。
- PWD,当前 Shell 路径。
- IFS,字符串分隔符。
在查看设置的变量时,有3个命令可以显示变量的值:
- set 命令,输出所有的变量,包括全局变量和局部变量。
- env 命令,只显示全局变量,包括shell的环境。
- declare 命令,输出所有的变量、函数、整数和已经导出的变量。
set -o
命令显示bash Shell 的所有参数配置信息。
2.1 设置环境变量
1. 设置环境变量
如果想要设置环境变量,就要在给变量赋值之后或在设置变量时使用export
命令,具体设置见下文的示例。其实,除了 export
命令,带-x
选项的declare
内置命令也可以完成同样的功能(注意:此处不要在变量名前面加$)。
export
命令和 declare
命令的格式如下:
export 变量名=value
变量名=value ; export 变量名
declare -x 变量名=value
示例:
export NAME=wsh
declare -x NAME=wsh
NAME=wsh;export NAME
下面来看看让环境变量永久生效的常用设置文件。
-
用户的环境变量配置文件:
~/.bash_profile
和~/.bashrc
。推荐在(~/.bashrc)文件中设置。 -
全局环境变量的配置文件:
/etc/profile
、/etc/bashrc
、/etc/profile.d/
。推荐在 /etc/bashrc 文件中设置。 若要在登录后初始化或显示加载内容,则把脚本文件放在/etc/profile.d/
下即可(无须加执行权限)。
2. 设置登录后提示信息
-
第一种是在
/etc/motd
里增加提示的字符串,如下:[wsh@shell ~]$ sudo vim /etc/motd welcome to wsh linux Shell training.#登录后效果 Last login: Sun Aug 24 15:32:46 2025 from 10.1.8.1 welcome to wsh linux Shell training.
-
第二种是在 /etc/profile.d/ 下面增加如下脚本。
[wsh@shell ~]$ sudo vim /etc/profile.d/wsh.sh echo Welcome to wsh linux Shell training.
补充:
- 登录前提示符由文件 /etc/issue 提供。
- 登录后时间提示符由ssh服务提供:在
/etc/ssh/sshd_config
文件中配置PrintLastLog yes
,默认启用。
2.2 显示与取消环境变量
- 通过
echo
或printf
命令打印环境变量。 - 用
env
或set
显示默认的环境变量。 - 用
unset
消除本地变量和环境变量。
2.3 环境变量初始化与对应文件的生效顺序
在登录 Linux 系统并启动一个bash shell
时,默认情况下 bash
会在若干个文件中查找环境变量的设置。这些文件可统称为系统环境文件。bash
检查的环境变量文件的情况取决于系统运行 Shell 的方式。
系统运行 Shell 的方式一般有3种:
- 通过系统用户登录后默认运行的 Shell 。
- 非登录交互式运行 Shell 。
- 执行脚本运行非交互式 Shell 。
当用户登录 Linux 系统时, Shell 会作为登录 Shell 启动。此时的登录 Shell 加载环境变量的顺序如下图所示。
-
用户登录系统后首先会加载
/etc/profile
全局环境变量文件,这是Linux系统上默认的 Shell 主环境变量文件。系统上每个用户登录都会加载这个文件。 -
当加载完
/etc/profile
文件后,才会执行/etc/profile.d
目录下的脚本文件,这个目录下的脚本文件有很多,例如:系统的字符集设置等。 -
之后开始运行
$HOME/.bash_profile
。在这个文件中,又会去找$HOME/.bashrc
:如果有,则执行;如果没有,则不执行。在HOME/.bashrc
文件中又会去找/etc/bashrc
(全局环境变量文件),如果有,则执行,如果没有,则不执行。 -
如果用户的 Shell 不是登录时启动的,比如手动敲下bash时启动,那么这种非登录 Shell 只会加载
$HOME/.bashrc
(用户环境变量文件),并会去找/etc/bashrc
(全局环境变量文件)。因此如果希望在非登录 Shell 下也可读到设置的环境变量等内容,就需要将变量设定等写入
$HOME/.bashrc
或者/etc/bashrc
,而不是$HOME/.bash_profile
或/etc/profile
。
2.4 环境变量的知识小结
- 变量名通常要大写。
- 变量可以在自身的 Shell 及子 Shell 中使用。
- 常用
export
来定义环境变量。 - 执行
env
默认可以显示所有的环境变量名称及对应的值。 - 输出时用
$变量名
,取消时用unset变量名
。 - 书写
crond
定时任务时要注意,脚本要用到的环境变量最好先在所执行的 Shell 脚本中重新定义。 - 如果希望环境变量永久生效,则可以将其放在用户环境变量文件或全局环境变量文件里。
3 普通变量
本地变量在用户当前 Shell 生存期的脚本中使用。例如,本地变量wsh
的定义为bingbing
, 这个值只在用户当前 Shell 生存期中有意义。如果在 Shell 中启动另一个进程或退出,那么变量wsh
的值将会无效。
3.1 普通变量定义
为普通变量的定义赋值,一般有以下3 种写法:
变量名=value
,赋值时不加引号。变量名='value'
,赋值时加单引号。变量名="value"
,赋值时加双引号。
3.2 变量名及变量值要求
- 变量名:一般是由字母、 数字 、下划线组成的,只可以以字母或下划线开头,例如:
wsh
、wsh_123
、wsh_training
。 - 变量值:可以用单引号或双引号引起来,也可不加引号,但是这三者的含义是不同的。
3.3 普通变量的定义及输出示例
采用不同的方式对普通变量进行定义,并打印输出。
示例1:
[wsh@shell ~]$ a=192.168.1.2
[wsh@shell ~]$ b='192.168.1.2'
[wsh@shell ~]$ c="192.168.1.2"
[wsh@shell ~]$ echo "a=$a"
a=192.168.1.2
[wsh@shell ~]$ echo "b=$b"
b=192.168.1.2
[wsh@shell ~]$ echo "c=${c}"
c=192.168.1.2
可见, 将连续的普通字符串的内容赋值给变量,不管用不用引号,或者不管用什么引号,它的内容是什么,打印变量时就会输出什么。
示例2:
[wsh@shell ~]$ a=192.168.1.2-$a
[wsh@shell ~]$ echo "a=$a"
a=192.168.1.2-192.168.1.2[wsh@shell ~]$ b='192.168.1.2-$a'
[wsh@shell ~]$ echo "b=$b"
b=192.168.1.2-$a[wsh@shell ~]$ c="192.168.1.2-$a"
[wsh@shell ~]$ echo "c=${c}"
c=192.168.1.2-192.168.1.2-192.168.1.2
-
变量定义的基本技巧总结
-
a=192.168.1.2-$a
, 定义a变量的方式是不加任何引号直接定义变量的内容, 当内容为简单连续的数字、字符串、路径名时,可以这样用,例如: a=1,b=wsh等。不加引号时,值里有变量的会被解析后再输出,上述变量定义中因为$a的值被解析为192.168.1.2 ,因此新的a值就是192.168.1.2-192.168.1.2。 -
b='192.168.1.2-$a'
,定义b 变量的方式是通过单引号定义。这种定义方式的特点是:输出变量内容时单引号里是什么就输出什么,即使内容中有变量和命令(命令需要反引起来)也会把它们原样输出。 这种方式比较适合于定义显示纯字符串的情况,即不希望解析变量 、命令等的场景,因此, 对于这里的b 的值,定义时看到的是什么就输出什么,即192.168.1.2-$a。
-
c="192.168.1.2-$a"
,定义c变量的方式是通过双引号定义变量。这种定义方式的特点是:输出变量内容时引号里的变量及命令会经过解析后再输出内容,而不是把双引号中的变量名及命令 (命令需要反引起来)原样输出。 这种方式比较适合于字符串中附带有变量及命令且想将其解析后再输出的变量定义。
-
3.4 把命令的结果作为变量的内容赋值
常见方法有两种,以ls命令为例:
- 变量名=`command`,把命令用反引号引起来,不推荐使用这种方法,因为容易和单引号混淆。
- 变量名=$(command) ,把命令用**$()**括起来 ,推荐使用这种方法。
[wsh@shell ~]$ ls
script.sh
[wsh@shell ~]$ CMD1=`ls`
[wsh@shell ~]$ echo $CMD1
script.sh[wsh@shell ~]$ CMD2=$(pwd)
[wsh@shell ~]$ echo $CMD2
/home/wsh[wsh@shell ~]$ date +%A
Friday
[wsh@shell ~]$ echo Today is $(date +%A).
Today is Friday.
生产场景中把命令的结果作为变量的内容进行赋值的方法在脚本开发时很常见。
3.5 变量定义技巧总结
可以多学习和模仿操作系统自带的 /etc/init.d/functions
函数库脚本的定义思路,多学习Linux系统脚本中的定义,有经验的读者最终应形成一套适合自己的规范和习惯。
-
变量名及变量内容定义小结。
-
变量名只能包含字母、数字或下划线,只能以字母或下划线开头。
-
变量名的定义要有一定的规范,并且要见名知意。
示例:
# 每个单词的首字母大写的写法 wshAge=l# 单词之间用_ wsh_age=l# 驼峰语法:首个单词的首字母小写,其余单词首字母大写 wshAge=l8 wshSex=man# 单词全大写 wshAGE=1
-
一般的变量定义、赋值常用双引号;简单连续的字符串可以不加引号;希望原样输出时使用单引号。
-
希望变量的内容是命令的解析结果时,要用反引号’`',或者用
$()
把命令括起来再赋值。
-
-
Shell 定义变量
a=1
里等号是赋值的意思;比较变量是否相等时也可以用=
或==
。 -
打印输出及使用变量
- 打印输出或使用变量时,变量名前要接
$
符号;变量名后面紧接其他字符的时候,要用大括号将变量部分单独括起来;在unset、export、(())等场景中使用但不打印变量时不加$,这个有些例外。 - 打印输出或使用变量时,一般加双引号或不加引号;如果是字符串变量,最好加双引号;希望原样输出时使用单引号。
- 打印输出或使用变量时,变量名前要接
Shell 变量进阶知识
1 Shell 中特殊变量
1.1 Shell 位置参数变量
在 Shell 中存在一些特殊且重要的变量,例如:$0
、$1
,我们称之为位置参数变量。要从命令行、函数或脚本执行等处传递参数时,就需要在 Shell 脚本中使用位置参数变量。
部分位置参数变量如下:
- $0 ,获取当前执行的 Shell 脚本的文件名,如果执行脚本包含了路径,那么就包括脚本路径。
- $n ,获取当前执行的 Shell 脚本的 第n个参数值 。如果n大于9,则用大括号括起来,例如${10},接的参数以空格隔开。
- $# ,获取当前执行的 Shell 脚本后面接的参数数量。
- $* ,获取当前 Shell 脚本所有传参的参数,不加引号和
$@
相同;如果给$*
加上双引号,例如:"$*"
,则表示将所有的参数视为单个字符串,相当于$1 $2 $3
。 - $@ ,获取当前 Shell 脚本所有传参的参数,不加引号和KaTeX parse error: Undefined control sequence: \* at position 1: \̲*̲相同;如果给@加上双引号,例如:
$@
,则表示将所有的参数视为不同的独立字符串,相当于"$1"、"$2"、"$3"...
,这是将多参数传递给其他程序的最佳方式,因为它会保留所有的内嵌在每个参数里的任何空白。
示例1:showargs.sh 内容如下
#!/bin/bash
echo $0
echo $1
echo $2
echo $10
echo ${10}
echo $#
echo $*
echo $@
echo "$@"
[wsh@test ~ ✔]$ bash showargs.sh {a..z}
showargs.sh
a
b
a0
j
26
a b c d e f g h i j k l m n o p q r s t u v w x y z
a b c d e f g h i j k l m n o p q r s t u v w x y z
a b c d e f g h i j k l m n o p q r s t u v w x y z
示例2:ssh_ctl 内容如下
#!/bin/bash
systemctl $1 sshd
执行效果如下:
[wsh@test ~ ✔]$ sudo ssh_ctl stop
[wsh@test ~ ✔]$ sudo ssh_ctl status
[wsh@test ~ ✔]$ sudo ssh_ctl start
1.2 Shell 进程中的特殊状态变量
$?
作用:获取执行上一个指令的执行状态返回值:0为成功,非零为失败,这个变量最常用。
[wsh@test ~ ✔]$ ls
hello script.sh showargs.sh
[wsh@test ~ ✔]$ echo $?
0
[wsh@test ~ ✔]$ ls /root
ls: 无法打开目录/root: 权限不够
[wsh@test ~ ✔]$ echo $?
2# man ls查看,退出码含义
[wsh@test ~ ✔]$ man ls
......Exit status:0 if OK,1 if minor problems (e.g., cannot access subdirectory),2 if serious trouble (e.g., cannot access command-line argument).
$$(不重要)
作用:获取当前执行的 Shell 脚本的进程号(PID),这个变量不常用,了解即可。
[wsh@test ~ ✔]$ echo $$
11373
[wsh@test ~ ✔]$ kill -9 $$Connection closed.Disconnected from remote host(controller) at 16:39:44.Type `help' to learn how to use Xshell prompt.[wsh@test ~ ✔]$ echo $$
11428
$!(不重要)
作用:获取上一个在后台工作的进程的进程号(PID),这个变量不常用,了解即可。
[wsh@test ~ ✔]$ echo $$
11428
[wsh@test ~ ✔]$ md5sum /dev/zero &
[1] 11454
[wsh@test ~ ✔]$ echo $!
11454
[wsh@test ~ ✔]$ ps o pid,%cpu,%mem,command $!PID %CPU %MEM COMMAND11454 98.3 0.0 md5sum /dev/zero
[wsh@test ~ ✔]$ kill $!
[1]+ Terminated md5sum /dev/zero
$_
作用:获取在此之前执行的命令或脚本的最后一个参数,这个变量不常用,了解即可。
[wsh@test ~ ✔]$ ls /etc/hosts /etc/fstab /etc/hostname
/etc/fstab /etc/hostname /etc/hosts
[wsh@test ~ ✔]$ cat $_
test.wsh.cloud
[wsh@test ~ ✔]$ cat /etc/hostname
test.wsh.cloud
2 Shell 内置变量命令
bash Shell 包含一些内置命令。 这些内置命令在目录列表里是看不见的,它们由 Shell 本身提供。常用的内部命令有: echo
、eval
、exec
、read
、shift
等。
下面简单介绍几个最常用的内置命令的格式和功能。
2.1 echo
echo命令参数选项:
- -n,不换行输出内容。
- -e,解析转义字符(见下面的字符)
转义字符:
- \n,换行。
- \t,制表符(tab)。
- \b,退格。
[wsh@test ~ ✔]$ echo -n "wsh ";echo wym
wsh wym
[wsh@test ~ ✔]$ echo -e "wsh\nwym"
wsh
wym
[wsh@test ~ ✔]$ echo -e "wsh\twym"
wsh wym[wsh@test ~ ✔]$ echo -e "1\b23"
23[wsh@test ~ ✔]$ echo -e "123\b"
123# 后面有东西才会覆盖
[wsh@shell scripts]$ echo -ne "123\b";echo haha
12haha
2.2 eval
当 Shell 程序执行到 eval 语句时, Shell 读入参数args,并将它们组合成一个新的命令,然后执行。
示例1:
[wsh@test ~ ✔]$ vim noeval.sh
echo \$$#
[wsh@test ~ ✔]$ bash noeval.sh hello world
$2[wsh@test ~ ✔]$ vim eval.sh
eval "echo \$$#"
[wsh@test ~ ✔]$ bash eval.sh hello world
world
示例2:
[wsh@test ~ ✔]$ ssh-agent
SSH_AUTH_SOCK=/tmp/ssh-OOl8dkeZVl9n/agent.11473; export SSH_AUTH_SOCK;
SSH_AGENT_PID=11474; export SSH_AGENT_PID;
echo Agent pid 11474;[wsh@test ~ ✔]$ eval $(ssh-agent)
Agent pid 11477
# 等效于
[wsh@test ~ ✔]$ ssh-agent > /tmp/env
[wsh@test ~ ✔]$ source /tmp/env
[wsh@test ~ ✔]$ rm -f /tmp/env
2.3 read
从标准输入读取字符串等信息, 传给 Shell 程序内部定义的变量。
[wsh@test ~ ✔]$ cat read.sh
#!/bin/sh
read -p "输入你想要说的话:" str
echo "你想要说的话是:$str"[wsh@test ~ ✔]$ bash read.sh
输入你想要说的话:`hello world`
你想要说的话是:hello world# 不显示输入的内容
[wsh@test ~ ✔]$ read -s -p "请设置用户密码: " password
请设置用户密码:
[wsh@test ~ ✔]$echo $password
redhat
2.4 exec
exec 命令能够在不创建新的子进程的前提下, 转去执行指定的命令, 当指定的命令执行完毕后, 该进程 (也就是最初的 Shell ) 就终止了。
示例1:
[wsh@test ~ ✔]$ sudo -i
[root@test ~ ✔]# ps $$PID TTY STAT TIME COMMAND11507 pts/1 S 0:00 -bash
[root@test ~ ✔]# exec sleep 10
# sleep命令运行完成后自动返回到原先普通用户shell了。
[wsh@test ~ ✔]$ # 在exec命令执行期间,新开一个窗口,查看PID是1741的进程信息
[wsh@test ~ ✔]$ ps 11507PID TTY STAT TIME COMMAND11507 pts/1 S+ 0:00 sleep 10
示例2: 读取文件内容
[wsh@test ~ ✔]$ cat exec.sh
#!/bin/bash# 生成序列内容文件
seq 5 > /tmp/seq.log# 从文件中读取内容,作为shell的标准输入
exec < /tmp/seq.log# read命令接收标准输入
while read line
doecho $line
done[wsh@test ~ ✔]$ bash exec.sh
1
2
3
4
5
2.5 shift
shift 语句会按如下方式重新命名所有的位置参数变量,即$2成为$1、$3成为2等,以此类推,在程序中每使用一次shift语句,都会使所有的位置参数依次向左移动一个位置,并使位置参数2等,以此类推,在程序中每使用一次shift语句,都会使所有的位置参数依次向左移动一个位置,并使位置参数2等,以此类推,在程序中每使用一次shift语句,都会使所有的位置参数依次向左移动一个位置,并使位置参数#减1,直到减到0为止。
如果脚本有3个参数,那么执行一次shift后 ,$3就变成了$2,$2变成了$1,原先的$1就消失了。
示例1:
[wsh@test ~ ✔]$ cat shift.sh
#!/bin/sh
echo $1
shift
echo $1[wsh@test ~ ✔]$ bash shift.sh hello world
hello
world
示例2: shell脚本接收中横杠-开头的选项参数。
[wsh@test ~ ✔]$ cat shift.sh
#!/bin/sh
if [ "$1" = "-c" ];thenshift
fi
command="$1"
echo $command
[wsh@test ~ ✔]$ bash shift.sh -c date
date
3 Shell 变量子串知识
3.1 Shell 变量子串介绍
读者可以执行 man bash 命令,然后搜索“Parameter Expansion”查找相关的帮助内容。
注意: 对于 Shell 新手来说,此部分内容可以暂时忽略,在学完本书后再回来学习。
常见 Shell 变量子串说明:
- ${parameter},返回变量 parameter 的内容。
- ${#parameter},返回变内容的长度(按字符),也适用于特殊变量。
- ${parameter:offset},在变量parameter中,从位置offset之后开始提取子串到结尾。
- ${parameter:offset:length},在变量parameter中,从位置offset之后开始提取长度为length
的子串。 - ${parameter#word},从变量parameter开头开始删除最短匹配的word子串。
- ${parameter##word},从变量parameter开头开始删除最长匹配的word子串。
- ${parameter%word},从变量parameter结尾开始删除最短匹配的word子串。
- ${parameter%%word},从变量parameter结尾开始删除最长匹配的word子串。
- ${parameter/pattem/string},使用string代替第一个匹配的pattern。
- ${parameter//pattem/string},使用string代替所有匹配的pattern。
3.2 Shell 变量子串的实践
示例1: ${parameter}
[wsh@test ~ ✔]$ str="abc123abc123"[wsh@test ~ ✔]$ echo ${str}
abc123abc123
示例2:${#parameter}
[wsh@test ~ ✔]$ str="abc123abc123"# 此方法获取字符串长度速度最快
[wsh@test ~ ✔]$ echo ${#str}
12# 其他方式计算字符串长度
[wsh@test ~ ✔]$ echo ${str} | wc -L
12
[wsh@test ~ ✔]$ expr length "${str}"
12
[wsh@test ~ ✔]$ echo "$str" | awk '{ print length($0)}'
12
示例3:${parameter:offset}
和 ${parameter:offset:length}
[wsh@test ~ ✔]$ str="abc123abc123"# 提取子串
[wsh@test ~ ✔]$ echo ${str:3}
123abc123
[wsh@test ~ ✔]$ echo ${str:3:4}
123a
示例4:${parameter#word}
和 ${parameter##word}
[wsh@test ~ ✔]$ str="abc123abc123"# 从左侧开始删除子串
[wsh@test ~ ✔]$ echo ${str#b*a}
abc123abc123# 未匹配到,第一个字符必须与元字串第一个子符一致
[wsh@test ~ ✔]$ echo ${str#a*c}
123abc123
[wsh@test ~ ✔]$ echo ${str##a*c}
123
示例5:${parameter%word}
和 ${parameter%%word}
[wsh@test ~ ✔]$ str="abc123abc123"# 从右侧开始删除子串
[wsh@test ~ ✔]$ echo ${str%a*c}
abc123abc123# 未匹配到,最后一个字符必须与元字串最后一个子符一致
[wsh@test ~ ✔]$ echo ${str%c*3}
abc123ab
[wsh@test ~ ✔]$ echo ${str%%c*3}
ab
示例6:${parameter/pattem/string}
[wsh@test ~ ✔]$ str="abc123abc123"# 只替换第一个
[wsh@test ~ ✔]$ echo ${str/abc/def}
def123abc123# 所有的全替换
[wsh@test ~ ✔]$ echo ${str//abc/def}
def123def123
3.3 变量子串的生产场景应用案例
示例1: 替换文件名中特定字符串
[wsh@test ~ ✔]$ touch stu-202212-snap.jpg
[wsh@test ~ ✔]$ ls stu-*
stu-202212-snap.jpg[wsh@test ~ ✔]$ file="stu-202212-snap.jpg"
[wsh@test ~ ✔]$ mv $file ${file/2022/2021}
[wsh@test ~ ✔]$ ls stu-*
stu-202112-snap.jpg
示例2: 删除文件名中特定字符串
[wsh@test ~ ✔]$ touch stu-202212-snap.jpg
[wsh@test ~ ✔]$ ls stu-*
stu-202212-snap.jpg[wsh@test ~ ✔]$ file="stu-202212-snap.jpg"
[wsh@test ~ ✔]$ mv $file ${file/-snap/}
[wsh@test ~ ✔]$ ls stu-*
stu-202212.jpg
4 Shell 特殊扩展变量
4.1 Shell 特殊扩展变量介绍
读者可以执行man bash命令,然后搜索“Parameter Expansion”查找相关的帮助内容。
注意: 对于 Shell 新手来说,此部分内容可以暂时忽略,在学完本书后再回来学习。
常见 Shell 特殊扩展变量使用说明:
-
${parameter:-word},如果parameter的变量值为空或未赋值,则会返回word字符串。
用途: 如果变量未定义,则返回备用的值,防止变量为空值或因未定义而导致异常。
-
${parameter:=word},如果parameter的变量值为空或未赋值,则设置这个变量值为word,并返回其值。位置变量和特殊变量不适用。
用途: 基本同上一个${parameter:-word},但该变量又额外给parameter变量赋值了。
-
${parameter:?word},如果parameter变量值为空或未赋值,那么word字符串将被作为标准错误输出,否则输出变量的值。
用途: 用于捕捉由于变量未定义而导致的错误,并退出程序。
-
${parameter:+word},如果parameter变量值为空或未赋值,则什么都不做,否则word字符串将替代变量的值。
说明: 每个表达式内的冒号都是可选的。**如果省略了表达式中的冒号,则将每个定义中的“为空或未赋值”部分改为“未赋值”,也就是说,运算符仅用于测试变量是否未赋值。
4.2 Shell 特殊扩展变量的实践
示例1:${parameter:-word}
# 变量未定义情况
[wsh@test ~ ✔]$ unset test
[wsh@test ~ ✔]$ echo ${test:-UNSET}
UNSET
[wsh@test ~ ✔]$ echo $test# 变量定义情况
[wsh@test ~ ✔]$ test=hello
[wsh@test ~ ✔]$ echo ${test:-UNSET}
hello
示例2:${parameter:=word}
# 变量未定义情况
[wsh@shell ~]$ unset SHELL
[wsh@shell ~]$ echo ${SHELL:=/bin/bash}
/bin/bash
[wsh@shell ~]$ echo $SHELL
/bin/bash# 变量定义情况
[wsh@shell ~]$ echo ${SHELL:=/bin/sh}
/bin/bash
[wsh@shell ~]$ echo $SHELL
/bin/bash
示例3:${parameter:?word}
# 变量未定义情况
[wsh@test ~ ✔]$ unset test
[wsh@test ~ ✔]$ echo ${test:?UNSET}
-bash: test: UNSET# 变量定义情况
[wsh@test ~ ✔]$ test=hello
[wsh@test ~ ✔]$ echo ${test:?UNSET}
hello
示例4:${parameter:+word}
# 变量未定义情况
[wsh@test ~ ✔]$ unset test
[wsh@test ~ ✔]$ echo ${test:+UNSET}# 变量定义情况
[wsh@test ~ ✔]$ test=hello
[wsh@test ~ ✔]$ echo ${test:+UNSET}
UNSET
4.3 Shell 特殊扩展变量的生产场景应用案例
示例1: 实现 Apache
服务启动脚本/etc/init.d/httpd
# /bin/bash
......
# Start httpd in the C locale by default.
HTTPD_LANG=${HTTPD_LANG:-"C" }
......
httpd=${HTTPD:-/usr/sbin/httpd}
pidfile=${PIDFILE:-/var/run/httpd.pid}
lockfile=${LOCKFILE:-/var/lock/subsys/httpd}
示例2: 删除过期文件
#!/bin/bash
# 防止path变量未定义,导致异常结果
find ${path:-/tmp} -name "*.tar.gz" -type f -mtime +7 |xargs rm -f
在企业中,针对目录路径等的处理就可以采用上述变量不存在(即赋指定值)的方式,防止因目录路径不存在而导致的异常。
例如:变量如果为NULL或没有定义,则赋予一个备用的值,特别是针对变量的删除操作,这种方式会很有用,否则所删除的变量如果不存在,则可能导致未知的危险。