跳转至

The Missing Semester of Your CS Education 笔记⚓︎

2508 个字 3 行代码 预计阅读时间 13 分钟

动机

大三春夏学期期末复习时,由于备考课程少,所以当天的复习计划很快就完成了,于是正在摸鱼的我不知第几次点开「CS 自学指南」网站。无意中,我点开了「MIT-Missing-Semester」的链接,并翻了下评论,结果发现时隔多年,今年竟然更新了!怀着好奇与激动的心情,我点开了这个熟悉的网站:第一眼还是那熟悉的味道,不过显而易见的是多了和 AI 相关的内容,想必这也是本次更新的一大原因。虽说里面提到的大多数工具我都有所了解,并且用过一段时间,但说实话用的并不熟练;再加上现在 AI 这么方便,像调试、写 Shell 脚本完成简单的自动化任务之类的活完全交给 AI 干了,自己快成啥也不会的甩手掌柜了,羞愧之心油然而生。

趁着期末考结束和实习 restart 的这段空闲时光,我想花些功夫好好回顾这些 CSer 应该掌握,且应该在实践中熟练运用的一些工具。虽说官方教程已经相当阅读友好了(而且民间中文版翻译也已经跟进了,但这毕竟是给大众看的,感觉还是得看完后以适合自己的形式进行输出才算真正地学进去——哦不,还得靠之后在平时的反复操练才行。除了整理教程内容外,我还会把这几年的经历将我看过或用过的一些工具或技巧整理进去,这样得到的笔记才算是我自己的学习心得。

虽然平时用 Mac 比较多,但工作时必须得用 Windows,不过我还是以 Mac 上的操作为主,因为毕竟是在家里学习(一打开 Windows 电脑就想打游戏 hh

课程目标:

We want to teach you how to make the most of the tools you know, show you new tools to add to your toolbox, and hopefully instill in you some excitement for exploring (and perhaps building) more tools on your own. This is what we believe to be the missing semester from most Computer Science curricula.

Shell 入门⚓︎

为什么要用 Shell

虽然 GUI 等交互界面在大多数时候很好用,但我们无法通过它们触发所有功能,这种时候往往只有 Shell 这样的文本界面能够做到。此外,Shell 还能让我们以创造性的方式组合(combine) 程序,从而实现各种任务的自动化。最后,掌握 Shell 的使用方法,对于探索开源软件世界(其安装说明通常需要用到 Shell、为软件项目搭建持续集成环境,以及在其他程序出错时进行调试都大有裨益。

我们可以在 Shell 中运行程序、提供输入,并以半结构化的方式检查输出。大多数平台默认都安装了 Shell,所以下面列出各平台常见的打开方式和默认 Shell

  • Linux:
    • 按下 Ctrl + Alt + T (适用于大多数发行版;或者在应用程序菜单中搜索 "Terminal"
    • 默认 Shell bash(全称 "Bourne Again Shell"
  • Windows:
    • 按下 Win + R ,输入 "cmd" "powerShell",然后按回车键;或在开始菜单中搜索 "cmd" "powerShell"
    • 强烈建议装个 Windows Terminal(到微软应用商店下载Win11 应该默认安装好的,比原版的 cmd PowerShell 界面美观多了,还提供了丰富的个性化设置
    • Windows 默认没有 bash,因此建议安装 WSL2(全称「Windows Subsystem for Linux)或 Linux 虚拟机实现 Linux 环境下的 Shell 交互
  • macOS:
    • 按下 Cmd + Space 打开聚焦搜索(Spotlight,输入 "Terminal",然后按回车键。或者可以在「应用程序(文件夹)->「实用工具」->「终端」中找到它
    • 我用的是 Warp(跨平台的,Windows Linux 也可以装,主要看的是颜值,并非它的 AI 功能(这得花不少钱 .jpg
    • 默认 Shell zsh(从 macOS 10.15 版本开始,原来是 bash,在 bash 基础上提供了更丰富的功能,比如自动补全、模糊匹配等

Bash 的默认提示符(prompt) 如下:

missing:~$
  • missing:主机名
  • ~:当前目录位置,~ 表示 home 目录
  • $:表示当前用户不是 root 用户

Shell 通过分割空白字符来解析命令,然后运行第一个单词对应的程序,并将后续每个单词作为程序可访问的参数传入。

Shell 被要求执行一条命令时,它会查询一个名为 $PATH 的环境变量,该变量列出了 Shell 在收到命令时应搜索程序的目录(目录之间以 : 分隔,找到同名的可执行(executable) 文件后便会执行它。也通过直接提供待执行文件的路径来绕过 $PATH 机制,执行我们自己指定的程序。

在提示符后面我们可以输入命令(command),由 Shell 解释并执行。下面列举一些常用命令:

  • date:打印当前日期和时间
  • echo xxx:打印参数 xxx

    • xxx 内包含特殊字符,需用单引号(')或双引号(")包裹 xxx,或者对特殊字符进行转义(即在特殊字符前加反斜杠 \
  • man cmd(mannual,手册:查询命令 cmd 的详细信息

    • 大多数命令会提供 --help 参数来获取简短的帮助信息
    • 建议安装 tldr 命令,它会提供命令的一些常见用法,实践中更好用
  • cd path(change directory,切换目录:传递路径 path,将其作为当前工作目录

    • 一般在 Shell 提示符中能看到当前的工作目录
    • Shell 自带自动补全功能,因此通常可以通过按 Tab 键来更快地补全路径
    • path 可以是绝对(absolute) 路径(以斜杠 / 开头,也可以是相对(relative) 路径(不以 / 开头,从当前目录开始)
    • 路径简写:

      • .:当前目录
      • ..:父目录(上一级目录)
    • 建议装一个 zoxide,之后使用 z 命令可以用更少的输入跳转到经常访问的路径,无需输入完整路径

  • pwdecho $PWD:打印 $PWD 环境变量,显示当前工作目录

  • which cmd:查看 cmd 对应的具体执行文件的路径
  • ls path:列出该路径下的文件

    • 建议安装 eza,相比 ls 命令提供更友好的输出内容
  • cat file:打印 file 的内容

    • 建议安装 bat,它具备语法高亮和滚动阅读功能
  • sort file:按排序顺序(一般是字典序)打印 file 的行文本

  • uniq file:打印时消除 file 中连续重复的行
  • head filetail file:分别用于打印 file 的前几行和后几行
  • grep pattern file:在 file 中查找与 pattern 匹配的行

    • pattern正则表达式(regular expression)
    • 也可以指定目录而非文件(如果是 . 则可以省略这一参数,此时需传递 -r 参数递归搜索目录中的所有文件
    • 建议安装 ripgrep,相比 grep 更快、更人性化,并默认递归搜索当前工作目录,但可移植性稍差
  • sed:一个程序化的 (programmatic) 文件编辑器,拥有自己的编程语言,用于对文件进行自动化编辑

    • sed -i 's/pattern/replacement/g' file:把 file 中所有 pattern 替换为 replacement
      • -i:表示替换操作在原处进行(而不是保留未修改的 file 并打印替换后的内容)
      • s/:表示执行替换操作的方式
      • /:分隔匹配模式与替换内容
      • /g:表示我们希望替换每行中的所有匹配项,而不仅仅是第一个
      • pattern:正则表达式
      • 正则表达式替换还允许 replacement 引用匹配模式中的部分内容
  • find(递归)查找符合特定条件的文件

    • 使用示例:

      • find ~/Downloads -type f -name "*.zip" -mtime +30:在下载目录中查找超过 30 天的 ZIP 文件
      • find ~ -type f -size +100M -exec ls -lh {} \;:在 home 目录中查找大于 100M 的文件并列出它们

        • -exec 接受一个以独立的 ; 结尾的命令(需要转移,所以,其中 {} 会被 find 替换为每个匹配的文件路径
      • find . -name "*.py" -exec grep -l "TODO" {} \;:查找任何包含待办事项(TODO items)的 .py 文件

    • 建议安装 fd,相比 find 更人性化,但可移植性较差

  • awk:和 sed 一样也有自己的编程语言,但它主要用于解析(parse) 文件

    • 最常见的用途是处理具有规则语法(如 CSV 文件)的数据文件,从中提取每条记录(即每行)的特定部分;还可以过滤行、计算聚合值等
    • awk '{print $2}' file:打印 file 中每行第二个以空格分隔的列
      • 若加上 -F,,则会打印每行第二个以逗号分隔的列
  • tee:将标准输入打印到标准输出(和 cat 一样,但同时也会将其写入一个文件

大多数 Shell 都实现了完整的编程语言,拥有变量、条件判断、循环和函数等。在 Shell 中运行命令时实际上是在编写一小段由 Shell 解释执行的代码。下面列举部分语法:

  • 管道(pipe)(|:获取 | 之前程序的标准输出(通常打印到终端的内容,并将其作为 | 之后程序的标准输入,从而实现 shell 程序的组合(composition)
  • 重定向(redirection)

    • >file:将程序的标准输出写入 file 而非终端(会覆盖 file 原有内容)
    • >>file:追加到 file 末尾而非覆盖
    • <file:从 file 中读取内容,而不是从键盘作为程序的标准输入
  • 条件(conditionals)

    • if command1; then command2; command3; fi:执行 command1,如果未产生错误,则运行 command2command3
    • 可以根据需要添加 else 分支
    • 最常用的 command1 命令是 test,通常简写为 [ ],用于评估诸如文件是否存在(test -f file / [ -f file ])或字符串是否相等([ "$var" = "string" ])等条件
    • Bash 中还有 [[ ]],它是 test 的一个更安全的内置版本,在引号处理方面异常行为更少
  • 循环

    • while command1; do command2; command3; done:只要 command1 不报错,就会反复执行整个操作
    • for varname in a b c d; do command; done:执行 command 四次,每次将 $varname 依次设为 abcd
    • for i in $(seq 1 10); do:执行命令 seq 1 10(打印从 1 10 的整数,将整个 $() 替换为该命令的输出,从而得到一个 10 次迭代的 for 循环
  • 后台任务&:实现程序并发运行

  • 算术扩展(arithmeic expansion)

对于较长的 Shell 脚本,建议将它们写入 .sh 文件中。.sh 文件开头可能会有这么两行:

#!/bin/bash
set -euo pipefail
  • 第一行称为 shebang(非 Shell 脚本顶部可能也会有这么一行:当一个以 #!/path 开头的文件被执行时,Shell 会启动 /path 处的程序,并将文件内容作为输入传递给它

    • 因此该脚本的内容会传给 /bin/bash
    • 也可以编写带有 shebang /usr/bin/python Python 脚本
  • 第二行是一种让 bash 更严格的方式,可以减少编写 Shell 脚本时的一些常见陷阱

    • -e:若任何命令失败,脚本会提前退出
    • -u:使用未定义的变量会导致脚本崩溃,而不是仅仅使用空字符串
    • -o pipefail:若 | 序列中的程序失败,整个 Shell 脚本也会提前退出

bash 存在很多陷阱,有不少网站列出这些陷阱:

一些建议

  • 在编写 Shell 脚本时大量使用 shellcheck
  • LLMs 编写和调试 Shell 脚本
  • 若脚本变得过于庞大(超过 100 行)而难以用 bash 处理时,将其转换为“真正的”编程语言(如 Python

命令行环境⚓︎

开发环境与工具⚓︎

调试与性能分析⚓︎

版本控制与 Git⚓︎

打包与发布代码⚓︎

智能体编程⚓︎

不止于代码⚓︎

代码质量⚓︎

评论区

如果大家有什么问题或想法,欢迎在下方留言~