Bash 零基础教程

Bash echo 与 printf 命令

Bash 提供了两个主要的命令用于向标准输出(通常是终端屏幕)显示内容:echoprintf。虽然它们都能完成向用户展示信息的任务,但它们在功能上(尤其是在格式化和转义字符解析方面)有着非常显著的区别。理解这些差异,是编写出清晰、格式美观的脚本输出的关键。

1. echo 命令

echo 命令是显示一行文本最直接、最简单的方法。对于输出简单的消息、变量的值以及提供基本的脚本反馈来说,它通常已经完全够用了。默认情况下,echo 会在其输出的末尾自动添加一个换行符(newline)。

1.1 基础用法

要显示一个简单的字符串,只需将其用引号(单引号或双引号)括起来;如果字符串中没有空格或特殊字符,也可以省略引号。不过,为了代码清晰并防止意外的分词或路径扩展(globbing),养成使用引号的习惯是一个好做法。

echo "Hello, Bash!"          # 打印 "Hello, Bash!" 并在末尾自动换行
echo Hello, Bash!            # 同样打印 "Hello, Bash!"(省略了引号,但在这里依然有效)
echo '欢迎来到第 5 章'        # 打印 '欢迎来到第 5 章'

1.2 echo 的常用选项

echo 支持几个用来修改其默认行为的选项:

  • -n: 取消末尾默认的换行符。当你希望后续的输出紧接着显示在同一行时,这个选项非常有用。
echo -n "正在启动进程..."     # 打印 "正在启动进程..." 但不换行
sleep 2                       # 暂停 2 秒(用于演示效果)
echo "完成。"                 # "完成。" 会紧接在 "正在启动进程..." 之后显示在同一行
# 最终输出: 正在启动进程...完成。
  • -e: 开启对反斜杠(\)转义序列的解析。如果不加 -eecho 会将反斜杠连同后面的字符作为普通文本直接打印出来。常见的转义序列包括:
    • \n: 换行 (Newline)
    • \t: 水平制表符 (Horizontal tab,类似于按 Tab 键)
    • \r: 回车 (Carriage return,将光标移回行首)
    • \\: 打印反斜杠自身
    • \c: 抑制当前 echo 命令产生更多的输出(类似于 -n,但可以用在字符串中间)。
echo "第一行\n第二行"             # 未加 -e,原样打印 "第一行\n第二行"
echo -e "第一行\n第二行"          # 加了 -e,输出结果为两行:
                                  # 第一行
                                  # 第二行
echo -e "姓名:\t张三"             # 打印 "姓名:   张三"(中间带有 Tab 缩进)
echo -e "进度: 50%\r进度: 100%"   # 打印 "进度: 100%"(因为 \r 让光标回到行首,后面的文本覆盖了前面的文本)
echo -e "输出将在这里停止。\c这句话将不会被打印出来。" # 仅仅打印 "输出将在这里停止。"

1.3 使用 echo 显示变量的值

echo 最常见的用途之一就是显示脚本中定义的变量值。

USER_NAME="Alice"
SERVER_IP="192.168.1.100"

echo "你好, $USER_NAME!"
echo "正在连接到服务器: $SERVER_IP。"

1.4 实际案例:简单的日志输出

考虑一个简单的脚本,用于记录某项操作的开始和结束时间。

#!/bin/bash
OPERATION="数据备份"
START_TIME=$(date +"%Y-%m-%d %H:%M:%S")

echo "--- $OPERATION 开始于 $START_TIME ---"

# 模拟一些耗时的工作
sleep 3

END_TIME=$(date +"%Y-%m-%d %H:%M:%S")
echo "--- $OPERATION 完成于 $END_TIME ---"

在这个脚本中,echo 被用来提供清晰的、带有时间戳的操作状态提示。

2. printf 命令

printf 命令提供了比 echo 更强大、更精确的格式化能力,其设计灵感直接来源于 C 编程语言中的 printf 函数。它使用一个格式化字符串(format string)来定义后续的参数应该如何显示,允许你精准控制对齐、填充、数字格式等等。

最核心的区别是:printf 不会自动在输出末尾添加换行符;如果你需要换行,必须在格式化字符串中显式加入 \n

