从 shell 谈起
“shell”,本意指(坚果、蛋类、贝类等)坚硬的外壳,在计算机科学中,特别是在类 Unix 系统(如 Linux、macOS)领域,是一个核心概念。它本质上是一种特殊的应用程序,充当用户与操作系统内核(Kernel)之间的命令解释器(Command Interpreter)。(同时也有 Linux 内核的一个外层保护工具的说法,“坚硬的外壳”)
用户通过文本命令行界面(CLI)输入命令。shell 程序负责接收这些命令,解析其语法和语义,将其转换为操作系统内核能够理解和执行的操作指令,并管理命令的执行过程(包括输入/输出重定向、管道连接、后台运行等)。当需要自动化执行一系列命令时,用户可以将这些命令以及必要的控制逻辑(如条件判断、循环)编写成一个文本文件,称为 shell 脚本(shell Script),然后交由相应的 shell 程序解释执行。

存在多种不同的 shell 程序实现,每一种都定义了自己的命令解释语法和脚本语言。其中比较流行的包括:
- Bourne shell (sh) - 经典 Unix shell
- C shell (csh)
- Korn shell (ksh)
- Bourne Again shell (bash) - 目前大多数 Linux 发行版的默认 shell
- Z shell (zsh) - macOS Catalina (10.15) 及之后版本的默认 shell
- Fish shell (fish)
- Almquist shell (dash / ash) - 常用于轻量级系统或作为
/bin/sh的替代
聊聊 Bash
Bash 是什么?
Bash (Bourne-Again Shell) 是 GNU 项目开发的一个 shell 程序,旨在兼容并扩展传统的 Bourne shell (sh)。它曾经是大多数 Linux 发行版和旧版本 macOS (Catalina 之前) 的默认交互式 shell 和脚本解释器。
Bash 是一个遵循 POSIX 标准的 Unix shell 和命令语言解释器。
Bash 的主要特点和增强包括:
- 高度兼容传统的 Bourne shell (sh)。
- 强大的命令行编辑功能(如历史命令回查
history、命令补全Tab)。 - 支持数组变量。
- 更丰富的条件测试和流程控制语法(如
[[ ]]条件判断)。 - 改进的变量处理机制(如变量扩展
${var})。 - 内置命令集丰富。
理解 Bash shell 的角色与语言
需要明确的是,不同的 shell 程序(如 Bash, Zsh, Fish, Dash)虽然共享一些基本概念和核心语法(特别是基于 Bourne shell 的),但它们在具体功能、扩展语法、配置方式以及脚本兼容性上存在差异。因此,在编写脚本或输入复杂的命令时,了解当前正在使用的是哪一种具体的 shell 实现非常重要。
用户通常笼统地称他们编写的自动化命令文件为 “shell 脚本”。从广义上讲,这指的是使用某种 shell 语言编写的脚本。更严谨的说法是指明具体的 shell 类型,例如 “Bash 脚本”、“Zsh 脚本” 或 “POSIX shell 脚本”(指仅使用标准 sh 特性的脚本)。这类似于区分 “Python 代码” 和 “Java 代码”,虽然都是“源码”,但语言不同。
核心理解:
- shell 程序(如 Bash)本身就是一个应用程序。 它的核心功能是读取用户的输入(命令或脚本),解释并执行它们。
- shell 语言 是用户与 shell 程序交互时所遵循的语法规则。它定义了如何组合命令、使用变量、进行流程控制等。
- Bash 作为最广泛使用的 shell 程序之一,实现了 Bash shell 语言。这种语言使得用户能够通过编写命令和脚本,高效地调用操作系统功能、管理文件和进程、组合实用程序以及实现自动化任务。
简言之:Bash 是一个实现了 Bash shell 语言的程序,其设计目标是为用户提供一个强大的命令行环境和脚本解释能力,用于与操作系统交互和执行任务。
Bash shell 的工作方式
-
交互模式
交互模式下,Bash shell 会等待用户发出命令然后再执行他们。Bash 会执行用户的每一条命令,且在它执行这条命令的期间,你将无法和 bash shell 交互。一旦它执行完这条命令,你就可以继续向它发出你的下一条指令。
-
非交互模式
Bash shell 也可以执行脚本。所谓脚本,就是提前写好的一系列指令,Bash 可以直接执行它们而无需停下来询问用户下一步要做什么。脚本通常以文件形式保存,多用来自动化执行各种任务。
除了 Bash 执行的命令来源不同,这两种模式非常相似。现在基本可以这样总结:如果 Bash 等待你给出执行任务的指令,你就处于交互模式中;如果它执行的是存在某文件中的指令,那它就是在非交互模式下运行一个脚本。
Bash 程序运行于基于文本的界面,本身不提供图形用户界面(GUI),因此用户必须通过终端(terminal)与其进行交互。现代终端通常是终端模拟器,即运行在图形界面中的软件程序,它们模拟传统物理终端,提供文本输入输出功能。

