全书最长(75 页)、最重要的章节。BASH = Bourne Again SHell,sh 的 GNU 增强版,Linux 默认 shell。打通 Shell 等于打通 Linux 任督二脉。

10.1 认识 BASH 这个 Shell

10.1.1 硬件、核心与 Shell

硬件 → 核心(Kernel) → Shell → 使用者。Shell 是使用者与核心之间的翻译官——你输入的每个指令,都是通过 Shell 交给核心驱动硬件。“壳程序”的名字就是因为它在最外层,像鸡蛋的壳。

10.1.2 为何要学文本界面的 shell?

  1. 所有 distribution 的 bash 都一样——“一法通、万法通”,不像 X Window 各家界面不同
  2. 远端管理命令行最快——传输量小,不易断线
  3. 打通任督二脉——shell 是 Linux 基础中的基础,配合管线+重导向,分析日志只需几分钟

10.1.3 系统的合法 shell 与 /etc/shells 功能

cat /etc/shells     # 系统有哪些合法 shell
# /bin/sh → 已被 /bin/bash 取代
# /bin/bash → Linux 默认
# /bin/tcsh → 整合 C Shell
# /sbin/nologin → "怪怪的 shell"——给 FTP 等服务用的,禁止交互登录

用户登陆时从 /etc/passwd 每行最后一列取得自己的 shell。系统账号(bin, daemon 等)用 /sbin/nologin 禁止登陆。

10.1.4 Bash shell 的功能

bash 六大核心功能:① history(记忆指令,~/.bash_history);② Tab 补全(指令/文件/参数补全,防打错+加速);③ alias(命令别名,alias lm='ls -al');④ job control(前后景切换,第十六章);⑤ shell scripts(批处理脚本,第十二章);⑥ 通配符* ? [] 文件匹配)。

10.1.5 查询指令是否为 Bash shell 的内置命令: type

type ls       # ls is aliased to `ls --color=auto'
type -t ls    # alias
type -a ls    # 列出所有同名指令(alias + 外部路径)
type cd       # cd is a shell builtin——所以 man cd 查不到,要 man bash!
 
# type 只找可执行文件,普通文件名不显示——可当 which 用
# 但 history 用 which 找不到(它是 bash 内置),要用 type

10.1.6 指令的下达与快速编辑按钮

# 反斜线 \ 跳脱 Enter,实现多行输入(\ 必须紧跟 Enter,中间不能有空格!)
cp /var/spool/mail/root /etc/crontab \
> /etc/fstab /root        # > 是系统自动出现的续行提示符
 
# 快速编辑组合键
Ctrl+u   # 从光标处向前删到行首
Ctrl+k   # 从光标处向后删到行尾
Ctrl+a   # 光标跳到行首
Ctrl+e   # 光标跳到行尾

10.2 Shell 的变量功能

变量 = 用一组文字或符号取代一些设置或保留数据。程序通过读变量而不是硬编码路径,实现一人一套配置(如 MAIL 指向每人不同的邮箱)。

10.2.1 什么是变量?

变量的好处MAIL 变量指向每人不同的邮箱路径(/var/spool/mail/dmtsai vs /var/spool/mail/root),mail 指令读这个变量就能自动找对邮箱——不必为每个人写一个 mail 程序。PATH、HOME、SHELL 都是环境变量,通常用大写表示。

10.2.2 变量的取用与设置:echo, 变量设置规则, unset

echo $PATH          # 取用($ 前缀)
echo ${PATH}        # 推荐:大括号明确变量边界
myname=VBird        # 设置(等号两边绝对不能有空格!)
unset myname        # 取消

变量设置九大规则

#规则正确错误
1等号连接,两边无空格name=VBirdname = VBird
2变量名=英文+数字,不能数字开头name11name
3内容含空格用引号包name="VBird Tsai"
4" 保留 $ 特性;' 纯文本"$name"→VBird'$name'→$name
5\ 跳脱特殊字符name=VBird\ Tsai
6$(cmd) 取得指令输出(推荐);或 `cmd`ver=$(uname -r)
7${var} 累加内容PATH=${PATH}:/home/binPATH=$PATHyes(会找 PATHyes 变量)
8export 转环境变量export name
9大写=系统变量,小写=自订变量

双引号 vs 单引号是新手最易搞混的点:"$name" = 取变量的值,'$name' = 字面上的 $name 五个字符。

10.2.3 环境变量的功能

