SELF
返回首页LEARNING NOTES / BASH

LINUX · BASH · PRACTICE

Linux Shell
学习手册。

从终端命令到 Bash 脚本。按顺序完成示例与练习,逐步把常用命令变成能独立编写的脚本。

32 节课程6 个模块
先准备练习目录

以下示例面向 Linux 上的 Bash 与常见 GNU 工具。请在练习目录中操作。

mkdir -p "$HOME/shell-lab" && cd "$HOME/shell-lab"
显示 32 / 32 节
01

终端与路径

5 节
01

Shell、终端与命令的关系

入门

终端负责输入与显示,Shell 解释命令。此手册以 Bash 为准;发行版和所装工具可能不同。

示例命令
printf '%s\n' "$SHELL"
bash --version | head -n 1
type cd printf ls
观察结果
可看到登录 Shell 路径、Bash 版本,以及命令是内建命令还是外部程序。
动手练习
用 type 查看 grep、pwd、echo 各属于哪类命令。
注意
环境变量 SHELL 记录登录 Shell,不一定是当前正在运行的 Shell。
官方参考
02

建立安全的练习目录

入门

后续文件操作都放在自己的练习目录。先确认当前位置,再进入目录,避免误操作真实项目。

示例命令
mkdir -p "$HOME/shell-lab"
cd "$HOME/shell-lab"
pwd
ls -la
观察结果
pwd 应显示当前用户主目录下的 shell-lab;新目录可能只有 . 和 ..。
动手练习
关闭终端再打开时,重新进入该目录并确认 pwd。
注意
不要不看 pwd 就执行覆盖或删除命令。
官方参考
03

绝对路径、相对路径与特殊目录

入门

以 / 开头的是绝对路径;相对路径以当前工作目录为基准。`.` 是当前目录,`..` 是上级目录,`~` 通常展开为当前用户主目录。

示例命令
cd "$HOME/shell-lab"
pwd
cd ..
pwd
cd ./shell-lab
pwd
观察结果
第二个 pwd 显示上级目录;最后回到 shell-lab。
动手练习
用 `cd -` 在最近两个工作目录间切换。
注意
脚本里的相对路径取决于运行时的工作目录,不一定是脚本所在目录。
官方参考
04

查命令帮助

入门

先确认命令是否存在,再看内建帮助、简短帮助或手册页。man 的内容可能来自本机安装的软件版本。

示例命令
type grep
help cd
grep --help | head -n 12
man grep
观察结果
man 内可用 /pattern 搜索、n 跳到下一个匹配、q 退出。
动手练习
查找 grep 的 -n 与 -F 选项分别做什么。
注意
help 主要用于 Bash 内建命令;有些外部程序不提供 --help。
官方参考
05

读取命令历史与自动补全

入门

交互式 Bash 可用方向键翻历史命令,用 Tab 补全路径和命令;history 列出当前会话历史。

示例命令
history | tail -n 10
printf '%s\n' "$HISTFILE
观察结果
history 显示最近输入的命令;HISTFILE 通常指向历史文件。
动手练习
输入 `cd ~/she` 后按 Tab 试着补全练习目录。
注意
历史记录可能包含敏感命令;不要把口令直接写在命令参数里。
官方参考
02

文件管理

5 节
06

创建文件并查看元数据

入门

printf 可以稳定写入文本;ls -l 显示权限、大小和修改时间;stat 提供更详细信息。

示例命令
cd "$HOME/shell-lab"
printf 'alpha\nbeta\n' > notes.txt
ls -l -- notes.txt
stat -- notes.txt
观察结果
notes.txt 包含两行文本;具体时间与大小取决于内容和环境。
动手练习
再写入第三行 gamma,并用 cat 确认。
注意
`>` 会截断已有文件;追加请用 `>>`。
官方参考
07

复制、移动与改名

入门

cp 复制文件;mv 移动或改名。目标已存在时可能被覆盖,操作前先查看目标。

