MIT-missing-semester计算机教育中缺失的一课
本文最后更新于:2025年6月25日 上午
Using the shell
访问带有空格的文件夹,比如my photos
- 使用双引号括起来:”my photos”
- 使用转义:- my\ photos
shell 执行 echo hello的过程:
- shell 基于空格分割命令并进行解析,然后执行第一个单词代表的程序,并将后续的单词作为程序可以访问的参数
- shell首先判断命令是不是编程关键字,如果不是就从环境变量
$PATH
中寻找,可以通过echo $PATH
命令查找路径- 可以通过which命令查找可执行文件/链接所在的路径
路径:
- shell 中的路径是一组被分割的目录,在 Linux 和 macOS 上使用 / 分割,而在Windows上是 \
- 绝对路径与相对路径
- ls可以列出路径
- ls -al命令的输出结果第一列有10个字母,第一个字母若是d表示是一个文件夹
- 若要进入某一个文件夹,需要可执行权限
- 若要列出它所包含的内容,需要可读权限
重定向
cat < hello.txt > hello2.txt
将hello.txt文件的内容重定向到cat命令,cat将输出重定向到hello2.txt文件- 使用管道可以重定向,比如
ls -l / | tail -n1
将会列出ls -l /
命令最后一行的输出 - 一个报错分析
1
2
3sudo echo 3 > brightness
An error occurred while redirecting file 'brightness'
open: Permission denied- shell (权限为您的当前用户) 在设置 sudo echo 前尝试打开 brightness 文件并写入,但是系统拒绝了 shell 的操作因为此时 shell 不是根用户
- 一个正确的做法:
echo 3 | sudo tee brightness
Shell Scripting
shell脚本与其他脚本语言不同之处在于,针对 shell 所从事的相关工作进行来优化:
- 创建命令流程(pipelines)
- 将结果保存到文件
- 从标准输入中读取输入
以上这些都是 shell 脚本中的原生操作,这让它比通用的脚本语言更易用
#
后的内容会被注释- shell脚本的第一行如果出现了
#!/bin/bash
,说明这个脚本将会被bash执行,而不是zsh之类的别的shell chmod +x xxx.sh
赋予可执行权限./xxx.sh
会执行xxx.sh这个脚本,不可以直接写上xxx.sh,否则会在PATH里面找有没有交xxx.sh的- bash赋值语法为
foo=bar
,表示将bar赋值给foo,假设写为foo = bar
,表示将=和bar当做foo的两个参数(在shell脚本中使用空格会起到分割参数的作用,有时候可能会造成混淆) - Bash中的字符串通过’ 和 “分隔符来定义,前者定义的字符串为原意字符串,其中的变量不会被转义,而后者将会对变量值进行替换
- 单引号里的任何字符都会原样输出,单引号字符串中的变量是无效的
- 双引号里可以有变量与转义字符
- 和其他大多数的编程语言一样,bash也支持if, case, while 和 for 这些控制流关键字、函数,一个函数的例子:
1
2
3
4mcd () {
mkdir -p "$1" # $1表示脚本的第一个参数
cd "$1"
} - bash使用许多特殊的变量来表示参数、错误代码和相关变量:
1
2
3
4
5
6
7
8$0 - 脚本名
$1 到 $9 - 脚本的参数。 $1 是第一个参数,依此类推。** 注意,$10 不能获取第十个参数,获取第十个参数需要${10}。当n>=10时,需要使用${n}来获取参数。 **
$@ - 所有参数
$# - 参数个数
$? - 前一个命令的返回值
$$ - 当前脚本的进程识别码
!! - 完整的上一条命令,包括参数。常见应用:当你因为权限不足执行命令失败时,可以使用 sudo !!再尝试一次。
$_ - 上一条命令的最后一个参数。如果你正在使用的是交互式 shell,你可以通过按下 Esc 之后键入 . 来获取这个值。 <(...)
被称为程序替换(progress substitution),它可以将命令输出转化为diff命令可以读取的类文件对象,因此diff <(ls foo) <(ls bar)
会显示文件夹 foo 和 bar 中文件的区别- 通配符*和?,分别表示匹配单个和匹配所有。对于文件foo, foo1, foo2, foo10 和 bar, rm foo?这条命令会删除foo1 和 foo2 ,而rm foo* 则会删除除了bar之外的所有文件。
- 可以使用 https://www.shellcheck.net/ 来检测shell脚本的错误
- 可以使用 https://explainshell.com/ 来分析shell脚本的功能
- 花括号{} - 当你有一系列的指令,其中包含一段公共子串时,可以用花括号来自动展开这些命令
cp /path/to/project/{foo,bar,baz}.sh /newpath
会展开为cp /path/to/project/foo.sh /path/to/project/bar.sh /path/to/project/baz.sh /newpath
mv *{.py,.sh} folder
会移动所有 *.py 和 *.sh 文件- 下面命令会创建
foo/a, foo/b, ... foo/h, bar/a, bar/b, ... bar/h
这些文件:touch {foo,bar}/{a..h}
- 查找文件:
find
命令、fd
(github上对于find命令的替代的开源项目)、locate命令
Vim
Vim的哲学:
- Vim可编程(可以使用 Vimscript 或者像 Python 一样的其他程序语言))
- Vim 的接口本身也是一个程序语言
- Vim 避免了使用鼠标,减少了手指移动
- Vim 是一个能跟上你思维速度的编辑器
Vim的几个模式:
- 正常(normal)模式:在文件中四处移动光标进行修改(启动后的默认模式)
- 插入(insert)模式:插入文本
- 替换(replace)模式:替换文本
- 可视化(visual)(一般,行,块)模式:选中文本块
- 命令(command)模式:用于执行命令
- Vim会在左下角显示当前的模式
在不同的操作模式下,键盘敲击的含义也不同。比如,x 在插入模式会插入字母x,但是在正常模式 会删除当前光标所在的字母,在可视模式下则会删除选中文块。
一些需要记忆的命令:
移动:
- 基本移动: hjkl (左, 下, 上, 右)
- 词: w (下一个词), b (词初), e (词尾)
- 行: 0 (行初), ^ (第一个非空格字符), $ (行尾)
- 屏幕: H (屏幕首行), M (屏幕中间), L (屏幕底部)
- 翻页: Ctrl-u (上翻), Ctrl-d (下翻)
- 文件: gg (文件头), G (文件尾)
- 行数: :{行数}
或者 {行数}G ({行数}为行数) - 杂项: % (找到配对,比如括号或者 /* */ 之类的注释对)
- 查找: f{字符}, t{字符}, F{字符}, T{字符}
- 查找/到 向前/向后 在本行的{字符}
- , / ; 用于导航匹配
- 搜索: /{正则表达式}, n / N 用于导航匹配
编辑:
- O / o 在之上/之下插入行
- d{移动命令} 删除 {移动命令}
- 例如, dw 删除词, d$ 删除到行尾, d0 删除到行头。
- c{移动命令} 改变 {移动命令}
- 例如, cw 改变词
- 比如 d{移动命令} 再 i
- x 删除字符(等同于 dl)
- s 替换字符(等同于 xi)
- 可视化模式 + 操作
- 选中文字, d 删除 或者 c 改变
- u 撤销,
重做 - y 复制 / “yank” (其他一些命令比如 d 也会复制)
- p 粘贴
- 更多值得学习的: 比如 ~ 改变字符的大小写
Data Wrangling
- 正则表达式
- sed
- awk
Command-line Environment
进程与信号
- 信号机制可以进行进程间通信,比如当我们输入 Ctrl-C 时,shell 会发送一个SIGINT 信号到进程
- 当一个进程接收到信号时,它会停止执行、处理该信号并基于信号传递的信息来改变其执行。就这一点而言,信号是一种软件中断
- SIGSTOP可以让进程暂停(ctrl+z),可以配合fg(前台继续)、bg(后台继续)恢复被暂停的工作
- 后台进程仍然是终端进程的子进程,关闭终端后该进程也会被终止
- 为了防止上述事情,可以使用nohup运行程序,也可以使用disown(对于已运行的程序)
- jobs列出当前终端会话的尚未完成的全部任务,echo $!打印出最近的任务的pid
- &可以让程序在后台运行
终端多路复用
会话 - 每个会话都是一个独立的工作区,其中包含一个或多个窗口
- tmux 开始一个新的会话
- tmux new -s NAME 以指定名称开始一个新的会话
- tmux ls 列出当前所有会话
- 在 tmux 中输入
d ,将当前会话分离 - tmux a 重新连接最后一个会话。您也可以通过 -t 来指定具体的会话
窗口 - 相当于编辑器或是浏览器中的标签页,从视觉上将一个会话分割为多个部分
c 创建一个新的窗口,使用 关闭 N 跳转到第 N 个窗口,注意每个窗口都是有编号的 p 切换到前一个窗口 n 切换到下一个窗口 , 重命名当前窗口 w 列出当前所有窗口
面板 - 像 vim 中的分屏一样,面板使我们可以在一个屏幕里显示多个 shell“ 水平分割 % 垂直分割 <方向> 切换到指定方向的面板,<方向> 指的是键盘上的方向键 z 切换当前面板的缩放 [ 开始往回卷动屏幕。您可以按下空格键来开始选择,回车键复制选中的部分 <空格> 在不同的面板排布间切换
别名
- alias可以给长命令取别名,比如alias ls=“ls -al;pwd”,注意到等号两遍没有空格
- unalias禁用别名
- 为了让别名持续生效,需要把别名放到.bashrc等shell的启动文件中
配置文件(Dotfile,点文件)
很多程序的配置都是有对应的配置文件的,以点开头,比如:
- bash - ~/.bashrc, ~/.bash_profile
- git - ~/.gitconfig
- vim - ~/.vimrc 和 ~/.vim 目录
- ssh - ~/.ssh/config
- tmux - ~/.tmux.conf
配置文件可以方便移植,定制化等
远程登录
- ssh user@ip command可以以user的身份执行command
- ssh可以配置免密登录
- ssh-keygen命令可以生成一对rsa秘钥
- ssh 会查询 .ssh/authorized_keys 来确认那些用户可以被允许登录,需要将公钥拷贝到此处
- scp命令可以拷贝文件
- rsync是scp的改进版,可以实现断点续传、防止重复拷贝等功能
- 编辑.ssh/config文件可以给给需要连接的远程服务器、用户取别名,简化命令
拷贝公钥的两个命令:
1 |
|
Version Control (Git)
版本控制系统的作用:
- 追踪项目的修改历史
- 查看某行代码编辑的时间和人
- 创建项目快照、多人合作、分支开发等
Git的特点:
- Git 在储存数据时,所有的对象都会基于它们的 SHA-1 哈希 进行寻址
- 给这些哈希值赋予人类可读的名字,也就是引用(references),比如master 引用通常会指向主分支的最新一次提交
- 当前位置的索引:HEAD
详细内容可以参考https://gls.show/p/3645f6a9/
Debugging and Profiling
最有效的 debug 工具就是细致的分析,配合恰当位置的打印语句
调试代码的方式:
print大法好
日志,比如journalctl可以查询系统日志,可以配合正则进行匹配
可以通过彩色的终端输出更好的分析调试(需要终端的彩色配置)
lnav提供了易于查看的日志
一些调试器:gdb、pwngdb、pdb
分析系统调用:strace
web开发:使用Chrome的开发者工具
python的 pycallgraph可以生成如下的调用图和流程图:
资源监控:
- htop,注意到一些快捷键的使用,比如
进程排序、 t 显示树状结构和 h 打开或折叠线程 - glance
- iotop可以显示实时 I/O 占用信息而且可以非常方便地检查某个进程是否正在执行大量的磁盘读写操作
- df 可以显示每个分区的信息,而 du 则可以显示当前目录下每个文件的磁盘使用情况( disk usage)。-h 选项可以使命令以对人类(human)更加友好的格式显示数据;ncdu是一个交互性更好的 du ,它可以让您在不同目录下导航、删除文件和文件夹
- lsof 可以列出被进程打开的文件信息、查看文件是被哪个进程打开的
Metaprogramming
构建系统,以makefile为例
1 |
|
- 左侧是构建目标,右侧是构建目标所需要的依赖
- 缩进的部分是上述过程所需要的程序
%
会匹配左右两端相同的字符串- 如果目标是 plot-foo.png, make 会去寻找 foo.dat 和 plot.py 作为依赖
依赖管理
- 语义版本号:主版本号.次版本号.补丁号,比如8.1.3
- 假设项目依赖A,A有版本B,那么只要B的主版本号和A相同,次版本号不低于A一般来说即可使用
- 如果我依赖的版本是1.3.7,那么使用1.3.8、1.6.1,甚至是1.3.0都是可以的。如果版本号是 2.2.4 就不一定能用
- 锁文件(lock files):锁文件列出了您当前每个依赖所对应的具体版本号,需要执行升级程序才能更新依赖的版本
持续集成
- CI(continuous integration 持续集成)
本课程的网站基于 GitHub Pages 构建,这就是一个很好的例子。Pages 在每次master有代码更新时,会执行 Jekyll 博客软件,然后使您的站点可以通过某个 GitHub 域名来访问。对于我们来说这些事情太琐碎了,我现在我们只需要在本地进行修改,然后使用 git 提交代码,发布到远端。CI 会自动帮我们处理后续的事情。
- Git 可以作为一个简单的 CI 系统来使用,在任何 git 仓库中的 .git/hooks 目录中,您可以找到一些文件(当前处于未激活状态),它们的作用和脚本一样,当某些事件发生时便可以自动执行
Security and Cryptography
- 散列函数
- 密钥生成函数 (Key Derivation Functions)
- 文件的信息摘要(Message digest)
- Git中的内容寻址存储(Content addressed storage)
- 对称加密
- 非对称加密
- PGP电子邮件加密,公布公钥,实现电子邮件加密
- 软件签名,验证下载的软件
- 密码管理器,比如keepass
- 两步验证
- ssh(非对称签名来验证用户身份)
Potpourri
- 修改键位映射
- Vim经常用到escape键但是太远了,因此可以将escape与caps键替换。通常这个功能由在计算机上运行的软件实现。当某一个按键被按下,软件截获键盘发出的按键事件(keypress event)并使用另外一个事件取代
- 守护进程
- 指后台持续运行的程序,一般以 d 结尾,比如 SSH 服务端 sshd,用来监听传入的 SSH 连接请求并对用户进行鉴权
- systemd((the system daemon)) 配置文件的详细指南可参见 freedesktop.org
- 定期运行程序可以使用cron
- 计算机启动时,BIOS 或者 UEFI 会在加载操作系统之前对硬件系统进行初始化,这被称为引导(booting)。你可以通过按下计算机提示的键位组合来配置引导,比如 Press F9 to configure BIOS. Press F12 to enter boot menu
- 交互式记事本。比如Jupyter,它的名字来源于所支持的三种核心语言:Julia、Python、R(JuPyteR)
- GitHub