2.1 基础用法与格式化字符串

printf 的通用语法是 printf FORMAT [ARGUMENT...]printf 格式 字符串/变量...)。FORMAT 字符串包含普通文本和格式控制符(format specifiers),这些控制符相当于后面 ARGUMENT 参数的占位符。

printf "Hello, %s!\n" "Bash" # 打印 "Hello, Bash!" 并换行
printf "这个数字是 %d。\n" 123  # 打印 "这个数字是 123。"
printf "默认不换行。"         # 打印 "默认不换行。" (不会换行)
printf " 默认不换行。\n"      # 紧接着打印 " 默认不换行。" 并换行

2.2 常用的格式控制符

  • %s: 字符串 (String)
  • %d %i: 有符号十进制整数 (Signed decimal integer)
  • %f: 浮点数,十进制记数法 (Floating-point number)
  • %x %X: 十六进制整数(分别对应小写或大写字母)
  • %o: 八进制整数 (Octal integer)
  • %c: 单个字符 (Single character)
  • %%: 打印一个字面的百分号 % 自身

2.3 标志位与宽度修饰符

printf 提供了多个标志位(flags)和宽度修饰符,用于实现极其精细的控制:

  • 宽度 (Width): %-10s(左对齐,占据 10 个字符的宽度),%10s(右对齐,占据 10 个字符的宽度)。
  • 精度 (Precision): %.2f(保留 2 位小数的浮点数),%.5s(将字符串截断为最多 5 个字符)。
  • 填充 (Padding): 默认情况下,使用空格进行填充补齐。若要用 0 对数字进行填充,可以使用 0 标志:%05d(用前导零将数字填充至 5 位数)。

常用标志位 (Flags):

  • -: 在设定的字段宽度内进行左对齐。
  • +: 强制为数值打印正负号(例如:+5-3)。
  • (空格): 正数前打印一个空格,负数前打印减号。
  • 0: 为数值类型的输出填充前导零。

2.4 printf 格式化详细示例

字符串格式化 (%s)

NAME="Jane Doe"
ROLE="Developer"
TEAM="Backend"

# 默认的字符串输出
printf "姓名: %s, 角色: %s\n" "$NAME" "$ROLE"

# 左对齐,宽度为 20 个字符
printf "姓名: %-20s角色: %s\n" "$NAME" "$ROLE"

# 右对齐,宽度为 20 个字符
printf "姓名: %20s角色: %s\n" "$NAME" "$ROLE"

# 将字符串截断为最多 5 个字符
printf "姓名缩写: %.5s\n" "$NAME"

整数格式化 (%d)

USER_ID=1001
LOGIN_COUNT=42

# 默认的整数输出
printf "用户 ID: %d, 登录次数: %d\n" "$USER_ID" "$LOGIN_COUNT"

# 填充前导零,总计 5 位
printf "用户 ID: %05d\n" "$USER_ID"

# 右对齐,占据 8 个字符宽度
printf "登录次数: %8d\n" "$LOGIN_COUNT"

# 为正数强制显示正号
printf "账户余额: %+d\n" 150
printf "账户余额: %+d\n" -75

浮点数格式化 (%f)

TEMPERATURE=23.456
PRICE=9.99

# 默认的浮点数输出(通常会附带很长的小数位)
printf "温度: %f\n" "$TEMPERATURE"

# 精确到小数点后两位
printf "温度: %.2f 摄氏度\n" "$TEMPERATURE"

# 右对齐,占据 10 个字符宽度,保留两位小数
printf "价格: %10.2f 欧元\n" "$PRICE"

十六进制与八进制格式化 (%x, %o)

DEC_VALUE=255

printf "十进制: %d\n" "$DEC_VALUE"
printf "十六进制 (小写): %x\n" "$DEC_VALUE"
printf "十六进制 (大写): %X\n" "$DEC_VALUE"
printf "八进制: %o\n" "$DEC_VALUE"

使用 printf 进行转义

echo -e 类似,printf 天然支持并在其格式化字符串中解析反斜杠转义序列。

printf "第一行\n第二行\n"               # 换行符
printf "商品\t数量\t价格\n"             # 制表符 (Tab)
printf "处理中...\r已完成。\n"          # 回车符 (会覆盖 "处理中...")

