Shell 是工程环境里最基础的交互层。它不只是”输命令的地方”——它是内核与用户之间的翻译官,是管道、重定向、变量、脚本和自动化的运行环境。真正理解 Shell 的操作模型,才能把临时操作变成可靠脚本,也才能让命令行成为 CI 和 Agent 都能信任的接口。

1. Shell 是什么

Shell(壳层)是为用户提供操作界面的程序——在操作系统内核和用户之间充当翻译官。内核不直接与用户交互,Shell 接收你的命令,调用内核提供的系统调用,再把结果返回给你。

Unix Shell 家族中,Bash 是 Linux 默认、最广泛兼容的选择;Zsh 是 macOS 默认、交互体验更丰富的选择;sh(Bourne Shell) 是最基础的公共语法子集,CI 脚本和 #!/bin/sh shebang 用它保证兼容性。

这里有一个重要区分:交互 Shell脚本 Shell 是两件事。.zshrc 的那些别名、插件、提示符是为个人交互服务的;真正要交给 CI、服务器、同事或 Agent 执行的脚本,应该有意识地用 #!/bin/bash#!/bin/sh 开头,不依赖任何个人 Shell 配置。

2. 命令结构

命令    [命令参数]    [命令对象]
  • 命令名称:语法中的动词,表达想要做的事情(lscpgit
  • 命令参数:调整命令行为(-l--verbose-r
  • 命令对象:命令作用的目标(文件名、目录、用户名)
格式示例
长格式ls --all --human-readable
短格式ls -ah

Linux/macOS 中的命令、参数、对象都严格区分大小写ls -Rls -r 是不同的参数。

3. 目录与上下文

目录位置是 Shell 操作的上下文。任何会删除、移动、生成文件的命令,执行前都应该先确认 pwd

命令说明
pwd打印当前目录
cd / cd ~回到当前用户的主目录
cd -回到上一次所在目录
pushd <path>进入目录并把当前目录压栈
popd从目录栈弹出,回到上一个目录

cd - 在两个目录之间来回切换时很实用。pushd/popd 适合需要临时跳到另一个目录再回来的场景。

4. 键盘快捷键

这些快捷键在 Bash 和 Zsh 中通用,是日常输入效率的基础:

快捷键功能
Tab自动补全命令或文件名
Ctrl+R反向搜索命令历史
Ctrl+A / Ctrl+E光标移到行首 / 行尾
Ctrl+U / Ctrl+K删除光标到行首 / 行尾
Ctrl+W删除光标前一个单词
Ctrl+Y粘贴被 Ctrl+U/K/W 删除的内容
Alt+B / Alt+F向前 / 向后移动一个单词
Ctrl+L清屏(等价于 clear
Ctrl+C终止当前命令
Ctrl+Z把当前进程挂起放到后台
Ctrl+D退出当前终端(或发送 EOF)

Ctrl+R 是最值得记住的——反向搜索历史,输入关键词实时过滤,比上下键翻历史高效得多。

5. 特殊符号

Shell 中有一批符号承担特殊含义,是组合命令和控制流程的基础:

符号功能示例
&&前一个命令成功才执行后一个make && make install
||前一个命令失败才执行后一个test -d dir || mkdir dir
;顺序执行,不论成功与否cd /tmp; ls
|管道,前一个命令的 stdout 作为后一个的 stdinps aux | grep nginx
>输出重定向(覆盖)ls > files.txt
>>输出重定向(追加)echo "done" >> log.txt
<输入重定向sort < data.txt
$()命令替换,将结果嵌入echo "today is $(date)"
{}大括号扩展echo {1..5} 输出 1 2 3 4 5
!!引用上一条命令sudo !!(提权重跑)
!$上一条命令的最后一个参数mkdir /some/path && cd !$
#注释# 这行不会执行

&& 是脚本里最常用的控制符——只有前一步成功才继续,是最简单的错误防护。

6. 引号与转义

Shell 对引号的处理有四种不同的语义,混淆了是大多数 Shell 错误的来源之一:

# 反斜杠(\):转义紧跟的单个字符
echo \$HOME      # 输出字面量 $HOME,不展开变量
 
# 单引号(''):完全转义,内部所有字符视为字面量
echo '$HOME'     # 输出 $HOME
 
# 双引号(""):保留变量展开和命令替换,但阻止通配符
echo "$HOME"     # 输出 /Users/username
 
# $():命令替换,执行内部命令并用其输出替换
echo "today: $(date +%Y-%m-%d)"

推荐用 $() 替代反引号 ` `:两者功能等价,但 $() 可以嵌套,也更易阅读。

拿不准时先加引号——尤其是路径中可能包含空格的情况下,"$path"$path 安全得多。

7. 环境变量

环境变量是 Shell 的全局配置层,影响命令的行为和搜索路径。

命名约定:变量名通常全大写(PATHHOME),这是约定俗成的规范。

变量作用
PATH可执行文件搜索路径,冒号分隔
HOME当前用户的主目录
SHELL当前使用的 Shell 路径
EDITOR默认文本编辑器(git commit 等会用到)
LANG系统语言和字符集
PWD / OLDPWD当前目录 / 上一个目录
USER当前登录用户名
HISTSIZE / HISTFILESIZE历史记录条数
PS1Bash 提示符格式

常用操作

# 查看某个变量
echo $PATH
 
# 设置变量(当前 Shell 会话有效)
export EDITOR="nvim"
 
# 临时覆盖(仅对该命令有效,不修改当前 Shell 环境)
EDITOR=vim git commit
 
# 追加到 PATH
export PATH="$HOME/.local/bin:$PATH"

写进 .zshrc.bash_profileexport 在每次启动 Shell 时生效。只在命令行临时 export 的变量,关掉终端就消失了。

8. Tab 补全与历史

Tab 补全是 Shell 的可发现性入口。它不仅减少输入,也能告诉你当前工具支持哪些子命令、参数和值。对陌生 CLI 来说,补全往往比翻文档更直接。

$ git ch<Tab>
checkout  cherry-pick  cherry

历史记录

history          # 列出历史命令(带编号)
!42              # 执行历史中第 42 条命令
!git             # 执行最近一条以 git 开头的命令
^old^new^        # 把上一条命令中的 old 替换为 new 并执行

重要习惯:从历史里复制命令时,要重新看一遍上下文——路径、环境变量、目标主机和危险参数可能已经不同了。

9. 别名

别名把长命令缩短为简短入口:

# 定义(在 .zshrc 中永久生效)
alias ll="ls -la"
alias gs="git status"
alias cat="bat"
 
# 临时取消(当前 Shell 会话)
unalias ll
 
# 查看所有已定义的别名
alias

边界:别名适合个人交互,不适合团队脚本。写给 CI、服务器或同事执行的脚本应该写完整命令,不依赖任何人的 Shell 配置。

10. 管道

管道把前一个命令的 stdout 直接连接到下一个命令的 stdin,是 Unix 文本流范式的核心:

# 统计 /etc/passwd 中禁止登录的用户数量
grep /sbin/nologin /etc/passwd | wc -l
 
# 查找进程
ps aux | grep nginx
 
# 提取日志中的 ERROR 行,去重计数
grep "ERROR" app.log | sort | uniq -c | sort -rn

管道可以无限延伸,但组合越长,可读性越差,失败定位越难。三四段之后通常应该考虑拆成脚本或保存中间结果。

管道可靠的前提是:上游输出格式足够稳定,下游能正确理解输入。如果上游命令改变了输出格式,整条管道就会静默地产生错误结果。

11. 重定向

Shell 的输入输出有三条标准流,每条可以独立重定向:

文件描述符说明
stdin0标准输入,默认来自键盘
stdout1标准输出,默认输出到屏幕
stderr2错误输出,默认输出到屏幕

输出重定向

ls -l > files.txt          # stdout 写入文件(覆盖)
ls -l >> files.txt         # stdout 追加到文件
ls /noexist 2> err.txt     # stderr 写入文件
ls /noexist 2>/dev/null    # 丢弃 stderr
cmd >> out.txt 2>&1        # stdout 和 stderr 都追加到文件
cmd &>> out.txt            # 等价简写

输入重定向

sort < data.txt            # 用文件内容作为 stdin
sort < data.txt > sorted.txt   # 同时重定向输入和输出
 
# Here 文档(heredoc):把多行文本作为 stdin
cat << EOF
line one
line two
EOF

脚本中的关键原则:区分 stdout 和 stderr,是为了让结果、日志和错误能被不同系统接住。不要把进度日志混进要被下游解析的数据流。

12. 通配符

通配符用于模式匹配,方便批量操作文件。批量操作前先用 ls 预览匹配范围,再执行删除或移动。

通配符含义示例
*匹配任意字符(含空字符串)*.log 匹配所有 .log 文件
?匹配单个任意字符?.txt 匹配 a.txt,不匹配 ab.txt
[a-z]匹配范围内的单个字符[a-z]*.md 匹配小写字母开头的 .md
[0-9]匹配单个数字log[0-9].txt
[[:alpha:]]匹配任意字母POSIX 字符类
[[:digit:]]匹配任意数字POSIX 字符类
# 安全做法:先预览,再执行
ls *.log
rm *.log     # 确认无误后再删
 
# 批量操作
mv report_[0-9][0-9].txt archive/

13. 命令替换

命令替换把一个命令的执行结果嵌入另一个命令中:

# 推荐写法(可嵌套)
echo "当前目录:$(pwd)"
echo "Git 分支:$(git branch --show-current)"
 
# 等价的传统写法(不推荐,不可嵌套)
echo "当前目录:`pwd`"
 
# 嵌套(只有 $() 支持)
echo "父目录:$(dirname $(pwd))"
 
# 赋值给变量
branch=$(git branch --show-current)
echo "当前在 $branch 分支"

14. 任务管理

Shell 可以管理前台和后台进程。

后台运行

# & 放到后台,但关闭终端会终止
sleep 300 &
 
# nohup:忽略挂断信号,关闭终端后仍继续运行
nohup sleep 300 &
 
# 重定向输出(nohup 默认输出到 nohup.out)
nohup ./server.sh > server.log 2>&1 &

管理后台任务

jobs            # 列出当前会话的后台任务
fg %1           # 把任务 1 切换到前台
bg %1           # 把挂起的任务 1 放到后台继续运行
Ctrl+Z          # 把前台任务挂起(发送 SIGTSTP)

查看和终止进程

ps aux                  # 查看所有进程
ps aux | grep nginx     # 过滤特定进程
kill %1                 # 终止后台任务(按 jobs 编号)
kill <PID>              # 终止进程(按 PID)
kill -9 <PID>           # 强制终止(SIGKILL,进程无法拦截)

边界&nohup 适合临时任务;长期运行的服务应该交给 systemdsupervisortmux 或专门的任务系统,带日志、重启策略和停止入口。

15. 获取帮助

# man 手册(最权威)
man ls
man bash
 
# man 页面导航
# 空格:下翻页  /keyword:搜索  n/N:下/上一个匹配  q:退出
 
# 内置帮助(快速查参数)
ls --help
git commit --help
 
# 查命令类型(内置 / 外部 / 别名 / 函数)
type ls
type cd      # 输出 "cd is a shell builtin"
 
# 查命令位置
which git
whereis python

man--help 的侧重不同:man 是完整参考文档,--help 是快速摘要。对陌生命令先看 --help,需要深入细节时看 man

16. 使用原则

先读上下文pwdwhoamiecho $SHELLecho $PATH 经常比命令本身更重要。在生产服务器上操作之前,先确认你在哪台机器、哪个目录、哪个用户。

交互配置和自动化分开:别名、补全、历史增强适合提高个人效率;要交给 Agent、CI 或同事执行的动作,应该写成完整命令、脚本或任务入口,不依赖任何人的 Shell 配置。

变量和引号要保守:路径、空格、引号、通配符是 Shell 错误高发区。拿不准时先 echo、先加双引号、先 dry-run。

stdout 是数据,stderr 是诊断:脚本和 Agent 都依赖这个边界。不要把进度日志混进要被下游解析的数据流。

先预览,再写入:通配符、重定向、rmmvxargs 组合前,先确认匹配范围和输出目标。

命令短不等于可靠:一行管道能解决问题,但如果不可读、不可调试、不可复用,就应该拆成有名字的脚本或任务。