跳到正文
本地阅读 · 管道、重定向与 Shell 引号

管道、重定向与 Shell 引号

计算机基础 · Linux

前置知识:文件查找、文本及日志处理

本章目录

本章学习目标

  • 追踪标准输入、输出与错误输出
  • 预测重定向顺序和管道退出状态
  • 正确使用单引号、双引号与命令替换

三个流的职责

程序通常有标准输入、标准输出和标准错误,对应文件描述符零、一、二。管道把左侧标准输出连接到右侧标准输入,不会自动传递标准错误。排序数据流中若混进诊断文字,会破坏后续解析,所以程序用 stdout 输出数据,用 stderr 解释错误。输出是否为空不能代替退出状态的成功判断。

示例将三行水果词送给 sort 与 uniq,得到每词次数。整个过程不需中间文件,适合线性文本处理。管道各程序可能并发运行,并不是先把全部左侧结果存进 Bash 再启动右侧。左侧输出太多而右侧提前退出时可能收到断管错误,严格脚本下 head 管道需留意这种情况。

重定向及顺序演算

> 覆盖输出文件,>> 追加,< 从文件读取输入。2> 单独重定向错误,2>&1 把当前错误流指向当前标准输出。command >all.log 2>&1 先把输出指到文件,再让错误跟过去,两者都入文件;command 2>&1 >out.log 先把错误指向原终端输出,再把标准输出移到文件,错误仍显示在终端。

重定向由 Shell 在启动程序前处理,所以目标文件可能在程序真正工作前被截断。不要写 sort file >file,这会先清空输入;应输出到独立临时文件,检查后再替换。tee 把输入同时复制到屏幕和文件,但文件路径与写入权限仍需检查。追加日志也要考虑重复运行是否造成重复记录。

引号与展开

单引号保留字面文本,双引号允许变量和命令替换但抑制词拆分与通配符展开。name='data science' 后 printf '%s\n' "$name" 只传一个值,未引用可能拆为两个。$(...) 获取命令标准输出并去掉末尾换行,不适合保存任意二进制内容。不要用它装下整个巨型文件再交给循环。

printf 比 echo 对选项与转义更可预测。固定格式写在第一参数,变量作为后续参数:printf '%s\n' "$value"。把未知值直接作为格式可能让百分号触发格式解释。引用并不是所有上下文完全相同;awk 代码外层单引号与文件路径双引号配合,可以分别保护代码和变量路径。

退出状态与 pipefail

$? 是上一条命令状态,要立即保存,否则下一条命令会覆盖。零一般成功,非零表示失败或特定结果;grep 一表示无匹配,不一定是异常。默认管道状态来自最后一段,前面失败后面成功可能被隐藏,set -o pipefail 让非零阶段影响总体状态。set -e 有条件、管道等例外,不能代替显式错误设计。

本章脚本故意在 if 中运行 false | cat,演示 pipefail 捕获失败,不让教学脚本整体失败。实际任务要决定空匹配是允许情况还是数据缺失,并用 if 处理;不能在每条命令尾巴加 || true 让所有错误消失。将输入、正常输出、诊断和状态同时核对,才知道脚本是否真正完成预期。

执行环境与验证边界

本课程命令使用 GNU 工具和 Bash 语法。Windows PowerShell 中的变量、引号、重定向和程序解析与 Bash 并不相同;请在 WSL 的 Ubuntu 终端运行示例,不能把整段 Bash 直接粘贴进 PowerShell。Git Bash 可以运行部分命令,但进程、权限、用户和工具选项可能不同;在 Git Bash 上成功不能证明已经在 Ubuntu 上验证。安装 WSL 是读者的环境准备步骤,课程脚本不会替读者更改系统配置。

每个可运行示例从脚本自身位置确定课程根目录,并只在课程 outputs/linux 子目录或自己建立的临时目录操作。首次运行前先阅读脚本,辨认哪些语句读取、哪些写入、哪些清理。检查路径中有空格时是否仍能运行,检查失败后是否返回非零状态,检查重新运行是否得到同样的结果。对生产数据进行操作时不能照搬练习目录的删除命令,必须重新确认目标范围。