终端模拟器的选择依赖于操作系统:
- Linux/BSD:
- 常见选项包括:
rxvt、xterm、gnome-terminal、konsole。
- 常见选项包括:
- macOS:
- 可使用:
Terminal.app或iTerm2。
- 可使用:
- Windows:
- 可用程序包括:
cmd.exe、Console2、mintty等。
- 可用程序包括:
选择一个你喜欢的终端模拟器并启动它,是开始与 Bash 进行有效交互的第一步。
当用户在图形界面中打开终端模拟器时,会弹出一个窗口,展示程序的输入与输出。窗口内的文本既有运行在终端中的各类程序输出的结果,也有你通过键盘输入的命令。需要强调的是,负责在屏幕上渲染文本的是终端模拟器,而非 Bash;终端读取来自 Bash(或其他程序)的文本,并将其显示在窗口中。终端同样为邮件客户端、聊天工具等基于文本的程序提供渲染功能,与 Bash 无关。
文本,终端,bash,程序,输入,输出
终端模拟器内往往同时运行多个程序,它们通过输入输出流相互协作,却缺乏直观的可视化提示。因此,了解进程何时启动、如何通信以及何时终止,对清晰把握基于文本的用户界面尤为重要。
下面以本地与远程交互为例:
- 在本地终端中,你启动 Bash,然后通过 SSH 发起到远程主机(例如 IP 为 192.168.1.1)的连接。
- 远程主机上,会新建一个 Bash 进程,其输入输出通过网络隧道回传到本地终端。
- 在远程 Bash 中,你启动了
screen(终端复用器),它能够在同一个终端窗口内模拟多个子终端,通过快捷键切换或分屏并行查看。 - 在
screen的一个子终端中,再启动一个 Bash,并运行邮件程序(如mail),输入邮件内容。
其中,“文本”在计算机科学中通常称为“字符串”,即由字符序列组成的数据结构。字符串既可以是简短的姓名(例如 “Leonard Cohen”),也可以是多行诗歌。

那么,程序究竟是什么?以及它们如何通信?
- 程序:预先编写并存储在磁盘上的指令集合。操作系统的内核负责加载并执行程序指令。
- 进程:当内核运行某个程序时,会为其创建一个进程实例,该实例包含程序的指令和内存空间。多个同一程序的进程可以并行运行,彼此互不干扰。
-
文件描述符(file descriptor, FD):进程用来连接外部资源(文件、设备、其他进程)的整数标识符。
- FD 0:标准输入(stdin),默认与终端接收的键盘输入相连;
- FD 1:标准输出(stdout),默认与终端显示相连;
- FD 2:标准错误(stderr),也默认与终端显示相连;
- 进程可根据需要创建新的 FD,并将其重定向至文件、管道或套接字。

当一个程序需要将输出传递给另一个程序时,会请求内核将其 stdout 重定向到目标进程的 stdin,形成管道(pipe)。此时,一方的输出流在内核中链接至另一方的输入流,字节按顺序到达并被读取,无法倒转。读取后字节会从管道中移除,流继续前行。
例如,对以下 Bash 脚本:
#!/usr/bin/env bash
$ (echo "Your name?" >&2; read name; echo "$name" ) | ( while read name; do echo "Hello, $name"; done )
Your name?
Maarten Billemont
Hello, Maarten Billemont
这条 Bash 命令通过两个子 shell 和管道连接实现交互流程:左侧子 shell 使用 echo “Your name?” >&2 将提示信息重定向到标准错误输出(stderr),避免干扰标准输出(stdout);随后读取用户输入并将其通过 stdout 传给右侧子 shell;右侧子 shell 从标准输入(stdin)读取该数据并输出个性化问候语。