2.5 将静态文本与变量结合展示

printf 最擅长以结构化的方式将静态文本和动态数据完美结合,特别是用于输出对齐的表格。

#!/bin/bash
# 定义数据
ITEM_NAME="笔记本电源"
ITEM_ID="LC-X123"
QUANTITY=5
UNIT_PRICE=25.50

# 使用 bc 命令进行浮点数乘法计算
TOTAL_COST=$(echo "$QUANTITY * $UNIT_PRICE" | bc) 

# 打印表头
printf "%-20s %-10s %-10s %-10s\n" "商品名称" "商品 ID" "数量" "单价"
printf "%-20s %-10s %-10s %-10s\n" "---------" "-------" "---" "-----"

# 打印商品详情
printf "%-20s %-10s %-10d %-10.2f\n" "$ITEM_NAME" "$ITEM_ID" "$QUANTITY" "$UNIT_PRICE"

# 打印总计 ("" 空字符串用来填补对齐前面的空白位置)
printf "\n总费用: %-30s %.2f 欧元\n" "" "$TOTAL_COST"

这个例子展示了如何创建一个排列整齐、类似表格的输出。如果用 echo 来实现对齐,过程将极其繁琐且容易错位。

3. 什么时候使用 echo,什么时候使用 printf?

推荐使用 echo 的场景:

  • 不需要复杂格式化的简单消息输出。
  • 快速进行脚本排错打印 (Debugging)。
  • 当你默认就需要结尾换行,并且偶尔才需要显式控制取消换行 (-n) 时。
  • 极少需要解析转义字符的时候。

推荐使用 printf 的场景:

  • 对输出有精准的格式要求,包含对齐、填充字符以及控制数字的精度。
  • 生成表格数据或规范的报告报表。
  • 需要精准控制是否换行(必须显式使用 \n 来换行)。
  • 处理各种不同类型的数据(字符串、整数、浮点数),并要求展现形式保持统一。
  • 编写要求高度可移植性(Portable)的稳健脚本,因为在不同的类 Unix 系统之间,printf 的行为表现比 echo 的差异要小得多(标准化程度更高)。

3.1 案例对比:磁盘使用情况报告

假设你正在编写一个脚本,向系统管理员汇报磁盘空间的使用情况。

使用 echo(结构松散,对齐困难):

#!/bin/bash
DEVICE="/dev/sda1"
MOUNT_POINT="/mnt/data"
TOTAL_SIZE="100G"
USED_SPACE="75G"
FREE_SPACE="25G"

echo "磁盘使用报告: $DEVICE ($MOUNT_POINT):"
echo "总计: $TOTAL_SIZE"
echo "已用: $USED_SPACE"
echo "剩余: $FREE_SPACE"
echo
echo "备注: 超出警戒阈值,请立即采取行动。"

这种输出虽然人类能看懂,但缺乏一致的对齐感。如果像 TOTAL_SIZE 这样的变量值长度发生变化,文字就会变得参差不齐。

使用 printf(结构严谨,高度易读):

#!/bin/bash
DEVICE="/dev/sda1"
MOUNT_POINT="/mnt/data"
TOTAL_SIZE="100G"
USED_SPACE="75G"
FREE_SPACE="25G"
USAGE_PERCENT=75

printf "--- 磁盘使用报告: %s ---\n" "$MOUNT_POINT"
printf "%-20s %s\n" "物理设备:" "$DEVICE"
printf "%-20s %s\n" "挂载点:" "$MOUNT_POINT"
printf "%-20s %s (已使用: %d%%)\n" "总计/已用/剩余:" "${TOTAL_SIZE}/${USED_SPACE}/${FREE_SPACE}" "$USAGE_PERCENT"
printf "\n"
printf "系统状态: %s\n" "超出警戒阈值,请立即采取行动。"

使用 printf 版本的输出生成了一个整洁得多、且始终如一对齐的报告,系统管理员可以一眼扫过抓取重点。在专业环境中,脚本通常会生成机器易于解析或人类易于扫描的报告和日志文件,保持这种一致性极为重要。