Shell 与脚本¶
常用 Shell¶
Bash¶
Bash(Bourne Again Shell)是Linux系统默认的shell,也是使用最广泛的shell。它提供了丰富的功能和良好的兼容性。
Zsh¶
Zsh(Z Shell)是一个功能强大的shell,提供了更好的自动补全、主题支持和插件系统。许多开发者选择使用Zsh作为他们的默认shell。
其他Shell¶
- sh:原始的Bourne Shell
- csh/tcsh:C Shell系列
- fish:友好的交互式shell
- dash:轻量级的POSIX兼容shell
Shell 基础¶
环境变量¶
Shell使用环境变量来存储系统配置信息:
# 查看环境变量
echo $PATH
echo $HOME
echo $USER
# 设置环境变量
export MY_VAR="value"
# 查看所有环境变量
env
命令历史¶
Shell会记录命令历史,方便重复使用:
# 查看历史命令
history
# 搜索历史命令
Ctrl+R
# 执行历史命令
!! # 执行上一个命令
!n # 执行第n个历史命令
!string # 执行最近以string开头的命令
通配符¶
Shell支持通配符来匹配文件名:
*:匹配任意字符?:匹配单个字符[abc]:匹配a、b或c[a-z]:匹配a到z的任意字符{a,b}:匹配a或b
编写和执行 Shell 脚本¶
创建Shell脚本¶
创建一个简单的Shell脚本:
#!/bin/bash
# 这是一个注释
echo "Hello, World!"
echo "当前用户: $USER"
echo "当前目录: $(pwd)"
脚本权限¶
给脚本添加执行权限:
chmod +x script.sh
执行脚本¶
# 方法1:直接执行(需要执行权限)
./script.sh
# 方法2:使用bash解释器
bash script.sh
# 方法3:使用sh解释器
sh script.sh
Shebang¶
脚本的第一行通常包含shebang,指定解释器:
#!/bin/bash # 使用bash
#!/bin/sh # 使用sh
#!/usr/bin/env python3 # 使用python3
常用脚本工具和技巧¶
我们初步认识了一些常用的命令。接下来我们会对它们进行一定的扩展。
- 系统命令
sudo命令用于提权。poweroff和shutdown两个命令用于关机。reboot命令用于重启电脑。whoami命令用于查看自己是哪个用户。which命令用于查找可执行文件的路径。ps命令用于显示当前运行的进程。-e:显示所有进程。-f:以全格式显示进程信息,包括父进程ID、用户等。-l:以长格式显示进程信息。-u:显示指定用户的进程。-p:显示指定进程ID的进程。-o:自定义输出格式。-H:以树形结构显示进程之间的关系。-j:以作业控制格式显示进程信息。-x:显示所有进程,包括没有控制终端的进程。
kill命令用于终止进程。fg命令可以将后台运行的任务调回前台,这个命令可以恢复被Ctrl+Z挂起的任务。bg命令可以将任务放到后台运行。- 列出和查找类
pwd命令用于显示当前工作目录的绝对路径。ls命令用于列出目录中的文件和子目录。-l:以长格式列出文件和目录的详细信息。-a:列出所有文件和目录,包括隐藏文件。-h:以人类可读的格式显示文件大小。-R:递归地列出子目录中的文件和目录。-t:按修改时间排序。
tree命令用于以树形结构显示目录中的文件和子目录。find命令用于在目录中查找文件和目录。- 文件系统操作类
cd命令用于切换当前工作目录。mkdir命令用于创建新目录。-p:递归地创建多级目录,如果上级目录不存在则一并创建。-v:显示创建目录的详细信息。-m:设置新目录的权限。
touch命令用于创建新文件或更新现有文件的修改时间。-a:只更新访问时间。-m:只更新修改时间。-c:如果文件不存在则不创建。-t:设置文件的时间戳。-r:使用指定文件的时间戳。
rm命令用于删除文件或目录。-r:递归地删除目录及其内容。-f:强制删除文件或目录,不提示确认。-i:在删除前提示确认。-v:显示删除的详细信息。
rmdir命令用于删除空目录。cp命令用于复制文件或目录。-r:递归地复制目录及其内容。-f:强制覆盖目标文件。-i:在覆盖前提示确认。-v:显示复制的详细信息。-u:只在源文件比目标文件新时才进行复制。
mv命令用于移动或重命名文件或目录。-f:强制覆盖目标文件。-i:在覆盖前提示确认。-v:显示移动的详细信息。-u:只在源文件比目标文件新时才进行移动。
ln命令用于创建链接。-s:创建软链接(符号链接)。-f:强制覆盖目标链接。-i:在覆盖前提示确认。-v:显示链接的详细信息。-T:将目标视为一个普通文件,而不是目录。
tar命令用于打包和解包文件。-c:创建一个新的归档文件。-x:从归档文件中提取文件。-f:指定归档文件的名称。-v:显示详细的操作信息。-z:使用 gzip 压缩或解压缩归档文件。-j:使用 bzip2 压缩或解压缩归档文件。-J:使用 xz 压缩或解压缩归档文件。-p:保留文件的权限和时间戳。-C:切换到指定目录后再进行打包或解包。
- 文本处理类
head命令用于显示文件的前几行。tail命令用于显示文件的后几行。cat命令用于连接文件并打印到标准输出。-n:为每一行添加行号。-b:为非空行添加行号。-s:压缩连续的空行。-E:在每行末尾显示$符号。-T:将制表符显示为^I。-v:显示不可见字符。-A:显示所有不可见字符,包括空格和制表符。-e:等同于-vE,显示不可见字符并在行末添加$符号。
echo命令用于在终端输出文本。-n:不在输出末尾添加换行符。-e:启用转义字符的解释,例如\n表示换行,\t表示制表符。-E:禁用转义字符的解释。-c:不输出任何内容。-C:将输出内容转换为大写字母。-l:将输出内容转换为小写字母。-a:将输出内容转换为首字母大写字母。-s:将输出内容转换为首字母小写字母。-p:将输出内容转换为首字母大写字母,并将其他字母转换为小写字母。
警告
导啊,咱们生产环境为啥执行 dpkg 会说 command not found 呀?
我之前干了啥?清了一下工作路径垃圾,好像是 sudo rm -rf /
什么叫相对路径要加个点?
警告:除非你知道你在输入什么,否则任何情况下均不要带上sudo执行删除命令!
命令联动¶
在 Linux 中,重定向、管道、变量和进程替换是四种把“数据”从一条命令挪到另一条命令(或文件)的核心手段。它们常被混用,但机理各不相同。
重定向¶
重定向只认识真正的文件(或文件描述符),有两种:> 把标准输出定向到文件(写);<把文件内容定向到标准输入(读)。
echo "Hello, World!" > hello.txt # 新建或覆盖文件
cat < hello.txt # 把文件当输入
管道¶
管道 | 在内核里创建一条匿名管道,让左边进程的标准输出直接成为右边进程的标准输入,两边同时运行。
ls | grep "file" # 边 ls 边 grep,流式处理
Here-String¶
<<< word是 Bash 的 here-string 语法,shell 会先把 word 的扩展结果写进一个临时文件(或匿名管道),再把该临时对象作为标准输入递给命令。因此它正好弥补了重定向只能读文件的不足。
grep "file" <<< "$(ls)" # 等价于 ls | grep "file",但没用管道
Here-String和管道有一定的区别。管道是流式的,边产生边消费;Here-String必须等整个字符串生成完才能开始消费。
进程替换¶
进程替换是一种"把命令输出/输入伪装成文件名"的 Bash 特性。<(cmd) 把命令的标准输出绑定到一个命名管道(或 /dev/fd/N),返回一个可读文件名;>(cmd) 则把命令的标准输入绑定到一个命名管道,返回一个可写文件名。对任何"只能读文件"的工具(diff、cat、sort…)来说,这就像凭空多了两个临时文件。
diff <(cmd1) <(cmd2) # 比较两条命令的输出,而无需临时落盘
sort >(uniq > result.txt) # 把排序结果直接丢给 uniq
变量与命令替换¶
$(cmd) 是"命令替换",shell 会等待该命令执行结束,把它的全部标准输出当成一段文本收回来,可以赋给变量,也可以直接嵌入命令行。
out=$(ls) # 把 ls 的输出存进变量
grep "file" <<< "$out" # 这里用 here-string 消费变量
diff <(echo "$out") <(ls) # 用进程替换再比一次
$(cmd) 本身不是管道,也不是重定向。它只是"把命令输出变成字符串"的一种手段。
| 机制 | 数据形态 | 左侧何时开始 | 右侧何时开始 |
|---|---|---|---|
cmd1 \| cmd2 |
管道字节流 | 立即 | 立即 |
cmd < file |
已有文件 | 立即 | - |
cmd <<< "$str" |
临时文件 | 字符串生成完后 | 字符串生成完后 |
cmd <(cmd1) |
命名管道/FD | 立即 | 立即 |
str=$(cmd1); cmd2 <<< "$str" |
变量→临时文件 | cmd1 结束后 | cmd1 结束后 |
掌握这四种手法后,你就可以根据各种实际条件,灵活选择最简洁、最高效的写法。
思考题
-
sl这个玩具软件仅有200行源代码,却能让你在终端里看到一列火车呼啸而过。请你阅读它的源代码,并简要描述它的实现原理;如果希望把该软件改成系统服务,开机就自动跑一列火车,你需要解决哪些问题?试着实践一下。 -
对比
apt、pacman的实现。为什么后者更容易实现滚动更新和回滚?怎么验证? -
bin、sbin、usr/bin这三个东西都是什么?为什么会有这么多类似的目录?它们之间有什么区别?为什么直到2020年仍然有发行版保持着/bin和/usr/bin的分离?能否把它们合并?如果可以,怎么做? -
chmod 777 /到底给敌人开了多少额外的后门?试着统计一个正常的系统中所有777文件分别被多少个不同的进程打开过。 -
管道和
Here-String的内存和延迟有多少?为什么后者会OOM,而管道不会?Here-String的实现原理是什么?如果要改进它,你会怎么做? -
试着在虚拟机里安装Arch Linux和NixOS;然后,试着将Arch的安装步骤翻译成
configuration.nix,并试着生成ISO。对比两者的异同,然后说明声明式系统在可维护性上的优势。但是为什么即使这样,Arch用户仍然比NixOS用户多?