news 2026/10/13 10:28:09

Shell 变量进阶:特殊变量与变量子串实战

作者头像

张小明

前端开发工程师

1.2k 24
文章封面图
Shell 变量进阶:特殊变量与变量子串实战

入门之后真正拉开差距的,是这些「别人脚本里看不懂的符号」。这篇把位置参数、状态变量、内置命令、变量子串与特殊扩展变量一次讲透,读完你能读懂绝大多数系统自带脚本,也能自己写带参数、会兜底的脚本。

本文概要:

项目内容
文章主题Shell 特殊变量、内置命令、变量子串与特殊扩展变量
所属类别Linux 系统管理 · Shell 编程
知识关键字位置参数、退出码、内置命令、参数扩展、变量子串

主线导读:先看两组特殊变量——位置参数负责「接参数」、状态变量负责「看结果」(一、二章),再看 5 个高频内置命令怎么用(三章),最后是变量子串与特殊扩展变量这两组「字符串手术刀」(四、五章)。

一、位置参数变量:脚本怎么接参数

1.1 五个位置参数

从命令行、函数或脚本执行处传参时,靠的就是位置参数变量。

变量含义说明
$0当前脚本的文件名带路径执行时包含路径
$n第 n 个参数值n 大于 9 要写成${10};参数之间用空格隔开
$#参数个数常用于判断「参数给够没有」
$*全部参数不加引号时与$@相同
$@全部参数不加引号时与$*相同

⚠️注意:$*与$@加上双引号后含义分道扬镳。"$*"把所有参数当成一个字符串(相当于$1 $2 $3);"$@"把每个参数当成独立字符串(相当于"$1" "$2" "$3")。要往其他程序透传参数,一律用"$@",它能保留每个参数里内嵌的空白。

1.2 示例:把所有参数逐个打印出来

[zhb@zhb-m2 ~]$catshowargs.sh#!/bin/bashecho$0echo$1echo$2echo$10# 注意:这是 $1 后面接字符 0echo${10}# 第 10 个参数要加花括号echo$#echo$*echo$@echo"$@"
[zhb@zhb-m2 ~]$bashshowargs.sh{a..z}showargs.sh a b a0 j26a b c d e f g h i j k l m n o p q r s t uvw x y z a b c d e f g h i j k l m n o p q r s t uvw x y z a b c d e f g h i j k l m n o p q r s t uvw x y z

$10输出a0是因为 Shell 把$1和字符0拼在了一起,这正是第 10 个参数必须写${10}的原因。

1.3 示例:给 systemctl 套一层封装

脚本接收的第一个参数直接透传给systemctl,一个脚本就能控制服务的启停与状态查询。

[zhb@zhb-m2 ~]$catssh_ctl#!/bin/bashsystemctl$1sshd[zhb@zhb-m2 ~]$sudossh_ctl stop[zhb@zhb-m2 ~]$sudossh_ctl status[zhb@zhb-m2 ~]$sudossh_ctl start

二、进程状态变量:看运行结果

2.1$?:上一条命令的退出码

0为成功,非零为失败,这是脚本里用得最多的特殊变量——if判断、&&串联都靠它。

[zhb@zhb-m2 ~]$lshello script.sh showargs.sh[zhb@zhb-m2 ~]$echo$?0[zhb@zhb-m2 ~]$ls/root ls: 无法打开目录/root: 权限不够[zhb@zhb-m2 ~]$echo$?2

退出码的具体含义可以查手册,例如man ls里的Exit status段列出:0表示成功、1表示小问题、2表示严重问题。

2.2$$/$!/$_:了解即可

变量含义演示
$$当前脚本的进程号(PID)echo $$→2447
$!上一个后台进程的 PIDmd5sum /dev/zero &后echo $!→2611
$_上一条命令的最后一个参数cat /etc/hosts /etc/fstab /etc/hostname后cat $_就是读/etc/hostname
# $$ 的用途:直接结束当前会话进程[zhb@zhb-m2 ~]$echo$$2447[zhb@zhb-m2 ~]$kill-9$$Connection closed by foreign host.# $! 的用途:对刚刚丢到后台的任务下手[zhb@zhb-m2 ~]$ md5sum /dev/zero&[1]2611[zhb@zhb-m2 ~]$pso pid,%cpu,%mem,command$!PID %CPU %MEM COMMAND261199.10.0md5sum /dev/zero[zhb@zhb-m2 ~]$kill$![1]+ Terminated md5sum /dev/zero

