入门之后真正拉开差距的,是这些「别人脚本里看不懂的符号」。这篇把位置参数、状态变量、内置命令、变量子串与特殊扩展变量一次讲透,读完你能读懂绝大多数系统自带脚本,也能自己写带参数、会兜底的脚本。
本文概要:
| 项目 | 内容 |
|---|---|
| 文章主题 | 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 |
$! | 上一个后台进程的 PID | md5sum /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 world3.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--stdinzhb3.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 worldeval在生产里最典型的用法是接管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/env3.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.sh123453.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}# 删最长匹配1234.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}UNSET5.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:-默认值}兜一层。变量不存在就可能导致未知的破坏,这条在清理脚本里尤其值钱。
附:命令英文全称速查表
| 命令 / 缩写 | 英文全称 / 原意 | 作用 |
|---|---|---|
eval | evaluate | 把参数拼成命令再求值执行 |
exec | execute | 用指定命令替换当前 Shell 进程 |
read | read | 从标准输入读入变量 |
shift | shift | 位置参数整体左移一位 |
parameter | parameter | 变量/参数 |
expansion | expansion | 扩展,即把变量替换成实际内容 |
offset | offset | 偏移量,从第几位开始截取 |
pattern | pattern | 模式,删除/替换时的匹配式 |
PID | ProcessID | 进程号 |
passwd --stdin | password --standard input | 从标准输入读密码,便于脚本改密 |
📚 参考资料(官方文档 · 中英双语)
英文原版
- Bash Reference Manual · Shell Parameter Expansion(变量子串与特殊扩展变量的权威定义)
- Bash Reference Manual · Special Parameters(
$?$#$@等特殊变量逐条说明)
中文版
- Bash 脚本教程 · 变量(阮一峰)(中文环境下对参数扩展的讲解)
其余内容在服务器上直接查,不依赖网络、不占链接数:
manbash# 搜 "Parameter Expansion" / "Special Parameters"helpread# 内置命令的选项helpshift总结:进阶部分的脉络是「位置参数接输入 → 状态变量看结果 → 内置命令补能力 → 变量子串做字符串手术 → 特殊扩展变量兜底」。三条最常用的记住就够:
$?判成败、"$@"透传参数、${var:-默认值}防变量为空。觉得有用可以收藏,读懂别人脚本时卡住的地方欢迎在评论区聊聊。