二、shell脚本--变量与数据类型
1. 变量的定义与使用
定义变量:简单直接
在 Shell 里定义变量相当容易:
- 基本格式:
variable_name=value
- 关键点 ❗:赋值号
=
的两边绝对不能有空格!这绝对是初学者最容易踩的坑之一 😨,务必留意! - 起名字的规矩: 变量名通常由字母、数字、下划线构成,而且不能用数字开头。大家习惯用全大写表示环境变量或脚本常量,用小写或驼峰式表示本地变量。
- 值的“类型”: Shell 对变量不太讲究类型,基本上都当字符串来看待。就算你存个数字,它本质上也是个字符串(不过在做数学计算时,Shell 会尝试把它当数字处理)。
- 给值加引号的学问:
- 如果值里没有空格或特殊符号,可以省略引号:
myvar=hello
- 如果值里有空格,必须用单引号
' '
或双引号" "
包起来:mymessage='Hello World'
或mymessage="Hello World"
- 单引号 (’ '):“死心眼”引用。里面写啥就是啥,完全按字面处理,变量 (
$var
) 不会展开,特殊字符也失去魔力。 - 双引号 (" "):“灵活”引用。里面的变量引用 (
$var
) 会被替换成值,某些特殊字符(像$
、\
、```````)仍然有效。
- 如果值里没有空格或特殊符号,可以省略引号:
# 正确的变量定义
name="Alice"
age=30
city="Beijing"
greeting1='你好, ${name}!' # 单引号内 ${name} 不会被替换
greeting2="你好, ${name}!" # 双引号内 ${name} 会被替换成 Alice# 错误的变量定义 (等号两边有空格)
# wrong_var = "error"
引用变量:取出仓库里的东西
想拿到或用上变量存的值,就在变量名前面加个 $ 符号:
- 基础用法:
$variable_name
- 更稳妥的用法:
${variable_name}
(用花括号{}
包起来)
为什么推荐用 ${}
? 🤔
- 划清界限: 当变量名后面紧跟着其他字符时,花括号能明确告诉 Shell 变量名到哪里结束,避免混淆。比如
${user}_id
就很清晰。 - 高级玩法: 之后要玩转字符串操作(比如截取、替换),花括号是必备的。
#!/bin/bashuser="Bob"
action="studying"
echo "用户是: ${user}" # 输出 用户是: Bob
echo "${user} 正在 ${action} Shell。" # 输出 Bob 正在 studying Shell。
只读变量与删除变量
- 只读变量 (Read-only): 把它锁起来 🛡️
- 用
readonly
命令,能让一个变量变成只读。 - 一旦设为只读,它的值就不能再改,也不能用
unset
删除。 - 适合定义脚本里不希望被意外修改的常量值。
- 用
#!/bin/bashreadonly app_version="1.0.2"
echo "当前应用版本: ${app_version}"# 尝试修改会报错
# app_version="1.0.3" # Error!# 尝试删除也会报错
# unset app_version # Error!
- 删除变量 (Unset): 清空仓库 🗑️
- 用
unset
命令可以彻底删除一个变量(名字和值都没了)。 - 记住:
readonly
的变量是删不掉的。
- 用
#!/bin/bashtmp_dir="/tmp/my_app_temp"
echo "临时目录: ${tmp_dir}"unset tmp_dir # 删除这个变量# 再用它,就是空的了
echo "删除后的临时目录: ${tmp_dir}" # 这里会输出空行
2. 变量的作用域与类型
搞清楚变量在什么地方能用(作用域)非常关键。Shell 里主要有两大类作用域的变量:
本地变量 (普通变量) 🏠
- 如何产生: 默认就是它!只要你用
variable_name=value
这样直接赋值,得到的就是本地变量。 - 活动范围: 非常有限,只在创建它的那个当前的 Shell 进程里有效。可以想象成你房间里的私人物品,别人进不来拿不到。
- 传给下一代? 不行 ❌。它不会被当前 Shell 启动的子进程(比如你运行的另一个脚本或命令)自动认识。子进程对父进程的本地变量一无所知。
#!/bin/bash
# 定义本地变量
my_secret="这是我的小秘密"
echo "父脚本说,我有秘密: ${my_secret}"# 启动一个子 Shell 试试看
echo "--- 启动子 Shell ---"
bash -c 'echo "子 Shell 说,秘密是啥? ${my_secret}"' # 这里 ${my_secret} 是空的,子 Shell 拿不到
echo "--- 子 Shell 结束 ---"
环境变量 (全局变量) 🌍 与 export
命令:让信息传递下去
-
活动范围: 环境变量的影响力更大,不只在当前 Shell 里有效。
-
传给下一代? 可以 ✅!它们能被当前 Shell 启动的所有子进程继承。就像家族的公开信息,子子孙孙都能知道和使用。
-
魔法棒
export
✨: 想让一个变量从本地的“私人物品”变成能被子进程继承的“公开信息”(环境变量)吗?那就得用export
命令!export
的核心作用就是把一个变量“发布”到环境中去。 -
export
的两种常见姿势 ✌️:1.先有鸡(本地变量),再有蛋(导出):
# 1. 先定义一个普普通通的本地变量 my_local_var="初始值" # 2. 然后,用 export 把它“提拔”成环境变量 export my_local_var
2.一步到位,定义的同时就导出: (这种更常用、简洁)
export shared_config_path="/etc/my_app/config"
-
效果立竿见影: 变量一旦被
export
,就光荣地成为了环境变量。从此刻起,这个 Shell 再启动任何新的子进程,这些子进程都能看到并使用这个环境变量了。 -
实例见真章:
#!/bin/bash
# 本地变量,无法继承
local_info="只在父脚本可见"
# 用 export 创建环境变量,可以继承
export shared_info="父子都能看到"echo "父脚本说,本地信息: ${local_info}"
echo "父脚本说,共享信息: ${shared_info}"# 启动子 Shell 来验证
echo "--- 启动子 Shell ---"
bash -c 'echo "子 Shell 说,本地信息: ${local_info}"; echo "子 Shell 说,共享信息: ${shared_info}"'
# 注意看输出:子 Shell 里的 local_info 是空的,但 shared_info 有值!
echo "--- 子 Shell 结束 ---"
- 查看当前的环境变量 📋: 想知道当前环境里都有哪些“公开信息”?在终端敲
env
或者printenv
命令就行。你会看到很多系统预设的环境变量(比如PATH
,HOME
,USER
等),以及你自己用export
添加的那些。
让环境变量“永久生效”:修改配置文件 ⚙️
上面用 export
设置的环境变量只是临时的,一旦你关闭当前的 Shell 窗口(终端),它们就消失了 💨。如果希望某个环境变量长期有效,每次打开新终端都能用,就需要把它写进 Shell 的配置文件里。
-
常见的配置文件 (Bash 为例):
~/.bashrc
: 最常用 👍。每次打开新的交互式 Shell(比如新开一个终端窗口)时,这里面的命令会被执行。非常适合放个人常用的环境变量和别名。~/.bash_profile
(或~/.profile
): 当你登录系统时(比如 TTY 登录,或者 SSH 登录)会执行一次。它通常会调用~/.bashrc
。如果主要在图形界面下开终端,~/.bashrc
更常用。/etc/profile
: 系统全局的配置文件,影响所有用户的登录 Shell。修改它需要管理员权限。
-
如何配置:
-
选择一个合适的配置文件(初学者推荐
~/.bashrc
)。 -
用文本编辑器打开它 (e.g.,
nano ~/.bashrc
或vim ~/.bashrc
)。 -
在文件末尾添加你的
export
命令,例如:export MY_API_KEY="your_secret_api_key_here" export JAVA_HOME="/usr/lib/jvm/java-11-openjdk-amd64"
-
保存文件并退出编辑器。
-
让更改生效:
- 方法一 (推荐): 在当前终端执行
source ~/.bashrc
命令,立即加载配置。 - 方法二: 关闭当前终端,重新打开一个新的终端窗口,新窗口会自动加载
.bashrc
。
- 方法一 (推荐): 在当前终端执行
-
-
示例:添加一个永久环境变量
# 把 export 命令追加到 .bashrc 文件末尾 echo 'export MY_APP_DATA_DIR="/var/myapp/data"' >> ~/.bashrc# 让当前终端也立刻知道这个新变量 source ~/.bashrc# 现在可以验证一下了 echo "我的应用数据目录是: ${MY_APP_DATA_DIR}"
现在,每次你打开新的终端,
${MY_APP_DATA_DIR}
这个变量就自动可用了!🎉
本地变量 vs 环境变量:总结回顾 🆚
特性 | 本地变量 (普通变量) 🏠 | 环境变量 (全局变量) 🌍 |
---|---|---|
定义方式 | variable_name=value (默认) | 使用 export 命令 (如 export var=val ) |
作用域 | 仅限当前 Shell 进程 | 当前 Shell 进程 及 其所有子进程 |
继承性 | 不被 子进程继承 ❌ | 可被 子进程继承 ✅ |
持久性 | 临时,随 Shell 关闭消失 | 临时(若仅 export ),可配置为永久(修改配置文件) |
关键命令 | (默认) | export (设置/导出), env /printenv (查看) |
用途 | 脚本内部计算、临时存储 | PATH 设置、跨脚本共享配置、传递给子命令 |
一句话总结: 默认变量是本地的,用 export
把它变成环境变量,才能让子进程看到。想永久生效?写进配置文件 (~/.bashrc
)。
变量 | 描述 |
---|---|
$0 | 当前脚本的名称 |
$1 至 $9 | 脚本传递给脚本的第一个到第九个参数 |
$# | 脚本传递给脚本的参数个数 |
$@ | 脚本传递给脚本的所有参数(如果加引号:“$@”,每个参数保持独立) |
$* | 脚本传递给脚本的所有参数(如果加引号:“$*”,参数作为一个整体) |
$$ | 当前脚本的进程ID |
$! | 最近一次后台运行命令的进程ID |
$? | 上一个命令的退出状态码 |
$_ | 上一个命令的最后一个参数 |
$- | 当前Shell的选项 |
$IFS | 输入字段分隔符,默认是空格、制表符和换行符赞 |
位置参数变量 🔢
(同前) 脚本运行时,跟在后面的参数。用 $0
, $1
, $2
…${10}
… 引用。
#!/bin/bash
# 文件名: show_params.sh
echo "脚本名字 (\$0): $0"
echo "第一个参数 (\$1): $1"
echo "第二个参数 (\$2): $2"
# 执行方式: ./show_params.sh apple banana
特殊变量 ✨
(同前) Shell 自带的,有特殊含义。$#
(个数), $*
(整体参数), $@
(独立参数), $?
(退出码), $$
(当前PID), $!
(后台PID)。
#!/bin/bash
# 文件名: special_vars.sh
echo "收到参数个数 (\$#): $#"
ls /no_such_file_or_dir # 制造一个错误
echo "上个命令是否成功 (\$?): $?" # 0 代表成功, 非 0 代表失败
echo "本脚本的进程号 (\$\$): $$"
echo "--- 逐个看参数 ---"
idx=1
for param in "$@" # 推荐用 "$@" 来循环处理所有参数
do
echo "第 ${idx} 个参数是: ${param}"
idx=$((idx + 1))
done
# 执行方式: ./special_vars.sh "带空格的参数" 第二个参数
3. 字符串操作 ✂️🔗📏
(这部分内容和之前的版本相同,变量名英文,提示中文,无代码缩进)
字符串的定义与拼接
(同前)
#!/bin/bash
prefix="file_"
timestamp=$(date +%Y%m%d)
full_filename="${prefix}${timestamp}.log"
echo "完整文件名: ${full_filename}"
获取字符串长度 📏
(同前) ${#variable_name}
#!/bin/bash
sentence="学习 Shell 很有趣!"
len=${#sentence}
echo "这句话是: '${sentence}'"
echo "它的长度是: ${len}"
提取子字符串 ✂️
(同前) ${variable_name:offset:length}
#!/bin/bash
full_url="https://example.com/products/item123"
protocol=${full_url:0:5} # 从第0位开始,取5个字符
echo "协议是: ${protocol}" # https
product_id=${full_url:26} # 从第26位开始,取到末尾
echo "产品ID是: ${product_id}" # item123
字符串替换 🔁
(同前) ${var/pat/rep}
(首个), ${var//pat/rep}
(全部)
#!/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}"
变量与数据类型练习题 🧠✍️
题目一:变量定义
❓ 以下哪个 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 third
。在 run.sh
内部,变量 $3
的值是什么?
题目五:特殊变量含义
❓ 执行一个命令后,应该检查哪个特殊变量来判断该命令是否成功执行?如果成功,这个变量的值通常是什么?
题目六:$*
vs $@
关键场景
❓ 当脚本的参数是 "文件 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"
中所有的冒号 :
替换为空格
?
参考答案 ✅💡
答案一:
C. count = 10
是错误的 ❌。
原因: Shell 变量赋值时,等号 =
两边绝对不能有空格。正确写法应为 count=10
。
答案二:
推荐使用花括号 {}
来界定变量名:${filename}_backup
✅。
答案三:
关键区别:本地变量 不会被子进程继承,而环境变量 可以被子进程继承 ✅。使用 export
命令将本地变量变为环境变量。
答案四:
变量 $3
的值是 third
。
答案五:
应该检查 $?
❓。如果命令成功执行,$?
的值通常是 0
✅。非零值表示有错误发生。
答案六:
结果不同:
for i in "$*"
: 循环只执行一次,变量i
的值是整个字符串"文件 1.txt 文件 2.txt"
。for i in "$@"
: 循环会执行两次。第一次i
是"文件 1.txt"
,第二次i
是"文件 2.txt"
。
因此,for i in "$@"
更适合逐个、且保持参数完整性地处理这些文件名 👍。
答案七:
使用 ${#address}
可以获取其长度 📏。
答案八:
可以使用 ${version_str:4:5}
。
4
是起始偏移量 (索引从0开始,所以第5个字符’1’的索引是4)。5
是要提取的长度 (‘1.0.5’ 这5个字符)。
答案九:
使用双斜杠 //
进行全局替换:${path_var//:/ }
🔁。(冒号被替换为空格)。