Shell 基础与基本工具
Shell 基础与基本工具
目录
- 什么是 Shell
- 管道 | 与重定向 > >> < 2>&1
- 进阶管道:tee 与 xargs
- 退出码 $? 与 && / ||
- 变量与命令替换
- 正则表达式基础:grep -E
- 环境变量与 Shell 展开
- 帮助文档:man / info / --help
- 历史与补全:history / !! / Tab / Ctrl+R
- 总结与练习
一、什么是 Shell
Shell(壳) 是用户与操作系统内核(Kernel)之间的命令行解释器。用户在 Shell 里输入命令,Shell 负责解析、执行,并把结果返回给用户。
常见 Shell:
- Bash(Bourne Again Shell):Linux 默认 shell,本博客主角。
- sh(Bourne Shell):最早的经典 shell,Bash 兼容它。
- zsh、fish:功能更强的现代 shell。
查看当前 shell:
echo $SHELL # 查看当前用户的默认 shell
echo $0 # 查看当前正在运行的 shell
二、管道 | 与重定向 > >> < 2>&1
2.1 三个标准数据流(stdin / stdout / stderr)
每个 Linux 进程运行时都有三个标准文件描述符(File Descriptor,FD):
| 文件描述符 | 名称 | 编号 | 默认指向 | 含义 |
|---|---|---|---|---|
| stdin | 标准输入 | 0 | 键盘 | 程序读取的输入来源 |
| stdout | 标准输出 | 1 | 屏幕 | 程序正常的输出结果 |
| stderr | 标准错误 | 2 | 屏幕 | 程序的错误/诊断信息 |
为什么要区分? 因为正常结果和错误信息要能分开处理。例如 ls /etc /no-such-dir 会同时产生正常输出(/etc 内容,走 stdout)和错误提示(no such dir,走 stderr)。
2.2 重定向(Redirection)
重定向就是改变数据流的方向,把输出写到文件、从文件读取输入等。
# 标准输出重定向(> 覆盖,>> 追加)
ls > list.txt # 把 ls 的正常输出写入 list.txt(覆盖原内容)
ls >> list.txt # 追加到 list.txt 末尾(不覆盖)
# 标准错误重定向
ls /no-such 2> err.txt # 把错误信息写入 err.txt
ls /no-such 2>> err.txt # 错误信息追加到 err.txt
# 标准输入重定向(< 从文件读)
sort < names.txt # 从 names.txt 读取内容并排序
wc -l < file.txt # 统计文件行数
# ★ 同时重定向 stdout 和 stderr
cmd > out.txt 2> err.txt # stdout 到 out.txt,stderr 到 err.txt(分开)
cmd > all.txt 2>&1 # stdout 和 stderr 都写入 all.txt
cmd >> all.txt 2>&1 # 都追加到 all.txt
cmd &> all.txt # 简化写法,等价于 2>&1(都写入)
cmd 2>&1 | less # 把 stdout 和 stderr 一起交给管道
2>&1 详解:
- 数字
2表示 stderr,1表示 stdout,>&表示"重定向到"。 2>&1的意思是:把标准错误(2)重定向到标准输出(1)当前所指向的地方。- 顺序很重要:
> file 2>&1先让 stdout 指向文件,再把 stderr 也指向同一个文件;而2>&1 > file顺序反了,stderr 会指向屏幕而不是文件。 &>是 Bash 提供的简写,cmd &> file等价于cmd > file 2>&1。
2.3 管道(Pipe)|
管道 | 把前一个命令的 stdout 作为后一个命令的 stdin,实现命令间的数据流动,无需中间文件。
cat file.txt | sort # 显示并排序
cat file.txt | sort | uniq # 排序并去重(多重管道)
ls -l | grep '^d' # 只显示目录(以 d 开头的行)
ps aux | grep httpd # 从进程列表里找 httpd
dmesg | tail -20 # 只查看系统启动信息最后 20 行
cat /var/log/messages | grep -i error | head -20 # 综合运用
管道的特点:
- 管道只传递 stdout,stderr 不会进管道(除非用
2>&1合并,见上文)。 - 管道是流式的:前一命令持续输出,后一命令就开始处理,不必等全部完成。
- 管道中的每个命令在独立的子进程中运行。
经典组合示例:
# 查看磁盘使用量最大的几个目录/文件
du -sh /var/* 2>/dev/null | sort -rh | head -5
# 查看当前系统监听端口(配合 ss 或 netstat)
ss -tlnp | head -20
# 统计日志中某关键词出现次数
grep -c "error" /var/log/messages
grep "error" /var/log/messages | wc -l
三、进阶管道:tee 与 xargs
3.1 tee —— 边看边存(既显示又写文件)
tee 把 stdin 的数据同时写到 stdout(屏幕)和指定的文件中,就像一个"三通"。适合"既要实时看,又要保存"的场景。
command | tee output.txt # 屏幕显示 + 写入 output.txt
command | tee -a output.txt # -a 追加而不是覆盖
command | tee file1 file2 # 同时写入多个文件
command | tee >(grep error) # 高级:分叉处理(进程替换)
sudo tee /etc/hosts < newhosts # 常用:以 root 权限写入文件
经典用法:
# 安装软件时既看输出又存日志
sudo dnf install -y httpd 2>&1 | tee install.log
# 备份:把命令输出存文件,同时仍能看到
df -h | tee disk-usage.txt
# 以 root 权限写入系统文件(echo 单独无法写受保护文件)
echo "127.0.0.1 myhost" | sudo tee -a /etc/hosts
3.2 xargs —— 把输出当作参数
xargs 从 stdin 读取数据,并把它们作为参数传给另一个命令。它解决了"管道只能传数据、不能传参数"的问题。
管道 vs xargs 的区别:
- 管道
|:把前一个命令的输出内容作为后一个命令的输入数据。 xargs:把前一个命令的输出内容作为后一个命令的命令行参数。
# 基本用法:把 find 的结果作为 rm 的参数
find . -name "*.tmp" | xargs rm
# -n 2:每批只传 2 个参数给命令
echo "1 2 3 4" | xargs -n 2 echo
# -I {}:把每项替换到命令中的指定位置({} 是占位符)
ls *.txt | xargs -I {} cp {} /backup/
# -0:以空字符分隔(配合 find -print0,处理含空格/换行的文件名)
find . -name "*.log" -print0 | xargs -0 rm
# 并行执行(-P 指定并发数)
cat urls.txt | xargs -P 4 -I {} curl -O {}
# 显示将要执行的命令(-t,调试用)
echo "1 2 3" | xargs -t echo
注意事项:
- 文件名含空格/特殊字符时,推荐用
-0配合find -print0或find -exec,避免参数被错误拆分。 - 现代 Linux 推荐用
find -delete或find -exec替代部分 xargs 场景,更安全。
四、退出码 $? 与 && / ||
4.1 退出码(Exit Status)
每个命令执行完都会返回一个退出码(0~255 的整数),表示执行结果:
- 0:命令成功执行。
- 非 0(1、2、127 等):命令失败或异常。
ls /etc
echo $? # 输出 0,表示上一条命令成功
ls /no-such-dir
echo $? # 输出 2,表示出错(常见 1=一般错误,2=用法/参数错误,127=命令找不到)
$?保存的是上一条命令的退出码,用一次后会被下一条命令覆盖。- 常见退出码:
1一般性错误、2用法错误、126有权限但不能执行、127命令未找到、130被 Ctrl+C 中断。
4.2 条件执行:&& 和 ||
把退出码和逻辑运算结合,可以控制命令的条件执行:
| 符号 | 含义 | 行为 |
|---|---|---|
&& |
与(AND) | 只有**前一条成功(退出码 0)**才执行后一条 |
| ` | ` | |
; |
顺序分隔 | 无论成败都依次执行 |
# && 示例:前一个成功才执行后一个
mkdir -p /tmp/newdir && cd /tmp/newdir # 建目录成功才进入
sudo dnf install -y httpd && systemctl start httpd
# || 示例:前一个失败才执行后一个
cd /no-such-dir || echo "目录不存在,无法进入"
which python3 || echo "python3 未安装"
# 结合使用:成功做 A,失败做 B
grep "keyword" file.txt && echo "找到关键词" || echo "未找到"
# ; 示例:不管成败都执行
cd /tmp; ls; pwd
注意:&& 和 || 的优先级(&& 高于 ||),以及它们判断的是退出码而非"真/假值",初学时注意区分。
五、变量与命令替换
5.1 变量:$VAR vs ${VAR}
name="rocky"
echo $name # 输出 rocky
echo ${name} # 输出 rocky(更严谨)
# 为什么推荐 ${VAR}?——边界清晰,避免歧义
echo $name_home # 会被解析为变量 name_home(可能为空)
echo ${name}_home # 输出 rocky_home(明确)
$VAR与${VAR}效果相同,但${VAR}能在变量名后紧接其他字符时避免歧义,是更严谨的写法。- 赋值时等号两边不能有空格:
name=rocky正确,name = rocky错误。 - 命名规则:以字母或
_开头,可含字母、数字、下划线。
变量的常见操作:
name="Alice"
echo ${name:-default} # 变量为空时用默认值
echo ${#name} # 变量长度(5)
echo ${name:0:3} # 截取子串(Ali)
unset name # 删除变量
readonly PI=3.14 # 只读变量
5.2 命令替换:$( ) 与反引号
命令替换:把命令的输出作为另一个命令/赋值的一部分。
# 两种写法:$(命令) 和 `命令`
today=$(date +%F) # 把 date 的输出赋给 today
echo "今天日期: $today"
# 推荐 $( ),反引号在嵌套时容易出错
echo "当前路径: $(pwd)"
files=$(ls *.txt)
echo $(echo hello) # 嵌套:$( ) 支持,反引号不行
# 典型用法
count=$(grep -c "error" /var/log/messages)
echo "错误数量: $count"
mkdir $(date +%Y%m%d) # 创建以日期命名的目录
$(命令)与`命令`功能相同,但$()更推荐:支持嵌套、可读性更好。- 注意区分:命令替换
$()是把命令的输出当结果;变量替换$VAR是把变量的值当结果。
六、正则表达式基础:grep -E
正则表达式(Regular Expression, regex) 是一种用于匹配文本模式的强大工具。grep 是 Linux 中最常用的文本搜索工具。
6.1 grep 基础用法
grep "pattern" file.txt # 在文件中搜索包含 pattern 的行
grep "pattern" *.log # 搜索多个文件
cat file.txt | grep "pattern" # 配合管道
grep -i pattern file # 忽略大小写
grep -n pattern file # 显示行号
grep -v pattern file # 反向匹配(不含 pattern 的行)
grep -c pattern file # 统计匹配行数
grep -r pattern dir/ # 递归搜索目录
grep -w word file # 整词匹配
grep -E "pattern" file # 使用扩展正则
6.2 基本 vs 扩展正则(BRE vs ERE)
- 基本正则(BRE):默认
grep使用,部分元字符需转义(如+、?、|、(、)需要\)。 - 扩展正则(ERE):
grep -E或egrep使用,元字符无需转义,更直观。
强烈建议用 grep -E,避免转义烦恼。
6.3 核心元字符
| 元字符 | 含义 | 示例 |
|---|---|---|
. |
匹配任意单个字符(除换行) | a.c 匹配 abc、a1c |
* |
匹配前一个字符 0 次或多次 | ab*c 匹配 ac、abc、abbbc |
+ |
匹配前一个字符 1 次或多次(扩展) | ab+c 匹配 abc,不匹配 ac |
? |
匹配前一个字符 0 次或 1 次(扩展) | ab?c 匹配 ac、abc |
^ |
行首锚点 | ^root 匹配以 root 开头的行 |
$ |
行尾锚点 | bash$ 匹配以 bash 结尾的行 |
[abc] |
字符类,匹配其中任一字符 | [aeiou] 匹配任一元音 |
[a-z] |
字符范围 | [0-9] 匹配任意数字 |
[^abc] |
否定字符类 | [^0-9] 匹配非数字 |
| ` | ` | 或(扩展) |
() |
分组(扩展) | (ab)+ 匹配 ab、abab |
{n,m} |
重复次数(扩展) | a{2,4} 匹配 aa~aaaa |
\b |
单词边界 | \bcat\b 匹配独立单词 cat |
注意区分:正则里的
*是"前一个字符重复";而**通配符(glob)**里的*是"匹配任意多个任意字符"。两者不是一回事!
6.4 实例
# 查找以 http 开头的行
grep -E "^http" /etc/services
# 查找以 .conf 结尾的行
grep -E "\.conf$" /etc/services
# 查找以 root 开头的行(匹配 passwd 文件里的 root 用户)
grep "^root" /etc/passwd
# 匹配任意数字(如 IP 片段)
grep -E "[0-9]{1,3}\.[0-9]{1,3}" /etc/hosts
# 匹配 email 格式(简单版)
grep -E "^[a-zA-Z0-9._%+-]+@[a-zA-Z0-9.-]+\.[a-zA-Z]{2,}$" emails.txt
# 在系统用户文件中查找非注释、非空行
grep -Ev "^#|^$" /etc/fstab
# 查找 IP 地址
ip addr | grep -E "inet " | grep -v "127.0.0.1"
6.5 grep 家族
grep:基本正则。grep -E/egrep:扩展正则(推荐)。grep -F/fgrep:固定字符串(不做正则解释)。zgrep:直接搜索 .gz 压缩文件。
七、环境变量与 Shell 展开
7.1 环境变量与 $PATH
环境变量是定义在 shell 环境中的命名值,影响命令和程序的行为。
echo $PATH # 查看 PATH(命令搜索路径)
echo $HOME # 用户主目录
echo $USER # 当前用户名
echo $SHELL # 当前 shell
echo $LANG # 语言/编码
echo $HOSTNAME # 主机名
env # 查看所有环境变量
printenv PATH # 另一个查看方式
$PATH 详解:
PATH是用冒号:分隔的目录列表,当你在终端输入一个命令名时,shell 会按顺序在这些目录里查找可执行文件。- 例如:
echo $PATH输出/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:...。 - 命令
which ls之所以能找到/usr/bin/ls,就是因为在 PATH 里搜到的。
export —— 把变量导出为环境变量:
MYVAR="hello" # 普通 shell 变量(仅当前 shell 可见)
export MYVAR # 导出为环境变量(子进程也能看到)
export JAVA_HOME=/usr/lib/jvm/java-11-openjdk # 常见用法
export PATH="/opt/mytools:$PATH" # 把新目录加到 PATH 前面
- 普通变量 vs 环境变量:环境变量会传递给子进程,普通变量不会。
export用于把变量升级为环境变量。- 想让变量在每次登录都生效,写入
~/.bashrc或~/.bash_profile。
7.2 常用的 Shell 展开(Expansion)
Shell 在执行命令前会进行多种展开,理解展开顺序是掌握 shell 的关键。
波浪号展开 ~
echo ~ # 当前用户主目录(/home/rocky)
cd ~/docs # 进入主目录下的 docs
echo ~other # other 用户的主目录
变量展开 $VAR / ${VAR}
echo $USER # 展开为用户名
echo ${HOME}/data # 展开为 /home/rocky/data
路径名展开(通配符)* ? [...]
ls *.txt # * 匹配任意多个字符
ls file?.log # ? 匹配单个字符
ls file[12].txt # 字符类
花括号展开 {}
echo {1..5} # 输出 1 2 3 4 5
echo {a,b,c}.txt # 输出 a.txt b.txt c.txt
mkdir -p dir/{src,bin,conf} # 一次创建多个子目录
touch file_{01..10}.log # 创建 file_01.log ~ file_10.log
echo {1..10..2} # 步长 2:1 3 5 7 9
算术展开 $(( ))
echo $((3 + 5)) # 输出 8
echo $((10 / 3)) # 输出 3(整数除法)
命令替换 $( )
echo $(date +%H:%M) # 输出当前时间
八、帮助文档:man / info / --help
学会查阅帮助是 Linux 学习者的必备能力,永远不要背命令参数,要学会查。
8.1 --help(快速查看用法)
ls --help # 快速查看命令的简要用法和常用选项
grep --help
绝大多数 GNU 命令都支持 --help,适合快速回忆选项。
8.2 man
man ls # 查看 ls 的完整手册
man -k keyword # 关键词搜索相关手册(等价于 apropos)
man 5 passwd # 查看第 5 节(文件格式)的 passwd
man -f ls # 简要描述(whatis)
man 手册的章节(Section):
| 章节 | 内容 | 例子 |
|---|---|---|
| 1 | 用户命令 | man 1 ls |
| 2 | 系统调用 | man 2 open |
| 3 | C 库函数 | man 3 printf |
| 5 | 文件格式与配置 | man 5 passwd、man 5 hosts |
| 7 | 杂项/约定 | man 7 regex |
| 8 | 系统管理命令 | man 8 systemctl |
在 man 页内操作(与 less 相同,man 使用 less 显示):
| 按键 | 作用 |
|---|---|
| 空格/f | 向下翻页 |
| b | 向上翻页 |
/关键词 |
向下搜索(n 下一个) |
?关键词 |
向上搜索 |
| g/G | 到开头/结尾 |
| q | 退出 |
读 man 的常见段落:NAME(简介)、SYNOPSIS(语法)、DESCRIPTION(详解)、OPTIONS(选项)、EXAMPLES(示例)、SEE ALSO(相关)。
8.3 info(更详细的手册)
info bash # 查看 bash 的详细文档(超链接式)
info coreutils
info是 GNU 提供的更详细的文档系统,支持超链接导航。- 多数命令的 info 文档比 man 更详尽,但 man 更常用。
- 通常先看
man,需要更深入再看info。
九、历史与补全:history / !! / Tab / Ctrl+R
9.1 命令历史 history
history # 显示命令历史列表
history 20 # 只显示最近 20 条
history -c # 清空当前会话历史
!5 # 执行历史中第 5 条命令
!! # ★ 执行上一条命令
!ls # 执行最近一条以 ls 开头的命令
!$ # 上一条命令的最后一个参数
history | grep dnf # 在历史中搜索某命令
常用快捷历史操作:
| 操作 | 效果 |
|---|---|
| 上下箭头 | 浏览历史 |
!! |
执行上一条命令 |
!$ |
上一条命令的最后一个参数 |
!string |
执行最近的以 string 开头的命令 |
Ctrl+R |
反向增量搜索历史 |
Ctrl+P / Ctrl+N |
上一条/下一条(等价上下箭头) |
典型场景:sudo !! 经常用于"上一条命令没加 sudo,重新用 sudo 执行一次"。
9.2 Tab 补全
- 命令补全:输入
sys按Tab,会补全为systemctl(如果唯一)。 - 路径补全:输入
cd /et按Tab,补全为/etc/。 - 多候选:按一次 Tab 无反应,再按一次会列出所有可能。
- 参数补全:部分命令支持参数补全(如
systemctl sta+ Tab)。 - 养成用 Tab 的习惯,又快又不易出错。
9.3 Ctrl+R 反向搜索
# 按 Ctrl+R,然后输入关键词,即可反向搜索历史命令
(reverse-i-search)`dnf`: sudo dnf update # 匹配到后按回车执行
- Ctrl+R 进入反向增量搜索:边输入边匹配历史。
- 再次按 Ctrl+R 跳到上一个匹配项。
- 按 回车 执行匹配到的命令;按 Esc 或方向键可编辑后再执行;Ctrl+C 退出搜索。
十、总结与练习
10.1 核心速查
| 需求 | 语法/命令 |
|---|---|
| 管道(串联命令) | `cmd1 |
| 输出覆盖/追加到文件 | cmd > file / cmd >> file |
| 从文件读入 | cmd < file |
| 错误与输出都进文件 | cmd > file 2>&1 |
| 边看边存 | `cmd |
| 输出当参数 | `cmd |
| 上一条命令退出码 | echo $? |
| 成功才执行 / 失败才执行 | A && B / `A |
| 变量 / 命令替换 | $VAR / $(cmd) |
| 扩展正则搜索 | grep -E "pattern" |
| 查看/导出环境变量 | echo $PATH / export VAR=value |
| 查看帮助 | man cmd / cmd --help / info cmd |
| 执行上一条命令 | !! |
| 补全 / 反向搜索 | Tab / Ctrl+R |
10.2 动手练习建议
# 1. 重定向练习
echo "hello world" > test.txt
cat test.txt
echo "second line" >> test.txt
cat test.txt
ls /no-such-dir 2> err.log; cat err.log
# 2. 管道练习
cat /etc/passwd | grep -v "^#" | cut -d: -f1 | sort
ps aux | grep ssh | grep -v grep
# 3. tee 练习
date | tee date.txt
cat date.txt
# 4. xargs 练习
mkdir -p /tmp/xargtest && cd /tmp/xargtest
touch a.log b.log c.log
ls *.log | xargs -I {} cp {} /tmp/
ls /tmp/*.log
# 5. 退出码与条件执行
true && echo "成功"
false || echo "失败"
test -f /etc/hosts && echo "hosts 存在"
# 6. 变量与命令替换
myname="Rocky"
echo "你好, ${myname}!"
echo "今天: $(date +%F)"
# 7. grep 正则
printf "root:0\nuser1:100\nroot2:200\n" | grep -E "^root[0-9]?:"
grep -E "^[0-9]" /etc/services | head -5
# 8. 环境变量
export MYTEST="hello env"
echo $MYTEST
echo $PATH | tr ":" "\n" | head
# 9. 历史与补全
history | tail -10
echo "第一条命令"
!! # 再次执行上一条(echo "第一条命令")
提示:
#开头的行是注释,复制练习时请去掉不需要的部分。逐条亲手敲一遍,才是掌握 Shell 的最佳方式。
参考资料
- GNU Bash Reference Manual:https://www.gnu.org/software/bash/manual/bash.html
- GNU Coreutils 官方文档:https://www.gnu.org/software/coreutils/manual/
- GNU Grep 官方文档:https://www.gnu.org/software/grep/manual/
- GNU Findutils(xargs)官方文档:https://www.gnu.org/software/findutils/manual/
- Advanced Bash-Scripting Guide:https://tldp.org/LDP/abs/html/
- Linux 手册页官网:https://www.kernel.org/doc/man-pages/
- man 手册(Rocky Linux 内运行
man bash、man grep等) - Rocky Linux 官方文档 - Linux 用户命令:https://docs.rockylinux.org/books/admin_guide/03-commands/
- 菜鸟教程 - Linux 教程 / Shell 教程:https://www.runoob.com/linux/linux-tutorial.html