三、五个高频内置命令

bash 自带一批内置命令,它们在目录列表里看不见,由 Shell 本身提供。常用的有echo、eval、exec、read、shift。

3.1echo:输出与转义

选项作用
-n不换行输出
-e解析转义字符

常用转义字符:\n换行、\t制表符、\b退格、\r回到行首。

[zhb@zhb-m2 ~]$echo-n"zhb ";echolaowang zhb laowang[zhb@zhb-m2 ~]$echo-e"zhb\nlaowang"zhb laowang[zhb@zhb-m2 ~]$echo-e"zhb\tlaowang"zhb laowang# \b 退格:只有后面还有内容时才会看到覆盖效果[zhb@zhb-m2 ~]$echo-ne"123\b";echohaha 12haha# \r 回到行首:后面的内容覆盖前面[zhb@zhb-m2 ~]$echo-e"hello world\r123"123lo world

3.2read:把输入读进变量

从标准输入读取字符串等信息,传给脚本内部定义的变量。

选项作用
-p "提示语"显示输入提示
-s不回显输入内容(适合输密码)
[zhb@zhb-m2 ~]$catread.sh#!/bin/shread-p"输入你想要说的话:"strecho"你想要说的话是:$str"[zhb@zhb-m2 ~]$bashread.sh 输入你想要说的话:hello world 你想要说的话是:hello world

把read -s与passwd --stdin组合,就是一条自动改密码的脚本:

[root@zhb-m2 ~]# cat set_password_zhb.sh#!/bin/bashread-sp'请输入用户新的密码: 'passwordecho$password|passwd--stdinzhb

3.3eval:把参数拼成新命令再执行

执行到eval时,Shell 把参数组合成一条新命令,然后再执行一次。它解决的是「变量需要被二次解析」的场景。

# 不加 eval:$$ 先被展开成 PID,# 成为普通字符[zhb@zhb-m2 ~]$catnoeval.shecho\$$#[zhb@zhb-m2 ~]$bashnoeval.sh hello world$2# 加 eval:先拼成 echo $2,再执行[zhb@zhb-m2 ~]$cateval.sheval"echo \$$#"[zhb@zhb-m2 ~]$basheval.sh hello world world

eval在生产里最典型的用法是接管ssh-agent的输出:

# 一行拿到 SSH_AUTH_SOCK 等环境变量[zhb@zhb-m2 ~]$eval$(ssh-agent)Agent pid1598# 等价于手动落盘再 source[zhb@zhb-m2 ~]$ ssh-agent>/tmp/env&&source/tmp/env&&rm-f/tmp/env

3.4exec:不新建进程,直接替换

exec会在不创建新子进程的前提下转去执行指定命令,命令结束时,这个进程(也就是最初的 Shell)也随之终止。

[root@zhb-m2 ~]# ps $$PID TTY STAT TIME COMMAND1741pts/0 S0:00-bash# exec 之后 PID 1741 这个进程变成了 sleep[root@zhb-m2 ~]# exec sleep 10# sleep 跑完直接回到普通用户 shell[zhb@zhb-m2 ~]$

exec还有一个常用形态:重定向当前 Shell 的标准输入,用来逐行读文件。

[zhb@zhb-m2 ~]$catexec.sh#!/bin/bashseq5>/tmp/seq.log# 生成一份序列文件exec</tmp/seq.log# 把文件设为 shell 的标准输入whilereadlinedoecho$linedone[zhb@zhb-m2 ~]$bashexec.sh12345

3.5shift:位置参数整体左移

每执行一次shift,所有位置参数依次左移一位——$2变成$1、$3变成$2,$#减 1,直到减到 0。原先的$1消失。

[zhb@zhb-m2 ~]$catshift.sh#!/bin/shecho$1shiftecho$1[zhb@zhb-m2 ~]$bashshift.sh hello world hello world

配合if判断,它能吃掉以-开头的选项参数:

[zhb@zhb-m2 ~]$catshift.sh#!/bin/shif["$1"="-c"];thenshiftficommand="$1"echo$command[zhb@zhb-m2 ~]$bashshift.sh-cdatedate

四、变量子串:取长、截取、删除、替换

4.1 语法总览

想查权威定义,man bash后搜索Parameter Expansion。

其中「按长度取子串」还可以用几种外部命令实现,但${#变量}速度最快:

[zhb@zhb-m2 ~]$str="abc123abc123"[zhb@zhb-m2 ~]$echo${#str}12echo${str}|wc-L# 12exprlength"${str}"# 12echo"$str"|awk'{ print length($0)}'# 12

匹配类删除子串有个容易踩的点:开头字符必须对得上。${str#b*a}因为str以a开头,匹配不上就原样返回。

[zhb@zhb-m2 ~]$str="abc123abc123"[zhb@zhb-m2 ~]$echo${str#b*a}# 首字符不匹配 → 原样返回abc123abc123[zhb@zhb-m2 ~]$echo${str#a*c}# 删最短匹配123abc123[zhb@zhb-m2 ~]$echo${str##a*c}# 删最长匹配123

4.2 生产案例:批量改文件名

替换文件名中的指定字符串——把2022换成2021:

[zhb@zhb-m2 ~]$file="stu-202212-snap.jpg"[zhb@zhb-m2 ~]$mv$file${file/2022/2021}[zhb@zhb-m2 ~]$lsstu-* stu-202112-snap.jpg

删除文件名中的指定字符串——把-snap抹掉,写法是把替换部分留空:

[zhb@zhb-m2 ~]$file="stu-202212-snap.jpg"[zhb@zhb-m2 ~]$mv$file${file/-snap/}[zhb@zhb-m2 ~]$lsstu-* stu-202212.jpg

五、特殊扩展变量:给变量兜底

5.1 四种写法的含义

写法里的冒号是可选的:省略冒号后,判据就从「为空或未赋值」收紧为「未赋值」——也就是只测试变量有没有被定义过。

5.2 逐条演示

# ${p:-word}:空就返回备用值[zhb@zhb-m2 ~]$unsettest[zhb@zhb-m2 ~]$echo${test:-UNSET}UNSET[zhb@zhb-m2 ~]$test=hello&&echo${test:-UNSET}hello# ${p:=word}:空就赋成 word 并返回[zhb@zhb-m2 ~]$unsetSHELL[zhb@zhb-m2 ~]$echo${SHELL:=/bin/bash}/bin/bash[zhb@zhb-m2 ~]$echo$SHELL/bin/bash# ${p:?word}:空就把 word 当标准错误抛出[zhb@zhb-m2 ~]$unsettest[zhb@zhb-m2 ~]$echo${test:?UNSET}-bash: test: UNSET# ${p:+word}:有值才替换[zhb@zhb-m2 ~]$unsettest[zhb@zhb-m2 ~]$echo${test:+UNSET}[zhb@zhb-m2 ~]$test=hello&&echo${test:+UNSET}UNSET

5.3 生产案例:防住「变量为空」的破坏

系统自带的httpd启动脚本就是这套写法的教科书,每个路径都兜了一个默认值:

# /etc/init.d/httpd 片段HTTPD_LANG=${HTTPD_LANG:-"C"}httpd=${HTTPD:-/usr/sbin/httpd}pidfile=${PIDFILE:-/var/run/httpd.pid}lockfile=${LOCKFILE:-/var/lock/subsys/httpd}

删除文件的场景更要小心——变量为空时,rm的路径会变成根目录之类的危险目标:

#!/bin/bash# 路径变量没定义时退回 /tmp,避免误删find${path:-/tmp}-name"*.tar.gz"-typef-mtime+7|xargsrm-f

⚠️注意:凡是「变量决定了要删什么、写到哪」的地方,都建议用${var:-默认值}兜一层。变量不存在就可能导致未知的破坏,这条在清理脚本里尤其值钱。

附:命令英文全称速查表

命令 / 缩写英文全称 / 原意作用
evalevaluate把参数拼成命令再求值执行
execexecute用指定命令替换当前 Shell 进程
readread从标准输入读入变量
shiftshift位置参数整体左移一位
parameterparameter变量/参数
expansionexpansion扩展,即把变量替换成实际内容
offsetoffset偏移量,从第几位开始截取
patternpattern模式,删除/替换时的匹配式
PIDProcessID进程号
passwd --stdinpassword --standard input从标准输入读密码,便于脚本改密

📚 参考资料(官方文档 · 中英双语)

英文原版

  • Bash Reference Manual · Shell Parameter Expansion(变量子串与特殊扩展变量的权威定义)
  • Bash Reference Manual · Special Parameters($?$#$@等特殊变量逐条说明)

中文版

  • Bash 脚本教程 · 变量(阮一峰)(中文环境下对参数扩展的讲解)

其余内容在服务器上直接查,不依赖网络、不占链接数:

manbash# 搜 "Parameter Expansion" / "Special Parameters"helpread# 内置命令的选项helpshift

总结:进阶部分的脉络是「位置参数接输入 → 状态变量看结果 → 内置命令补能力 → 变量子串做字符串手术 → 特殊扩展变量兜底」。三条最常用的记住就够:$?判成败、"$@"透传参数、${var:-默认值}防变量为空。觉得有用可以收藏,读懂别人脚本时卡住的地方欢迎在评论区聊聊。

版权声明: 本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若内容造成侵权/违法违规/事实不符,请联系邮箱:809451989@qq.com进行投诉反馈,一经查实,立即删除!
网站建设 2026/10/13 10:25:35

安装R包curl报错解决

options(install.packages.check.source "no") install.packages("curl", type "win.binary", repos "https://mirrors.tuna.tsinghua.edu.cn/CRAN/")使用上述代码安装即可。安装httr包、seurat包报错都是这个原因。

作者头像 李华
网站建设 2026/10/13 10:24:19

AI 编程的需求交付,我是这样管起来的

一、代码没错&#xff0c;测试全绿&#xff0c;但不是我想要的 先讲一件真事。前阵子我让 agent 做一个业务功能&#xff0c;它交得很快&#xff1a;代码挑不出毛病&#xff0c;单测全绿&#xff0c;附带的说明写得头头是道。真正点开页面用的时候我才反应过来&#xff0c;它实…

作者头像 李华
网站建设 2026/10/13 10:23:27

AnyPS5:跨平台脚本运行与任务调度框架实战指南

1. 项目缘起与核心定位AnyPS5 这个名字第一次出现在我视野里的时候&#xff0c;我正被一堆跨平台脚本的兼容性问题折腾得够呛。简单来说&#xff0c;AnyPS5 是一个面向多平台脚本运行与自动化任务调度的轻量级框架&#xff0c;它的核心目标是让同一套脚本逻辑能够在不同操作系统…

作者头像 李华
网站建设 2026/10/13 10:22:53

AI生成代码如何审查?三大盲区与一套落地流程

最近在带一个内部模块的重构&#xff0c;团队里几乎人人都开着AI辅助编程&#xff0c;提效数据确实漂亮——有个功能模块的交付速度大概提了三倍。可等我推进到代码评审阶段&#xff0c;问题一个个冒出来了。不是AI写得差&#xff0c;是我们的审查逻辑还停留在“人写代码”的惯…

作者头像 李华
网站建设 2026/10/13 10:21:31

Android Room数据库升级迁移实战:从原理到避坑指南

1. 从一次数据库迁移事故说起&#xff1a;为什么Room升级值得单独拎出来讲做Android开发的人&#xff0c;几乎都经历过这样的场景&#xff1a;App上线了一个版本&#xff0c;本地数据库表结构设计得不太合理&#xff0c;产品经理又提了新需求&#xff0c;需要加字段、改类型、拆…

作者头像 李华
网站建设 2026/10/13 10:21:30

第十三节【Git部署篇】基于Ubuntu与Docker环境部署Gogs服务器详细教程

前言 在团队协作与个人开发中,代码托管是保障项目版本管理与多人协同的基础环节。Gogs 作为一款轻量级的 Git 服务,凭借其低资源占用与快速部署的特性,成为自建代码仓库的理想选择。本文将以 Ubuntu 24.04.2 LTS 为环境,通过 Docker 容器化方式完整演示 Gogs 的部署与初始化…

作者头像 李华