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

相关推荐
liulilittle1 小时前
C++ 并发双阶段队列设计原理与实现
linux·开发语言·c++·windows·算法·线程·并发
YFLICKERH1 小时前
【Linux系统】磁盘文件系统
linux
森G1 小时前
五、Linux字符设备驱动
linux·arm开发·c++·ubuntu
生信大表哥1 小时前
Claude Code / Gemini CLI / Codex CLI 安装大全(Linux 服务器版)
linux·python·ai·r语言·数信院生信服务器
知识分享小能手1 小时前
CentOS Stream 9入门学习教程,从入门到精通, Linux文本编辑器 —— 语法详解与实战案例(5)
linux·学习·centos
网安老伯1 小时前
什么是网络安全?网络安全包括哪几个方面?学完能做一名黑客吗?
linux·数据库·python·web安全·网络安全·php·xss
tang_vincent2 小时前
Linux虚拟内存固定映射区-fixmap
linux
企鹅侠客2 小时前
Ubuntu本地部署AnythingLLM实现本地文档RAG
linux·运维·ubuntu·llm
醇氧2 小时前
Git 合并冲突提示 Local Changes Prevent from Merge
运维·git