MIT-黑客工具笔记-全-

MIT 黑客工具笔记(全)

001:课程概述 🛠️

在本节课中,我们将学习《黑客工具》这门课程的整体介绍、课程目标、结构安排以及学习方法。我们将明确“黑客”一词在本课程中的含义,并了解如何通过实践和分享来掌握这些强大的编程工具。

课程简介

大家好,欢迎来到《黑客工具》课程。

首先介绍所有讲师。我的名字是Nicolas。另一位是Anish。我们将从课程简介开始,然后深入课程内容。

课程网站是 hacker-tools.github.io。网站上有许多有用的内容。正如网站所说,这门课是关于“黑客工具”的,而不是“骇客工具”。如果你查看课程概述,那里有一些有用的链接。

“黑客”的定义

“黑客”这个词有两种常见的定义。

一种定义是指程序员或对充分利用软件充满热情的人,例如编写酷炫的程序,让计算机做有趣的事情。这是在MIT起源的“黑客”定义。如果你点击第一个链接,它会带你到一个维基百科页面,告诉你更多关于这个术语的历史。如果你好奇,我推荐阅读,MIT的历史很酷。

另一种“黑客”是指安全骇客,即入侵网络或逆向工程软件的人。本课程是关于第一种“黑客”,而不是第二种。为了避免歧义,我们将使用 Hacker 来描述程序员,使用 Cracker 来描述从事安全事务的人。

希望你们所有人都是来学习编程和编程工具的,而不是非常具体的安全相关事务。尽管我们会涉及一点安全内容,但比其他部分少得多。安全内容在最后一讲的后半部分有所涉及。

课程动机

接下来,我想告诉你们更多关于这门课程背后的动机。

MIT之前有很多课程。我们注意到的一点是,MIT的课程在教授计算机科学理论方面做得很好,甚至在教你如何编码方面也做得很好,因为你在这里上的许多课程都需要编写大量程序。

但有一件事这些课程没有做,那就是没有教你如何充分利用编程、高效调试代码等所需的工具。我们认为这是这里课程设置中缺失的东西。根据我们个人的经验,我们发现精通这些工具非常有益。在你本科期间,甚至可能在接下来的几个月里,任何投入到这些工具上的时间都会得到回报。

因此,这就是我们教授这门课程的原因。我们将教你新工具,教你如何充分利用你的工具,告诉你如何根据你的工作流程定制工具,以及如何通过插件扩展甚至自己修改工具。

课程结构

接下来是课程结构。我们将有六次讲座。这是讲座日程,可能会有变动。此外,如果任何人对想学习的特定内容有强烈意见,请随时告诉我们,我们很乐意添加。

课程结构方面,我们有两个小时的时间段,它们将被分成两个50分钟的讲座,中间有10分钟的休息时间,这很标准。如果讲座提前结束,我们之后会留下来,进行类似答疑时间,我们可以四处走动,帮助你们设置工具等。

两个小时的时间并不足以教会你们所有想学的东西。因此,这门课程更多的是向你们展示有哪些可能性,激励你们了解更多关于这些工具的知识。向你们展示一堆很酷的东西,并给你们资源,让你们可以自己学习。

学习方法与实践

对于所有不同的模块,我们也会为你们准备练习。例如,如果你查看今天关于虚拟机和容器的模块,我们列出了一些与虚拟机相关的练习,以及一些与容器相关的练习。

如果你想从这门课程中学到东西,你真的应该完成这些练习,甚至更进一步,抓住这个机会去试验你的工具,摆弄你的工具,尝试不同的东西。如果你想真正精通某个编辑器,也许可以尝试玩一下Vim和Emacs。

我们会提供所有学习这些内容的资源,但你需要投入时间才能真正精通这些工具。

此外,我们使用这些工具中的许多已经很久了,经常有些东西只是我们的肌肉记忆,我们不会特意告诉你们它们是如何工作的,或者你们可能觉得奇怪。所以,如果你们在学习过程中有任何问题,这正是本课程的意义所在:弄清楚如何充分利用你的工具,这包括提出诸如“有没有更好的方法来做我通常做的这件事?”这样的问题。所以,如果我们讲的任何内容你不明白它是如何工作的,或者为什么我们这样做,或者你看到我们设置了一个你不理解的命令,请提问,我们可以解释。通过在实践中发现、观察别人怎么做,是你让自己工具用得更好的方式。

交流与分享平台

我们为这门课程设置了一个Piazza论坛。在课程网站的主页和本概述的笔记中都有链接。

我们将尝试一些有趣的事情。我们要求,在每次讲座之后,当你们自己练习和试验这些工具时,如果你们发现了任何有趣的东西,或者你们对这些工具有任何先验知识想与全班分享,请在Piazza上发帖。我们非常希望你们每个人每节课在Piazza上发一个帖子,内容是关于你学到的东西或你已经知道的东西。

这样我们都可以互相学习。我们要学习使用的工具非常复杂。即使在实验室里,当我们一起工作时,看着John的屏幕,他正在为某个项目工作,John做了一些我以前不知道的事情,我就会想,哇,我可以学到东西。

这同样适用于,如果你为我们试图教你的某些内容找到了一个好的学习资源,请发布链接,这样其他人也可以看到,因为他们可能也会从你分享的东西中学到东西。

课程反馈

最后,我们有一个注册表,填写大约需要10秒钟。如果你还没有填写,请填写一下。这让我们有机会了解你对哪些主题感兴趣想学习。

还有什么问题吗?有没有什么特别的东西不在日程表上,但你希望可以学到的?具体来说,我们计划涵盖的内容包括:虚拟机和容器、Shell脚本、命令行环境、数据处理、编辑器、版本控制、配置文件与备份、机器内省与程序内省、包管理、依赖管理、操作系统定制、操作系统自动化、网页浏览器以及安全。

有没有什么看起来没有被这些类别涵盖,而你非常好奇并想了解更多的东西?如果有,请说出来,我们也许可以做点什么。当然,没有也没关系,这些是我们认为有趣的东西,但完全可能有其他东西是你们希望我们教的。


本节课中我们一起学习了《黑客工具》课程的概述,明确了课程中“黑客”的定义是创造性的程序员。我们了解了课程旨在弥补传统计算机教育在实用工具教学上的不足,并介绍了通过讲座、实践练习以及Piazza论坛分享来学习的高效方法。记住,精通工具的关键在于主动探索、实践和社区交流。

002:Shell与脚本 😊

在本节课中,我们将要学习Shell的基础知识及其脚本编程能力。Shell是一个强大的文本界面,允许你高效地与计算机交互和执行任务。我们将从基本命令开始,逐步深入到变量、控制流、命令组合等高级主题,帮助你理解如何利用Shell自动化工作流程。

Shell简介

Shell是一个高效的文本界面,允许你通过命令与计算机交互。熟悉Shell后,你会发现几乎无需使用鼠标即可完成大部分操作。

当你打开Shell时,会看到一个提示符,允许你运行程序和命令。例如,mkdir创建目录,cd切换目录,ls列出文件,mv移动文件,cp复制文件。

mkdir foobar
cd foobar
touch foo bar
ls
mv bar baz
cp foo foobar
ls

这些是Shell的基础操作,但Shell的功能远不止于此。你可以调用计算机上的任何程序,命令行工具通常比图形界面更高效。

Shell脚本编程

Shell本质上是一种交互式编程语言,通常称为脚本。有多种不同的Shell,如shbashcshfishzshksh。本课程主要使用bash,因为它最普遍。

Shell脚本非常有用,你既可以直接在命令行输入命令,也可以将命令写入文件执行。文件顶部的#!行(称为shebang)指定了解释器。

#!/bin/sh
echo "Hello"
#!/usr/bin/python
print("Hello from Python")

通过chmod +x命令使文件可执行后,即可直接运行。

变量与特殊变量

在Shell中,你可以使用变量存储数据。变量赋值时不能有空格。此外,还有一些特殊的预定义变量。

foo=bar
echo $foo

特殊变量包括:

  • $0:脚本或命令的名称。
  • $1$9:脚本的参数。
  • $#:参数的数量。
  • $$:当前脚本的进程ID。
#!/bin/bash
echo "程序名: $0"
echo "第一个参数: $1"
echo "参数个数: $#"

控制流:循环与条件判断

Shell支持for循环和if条件判断等控制流结构,其语法与其他编程语言略有不同。

for循环遍历一个由空格分隔的列表。

for i in 1 2 3 4 5
do
    echo $i
done

你可以使用命令替换$(command)来生成循环列表。

for i in $(seq 1 5)
do
    echo $i
done