示例命令
cd "$HOME/shell-lab"
cp -i -- notes.txt notes-copy.txt
mv -i -- notes-copy.txt notes-backup.txt
ls -l -- notes*.txt
观察结果
应看到 notes.txt 与 notes-backup.txt。
动手练习
新建 drafts 目录,将 notes-backup.txt 移进去。
注意
`-i` 会在覆盖前询问,但脚本不应依赖交互确认。
官方参考
08

安全删除与预览

入门

rm 删除后通常无法从回收站恢复。先列出目标,再用 -i 交互确认;这里仅删除练习目录中的副本。

示例命令
cd "$HOME/shell-lab"
ls -l -- notes-backup.txt
rm -i -- notes-backup.txt
ls -l -- notes.txt
观察结果
确认删除后,原文件 notes.txt 仍应存在。
动手练习
尝试用 `find . -maxdepth 1 -type f -print` 预览练习文件。
注意
不要在不确定路径时复制粘贴递归删除命令;`--` 可防止以 - 开头的文件名被当作选项。
官方参考
09

通配符与隐藏文件

基础

`*`、`?`、`[abc]` 由 Shell 对文件名展开。以点开头的隐藏文件通常不会被普通 `*` 匹配。

示例命令
cd "$HOME/shell-lab"
printf 'one\n' > a.log
printf 'two\n' > b.log
printf 'hidden\n' > .secret
printf '%s\n' *.log
ls -a
观察结果
*.log 展开为 a.log 与 b.log;ls -a 还能看到 .secret。
动手练习
比较 `printf '%s\n' *.log` 与 `printf '%s\n' '*.log'`。
注意
若没有匹配项,Bash 默认可能保留原样的 `*.log`;不要据此假设一定找到了文件。
官方参考
10

文件类型与符号链接

基础

file 猜测文件类型;ln -s 创建符号链接。链接保存目标路径,目标移动后可能失效。

示例命令
cd "$HOME/shell-lab"
file -- notes.txt
ln -s -- notes.txt notes-link.txt
ls -l -- notes-link.txt
readlink -- notes-link.txt
观察结果
ls -l 显示 notes-link.txt 指向 notes.txt。
动手练习
把原文件临时改名,观察链接会发生什么,再改回。
注意
符号链接与文件副本不同;删除链接本身不会删除其目标。
官方参考
03

文本检索与管道

6 节
11

快速查看文件

入门

cat 适合短文件;head 和 tail 分别看开头与结尾;less 适合较长文件并支持搜索。

示例命令
cd "$HOME/shell-lab"
cat -- notes.txt
head -n 1 -- notes.txt
tail -n 1 -- notes.txt
less -- notes.txt
观察结果
less 中按 q 退出,按 / 输入关键词搜索。
动手练习
用 `nl -ba notes.txt` 显示行号。
注意
不要用 cat 一次输出巨大日志,以免淹没终端。
官方参考
12

grep 查找文本

入门

grep -n 显示匹配行号;-F 把模式当普通字符串;-i 忽略大小写。

示例命令
cd "$HOME/shell-lab"
printf 'ERROR disk\nINFO ready\nerror retry\n' > app.log
grep -n -i -F 'error' app.log
观察结果
应匹配第 1 行与第 3 行。
动手练习
试试 `grep -n -v -F 'INFO' app.log`,观察 -v 的作用。
注意
默认模式是正则表达式;搜索含 `.`、`[` 等普通字符时可用 -F。
官方参考
13

find 按条件找文件

基础

find 按路径、名称和类型筛选。先打印结果核对,再考虑对结果执行操作。

示例命令
cd "$HOME/shell-lab"
find . -maxdepth 1 -type f -name '*.log' -print
find . -maxdepth 1 -type l -print
观察结果
第一条列出练习目录中的 .log 普通文件,第二条列出符号链接。
动手练习
在子目录里新建一个 .log 文件,再去掉 -maxdepth 1 比较结果。
注意
文件名可能包含空格或换行;批量传给其他命令时用 -print0 与支持 NUL 分隔的工具。
官方参考
14

