八、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%/*}
    解析: %/* 表示从字符串结尾 (%) 删除最短匹配 /* (一个斜杠及后面的所有字符) 的部分。
目录
相关文章
|
Ubuntu Linux 网络安全
Linux系统初始化脚本
一款支持Rocky、CentOS、Ubuntu、Debian、openEuler等主流Linux发行版的系统初始化Shell脚本,涵盖网络配置、主机名设置、镜像源更换、安全加固等多项功能,适配单/双网卡环境,支持UEFI引导,提供多版本下载与持续更新。
1248 3
Linux系统初始化脚本
|
算法 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是在当前“房间”里跑,适合用来加载环境变量和配置文件。
1122 9
|
Linux Shell Perl
查找目录下的所有文件中是否含有某个字符串 linux
查找目录下的所有文件中是否含有某个字符串 find .|xargs grep -ri "IBM" 查找目录下的所有文件中是否含有某个字符串,并且只打印出文件名 find .|xargs grep -ri "IBM" -l 1.正则表达式    (1)正则表达式一般用来描述文本模式的特殊用法,由普通字符(例如字符a-z)以及特殊字符(称为元字符,如/、*、?等)组成。
1150 0
|
Linux 应用服务中间件 Shell
二、Linux文本处理与文件操作核心命令
熟悉了Linux的基本“行走”后,就该拿起真正的“工具”干活了。用grep这个“放大镜”在文件里搜索内容,用find这个“探测器”在系统中寻找文件,再用tar把东西打包带走。最关键的是要学会使用管道符|,它像一条流水线,能把这些命令串联起来,让简单工具组合出强大的功能,比如 ps -ef | grep 'nginx' 就能快速找出nginx进程。
1173 1
二、Linux文本处理与文件操作核心命令
linux命令—stat
`stat` 是 Linux 系统中用于查看文件或文件系统详细状态信息的命令。相比 `ls -l`,它提供更全面的信息,包括文件大小、权限、所有者、时间戳(最后访问、修改、状态变更时间)、inode 号、设备信息等。其常用选项包括 `-f` 查看文件系统状态、`-t` 以简洁格式输出、`-L` 跟踪符号链接,以及 `-c` 或 `--format` 自定义输出格式。通过这些选项,用户可以灵活获取所需信息,适用于系统调试、权限检查、磁盘管理等场景。
887 137
|
安全 Ubuntu Unix
一、初识 Linux 与基本命令
玩转Linux命令行,就像探索一座新城市。首先要熟悉它的“地图”,也就是/根目录下/etc(放配置)、/home(住家)这些核心区域。然后掌握几个“生存口令”:用ls看周围,cd去别处,mkdir建新房,cp/mv搬东西,再用cat或tail看文件内容。最后,别忘了随时按Tab键,它能帮你自动补全命令和路径,是提高效率的第一神器。
1867 58
|
存储 安全 Linux
Linux卡在emergency mode怎么办?xfs_repair 命令轻松解决
Linux虚拟机遇紧急模式?别慌!多因磁盘挂载失败。本文教你通过日志定位问题,用`xfs_repair`等工具修复文件系统,三步快速恢复。掌握查日志、修磁盘、验重启,轻松应对紧急模式,保障系统稳定运行。
1807 2
|
Unix Linux 程序员
Linux文本搜索工具grep命令使用指南
以上就是对Linux环境下强大工具 `grep` 的基础到进阶功能介绍。它不仅能够执行简单文字查询任务还能够处理复杂文字处理任务,并且支持强大而灵活地正则表达规范来增加查询精度与效率。无论您是程序员、数据分析师还是系统管理员,在日常工作中熟练运用该命令都将极大提升您处理和分析数据效率。
1084 16