【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}有什么区别?提示:前者不仅返回默认值,还会把默认值赋给变量。试试看,这是很多生产环境脚本用来自动初始化配置变量的技巧。

相关推荐
idolao2 小时前
CentOS 7 安装 jprofiler_linux64_7_2_3.tar.gz 详细步骤(解压、配置、远程连接)
linux·python·centos
深邃-2 小时前
【Web安全】-Kali,Linux配置(1):Kali网络配置,LinuxEnvConfig配置脚本,APT源的讲解,Kali设置中文
linux·运维·开发语言·网络·安全·web安全·网络安全
Hello World . .2 小时前
Linux驱动编程:内核同步的艺术-从互斥到底半部
linux·开发语言·数据库
keyipatience2 小时前
11.Git版本控制:从入门到精通
大数据·linux·elasticsearch·搜索引擎
林熙蕾LXL2 小时前
Ubuntu——APT软件包
linux·运维·ubuntu
s6516654962 小时前
Makefile语法学习
java·linux·前端
ofoxcoding2 小时前
OpenClaw 自动化交易机器人怎么配置?从零搭建 + 踩坑全记录(2026)
运维·ai·机器人·自动化
时空自由民.2 小时前
嵌入式-CI(Continuous Integration)介绍
linux·单片机·ci/cd
有谁看见我的剑了?2 小时前
ubuntu 搭建本地镜像仓库
linux·运维·ubuntu