操作系统学习笔记 · 第 25 课 · Shell 是什么——交互式与脚本
从这一课起进入第七阶段:Shell 与脚本编程。前面几十课,你一直在"用命令"——ls、cd、grep、gcc——但你有没有想过:你敲下ls回车,到底是谁在背后把它翻译成动作、再交给内核执行的? 那个幕后的"翻译官",就叫 shell。这一课,我们回到终端这个最熟悉的地方,揭开 shell 的面纱。
从"敲命令"这个动作说起
你在终端敲下 ls,回车,屏幕上出现一堆文件名。
这中间发生了什么?不是内核直接"看懂"了 ls 这两个字母。内核只认"执行某个程序"这种机器指令,它不知道 ls 是什么。中间有个翻译官:
- 你敲
ls; - shell 读到这两个字母,理解"你想执行
ls这个命令"; - shell 去
PATH里找到/bin/ls这个程序; - shell
fork一个子进程去跑它(还记得第 7 课的 fork 吗); - 结果打印回你的屏幕。
这就是 shell 的本职:命令解释器。
25.1 shell 是什么
一句话理解:shell 是命令解释器——你在终端敲的命令,由它解析、找到程序、交给内核去执行,再把结果打印回来。它是"你和内核之间的翻译官"。
shell 扮演的角色,可以这样记:
- 对上(对用户):接收你敲的命令,理解你的意图;
- 对下(对内核):找到程序、fork 子进程、让它去执行;
- 结果:把输出打印回来给你看。
所以 shell 不是内核的一部分,它是一个用户态程序,只是它的工作就是"帮你跟内核打交道"。
25.2 bash / zsh / sh 的关系
你可能听过好几个名字:sh、bash、zsh。它们都是 shell,是一家人:
一句话理解:sh是最早的 shell,bash是它的增强兼容版(Linux 默认),zsh交互体验更强(补全、主题)。脚本第一行#!/bin/bash就是指定用哪个解释器。
- sh:最古老、最标准的 shell;
- bash:
sh的增强兼容版,Linux 的默认 shell,功能更强、更好用; - zsh:交互体验最炫(强大的自动补全、主题、插件),很多人手动换它当日常 shell。
你写脚本时,第一行的 #!/bin/bash(叫 shebang)就是在声明:"这个脚本,请用 bash 来解释执行"。两个命令帮你了解现状:
echo $SHELL:看当前默认 shell;cat /etc/shells:看系统装了哪些 shell。
25.3 登录 shell vs 非登录 shell
shell 还分两种"身份",加载的配置不同:
| 类型 | 什么时候 | 加载的配置 |
|---|---|---|
| 登录 shell | SSH 登录、su - | /etc/profile、~/.bash_profile/~/.profile |
| 非登录 shell | 开新终端窗口、跑脚本 | ~/.bashrc |
一句话理解:登录时读"全局 + 个人 profile",日常开终端读~/.bashrc——所以你的别名和提示符通常写在~/.bashrc。
- 登录 shell:你 SSH 登录进系统、或
su - user切用户时,那个 shell 是"登录 shell",会加载全局配置/etc/profile和个人配置~/.bash_profile; - 非登录 shell:你登录后新开一个终端窗口,或跑一个脚本,那个 shell 是"非登录 shell",只加载
~/.bashrc。
这就是为什么你平时定义的别名(alias ll='ls -l')通常写在~/.bashrc里——因为日常用的都是非登录 shell,读的是它。
判断方法:echo $0,前面带 - 的是登录 shell;su - user 是登录、su user 不是。
25.4 环境变量与 PATH
一句话理解:环境变量是"传给子进程的全局设置",PATH 是最重要的一个——它列出 shell 找命令时搜索的目录列表。环境变量:一串 名字=值 的全局设置,会被传给子进程。
其中最重要的就是 PATH:
你敲ls,shell 去哪找?就是按PATH里列出的目录,一个个找过去,找到第一个叫ls的可执行文件就用它。
echo $PATH:看到一串冒号分隔的目录(比如/usr/local/bin:/usr/bin:/bin);which ls:告诉你ls到底在哪个目录被找到。
一句话:PATH 就是 shell 的"寻人启事"——它决定了哪些命令能"裸敲"(不带路径)直接执行。25.5 内建命令 vs 外部命令
这是 shell 里一个很反直觉、但很重要的区别:
一句话理解:cd、export、echo是 shell 内建的(在 shell 进程里直接执行);ls、grep是外部程序(要 fork 子进程)。所以cd能改变当前 shell 的工作目录,外部命令不行。
- 内建命令(builtin):
cd、export、echo等,就是 shell 自己的一部分,在当前 shell 进程里直接执行; - 外部命令:
ls、grep等,是独立的程序,shell 要fork一个子进程去跑。
这个区别导致一个关键现象:
cd能真正改变你当前 shell 的工作目录,因为它是内建的、在"你"这个进程里执行。 而外部命令在子进程里跑,子进程怎么折腾(包括cd)都影响不到父进程。
验证:
type cd→ 显示 "cd is a shell builtin"(内建);type ls→ 显示 "ls is /bin/ls"(外部程序)。
动手实验:命令观察
echo $SHELL # 当前默认 shell
echo $0 # 当前是登录 shell 吗(带 - 前缀)
echo $PATH # 看搜索路径
which python3 # python3 在哪
export GREETING="hello shell"
echo $GREETING # 变量已导出
bash -c 'echo $GREETING' # 子 shell 也能看到(因为 export 了)再做一组对比,理解"内建 vs 外部":
cd /tmp # 内建命令,当前 shell 目录真的变了
pwd # 确认:/tmp然后写个小脚本:
cat > /tmp/cd_test.sh << 'EOF'
#!/bin/bash
cd /home
EOF
chmod +x /tmp/cd_test.sh
./cd_test.sh # 脚本里 cd /home
pwd # 结果还是 /tmp,没变!为什么pwd还是/tmp? 因为脚本里的cd /home发生在子进程里——脚本运行在一个全新的子 shell,它cd到/home,子进程一结束就消失了,你的当前 shell 纹丝不动。这正好印证了"内建命令在自家里执行、能改状态;外部/子进程里的操作改不了父进程"。
深入点:两个进阶话题
① 子 shell 与变量作用域
不加export的变量,只在当前 shell 里有效;要传给子进程,必须export。
A=1 # 普通变量,只在本 shell
bash -c 'echo $A' # 空(子进程看不到)
export A=1
bash -c 'echo $A' # 1(export 后子进程能看到了)② source vs ./script
./script:开一个子进程跑脚本,脚本里改的环境变量、目录,跑完就没了;source script:在当前 shell 里逐行执行脚本,改的变量、目录保留在当前 shell。
这就是为什么你改了~/.bashrc后,要source ~/.bashrc才生效——因为要在当前 shell 里重新执行一遍,而不是开个子进程。
小结与思考题
这一课,我们揭开了 shell 的面纱:
- shell 是命令解释器:你和内核之间的翻译官;
- sh → bash → zsh:一家三代,bash 是 Linux 默认;
- 登录 vs 非登录 shell:登录读 profile,日常读
.bashrc; - 环境变量与 PATH:传给子进程的全局设置,PATH 决定命令去哪找;
- 内建 vs 外部命令:内建在自家执行(能改状态),外部要 fork 子进程。
留三个问题:
- shell 在用户和内核之间扮演什么角色?
- 登录 shell 和非登录 shell 分别加载哪些配置文件?
- 为什么
export之后子进程才能看到变量?
现在你知道了 shell 是什么、命令是怎么被执行的。但 shell 能做的远不止"一条条敲命令"——它还能把一堆命令写进一个文件,按顺序、带逻辑地自动执行,这就是 shell 脚本。脚本里的变量怎么定义?怎么判断、怎么循环?怎么写一个真正能用的自动化脚本?下一课,我们写第一个脚本。