【Linux从入门到精通】第22篇:Shell变量与数据类型——数字与字符串处理

目录

一、引言:变量不只是"存个值"

[二、环境变量 vs 局部变量:作用域的秘密](#二、环境变量 vs 局部变量:作用域的秘密)

[2.1 用实验理解差别](#2.1 用实验理解差别)

[2.2 什么时候用哪种?](#2.2 什么时候用哪种?)

[2.3 查看当前所有环境变量](#2.3 查看当前所有环境变量)

[2.4 持久化环境变量](#2.4 持久化环境变量)

三、只读变量与常量保护

[3.1 readonly:让变量变成"常量"](#3.1 readonly:让变量变成“常量”)

[3.2 什么时候用只读变量?](#3.2 什么时候用只读变量?)

[3.3 查看所有只读变量](#3.3 查看所有只读变量)

四、字符串处理:Shell脚本的核心技艺

[4.1 字符串拼接](#4.1 字符串拼接)

[4.2 获取字符串长度](#4.2 获取字符串长度)

[4.3 截取子字符串](#4.3 截取子字符串)

[4.4 从首尾删除模式匹配](#4.4 从首尾删除模式匹配)

[4.5 字符串替换](#4.5 字符串替换)

[4.6 其他常用判断](#4.6 其他常用判断)

五、数字运算:Shell的"计算器"

[5.1 整数运算:(()) 和 \[\]](#5.1 整数运算:(()) 和 [])

[5.2 常用的数学场景](#5.2 常用的数学场景)

六、综合实战:Nginx日志解析器

七、本篇小结

动手练习

八、下篇预告


一、引言:变量不只是"存个值"

写脚本久了你会发现,真正花时间的不是"怎么定义变量",而是怎么处理变量里的内容

  • 从日志文件中提取出IP地址

  • 批量修改文件名后缀(.jpg.png

  • 检查路径末尾有没有多余的斜杠

  • 拼接出带时间戳的备份文件名

Shell是一种"弱类型"语言------变量不需要声明类型,同一个变量可以存数字、存字符串、甚至存命令输出。这种灵活性是便利也是坑:你必须清楚知道"现在这个变量里到底装着什么"。今天的目标就是掌握这些处理技巧。

二、环境变量 vs 局部变量:作用域的秘密

2.1 用实验理解差别

先做一个小实验。打开两个终端,在第一个终端中:

bash

复制代码
# 终端1
my_var="hello"
echo $my_var         # 输出:hello
bash                 # 启动一个子Shell
echo $my_var         # 输出:空!(子Shell访问不到父Shell的局部变量)
exit                 # 退出子Shell

现在改用export

bash

复制代码
# 终端1
export my_var="hello"
bash                 # 启动一个子Shell
echo $my_var         # 输出:hello(子Shell能访问到环境变量了)
exit

这个实验揭示了局部变量环境变量的核心区别:前者只在当前Shell进程内可见,后者会传递给所有子进程。

2.2 什么时候用哪种?

局部变量(默认):

bash

复制代码
# 脚本内部的临时变量,用局部变量
temp_file="/tmp/backup_$(date +%s).tar.gz"
counter=0

环境变量(需要export):

bash

复制代码
# 需要让子进程(如Python脚本、其他Shell)读取的配置
export DATABASE_URL="mysql://localhost:3306/mydb"
export APP_ENV="production"

2.3 查看当前所有环境变量

bash

复制代码
env           # 查看所有环境变量
printenv      # 同上
printenv HOME # 查看特定环境变量

常见预定义环境变量

变量 含义 示例值
$HOME 当前用户家目录 /home/zhangsan
$PATH 命令搜索路径 /usr/local/bin:/usr/bin:/bin
$USER 当前用户名 zhangsan
$PWD 当前工作目录 /home/zhangsan/project
$SHELL 当前Shell /bin/bash
$OLDPWD 上一个工作目录 /tmp
$RANDOM 0-32767的随机数 12345
$$ 当前Shell进程的PID 5678

2.4 持久化环境变量

在脚本中用export定义的环境变量只在当前会话生效。要让环境变量永久生效,需要写入Shell配置文件:

bash

复制代码
# 添加到 ~/.bashrc(每个交互式Shell都会加载)
vim ~/.bashrc
export MY_PROJECT_HOME="/opt/myproject"

# 让配置立即生效
source ~/.bashrc

注意~/.bashrc每次打开终端都会执行,~/.bash_profile只执行一次。一般把环境变量定义在~/.bashrc中,然后在~/.bash_profile里source它。

三、只读变量与常量保护

3.1 readonly:让变量变成"常量"

bash

复制代码
#!/bin/bash
readonly PI=3.14159
PI=3.14   # 报错:PI: readonly variable

3.2 什么时候用只读变量?

bash

复制代码
#!/bin/bash
# 配置文件路径——不希望在脚本中被意外修改
readonly CONFIG_FILE="/etc/myapp/config.yaml"
readonly LOG_DIR="/var/log/myapp"
readonly MAX_RETRIES=3

# 后续代码中 CONFIG_FILE 绝不会被意外覆盖

3.3 查看所有只读变量

bash

复制代码
readonly        # 列出所有只读变量(包括系统预定义的)
readonly -p     # 同上

你会发现$HOME$USER等也是只读的------系统不允许你修改这些关键信息。

四、字符串处理:Shell脚本的核心技艺

Shell中一切都是字符串。你输入的IP、文件名、路径、日志内容------全是字符串。掌握字符串处理,就掌握了Shell脚本的半壁江山。

4.1 字符串拼接

Shell的拼接非常直接------把变量挨着写就行

bash

复制代码
name="zhangsan"
greeting="Hello, ${name}!"
echo $greeting   # Hello, zhangsan!

# 多个变量拼接
src="/var/log"
app="nginx"
filename="error.log"
full_path="${src}/${app}/${filename}"
echo $full_path   # /var/log/nginx/error.log

避免歧义的拼接

bash

复制代码
prefix="super"
echo "${prefix}man"   # superman(推荐)
echo "$prefix"man     # superman(也可,但不够清晰)
echo $prefix"man"     # superman
echo $prefix man      # super man(空格被保留了,不是拼接!)

4.2 获取字符串长度

bash

复制代码
str="Hello World"
echo ${#str}         # 11

实用场景:判断用户输入是否为空:

bash

复制代码
if [ ${#input} -eq 0 ]; then
    echo "输入不能为空!"
    exit 1
fi

4.3 截取子字符串

格式:${变量:起始位置:长度}起始位置从0开始

bash

复制代码
str="Hello World"

echo ${str:0:5}      # Hello (位置0开始,取5个字符)
echo ${str:6}        # World  (位置6开始,取到末尾)
echo ${str:6:3}      # Wor    (位置6开始,取3个)
echo ${str: -5}      # World  (从末尾倒数5个,注意冒号后有空格)

实用场景

bash

复制代码
# 从日期时间戳中提取年月日
timestamp="20260426_153000"
date_part=${timestamp:0:8}   # 20260426
time_part=${timestamp:9}     # 153000

# 去掉文件扩展名
filename="report.tar.gz"
base=${filename:0:10}        # 依赖硬编码位置,不够通用
# 更好的方法见下一节的模式匹配

4.4 从首尾删除模式匹配

这是Shell字符串处理中最强大的功能,有四种模式:

语法 含义 记忆
${var#pattern} 开头 删除最短匹配 #像一个井号,开头的标记
${var##pattern} 开头 删除最长匹配 ##多删
${var%pattern} 末尾 删除最短匹配 %像百分号,结尾的标记
${var%%pattern} 末尾 删除最长匹配 %%多删

实战:处理文件路径

bash

复制代码
path="/home/zhangsan/project/report.tar.gz"

# 获取文件名(删除最后一个/之前的所有内容)
filename=${path##*/}
echo $filename   # report.tar.gz

# 获取目录名(删除文件名部分)
dirname=${path%/*}
echo $dirname    # /home/zhangsan/project

# 获取文件扩展名(删除最后一个.之前的所有内容)
ext=${path##*.}
echo $ext        # gz(注意不是 tar.gz)

# 获取不带扩展名的文件名
base=${filename%%.*}
echo $base       # report(删除了 .tar.gz)

实战:批量处理日志文件

bash

复制代码
# 把所有 .log 文件重命名为 .log.bak
for file in *.log; do
    base=${file%.log}               # 去掉 .log 后缀
    mv "$file" "${base}.log.bak"
done

4.5 字符串替换

格式:${变量/旧内容/新内容}

bash

复制代码
url="http://example.com/index.html"

# 替换第一个匹配
echo ${url/http/https}        # https://example.com/index.html

# 替换所有匹配(加一个/)
path="/usr/local/bin:/usr/bin:/bin"
echo ${path//\/usr/\/opt}     # /opt/local/bin:/opt/bin:/bin

单斜杠与双斜杠的区别

bash

复制代码
text="aaaa"
echo ${text/a/A}    # Aaaa(只替换第一个)
echo ${text//a/A}   # AAAA(替换所有)

4.6 其他常用判断

语法 含义 示例
${var:-默认值} 如果var为空,返回默认值 ${user:-root}
${var:=默认值} 如果var为空,赋值为默认值并返回 ${count:=1}
${var:+替代值} 如果var不为空,返回替代值 ${debug:+verbose}
${var:?错误信息} 如果var为空,打印错误并退出 ${config:?未设置}

bash

复制代码
#!/bin/bash
# 使用默认值,避免变量未定义
DB_HOST=${DB_HOST:-"localhost"}
DB_PORT=${DB_PORT:-3306}
echo "连接数据库 ${DB_HOST}:${DB_PORT}"

五、数字运算:Shell的"计算器"

Shell默认把变量值当字符串处理,要做数学运算需要特殊语法。

5.1 整数运算:(()) 和 \[\]

bash

复制代码
a=10
b=3

echo $((a + b))    # 13
echo $((a - b))    # 7
echo $((a * b))    # 30
echo $((a / b))    # 3(整数除法,向下取整)
echo $((a % b))    # 1(取余)
echo $((a ** b))   # 1000(指数)

也可以写成

bash

复制代码
echo $[a + b]     # 老式写法,仍然可用但不推荐
let "c = a + b"   # 使用let命令

5.2 常用的数学场景

bash

复制代码
# 计数器(循环中常用)
count=0
count=$((count + 1))

# 计算百分比
total=100
used=35
percent=$((used * 100 / total))   # 35
echo "使用率:${percent}%"

# 随机数生成
random_num=$((RANDOM % 100))      # 0-99的随机数
echo $random_num

六、综合实战:Nginx日志解析器

把今天学的字符串处理技巧整合到一个实用脚本中:

bash

复制代码
#!/bin/bash
# 练习脚本:简单Nginx访问日志解析

# 模拟一条Nginx日志(实际使用时应从文件读取)
log_line='192.168.1.100 - - [26/Apr/2026:15:30:00 +0800] "GET /index.html HTTP/1.1" 200 1024 "http://example.com" "Mozilla/5.0"'

echo "原始日志:"
echo "${log_line}"
echo ""

# 1. 提取IP地址(第一个字段)
ip=${log_line%% *}
echo "IP地址:${ip}"

# 2. 提取请求时间([]之间的内容)
# 先删除 [ 之前的所有内容
time_bracket=${log_line#*[}
# 再删除 ] 之后的所有内容
time=${time_bracket%%]*}
echo "请求时间:${time}"

# 3. 提取请求方法和路径(引号之间的内容)
request_part=${log_line#*\"}
request=${request_part%%\"*}
echo "请求内容:${request}"

# 4. 提取HTTP状态码(请求行后面的三个数字)
# 先找到"开始的第一次出现,去掉它
after_request=${log_line#*\"}
# 再去掉中间的 HTTP版本"部分
status_part=${after_request#*\" }
# 取第一个字段(状态码)
status_code=${status_part%% *}
echo "状态码:${status_code}"

# 5. 判断状态码类型
if [ ${#status_code} -eq 3 ]; then
    case ${status_code:0:1} in
        2) echo "  → 请求成功" ;;
        3) echo "  → 重定向" ;;
        4) echo "  → 客户端错误" ;;
        5) echo "  → 服务器错误" ;;
    esac
fi

# 6. 统计:请求路径中包含home的请求次数(实际使用时应循环处理所有行)
path_part=${request#* }
path=${path_part%% *}
if [ "${path:0:5}" = "/home" ]; then
    echo "  → 这是首页请求"
fi

该练习展示了:日志提取字段 + 字符串截取 + 长度判断 + 条件分支的组合使用。实际生产场景中只需把输入改为while read line; do ... done < access.log循环即可。

七、本篇小结

环境变量 vs 局部变量

  • 局部变量只在当前Shell内可见

  • export让变量传递给所有子进程

  • 永久生效写到~/.bashrc

只读变量

  • readonly定义不可修改的常量

  • 适用于配置文件路径、限制参数等

字符串处理核心技巧

操作 语法 示例
长度 ${#var} echo ${#name}
子串截取 ${var:pos:len} ${str:0:5}
开头删最短 ${var#pattern} ${path#*/}
开头删最长 ${var##pattern} ${path##*/}
结尾删最短 ${var%pattern} ${path%.*}
结尾删最长 ${var%%pattern} ${path%%.*}
替换首个 ${var/old/new} ${url/http/https}
替换所有 ${var//old/new} ${text//a/A}
默认值 ${var:-默认值} ${user:-root}

记忆口诀

#从左删,%从右删;一个删最短,两个删最长;
/替换,//全换;:-给默认,:=设默认。

动手练习

bash

复制代码
#!/bin/bash
# 练习:文件路径处理

# 给定文件路径
file="/var/log/nginx/access.log"

echo "原始路径:${file}"

# 1. 提取文件名
echo "文件名:${file##*/}"

# 2. 提取目录路径
echo "目录:${file%/*}"

# 3. 提取不带扩展名的文件名
basename="${file##*/}"
echo "文件名(无扩展名):${basename%.*}"

# 4. 修改扩展名为 .bak
echo "备份文件名:${file%.log}.bak"

# 5. 练习字符串默认值
read -p "请输入你的名字(直接回车使用默认值):" name
echo "你好,${name:-访客}!"

八、下篇预告

掌握了字符串和变量,脚本还缺少一项关键能力:判断。没有判断的脚本只能顺序执行,遇到不同情况无法做出不同反应。

下一篇我们将进入条件判断的世界,学习:

  • test命令与[ ][[ ]]的用法和区别

  • 文件测试(是否存在、是否是目录、是否可写)

  • 字符串比较与数字比较

这是构建脚本逻辑能力的第一个台阶。学会条件判断,你的脚本就拥有了"大脑"。


延伸思考 :有些运维脚本里你会看到${var:=default}这种写法。它和${var:-default}有什么区别?提示:前者不仅返回默认值,还会把默认值赋给变量。试试看,这是很多生产环境脚本用来自动初始化配置变量的技巧。

相关推荐
大树887 小时前
金刚石散热越强,管路越先见顶
大数据·运维·服务器·人工智能·ai
摇滚侠7 小时前
Linux CentOS7 rpm 安装 MySQL 5.7
linux·运维·mysql
霸道流氓气质8 小时前
领域驱动设计(DDD)在 Spring Boot 微服务中的实践指南
运维·spring boot·微服务
bush48 小时前
嵌入式linux学习记录十四、术语
linux·嵌入式
载数而行5208 小时前
Linux 11 动态监控指令top
linux
Inhand陈工9 小时前
基于台达PLC与映翰通IG502的智慧水产养殖精准投喂与远程运维解决方案
运维·人工智能·物联网·阿里云·信息与通信
酣大智9 小时前
ARP代理--工作原理
运维·网络·arp·arp代理
不会C语言的男孩9 小时前
Linux 系统编程 · 第 8 章:进程基础
linux·c语言
shushangyun_9 小时前
2026年快消品B2B系统推荐:支持终端门店订货、促销政策自动化的工具?
java·运维·网络·数据库·人工智能·spring·自动化
古城小栈9 小时前
Unix 与 Linux 异同小叙
linux·服务器·unix