Shell 变量进阶知识

4. Shell 变量进阶知识

4.1 Shell 中特殊变量

4.1.1 Shell 位置参数变量

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

部分位置参数变量如下:

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

示例1showargs.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 本身提供。常用的内部命令有: echoevalexecreadshift等。

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

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或没有定义,则赋予一个备用的值,特别是针对变量的删除操作,这种方式会很有用,否则所删除的变量如果不存在,则可能导致未知的危险。

相关推荐
大树886 小时前
金刚石散热越强,管路越先见顶
大数据·运维·服务器·人工智能·ai
摇滚侠6 小时前
Linux CentOS7 rpm 安装 MySQL 5.7
linux·运维·mysql
霸道流氓气质7 小时前
领域驱动设计(DDD)在 Spring Boot 微服务中的实践指南
运维·spring boot·微服务
bush47 小时前
嵌入式linux学习记录十四、术语
linux·嵌入式
载数而行5207 小时前
Linux 11 动态监控指令top
linux
Inhand陈工8 小时前
基于台达PLC与映翰通IG502的智慧水产养殖精准投喂与远程运维解决方案
运维·人工智能·物联网·阿里云·信息与通信
酣大智8 小时前
ARP代理--工作原理
运维·网络·arp·arp代理
不会C语言的男孩8 小时前
Linux 系统编程 · 第 8 章:进程基础
linux·c语言
shushangyun_8 小时前
2026年快消品B2B系统推荐:支持终端门店订货、促销政策自动化的工具?
java·运维·网络·数据库·人工智能·spring·自动化
古城小栈8 小时前
Unix 与 Linux 异同小叙
linux·服务器·unix