管道把命令接起来

基础

`|` 将左边命令的标准输出交给右边作为标准输入。默认情况下管道状态通常取最后一个命令。

示例命令
cd "$HOME/shell-lab"
grep -i -F 'error' app.log | wc -l
printf '%s\n' b a b | sort | uniq -c
观察结果
第一条统计错误行数;第二条先排序再统计重复值。
动手练习
把 `grep -i -F 'error' app.log` 接到 `head -n 1`。
注意
uniq 只合并相邻的重复行;需要全局统计时通常先 sort。
官方参考
15

重定向标准输出和错误

基础

`>` 覆盖标准输出,`>>` 追加;`2>` 写标准错误。顺序会影响 `2>&1` 的效果。

示例命令
cd "$HOME/shell-lab"
printf 'first\n' > result.txt
printf 'second\n' >> result.txt
ls missing-file > listed.txt 2> errors.txt
cat -- result.txt errors.txt
观察结果
result.txt 有两行;missing-file 的错误写进 errors.txt。
动手练习
试用 `ls notes.txt missing-file > all.txt 2>&1`,查看两类输出。
注意
`>` 会覆盖原文件;重定向在命令执行前建立,目标写错仍可能造成数据丢失。
官方参考
16

sort、uniq、wc 与 cut

基础

这些工具可组合处理简单文本表格;cut -d 指定分隔符,-f 选择字段。

示例命令
cd "$HOME/shell-lab"
printf 'alice:dev\nbob:ops\nalice:dev\n' > people.txt
cut -d: -f2 people.txt | sort | uniq -c
wc -l -- people.txt
观察结果
部门 dev 出现 2 次,ops 出现 1 次;文件共有 3 行。
动手练习
找出 people.txt 中不重复的姓名。
注意
cut 适合简单分隔文本;包含引号和转义的 CSV 需要专门解析器。
官方参考
04

权限、进程与环境

5 节
17

理解读、写、执行权限

基础

ls -l 的权限分为所有者、组、其他用户三组;目录的执行位表示可进入或穿越该目录。

示例命令
cd "$HOME/shell-lab"
ls -ld .
ls -l -- notes.txt
chmod u+rw,go-rwx -- notes.txt
ls -l -- notes.txt
观察结果
notes.txt 仅所有者保留读写权限;具体权限还受文件系统和 ACL 影响。
动手练习
用 `stat -c '%A %a %n' notes.txt` 对照符号和数字权限。
注意
不要把 chmod 777 当作通用修复;权限应按实际需要授予。
官方参考
18

查看进程与退出状态

基础

ps 列出进程;命令退出状态 0 通常表示成功,非 0 表示失败或其他状态。`$?` 保存最近前台管道的状态。

示例命令
ps -o pid,ppid,stat,comm -p $$
true
printf 'true: %s\n' "$?"
false
printf 'false: %s\n' "$?
观察结果
通常看到 true: 0、false: 1;进程号因环境不同。
动手练习
用 `if grep -q -F 'alpha' "$HOME/shell-lab/notes.txt"; then ...; fi` 判断是否匹配。
注意
读取 `$?` 前不要插入其他命令,否则得到的是后来命令的状态。
官方参考
19

前台、后台与作业控制

基础

交互式 Bash 中,`&` 可启动后台作业,jobs 查看当前 Shell 的作业,wait 等待子进程。

示例命令
sleep 2 &
jobs
wait
printf 'done\n'
观察结果
约 2 秒后打印 done;jobs 的编号和进程号会变化。
动手练习
尝试用 Ctrl+Z 暂停一个 sleep,再用 fg 恢复。
注意
关闭终端不等于可靠的长期任务管理;长期服务应使用 systemd 等进程管理机制。
官方参考
20

环境变量与 PATH

基础

