Shell 编程
文章目录
- Shell 编程
- 创建第一个 Shell 脚本
- Shell 脚本
- 脚本开头
- 脚本注释
- bash 与 sh 的区别
- Shell 脚本的执行
- 示例
- Shell 中特殊变量
- Shell 位置参数变量
- Shell 进程中的特殊状态变量
- $?
- Shell 内置变量命令
- echo
- read
- 示例1
- 示例2
- 示例3
- 变量的数值计算实践
- Shell 中常见的算术运算命令
- (()) 双小括号数运算实践
- let 命令
- bc 命令
- Shell 脚本的条件测试
- 条件测试方法综述
- test 条件测试
Shell 编程
创建第一个 Shell 脚本
Shell 脚本
在 Linux 系统中, Shell 脚本通常是在编辑器 vi/vim 中编写的,由UNIX/Linux命令、bash Shell 命令、程序结构控制语句和注释等内容组成。这里推荐用Linux自带的功能更强大的vim编辑器来编写,可以事先做一个别名
alias vi='vim'
,并使其永久生效,这样以后习惯输入 vi 的读者也就可以直接调用vim
编辑器了。设置方法如下:
[root@server ~ 16:50:18]# echo "alias vi='vim'" >> /etc/bashrc
[root@server ~ 16:50:47]# source /etc/bashrc
脚本开头
一个规范的 Shell 脚本在第一行会指出由哪个程序(解释器)来执行脚本中的内容,这一行内容在Linux bash 的编程一般为:
#!/bin/bash
在执行bash脚本的时候,内核会根据"#!"字符后的解释器来确定该用哪个程序解释这个脚本中的内容。
脚本注释
在 Shell 脚本中,跟在
#
后面的内容表示注释,用来对脚本进行注释说明,注释部分不会被当作程序来执行,仅仅是给开发者和使用者看的,系统解释器是看不到的,更不会执行。注释可自成一行,也可以跟在脚本命令的后面与命令在同一行。
开发脚本时,如果没有注释,那么团队里的其他人就会很难理解脚本对应内容的用途,而且若时间长了,自己也会忘记。因此,我们要尽量养成为所开发的 Shell 脚本书写关键注释的习惯,书写注释不光是为了方便别人,更是为了方便自己,避免影响团队的协作效率,以及给后来接手的人带来维护困难。
特别提示一下,注释尽量不要用中文,在脚本中最好也不要有中文。
bash 与 sh 的区别
早期的bash与sh稍有不同,但大多数脚本都可以不加修改地在sh上运行。
sh为bash的软链接,大多数情况下,脚本的开头使用
#!/bin/bash
和#!/bin/sh
是没有区别的,但更规范的写法是在脚本的开头使用#!/bin/bash
。
[wan@server ~ 16:57:16]$ ll /bin/sh
lrwxrwxrwx. 1 root root 4 9月 2 10:18 /bin/sh -> bash
Shell 脚本的执行
Shell 脚本的执行通常可以采用以下几种方式:
bash script-name 或sh script-name:这是当脚本文件本身没有可执行权限(即文件权限属性x位为-号)时常使用的方法,或者脚本文件开头没有指定解释器时需要使用的方法。
[wan@server ~ 16:59:23]$ bash script.sh
Hello World!
/path/script-name 或 ./script-name:指在当前路径下执行脚本(脚本需要有执行权限),需要将脚本文件的权限先改为可执行,然后通过脚本绝对路径或相对路径就可以直接执行脚本了。
[wan@server ~ 16:59:33]$ chmod +x script.sh# 绝对路径
[wan@server ~ 16:59:55]$ /home/wan/script.sh# 相对路径
[wan@server ~ 17:00:13]$ ./script.sh
Hello World!
source script-name 或 . script-name:这种方法通常是使用
source
或.
点号读入或加载指定的 Shell 脚本文件,然后,依次执行指定的 Shell 脚本文件中所有语句。这些语句将在当前父 Shell 脚本进程中运行(其他几种模式都会启动新的进程执行子脚本)。因此,**使用source
可以将子脚本中的变量值、函数值等传递到当前父 Shell 脚本中使用。**这是它和其他几种方法最大的区别,也是值得读者特别注意的地方。
[wan@server ~ 17:00:42]$ source script.sh
Hello World!
# 等效于
[wan@server ~ 17:01:23]$ . script.sh
Hello World!
bash < script-name 或 cat scripts-name|sh:同样适用于bash,这种用法也很常见。
[wan@server ~ 17:01:29]$ bash < script.sh
Hello World![wan@server ~ 17:02:12]$ cat script.sh | bash
Hello World!
示例
# 创建所需目录
[wan@server ~ 17:05:02]$ mkdir bin# 编写脚本
[wan@server ~ 17:05:08]$ vim ssh_ctl
# 在文件中写入以下内容
#!/bin/bash
systemctl $1 sshd# 赋予文件可执行权限
[wan@server ~ 17:06:33]$ chmod +x ssh_ctl# 普通用户没有权限,切换root用户
[wan@server ~ 17:08:07]$ su - root# 执行脚本
[root@server bin 17:09:38]# bash ssh_ctl status
● sshd.service - OpenSSH server daemonLoaded: loaded (/usr/lib/systemd/system/sshd.service; enabled; vendor preset: enabled)Active: active (running) since 四 2025-10-09 16:46:27 CST; 23min agoDocs: man:sshd(8)man:sshd_config(5)Main PID: 86202 (sshd)CGroup: /system.slice/sshd.service└─86202 /usr/sbin/sshd -D10月 09 16:46:27 server systemd[1]: Starting OpenSSH server da...
10月 09 16:46:27 server sshd[86202]: Server listening on 0.0.0...
10月 09 16:46:27 server sshd[86202]: Server listening on :: po...
10月 09 16:46:27 server systemd[1]: Started OpenSSH server dae...
10月 09 16:57:15 server sshd[109383]: Accepted password for wa...
Hint: Some lines were ellipsized, use -l to show in full.[root@server bin 17:09:53]# bash ssh_ctl stop[root@server bin 17:10:15]# bash ssh_ctl start
Shell 中特殊变量
Shell 位置参数变量
在 Shell 中存在一些特殊且重要的变量,例如:
$0
、$1
,我们称之为位置参数变量。要从命令行、函数或脚本执行等处传递参数时,就需要在 Shell 脚本中使用位置参数变量。部分位置参数变量如下:
- $0,获取当前执行的 Shell 脚本的文件名,如果执行脚本包含了路径,那么就包括脚本路径。
- **n∗∗,获取当前执行的Shell脚本的∗∗第n个参数值∗∗。如果n大于9,则用大括号括起来,例如n**,获取当前执行的 Shell 脚本的**第n个参数值**。如果n大于9,则用大括号括起来,例如n∗∗,获取当前执行的Shell脚本的∗∗第n个参数值∗∗。如果n大于9,则用大括号括起来,例如{10},接的参数以空格隔开。
- $#,获取当前执行的 Shell 脚本后面接的参数数量。
- **∗∗∗,获取当前Shell脚本所有传参的参数,不加引号和‘***,获取当前 Shell 脚本所有传参的参数,不加引号和`∗∗∗,获取当前Shell脚本所有传参的参数,不加引号和‘@
相同;如果给
∗‘加上双引号,例如:‘"*`加上双引号,例如:`"∗‘加上双引号,例如:‘"*",则表示将所有的参数视为单个字符串,相当于
$1 $2 $3`。- **@∗∗,获取当前Shell脚本所有传参的参数,不加引号和@**,获取当前 Shell 脚本所有传参的参数,不加引号和@∗∗,获取当前Shell脚本所有传参的参数,不加引号和*相同;如果给@加上双引号,例如:‘@加上双引号,例如:`@加上双引号,例如:‘@
,则表示将所有的参数视为不同的独立字符串,相当于
"$1"、“$2”、“$3”…`,这是将多参数传递给其他程序的最佳方式,因为它会保留所有的内嵌在每个参数里的任何空白。
Shell 进程中的特殊状态变量
$?
作用:获取执行上一个指令的执行状态返回值:0为成功,非零为失败,这个变量最常用。
[wan@server ~ 17:15:00]$ ls
bin ssh_ctl
# 说明ls命令执行成功
[wan@server ~ 17:15:01]$ echo $?
0[wan@server ~ 17:15:23]$ ls /root
ls: 无法打开目录/root: 权限不够
# 说明ls命令执行失败
[wan@server ~ 17:15:52]$ echo $?
2
Shell 内置变量命令
bash Shell 包含一些内置命令。 这些内置命令在目录列表里是看不见的,它们由 Shell 本身提供。常用的内部命令有:
echo
、eval
、exec
、read
、shift
等。
echo
echo命令参数选项:
- -n,不换行输出内容。
- -e,解析转义字符(见下面的字符)
转义字符:
- \n,换行。
- \t,制表符(tab)。
- \b,退格。
[wan@server ~ 17:17:29]$ echo -n "wan ";echo wang
wan wang[wan@server ~ 17:17:54]$ echo -e "wan\nwang"
wan
wang[wan@server ~ 17:18:12]$ echo -e "wan\twang"
wan wang[wan@server ~ 17:19:00]$ echo -e "1\b23"
23[wan@server ~ 17:19:21]$ echo -e "123\b"
123
read
从标准输入读取字符串等信息, 传给 Shell 程序内部定义的变量。
示例1
# 创建所需文件
[wan@server ~ 17:21:00]$ vim useradd.sh
# 在文件中写入以下内容
#!/bin/bash# 提供用户名
read -p "请输入用户名:" user_name# 提供用户性别
read -p "请输入用户性别:" user_sex# 提供用户年龄
read -p "请输入用户年龄:" user_age# 提供用户密码,-s 选项,实现不显示用户密码
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中."# 给予权限
[wan@server ~ 17:23:06]$ chmod +x useradd.sh# 执行脚本,因为普通用户没有修改密码权限,所以无法修改密码
[wan@server ~ 17:23:20]$ bash useradd.sh
请输入用户名:wang
请输入用户性别:male
请输入用户年龄:18
请输入用户密码:'123'
useradd: Permission denied.
useradd:无法锁定 /etc/passwd,请稍后再试。
只有根用户才能进行此操作。
useradd.sh:行22: /etc/users.info: 权限不够
用户信息已经存储到/etc/users.info中.
示例2
# 准备所需文件
[root@server ~ 17:30:20]# vim users.info
user_name=zhangsan
user_sex=male
user_age=18
user_pass=123# 读取文件内容到当前shell中
[root@server ~ 17:30:52]# source users.info
[root@server ~ 17:31:14]# echo $user_name
zhangsan# 创建用户,用户信息提前准备好,存放在/root/users.info中
[root@server ~ 17:32:01]# vim useradd-2.sh
#!/bin/bash# 获取用户信息:读取用户信息到当前shell中
source /root/users.info# 添加用户
useradd ${user_name}# 设置用户密码
echo ${user_pass} | passwd --stdin ${user_name}# 执行脚本
[root@server ~ 17:33:38]# bash useradd-2.sh
更改用户 zhangsan 的密码 。
passwd:所有的身份验证令牌已经成功更新。
示例3
# 判断数值大小
[wan@server tmp 18:02:06]$ vim test_num.sh
#!/bin/bashread -p "请输入一个数字:" num
# 大于 10
(( $num>10 )) && echo "$num > 10"
# 小于 10
(( $num<10 )) && echo "$num < 10"
# 等于 10
(( $num==10 )) && echo "$num = 10"# 验证
[wan@server tmp 18:02:31]$ bash test_num.sh
请输入一个数字:1
1 < 10
[wan@server tmp 18:03:04]$ bash test_num.sh
请输入一个数字:13
13 > 10
[wan@server tmp 18:03:10]$ bash test_num.sh
请输入一个数字:10
10 = 10
变量的数值计算实践
Shell 中常见的算术运算命令
- (()),用于整数运算的常用运算符,效率很高。
- let,用于整数运算,类似于
(())
。- expr,可用于整数运算,但还有很多其他的额外功能。
- bc,Linux下的一个计算器程序(适合整数及小数运算)。
- $[],用于整数运算。
- awk,awk 既可以用于整数运算,也可以用于小数运算。
- declare,定义变量值和属性,-i参数可以用于定义整形变量,做运算。
(()) 双小括号数运算实践
[root@server ~ 17:33:40]# echo $((1+2))
3# 计算从1加到100的值
[root@server ~ 17:37:26]# echo $(($(echo {1..100} | sed 's/ /+/g')))
5050
let 命令
[root@server ~ 17:38:35]# let sum=1+2
[root@server ~ 17:41:16]# echo $sum
3
bc 命令
bc
是UNIX/Linux下的计算器,因此,除了可以作为计算器来使用,还可以作为命令行计算工具使用。
# echo无法直接计算小数
[root@server ~ 17:41:19]# echo $[1.1+2.2]
-bash: 1.1+2.2: 语法错误: 无效的算术运算符 (错误符号是 ".1+2.2")# 使用bc可以计算小数
[root@server ~ 17:42:51]# echo 1.1+2.2 | bc
3.3
Shell 脚本的条件测试
条件测试方法综述
通常,在 bash 的各种条件结构和流程控制结构中都要进行各种测试,然后根据测试结果执行不同的操作,有时也会与if等条件语句相结合,来完成测试判断,以减少程序运行的错误。
执行条件判断表达式后通常会返回"真"或"假",就像执行命令后的返回值为0,表示真;非0,表示假。
在 bash 编程里,条件测试常用的语法如下:
语法1:test <判断表达式>,test命令和"<判断表达式>"之间至少有一个空格。
语法2:[ <判断表达式> ],和test命令的用法相同,这是推荐方法。
[]
的边界和内容之间至少有一个空格。语法3:[[ <判断表达式> ]],是比test和[]更新的语法格式。
[[]]
的边界和内容之间至少有一个空格。语法4:(( <判断表达式> )),一般用于 if 语句。
(())
(双小括号)两端不需要有空格。语法5:comand,命令的返回值确定表达式的真值或假值。
有几个注意事项需要说明一下:
语法1中的
test命令
和语法2中的[]
是等价的,语法3
中的[[]]
为扩展的test命令,语法4中的(())
常用于计算。建议使用相对友好的语法2,即中括号[]
的语法格式。在
[[ ]]
(双中括号)中可以使用通配符等进行模式匹配,这是其区别于其他几种语法格式的地方。
&&、||、>、<
等操作符可以应用于[[]]
中,但不能应用于[]
中,在[]
中一般用-a、-o、-gt(
用于整数)、-lt
(用于整数)代替上述操作符。对于整数的关系运算,也可以使用 Shell 的算术运算符
(())
。
test 条件测试
# 判断文件是否存在
[root@server ~ 17:47:12]# test -f file && echo true || echo false
false# -z 选项判断变量值是否为空,如果为空,则为真值,反之为假值
[root@server ~ 17:47:28]# unset string
[root@server ~ 17:48:02]# test -z "$string" && echo null string || echo $string
null string# 判断文件状态
# 如果目录存在,就提示目录存在
[wan@server ~ 17:49:44]$ mkdir /tmp/zhangsan
[wan@server ~ 17:50:16]$ test -d /tmp/zhangsan/ && echo /tmp/zhangsan/ is exist
/tmp/zhangsan/ is exist# 如果目录不存在,就创建目录
[wan@server bin 17:53:14]$ rmdir /tmp/zhangsan/
[wan@server bin 17:53:46]$ test ! -d /tmp/zhangsan/ && mkdir /tmp/zhangsan/
[wan@server bin 17:54:07]$ echo $?
0# 第二次执行,目录已经存储
[wan@server bin 17:54:07]$ test ! -d /tmp/zhangsan && mkdir /tmp/zhangsan
[wan@server bin 17:55:07]$ echo $?
1# 判断用户是否为root
# 确保脚本以root身份运行
[wan@server tmp 17:57:39]$ vim ssh_ctl
#!/bin/bash
test "$USER" != "root" && echo Pls run as root. && exit
systemctl $1 sshd# 确保脚本以root身份运行。
[wan@server tmp 18:00:21]$ vim ssh_ctl
#!/bin/bash
(( $UID!=0 )) && echo Pls run as root. && exit
systemctl $1 sshd