env        # 查看所有环境变量(= export 输出)
set        # 查看所有变量(含环境变量+自订变量+函数)
重要变量含义
HOME用户主文件夹(cd ~ 取这个)
SHELL当前 shell 路径
HISTSIZE历史指令记录条数(默认 1000)
MAIL邮件信箱路径
PATH可执行文件搜寻路径(冒号分隔,有顺序!)
LANG语系
PS1命令提示字符格式
$当前 shell 的 PID
?上一个指令的回传值(0=成功,非0=错误)

PS1 格式变量

PS1='[\u@\h \W]\$ '
# \u=用户名, \h=主机名(短), \W=当前目录(仅最后一级), \$=提示符(#或$)
PS1='[\u@\h \w \A #\#]\$ '
# \w=完整路径, \A=24h时间, \#=第几个指令

$? 回传值:判断上条指令成功与否的标准方法。0=成功;非0=各种错误(echo $? 只能用一次——再执行任何指令都会覆盖)。

export:让自订变量从”父程序私有”变成”子程序可继承”。

name=VBird; bash; echo $name    # 空——子程序拿不到
exit; export name; bash; echo $name   # VBird!

10.2.4 影响显示结果的语系变量 (locale)

locale              # 查看当前语系
locale -a           # 系统支持的所有语系
LANG=en_US.utf8     # 临时切英文(tty 终端中文乱码时用)
export LC_ALL=en_US.utf8   # CentOS 7 需要 LC_ALL 配合才生效

/etc/locale.conf 是系统默认语系配置文件。tty1~tty6 原生不支持中文显示,必须切英文或在图形终端改编码。

10.2.5 变量的有效范围

环境变量 = 全域变量(子程序可继承);自订变量 = 区域变量(仅当前 shell 有效)。

原理:启动 shell 时 OS 分配一块内存,export 把变量写入这块内存 → 子 shell 启动时从内存导入 → 所以子程序能读到。

10.2.6 变量键盘读取、阵列与宣告: read, array, declare

read -p "Your name: " -t 30 named      # 30 秒内读取键盘输入
 
declare -i sum=100+300+50               # 宣告为整数 → 450(默认是字串 "100+300+50"!)
declare -x sum                           # 转为环境变量(同 export)
declare -r sum                           # 只读(登出才能解)
declare +x sum                           # + 取消操作
declare -a arr                           # 阵列
 
var[1]="one"; var[2]="two"             # 阵列赋值
echo "${var[1]}, ${var[2]}"            # 读取阵列

bash 默认把所有变量当字串——sum=1+2 不会算出 3。要用 declare -i 宣告为整数。

10.2.7 与文件系统及程序的限制关系: ulimit

ulimit -a             # 查看所有限制(打开文件数、CPU 时间、内存…)
ulimit -f 10240      # 限制单文件最大 10MB(单位 KBytes)
# 超过限制:File size limit exceeded
# 复原限制最简单的方法:登出再登陆

10.2.8 变量内容的删除、取代与替换 (Optional)