Shell 变量仅在当前 Shell 中存在;export 后子进程才能继承。PATH 决定命令搜索目录。

示例命令
LAB_MODE=demo
export LAB_MODE
bash -c 'printf "child: %s\n" "$LAB_MODE"'
printf '%s\n' "$PATH
观察结果
子 Bash 输出 child: demo;PATH 通常由多个冒号分隔的目录构成。
动手练习
用 `command -v bash` 查找 Bash 可执行文件路径。
注意
不要把当前目录 `.` 随意放进 PATH,以免误运行同名文件。
官方参考
21

磁盘空间与目录大小

基础

df 查看文件系统可用空间;du 统计目录占用;两者衡量范围不同。

示例命令
df -h "$HOME"
du -sh "$HOME/shell-lab
观察结果
df 给出所在文件系统容量;du 给出练习目录占用。
动手练习
在练习目录加入一个文件,再比较 du 输出。
注意
du 默认可能跟实际磁盘占用与文件逻辑大小不同;稀疏文件和硬链接会影响理解。
官方参考
05

Bash 脚本

9 节
22

第一个 Bash 脚本

基础

脚本是按顺序执行的文本命令。shebang 指定解释器;也可明确用 bash 文件名执行。

示例命令
cd "$HOME/shell-lab"
printf '#!/usr/bin/env bash\nprintf "hello shell\\n"\n' > hello.sh
bash hello.sh
bash -n hello.sh
观察结果
运行时输出 hello shell;bash -n 只检查语法,不执行脚本。
动手练习
给 hello.sh 加执行权限,再运行 `./hello.sh`。
注意
脚本没有执行权限时,可用 `bash hello.sh`;`bash -n` 不检查运行时逻辑。
官方参考
23

变量与引号

基础

赋值号两侧不能加空格。双引号保留空格并允许变量展开;单引号把变量写成字面文本。

示例命令
name='Linux Shell'
printf 'hello %s\n' "$name"
printf '%s\n' '$name'
printf '%s\n' "$name
观察结果
第二条打印字面文本 $name,第三条打印 Linux Shell。
动手练习
设 `file='my notes.txt'`,比较 `printf '%s\n' "$file"` 与未加引号的写法。
注意
处理路径和普通字符串时优先引用变量:`"$file"`;不要依赖词拆分。
官方参考
24

位置参数与参数检查

基础

`$1` 是第一个参数,`$#` 是参数个数;`"$@"` 可保留各参数边界。

示例命令
printf '#!/usr/bin/env bash\nprintf "count=%s\\n" "$#"\nprintf "arg=%s\\n" "$1"\n' > args.sh
bash args.sh hello
观察结果
输出 count=1 和 arg=hello。
动手练习
修改脚本,在没有参数时打印用法并以非零状态退出。
注意
启用 `set -u` 后,直接读取未传入的 `$1` 会报错;可用 `${1:-}` 或先检查 `$#`。
官方参考
25

条件判断与文件测试

基础

`[[ ... ]]` 是 Bash 条件语法;`-f` 检查普通文件,`-d` 检查目录。if 根据命令退出状态选择分支。

示例命令
file="$HOME/shell-lab/notes.txt"
if [[ -f "$file" ]]; then
  printf 'found: %s\n' "$file"
else
  printf 'missing\n'
fi
观察结果
若前面创建了 notes.txt,应输出 found。
动手练习
改成检查目录 `"$HOME/shell-lab"`,再试 `-d`。
注意
`[[ ... ]]` 是 Bash 特性;如果脚本用 `/bin/sh` 执行,不能假定它可用。
官方参考
26

for 循环与数组

进阶

for 可遍历列表;Bash 数组用括号创建,`"${items[@]}"` 保留每个元素。

示例命令
items=('alpha one' 'beta two')
for item in "${items[@]}"; do
  printf 'item: %s\n' "$item"