if语句根据命令的退出状态码(0表示成功,非0表示失败)决定是否执行代码块。test命令(或[)用于进行各种测试。

if [ -d "$file" ]
then
    echo "$file 是一个目录"
fi

注意:在bash中,使用双括号[[ ]]是更好的选择,它能更智能地处理变量,特别是空值。

if [[ -d "$file" ]]
then
    echo "$file 是一个目录"
fi

引号与空格问题

Bash默认会按照空格分割参数,这可能导致文件名包含空格时出现问题。使用引号可以防止单词被分割。

# 错误示例:文件名“my documents”会被分割成“my”和“documents”
for file in $(ls); do echo $file; done

# 正确示例:使用通配符*,Bash会正确处理文件名
for file in *; do echo "$file"; done

在变量替换时,也应使用引号。

if [ -d "$file" ]; then ... fi
# 或更好:
if [[ -d "$file" ]]; then ... fi

通配符

通配符(Globbing)是Shell用于匹配文件名的强大模式。

  • *:匹配任意字符串。
  • ?:匹配任意单个字符。
  • {a,b,c}:匹配a、b或c。
ls *.txt          # 列出所有.txt文件
ls ?.txt          # 列出所有单个字符的.txt文件
ls {foo,bar}/*.py # 列出foo和bar目录下的所有.py文件

组合命令:管道与重定向

Shell的强大之处在于能够组合多个简单的程序。管道|将一个命令的输出作为另一个命令的输入。

ls | grep o           # 列出包含字母‘o’的文件
ps aux | grep username # 查找属于特定用户的进程
journalctl | grep kernel | tail -5 # 查找最后5条包含‘kernel’的系统日志

重定向可以改变命令的输入/输出源。

  • >:将标准输出重定向到文件(覆盖)。
  • >>:将标准输出重定向到文件(追加)。
  • <:将文件内容作为标准输入。
  • 2>:将标准错误重定向到文件。
cat < input.txt > output.txt # 从input.txt读取,写入output.txt
command 2> error.log         # 将错误信息保存到error.log

进程替换<(command)允许你将命令的输出视为文件。

diff <(journalctl -b -1) <(journalctl -b -2) # 比较两次启动的日志

任务控制

你可以在后台运行程序,并管理这些任务。

  • &:在后台运行命令。
  • jobs:列出后台任务。
  • fg %1:将任务1切换到前台。
  • bg %1:将任务1在后台继续运行。
  • Ctrl+Z:暂停前台任务并放入后台。
  • disown:使任务与当前Shell分离,即使退出Shell也不会终止。
server &          # 在后台启动服务器
jobs              # 查看后台任务
fg %1             # 将服务器切换到前台
Ctrl+Z            # 暂停服务器
bg %1             # 让服务器在后台继续运行
disown %1         # 分离任务,退出Shell也不影响

进程管理

你可以查看和管理系统上运行的其他进程。

  • ps:查看进程。
  • pgrep:根据名称查找进程ID。
  • pkill:根据名称发送信号给进程。
  • kill:根据进程ID发送信号。
  • kill -9Ctrl+\:强制终止进程(不推荐,不进行清理)。
  • killCtrl+C:请求进程终止(推荐,允许清理)。
ps aux
pgrep -af server
pkill server
kill 12345
kill -9 12345 # 强制终止

命令行标志

大多数命令行工具接受以-开头的标志来修改其行为。

  • 短标志:-h(帮助),-v(详细输出),-a(全部),-f(强制)。
  • 长标志:--help--verbose--all--force
  • 组合短标志:-avf 等同于 -a -v -f
  • 使用--可以明确指示后续参数不是标志,例如创建以-开头的文件:touch -- -foo
ls -a           # 显示所有文件(包括隐藏文件)
rm -f file      # 强制删除文件
curl -v https://example.com # 显示详细输出
command --help  # 显示帮助信息
touch -- -myfile # 创建名为“-myfile”的文件

总结

本节课我们一起学习了Shell脚本编程的核心概念。我们从Shell的基本命令和变量开始,探讨了控制流、引号与空格处理、以及强大的通配符功能。接着,我们深入了解了如何通过管道和重定向组合命令,以及如何进行任务控制和进程管理。最后,我们介绍了命令行标志的常见用法。掌握这些知识将使你能够更高效地在命令行环境中工作,并编写脚本来自动化复杂任务。建议你通过实践练习来巩固这些概念,并进一步学习数据整理和命令行环境优化等相关主题。

003:命令行环境

在本节课中,我们将学习如何定制和优化你的命令行环境,使其更高效、更符合你的工作习惯。我们将涵盖别名、Shell配置、终端模拟器、终端复用器以及一系列能极大提升生产力的工具。

概述

上一节我们介绍了基本的Shell操作。本节中,我们将深入探讨如何通过定制Shell、使用高效的终端工具和复用器来打造一个强大的命令行工作环境,让你的日常操作事半功倍。

Shell别名

在Shell中,你可以为常用命令创建简短的别名,以节省输入时间。

例如,如果你经常使用 ls -l 命令,可以为其创建一个别名:

alias ll='ls -l'

现在,当你输入 ll 时,它就会自动展开为 ls -l

别名可以组合使用。例如,你可以先定义一个别名,再基于它创建另一个:

alias ll='ls -l'
alias lla='ll -a'

输入 lla 会先展开为 ll -a,再进一步展开为 ls -l -a

你甚至可以覆盖现有的命令名。例如,将 ls 定义为其他内容:

alias ls='ls --color=auto'

如果你想临时使用命令的原始版本,而不是别名,可以在命令前加上反斜杠 \

\ls

或者使用 command 命令:

command ls

要永久删除一个别名,可以使用 unalias 命令:

unalias ls

Shell配置与启动文件

在当前的Shell会话中定义的别名是临时的。为了让它们在所有新会话中生效,你需要将别名定义写入Shell的启动配置文件。

以下是不同Shell常见的配置文件:

  • Bash: ~/.bashrc
  • Zsh: ~/.zshrc
  • Fish: ~/.config/fish/config.fish

例如,在Bash中,你可以编辑 ~/.bashrc 文件,将别名定义添加进去。这样,每次打开新的终端时,这些别名都会被自动加载。

一个很好的做法是将配置集中管理,例如创建一个单独的配置文件(如 ~/.aliases),然后在主配置文件中引用它:

# 在 ~/.bashrc 中添加
if [ -f ~/.aliases ]; then
    . ~/.aliases
fi

更智能的Shell:Zsh

除了Bash,还有许多其他Shell可供选择,例如Zsh。Zsh提供了许多开箱即用的便利功能。

Zsh支持强大的路径扩展。例如,使用 ** 可以进行递归匹配:

# 查找当前目录及所有子目录下的 .txt 文件
ls **/*.txt

它还具有出色的自动补全功能,可以补全命令、参数和路径。许多Zsh配置框架(如Oh My Zsh)还提供了主题、插件(如语法高亮、命令纠错)等,能显著提升用户体验。

例如,启用语法高亮后,当你输入无效命令时,它会立即显示为红色。命令纠错功能可以在你输错命令时提示正确的命令。

终端模拟器

Shell是解释命令的程序,而终端模拟器则是运行Shell的图形界面程序。你可以选择不同的终端模拟器来获得更好的特性支持,例如:

  • 更丰富的快捷键绑定
  • 可自定义的字体和配色方案
  • 更好的性能(如GPU加速渲染)
  • 分页或分割面板功能

终端复用器:Tmux

Tmux是一个终端复用器,允许你在一个终端窗口内运行多个Shell会话。

使用Tmux,你可以:

  • 创建多个标签页。
  • 在垂直或水平方向分割窗格。
  • 从Tmux会话中分离,让程序在后台继续运行,稍后再重新连接。这对于在远程服务器上运行长时间任务特别有用。

基本Tmux命令:

  • tmux new -s <session_name>: 创建新会话。
  • Ctrl-b d: 分离当前会话。
  • tmux attach -t <session_name>: 重新连接到指定会话。
  • Ctrl-b %: 垂直分割当前窗格。
  • Ctrl-b ": 水平分割当前窗格。

高效的文件导航

在文件系统中快速跳转可以节省大量时间。除了 cd 命令,还有一些更智能的工具。

fasd 工具通过记录你访问最频繁和最近使用的文件和目录,实现快速跳转。

# 快速跳转到经常访问的目录
z project
# 快速打开最近使用的文件
v config.yaml

ranger 是一个控制台下的文件管理器,提供可视化导航、预览文件等功能,比单纯使用 lscd 更高效。

改进的基础命令替代品

许多经典Unix命令都有功能更强大、用户友好的现代替代品。

  • exa 替代 ls:支持图标、更好的文件大小显示(如KB、MB)和Git状态集成。
    exa -l --git
    
  • ripgrep (rg) 替代 grep:速度极快,默认递归搜索,能自动忽略.gitignore中的文件。
    rg "function_name" --type py
    
  • fd 替代 find:语法更简单,默认忽略隐藏文件,搜索速度更快。
    fd "pattern"
    
  • fzf 是一个通用的模糊查找器,可以交互式地筛选列表(如文件、命令历史)。它常与Ctrl-R绑定,用于搜索历史命令。
  • tldr 替代 man:提供简洁实用的命令使用示例,比冗长的手册页更易上手。
    tldr tar
    
  • trash-cli 替代 rm:将文件移至“回收站”而非永久删除,更安全。
    trash file.txt
    
  • rsync 替代 scp 用于同步文件:支持增量同步和断点续传,效率更高。
    rsync -avz source/ user@host:destination/
    

总结

本节课中我们一起学习了如何深度定制命令行环境。我们从创建Shell别名和配置启动文件开始,探讨了Zsh等更智能的Shell及其增强功能。接着,我们了解了终端模拟器的选择以及使用Tmux进行终端复用的强大之处。最后,我们介绍了一系列能极大提升文件操作、搜索和日常工作效率的现代命令行工具替代品。通过合理配置和使用这些工具,你的命令行界面将成为一个极其高效的工作站。

004:数据整理 📊

在本节课中,我们将要学习命令行中一项核心技能:数据整理。数据整理是指当你有一堆文本数据,并希望将其转换成另一种形式(通常是更精简的形式)时所做的事情。例如,系统日志通常非常冗长,从中找到有用信息很困难,因此需要将其浓缩。命令行提供了强大的工具来帮助你筛选和转换数据。

在上一讲中,我们简单介绍了 grep 和管道 | 等基础工具。本节中,我们将深入探讨所有能帮助你“按摩”数据、将其从一种格式转换为所需格式的工具。我们将以一个实际的服务器日志文件为例进行操作。


从日志中提取信息

假设我们想查看服务器上 SSH 的登录尝试记录。服务器日志通常包含大量信息,直接查看非常困难。

首先,我们使用 grep 命令筛选出包含 “sshd” 的行,但这仍然会输出太多文本。

grep sshd log.txt

我们注意到,那些包含“用户名”的行通常有 “disconnected from” 这样的信息。因此,我们可以进一步筛选。

grep "disconnected from" log.txt

这样,输出的行数减少了,并且每行都包含了用户名信息。然而,每行开头仍然有很多我们不需要的元数据(如日期、主机名、进程ID等)。


使用 sed 进行流编辑

为了去除每行开头的冗余信息,我们使用一个名为 sed 的工具。sed 是一个流编辑器,允许你编写命令来逐行编辑文本。

以下命令使用 sed 的替换功能,删除每行中 “disconnected from” 之前的所有内容。

sed 's/.*disconnected from //'

命令解释

  • s 代表替换(substitute)。
  • 模式 .*disconnected from 会匹配 “disconnected from” 之前的任何字符串。
  • 替换部分为空,意味着删除匹配到的内容。

这样,我们就得到了以用户名开头的更简洁的行。但行末尾仍然有 IP 地址、端口号等信息。


深入正则表达式

sed 中使用的模式被称为正则表达式。它是一种强大的文本匹配语言。以下是一些基础概念:

  • . (点):匹配任意单个字符。
  • * (星号):匹配零个或多个前面的模式。
  • + (加号):匹配一个或多个前面的模式。
  • [] (方括号):匹配括号内的任意一个字符。例如 [abc] 匹配 a、b 或 c。
  • | (竖线):表示“或”。例如 (pattern1|pattern2) 匹配 pattern1 或 pattern2。
  • ^ (脱字符):匹配行的开头
  • $ (美元符):匹配行的结尾

sed 默认使用一个较老的正则表达式版本。为了使用更现代的特性(如避免“贪婪匹配”),我们有时会使用 perl 命令的 -p 选项,它支持非贪婪操作符 ?

然而,sed 几乎在所有 Unix 系统上都默认安装,因此更为通用。为了处理行尾的 IP 地址和端口,我们构建一个更复杂的 sed 命令。

sed -E 's/^.*disconnected from (invalid |authenticating )?user .* [^ ]+ port [0-9]+( \[preauth\])?$//'

这个模式逐步匹配了:

  1. 行首到 “disconnected from”。
  2. 可选的 “invalid ” 或 “authenticating ”。
  3. “user ” 字符串。
  4. 用户名(任意非空字符串)。
  5. IP地址(任意非空格字符串)。
  6. “ port ” 后跟端口号(数字)。
  7. 可选的 “ [preauth]” 字符串。
  8. 行尾。

但这样会把整行都替换为空,我们只想保留用户名。


使用捕获组

正则表达式允许使用捕获组来保留匹配到的部分内容。将模式的一部分用括号 () 括起来,就可以在替换时通过 \1\2 等来引用它们。

我们修改命令,将用户名部分用括号捕获,并在替换时只保留它。

sed -E 's/^.*disconnected from (invalid |authenticating )?user (.*) [^ ]+ port [0-9]+( \[preauth\])?$/\2/'

命令解释

  • (invalid |authenticating )? 是第一个捕获组 \1
  • (.*) 是第二个捕获组 \2,即我们想要的用户名。
  • ( \[preauth\])? 是第三个捕获组 \3
  • 替换为 \2,意味着整行只被替换为捕获到的用户名。

现在,我们成功地从杂乱的日志行中提取出了纯净的用户名列表。


调试正则表达式

编写复杂的正则表达式可能很困难。在线工具如 regex101.com 非常有用,它可以可视化解释你的模式,高亮显示匹配部分和捕获组,帮助你调试。


对结果进行统计

现在我们有了用户名列表,可以进行一些有趣的分析。例如,统计每个用户名出现的频率。

首先,使用 sort 对用户名排序,使相同的用户名排列在一起。

sort

然后,使用 uniq -c 统计每个连续重复行的数量。

uniq -c

输出结果显示了用户名及其出现次数。为了查看最常见的用户名,我们可以按计数降序排列。

sort -nk1,1

命令解释

  • -n 表示按数值排序。
  • -k1,1 表示仅按第一列(计数)排序。

使用 tail 可以查看最后10行(即出现次数最多的10个用户名)。

tail -10

使用 awk 处理字段数据

awk 是另一个强大的文本处理工具,特别擅长处理按列(字段)组织的数据。默认情况下,awk 以空白字符(空格、制表符)作为字段分隔符。

例如,从 uniq -c 的输出中,如果我们只想提取用户名(第二列),可以这样做:

awk '{print $2}'

变量解释

  • $0 代表整行。
  • $1 代表第一个字段(计数)。
  • $2 代表第二个字段(用户名)。

awk 本身就是一个完整的编程语言。你可以在其中添加条件判断。例如,找出所有只出现一次且以 ‘c’ 开头、以 ‘e’ 结尾的用户名:

awk '$1 == 1 && $2 ~ /^c.*e$/ {print $2}'

你还可以在 awk 中执行更复杂的计算,例如对满足条件的行进行求和。

awk 'BEGIN {rows=0} $1 != 1 && $2 ~ /^c/ {rows+=$1} END {print rows}'

结构解释

  • BEGIN 块在处理任何行之前执行,用于初始化变量。
  • 中间的模式-动作对 $1 != 1 && $2 ~ /^c/ {rows+=$1} 对满足条件的行,将其计数加到 rows 变量中。
  • END 块在处理完所有行后执行,用于输出最终结果。


其他实用工具

  • paste:将多行输入合并为一行,用指定分隔符连接。例如,用加号连接数字后传给计算器 bc 求和。
    paste -sd+ | bc
    
  • R 语言:可以通过命令行模式进行快速的统计分析。例如,计算一列数字的摘要统计信息。
    R -q -e 'x <- scan(file="stdin", quiet=TRUE); summary(x)'
    
  • gnuplot:一个简单的命令行绘图工具,适合快速可视化。
    gnuplot -p -e 'set terminal dumb; plot "/dev/stdin" using 1:2 with boxes'
    
  • xargs:将标准输入的行转换为后面命令的参数。例如,批量删除多个工具链版本。
    xargs rustup toolchain uninstall
    


处理非文本数据

数据整理不仅限于纯文本。对于结构化数据,有更合适的工具:

  • HTML:使用 pup,它支持 CSS 选择器来提取元素。
    curl -s https://example.com | pup 'table .title a attr{href}'
    
  • JSON:使用 jq,它可以灵活地查询和提取 JSON 数据。
    curl -s https://api.example.com/data.json | jq '.[].attribute'
    
  • CSV:可以使用 awk 并指定逗号作为分隔符 -F, 来处理。

总结

本节课中我们一起学习了命令行数据整理的核心技能。我们从杂乱的系统日志开始,逐步使用 grepsed(及其正则表达式)、sortuniqawk 等工具,最终提取出有价值的用户名频率信息。我们还介绍了 pastexargs 等辅助工具,以及处理 JSON、HTML 等结构化数据的专用工具。

数据整理的魅力在于,通过组合这些简单而强大的工具,你可以将任何原始数据流转换为清晰、有用的格式,从而自动化完成许多原本繁琐的手动任务。掌握这些技能将极大提升你在命令行环境下的工作效率。

005:文本编辑器 📝

在本节课中,我们将要学习文本编辑器和版本控制。作为程序员,我们大部分时间都在与纯文本文件打交道,因此选择一个趁手的文本编辑器并熟练掌握它至关重要。我们将重点介绍一款功能强大的命令行编辑器 Vim,讲解其背后的设计哲学、基本操作以及一些高级功能,帮助你找到最适合自己的工具。

编辑器的重要性与选择

作为程序员,我们大部分时间都在与纯文本文件交互。在所有工具中,文本编辑器是你将花费最多时间的工具,因此值得投入时间去寻找最适合你的编辑器,并深入学习它。

以下是选择和学习编辑器的一些建议:

  • 尝试不同编辑器:人们对此有不同看法,值得找到一个符合你需求的编辑器。
  • 深入学习:投入时间学习并自定义你的编辑器,让它完全按你的意愿工作。
  • 强制使用:学习新编辑器的最佳方式是强迫自己在几周内用它完成所有工作。起初效率可能略有下降,但几周后就会开始受益。

在本课程中,我们将教授 Vim 的基础知识,这是一款功能强大且常用的文本编辑器。我们鼓励你尝试其他选项。我们选择教授 Vim 是因为讲师们都使用它。不同的人有不同的偏好,课程笔记中甚至链接了关于“编辑器战争”的讨论。

在接下来的 50 分钟里,我们将介绍 Vim 的基础知识。我们无法在这么短的时间内教会你如何使用这款强大的编辑器,因此重点在于教授基础、展示高级功能、阐释其设计哲学,并提供资源链接,以便你自行深入学习。

文本编辑器的分类

文本编辑器主要分为两类:具有图形界面的编辑器(如 Atom 或 Sublime Text)和命令行文本编辑器。

即使你最终决定使用图形界面编辑器,也值得学习一些命令行编辑器的基础知识。这在远程编辑文件时非常有用,例如,当你需要 SSH 登录到远程机器并直接在终端中编辑文件时,这比来回复制文件或使用更复杂的技术要方便得多。

在深入 Vim 之前,我们先看一个不同的文本编辑器:Nano。

基础命令行编辑器:Nano

你可以通过 nano 命令启动 Nano,并指定要编辑的文件名。例如:

nano lecture_notes.txt

Nano 的界面底部始终显示操作指令。你可以使用方向键移动光标,并直接输入文本。

以下是需要记住的几个关键组合:

  • Ctrl + O:写入文件(保存)。编辑器会询问文件名,默认是打开的文件名。
  • Ctrl + X:关闭编辑器。

Nano 是一个非常简单的文本编辑器,预装在大多数系统上。当你登录到没有安装其他编辑器的远程机器并需要做小修改时,它非常方便。但它不适合编写复杂的程序,因为它缺乏高级功能支持。

那么,你应该使用什么呢?接下来,我们将学习功能更强大的 Vim。

Vim 的设计哲学

Vim 是一个功能复杂的工具,背后有许多巧妙的设计理念。

核心理念 1:模态编辑
编程时,我们大部分时间不是在输入字符,而是在移动、阅读和操作文本。因此,Vim 是一个模态编辑器,拥有不同的模式来应对不同的任务:

  • 插入模式:用于输入文本。
  • 普通模式:用于在文件中移动和操作文本。由于编程时大部分时间在阅读和操作代码,你将在普通模式中度过大部分时间。

核心理念 2:可编程性
Vim 内置了一门叫做 Vimscript 的编程语言。你可以通过编写配置或插件来高度自定义编辑器。它也可以通过其他语言(如 Python、Ruby)的绑定进行编程。

核心理念 3:类似编程语言的界面
在普通模式下,你通过一系列命令(通常是单键击)来移动光标和修改文件。这些命令具有助记名称,并且可以组合。例如:

  • w 是一个移动命令,向前移动一个单词。
  • c 是一个编辑命令,表示“修改”。
  • 组合 cw 意味着“修改单词”,它会删除当前单词并进入插入模式让你输入新词。
  • 组合 c$ 意味着“修改到行尾”,它会删除从光标到行尾的内容并进入插入模式。

这种组合性使得 Vim 非常强大。

其他重要理念

  • 避免使用鼠标:鼠标不够精确且速度慢。Vim 鼓励使用键盘,通过肌肉记忆实现快速导航和操作。
  • 速度匹配思维:编辑器应该以你思考的速度工作,让你想到哪里,光标就能立刻到哪里。

Vim 基础:模式与切换

Vim 是一个模态编辑器。你当前所处的模式会显示在编辑器左下角。

主要模式:

  • 普通模式:用于移动和操作。左下角无显示或显示“正常”。
  • 插入模式:用于输入文本。显示“插入”。
  • 可视模式:用于选择文本块。有多种变体。

模式切换:

  • 从普通模式进入插入模式:按 i
  • 从普通模式进入可视模式:按 v(字符选择)、V(行选择)、Ctrl+v(块选择)。
  • 从其他模式返回普通模式:按 Esc 键。

关于 Esc 键的提示:
标准键盘的 Esc 键位置不便。在 Vim 中,你经常需要在模式间切换,因此将 Caps Lock 键映射为 Esc 是一个常见做法(例如,在 Mac 系统偏好设置的键盘->修饰键中修改)。

基本 Vim 操作:保存、退出与帮助

在普通模式下,按 : 进入“命令模式”,光标会跳到底部,可以输入命令。

基本命令:

  • :q – 退出(quit)。
  • :w – 保存(write)。
  • :wq:x – 保存并退出。
  • :e <文件名> – 编辑另一个文件。
  • :ls – 列出所有打开的缓冲区。
  • :bn – 切换到下一个缓冲区。

帮助系统:
Vim 拥有强大的内置文档。使用 :help <主题> 来查看帮助。例如,:help w 会告诉你 w 命令的作用。

在文件中移动

虽然可以使用方向键,但 Vim 提供了更高效的移动命令。建议初学者禁用方向键以培养好习惯。可以将以下配置加入 ~/.vimrc 文件:

noremap <Left> :echo "Use h!"<CR>
noremap <Right> :echo "Use l!"<CR>
noremap <Up> :echo "Use k!"<CR>
noremap <Down> :echo "Use j!"<CR>

基本移动(HJKL):

  • h – 左移
  • j – 下移
  • k – 上移
  • l – 右移

按单词移动:

  • w – 移动到下一个单词开头。
  • b – 移动到上一个单词开头。
  • e – 移动到当前单词末尾。

行内移动:

  • 0 – 移动到行首。
  • ^ – 移动到行首第一个非空白字符。
  • $ – 移动到行尾。

屏幕与文件内移动:

  • H – 移动到屏幕顶部。
  • M – 移动到屏幕中部。
  • L – 移动到屏幕底部。
  • Ctrl+d – 向下翻半页。
  • Ctrl+u – 向上翻半页。
  • gg – 移动到文件开头。
  • G – 移动到文件末尾。
  • :<行号>:<行号>G – 跳转到指定行。

高级移动:

  • % – 在配对的括号、引号间跳转。
  • f<字符> – 向右查找并跳转到下一个指定字符。
  • F<字符> – 向左查找并跳转到上一个指定字符。
  • t<字符> – 向右跳转到指定字符前。
  • T<字符> – 向左跳转到指定字符前。

使用数字重复动作:
在命令前加数字可以重复该动作。例如:

  • 5j – 向下移动 5 行。
  • 3w – 向前移动 3 个单词。

搜索移动:

  • / – 进入搜索模式,输入文本后按回车跳转到下一个匹配项。
  • n – 跳转到下一个匹配项。
  • N – 跳转到上一个匹配项。

文本选择(可视模式)

可视模式用于选择文本块,然后进行操作。

进入可视模式:

  • v – 进入字符选择可视模式。
  • V – 进入行选择可视模式。
  • Ctrl+v – 进入块选择可视模式。

在可视模式下,可以使用所有之前学过的移动命令(如 w, j, $)来调整选择范围。按 Esc 可以取消选择并返回普通模式。

文本操作与编辑

基本的编辑流程是:在普通模式下移动光标到目标位置,进入插入模式(i)进行修改,然后按 Esc 返回普通模式。

更高效的操作命令:
这些命令可以与移动命令组合。

删除命令 d (delete):

  • dw – 删除一个单词(从光标到单词末尾)。
  • d$ – 删除到行尾。
  • d0 – 删除到行首。
  • d3j – 向下删除 3 行。
  • d3w – 删除 3 个单词。

修改命令 c (change):
c 命令会删除指定文本并进入插入模式。

  • cw – 修改单词(等同于 dw + i)。
  • c$ – 修改到行尾。
  • c3w – 修改 3 个单词。

其他有用命令:

  • x – 删除光标下的字符。
  • s – 替换光标下的字符并进入插入模式(等同于 x + i)。
  • r<字符> – 替换光标下的字符(不进入插入模式)。
  • ~ – 切换光标下字符的大小写。
  • o – 在当前行下方插入新行并进入插入模式。
  • O – 在当前行上方插入新行并进入插入模式。

在可视模式下操作:
先进入可视模式选择文本,然后按 d 删除或 c 修改。

撤销与重做:

  • u – 撤销上一次更改。
  • Ctrl+r – 重做被撤销的更改。

如何学习 Vim

记住所有命令可能令人望而生畏,但通过练习它们会变成肌肉记忆。

学习资源:

  • Vim Tutor:在终端输入 vimtutor 即可启动一个交互式教程,它会带你从基础学到进阶操作。
  • Vim Adventures:一个在线的游戏化学习网站 (vim-adventures.com),通过游戏教你 Vim 命令,非常有趣。

自定义你的 Vim:.vimrc 文件

Vim 通过用户主目录下的 ~/.vimrc 文件进行配置。你可以在此添加设置来改变编辑器的行为和外观。

一些有用的基础设置:

set number          " 显示行号
set incsearch       " 输入搜索内容时实时高亮匹配
set hlsearch        " 高亮所有搜索匹配项
syntax on           " 开启语法高亮
set backspace=indent,eol,start  " 允许在插入模式下用退格键删除

互联网上有大量关于 Vim 配置的技巧和文章。你也可以在 GitHub 上查看他人分享的配置文件,从中获取灵感。

高级功能演示

Vim 的功能远不止于此。以下是一些高级功能的简要演示,旨在激发你的探索兴趣。

搜索与替换:
使用 :%s/旧文本/新文本/g 可以进行全局替换。这类似于 sed 命令。添加 c 标志(如 :%s/foo/bar/gc)可以在替换前进行确认。

分屏:
使用 :split:vsplit 可以水平或垂直分割窗口,同时查看或编辑文件的不同部分。

宏:
宏可以记录并回放一系列操作,用于自动化重复性任务。

  1. q 后跟一个寄存器字母(如 q)开始录制。
  2. 执行你的操作序列。
  3. q 停止录制。
  4. @ 后跟寄存器字母(如 @)回放宏。
  5. 使用数字前缀(如 1000@)可以重复执行宏多次,直到出错为止。

讲师现场演示了如何使用宏将一个简单的 XML 数据片段快速转换为 JSON 格式,这展示了 Vim 强大的文本处理能力。

插件:
Vim 拥有丰富的插件生态系统,可以实现诸如可视化撤销历史树等高级功能。值得在熟悉基础后深入探索。


本节课中我们一起学习了文本编辑器的重要性,重点探讨了 Vim 这款强大的模态编辑器。我们从其设计哲学入手,学习了模式切换、基本导航、文本操作以及如何通过 .vimrc 文件进行自定义。最后,我们还预览了搜索替换、分屏和宏等高级功能。掌握一个高效的文本编辑器需要时间和练习,但这项投资将极大地提升你的编程生产力。希望本课能激发你深入学习 Vim 或其他编辑器的兴趣。

006:版本控制 📚

在本节课中,我们将要学习版本控制系统,特别是Git。版本控制系统能帮助我们追踪文件随时间的变化,允许我们撤销错误、查看历史记录,并与他人协作。我们将从Git的核心概念开始,逐步了解其基本操作和工作原理。

概述

版本控制系统是一种用于管理源代码、文本文件、配置文件等文件的工具。它能维护这些文件变化的持久历史记录。当你需要追踪随时间的变化时,版本控制系统是一个理想的起点。此外,它还能在不同用户之间共享变更,允许多人协作同一个项目。

市面上有许多版本控制系统,它们的工作方式、功能、易用性和适用场景各不相同。本讲座将重点介绍一个名为Git的工具,它是目前较为常见的版本控制系统之一,支持多用户、多机器协作,并且得到了在线平台(如GitHub)的良好支持。虽然我们会聚焦于Git,但其中的许多概念也适用于其他系统。

Git的数据模型

理解Git的关键在于理解其数据模型,即它如何存储文件及其历史。一旦理解了数据模型,许多操作命令背后的逻辑就会变得清晰。

提交(Commit)

Git的核心是提交。一个提交本质上是某个时间点下,你所关注的文件夹状态的快照。当你使用版本控制系统时,它通常基于某个文件夹及其所有子内容。提交就是这个文件夹在某个时间点的“冻结”版本。

每个Git提交都有一个唯一的名称,称为哈希值。它看起来像这样:522e6...。你捕获的每一个快照(即每个提交)都有这样一个哈希值,并且保证在给定的仓库中是唯一的。

提交还包含其他信息:

  • 作者:谁创建了这个快照。
  • 提交信息:描述这个快照的原因。
  • 父提交哈希:指向之前的提交,从而形成一个时间线,展示随时间发生的变化以及变更之间的依赖关系。

提交代表了自上一个提交以来所做的更改。你可以将每个快照视为捕获了从一个时间点到另一个时间点的变化。

实际上,Git存储的是完整的快照,而不仅仅是差异。但你可以将每个提交视为对前一个状态应用了一组更改,这就是为什么它需要记住前一个提交。

基本操作

上一节我们介绍了Git的核心数据模型——提交。本节中我们来看看如何使用Git进行基本的版本控制操作。

初始化仓库

首先,你需要一个Git仓库来开始追踪文件。仓库是Git存储其管理的文件夹所有信息的地方。

使用 git init 命令在指定文件夹中创建一个新的Git仓库。如果文件夹不存在,它会被创建。这个命令会在当前目录下创建一个名为 .git 的隐藏文件夹,用于存储Git追踪历史所需的所有信息。

初始化后,你的命令行提示符可能会显示版本控制状态信息(如果已配置),表明当前文件夹已处于版本控制之下。

状态与暂存

使用 git status 命令可以查看Git认为的当前目录状态。它会告诉你很多信息,例如当前所在的分支、提交历史以及哪些更改已准备就绪。

一个关键概念是暂存区。你并不总是希望将所有更改一次性提交。有时你可能只想提交部分文件,或者将一个文件的更改分多次提交。git add <文件名> 命令可以将特定文件的更改暂存,即标记为要包含在下一次提交中。

你可以使用 git diff 查看尚未暂存的更改,使用 git diff --staged 查看已暂存的更改。

创建提交

当你准备好将暂存的更改永久记录时,使用 git commit 命令。这会创建一个新的提交(快照)。Git会打开一个编辑器,让你输入提交信息来描述这次更改的原因。

你也可以使用 git commit -m "提交信息" 来快速提交,而无需打开编辑器。

查看历史

使用 git log 可以查看所有已做出的提交历史。git log --oneline 会提供更简洁的摘要视图。要查看特定提交的详细更改内容,可以使用 git show <提交哈希>git show -p(显示补丁)。

撤销与回退

Git提供了多种方式来撤销更改:

  • git revert <提交哈希>:创建一个新的提交,该提交会撤销指定提交引入的更改。这是一种安全的撤销方式,因为它保留了历史记录。
  • git reset:这个命令有两种主要用法:
    • git reset <提交哈希>:将当前分支的指针移动到指定的历史提交,但默认不改变工作目录中的文件。这常用于“撤销”尚未推送的提交。
    • git reset <文件名>:将已暂存的文件更改移出暂存区。

分支与切换

分支是Git中一个强大的功能,它允许你在独立的时间线上开发。

  • git branch <分支名>:创建一个指向当前提交的新分支。
  • git checkout <分支名>:切换到指定的分支。
  • git checkout -:切换到上一个分支,方便快速来回切换。

默认分支通常叫 master。当你处于某个分支并创建提交时,只有该分支的指针会向前移动。

高级概念与协作

到目前为止,我们学习了如何在本地使用Git。接下来,我们将探讨如何重写历史以及如何与他人协作。

修改历史

有时提交历史会变得杂乱,包含许多微小的修复或尝试。Git允许你整理历史,使其更清晰。

  • git commit --amend:修改最新的提交。你可以添加漏掉的文件或修改提交信息。
  • git rebase -i:交互式变基。这是一个强大的工具,允许你重新排序、合并(squash)、修改(reword)或删除一系列提交。这在向开源项目提交代码前清理个人工作历史时非常有用。

远程仓库与协作

为了与他人协作,Git引入了远程仓库的概念。远程仓库是托管在别处(如GitHub)的同一仓库的副本。

  • git remote -v:查看已配置的远程仓库。
  • git fetch <远程名>:从远程仓库下载最新的提交和分支信息,但不会自动合并到你的工作。
  • git pull:相当于 git fetch 后接 git merge,即获取远程更改并合并到当前分支。
  • git push <远程名> <分支名>:将你的本地提交上传到远程仓库,并更新远程仓库的分支指针。

合并与冲突

当多人在同一文件的不同部分或同一部分进行修改时,就需要合并。

  • git merge <分支名>:将指定分支的更改合并到当前分支。Git会尝试自动合并。如果成功,它会创建一个新的“合并提交”。
  • 合并冲突:如果Git无法自动合并(例如,两人修改了同一行代码),就会产生冲突。你需要手动编辑文件来解决冲突(文件中会有 <<<<<<<=======>>>>>>> 标记出冲突部分),然后使用 git add 标记冲突已解决,最后完成合并提交。

git pull 本质上就是先获取远程更改,然后尝试将其合并到你的本地分支。

总结

本节课中我们一起学习了版本控制系统Git。我们从其核心数据模型——提交和哈希开始,了解了如何初始化仓库、暂存更改、创建提交以及查看历史。接着,我们探讨了分支管理、撤销操作以及如何修改提交历史。最后,我们介绍了远程协作的基本流程,包括获取、拉取、推送代码以及如何处理合并冲突。

希望你能理解,Git的核心在于通过哈希值唯一标识提交,并通过分支名、标签名等引用这些提交。将“名称”和“哈希值”视为独立的实体来思考,是理解Git许多操作的关键。虽然Git命令繁多,但掌握这些基本概念将帮助你更自信地使用这个强大的工具。请务必查阅课程笔记中的进一步阅读链接和交互式教程,以加深理解。

007:配置文件 📁

在本节课中,我们将要学习配置文件(通常称为“点文件”),了解它们是什么、为什么重要,以及如何有效地组织和管理它们,以便在不同的计算机上保持一致的开发环境。


什么是点文件? 🤔

上一节我们介绍了文本编辑器,并查看了.vimrc文件。许多命令行工具都可以通过纯文本文件进行高度配置,这些文件被称为“点文件”。之所以这样称呼,是因为它们的文件名通常以一个点(.)开头,例如 .vimrc.bashrc

点文件默认是隐藏的。在Unix/Linux系统中,以点开头的文件在普通的目录列表(ls命令)中不会显示。这是早期ls工具实现的一个历史遗留特性,后来被用户用来存放不希望总是看到的配置文件。

为什么需要自定义工具? ⚙️

我们认为,投入时间定制工具,使其完全按照你的期望工作,是完全值得的。不同的工具有不同的配置方式:

  • 通过编程语言:例如,你的Shell(如Bash)在启动时会读取并执行像.bashrc这样的脚本文件。Vim则通过Vim脚本语言进行配置。
  • 通过特定文件格式:例如,Git使用.gitconfig文件,你可以在其中设置各种键值对。

虽然有些人会直接下载并使用他人的配置文件,但我们不建议这样做。你应该根据自己的需求来定制工具。不过,查看他人的配置是了解可用选项的好方法。

以下是探索和自定义工具配置的几种途径:

  • 在线搜索特定工具的教程(例如搜索“axel rc”)。
  • 查阅工具的man手册页(例如 man bash)。
  • 在GitHub等平台上浏览他人公开的“dotfiles”仓库,学习他们的配置方法。

如何组织你的点文件? 🗂️

现在,我们来看看如何组织这些文件。当你在一台机器上精心配置了环境后,你肯定希望在其他机器(如实验室电脑或服务器)上也能轻松复制相同的设置。

组织点文件的目标主要有三个:

  1. 易于安装:在新机器上,应该能通过简单的步骤(如克隆仓库并运行一个脚本)快速完成环境配置。
  2. 可移植性:配置应能在不同机器间轻松同步和保持一致。
  3. 变更追踪:配置的调整是一个长期过程,使用版本控制系统(如Git)来管理点文件,可以方便地查看历史、回滚更改。

一个推荐的组织方案

一个有效的方法是:创建一个单独的目录(例如 ~/dotfiles)来存放所有配置文件,并将这个目录置于版本控制之下(例如Git仓库)。

但这里有一个问题:许多程序期望它们的配置文件位于用户主目录的特定位置(如 ~/.bashrc),而不是我们集中管理的 ~/dotfiles 目录里。

解决方案是使用符号链接(Symbolic Links)。符号链接类似于快捷方式,它指向另一个文件的实际位置。

例如,你可以将 ~/.bashrc 创建为一个符号链接,指向你版本库中的实际文件 ~/dotfiles/bashrc

ln -s ~/dotfiles/bashrc ~/.bashrc

这样,当你编辑 ~/dotfiles/bashrc 时,Shell读取的 ~/.bashrc 内容也会同步更新。

自动化安装脚本

为了简化在多台机器上的部署过程,可以编写一个安装脚本(例如 install.sh)。这个脚本的核心任务就是为 ~/dotfiles 目录中的每个配置文件创建指向正确位置的符号链接。

一个简单的脚本示例如下:

#!/bin/bash
cd $(dirname "$0") # 切换到脚本所在目录(即dotfiles目录)

ln -s $PWD/bashrc ~/.bashrc
ln -s $PWD/vimrc ~/.vimrc
# ... 为其他文件创建更多链接

之后,在新机器上设置环境只需三步:

  1. git clone <你的dotfiles仓库地址>
  2. cd dotfiles
  3. ./install.sh

我们提供的简单脚本在链接已存在时会报错。在实际应用中,你可以使用更健壮的工具(如 GNU Stow 或一些专用的点文件管理器),课程笔记中提供了相关链接。此外,讲师们(Anish, John, Jose)的配置文件仓库也作为实例供参考。


处理机器特定的配置 🖥️

当你需要在不同机器上使用略有不同的配置时(例如,笔记本和服务器硬件不同),有几种方法可以处理:

  1. 在配置文件中使用条件判断:由于许多点文件本身就是脚本(如 .bashrc),你可以在其中加入条件逻辑。
    # 在 .bashrc 中
    if [[ "$(hostname)" == "my-laptop" ]]; then
        # 笔记本电脑特定的设置
        export BATTERY_ALERT=true
    fi
    
  2. 使用包含指令:许多工具支持从其他文件读取配置。例如,在Git配置中,你可以有一个通用的 ~/.gitconfig,然后在末尾包含一个机器特定的文件。
    # ~/.gitconfig (通用部分)
    [user]
        name = John Doe
    [include]
        path = ~/.gitconfig.local # 机器特定的配置放在这里
    


总结 📝

本节课中我们一起学习了:

  • 点文件是什么,以及它们因历史原因而隐藏的特性。
  • 自定义工具以适应个人工作流程的重要性。
  • 如何通过集中存储版本控制符号链接来有效地组织点文件,实现跨机器的环境同步。
  • 如何利用条件判断包含文件的机制来处理不同机器间的细微配置差异。

建立一个良好组织的点文件系统,是对你长期开发效率的一项宝贵投资。

008:数据备份 💾

在本节课中,我们将要学习数据备份的核心概念、最佳实践以及常用工具。数据丢失可能随时发生,因此建立可靠的备份策略至关重要。

概述

数据备份是保护重要信息免受意外丢失的关键防御措施。许多人直到经历严重的数据丢失后才意识到备份的重要性。本节将介绍推荐的备份指导原则、常见陷阱以及如何构建一个健壮的备份系统。

备份的核心理念:3-2-1 原则

上一节我们介绍了数据备份的重要性,本节中我们来看看一个被广泛认可的备份策略核心原则:3-2-1 原则。

这个原则建议你应当:

  • 拥有 3 份数据副本
  • 使用 2 种不同的存储介质
  • 确保其中 1 份副本存放在异地

这个原则的核心思想是“不要把所有鸡蛋放在同一个篮子里”。例如,如果你的主数据在电脑硬盘上,那么备份不应该只是同一块硬盘上的另一个分区。如果电脑被盗或损坏,两个副本会同时丢失。你需要使用不同的物理设备,或者使用云存储服务。

异地备份至关重要。你可以将备份硬盘放在房间的抽屉里,但如果发生火灾等灾害,你仍然会失去所有数据。因此,至少有一份副本需要存放在物理上分离的地点。

通常的建议是保留一份本地副本,便于快速访问和恢复,因为从互联网下载大量数据可能非常耗时或昂贵。同时,再保留一份异地副本,以防本地发生灾难。

备份的常见陷阱与注意事项

理解了基本原则后,我们还需要注意实践中常见的错误。仅仅设置了备份流程并不足够。

以下是两个关键的注意事项:

  1. 必须定期测试备份的有效性。仅仅运行备份脚本并看到它输出文件列表是不够的。可能存在权限问题、配置错误或数据损坏,导致备份实际上无法恢复。历史上曾有著名电影因备份配置错误而几乎永久丢失的案例。正确的方法是定期尝试从备份中实际恢复数据,以验证其完整性。
  2. 镜像不等于备份。简单地创建数据的实时同步副本(镜像)不能替代备份。这是因为如果源数据被意外删除、损坏或被恶意软件加密,这些变更会立刻同步到“备份”端,导致你同样失去数据。真正的备份需要能够保留数据在过去某个时间点的状态,即版本快照功能。

许多云服务如 Dropbox 和 Google Drive 提供了一定的版本历史功能,但它们通常只保留有限时间(例如30天)。你需要使用能够长期保留多个版本的工具。

高效备份技术:去重与快照

如果每次备份都完整复制所有文件,存储成本会非常高昂,并且会产生大量冗余数据。

为了解决这个问题,可以采用以下技术:

  • 快照:记录文件系统在某个时间点的状态,而不是持续同步变化。
  • 数据去重:识别并只存储不同版本之间变化的部分,而不是重复存储未更改的数据。

例如,假设:

  • 时间点1,你有文件 AB
  • 时间点2,你有文件 BC

高效的备份系统不会存储两份完整的 B,而是存储 ABC 各一份,然后让时间点1的快照指向 AB,时间点2的快照指向 BC。这样,B 只被存储了一次。

这种基于指针(类似符号链接)的方法,在删除某个快照时,只要数据块还被其他快照引用,就不会被删除,非常高效。

备份安全:客户端加密

将数据备份到第三方云服务时,隐私是一个重要考量。你的税务文件、护照信息等敏感数据可能不希望被服务提供商直接访问。

解决方案是客户端加密。其核心流程可以用以下伪代码表示:

# 在客户端进行加密后再上传
encrypted_data = encrypt(raw_data, client_secret_key)
upload_to_cloud(encrypted_data)

# 从云端下载后,在客户端解密
downloaded_data = download_from_cloud()
raw_data = decrypt(downloaded_data, client_secret_key)

这样,服务器上存储的只是加密后的密文。没有客户端的密钥,服务商无法读取你的任何原始数据。这种加密方式可以与上述的快照和去重技术兼容。

数据分类与系统备份

并非所有数据都需要相同的备份策略。你可以根据数据的重要性和敏感性进行分类:

  • 关键数据:如文档、密码、密钥、财务记录,需要强加密和多重备份。
  • 大型媒体文件:如照片、视频,可能只需要一份或两份副本,并可能使用不同的存储服务(如 Google Photos)。
  • 系统备份:有些工具可以创建整个系统的磁盘映像。这样当硬盘故障时,你可以快速恢复整个工作环境,而无需重新安装所有软件。

云端服务数据的备份

现代生活中,大量数据存储在第三方网络服务中(如邮件、社交媒体、笔记应用)。这些数据同样面临丢失风险(例如服务商关闭或发生故障)。

你需要考虑如何备份这些数据:

  • 利用导出功能:许多服务(如 GitHub, 某些笔记应用)提供将数据导出为标准格式(如 JSON、CSV)的功能。定期执行此操作并将导出文件纳入你的本地备份计划。
  • 网页存档:对于你关心的网页内容,可以使用 Wayback Machine(互联网档案馆的项目)。你可以提交网址,它会保存页面的快照。你也可以用它来查看已消失网页的历史版本。

备份工具讨论

在课堂讨论中,师生们提到了多种备份工具及其适用场景:

  • BorgBackup:一个功能强大的开源去重备份工具。支持加密、压缩、去重,并可以备份到远程服务器。适合备份代码、文档等非大型媒体文件。
  • Restic:另一个类似 Borg 的开源工具,同样支持加密、去重和快照。你需要自己提供后端存储位置(如自己的服务器、云存储桶)。
  • rsync:一个高效的远程文件同步工具。它可以高效地只传输变化的文件,但本身不提供快照和去重功能,需要额外脚本管理版本。
  • rclone:一个命令行程序,用于同步文件和目录到多种云存储服务(如 Google Drive, Dropbox, S3)。它充当了云存储的“通用接口”。
  • Time Machine (macOS):苹果系统内置的备份解决方案。它提供了完整的系统时间点快照功能,使用方便。但通常只能备份到单个目标磁盘,且历史上有时存在可靠性问题。
  • 云存储同步(如 Dropbox):这更像是一种实时镜像而非严格意义上的备份。虽然它们提供有限的版本历史,但如果账户被入侵或文件被恶意加密,风险会蔓延。将其作为备份方案的一部分时,需要结合强密码、双因素认证和客户端加密来增强安全性。

安全与备份的交叉考量

备份策略与安全紧密相关。一个关键问题是:如果你的笔记本电脑被盗,攻击者能否删除你的所有备份?

  • 如果你使用 Dropbox 且浏览器已登录,攻击者有可能通过你的电脑删除云端文件。
  • 使用 Borg/Restic 等工具,配合仅支持追加操作的加密仓库,即使攻击者拿到你的电脑,没有密钥也无法删除已有的备份快照。

这引出了更深层的安全实践:

  • 全盘加密:为笔记本电脑硬盘启用全盘加密(如 macOS FileVault, Linux LUKS),防止物理接触设备时数据被直接读取。
  • 密码管理器:使用密码管理器(如 KeePassXC, 1Password)生成并存储高强度、唯一的密码,避免因一个服务密码泄露导致备份账户被入侵。

总结

本节课中我们一起学习了构建可靠数据备份系统的完整知识:

  1. 遵循 3-2-1 原则(3份副本,2种介质,1份异地)是基础。
  2. 定期测试恢复流程和区分镜像与版本化备份至关重要。
  3. 采用快照数据去重技术可以高效管理存储空间。
  4. 使用客户端加密保护备份数据的隐私,即使使用第三方云服务。
  5. 对数据进行分类,并为云端服务中的数据制定专门的备份计划。
  6. 根据需求选择合适工具(如 Borg, Restic, rsync),并始终将备份策略与安全实践(如全盘加密、密码管理)结合考虑。

最终,备份的复杂程度取决于你对数据丢失风险的承受能力以及对第三方服务商的信任程度。最简单的备份就是将一切镜像到云端,但最安全的方案则需要更精心的设计和权衡。

009:自动化 🕒

在本节课中,我们将学习如何使用自动化工具来简化日常任务。我们将重点介绍 cron 这个强大的系统守护进程,它允许你按计划自动运行命令或脚本。通过本教程,你将学会如何配置 cron 任务,理解其语法,并了解一些最佳实践和替代方案。


什么是 cron

上一节我们介绍了自动化的概念,本节中我们来看看 cron 这个工具。cron 是一个在 Unix 类系统中持续运行的后台守护进程。它的主要功能是根据预定的时间表自动执行任务。你可以通过检查其状态来确认它是否正在运行。

要查看当前用户的 cron 任务,可以使用命令 crontab -e。这会打开一个文件,具体内容取决于你的操作系统发行版。该文件可能为空,也可能包含类似下面的示例,它解释了 cron 的基本语法。

一个 cron 任务行包含五个字段,通常由星号或数字表示:

  • 分钟
  • 小时
  • 月份中的日期
  • 月份
  • 星期几

以下是每个字段含义的说明:

  • 星号 (*): 匹配该字段的所有可能值。
  • 数字: 匹配该特定值。例如,1 表示“在1分钟时运行”。
  • 范围: 可以使用连字符指定范围,如 9-17
  • 步长: 可以使用斜杠指定间隔,如 */5 表示“每5个单位”。

cron 的一个限制是,其最高执行频率是每分钟一次。但对于大多数应用场景来说,这已经足够了。


cron 语法示例

理解了基本结构后,我们来看一些具体的例子。以下是几个常见的 cron 时间表达式:

  • */5 * * * *: 每5分钟运行一次。
  • 2 * * * *: 每小时的第2分钟运行一次。
  • 0 9 * * *: 每天上午9点运行一次。
  • 0 9-17 * * 1-5: 每周一至周五的上午9点到下午5点之间,每小时运行一次。

时间表达式可以变得非常复杂。幸运的是,有一些在线工具可以帮助你生成正确的表达式。


测试与运行 cron 任务

现在,让我们动手测试一个 cron 任务。我们可以创建一个简单的任务,每分钟将当前日期写入一个临时文件。

首先,使用 crontab -e 编辑你的 cron 任务列表,并添加以下行:

* * * * * date >> /tmp/date.log

保存并退出后,cron 就会开始执行这个任务。等待一分钟后,你可以检查 /tmp/date.log 文件,应该能看到每分钟新增的时间戳。

使用 cron 时需要注意几个问题:

  1. 环境变量cron 运行时不会加载你的 shell 配置文件(如 .bashrc.bash_profile)。这意味着它可能不知道你的 PATH 或其他环境变量。因此,在 cron 任务中,最好使用命令或脚本的完整路径
  2. 错误排查: 如果 cron 任务失败,默认不会直接通知你。一个有效的做法是将输出重定向到日志文件,以便后续检查。例如:
    * * * * * /full/path/to/your/script.sh >> /tmp/script.log 2>&1
    

实用的 cron 应用实例

了解了基础知识后,我们可以用它来解决实际问题。直接编写复杂的命令可能难以维护。更好的做法是创建一个有意义的脚本,然后在 cron 中调用它。

例如,创建一个名为 organize_downloads.sh 的脚本,内容如下:

#!/bin/bash
# 将下载文件夹中的 .pdf 文件移动到桌面
mv ~/Downloads/*.pdf ~/Desktop/ 2>/dev/null
# 将下载文件夹中的图片移动到图片文件夹
mv ~/Downloads/*.{jpg,jpeg,png,gif} ~/Pictures/ 2>/dev/null

然后,在 cron 中设置每分钟运行一次这个脚本:

* * * * * /full/path/to/organize_downloads.sh

这样,系统就会自动帮你整理下载文件夹中的文件。


其他工具与技巧

除了 cron,还有其他有用的工具。watch 命令可以定期(默认每2秒)执行指定的命令并刷新显示输出,这对于监控非常方便。

cron 的一个缺点是,如果到了计划时间点,但计算机处于关机状态,那么该任务不会在开机后补执行。对于需要严格保证执行的任务(尤其是低频任务,如每周一次),这可能是个问题。此时可以考虑使用 anacron 工具,它设计用于在开机后执行错过周期的任务,但最高频率通常为每天一次。

此外,对于需要更精细控制或由文件系统事件触发的任务,可以考虑使用 incronsystemd 计时器。

还有一个常用技巧:如果你有多个需要每分钟运行的任务,为了避免所有任务都在同一秒启动(造成瞬间负载高峰),可以给它们设置不同的“秒”偏移。虽然 cron 的分钟字段最小单位是分钟,但你可以通过让任务在每分钟的不同秒数启动来实现错峰。例如,一些动态 DNS 更新服务就建议随机化任务启动的秒数。


本节课中我们一起学习了 cron 这个强大的自动化工具。我们了解了其基本语法和字段含义,学习了如何创建和测试任务,并探讨了使用时的注意事项(如环境变量和日志记录)。我们还看到了一些实际应用例子,并简要介绍了 watchanacron 等替代或补充工具,以及错峰执行的实用技巧。掌握 cron 能极大地提升工作效率,实现任务的自动化管理。

010:机器探查与系统自检 🔍

在本节课中,我们将学习一系列用于诊断计算机问题的工具。当系统出现故障或行为异常时,这些工具能帮助你查明原因、了解当前状态,并防止未来发生类似问题。


权限与 sudo 命令

要进行机器探查,通常需要特殊权限。内核通常不允许你随意查看系统状态,因此你通常需要属于特定用户组,例如 wheel(可以充当 root 用户的组)。其他组如 poweraudiovideooptical 等用于更特殊的目的。通常,将自己添加到 wheel 组即可。如果你对安全性有顾虑,可以仅通过 sudo 命令临时获取权限。

sudo 命令是一个前缀,你可以将其加在任何命令之前运行。它会以 root 用户的身份执行该命令,从而获得机器上几乎所有可能的权限。

例如,我可以以普通用户身份运行 datewhoami 命令:

whoami

输出会显示我是 john

如果我使用 sudo

sudo whoami

它会提示我输入密码,输入后输出会显示 root

sudo 会记住你在一段时间内的授权,但超时后会再次要求输入密码。你可以通过编辑 /etc/sudoers 文件来配置 sudo 的行为,例如禁用密码提示。使用 visudo 命令编辑此文件是安全的,因为它会在保存前检查语法。

su 命令允许你以另一个用户的身份启动终端,默认是 root 用户:

sudo su

这会给你一个 root 用户的终端提示符。


查看系统日志

当出现问题时,首先要做的是查看日志。你的机器上有许多日志文件,传统上大多位于 /var/log 目录下。

  • /var/log/Xorg.0.log:当 X 窗口系统失败时很有用。
  • 特定子系统的日志文件夹,如 cups(打印服务)。
  • 内核日志:过去在 /var/log 中,现在通常通过 dmesg 命令查看。dmesg 会显示自系统启动以来内核打印的所有消息。

你可以使用之前数据整理课程中学到的工具(如 grep)来筛选这些日志,查找特定模式。

现代系统通常使用 systemd 来管理系统服务,它有自己的日志守护进程 journald。查看日志的命令是 journalctl

journalctl 默认显示自上次启动以来的所有消息。你可以滚动查看,也可以将其输出通过管道传递给其他工具进行处理。

以下是 journalctl 的一些常用选项:

  • -u <unit>:仅显示指定系统单元(如服务)的日志。
  • -b:仅显示本次启动以来的日志。
  • -b -1:显示上一次启动的日志(用于调试导致重启的问题)。
  • -f:实时跟踪日志输出(类似 tail -f)。
  • -n 100:仅显示最后 100 行日志。

有时 journalctl 会截断过长的日志行并显示 ...,此时可以使用 --no-pager 或重定向输出来查看完整内容。


监控系统进程与资源

要了解系统正在运行什么,可以从 top 命令开始。top 显示机器上所有进程、它们的 CPU 和内存使用情况,以及一系列系统统计信息。

现在更多人使用 htop,它提供了更友好的界面,例如显示每个核心的负载、更图形化的表示,并且可以按树状结构显示进程关系(按 t 键),这有助于理解进程的启动关系。

如果只关心进程树,可以使用 pstree 命令。添加 -p 选项可以显示进程 ID(PID)。

要实时查看特定服务的日志输出,可以使用 journalctl -f -u <unit>。对于内核消息,可以使用 dmesg -w。对于普通日志文件(如 /var/log/Xorg.0.log),可以使用 tail -f

另一个监控资源使用的强大工具是 dstatdstat 监控机器的各种子系统(网络流量、磁盘 I/O、CPU 中断、上下文切换、进程数、CPU 利用率等),并实时打印信息。它有大量选项用于监控不同方面,例如 -c 监控 CPU,-d 监控磁盘,-n 监控网络。

dstat 非常适合快速了解计算机的繁忙程度以及它在忙什么。如果你运行一个程序时它似乎卡住了,打开 dstat 看看是否有任何活动。如果所有数字都是零,那可能就是程序本身的问题。


检查磁盘与网络

对于磁盘空间,最常用的工具是 dfdf 显示机器上所有文件系统的已用空间、可用空间和挂载点。默认以字节显示,使用 -h 选项可以以人类可读的格式(如 GB、MB)显示。

要找出特定目录中哪些文件占用了大量空间,可以使用 du 命令。du 显示文件或目录的磁盘使用量。例如:

du -sh *

-s 选项显示摘要,-h 选项以人类可读格式显示。还有一个工具叫 dust,它提供更直观的界面来查看磁盘使用情况。

对于网络连接,ss 命令非常有用。ss 用于查看机器上的所有网络连接。默认显示所有支持的协议的所有连接。

常用选项:

  • -t:显示所有 TCP 连接。
  • -l:显示所有监听端口(服务器端口)。
  • -p:显示使用端口的进程和 PID。
  • -n:以数字形式显示端口和地址,而非服务名。

保持监听端口列表为空通常是安全的。浏览器等应用程序可能会打开临时端口。

netcatnc)是一个方便的工具,用于在机器之间传输原始数据,黑客经常使用它,但有时也确实有用。


网络配置与诊断

网络配置主要使用 ip 命令。ip 命令可以配置机器上几乎所有与网络相关的设置。

基本命令 ip addr 显示所有网络接口及其绑定的 IP 地址。例如,lo 是回环接口,eth* 通常是以太网端口,wlan* 通常是无线端口。UP 表示接口已启用,DOWN 表示未连接。

ip 命令功能强大但语法复杂。通常使用 ip help 查看子命令帮助,或查阅手册页 man ip

另一个重要的子命令是 ip route,它显示路由表,即数据包如何发送到其他机器。它告诉你哪些流量发送到本地网络,哪些流量通过默认网关(路由器)发送到外部网络。

当网络出现问题时,ping 是首选的诊断工具。操作顺序通常是:

  1. ping 一个主机名(如 example.com)。如果成功,说明网络正常。
  2. 如果失败,尝试 ping 一个 IP 地址(如 8.8.8.8)。
  3. 如果能 ping 通 IP 地址但不通主机名,问题可能出在 DNS 解析。
  4. 如果两者都失败,尝试 ping 你的路由器(网关 IP)。如果能通,说明本地网络正常,问题出在路由器或更远的地方。
  5. 如果连路由器都 ping 不通,问题就在本地机器或连接上。

/etc/resolv.conf 文件定义了系统使用的 DNS 服务器。


管理系统服务

对于要在后台运行的服务(如 Web 服务器、SSH 守护进程),现代 Linux 系统通常使用 systemd 进行管理。

每个服务都有一个对应的单元文件(.service 文件)。你可以使用 journalctl -u <unit> 查看特定服务的日志。

使用 systemctl 命令控制服务:

  • systemctl status:显示所有正在运行的服务及其状态和依赖关系。
  • systemctl start <unit>:启动一个服务。
  • systemctl stop <unit>:停止一个服务。
  • systemctl restart <unit>:重启一个服务。
  • systemctl enable <unit>:设置服务在开机时自动启动。
  • systemctl disable <unit>:禁用服务的开机自启。

你甚至可以编写自己的 systemd 单元文件,它们通常放在 /etc/systemd/system/ 目录下。

如果感觉系统启动缓慢,可以使用 systemd-analyze 分析启动时间。systemd-analyze blame 可以显示每个服务启动所花费的时间。


其他实用工具

locate 命令可以快速在文件系统中查找文件。它基于一个预构建的数据库(通过 updatedb 命令更新)进行搜索,速度比 find 快得多。

dmidecode 命令解析机器固件信息,告诉你运行的硬件详情,如 CPU 型号、支持的功能等。这在进行固件升级或好奇硬件规格时有用。

lstopo 命令(来自 hwloc 包)可以图形化或文本化显示 CPU 的物理布局,包括所有核心和缓存层次结构。这对于性能调试很有帮助,它还可以用来将程序绑定到特定 CPU 核心上运行。

/sys/proc 是内核暴露的特殊虚拟文件系统,用于查询和设置内核及进程的各种信息。

  • /sys/class/ 包含按类别组织的设备信息。例如,你可以通过向 /sys/class/backlight/.../brightness 写入值来调整屏幕亮度。
  • /proc/<PID>/ 包含特定进程的详细信息,如命令行参数(cmdline)、当前工作目录(cwd)等。htoptop 的信息就来源于此。

/boot 目录包含启动时所需的文件,如内核镜像(vmlinuz)和初始内存盘(initramfs)。启动配置(如内核参数)也常在这里。

iptables 是 Linux 内核内置的防火墙工具,它允许你设置复杂的规则来过滤、修改或重定向网络数据包。虽然功能强大,但配置可能较为复杂。

WireGuard 是一个现代、简洁、快速且安全的 VPN 实现。它使用公私钥对,配置简单,通常只需 wg-quick up <接口名>wg-quick down <接口名> 即可启动和停止。


Shell 选择小贴士

常见的 Shell 有 bashzshfish

  • zsh 试图与 bash 兼容,同时提供了更多功能,通过插件(如 Oh My Zsh)可以高度定制。
  • fish 则选择了一条不同的道路,它不追求与 bash 兼容,而是专注于提供更人性化、更直观的交互体验,例如强大的自动补全和语法高亮。

选择哪一个取决于你的需求。如果你需要与大量现有 bash 脚本兼容,zsh 可能是更好的选择。如果你更看重开箱即用的优秀交互体验,fish 值得一试。无论选择哪个,都值得花时间探索和配置它的特性,以提升工作效率。


本节课中,我们一起学习了机器探查与系统自检的核心工具。从获取权限、查看日志,到监控进程、检查磁盘和网络,再到配置服务和了解系统深层信息,这些工具构成了诊断和解决系统问题的基础技能树。掌握它们将帮助你更好地理解和管理你的计算机系统。

011:程序自省与探查 🔍

在本节课中,我们将学习如何深入理解程序的内部运行状态。我们将介绍两种主要的技术:调试(Debugging)和性能剖析(Profiling)。调试用于找出程序为何出错,而性能剖析则用于找出程序为何运行缓慢或占用过多资源。


调试基础:超越 print 语句

上一节我们概述了程序自省的目的。本节中,我们来看看最基础的调试方法——print 调试,以及它的局限性。

当程序出现问题时,一个常见的方法是插入大量的 print 语句来输出变量的值。这是一个有效的方法,但有时并不足够。这时,我们就需要更强大的工具:调试器。

调试器允许你与程序的执行过程进行交互。以下是调试器能做的几件事:

  • 设置断点:让程序运行,并在到达代码的特定行时暂停执行。
  • 检查状态:程序暂停后,你可以查看内存内容、打印变量值。
  • 单步执行:一次执行一行代码,并在每行后暂停,以便检查程序状态。

使用 GDB 调试 C 程序 🛠️

现在,让我们通过 GNU 调试器 (GDB) 来具体了解调试器的功能。我们将使用一个简单的 C 程序作为例子。

该程序循环调用一个函数,打印数字 1 到 10。其核心代码如下:

for (int i = 1; i <= 10; i++) {
    say(i);
}

要使用 GDB,需要在编译时添加 -g 标志,以便在二进制文件中包含调试信息。

gcc -g -o example example.c

然后,启动 GDB 并加载程序:

gdb ./example

以下是 GDB 的一些基本命令演示:

  • 运行程序:在 GDB 提示符下输入 run
  • 设置断点:可以使用函数名或行号设置断点。
    • break main:在 main 函数入口处暂停。
    • break example.c:18:在 example.c 文件的第 18 行暂停。
  • 继续执行:程序暂停后,输入 continue 可让其继续运行,直到下一个断点。
  • 检查变量:在暂停时,使用 print i 可以查看变量 i 的当前值。
  • 单步执行
    • step:执行下一行代码,如果遇到函数调用,会进入该函数。
    • next:执行下一行代码,但会跳过(不进入)函数调用。
    • finish:继续执行,直到当前函数返回。

高级调试功能

除了基本功能,调试器还提供了一些强大的高级特性。

  • 监视点:可以监视一个变量或内存地址,当其值改变时暂停程序。命令是 watch
  • 反向调试:使用 rr 等工具,可以记录程序的执行,然后反向步进,这对于调试难以复现的并发问题非常有用。
  • 图形化界面:GDB 也支持文本用户界面模式,使用 layout 命令可以分屏显示源代码、汇编代码和寄存器状态。

其他语言的调试器

调试的概念适用于几乎所有编程语言。

  • Python:使用自带的 pdb 模块。在代码中插入 import pdb; pdb.set_trace(),程序运行到此处会进入交互式调试环境。它结合了调试器命令和 Python shell 的功能。
  • JavaScript:现代网页浏览器(如 Chrome、Firefox)都内置了强大的图形化调试工具。你可以在源代码中设置断点,单步执行,并实时查看变量和调用栈。
  • 集成开发环境:大多数 IDE(如 VS Code, IntelliJ)都集成了图形化调试器,通过点击即可设置断点,操作更加直观。

系统调用追踪:strace

对于系统编程,另一个有用的工具是 strace。它运行一个程序,并打印出该程序发出的每一个系统调用及其参数。

例如,运行 strace echo hello 会显示 echo 命令执行过程中所有系统调用的细节,最终你会看到它通过 write 系统调用将 “hello” 输出到标准输出。


性能剖析:找出程序瓶颈 ⏱️

调试器用于解决程序“出错”的问题。而当程序“太慢”或占用资源过多时,我们就需要使用性能剖析器。

剖析器会监测程序的运行,并生成报告,指出时间或资源消耗在了哪里。主要有两种类型:

  1. CPU 剖析:找出程序在哪些函数上花费了最多的 CPU 时间。
  2. 内存剖析:找出程序在哪些地方分配了最多的内存。

使用 Go 工具进行剖析

以下以 Go 语言为例,展示剖析的基本流程:

  • CPU 剖析:运行程序并记录剖析数据,然后使用 go tool pprof 分析并可视化结果。它会生成一个调用关系图,用红色高亮显示最耗时的函数。
    # 记录CPU剖析数据
    go test -cpuprofile=cpu.prof
    # 启动Web界面查看剖析结果
    go tool pprof -http=:8080 cpu.prof
    

  • 内存剖析:类似地,可以记录和分析内存分配情况。
    # 记录内存剖析数据
    go test -memprofile=mem.prof
    # 分析内存剖析结果
    go tool pprof mem.prof
    

剖析报告能帮助你精准定位优化点,避免盲目修改代码。


其他剖析工具

  • perf:Linux 系统上的强大性能分析工具,可以对任何程序进行 CPU 剖析。
    perf record -g ./my_program
    perf report
    
  • time 命令:最简单的“剖析器”,用于测量程序运行的真实时间、用户态 CPU 时间和系统态 CPU 时间。
    time ./my_program
    

总结

本节课中我们一起学习了程序自省的核心技术。

  • 我们首先了解了调试器如何超越 print 语句,允许我们设置断点、检查变量和单步执行程序。我们以 GDB 为例进行了演示,并提到了其他语言(如 Python 的 pdb、浏览器的开发者工具)的调试器。
  • 接着,我们介绍了 strace 工具,用于追踪程序的系统调用。
  • 最后,我们探讨了性能剖析。当程序性能不佳时,使用 CPU 和内存剖析器可以精准定位瓶颈所在,指导我们进行有效的优化。

掌握这些工具将极大地提升你理解、调试和优化程序的能力。

012:包与依赖管理 📦

在本节课中,我们将要学习软件包与依赖管理。这是比性能分析和调试更高层次的主题,因为它直接关系到如何组织和管理项目所依赖的外部代码库。虽然具体的工具因编程语言而异,但其背后的核心概念是相通的。

概述

软件通常建立在其他软件之上。我们编写的任何程序都可能使用语言的标准库和许多第三方软件包。因此,我们需要一种方法来管理这些依赖关系。本节将介绍包管理的核心概念,包括版本控制、依赖解析、虚拟环境和代码内嵌。

包仓库

首先,我们需要一个地方来存放和分发软件包。当人们编写库或程序时,需要将其放在某个地方,以便其他人可以访问和使用。

不同的语言和构建工具使用不同的网站来托管这些包。例如:

  • Python 包通常存放在 Python Package Index (PyPI)
  • Rust 的包(crate)存放在 crates.io

虽然具体位置因语言而异,但通常每个语言社区都会约定一个中心位置来存放所有公开的库,方便开发者查找。

这些网站不仅存储源代码,通常还会为不同平台提供预编译的二进制文件。每个软件包的每个版本都会对应一份源代码和一些二进制文件。

软件版本控制

软件会随着时间不断演化。我们需要一种方法来指代软件的不同版本。

一种简单的方法是给每个版本一个顺序编号,例如 1, 2, 3, 4。但这很笨拙,无法传达有意义的变更信息。

另一种方法是使用 Git 的提交哈希值来指代版本。但这只告诉了你对应的确切代码,对人类来说并不直观。

目前,大多数社区正在标准化一种称为 语义化版本控制(Semantic Versioning) 的方法。

语义化版本控制

语义化版本号通常由三部分组成:主版本号.次版本号.修订号,例如 2.1.3

其含义如下:

  • 当你修复了错误,且没有改变任何其他行为(即向后兼容)时,递增修订号。例如,从 1.2.31.2.4。依赖此版本库的软件应能继续正常工作。
  • 当你以向后兼容的方式添加新功能时,递增次版本号。例如,从 1.2.31.3.0。依赖此版本库的软件应能继续正常工作。
  • 当你引入了不向后兼容的更改时,递增主版本号。例如,从 1.2.32.0.0。依赖旧主版本的软件可能无法与新版本一起工作。

这种版本号非常有用。例如,如果我的程序声明它依赖于某个库的 1.2.x 版本,这意味着它可以与任何主版本为 1、次版本至少为 2 的版本一起工作。

指定依赖版本

在指定依赖时,除了版本号,我们还可以做更多事情来确保项目的稳定性和可重现性。

以下是常见的依赖指定方式:

  • 约束条件:例如,packageA >= 1.2 表示至少需要 1.2 版本;packageB == 3.5.4 表示必须使用确切的 3.5.4 版本。指定最低版本的好处是,当依赖库修复错误时(修订号增加),你的项目可以自动受益。但指定精确版本可以避免因上游库意外引入不兼容变更而导致的问题。
  • 锁文件:除了声明约束,工具还可以生成一个 锁文件(如 package-lock.json, Cargo.lock)。这个文件会记录当前项目所有依赖的精确版本,甚至可能包含其内容的加密哈希值。这确保了任何人(包括未来的你)在安装依赖时,都能得到与最初开发时完全相同的代码,增强了安全性和可重现性。

依赖解析

上一节我们介绍了如何指定版本,本节我们来看看包管理器如何根据这些约束来实际安装依赖。这个过程称为依赖解析

包管理器使用不同的算法来查看所有要求,并找出满足它们的版本组合。这个问题可能变得非常复杂,尤其是在依赖关系存在冲突时。

考虑以下场景:

  • 项目 X 依赖于包 A(版本 ==1.3)和包 B(版本 >=1.5, <2.0)。
  • 包 B 又依赖于包 C(版本 >=1.5, <2.0)。
  • 但包 A 恰好依赖于包 C(版本 ==1.4)。

这就产生了冲突:无法找到一个同时满足 ==1.4>=1.5 的包 C 版本。不同的包管理器处理此类冲突的方式不同:有些采用简单策略(如总是安装最新兼容版本,忽略潜在冲突),有些则采用更复杂的算法,并会向用户报告错误。

虚拟环境

在实际开发中,你可能同时进行多个项目,而它们可能依赖同一个库的不同版本。如果所有库都安装在系统全局位置,就会产生冲突。

虚拟环境 是解决此问题的一种方案。它的思想是为每个项目创建独立的依赖安装空间,而不是全局安装。

以下是一个使用 Python 工具 venv 的简单示例:

# 在系统全局环境中,numpy 版本是 1.15.4
python -c "import numpy; print(numpy.__version__)"

# 为当前项目创建一个新的虚拟环境
python -m venv my_project_env

# 激活虚拟环境(不同系统命令不同)
# Linux/macOS:
source my_project_env/bin/activate
# Windows:
my_project_env\Scripts\activate

# 在虚拟环境中安装特定版本的 numpy
pip install numpy==1.16.0

# 现在,在虚拟环境中,numpy 版本是 1.16.0
python -c "import numpy; print(numpy.__version__)"

# 退出虚拟环境
deactivate

通过激活不同的虚拟环境,你可以轻松地在不同项目的依赖集之间切换。其他语言也有类似工具,如 Ruby 的 Bundler。有些语言(如 Rust)默认将依赖静态链接到最终二进制文件中,从而天然避免了这个问题。

代码内嵌

最后,我们将介绍一种完全不同的依赖管理方法:代码内嵌

与其处理复杂的包管理、仓库和依赖解析,你可以直接将所需库的全部源代码复制并粘贴到你的项目目录中。这样,你的项目就包含了一个包含所有自身代码和第三方代码的巨型代码树,然后一次性构建所有内容。

这种方法有一些优点:

  • 不再依赖外部包仓库的可用性。
  • 避免了依赖解析的复杂性。
  • 确保了构建的绝对一致性和可重现性。

一些大型公司(如 Google)就采用这种方式,将所有内部和外部代码维护在一个统一的巨型代码仓库中。这是一种在特定场景下非常有效的依赖管理策略。

总结

本节课中,我们一起学习了软件包与依赖管理的核心概念。我们了解了语义化版本控制的意义,探讨了如何指定依赖版本以确保项目的稳定性和安全性。我们还学习了包管理器如何解析复杂的依赖关系,以及如何使用虚拟环境来隔离不同项目的依赖。最后,我们介绍了一种替代方案——代码内嵌。掌握这些高层思想后,学习特定语言(如 Python 的 pip、Node.js 的 npm、Rust 的 Cargo)的具体工具将会更加容易。

013:操作系统自定义 🛠️

在本节课中,我们将学习如何自定义你的操作系统,以提升工作效率和用户体验。我们将探讨键盘重映射、隐藏系统设置、窗口管理以及布局自动化等核心概念。这些技巧能帮助你打造一个更符合个人习惯、更高效的工作环境。

键盘重映射 ⌨️

上一节我们介绍了自定义操作系统的概览,本节中我们来看看键盘重映射。键盘是与计算机交互时比鼠标更高效的工具,因此应尽可能优化其使用方式。你的键盘上可能有一些不常用的键,与其让它们闲置,不如将它们重映射为更有用的功能。

以下是键盘重映射的几个关键点:

  • 基础重映射:例如,在macOS中,你可以进入“系统偏好设置”->“键盘”->“修饰键”,将不常用的键(如Caps Lock)映射为其他功能键(如Escape)。对于Vim用户,将Caps Lock映射为Escape可以显著提升效率,因为Escape键用于切换回普通模式。
  • 高级重映射:使用第三方软件可以实现更复杂的映射。你不仅可以映射单个键,还可以将组合键映射为任意命令。例如,将 Ctrl+Alt+T 映射为启动终端。
  • 条件映射:一些工具甚至允许你进行编程式映射,例如,当特定程序运行时,某个组合键可以触发菜单中的某个点击操作。

通过投入精力进行键盘自定义,可以极大地优化你的工作流程。

隐藏的操作系统设置 🔧

上一节我们探讨了键盘优化,本节中我们来看看如何调整那些未在图形界面中直接暴露的隐藏系统设置。无论你使用哪种操作系统,除了图形设置界面外,通常还有更多可自定义的选项。

以下是关于隐藏设置的核心信息:

  • 访问方式:例如,在macOS中,可以使用 defaults 命令来修改大量隐藏设置。命令格式通常为 defaults write <domain> <key> <value>
  • 实用示例:一个有用的设置是让隐藏的应用程序在程序坞(Dock)中显示为半透明,以便区分哪些程序是隐藏的。这可以通过类似 defaults write com.apple.Dock showhidden -bool YES 的命令实现。
  • 寻找配置:网上有大量关于这些隐藏设置的资源。一个很好的方法是查看其他人(尤其是开发者)的配置文件(dotfiles),他们通常会分享并解释自己使用的优化配置。

调整这些隐藏设置对于打造高度优化的个人环境非常有价值,这个理念在所有操作系统中都适用。

窗口管理 🪟

上一节我们介绍了系统级别的隐藏设置,本节中我们来看看直接影响日常操作的窗口管理。在使用图形界面时,如何高效地排列和管理多个窗口是一个关键问题。

以下是几种窗口管理策略:

  • 平铺式窗口管理:这是一种高效的管理风格。它不鼓励窗口重叠,而是将屏幕空间划分为不重叠的框架(平铺),让每个窗口都能完全显示。基于Linux的系统有专门的平铺窗口管理器(如i3, Awesome)。在Windows或macOS上,可以安装第三方应用(如Amethyst, Rectangle)来模拟类似行为。
  • 键盘快捷键:为窗口操作配置键盘快捷键至关重要。例如,可以设置快捷键将窗口快速移动到屏幕左半部分、右半部分,或在不同显示器间移动。
  • 网格系统:一些工具提供网格覆盖功能,允许你通过快捷键选择将当前窗口放置到屏幕的特定网格区域,实现精确布局。
  • 高度可定制:所有这些功能都可以深度定制。例如,你可以根据显示器尺寸定义不同的网格密度,并为各种布局操作绑定自己喜欢的快捷键。

布局编码与自动化 🤖

上一节我们学习了如何手动管理窗口,本节中我们来看看如何将常用的窗口布局自动化。如果你有固定的工作环境(如办公室的多显示器设置),每天手动排列窗口会浪费大量时间。根据本课程“自动化重复性任务”的主题,我们应该将布局保存并一键恢复。

以下是实现布局自动化的方法:

  • 工具示例:对于macOS,HammerSpoon是一个强大的自动化工具,它允许你用Lua脚本编码窗口布局。
  • 工作流程:你可以为不同场景(如在实验室、在家中)定义不同的布局配置,并为其命名。然后,将这些布局绑定到快捷键或菜单项上。
  • 效果:只需点击一个按钮或按下一个快捷键,所有窗口就会自动调整到预设的大小和位置,无论是显示、隐藏还是位于特定显示器上。这能节省大量日常设置时间。

总结与资源 📚

本节课中我们一起学习了操作系统自定义的四个主要方面:键盘重映射隐藏设置调整高效的窗口管理以及布局自动化。这些技巧的核心目标是减少手动操作,让你的计算机环境完全适应你的工作习惯,从而提升整体效率。

由于具体操作高度依赖于操作系统,我们无法深入每个细节。但课程笔记中提供了大量资源的链接。特别推荐你浏览“Unix Porn”这类社区,那里的人们会分享他们高度定制化的系统截图、配置说明甚至配置文件,你可以从中汲取灵感,复制对你有用的部分。

不断探索和优化你的系统设置,是成为一名高效用户的重要旅程。

014:远程机器 🖥️

在本节课中,我们将要学习如何使用SSH(Secure Shell)工具来连接和管理远程计算机。我们将涵盖从基础连接到高级功能,如密钥认证、文件传输、端口转发和会话管理,确保您能高效安全地操作远程系统。


连接到远程机器 🔌

上一节我们介绍了课程概述,本节中我们来看看如何建立基础的SSH连接。

您可能无法直接获得一台远程服务器,但可以在本地进行模拟。例如,您可以创建一个虚拟机(VM),该虚拟机会拥有自己的IP地址,然后通过SSH连接到它。默认情况下,Linux系统可能没有安装SSH服务器,您需要手动安装并启动一个名为sshd的守护进程,它会在22号端口监听连接请求。

连接时,您需要知道目标机器的IP地址。对于本地虚拟机,您可以使用localhost或特定的本地IP。连接命令的基本格式是:

ssh username@hostname

例如,连接到本地虚拟机:

ssh user@localhost

系统会提示您输入密码进行身份验证。成功登录后,您将进入一个完全不同的命令行环境,即远程机器的终端。


执行远程命令 📟

SSH的功能远不止打开一个交互式终端。很多时候,您可能只关心某个命令的输出结果。

您可以直接在SSH命令后指定要运行的命令。例如,以下命令会在远程机器上执行ls命令,并将结果返回到您的本地终端:

ssh user@hostname ls

这意味着SSH可以无缝集成到脚本或管道命令中。例如,您可以先在本地列出文件,然后通过管道将列表发送到远程机器进行处理:

ls | ssh user@hostname grep ".txt"

同样,您也可以将远程命令的输出通过管道传递给本地命令:

ssh user@hostname ls | head -5

SSH密钥认证 🔑

每次连接都输入密码非常繁琐。现代程序通过使用SSH密钥对来解决这个问题。

如果您使用过GitHub,可能已经配置过SSH密钥。密钥对存储在用户主目录下的隐藏文件夹.ssh中。为了演示,我们先移除现有密钥(如果您已配置过)。通常,如果您从未设置过,可能只有一个known_hosts文件,它记录了您连接过的主机信息。

以下是创建SSH密钥对的步骤:

  1. 使用ssh-keygen命令生成密钥。您可以选择算法(如RSA)和密钥长度。
    ssh-keygen -t rsa -b 4096
    
  2. 命令会提示您输入保存密钥的文件路径和一個可选的密码短语。为密钥设置密码短语可以增加一层安全保护,这意味着每次使用密钥时都需要解密。
  3. 生成后,您会得到两个文件:id_rsa(私钥,必须严格保密)和id_rsa.pub(公钥,可以分发)。

更便捷的解决方案是使用ssh-agent。它就像一个保险箱,您只需在会话开始时输入一次密码短语来解锁私钥,之后ssh-agent会替您管理,无需重复输入。

要将公钥复制到远程服务器以实现免密登录,可以使用ssh-copy-id工具:

ssh-copy-id user@hostname

这个工具会安全地将您的公钥添加到远程服务器~/.ssh/authorized_keys文件中。之后,您再尝试SSH连接时,就会使用密钥进行认证。


传输文件 📂

在远程服务器上工作,经常需要上传或下载文件。

一种简单的方法是结合SSH和重定向。例如,将本地文件发送到远程服务器:

cat localfile.txt | ssh user@hostname "cat > remotefile.txt"

但对于大量文件,这种方法效率很低。为此,有专门的工具:

  • SCP (Secure Copy):语法类似于本地cp命令。
    # 复制本地文件到远程
    scp localfile.txt user@hostname:/remote/path/
    # 从远程复制文件到本地
    scp user@hostname:/remote/path/remotefile.txt ./
    # 递归复制整个目录
    scp -r localdir/ user@hostname:/remote/path/
    
  • Rsync:这是一个更强大的工具,特别适合同步大量文件或需要增量备份的场景。它只传输发生变化的文件部分,并且支持断点续传。
    rsync -avz localdir/ user@hostname:/remote/path/
    

管理长时间运行的任务 ⏳

如果您在远程服务器上启动了一个长时间运行的程序(如模拟计算),然后关闭了SSH连接,该程序通常会被终止。

简单的解决方案是使用nohup命令,它可以让进程忽略挂断信号,并在后台运行:

nohup ./long_running_script.sh &

但这样您就无法再方便地查看或交互该程序的输出。

更好的方法是使用终端复用器,例如 screentmux。它们可以创建虚拟的终端会话,即使您断开SSH连接,会话中的程序也会继续运行。之后重新连接时,可以随时恢复这个会话。

以下是tmux的基本用法:

# 启动一个新的tmux会话
tmux
# 在会话中运行您的程序
./my_program
# 按下 Ctrl+b,然后按 d 来分离(detach)当前会话(程序在后台继续运行)
# 重新连接后,恢复之前的会话
tmux attach

使用终端复用器,您还可以在单个窗口内分割多个面板,同时管理多个任务,非常高效。


端口转发 🌉

有时,远程服务器上运行的程序(如Web服务器监听8080端口)无法直接从外部互联网访问(可能由于防火墙限制)。

这时可以使用SSH的端口转发功能,它能在本地和远程机器之间建立一条安全隧道。

主要有两种类型:

  1. 本地端口转发:将发送到本地某端口的数据,转发到远程机器的指定端口。
    ssh -L 9999:localhost:8080 user@hostname
    
    执行后,在您的本地浏览器访问 http://localhost:9999,流量就会通过SSH隧道转发到远程服务器的 localhost:8080
  2. 远程端口转发:将发送到远程机器某端口的数据,转发到本地机器的指定端口。这在让外部访问您本地开发的服务时很有用。
    ssh -R 9000:localhost:3000 user@hostname
    
    执行后,在远程服务器上访问 localhost:9000,流量会被转发到您本地机器的 localhost:3000 端口。

图形界面与高级功能 🖼️

对于带有图形界面的软件,可以通过SSH的X11转发功能,将远程的图形界面显示在本地。这需要远程SSH服务器支持X11转发,并在连接时加上 -X-Y 参数。

ssh -X user@hostname
# 连接后,可以启动图形程序,如 Firefox
firefox

程序窗口将会显示在您的本地桌面上。

另一个强大的工具是 Mosh (Mobile Shell)。它针对移动和网络不稳定的环境做了优化,在连接断开或IP地址变更时,能更好地保持会话,体验比传统SSH更流畅。


SSH配置文件 📄

为了避免每次输入冗长的连接参数(如用户名、端口、密钥位置、端口转发规则),可以配置 ~/.ssh/config 文件。

以下是一个配置示例:

Host myserver
    HostName server.example.com
    User myusername
    Port 2222
    IdentityFile ~/.ssh/id_rsa_custom
    LocalForward 9999 localhost:8080
    ProxyJump bastion.example.com

配置后,您只需输入 ssh myserver 即可使用所有预设选项进行连接。ProxyJump 指令对于需要通过跳板机(堡垒主机)访问内网机器的情况特别有用。

此外,您还可以使用 sshfs 工具将远程目录挂载到本地文件系统,像操作本地文件夹一样操作远程文件:

sshfs user@hostname:/remote/path /local/mountpoint

总结 📚

本节课中我们一起学习了SSH这一强大的远程管理工具的核心用法。我们从基本的连接和命令执行开始,逐步深入到密钥认证、安全的文件传输(SCP/rsync)、管理持久会话(tmux/screen)、通过端口转发访问受限服务,以及利用配置文件简化操作。掌握这些技能,将使您能够灵活、安全、高效地在任何远程机器上开展工作。

015:网页与浏览器 🕸️

在本节课中,我们将学习关于网页浏览器的高级使用技巧。内容涵盖浏览器快捷键、高效搜索、隐私保护、自定义网页样式、使用用户脚本、Web API以及浏览器自动化。掌握这些工具和概念,能让你更高效、更安全地浏览网页,并解锁浏览器的强大自定义能力。

浏览器快捷键 ⌨️

上一节我们介绍了课程概述,本节中我们来看看如何通过键盘快捷键更高效地使用浏览器。掌握快捷键可以显著提升你的浏览效率。

以下是几个核心的浏览器快捷键:

  • Ctrl/Cmd + T:打开新标签页。
  • Ctrl/Cmd + Shift + T:重新打开最近关闭的标签页。
  • Ctrl/Cmd + L:将光标聚焦到地址栏,方便快速输入网址或搜索。
  • Ctrl/Cmd + F:在当前网页内查找文本。
  • Ctrl/Cmd + W:关闭当前标签页。
  • 鼠标中键点击标签页:关闭该标签页。

高级搜索技巧 🔍

我们已经了解了如何快速导航,接下来看看如何更精准地获取信息。搜索引擎(如Google)支持许多高级操作符,能帮你精确过滤结果。

以下是一些实用的搜索操作符示例:

  • 精确短语搜索:使用引号 " "。例如,"exact phrase" 会搜索完全匹配这个短语的页面。
  • 站内搜索:使用 site:。例如,site:stackoverflow.com python 只在 Stack Overflow 网站内搜索关于 Python 的内容。
  • 排除特定词:使用减号 -。例如,Cambridge -UK 会排除包含 “UK” 的搜索结果。
  • 逻辑“或”搜索:使用 OR。例如,electronics OR circuits 会搜索包含任一关键词的结果。
  • 指定文件类型:使用 filetype:。例如,lecture notes filetype:pdf 只搜索 PDF 格式的讲义。

自定义搜索引擎 🔧

除了通用搜索,你还可以为特定网站设置快捷搜索。这允许你直接从地址栏跳转到目标网站的搜索结果页。

设置方法因浏览器而异,但核心思想是让浏览器识别特定关键词。例如,你可以设置:

  • 输入 am 后按 Tab 键,直接在亚马逊(Amazon)搜索。
  • 输入 yt 后按 Tab 键,直接在 YouTube 搜索。

隐私与安全扩展 🛡️

在享受便捷的同时,保护隐私和安全至关重要。安装合适的浏览器扩展是有效手段。

以下是三个推荐的基础扩展:

  1. 广告拦截器 (如 uBlock Origin):不仅能屏蔽广告,其内置的过滤器列表也能有效阻止访问已知的恶意软件站点。你可以针对特定网站临时禁用。
  2. 隐私保护工具 (如 Privacy Badger):阻止第三方跟踪器收集你的浏览数据,防止跨站指纹识别。
  3. HTTPS 强制升级 (如 HTTPS Everywhere):自动将网站连接从明文的 HTTP 升级到加密的 HTTPS,保护数据传输安全。

自定义网页外观 🎨

浏览器不仅是内容查看器,也是内容修改器。网页由 HTML(结构)、CSS(样式)和 JavaScript(行为)构成。你可以覆盖这些层来自定义体验。

首先,使用浏览器的开发者工具(按 F12)可以临时编辑任何网页的 HTML 和 CSS。例如,你可以将一段文字的颜色改为红色:

p { color: red; }

要使更改永久化,可以安装 Stylus 扩展。它允许你为特定网站编写并应用自定义 CSS 样式。更有趣的是,存在 UserStyles.org 等社区网站,分享针对各种网站(如 GitHub、Wikipedia)的现成样式主题,你可以直接安装使用。

使用用户脚本 🤖

如果修改样式还不够,你还可以通过用户脚本修改网页的行为。这需要更谨慎,因为脚本能力更强。

安装 TampermonkeyViolentmonkey 等扩展来管理用户脚本。例如,你可以编写一个脚本,在课程网站上用 JK 键实现快速滚动:

// ==UserScript==
// @name         Class Site JK Scroll
// @match        https://example.com/*
// ==/UserScript==
document.addEventListener('keydown', (e) => {
  if (e.key === 'j') window.scrollBy(0, 100);
  if (e.key === 'k') window.scrollBy(0, -100);
});

警告:只运行你信任的来源的脚本,恶意脚本可能窃取你的信息。

利用 Web API 🌐

许多网站和服务提供 Web API(应用程序接口),允许你通过程序(而非浏览器)直接获取数据或触发操作。这通常通过发送 HTTP 请求并接收结构化数据(如 JSON)来完成。

例如,你可以用 curl 命令获取你的公网 IP 信息:

curl https://ipinfo.io/json

或者,利用 Google 的搜索建议 API:

curl "https://suggestqueries.google.com/complete/search?client=firefox&q=mit"

更强大的应用是结合不同服务的 API 实现自动化,例如:当你的位置显示到家时,自动通过智能家居 API 打开电灯。IFTTTZapier 这类平台提供了无需编程的 API 连接方式。

处理返回的 JSON 数据时,可以使用 jq 工具进行过滤和格式化:

curl ... | jq '.[1]'

浏览器自动化 🤖

当网站没有提供 API,或者你需要模拟复杂的用户交互(如点击、填写表单)时,可以使用浏览器自动化工具。

Selenium 是一个流行的框架,它通过 WebDriver 控制真实的浏览器实例。你可以用 Python 等语言编写脚本,指示浏览器执行一系列操作。

例如,自动将网页保存到 Wayback Machine:

from selenium import webdriver
driver = webdriver.Firefox()
driver.get("https://web.archive.org/save")
url_field = driver.find_element_by_id("web-save-url-input")
url_field.clear()
url_field.send_keys("https://example.com")
url_field.submit()
# ... 等待并处理结果
driver.quit()

这适用于自动化测试、数据抓取或任何重复性的网页操作任务。


本节课中我们一起学习了网页浏览器的强大功能。从提升效率的快捷键和搜索技巧,到保护隐私的扩展;从深度自定义网页外观和行为,到利用 Web API 和自动化工具与网络服务交互。希望这些知识能帮助你更聪明、更安全、更高效地使用浏览器这个日常核心工具。

016:安全与隐私 🔐

在本节课中,我们将探讨计算机安全与隐私的现状。我的目标是让大家至少能意识到自己在安全和隐私方面的“足迹”。世界充满风险,但通过了解威胁模型并采取相应措施,我们可以在便利性与安全性之间找到平衡。本节课将涵盖从身份验证到安全通信、文件加密、备份以及安全浏览等一系列核心主题。


身份验证 🔑

上一节我们概述了安全与隐私的基本概念,本节中我们来看看身份验证,这是保护账户安全的第一步。

密码管理器

你应该使用密码管理器。你不应该自己编造密码,也不应该试图记住密码,更不应该把密码写下来。

以下是推荐的密码管理器:

  • 1Password:易于在多设备间同步和使用。
  • pass:Linux 标准密码管理器,简单直接,使用 GPG 加密文件。

你应该使用这些应用为所有你关心的网站生成密码。如果你目前还没有使用密码管理器,今天就应该去更改所有重要服务的密码。

双因素认证 (2FA)

双因素认证能极大提升安全性。其核心思想是:除了你知道的东西(密码),还需要你拥有的东西(如手机或安全密钥)。

常见的 2FA 方案包括:

  • 短信验证码 (SMS):可用,但安全性很差,容易受到钓鱼攻击和 SIM 卡交换攻击。
  • 基于应用的验证器 (如 Google Authenticator, Duo):比短信好,但仍可能受到钓鱼攻击。
  • U2F / FIDO 安全密钥:这是目前最佳实践。它是一个物理设备(如 YubiKey),通过加密握手验证网站身份,能有效防御钓鱼攻击。公式可简化为:安全登录 = 密码 (你知道的) + 安全密钥 (你拥有的)

当你启用双因素认证时,网站通常会提供一些恢复代码。这些代码应该被妥善保管(例如打印出来放在安全的地方),以防你丢失了第二因素设备。


私人通信 📡

在讨论了身份验证之后,我们来看看如何保护通信内容的安全。

对于私人通信,目前的最佳选择是 Signal。它的协议设计精良,也用于 WhatsApp 的一对一聊天。Wire 也是一个不错的选择。

应避免使用 Telegram,因为它在加密方面的记录不佳。

对于电子邮件,加密是可能的(例如使用 GPG),但设置复杂,且存在密钥分发、不具备前向保密性等问题。因此,对于敏感内容,最好避免使用电子邮件,转而使用 Signal 或 Wire。

不建议在桌面电脑上安装这些通信应用的客户端,因为笔记本电脑比手机更容易被攻破,这会扩大你的受攻击面。


文件安全 💾

保护通信安全后,我们还需要保护存储在设备上的文件。

文件安全非常困难,威胁模型至关重要。如果你只是想防御离线攻击(例如电脑关机时被偷),启用全盘加密(如 Linux 的 LUKS,Windows 的 BitLocker,macOS 的 FileVault)就足够了。

但如果攻击发生在电脑开机时(在线攻击),全盘加密就无效了。此时,你需要文件级加密。

有两种主要方法:

  1. 加密卷:创建一个大的加密文件,使用时将其作为目录挂载。工具包括 ecryptfsEncFS。代码示例:encfs ~/.secure ~/secure_mount
  2. 单独加密文件:使用如 GPG 等工具对单个文件进行加密。可以使用对称加密(gpg -c file.txt)或非对称加密。

可信否认性

在某些极端情况下(如过海关),你甚至希望隐藏加密文件的存在,这就是“可信否认性”。它试图通过隐写术等技术将数据隐藏在其他文件中。工具如 VeraCrypt(TrueCrypt 的继任者)提供了此功能,但它性能较低,数据更易丢失,且实际效果存在争议。


加密备份 ☁️

文件需要保护,备份同样需要。加密备份至关重要。

不要尝试自己组合 tarrsyncGPG 来制作加密备份方案。推荐使用专门的服务,如 Tarsnap。它经过精心设计,支持加密增量备份,且密码学上是安全的。

备份时还需考虑:如果攻击者控制了你的源机器,他能否删除所有备份?理想的备份系统应提供“仅追加”权限,使攻击者只能添加新备份,无法删除旧备份。Tarsnap 对此有解决方案。


网络安全 🌐

现在让我们把视线从本地设备扩展到整个网络。互联网本身就是一个危险的环境。

公共 Wi-Fi

公共 Wi-Fi 网络非常危险。设备会广播曾连接过的网络名称并自动重连,攻击者可以利用“Wi-Fi 菠萝”等设备伪装成这些网络,诱使你的设备连接,从而监听所有流量。

安全建议:

  • 连接公共 Wi-Fi 后,记得从设备的“已知网络”列表中删除它。
  • 在不信任的网络上,考虑使用 VPN。但请注意,使用 VPN 只是将信任从网络提供商转移到了 VPN 提供商。免费 VPN 通常更不可信。
  • 如果你技术过硬,可以在云服务器上搭建自己的 VPN。推荐使用 WireGuard,它配置简单,采用现代加密原语。

安全配置

在配置服务器或客户端软件(如 SSH)时,应使用安全的密码套件和密钥交换算法。网站 Cipherli.st 提供了各种软件的推荐安全配置。

对于注重隐私的用户,网站 Privacy Tools 列出了推荐的浏览器扩展、配置和工具。


网页浏览安全 🕵️♂️

最后,我们进入最复杂的环节:网页浏览。浏览器是一个复杂的“操作系统”,运行着来自任意网站的代码。

浏览器扩展

以下扩展能显著提升浏览安全:

  • HTTPS Everywhere:强制网站使用 HTTPS(TLS/SSL)连接。这主要加密了你与服务器之间的流量,防止窃听。
  • uBlock Origin:一个广谱内容拦截器。除了拦截广告,它还能阻止第三方脚本、框架等,大幅减少被攻击的风险。你可以启用“中等模式”或“困难模式”来获得更强保护,但这可能导致部分网站功能异常,需要手动调整。
  • 容器标签页 (Firefox) / 多用户配置文件 (Chrome):将不同用途的浏览活动(如银行、工作、社交)隔离在不同的容器或配置文件中。这能防止一个网站上的恶意脚本窃取你在另一个网站上的登录凭据或数据。

TLS 与证书

HTTPS 的核心是 TLS 协议。它不仅能加密流量,还能通过证书验证服务器身份。浏览器内置了一个受信任的证书颁发机构 (CA) 列表。如果服务器提供的证书由这些 CA 签发,浏览器就认为连接是安全的。

攻击者可能尝试进行“降级攻击”或使用无效证书。极端偏执的做法是:清空浏览器信任的 CA 列表,然后只手动添加你访问网站所需的少数几个 CA。这能极大限制可对你发起的证书攻击类型。

Tor 浏览器

Tor 通过多层路由隐藏你的 IP 地址,但它并非万能。它对于防御强大的全局攻击者(如国家行为体)效果有限,且容易受到流量分析攻击。此外,浏览器指纹(如安装的字体、窗口大小等)仍可能暴露你的身份。Tor 浏览器试图掩盖这些指纹,但并非完美。因此,Tor 主要适用于隐藏你对服务器的身份,而非提供全面匿名。


总结 📝

本节课中我们一起学习了计算机安全与隐私的多个关键方面。

核心要点是:明确你的威胁模型。根据你担心遭受的攻击类型(如设备丢失、网络窃听、针对性攻击),来决定需要采取哪些安全措施,并接受随之而来的便利性成本。

无论如何,你都应该立即做三件事:

  1. 安装并使用密码管理器
  2. 为所有重要账户启用双因素认证,并优先使用 U2F/FIDO 安全密钥
  3. 保持警惕,安全是一个持续的过程,需要不断学习和更新知识。

希望本课程对你有益。保持安全,保持好奇。

posted @ 2026-03-29 09:23  绝不原创的飞龙  阅读(3)  评论(0)    收藏  举报