4. Shell 变量进阶知识
4.1 Shell 中特殊变量
4.1.1 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"...`,这是将多参数传递给其他程序的最佳方式,因为它会保留所有的内嵌在每个参数里的任何空白。
示例1:showargs.sh 内容如下
bash
#!/bin/bash
echo $0
echo $1
echo $2
echo $10
echo ${10}
echo $#
echo $*
echo $@
echo "$@"
bash
[root@shell ~]$ 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 内容如下
bash
#!/bin/bash
systemctl $1 sshd
执行效果如下:
bash
[root@shell ~]$ ./ssh_ctl stop
[root@shell ~]$ ./ssh_ctl status
[root@shell ~]$ ./ssh_ctl start
4.1.2 Shell 进程中的特殊状态变量
$?
作用:获取执行上一个指令的执行状态返回值:0为成功,非零为失败,这个变量最常用。
bash
[root@shell ~]$ ls
hello script.sh showargs.sh
[root@shell ~]$ echo $?
0
[root@shell ~]$ ls /root
ls: 无法打开目录/root: 权限不够
[root@shell ~]$ echo $?
2
# man ls查看,退出码含义
[root@shell ~]$ 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).
查询SELINUX和防火墙是否开启
bash
[root@shell ~ 13:42:44]# grep 'SELINUX=disabled' /etc/selinux/config
SELINUX=disabled
[root@shell ~ 13:43:13]# grep -q 'SELINUX=disabled' /etc/selinux/config
[root@shell ~ 13:43:21]# echo $?
0 # $?返回结果为0,说明SELINUX未启用
[root@shell ~ 13:43:24]# grep -q 'SELINUX=disdddabled' /etc/selinux/config
[root@shell ~ 13:43:32]# echo $?
[root@shell firewalld 13:51:57]# systemctl is-enabled firewalld.service | grep disabled
disabled
[root@shell firewalld 13:55:18]# systemctl is-enabled firewalld.service
disabled
[root@shell firewalld 13:55:24]# echo $?
1
[root@shell firewalld 13:53:40]# systemctl enable firewalld.service
[root@shell firewalld 13:54:00]# systemctl is-enabled firewalld.service
enabled
[root@shell firewalld 13:54:06]# echo $?
0
$$(不常用)
作用:获取当前执行的 Shell 脚本的进程号(PID),这个变量不常用,了解即可。
bash
[root@shell ~]$ echo $$
2447
[root@shell ~]$ kill -9 $$
Connection closed by foreign host.
Disconnected from remote host(10.1.8.88) at 21:00:36.
Type `help' to learn how to use Xshell prompt.
$!(不常用)
作用:获取上一个在后台工作的进程的进程号(PID),这个变量不常用,了解即可。
bash
[root@shell ~]$ md5sum /dev/zero &
[1] 2611
[root@shell ~]$ echo $!
2611
[root@shell ~]$ ps o pid,%cpu,%mem,command $!
PID %CPU %MEM COMMAND
2611 99.1 0.0 md5sum /dev/zero
[root@shell ~]$ kill $!
[1]+ Terminated md5sum /dev/zero
$_
作用:获取在此之前执行的命令或脚本的最后一个参数,这个变量不常用,了解即可。
bash
[root@shell ~]$ ls /etc/hosts /etc/fstab /etc/hostname
/etc/fstab /etc/hostname /etc/hosts
[root@shell ~]$ cat $_
shizhan-shell
[root@shell ~]$ cat /etc/hostname
shizhan-shell
4.2 Shell 内置变量命令
bash Shell 包含一些内置命令。 这些内置命令在目录列表里是看不见的,它们由 Shell 本身提供。常用的内部命令有: echo、eval、exec、read、shift等。
下面简单介绍几个最常用的内置命令的格式和功能。
4.2.1 echo
echo命令参数选项:
- -n,不换行输出内容。
- -e,解析转义字符(见下面的字符)
转义字符:
- \n,换行。
- \t,制表符(tab)。
- \b,退格。
bash
[root@shell ~]$ echo -n "shizhan ";echo laowang
shizhan laowang
[root@shell ~]$ echo -e "shizhan\nlaowang"
shizhan
laowang
[root@shell ~]$ echo -e "shizhan\tlaowang"
shizhan laowang
[root@shell ~]$ echo -e "1\b23"
23
[root@shell ~]$ echo -e "123\b"
123
# 后面有东西才会覆盖
[root@shell scripts]$ echo -ne "123\b";echo haha
12haha
4.2.2 eval
当 Shell 程序执行到 eval 语句时, Shell 读入参数args,并将它们组合成一个新的命令,然后执行。
示例1:
bash
[root@shell ~]$ vim noeval.sh
echo \$$#
[root@shell ~]$ bash noeval.sh hello world
$2
[root@shell ~]$ vim eval.sh
eval "echo \$$#"
[root@shell ~]$ bash eval.sh hello world
world
示例2:
bash
[root@shell ~]$ ssh-agent
SSH_AUTH_SOCK=/tmp/ssh-LmBsuYheypEC/agent.1589; export SSH_AUTH_SOCK;
SSH_AGENT_PID=1590; export SSH_AGENT_PID;
echo Agent pid 1590;
[root@shell ~]$ eval $(ssh-agent)
Agent pid 1598
# 等效于
[root@shell ~]$ ssh-agent > /tmp/env
[root@shell ~]$ source /tmp/env
[root@shell ~]$ rm -f /tmp/env
4.2.3 read
从标准输入读取字符串等信息, 传给 Shell 程序内部定义的变量。
bash
[root@shell ~]$ cat read.sh
#!/bin/sh
read -p "输入你想要说的话:" str
echo "你想要说的话是:$str"
[root@shell ~]$ bash read.sh
输入你想要说的话:`hello world`
你想要说的话是:hello world
# 不显示输入的内容
[root@shell ~]$ read -s -p "请设置用户密码: " password
请设置用户密码:
[root@shell ~]$ echo $password
redhat
输出脚本,实现启动或停止sshd服务的功能:
bash
[root@shell ~ 14:44:17]# vim sshd_ctl.sh
#!/bin/bash
read -p "what do you want to do for server, stop|start|status|restart : " action
systemctl $action sshd
[root@shell ~ 14:16:56]# ./sshd_ctl.sh
what do you want to do for server, stop|start|status|restart : start
[root@shell ~ 14:17:03]# ./sshd_ctl.sh
what do you want to do for server, stop|start|status|restart : status
输出脚本,实现修改用户密码功能,密码从标准输入读取:
bash
[root@shell ~ 14:39:42]# useradd zhangsan
# read -s -p: -s 隐藏密码显示
[root@shell ~ 14:43:24]# vim set_user_password.sh
#!/bin/bash
read -s -p "please input user new password: " password
echo
echo $password | passwd --stdin zhangsan
[root@shell ~ 14:43:20]# ./set_user_password.sh
please input user new password:
Changing password for user zhangsan.
passwd: all authentication tokens updated successfully.
4.2.4 exec
exec 命令能够在不创建新的子进程的前提下, 转去执行指定的命令, 当指定的命令执行完毕后, 该进程 (也就是最初的 Shell ) 就终止了。
示例1:
bash
[root@shell ~]$ sudo -i
[root@shell ~]# ps $$
PID TTY STAT TIME COMMAND
1741 pts/0 S 0:00 -bash
[root@shell ~]# exec sleep 10
# sleep命令运行完成后自动返回到原先普通用户shell了。
[root@shell ~]$
# 在exec命令执行期间,新开一个窗口,查看PID是1741的进程信息
[root@shell ~]$ ps 1741
PID TTY STAT TIME COMMAND
1741 pts/0 S+ 0:00 sleep 10
示例2: 读取文件内容
bash
[root@shell ~]$ cat exec.sh
#!/bin/bash
# 生成序列内容文件
seq 5 > /tmp/seq.log
# 从文件中读取内容,作为shell的标准输入
exec < /tmp/seq.log
# read命令接收标准输入
while read line
do
echo $line
done
[root@shell ~]$ bash exec.sh
1
2
3
4
5
4.2.5 shift
shift 语句会按如下方式重新命名所有的位置参数变量,即2成为1、$3成为2等,以此类推,在程序中每使用一次shift语句,都会使所有的位置参数依次向左移动一个位置,并使位置参数2等,以此类推,在程序中每使用一次shift语句,都会使所有的位置参数依次向左移动一个位置,并使位置参数2等,以此类推,在程序中每使用一次shift语句,都会使所有的位置参数依次向左移动一个位置,并使位置参数#减1,直到减到0为止。
如果脚本有3个参数,那么执行一次shift后 ,3就变成了2,2变成了1,原先的$1就消失了。
示例1:
bash
[root@shell ~]$ cat shift.sh
#!/bin/sh
echo $1
shift
echo $1
[root@shell ~]$ bash shift.sh hello world
hello
world
示例2: shell脚本接收中横杠-开头的选项参数。
bash
[root@shell ~]$ cat shift.sh
#!/bin/sh
if [ "$1" = "-c" ];then
shift
fi
command="$1"
echo $command
[root@shell ~]$ bash shift.sh -c date
date
补充
系统中有文件记录的,通过grep直接过滤,确认配置是否存在
[root@centos7 ~ 13:57:51]# grep 'SELINUX=disabled' /etc/selinux/config
SELINUX=disabled
确认某个服务实时状态,是通过命令获得的,命令的返回值$?或者使用管道和grep过滤确认。
[root@centos7 ~ 14:00:39]# systemctl is-active firewalld.service
inactive
grep 压缩输出使用-q选项
有些命令没有压缩输出选项,使用shell重定向 &>/dev/null
4.3 Shell 变量子串知识
4.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。
4.3.2 Shell 变量子串的实践
示例1: ${parameter}
bash
[root@shell ~]$ str="abc123abc123"
[root@shell ~]$ echo ${str}
abc123abc123
示例2: ${#parameter}
bash
[root@shell ~]$ str="abc123abc123"
# 此方法获取字符串长度速度最快
[root@shell ~]$ echo ${#str}
12
# 其他方式计算字符串长度
[root@shell ~]$ echo ${str} | wc -L
12
[root@shell ~]$ expr length "${str}"
12
[root@shell ~]$ echo "$str" | awk '{ print length($0)}'
12
示例3: ${parameter:offset} 和 ${parameter:offset:length}
bash
[root@shell ~]$ str="abc123abc123"
# 提取子串
[root@shell ~]$ echo ${str:3}
123abc123
[root@shell ~]$ echo ${str:3:4}
123a
示例4: ${parameter#word} 和 ${parameter##word}
bash
[root@shell ~]$ str="abc123abc123"
# 从左侧开始删除子串
[root@shell ~]$ echo ${str#b*a}
abc123abc123
# 未匹配到,第一个字符必须与元字串第一个子符一致
[root@shell ~]$ echo ${str#a*c}
123abc123
[root@shell ~]$ echo ${str##a*c}
123
示例5: ${parameter%word} 和 ${parameter%%word}
bash
[root@shell ~]$ str="abc123abc123"
# 从右侧开始删除子串
[root@shell ~]$ echo ${str%a*c}
abc123abc123
# 未匹配到,最后一个字符必须与元字串最后一个子符一致
[root@shell ~]$ echo ${str%c*3}
abc123ab
[root@shell ~]$ echo ${str%%c*3}
ab
示例6: ${parameter/pattem/string}
bash
[root@shell ~]$ str="abc123abc123"
# 只替换第一个
[root@shell ~]$ echo ${str/abc/def}
def123abc123
# 所有的全替换
[root@shell ~]$ echo ${str//abc/def}
def123def123
查询md5进程数量:
bash
[root@shell ~ 14:59:40]# ps axu | grep md5sum | grep -v -c 'grep'
2
[root@shell ~ 14:56:01]# pgrep md5sum | wc -l
2
4.3.3 变量子串的生产场景应用案例
**示例1:**替换文件名中特定字符串
bash
[root@shell ~]$ touch stu-202212-snap.jpg
[root@shell ~]$ ls stu-*
stu-202212-snap.jpg
[root@shell ~]$ file="stu-202212-snap.jpg"
[root@shell ~]$ mv $file ${file/2022/2021}
[root@shell ~]$ ls stu-*
stu-202112-snap.jpg
**示例2:**删除文件名中特定字符串
bash
[root@shell ~]$ touch stu-202212-snap.jpg
[root@shell ~]$ ls stu-*
stu-202212-snap.jpg
[root@shell ~]$ file="stu-202212-snap.jpg"
[root@shell ~]$ mv $file ${file/-snap/}
[root@shell ~]$ ls stu-*
stu-202212.jpg
4.4 Shell 特殊扩展变量
4.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.4.2 Shell 特殊扩展变量的实践
示例1: ${parameter:-word}
bash
# 变量未定义情况
[root@shell ~]$ unset test
[root@shell ~]$ echo ${test:-UNSET}
UNSET
[root@shell ~]$ echo $test
# 变量定义情况
[root@shell ~]$ test=hello
[root@shell ~]$ echo ${test:-UNSET}
hello
示例2: ${parameter:=word}
bash
# 变量未定义情况
[root@shell ~]$ unset SHELL
[root@shell ~]$ echo ${SHELL:=/bin/bash}
/bin/bash
[root@shell ~]$ echo $SHELL
/bin/bash
# 变量定义情况
[root@shell ~]$ echo ${SHELL:=/bin/sh}
/bin/bash
[root@shell ~]$ echo $SHELL
/bin/bash
示例3: ${parameter:?word}
bash
# 变量未定义情况
[root@shell ~]$ unset test
[root@shell ~]$ echo ${test:?UNSET}
-bash: test: UNSET
# 变量定义情况
[root@shell ~]$ test=hello
[root@shell ~]$ echo ${test:?UNSET}
hello
示例4: ${parameter:+word}
bash
# 变量未定义情况
[root@shell ~]$ unset test
[root@shell ~]$ echo ${test:+UNSET}
# 变量定义情况
[root@shell ~]$ test=hello
[root@shell ~]$ echo ${test:+UNSET}
UNSET
4.4.3 Shell 特殊扩展变量的生产场景应用案例
**示例1:**实现 Apache 服务启动脚本/etc/init.d/httpd
bash
# /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:**删除过期文件
bash
#!/bin/bash
# 防止path变量未定义,导致异常结果
find ${path:-/tmp} -name "*.tar.gz" -type f -mtime +7 |xargs rm -f
在企业中,针对目录路径等的处理就可以采用上述变量不存在(即赋指定值)的方式,防止因目录路径不存在而导致的异常。
例如:变量如果为NULL或没有定义,则赋予一个备用的值,特别是针对变量的删除操作,这种方式会很有用,否则所删除的变量如果不存在,则可能导致未知的危险。