${path#/*:}         # 从开头最短匹配删除
${path##/*:}        # 从开头最长匹配删除(只保留最后一个目录)
${path%:*}          # 从结尾最短匹配删除
${path%%:*}         # 从结尾最长匹配删除
${var/old/new}      # 替换第一个匹配
${var//old/new}     # 替换所有匹配
 
# 变量默认值
${var:-default}     # var 未设置或为空→返回 default(不改变 var,最常用!)
${var:=default}     # var 未设置或为空→var 本身被设为 default
${var:?error}       # var 未设置或为空→输出 error 到 stderr 并退出

10.3 命令别名与历史命令

10.3.1 命令别名设置: alias, unalias

alias lm='ls -al'    # 设置别名
alias                 # 查看所有别名
unalias lm            # 取消
# 跳过别名用 \rm(前面加反斜线)

CentOS 7 默认已有 alias rm='rm -i'alias ll='ls -l --color=auto' 等。

10.3.2 历史命令:history

history               # 显示所有历史
history 10            # 最近 10 条
!123                  # 执行第 123 号历史
!gzip                 # 执行最近以 gzip 开头的命令
!!                    # 执行上一条(= ↑ + Enter)

~/.bash_history 只在登出时写入当前 session 的指令(内存暂存→登出时刷入)。

10.4 Bash Shell 的操作环境

10.4.1 路径与指令搜寻顺序

指令执行时,按以下顺序查找:① 相对/绝对路径指定的指令 → ② alias → ③ bash 内置指令 → ④ $PATH 顺序查找。

10.4.2 bash 的进站与欢迎讯息: /etc/issue, /etc/motd

/etc/issue = 登陆前显示的欢迎画面(tty1~tty6 看到的那几行);/etc/motd = 登陆后显示的消息(Message of The Day)。

10.4.3 bash 的环境配置文件

类型触发场景读取顺序
login shelltty1~tty6 登陆、su -/etc/profile~/.bash_profile~/.bashrc/etc/bashrc
non-login shellX 里开终端、bash 子程序、su(不加-)~/.bashrc

~/.bashrc 最终都会被读到——所以个人设置优先放这里。source ~/.bashrc 重新加载。

10.4.4 终端机的环境设置: stty, set

stty -a              # 查看终端机所有设置(erase=^? 即退格键)
set                  # 查看所有变量+函数

10.4.5 万用字符与特殊符号

符号含义示例
*0~∞ 个任意字符ls /usr/bin/X*
?1 个任意字符ls test?
[]括号内任一字符ls [abc]*
[-]范围ls [0-9]*
[^]反向选择ls [^a-z]*

通配符(wildcard) ≠ 正则表达式(RE),两套完全不同的体系!

10.5 数据流重导向

10.5.1 什么是数据流重导向

三个标准流:stdin(0)=键盘、stdout(1)=屏幕、stderr(2)=屏幕。

符号含义
>stdout 覆写;>> = 追加
2>stderr 覆写;2>> = 追加
&>2>&1stdout+stderr 合到一个文件
<从文件读入替代键盘
<<eof键盘输入直到输入 eof 为止
ls -l > /dev/null 2>&1      # 全部丢弃——最安静模式!
find / -name "*.conf" 2> /dev/null   # 只要结果,错误全丢
# /dev/null = 黑洞,写入即消失;/dev/zero = 无限供应 0x00

10.5.2 命令执行的判断依据: ; , &&, ||

cmd1; cmd2         # 顺序执行,不管 cmd1 成功与否
cmd1 && cmd2       # cmd1 成功($?=0)才执行 cmd2
cmd1 || cmd2       # cmd1 失败($?≠0)才执行 cmd2
# 经典:ls /tmp/abc || mkdir /tmp/abc && echo "created"

10.6 管线命令 (pipe)

| 把左边指令的 stdout 传给右边指令的 stdin。管线命令必须能接受 stdin 作为输入!(cp, rm, mv 不接受 stdin,不能放在管线右边)。

10.6.1 撷取命令: cut, grep

cut -d':' -f1,3 /etc/passwd        # 以冒号分隔,取第1,3列
cut -c 5-10 file                    # 取每行第5~10个字符
grep -n 'keyword' file              # 带行号
grep -v '^#' file                   # 排除注释行
grep -A3 -B2 'keyword' file         # 上下文:前2行+后3行

10.6.2 排序命令: sort, wc, uniq

sort -t':' -k3 -n /etc/passwd      # 按第3列数字排序(-t分隔符, -k列, -n数字)
sort -r                              # 反序
uniq -c                              # 去重+计数(必须先 sort!)
wc -l file                           # 行数;wc -w = 词数

10.6.3 双向重导向: tee

ls -l | tee list.txt | wc -l        # 既存盘又传给下个命令

10.6.4 字符转换命令: tr, col, join, paste, expand

cat file | tr '[a-z]' '[A-Z]'       # 全部转大写
cat file | tr -d '\r'               # 删除 DOS 的 ^M
col -x                               # Tab → 空格
paste file1 file2                    # 行对行粘贴
join file1 file2                     # 按共同列合并两文件
expand -t 4 file                     # Tab → 每 Tab=4 空格

10.6.5 分区命令: split

split -b 300m bigfile               # 切成 300MB 块(bigfile.aa, bigfile.ab...)
cat smallfile* > bigfile             # 还原

10.6.6 参数代换: xargs

find / | xargs grep -l 'keyword'    # 把 find 结果作为 grep 的参数
# xargs 把 stdin 转成命令行参数——解决有些命令不接受管线输入的问题

10.6.7 关于减号 - 的用途

- 在需要文件名的地方代表 stdin/stdout:

tar -cvf - /etc | tar -xvf - -C /tmp    # 打包+解包串联,不产生实体文件

关键命令

命令用途
type cmd判断指令类型
echo ${VAR} / export VAR / unset VAR变量读/导出/取消
env / set查看环境/所有变量
read -p -t键盘输入
declare -i/-x/-r变量类型宣告
ulimit -a资源限制
alias / unalias / history别名/历史
source ~/.bashrc重载配置
> >> 2> &> < <<数据流重导向
&& `
|管线
cut / grep / sort / uniq / wc / tee / tr / split / xargs管线核心工具