【Shell变量替换与测试】

文章目录

  • [一、变量替换(Parameter Expansion)概述](#一、变量替换(Parameter Expansion)概述)
    • 常见形式与含义
      • [1. 默认值与赋值](#1. 默认值与赋值)
      • [2. 字符串长度](#2. 字符串长度)
      • [3. 子串提取](#3. 子串提取)
      • [4. 模式删除(从头/尾裁剪)](#4. 模式删除(从头/尾裁剪))
      • [5. 字符串替换](#5. 字符串替换)
      • [6. 去除首尾空白(纯 Bash 方法)](#6. 去除首尾空白(纯 Bash 方法))
  • 二、测试
    • [1. 基本用法](#1. 基本用法)
    • [2. 常见测试选项](#2. 常见测试选项)
    • [3. 组合逻辑与返回值](#3. 组合逻辑与返回值)
    • [4. 注意事项](#4. 注意事项)
  • 三、进阶技巧
    • [1. 引号与扩展顺序](#1. 引号与扩展顺序)
    • [2. 在 POSIX sh 中的差异](#2. 在 POSIX sh 中的差异)
    • [3. 性能与可维护性](#3. 性能与可维护性)
    • [4. 使用 `set -u` 时的安全处理](#4. 使用 set -u 时的安全处理)
  • 总结

一、变量替换(Parameter Expansion)概述

变量替换指通过 ${...}$var 访问并用不同方式处理变量内容。Bash 提供了非常丰富的扩展语法,用于默认值、替换、截断、长度、模式删除等。

常见形式与含义

代码如下:

bash 复制代码
#!/usr/bin/env bash
name="alice"
echo "$name"         # alice
echo "${name}"       # alice(显式)

下面按功能分类说明:

1. 默认值与赋值

  • ${var:-word} --- 如果 var 未设置或为空,结果为 word不改变 var)。
  • ${var:=word} --- 如果 var 未设置或为空,则将 var 设为 word 并输出它。
  • ${var:+word} --- 如果 var 已设置且非空,结果为 word,否则为空。
  • ${var:?message} --- 如果 var 未设置或为空,打印 message 到 stderr 并退出(用于参数校验)。

示例:

bash 复制代码
#!/usr/bin/env bash
unset A
B=""
echo "${A:-default}"   # 输出 default(A 未设置)
echo "${B:-default}"   # 输出 default(B 为空)

echo "${A:=hello}"     # A=hello,并输出 hello
echo "$A"              # hello

C="ok"
echo "${C:+present}"   # 输出 present

# 强制报错并退出(用于必需参数)
D=""
: "${D:?D is required}"

2. 字符串长度

  • ${#var} --- 变量长度(字符数)。
bash 复制代码
s="hello"
echo "${#s}"  # 5

3. 子串提取

  • ${var:offset}${var:offset:length} --- 按字符位置提取(支持负数偏移,Bash 特性)。
bash 复制代码
s="abcdefgh"
echo "${s:2}"      # cdefgh
echo "${s:2:3}"    # cde
echo "${s: -3}"    # fgh (注意有空格以区别负数偏移)

4. 模式删除(从头/尾裁剪)

  • ${var#pattern} --- 从开头匹配最短模式,删除。
  • ${var##pattern} --- 从开头匹配最长模式,删除。
  • ${var%pattern} --- 从结尾匹配最短模式,删除。
  • ${var%%pattern} --- 从结尾匹配最长模式,删除。
bash 复制代码
path="/home/user/docs/readme.txt"
echo "${path#*/}"       # home/user/docs/readme.txt (删除第一个 / 及之前)
echo "${path##*/}"      # readme.txt (删除最长前缀,保留最后一个文件名)
echo "${path%/*}"       # /home/user/docs (删除最后一个 / 及之后)
echo "${path%%/*}"      # (空) 删除最长后缀,原理同上

5. 字符串替换

  • ${var/pattern/repl} --- 替换第一个匹配(Bash)。
  • ${var//pattern/repl} --- 替换所有匹配。
  • ${var/#pattern/repl} --- 如果匹配开头则替换。
  • ${var/%pattern/repl} --- 如果匹配结尾则替换。
bash 复制代码
s="a-b-c-a"
echo "${s/-/_}"      # a_b-c-a (第一个 -)
echo "${s//-/_}"     # a_b_c_a (全部替换)
echo "${s/#a/X}"     # X-b-c-a (开头的 a 替换)
echo "${s/%a/X}"     # a-b-c-X (结尾的 a 替换)

6. 去除首尾空白(纯 Bash 方法)

Bash 没有内置 trim 函数,但可用模式删除实现:

bash 复制代码
s="  hello  "
# 去左空格
s="${s#"${s%%[![:space:]]*}"}"
# 去右空格
s="${s%"${s##*[![:space:]]}"}"
echo ">$s<"  # >hello<

(注:这段技巧依赖于模式匹配,复杂但无须外部命令)


二、测试

在脚本里判断条件通常使用 test 命令、[ ... ]、或 [[ ... ]](Bash 扩展)。还可以使用 (( ... )) 进行算术测试。

1. 基本用法

bash 复制代码
if [ -f "$file" ]; then
  echo "file exists"
fi

# 或
if test -f "$file"; then
  echo "file exists"
fi

# Bash 扩展(推荐用于字符串比较、模式匹配)
if [[ -n "$var" && "$var" == foo* ]]; then
  echo "starts with foo"
fi

注意:[ ... ]test 是 POSIX 标准;[[ ... ]](( ... )) 是 Bash / ksh 特性,支持更多语法且减少字符串拆词风险。

2. 常见测试选项

文件相关(常用)

  • -e file:存在(包括目录、特殊文件)
  • -f file:普通文件且存在
  • -d file:目录
  • -s file:存在且大小 > 0
  • -r file:可读
  • -w file:可写
  • -x file:可执行
  • -L file:符号链接

示例:

bash 复制代码
if [ -d "$dir" ]; then
  echo "dir exists"
else
  mkdir -p "$dir"
fi

字符串相关

  • -z str:字符串长度为 0(空)
  • -n str:字符串长度非 0
  • str1 = str2(在 [ 中)或 ==(在 [[ 中)比较相等
  • 注意 <>[ 中需要转义或使用 \> 等(更安全用 [[

示例:

bash 复制代码
if [ -z "$username" ]; then
  echo "username empty"
fi

if [[ "$name" == "alice" ]]; then
  echo "hello alice"
fi

整数比较(使用 -eq -ne -lt -le -gt -ge(( ))

  • [ "$a" -eq "$b" ]
  • (( a == b )) --- 算术测试,支持 C 风格运算符

示例:

bash 复制代码
a=5
b=10
if [ "$a" -lt "$b" ]; then
  echo "a < b"
fi

# 或
if (( a < b )); then
  echo "a < b"
fi

3. 组合逻辑与返回值

  • &&|| 可在 shell 中串联命令(短路求值)。
  • if 判断的是命令的退出状态(0 为真,非 0 为假)。
  • test / [ 返回退出码(0/1/2),[[ 返回更友好。

示例:

bash 复制代码
cmd && echo "success" || echo "failed"

4. 注意事项

  • 变量引用必须加引号"$var"),避免空格与通配符展开导致脚本错误。
  • [ ... ] 中,用 = 比较字符串相等,且两边要有空格: [ "$a" = "$b" ]
  • [[ ... ]] 中不需要对 < > 进行转义用于字符串比较,但仍建议用 [[ 来避免单词分割和路径名扩展。
  • (( ... )) 中变量可不加 $,但最好加可读性更好。
  • 当用 -n/-z 时仍旧加双引号:[ -z "$s" ]

三、进阶技巧

1. 引号与扩展顺序

  • 总是为变量加双引号:"$var",避免字段分割与通配符展开(pathname expansion)。
  • 当希望展开数组或保留单元边界时使用 "$@""$*"(通常不推荐)

2. 在 POSIX sh 中的差异

  • [[ ... ]]${var//pattern/repl}${var:offset} 等是 Bash 扩展,不可在纯 /bin/sh 中使用(有些系统的 /bin/sh 是 Dash)。

  • 如果需要跨平台兼容(比如在 BusyBox 或 Dash 中运行),请仅使用 POSIX 功能:

    • 使用 exprcutawk 等外部工具替代复杂替换,或用更保守的模式删除(${var#pattern} / ${var%pattern} 在 POSIX 中可用,但 ${var//} 不可用)。

3. 性能与可维护性

  • 当要做复杂字符串处理时,调用外部命令(sedawk)常比复杂的 Bash 花式更可读。
  • 对于大量文件处理,避免不必要的子 shell 与外部命令调用以减少开销。

4. 使用 set -u 时的安全处理

  • 因为 set -u 会在使用未定义变量时报错,常用的防御写法为:${VAR:-}${VAR+set},或在使用前显式赋默认值。

总结

本文介绍了 Shell 脚本中变量替换 的常用语法(默认值、赋值、替换、截断、长度、模式删除等),并介绍了条件测试 (文件测试、字符串测试、整数比较、[ ][[ ]](( )) 的差别)。

相关推荐
CappuccinoRose1 小时前
CSS 语法学习文档(十九)
前端·css·属性·flex·grid·学习资源·格式化上下文
雷电法拉珑2 小时前
财务数据批量采集
linux·前端·python
We་ct3 小时前
LeetCode 105. 从前序与中序遍历序列构造二叉树:题解与思路解析
前端·算法·leetcode·链表·typescript
前端 贾公子3 小时前
深入理解 Vue3 的 v-model 及自定义指令的实现原理(下)
前端·html
Roc.Chang3 小时前
Vite 启动报错:listen EACCES: permission denied 0.0.0.0:80 解决方案
linux·前端·vue·vite
Desirediscipline3 小时前
cerr << 是C++中用于输出错误信息的标准用法
java·前端·c++·算法
sunny_3 小时前
前端构建产物里的 __esModule 是什么?一次讲清楚它的原理和作用
前端·架构·前端工程化
Soulkey5 小时前
复刻小红书Web端打开详情过渡动画
前端