📦 Day 2 of 7

变量、字符串与输入输出

📖 预计时长:2.5 小时 🎯 难度:初级
变量是脚本编程的核心。掌握变量的定义、作用域、字符串操作以及输入输出重定向,是写出功能完整脚本的必备基础。
变量定义与引用 环境变量 vs 局部变量 字符串切片与替换 位置参数与特殊变量 read 交互输入 输入输出重定向

1️⃣ 变量基础

变量定义与赋值

#!/bin/bash

# 定义变量(=两侧不能有空格!)
name="张运维"
age=28
city="濮阳"

# 引用变量:$变量名 或 ${变量名}(推荐后者)
echo "姓名:$name"
echo "年龄:${age} 岁"

# 命令替换:$() 或 反引号(推荐前者)
today=$(date '+%Y-%m-%d')
hostname=$(hostname)
echo "今天是 $today,主机为 $hostname"

# 只读变量
readonly PI=3.14159
# PI=3 # 会报错:readonly variable
bash
❌
常见错误name = "张运维"(等号两侧有空格)会报错,Shell 会将 name 当作命令。定义变量时等号两侧绝对不能有空格!

变量类型声明(declare)

declare -i num=10     # 整数型
declare -r CONST="abc" # 只读
declare -x MYVAR="val" # 导出为环境变量(等同于 export)
declare -a arr          # 普通数组
declare -A map          # 关联数组(字典)
bash

2️⃣ 环境变量 vs 局部变量

# 局部变量:只在当前 Shell/脚本中有效
my_var="local value"

# 环境变量:导出到子进程
export MY_ENV="exported"

# 查看所有环境变量
env
printenv

# 常用内置环境变量
echo $PATH     # 命令搜索路径
echo $HOME     # 用户家目录
echo $USER     # 当前用户名
echo $SHELL    # 当前 Shell 路径
echo $PWD      # 当前工作目录
echo $HOSTNAME # 主机名
bash

3️⃣ 字符串操作(核心重点)

字符串截取

str="Hello, Shell World!"

# 获取长度
echo ${#str}           # 19

# 截取:${变量:起始位置:长度}
echo ${str:0:5}        # Hello
echo ${str:7}          # Shell World!(从第7个到末尾)
echo ${str: -6}        # orld!(从倒数第6个)
bash

字符串替换与删除

filename="backup-2026-05-21.tar.gz"

# 替换(第一个匹配)
echo ${filename/2026/2025}     # backup-2025-05-21.tar.gz

# 全局替换
echo ${filename//-/_}          # backup_2026_05_21.tar.gz

# 删除前缀(最短匹配 #,最长匹配 ##)
echo ${filename#backup-}       # 2026-05-21.tar.gz
echo ${filename##*.}           # gz(获取最终扩展名)

# 删除后缀(最短匹配 %,最长匹配 %%)
echo ${filename%.*}           # backup-2026-05-21.tar
echo ${filename%%.*}          # backup-2026-05-21(去掉所有扩展名)
bash

变量默认值

# ${var:-default}  变量未设置或为空时使用默认值
echo ${name:-"未知用户"}

# ${var:=default}  变量未设置时赋值并使用默认值
echo ${port:=8080}

# ${var:+value}    变量已设置时才使用 value
echo ${debug:+" [DEBUG] "}

# ${var:?message}  变量未设置时输出错误并退出
echo ${DB_HOST:?"ERROR: 必须设置 DB_HOST 环境变量"}
bash

大小写转换(Bash 4+)

str="Hello World"
echo ${str^^}    # HELLO WORLD(全大写)
echo ${str,,}    # hello world(全小写)
echo ${str^}     # Hello world(首字母大写)
bash

4️⃣ 位置参数与特殊变量

#!/bin/bash
# 假设执行:./script.sh arg1 arg2 arg3

echo "脚本名:$0"         # ./script.sh
echo "第1个参数:$1"      # arg1
echo "第2个参数:$2"      # arg2
echo "参数总数:$#"       # 3
echo "所有参数:$@"       # arg1 arg2 arg3
echo "所有参数:$*"       # arg1 arg2 arg3(和$@区别见下文)

# $@ 与 $* 的区别(在双引号内):
# "$@" 每个参数单独作为一个词 -> "arg1" "arg2" "arg3"
# "$*" 所有参数合并为一个词 -> "arg1 arg2 arg3"
# 推荐始终使用 "$@"

# shift:移位参数(每次调用,$1变为原$2,$2变为原$3...)
shift
echo "shift后的第1个参数:$1"  # arg2
bash

5️⃣ read 交互输入

#!/bin/bash

# 基本读取
read -p "请输入用户名: " username
echo "你好, $username!"

# 读取密码(不回显)
read -sp "请输入密码: " password
echo  # 换行

# 超时设置
read -t 10 -p "10秒内确认 [y/N]: " confirm

# 读取多个变量
read -p "输入姓名和年龄(空格分隔): " name age

# 读取整行,保留空格
read -r line

# 逐行读取文件
while IFS= read -r line; do
    echo "行内容: $line"
done < /etc/hosts
bash

6️⃣ 输入输出重定向

# 标准输入  stdin  = 文件描述符 0
# 标准输出  stdout = 文件描述符 1
# 标准错误  stderr = 文件描述符 2

# 输出重定向(覆盖)
echo "hello" > output.txt

# 输出重定向(追加)
echo "world" >> output.txt

# 错误重定向
ls /nonexist 2> error.log

# 同时重定向标准输出和标准错误到同一文件
command > all.log 2>&1
# Bash 4+ 简写
command &> all.log

# 丢弃输出(/dev/null 是黑洞设备)
command >/dev/null 2>&1

# 输入重定向
sort < input.txt

# Here Document(内嵌多行文本)
cat << EOF
第一行
第二行
第三行
EOF

# Here String(单行字符串作为输入)
grep "hello" <<< "hello world"
bash

管道(Pipeline)

# 管道:将前一个命令的 stdout 作为后一个命令的 stdin
ps aux | grep nginx | grep -v grep

# 统计活跃连接数
ss -tn | grep ESTABLISHED | wc -l

# 按磁盘用量排序
du -sh /var/* | sort -rh | head -10

# tee:同时输出到文件和终端
df -h | tee disk_report.txt

# xargs:将stdin转为命令参数
find . -name "*.log" | xargs rm -f
bash

🏋️ 今日练习

  1. 写一个脚本,接收文件名参数,判断文件是否存在,并输出文件行数、大小、最后修改时间
  2. 练习字符串操作:给定路径 /home/user/backup-2026-05-21.tar.gz,分别提取文件名、扩展名、目录名
  3. 写一个交互式脚本,读取用户输入的服务器 IP、用户名、密码,输出连接信息(密码用 * 掩码)
  4. 使用 Here Document 生成一个 nginx 配置文件模板,其中服务器名和端口从变量中读取
  5. 将 /var/log/syslog(或 /var/log/messages)中包含 "error" 的行统计数量,重定向到 error_count.txt