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

简介: Shell脚本里的变量就像一个个贴着标签的“箱子”。装东西(赋值)时,=两边千万不能有空格。用单引号''装进去的东西会原封不动,用双引号""则会让里面的$变量先“变身”再装箱。默认箱子只能在当前“房间”(Shell进程)用,想让隔壁房间(子进程)也能看到,就得给箱子盖个export的“出口”戳。此外,Shell还自带了$?(上条命令的成绩单)和$1(别人递进来的第一个包裹)等许多特殊箱子,非常有用。
在Shell脚本编程中, 变量是 存储和操作数据的 基石。理解 如何定义、使用、传递变量以及 如何处理字符串,是 编写高效、灵活脚本的 第一步,也是 最关键的一步。

## 思维导图
image.png
image.png
image.png
image.png


## 一、变量的定义与使用

### 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 的两种用法:

  1. 先定义,后导出:
    my_local_var="初始值"
    export my_local_var
    
    1. 定义并同时导出 (更常用):
      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.保存并退出

  1. 使配置立即生效,执行 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}"

练习题

题目:

  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 输出 value。unset my_var 后 echo 输出一个空行。
    解析: unset 彻底移除了变量,而不仅仅是将其值设为空。
  12. 答案:
    export API_TOKEN="your_token_here"
    python my_script.py
    
    • 解析: 必须先使用 export 将 API_TOKEN 提升为环境变量,这样 python 这个子进程才能继承并访问它。
  13. 答案: 会输出当前正在执行 echo 命令的那个 Shell 进程的进程ID (PID)。
  14. 答案: ${full_path##*/}
    解析: ##*/ 表示从字符串开头 (##) 删除最长匹配 */ (任何字符直到最后一个斜杠) 的部分。
  15. 答案: ${full_path%/*}
    解析: %/* 表示从字符串结尾 (%) 删除最短匹配 /* (一个斜杠及后面的所有字符) 的部分。
目录
相关文章
|
算法 Linux Shell
Linux实用技能:打包压缩、热键、Shell与权限管理
本文详解Linux打包压缩技巧、常用命令与原理,涵盖.zip与.tgz格式操作、跨系统传文件方法、Shell运行机制及权限管理,助你高效使用Linux系统。
Linux实用技能:打包压缩、热键、Shell与权限管理
|
存储 安全 Unix
七、Linux Shell 与脚本基础
别再一遍遍地敲重复的命令了,把它们写进Shell脚本,就能一键搞定。脚本本质上就是个存着一堆命令的文本文件,但要让它“活”起来,有几个关键点:文件开头最好用#!/usr/bin/env bash来指定解释器,并用chmod +x给它执行权限。执行时也有讲究:./script.sh是在一个新“房间”(子Shell)里跑,不影响你;而source script.sh是在当前“房间”里跑,适合用来加载环境变量和配置文件。
1074 9
|
Web App开发 缓存 安全
Linux一键清理系统垃圾:释放30GB空间的Shell脚本实战​
这篇博客介绍了一个实用的Linux系统盘清理脚本,主要功能包括: 安全权限检查和旧内核清理,保留当前使用内核 7天以上日志文件清理和系统日志压缩 浏览器缓存(Chrome/Firefox)、APT缓存、临时文件清理 智能清理Snap旧版本和Docker无用数据 提供磁盘空间使用前后对比和大文件查找功能 脚本采用交互式设计确保安全性,适合定期维护开发环境、服务器和个人电脑。文章详细解析了脚本的关键功能代码,并给出了使用建议。完整脚本已开源,用户可根据需求自定义调整清理策略。
1525 1
|
Linux Shell
在Linux、CentOS7中设置shell脚本开机自启动服务
以上就是在CentOS 7中设置shell脚本开机自启动服务的全部步骤。希望这个指南能帮助你更好地管理你的Linux系统。
2548 25
|
Linux Shell
Centos或Linux编写一键式Shell脚本删除用户、组指导手册
Centos或Linux编写一键式Shell脚本删除用户、组指导手册
505 4
|
Linux Shell 数据安全/隐私保护
Centos或Linux编写一键式Shell脚本创建用户、组、目录分配权限指导手册
Centos或Linux编写一键式Shell脚本创建用户、组、目录分配权限指导手册
792 3
|
Shell Linux
【linux】Shell脚本中basename和dirname的详细用法教程
本文详细介绍了Linux Shell脚本中 `basename`和 `dirname`命令的用法,包括去除路径信息、去除后缀、批量处理文件名和路径等。同时,通过文件备份和日志文件分离的实践应用,展示了这两个命令在实际脚本中的应用场景。希望本文能帮助您更好地理解和应用 `basename`和 `dirname`命令,提高Shell脚本编写的效率和灵活性。
1684 32
|
运维 监控 Shell
深入理解Linux系统下的Shell脚本编程
【10月更文挑战第24天】本文将深入浅出地介绍Linux系统中Shell脚本的基础知识和实用技巧,帮助读者从零开始学习编写Shell脚本。通过本文的学习,你将能够掌握Shell脚本的基本语法、变量使用、流程控制以及函数定义等核心概念,并学会如何将这些知识应用于实际问题解决中。文章还将展示几个实用的Shell脚本例子,以加深对知识点的理解和应用。无论你是运维人员还是软件开发者,这篇文章都将为你提供强大的Linux自动化工具。
|
存储 Shell Linux
【Linux】shell基础,shell脚本
Shell脚本是Linux系统管理和自动化任务的重要工具,掌握其基础及进阶用法能显著提升工作效率。从简单的命令序列到复杂的逻辑控制和功能封装,Shell脚本展现了强大的灵活性和实用性。不断实践和探索,将使您更加熟练地运用Shell脚本解决各种实际问题
411 1
|
人工智能 监控 Shell
常用的 55 个 Linux Shell 脚本(包括基础案例、文件操作、实用工具、图形化、sed、gawk)
这篇文章提供了55个常用的Linux Shell脚本实例,涵盖基础案例、文件操作、实用工具、图形化界面及sed、gawk的使用。
5036 2

热门文章

最新文章