在Shell脚本编程中,
变量是
存储和操作数据的
基石。理解
如何定义、使用、传递变量以及
如何处理字符串,是
编写高效、灵活脚本的
第一步,也是
最关键的一步。
## 思维导图
## 一、变量的定义与使用
### 1.1 定义变量
基本格式:
命名规则: 变量名通常由字母、数字、下划线构成,且不能以数字开头。习惯上,全大写用于环境变量或常量,小写或驼峰式用于本地变量。 值的“类型”: Shell 不严格区分数据类型,几乎所有值都可视为 字符串。即使是数字,在进行 算术运算前,本质上也是字符串。
引号的使用规则:
> 如果值 不包含空格或特殊字符,可以 省略引号:
> 如果值 包含空格, 必须使用 单引号
单引号 (' '): 强引用或 “字面”引用。其内部所有内容都 按原样处理, 变量不会被解析,特殊字符也 失去其特殊含义。
双引号 (" "): 弱引用或 “解析”引用。其内部的 变量引用 (
### 1.2 引用变量
要 获取或使用变量的值,在变量名前加上 $ 符号。
基础用法:
为什么推荐使用
> 明确边界: 当变量名后紧跟其他字符时,花括号能 清晰地界定变量名的范围,避免歧义。例如,
高级功能: 所有 高级字符串操作 (如截取、替换) 都 必须在花括号内进行。
### 1.3 只读变量与删除变量
只读变量 (
使用
使用
## 二、变量的作用域
变量的 有效范围 (作用域) 是脚本编程中的 核心概念2.2 环境变量 与
## 思维导图
## 一、变量的定义与使用
### 1.1 定义变量
基本格式:
variable_name=value
关键点:赋值号
= 的两边
绝对不能有空格!这是初学者
最常犯的错误之一,务必留意!
命名规则: 变量名通常由字母、数字、下划线构成,且不能以数字开头。习惯上,全大写用于环境变量或常量,小写或驼峰式用于本地变量。 值的“类型”: Shell 不严格区分数据类型,几乎所有值都可视为 字符串。即使是数字,在进行 算术运算前,本质上也是字符串。
引号的使用规则:
> 如果值 不包含空格或特殊字符,可以 省略引号:
myvar=hello
> 如果值 包含空格, 必须使用 单引号
' ' 或
双引号
" " 包围:
message='Hello World'
单引号 (' '): 强引用或 “字面”引用。其内部所有内容都 按原样处理, 变量不会被解析,特殊字符也 失去其特殊含义。
双引号 (" "): 弱引用或 “解析”引用。其内部的 变量引用 (
$var) 会被替换为变量的值,某些特殊字符 (如
$、
\、
` ) 依然
有效。
bash # 正确的变量定义 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 的变量。
高级功能: 所有 高级字符串操作 (如截取、替换) 都 必须在花括号内进行。
bash #!/bin/bash user="Bob" action="studying" echo "用户是: ${user}" echo "${user}正在${action}Shell。"
### 1.3 只读变量与删除变量
只读变量 (
readonly):使用
readonly 命令可以将一个变量锁定,使其变为只读。一旦设置,其值不能被修改,也无法被 unset 删除。非常适合定义脚本中的常量。bash #!/bin/bash readonly APP_VERSION="1.0.2" echo "当前应用版本: ${APP_VERSION}" # 尝试修改将导致错误 # APP_VERSION="1.0.3" # 尝试删除也将导致错误 # unset APP_VERSION
删除变量 (unset):
使用
unset 命令可以
彻底删除一个变量 (包括其名称和值)。
注意:
readonly 的变量无法被删除。
bash #!/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 的两种用法:
- 先定义,后导出:
my_local_var="初始值" export my_local_var- 定义并同时导出 (更常用):
export SHARED_CONFIG_PATH="/etc/my_app/config"
- 定义并同时导出 (更常用):
- 验证继承性:
```bash!/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有值!
* **查看环境变量:** 使用 `env` 或 `printenv` 命令可以<font color='purple'>列出当前</font>所有的环境变量。
### **<font color='orange'>2.3 让环境变量“永久生效”</font>**
通过 `export` 设置的环境变量是<font color='red'>临时的</font>,随当前 Shell 会话关闭而<font color='gray'>失效</font>。要使其<font color='green'>永久生效</font>,需要将其写入<font color='blue'>Shell的配置文件</font>。
**常用配置文件 (Bash):**
> `~/.bashrc`: <font color='purple'>常用</font>。每次打开<font color='red'>新的交互式终端</font>时加载。适合<font color='teal'>个人常用</font>的环境变量。
`~/.bash_profile`: 用户<font color='red'>登录</font>时加载一次。
`/etc/profile`: <font color='purple'>最常用。</font><font color='red'>系统全局</font>配置,影响<font color='blue'>所有用户</font>。
**配置步骤:**
1. 用文本编辑器打开配置文件 (如 `vim /etc/profile`)
2. 在文件<font color='blue'>末尾</font>添加 `export` 命令:
```bash
export JAVA_HOME="/usr/lib/jvm/java-11-openjdk-amd64"
export PATH=$PATH:$JAVA_HOME/bin
3.保存并退出
- 使配置立即生效,执行
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` | 内部字段分隔符 (默认为空格、制表符、换行符) | * **位置参数变量:** ```bash #!/bin/bash # 文件名: show_params.sh echo "脚本名 (\$0): $0" echo "第一个参数 (\$1): $1" echo "第二个参数 (\$2): $2" # 执行: ./show_params.sh apple banana ``` * **特殊含义变量:** ```bash #!/bin/bash # 文件名: special_vars.sh echo "收到参数个数 (\$#): $#" ls /no_such_dir echo "上个命令的退出状态码 (\$?): $?" echo "本脚本的进程号 (\$\$): $$" |
## **<font color="darkblue">四、字符串操作</font>**
Shell 提供了<font color='red'>内置</font>的、<font color='blue'>强大</font>的字符串<font color='green'>处理能力</font>。
### **<font color="teal">4.1 获取字符串长度</font>**
使用 `${#variable_name}` 语法。
```bash
#!/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}"
练习题
题目:
- 以下哪个 Shell 变量定义是错误的,为什么?
A.my_var=hello
B._value="some text"
C.count = 10
D.message='Error code: $?' - 假设有变量
filename="data.csv",如何安全地将其与字符串_backup拼接成data.csv_backup? - 本地变量和环境变量在被子进程继承方面有什么关键区别?哪个命令用于将本地变量变为环境变量?
- 一个脚本
run.sh被这样调用:./run.sh first "second arg" third。在run.sh内部,变量$#的值是什么? - 执行一个不存在的命令
non_exist_cmd后,$?的值通常是什么 (一个非零值)? - 当脚本的参数是
"文件 1.txt"和"文件 2.txt"时,for i in "$*"和for i in "$@"遍历的结果有何不同? - 如何获取变量
address="北京市海淀区"的长度? - 给定
version_str="app-1.0.5-release",如何提取出中间的版本数字1.0.5? - 如何将字符串
path_var="/usr/bin:/usr/local/bin:/bin"中所有的冒号:替换为空格? readonly my_const="cannot_change"执行后,再执行unset my_const会发生什么?my_var="value"和unset my_var之后,echo ${my_var}的输出有何不同?- 如何将一个名为
API_TOKEN的本地变量传递给一个用python my_script.py启动的子进程? echo $$命令会输出什么?- 给定
full_path="/home/user/documents/report.docx",如何只提取出文件名report.docx? - 给定
full_path="/home/user/documents/report.docx",如何只提取出目录路径/home/user/documents?
- 答案: C.
count = 10是错误的。
解析: Shell 变量赋值时,等号=两边绝对不能有空格。正确写法应为count=10。 - 答案:
${filename}_backup
解析: 推荐使用花括号{}来明确界定变量名,防止 Shell 将_backup视为变量名的一部分。 - 答案: 本地变量 不会被子进程继承,而环境变量 可以被子进程继承。
export命令用于将本地变量变为环境变量。 - 答案:
$#的值是3。
解析:"second arg"因为被双引号包围,被视为一个独立的、完整的参数。 - 答案:
$?的值会是一个非零值 (通常是127,表示 "command not found")。
解析:$?记录上一个命令的退出状态码。0代表成功,任何非零值都表示某种形式的失败。 - 答案:
for i in "$*": 循环只执行一次,变量i的值是整个字符串"文件 1.txt 文件 2.txt"。for i in "$@": 循环会执行两次。第一次i是"文件 1.txt",第二次i是"文件 2.txt"。"$@"更适合逐个处理带空格的参数。 - 答案:
${#address}- 解析:
${#variable}是获取字符串长度的标准语法。
- 解析:
- 答案:
${version_str:4:5}
解析: 从索引4 (第5个字符) 开始,提取5个字符。 - 答案:
${path_var//:/ }
解析: 双斜杠//表示全局替换 (替换所有匹配项)。 - 答案: 会报错,提示变量是只读的,无法被
unset。
解析:readonly属性保护变量不被修改或删除。 - 答案:
my_var="value"后echo输出value。unset my_var后echo输出一个空行。
解析:unset彻底移除了变量,而不仅仅是将其值设为空。 - 答案:
export API_TOKEN="your_token_here" python my_script.py- 解析: 必须先使用
export将API_TOKEN提升为环境变量,这样python这个子进程才能继承并访问它。
- 解析: 必须先使用
- 答案: 会输出当前正在执行
echo命令的那个 Shell 进程的进程ID (PID)。 - 答案:
${full_path##*/}
解析:##*/表示从字符串开头 (##) 删除最长匹配*/(任何字符直到最后一个斜杠) 的部分。 - 答案:
${full_path%/*}
解析:%/*表示从字符串结尾 (%) 删除最短匹配/*(一个斜杠及后面的所有字符) 的部分。