done
观察结果
输出两行,每个含空格的元素仍保持为一个整体。
动手练习
写一个循环,逐个打印练习目录下的 `*.log` 文件名。
注意
不要用 `for file in $(ls)` 处理文件名;空格、换行和通配符会使结果出错。
官方参考
27

while read 逐行读取

进阶

`IFS= read -r` 保留行首尾空白和反斜杠;从文件重定向输入,避免把管道子 Shell 的变量变化当作主 Shell 状态。

示例命令
cd "$HOME/shell-lab"
while IFS= read -r line; do
  printf 'line: %s\n' "$line"
done < notes.txt
观察结果
notes.txt 的每一行都加上 line: 前缀。
动手练习
计算文件行数,并比较 wc -l 的结果。
注意
read 对没有结尾换行的最后一行会返回非零;生产脚本需考虑该边界。
官方参考
28

函数与局部变量

进阶

函数封装可复用步骤;`local` 把变量作用域限制在 Bash 函数内。

示例命令
greet() {
  local name=${1:-friend}
  printf 'hello %s\n' "$name"
}
greet shell
greet
观察结果
先输出 hello shell,再输出 hello friend。
动手练习
写一个函数接收文件名,检查文件是否存在并打印结果。
注意
函数的 return 表示退出状态,不适合返回任意字符串;字符串可用 stdout 输出。
官方参考
29

错误处理与 pipefail

进阶

`set -e`、`-u`、`pipefail` 能帮助发现部分错误,但 `set -e` 在 if、&&、管道等上下文有例外,不能代替显式检查。

示例命令
set -o pipefail
if grep -F 'missing-text' "$HOME/shell-lab/notes.txt" | wc -l; then
  printf 'pipeline finished\n'
fi
set +o pipefail
观察结果
grep 未匹配时退出 1;启用 pipefail 后,管道状态可反映该失败,但 wc 仍会输出 0。
动手练习
把 `grep -q` 用在 if 条件中,分别处理找到、没找到和读取出错。
注意
未匹配对 grep 来说不一定是异常;不要对所有非零状态机械报错。
官方参考
30

trap 清理临时资源

进阶

trap 可在脚本退出时执行清理。临时文件要用 mktemp 创建,路径必须引用,避免误删其他文件。

示例命令
tmp=$(mktemp)
trap 'rm -f -- "$tmp"' EXIT
printf 'temporary\n' > "$tmp"
cat -- "$tmp
观察结果
退出当前 Shell 或脚本时会清理临时文件;在交互终端试验前先理解 trap 的作用域。
动手练习
把这些命令写进单独脚本运行,检查临时文件是否消失。
注意
建议在独立脚本里演练 trap,避免覆盖交互式 Shell 已有的 EXIT 处理。
官方参考
06

综合练习

2 节
31

练习:统计日志级别

实战

综合使用 printf、cut、sort、uniq 统计结构简单的练习日志。

示例命令
cd "$HOME/shell-lab"
printf 'INFO:ready\nERROR:disk\nINFO:done\nWARN:slow\n' > sample.log
cut -d: -f1 sample.log | sort | uniq -c
观察结果
按字母序显示 ERROR 1、INFO 2、WARN 1;前面的数字是计数。
动手练习
只统计 ERROR 与 WARN,并把输出写入 report.txt。
注意
这只适用于 `级别:内容` 格式;真实日志应先确认字段格式和字符编码。
官方参考
32

练习:生成文件清单

实战

先生成清单再人工检查。用 NUL 分隔可以安全处理含空格或换行的文件名,但打印到终端时应使用普通的 -print。

示例命令
cd "$HOME/shell-lab"
find . -type f -name '*.log' -print
find . -type f -name '*.log' -print0 | xargs -0 -r wc -l
观察结果
先列出 .log 文件,再分别统计行数;GNU xargs 的 -r 在无输入时不运行 wc。
动手练习
把检索条件改为 '*.txt',比较输出。
注意
不要把未审核的 find 结果直接接到删除命令;`-r` 是 GNU xargs 选项。
官方参考