Linux 学习应关注输入、输出、错误输出和退出状态这四件事。标准输出用于数据,标准错误用于诊断,退出状态用于让上层脚本判断成功与否;三者不能互相替代。把手工执行过程写成小脚本后,再通过独立目录重复运行一次。遇到差异时记录操作系统、Bash 和工具版本,把环境限制和代码错误区分开,这比无条件写下“全部通过”更有用。

完整示例与实际输出

#!/usr/bin/env bash
set -euo pipefail
ROOT="$(cd -- "$(dirname -- "${BASH_SOURCE[0]}")/../.." && pwd)"
WORK="$ROOT/outputs/linux/linux-pipes"
mkdir -p -- "$WORK"
printf '%s\n' pear apple pear | LC_ALL=C sort | uniq -c | tee "$WORK/counts.txt"
{ printf '%s\n' 'data row'; printf '%s\n' 'diagnostic' >&2; } > "$WORK/stdout.txt" 2> "$WORK/stderr.txt"
printf 'stdout: '; cat "$WORK/stdout.txt"
printf 'stderr: '; cat "$WORK/stderr.txt"
name='data science'
printf 'quoted variable: <%s>\n' "$name"
printf 'literal variable: %s\n' '$name'
if false | cat; then
  printf '%s\n' 'unexpected pipeline success' >&2; exit 1
else
  printf '%s\n' 'pipefail detected failure'
fi

实际运行通过Git Bash/MSYS on Windows compatibility run; Ubuntu and remote behavior not verified.

1 apple
      2 pear
stdout: data row
stderr: diagnostic
quoted variable: <data science>
literal variable: $name
pipefail detected failure

五道自测:先回答,再核对

01 · 普通管道会传递 stderr 吗?

查看提示

文件描述符。

查看参考答案

不会,只把 stdout 交给右侧 stdin。

02 · > 与 >> 有什么差别?

查看提示

重跑后文件。

查看参考答案

前者覆盖,后者追加。

03 · 2>&1 放前后一样吗?

查看提示

当时的目标。

查看参考答案

不同,重定向从左到右处理,复制的是当时的目标。

04 · 双引号中的变量会展开吗?

查看提示

与单引号对照。

查看参考答案

会,但抑制词拆分;单引号保留美元文本。

05 · pipefail 解决什么问题?

查看提示

前段失败后段成功。

查看参考答案

让前段失败不会被最后段的成功状态隐藏。

两项动手任务

任务 1 · 把正常输出和诊断分别写到两个临时文件并读取。

提示

分别重定向一和二。

完整参考解答与检查方式tmp_dir=$(mktemp -d); { printf '%s\n' ok; printf '%s\n' warn >&2; } >"$tmp_dir/out" 2>"$tmp_dir/err"; cat "$tmp_dir/out" "$tmp_dir/err"; rm -f -- "$tmp_dir/out" "$tmp_dir/err"; rmdir -- "$tmp_dir" 两文件各一行,stdout 与 stderr 不混合。

任务 2 · 解释并修复 sort input.txt >input.txt。

提示

输出截断发生在启动前。

完整参考解答与检查方式该命令先截断原文件,所以不能排序原内容。仅在自己创建的练习目录使用:tmp_dir=$(mktemp -d); printf '%s\n' b a >"$tmp_dir/input.txt"; sort "$tmp_dir/input.txt" >"$tmp_dir/sorted.txt"; mv -- "$tmp_dir/sorted.txt" "$tmp_dir/input.txt"; cat "$tmp_dir/input.txt"; rm -f -- "$tmp_dir/input.txt"; rmdir -- "$tmp_dir"。实际文件替换前还应检查排序退出状态。

官方资料与掌握状态

完成两项任务后,按独立完成程度记录。状态会尽量保存在当前浏览器,可导出备份。

动手任务记录

掌握状态与复习

关掉资料独立完成后记 A;提示后完成记 B;需要重学记 C。

记录尽量保存在当前浏览器,建议定期导出备份。