八、Linux Shell 脚本:变量与字符串

举报
IvanCodes 发表于 2025/10/15 09:36:20 2025/10/15
【摘要】 Shell脚本里的变量就像一个个贴着标签的“箱子”。装东西(赋值)时,=两边千万不能有空格。用单引号''装进去的东西会原封不动,用双引号""则会让里面的$变量先“变身”再装箱。默认箱子只能在当前“房间”(Shell进程)用,想让隔壁房间(子进程)也能看到,就得给箱子盖个export的“出口”戳。此外,Shell还自带了$?(上条命令的成绩单)和$1(别人递进来的第一个包裹)等许多特殊箱子,非常有用

思维导图

image.png
image.png
image.png
image.png

一、变量的定义与使用

1.1 定义变量

  • 基本格式: variable_name=value
  • 关键点:赋值号 = 的两边绝对不能有空格!这是初学者最常犯的错误之一,务必留意!
  • 命名规则: 变量名通常由字母、数字、下划线构成,且不能以数字开头。习惯上,全大写用于环境变量常量小写或驼峰式用于本地变量
  • 值的“类型”: Shell 不严格区分数据类型,几乎所有值都可视为字符串。即使是数字,在进行算术运算前,本质上也是字符串。

引号的使用规则:

如果值不包含空格或特殊字符,可以省略引号myvar=hello
如果值包含空格必须使用单引号 ' '双引号 " " 包围:message='Hello World'
单引号 (’ ')强引用“字面”引用。其内部所有内容都按原样处理,变量不会被解析,特殊字符也失去其特殊含义。
双引号 (" ")弱引用“解析”引用。其内部的变量引用 ($var) 会被替换为变量的值,某些特殊字符 (如 $\`) 依然有效

# 正确的变量定义
name="Alice"
age=30
city="Beijing"
greeting1='你好, ${name}!' # 单引号内 ${name} 不会被替换
greeting2="你好, ${name}!" # 双引号内 ${name} 会被替换成 Alice

# 错误的变量定义 (等号两边有空格)
# wrong_var = "error"

1.2 引用变量

获取或使用变量的值,在变量名前加上 $ 符号。

  • 基础用法: $variable_name
  • 推荐用法: ${variable_name} (使用花括号 {} 包围)

为什么推荐使用 ${}

明确边界: 当变量名后紧跟其他字符时,花括号能清晰地界定变量名的范围,避免歧义。例如,${user}_id 可以正确解析,而 $user_id 会试图查找一个名为 user_id 的变量。
高级功能: 所有高级字符串操作 (如截取、替换) 都必须在花括号内进行。

#!/bin/bash
user="Bob"
action="studying"
echo "用户是: ${user}"
echo "${user}正在${action}Shell。"

1.3 只读变量与删除变量

  • 只读变量 (readonly):
    使用 readonly 命令可以将一个变量锁定,使其变为只读。一旦设置,其值不能被修改,也无法被 unset 删除。非常适合定义脚本中的常量
#!/bin/bash
readonly APP_VERSION="1.0.2"
echo "当前应用版本: ${APP_VERSION}"

# 尝试修改将导致错误
# APP_VERSION="1.0.3"

# 尝试删除也将导致错误
# unset APP_VERSION
  • 删除变量 (unset):
    使用 unset 命令可以彻底删除一个变量 (包括其名称和值)。注意: readonly 的变量无法被删除。
#!/bin/bash
tmp_dir="/tmp/my_app_temp"
echo "临时目录: ${tmp_dir}"

unset tmp_dir

echo "删除后的临时目录: ${tmp_dir}" # 此处将输出空行

二、变量的作用域

变量的有效范围 (作用域) 是脚本编程中的核心概念

2.1 本地变量

  • 定义方式: 默认通过 variable_name=value 定义的都是本地变量
  • 作用范围: 仅在创建它的当前 Shell 进程中有效。
  • 继承性: 无法被当前 Shell 启动的子进程 (如执行另一个脚本) 自动继承
#!/bin/bash
# 定义本地变量
my_secret="这是我的小秘密"
echo "父脚本中的秘密: ${my_secret}"

# 启动一个子Shell
bash -c 'echo "子Shell中能看到秘密吗? ${my_secret}"' # 此处 ${my_secret} 为空

2.2 环境变量export 命令

  • 作用范围:当前 Shell 及其启动的所有子进程中都有效
  • 继承性: 可以被子进程继承
  • export 命令: 该命令用于将一个本地变量 “发布” 或 “导出” 为环境变量,使其能够被子进程访问。

export 的两种用法:

  1. 先定义,后导出:
my_local_var="初始值"
export my_local_var
  1. 定义并同时导出 (更常用):
export SHARED_CONFIG_PATH="/etc/my_app/config"
  • 验证继承性:
#!/bin/bash
local_info="只在父脚本可见"
export shared_info="父子都能看到"

echo "父脚本中的本地信息: ${local_info}"
echo "父脚本中的共享信息: ${shared_info}"

# 启动子Shell来验证
bash -c 'echo "子Shell中的本地信息: ${local_info}"; echo "子Shell中的共享信息: ${shared_info}"'
# 输出显示:子Shell中local_info为空,但shared_info有值!
  • 查看环境变量: 使用 envprintenv 命令可以列出当前所有的环境变量。

2.3 让环境变量“永久生效”

通过 export 设置的环境变量是临时的,随当前 Shell 会话关闭而失效。要使其永久生效,需要将其写入Shell的配置文件

常用配置文件 (Bash):

~/.bashrc: 常用。每次打开新的交互式终端时加载。适合个人常用的环境变量。
~/.bash_profile: 用户登录时加载一次。
/etc/profile: 最常用。系统全局配置,影响所有用户

配置步骤:
1. 用文本编辑器打开配置文件 (如 vim /etc/profile)
2. 在文件末尾添加 export 命令:

export JAVA_HOME="/usr/lib/jvm/java-11-openjdk-amd64"
export PATH=$PATH:$JAVA_HOME/bin

3.保存并退出
4. 使配置立即生效,执行 source /etc/profile重新打开一个终端

2.4 作用域总结

特性 本地变量 环境变量
定义方式 variable_name=value (默认) 使用 export 命令 (如 export var=val)
作用域 仅限当前 Shell 进程 当前 Shell 进程 其所有子进程
继承性 不被 子进程继承 可被 子进程继承
持久性 临时,随 Shell 关闭消失 临时(若仅 export),可配置为永久(修改配置文件)
关键命令 (无) export, env, printenv
用途 脚本内部临时存储、计数器 PATH设置、跨脚本共享配置、系统级参数

三、预定义与特殊变量

Shell 预先定义了一些特殊变量,它们在特定上下文中有固定含义

变量 描述
$0 当前脚本的名称
$1 - $9 第一个到第九个位置参数
$# 传递给脚本的参数总个数
$@ 所有位置参数列表,加引号 "$@" 后每个参数为独立字符串
$* 所有位置参数列表,加引号 "$*" 后所有参数合并为单个字符串
$$ 当前脚本的进程ID (PID)
$! 最近一个放入后台的作业的进程ID (PID)
$? 上一个命令的退出状态码 (0表示成功,非0表示失败)
$_ 上一个命令的最后一个参数
$IFS 内部字段分隔符 (默认为空格、制表符、换行符)
  • 位置参数变量:
#!/bin/bash
# 文件名: show_params.sh
echo "脚本名 (\$0): $0"
echo "第一个参数 (\$1): $1"
echo "第二个参数 (\$2): $2"
# 执行: ./show_params.sh apple banana
  • 特殊含义变量:
#!/bin/bash
# 文件名: special_vars.sh
echo "收到参数个数 (\$#): $#"
ls /no_such_dir
echo "上个命令的退出状态码 (\$?): $?"
echo "本脚本的进程号 (\$\$): $$"

四、字符串操作

Shell 提供了内置的、强大的字符串处理能力

4.1 获取字符串长度

使用 ${#variable_name} 语法。

#!/bin/bash
sentence="学习 Shell 很有趣!"
len=${#sentence}
echo "字符串 '${sentence}' 的长度是: ${len}"

4.2 提取子字符串

使用 ${variable_name:offset:length} 语法。

#!/bin/bash
full_url="https://example.com/products/item123"
protocol=${full_url:0:5}
echo "协议是: ${protocol}"
product_id=${full_url:26}
echo "产品ID是: ${product_id}"

4.3 字符串替换

  • ${variable/pattern/replacement}: 只替换第一个匹配。
  • ${variable//pattern/replacement}: 替换所有匹配。
#!/bin/bash
raw_text="path is /home/user/data dir"
fixed_text=${raw_text/path is/directory is}
echo "修正后的文本: ${fixed_text}"
no_spaces=${raw_text// /_}
echo "无空格版本: ${no_spaces}"

4.4 字符串删除 (裁剪)

  • ${variable#pattern}: 从开头删除最短匹配。
  • ${variable##pattern}: 从开头删除最长匹配。
  • ${variable%pattern}: 从结尾删除最短匹配。
  • ${variable%%pattern}: 从结尾删除最长匹配。
    pattern 可以使用 * 通配符。
#!/bin/bash
filepath="/var/log/httpd/access.log"
# 从开头删除最短的 */
filename=${filepath#*/} # var/log/httpd/access.log
# 从开头删除最长的 */
basename=${filepath##*/} # access.log
# 从结尾删除最短的 .*
name_no_ext=${basename%.*} # access
# 从结尾删除最长的 /
dirname=${filepath%/*} # /var/log/httpd
echo "文件名: ${basename}"
echo "目录名: ${dirname}"
echo "无后缀名: ${name_no_ext}"

练习题

题目:

  1. 以下哪个 Shell 变量定义是错误的,为什么?
    A. my_var=hello
    B. _value="some text"
    C. count = 10
    D. message='Error code: $?'
  2. 假设有变量 filename="data.csv",如何安全地将其与字符串 _backup 拼接成 data.csv_backup
  3. 本地变量环境变量被子进程继承方面有什么关键区别?哪个命令用于将本地变量变为环境变量?
  4. 一个脚本 run.sh 被这样调用:./run.sh first "second arg" third。在 run.sh 内部,变量 $# 的值是什么?
  5. 执行一个不存在的命令 non_exist_cmd 后,$? 的值通常是什么 (一个非零值)?
  6. 当脚本的参数是 "文件 1.txt""文件 2.txt" 时,for i in "$*"for i in "$@" 遍历的结果有何不同
  7. 如何获取变量 address="北京市海淀区"长度
  8. 给定 version_str="app-1.0.5-release",如何提取出中间的版本数字 1.0.5
  9. 如何将字符串 path_var="/usr/bin:/usr/local/bin:/bin"所有的冒号 : 替换为空格
  10. readonly my_const="cannot_change" 执行后,再执行 unset my_const 会发生什么?
  11. my_var="value"unset my_var 之后,echo ${my_var} 的输出有何不同?
  12. 如何将一个名为 API_TOKEN 的本地变量传递给一个用 python my_script.py 启动的子进程?
  13. echo $$ 命令会输出什么?
  14. 给定 full_path="/home/user/documents/report.docx",如何只提取出文件名 report.docx
  15. 给定 full_path="/home/user/documents/report.docx",如何只提取出目录路径 /home/user/documents

答案与解析:

  1. 答案: C. count = 10 是错误的。
    解析: Shell 变量赋值时,等号 = 两边绝对不能有空格。正确写法应为 count=10
  2. 答案: ${filename}_backup
    解析: 推荐使用花括号 {} 来明确界定变量名,防止 Shell 将 _backup 视为变量名的一部分。
  3. 答案: 本地变量 不会被子进程继承,而环境变量 可以被子进程继承。export 命令用于将本地变量变为环境变量。
  4. 答案: $# 的值是 3
    解析: "second arg" 因为被双引号包围,被视为一个独立的、完整的参数
  5. 答案: $? 的值会是一个非零值 (通常是127,表示 “command not found”)。
    解析: $? 记录上一个命令的退出状态码0 代表成功,任何非零值都表示某种形式的失败。
  6. 答案:
    for i in "$*": 循环只执行一次,变量 i 的值是整个字符串 "文件 1.txt 文件 2.txt"
    for i in "$@": 循环会执行两次。第一次 i"文件 1.txt",第二次 i"文件 2.txt""$@" 更适合逐个处理带空格的参数。
  7. 答案: ${#address}
    • 解析: ${#variable} 是获取字符串长度的标准语法。
  8. 答案: ${version_str:4:5}
    解析:索引4 (第5个字符) 开始,提取5个字符。
  9. 答案: ${path_var//:/ }
    解析: 双斜杠 // 表示全局替换 (替换所有匹配项)。
  10. 答案:报错,提示变量是只读的,无法被unset
    解析: readonly 属性保护变量不被修改或删除。
  11. 答案: my_var="value"echo 输出 valueunset my_varecho 输出一个空行
    解析: unset 彻底移除了变量,而不仅仅是将其值设为空。
  12. 答案:
export API_TOKEN="your_token_here"
python my_script.py
  • 解析: 必须先使用 exportAPI_TOKEN 提升为环境变量,这样 python 这个子进程才能继承并访问它。
  1. 答案: 会输出当前正在执行 echo 命令的那个 Shell 进程的进程ID (PID)
  2. 答案: ${full_path##*/}
    解析: ##*/ 表示从字符串开头 (##) 删除最长匹配 */ (任何字符直到最后一个斜杠) 的部分。
  3. 答案: ${full_path%/*}
    解析: %/* 表示从字符串结尾 (%) 删除最短匹配 /* (一个斜杠及后面的所有字符) 的部分。
【声明】本内容来自华为云开发者社区博主,不代表华为云及华为云开发者社区的观点和立场。转载时必须标注文章的来源(华为云社区)、文章链接、文章作者等基本信息,否则作者和本社区有权追究责任。如果您发现本社区中有涉嫌抄袭的内容,欢迎发送邮件进行举报,并提供相关证据,一经查实,本社区将立刻删除涉嫌侵权内容,举报邮箱: cloudbbs@huaweicloud.com
  • 点赞
  • 收藏
  • 关注作者

评论(0

0/1000
抱歉,系统识别当前为高风险访问,暂不支持该操作

全部回复

上滑加载中

设置昵称

在此一键设置昵称,即可参与社区互动!

*长度不超过10个汉字或20个英文字符,设置后3个月内不可修改。

*长度不超过10个汉字或20个英文字符,设置后3个月内不可修改。