前言

这个课程是mit的《计算机教育缺失的一课》,里面有很多计算机相关的工具介绍,本人学习的视频是计算机教育缺失的一课(2020),这是第一讲,其他视频在该up主页里也能找到。本课程有配套的官方讲义计算机教育中缺失的一课,若打开很慢请用魔法,无论是视频还是讲义都是配有中文的。本人的笔记只是根据个人喜好从视频以及讲义中整理。

计算机教育缺失的一课

课程概述与shell

  1. shell 参数以空格分开,若要显示空格,需要’\‘转义

    1
    2
    $ echo hello\ world \\echo为打印字符串程序
    hello world
  2. 或者可以加双引号

    1
    $ echo "hello world" \\同上
  3. shell命令汇总

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    $ echo $PATH \\显示所有路径
    $ which echo \\指出运行哪一个
    $ pwd \\当前目录
    $ cd \home \\cd改变目录,改到home
    $ cd .. \\父目录
    $ cd . home\\ .为当前目录
    $ ls \\显示当前目录文件
    $ cd ~ \\~为根目录
    $ cd - \\切换为之前的目录
    $ ls --help \\列出ls的所有命令
    $ mv d.md f.md \\重命名,mv也可以移动
    $ cp dot.md ../food.md \\复制到后面的目录
    $ rm ../food.md \\删除food.md,不能删除目录(不能递归删除)
    $ -r \\可递归删除
    $ rmdir \\只能删除空目录
    $ mkdir \\创建目录
    $ man ls \\显示ls的手册内容
    $ echo hello > hello.txt \\ >可重定向输出流,将hello输出到txt
    $ cat hello.txt \\打印文件的内容
    $ cat < hello.txt > hello2.txt \\ <可重定向输入流,将hello.txt的内容输出到hello2.txt中(若不存在文件就创建一个)
    $ cat < hello.txt >> hello2.txt \\ >>表示追加内容
    $ ls -l / | tail -n1 > ls.txt \\ | 为管道符,把左侧的的内容当作输入,右侧的内容当作输出,> 又输出到ls.txt中
    $ sudo su\\成为root,成为root后 $ 将变成 # .
    $ echo 1060 | sudo tee brightness \\将1060传给屏幕亮度,sudo使得tee以root身份运行,tee是输出内容到文件,同时输出到屏幕
    $ xdg-open lectures.html \\用适当的程序打开lectures.html
  4. ctrl L 可以清除终端并回到顶部,上键可以打出上一条指令

shell工具与脚本

  1. 定义变量

    1
    2
    3
    >>> foo=bar //不可分开为foo = bar
    >>>echo $foo
    bar
  2. 单双引号区别

    1
    2
    3
    4
    >>>echo "Value is $foo"
    Value is var
    >>>echo 'Value is $foo'
    Value is $foo
  3. $的相关用法

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    $@ \\ 所有参数
    $$ \\当前脚本的进程识别码
    $# \\参数个数
    $0 \\文件名
    $1 \\之后类似argv
    $? \\获取上个命令的错误信息
    echo $? \\打印错误
    $_ \\上个命令的最后一个参数
    !! \\完整的上一条命令,包括参数
    sudo !! \\用来再执行一次因为权限不足而做不了的命令
  4. 可搭配 || && ; ,其中;可连接两条指令在同一行(同一行的多个命令)

  5. 变量存储

    1
    2
    3
    4
    5
    foo=$(pwd) \\将当前路径赋值给foo
    echo $foo
    x/x/x/x/xx \\打印当前路径的结果

    cat <(ls) <(ls ..) \\将当前目录和上一级目录的文件列表连接起来并打印到标准输出。
  6. 命令替换
    $( CMD ) 这样的方式来执行CMD 这个命令时,它的输出结果会替换掉 $( CMD )

  7. 进程替换
    <( CMD )会执行 CMD 并将结果输出到一个临时文件中,并将 <( CMD ) 替换成临时文件名。

    1
    diff <(ls foo) <(ls bar) \\会显示文件夹 foo 和 bar 中文件的区别。
  8. 通配符与花括号
    使用 ? 和 * 来匹配一个或任意个字符

    1
    2
    3
    4
    5
    6
    7
    8
    9
    convert image.{png,jpg}
    \\会展开为
    convert image.png image.jpg

    mv *{.py,.sh} folder
    \\会移动所有 *.py 和 *.sh 文件

    \\ 下面命令会创建foo/a, foo/b, ... foo/h, bar/a, bar/b, ... bar/h这些文件
    touch {foo,bar}/{a..h}
  9. 在shell中运用python

    1
    2
    3
    4
    #!/usr/local/bin/python      \\shebang,指出python解释器
    import sys
    for arg in reversed(sys.argv[1:]):
    print(arg)
  10. 查找文件

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    #find查找所有名称为src的文件夹
    find . -name src -type d
    # 查找所有文件夹路径中包含test的python文件
    find . -path '*/test/*.py' -type f
    # 查找前一天修改的所有文件
    find . -mtime -1
    # 查找所有大小在500k至10M的tar.gz文件
    find . -size +500k -size -10M -name '*.tar.gz'
    # 删除全部扩展名为.tmp 的文件
    find . -name '*.tmp' -exec rm {} \;
    # 查找全部的 PNG 文件并将其转换为 JPG
    find . -name '*.png' -exec convert {} {}.jpg \;

    其他做法:fd,locate

  11. 查找代码
    grep,-c:获取查找结果的上下文(Context);-v 将对结果进行反选(Invert)。
    grep -C 5 会输出匹配结果前后五行。当需要搜索大量文件的时候,使用 -R 会递归地进入子目录并搜索所有的文本文件。
    其他做法:ack,ag,rg

    1
    2
    3
    4
    5
    6
    7
    8
    # 查找所有使用了 requests 库的文件
    rg -t py 'import requests'
    # 查找所有没有写 shebang 的文件(包含隐藏文件)
    rg -u --files-without-match "^#!"
    # 查找所有的foo字符串,并打印其之后的5行
    rg foo -A 5
    # 打印匹配的统计信息(匹配的行和文件的数量)
    rg --stats PATTERN
  12. 查找shell命令

    • 上键
    • history会在标准输出中打印shell中的里面命令,history | grep find 会打印包含find子串的命令。
    • Ctrl+R 对命令历史记录进行回溯搜索。敲 Ctrl+R 后您可以输入子串来进行匹配,查找历史命令行。
  13. 文件夹导航
    fasd使用命令 z 帮助我们快速切换到最常访问的目录,如果您经常访问/home/user/files/cool_project 目录,那么可以直接使用 z cool 跳转到该目录。对于 autojump,则使用j cool代替即可。
    其他:tree,broot,nnn,ranger

编辑器Vim

  1. 操作模式:
    按下 <ESC>(退出键)从任何其他模式返回正常模式。
    在正常模式,键入 i 进入插入 模式,R 进入替换模式,v 进入可视(一般)模式,V 进入可视(行)模式,<C-v> (Ctrl-V, 有时也写作 ^V)进入可视(块)模式,: 进入命令模式。

  2. 命令模式:

    1
    2
    3
    4
    5
    6
    7
    8
    :q 退出(关闭窗口)
    :w 保存(写)
    :wq 保存然后退出
    :e {文件名} 打开要编辑的文件
    :ls 显示打开的缓存
    :help {标题} 打开帮助文档
    :help :w 打开 :w 命令的帮助文档
    :help w 打开 w 移动的帮助文档
  3. 正常模式
    基本移动: hjkl (左, 下, 上, 右)
    词: w (下一个词), b (词初), e (词尾)
    行: 0 (行初), ^ (第一个非空格字符), $ (行尾)
    屏幕: H (屏幕首行), M (屏幕中间), L (屏幕底部)
    翻页: Ctrl-u (上翻), Ctrl-d (下翻)
    文件: gg (文件头), G (文件尾)
    行数: :{行数}<CR> 或者 {行数}G ({行数}为行数)
    杂项: % (找到配对,比如括号或者 /* */ 之类的注释对)
    查找: f{字符}, t{字符}, F{字符}, T{字符}
    查找/到 向前/向后 在本行的{字符}
    , / ; 用于导航匹配
    搜索: /{正则表达式}, n / N 用于导航匹配

  4. 选择
    可视化模式:

    • 可视化:v
    • 可视化行: V
    • 可视化块:Ctrl+v

    可以用移动命令来选中。

  5. 编辑
    i 进入插入模式
    但是对于操纵/编辑文本,不单想用退格键完成

    O / o 在之上/之下插入行
    d{移动命令} 删除 {移动命令}
    例如,dw 删除词, d$ 删除到行尾, d0 删除到行头。

    c{移动命令} 改变 {移动命令}
    例如,cw 改变词
    比如 d{移动命令} 再 i

    x 删除字符(等同于 dl)
    s 替换字符(等同于 xi)
    可视化模式 + 操作
    选中文字, d 删除 或者 c 改变

    u 撤销, <C-r> 重做
    y 复制 / “yank” (其他一些命令比如 d 也会复制)
    p 粘贴
    ~ 改变字符的大小写

  6. 计数(多次执行)
    3w 向后移动三个词
    5j 向下移动5行
    7dw 删除7个词

  7. 修饰语
    你可以用修饰语改变“名词”的意义。修饰语有 i,表示“内部”或者“在内”,和 a, 表示“周围”。
    ci( 改变当前括号内的内容
    ci[ 改变当前方括号内的内容
    da’ 删除一个单引号字符串, 包括周围的单引号

  8. 搜索和替换
    :s (替换)命令(文档)。
    %s/foo/bar/g
    在整个文件中将 foo 全局替换成 bar
    %s/\[.*\](\(.*\))/\1/g
    将有命名的 Markdown 链接替换成简单 URLs

  9. 多窗口
    用 :sp / :vsp 来分割窗口
    同一个缓存可以在多个窗口中显示。


  10. q{字符} 来开始在寄存器{字符}中录制宏
    q停止录制
    @{字符} 重放宏
    宏的执行遇错误会停止
    {计数}@{字符} 执行一个宏{计数}次
    宏可以递归

    1. 首先用q{字符}q清除宏
    2. 录制该宏,用 @{字符} 来递归调用该宏 (在录制完成之前不会有任何操作)

数据整理

可用工具:|, grep, sed, 正则表达式, perl, sort

uniq -c(把连续出现的行折叠为一行并使用出现次数作为前缀)
sort -n(按照数字顺序对输入进行排序(默认字典顺序));-kl,1表示仅基于以空格分割的第一列进行排序; ,n部分表示仅排序到第n个部分,默认为行尾;sort-r倒序排序
paste -s合并行,-d指定一个分隔符进行分割
awk:一种编程语言,非常善于处理文本
R:编程语言,非常适合进行数据分析(st)和绘制图表(gnuplot)
xargs:例如利用数据整理技术从长串列表中找到所需要安装或移除的东西
ffmpeg:从相机捕获一张图片

对于二进制文件同样可以进行数据整理

命令行环境

结束进程

Ctrl-c 发送SIGNINT信号,Ctrl-\发送SIGQUIT信号,终止程序
kill -TERM \<PID> 更通用优雅的退出信号

暂停和后台执行进程

SIGSTOP 进程暂停(Ctrl-z)
可用fg和bg恢复暂停的工作,表示在前台继续或在后台继续

jobs列出当前终端尚未完成的全部任务,可使用pid引用任务(也可以用pgrep找出pid)。
也可以用百分号加任务编号选取任务;最近一个任务使用$!

在命令后加&可以使命令直接在后台运行,但仍会使用shell标准输出

让运行的进程后台运行,Ctrl-z bg。关闭终端或者发送SIGHUP,后台进程终止,可使用nohup运行程序来忽略SIGHUP的封装。已经运行的程序可以用disown。

SIGKILL不能被捕获并且马上结束进程,但会留下孤儿进程。

终端多路复用

现在流行的终端多路器是tmux

tmux快捷键类似 C-b x 的组合
tmux对象继承结构如下:
会话:独立的工作区,可包含多个窗口
tmux开启一个新对话
tmux new -s NAME 指定名称开始一个新的会话
tmux ls列出当前所有会话
在tmux中输入<C-b> d 将当前会话分离
tmux a 重新连接最后一个会话,-t来指定具体会话
tmux attach-session -t 【会话名】 ,重新进入会话,继续工作。
窗口:相当于浏览器标签页
C-b c 创建新窗口,C-d关闭
C-b N 跳转到第N个窗口
p 切换到前一个窗口
n 切换到下一个
, 重命名当前窗口
w 列出当前所有窗口
面板:显示多个shell
C-b “ 水平分割
% 垂直分割
<方向键> 切换到指定方向的面板
z 切换当前面板的缩放
[ 开始往回卷动屏幕,可以按下空格键开始选择,回车键复制选中部分
<空格> 在不同面板排布间切换

别名

alias mkdir="mkdir -p"
等号左右无空格

\ls
忽略某个别名

unalias la
禁用别名

alias la="ls -A"
alias lla="la -l"
别名可以组合使用

alias ll
获取别名的定义(会打印 ll=’ls -lh’)

若使别名持续生效,需要将配置放进shell启动文件,例如 .bashrc或者 .zshrc

配置文件(Dotfiles)

对bash来说,可编辑 .bashrc或 .bash_profile配置

实际上,很多程序都要求您在 shell 的配置文件中包含一行类似 export PATH="$PATH:/path/to/program/bin"的命令,这样才能确保这些程序能够被 shell 找到

使用版本控制系统进行管理,然后通过脚本将其符号链接到需要的地方

可移植性

if语句,对不同设备编写不同配置

1
2
3
4
5
6
7
if [[ "$(uname)" == "Linux" ]]; then {do_something}; fi

# 使用和 shell 相关的配置时先检查当前 shell 类型
if [[ "$SHELL" == "zsh" ]]; then {do_something}; fi

# 您也可以针对特定的设备进行配置
if [[ "$(hostname)" == "myServer" ]]; then {do_something}; fi

若配置文件支持include功能,可以:

1
2
[include]
path = ~/.gitconfig_local

在bash和zsh中同时启用一些别名,可以写在 .aliases里,同时在这两个shell应用:

1
2
3
4
# Test if ~/.aliases exists and source it
if [ -f ~/.aliases ]; then
source ~/.aliases
fi

远端设备

安全shell(SSH)
连接服务器:

1
2
ssh [email protected]
ssh [email protected]

@后可接URL或者IP

执行命令

直接远程执行命令:

1
ssh foobar@server ls //直接在foobar的命令下执行ls命令

SSH密钥

私钥登录可避免每次登录都输入密码,通常位于 ~/.ssh/id_rsa 或者 ~/.ssh/id_ed25519

密钥生成

使用ssh-keygen生成一对密钥

1
ssh-keygen -o -a 100 -t ed25519 -f ~/.ssh/id_ed25519

可以为密钥设置密码,防止有人持有您的私钥并使用它访问您的服务器。
您可以使用 ssh-agent 或 gpg-agent ,这样就不需要每次都输入该密码了

检查您是否持有密码并验证它,您可以运行 ssh-keygen -y -f /path/to/key.

基于密钥的认证机制

ssh 会查询 .ssh/authorized_keys 来确认那些用户可以被允许登录。您可以通过下面的命令将一个公钥拷贝到这里:

1
cat .ssh/id_ed25519.pub | ssh foobar@remote 'cat >> ~/.ssh/authorized_keys'

如果支持 ssh-copy-id 的话:

1
ssh-copy-id -i .ssh/id_ed25519.pub foobar@remote

通过SSH复制文件

ssh+tee, 最简单的方法是执行 ssh 命令,然后通过这样的方法利用标准输入实现,tee 命令会将标准输出写入到一个文件;

1
cat localfile | ssh remote_server tee serverfile

scp :当需要拷贝大量的文件或目录时,使用scp 命令则更加方便,因为它可以方便的遍历相关路径:

1
scp path/to/local_file remote_host:path/to/remote_file;

rsync 对 scp 进行了改进,它可以检测本地和远端的文件以防止重复拷贝。它还可以提供一些诸如符号连接、权限管理等精心打磨的功能。甚至还可以基于 –partial标记实现断点续传。rsync 的语法和scp类似;

端口转发

监听特定设备的端口。本地/远程端口转发。本机可用 localhost:PORT 或 127.0.0.1:PORT

远程服务器:常见的情景是使用本地端口转发,即远端设备上的服务监听一个端口,而您希望在本地设备上的一个端口建立连接并转发到远程端口上。例如,我们在远端服务器上运行 Jupyter notebook 并监听 8888 端口。 然后,建立从本地端口 9999 的转发,使用ssh -L 9999:localhost:8888 foobar@remote_server 。这样只需要访问本地的 localhost:9999 即可

SSH配置

可以:

1
alias my_server="ssh -i ~/.id_ed25519 --port 2222 -L 9999:localhost:8888 foobar@remote_server

更好方法:使用~/.ssh/config

1
2
3
4
5
6
7
8
9
10
Host vm
User foobar
HostName 172.16.174.141
Port 2222
IdentityFile ~/.ssh/id_ed25519
LocalForward 9999 localhost:8888

# 在配置文件中也可以使用通配符
Host *.mit.edu
User foobaz

服务器侧的配置通常放在 /etc/ssh/sshd_config。您可以在这里配置免密认证、修改 ssh 端口、开启 X11 转发等等。 您也可以为每个用户单独指定配置。

杂项

连接远程服务器,Mosh(即 mobile shell )对 ssh 进行了改进,它允许连接漫游、间歇连接及智能本地回显

有时将一个远端文件夹挂载到本地会比较方便, sshfs 可以将远端服务器上的一个文件夹挂载到本地,然后您就可以使用本地的编辑器了

Shell & 框架

zsh shell 是 bash 的超集并提供了一些方便的功能。
比较流行的通用框架包括prezto 或 oh-my-zsh。

终端模拟器

选择合适的终端模拟器并进行终端设置

版本控制(Git)

VCSs:版本控制系统,追踪源代码改动的工具。
Git里文件称为Blob对象(数据对象),目录称为树。
历史记录是一个由快照组成的有向无环图。
Git对象基于SHA-1 哈希寻址

1
git cat-file -p 698281bc680d1995c5f4caaf3359721a5a58d48d //可通过类似的命令通过哈希值进行可视化

快照可用引用来表示某个提交,例如master。HAED为当前位置

丢弃未提交的修改和将 ‘master’ 引用指向提交 5d83f9e:

1
git checkout master; git reset --hard 5d83f9e

Git的命令行接口

基础

1
2
3
4
5
6
7
8
9
10
git help <command>: 获取 git 命令的帮助信息
git init: 创建一个新的 git 仓库,其数据会存放在一个名为 .git 的目录下
git status: 显示当前的仓库状态
git add <filename>: 添加文件到暂存区
git commit: 创建一个新的提交
git log: 显示历史日志
git log --all --graph --decorate: 可视化历史记录(有向无环图)
git diff <filename>: 显示与暂存区文件的差异
git diff <revision> <filename>: 显示某个文件两个版本之间的差异
git checkout <revision>: 更新 HEAD 和目前的分支

分支和合并

1
2
3
4
5
6
7
git branch: 显示分支
git branch <name>: 创建分支
git checkout -b <name>: 创建分支并切换到该分支
相当于 git branch <name>; git checkout <name>
git merge <revision>: 合并到当前分支
git mergetool: 使用工具来处理合并冲突
git rebase: 将一系列补丁变基(rebase)为新的基线

远端操作

1
2
3
4
5
6
7
git remote: 列出远端
git remote add <name> <url>: 添加一个远端
git push <remote> <local branch>:<remote branch>: 将对象传送至远端并更新远端引用
git branch --set-upstream-to=<remote>/<remote branch>: 创建本地和远端分支的关联关系
git fetch: 从远端获取对象/索引
git pull: 相当于 git fetch; git merge
git clone: 从远端下载仓库

撤销

1
2
3
4
git commit --amend: 编辑提交的内容或信息
git reset HEAD <file>: 恢复暂存的文件
git checkout -- <file>: 丢弃修改
git restore: git2.32版本后取代git reset 进行许多撤销操作

Git 高级操作

1
2
3
4
5
6
7
8
git config: Git 是一个 高度可定制的 工具
git clone --depth=1: 浅克隆(shallow clone),不包括完整的版本历史信息
git add -p: 交互式暂存
git rebase -i: 交互式变基
git blame: 查看最后修改某行的人
git stash: 暂时移除工作目录下的修改内容
git bisect: 通过二分查找搜索历史记录
.gitignore: 指定 故意不追踪的文件

调试及性能分析

打印调试法与日志

  1. 添加打印语句
  2. 使用日志

可以使终端信息具有颜色,ANSI escape code是一系列特殊字符,可以改变输出结果的颜色

1
echo -e "\e[38;2;255;0;0mThis is red\e[0m" //会打印红色的字符串

第三方日志系统

UNIX系统保存程序日志在 /var/log
linux系统中systemd将日志存放在/var/log/journal中,可以使用journalctl命令显示消息

对于大多数UNIX系统,可以用dmesg读取内核日志

将日志加入系统日志,可以使用logger命令

1
2
3
4
5
logger "Hello Logs"
# On macOS
log show --last 1m | grep Hello
# On Linux
journalctl --since "1m ago" | grep Hello

lnav类的工具可以提供更好的展现和浏览方式

调试器

python调试器为pdb

1
2
3
4
5
6
7
l(ist) - 显示当前行附近的11行或继续执行之前的显示;
s(tep) - 执行当前行,并在第一个可能的地方停止;
n(ext) - 继续执行直到当前函数的下一条语句或者 return 语句;
b(reak) - 设置断点(基于传入的参数);
p(rint) - 在当前上下文对表达式求值并打印结果。还有一个命令是pp ,它使用 pprint 打印;
r(eturn) - 继续执行直到当前函数返回;
q(uit) - 退出调试器。

因为 Python 是一种解释型语言,所以我们可以通过 pdb shell 执行命令。 ipdb 是一种增强型的 pdb ,它使用IPython 作为 REPL并开启了 tab 补全、语法高亮、更好的回溯和更好的内省,同时还保留了pdb 模块相同的接口

对类C语言,gdb,pwndbg,lldb,允许探索底层信息

专门工具

追踪程序的系统调用,linux命令strace
如下为显示ls执行系统调用:

1
2
# On Linux
sudo strace -e lstat ls -l > /dev/null

查看网络数据包定位问题,工具:tcpdump,Wireshark
web开发,浏览器开发者工具

静态分析

python使用pyflakes分析代码,mypy

风格/安全检查:vim中为ale或syntastic,在python中为pylint,pep8;bandit用于检查安全相关问题

性能分析

用户时间+系统时间 代表进程消耗实际CPU
真实时间为从程序开始到结束流失的真实时间,包括等待网络等等的时间
User - CPU 执行用户代码所花费的时间;
Sys - CPU 执行系统内核代码所花费的时间。

性能分析工具

CPU:追踪分析器及采样分析器
追踪分析器 会记录程序的每一次函数调用,而采样分析器则只会周期性的监测(通常为每毫秒)您的程序并记录程序堆栈。它们使用这些记录来生成统计信息,显示程序在哪些事情上花费了最多的时间。

python使用cProfile模块分析函数调用消耗时间

1
python -m cProfile -s tottime grep.py 1000 '^(import|\s*def)[^,]*$' *.py

line_profiler,它会基于行来显示时间

1
kernprof -l -v a.py

内存:C类语言可以使用Valgrind检查内存泄漏
memory-profiler

1
python -m memory_profiler example.py

事件分析
perf只报告与程序相关的系统事件

1
2
3
4
perf list - 列出可以被 pref 追踪的事件;
perf stat COMMAND ARG1 ARG2 - 收集与某个进程或指令相关的事件;
perf record COMMAND ARG1 ARG2 - 记录命令执行的采样信息并将统计数据储存在perf.data中;
perf report - 格式化并打印 perf.data 中的数据。

可视化
显示CPU分析数据形式:火焰图( Y 轴显示函数调用关系,并在 X 轴显示其耗时的比例。可显示栈追踪)
调用图,控制流图
在python中可使用pycallgraph生成这些图片

资源监控
通用监控:htop,显示进程的统计信息,<F6> 进程排序、 t 显示树状结构和 h 打开或折叠线程。
glances类似。dstat可以实时计算不同子系统资源的度量数据

IO操作:iotop,显示实时 I/O 占用信息而且可以非常方便地检查某个进程是否正在执行大量的磁盘读写操作

磁盘使用 - df 可以显示每个分区的信息,而 du 则可以显示当前目录下每个文件的磁盘使用情况( disk usage)。-h 选项可以使命令以对人类(human)更加友好的格式显示数据;ncdu是一个交互性更好的 du ,它可以让您在不同目录下导航、删除文件和文件夹;

内存使用 - free 可以显示系统当前空闲的内存。内存也可以使用 htop 这样的工具来显示;

打开文件 - lsof 可以列出被进程打开的文件信息。 当我们需要查看某个文件是被哪个进程打开的时候,这个命令非常有用;

网络连接和配置 - ss 能帮助我们监控网络包的收发情况以及网络接口的显示信息。ss 常见的一个使用场景是找到端口被进程占用的信息。如果要显示路由、网络设备和接口信息,您可以使用 ip 命令。注意,netstat 和 ifconfig 这两个命令已经被前面那些工具所代替了。

网络使用 - nethogs 和 iftop 是非常好的用于对网络占用进行监控的交互式命令行工具。
如果您希望测试一下这些工具,您可以使用 stress 命令来为系统人为地增加负载。

专用工具
进行基准测试,hyperfine
例:比较fd与find:

1
hyperfine --warmup 3 'fd -e jpg' 'find . -iname "*.jpg"'

元编程

构建系统

make,中小型系统,执行make时会参考当前目录下Makefile文件

1
2
3
4
5
paper.pdf: paper.tex plot-data.png
pdflatex paper.tex

plot-%.png: %.dat plot.py
./plot.py -i $*.dat -o $@

冒号左侧是构建目标,右侧是构建所需依赖,缩进部分是用到的一些命令

依赖管理

Ubuntu软件仓库,apt访问

语义版本号,主版本号.次版本号.补丁号:
如果新的版本没有改变 API,请将补丁号递增;
如果您添加了 API 并且该改动是向后兼容的,请将次版本号递增;
如果您修改了 API 但是它并不向后兼容,请将主版本号递增

锁文件:锁文件列出了您当前每个依赖所对应的具体版本号。
一种极端的依赖锁定叫做 vendoring,它会把您的依赖中的所有代码直接拷贝到您的项目中,这样您就能够完全掌控代码的任何修改,同时您也可以将自己的修改添加进去,不过这也意味着如果该依赖的维护者更新了某些代码,您也必须要自己去拉取这些更新。

持续集成系统

持续集成(CI):当您的代码变动时,自动运行的东西。
Travis CI、Azure Pipelines 和 GitHub Actions

测试

测试套件:所有测试的统称。
单元测试:一种“微型测试”,用于对某个封装的特性进行测试。
集成测试:一种“宏观测试”,针对系统的某一大部分进行,测试其不同的特性或组件是否能协同工作。
回归测试:一种实现特定模式的测试,用于保证之前引起问题的 bug 不会再次出现。
模拟(Mocking): 使用一个假的实现来替换函数、模块或类型,屏蔽那些和测试不相关的内容。例如,您可能会“模拟网络连接” 或 “模拟硬盘”。

安全和密码学

熵(Entropy) 度量了不确定性并可以用来决定密码的强度。
熵的单位是 比特。对于一个均匀分布的随机离散变量,熵等于log_2(所有可能的个数,即n)

散列函数

密码散列函数 (Cryptographic hash function) 可以将任意大小的数据映射为一个固定大小的输出。

SHA-1是Git中使用的一种散列函数, 它可以将任意大小的输入映射为一个160比特(可被40位十六进制数表示)的输出。对于相同的字符,输出固定。

1
2
$ printf 'hello' | sha1sum
aaf4c61ddcc5e8a2dabede0f3b482cd9aea9434d

性质
确定性:对于不变的输入永远有相同的输出。
不可逆性:对于hash(m) = h,难以通过已知的输出h来计算出原始输入m。
目标碰撞抵抗性/弱无碰撞:对于一个给定输入m_1,难以找到m_2 != m_1且hash(m_1) = hash(m_2)。
碰撞抵抗性/强无碰撞:难以找到一组满足hash(m_1) = hash(m_2)的输入m_1, m_2(该性质严格强于目标碰撞抵抗性)。

密码散列函数的应用

Git中的内容寻址存储
文件的信息摘要(Message digest),可依据哈希值确认两文件是否一样
承诺机制,验证哈希值来检查先前承诺的值

密钥生成函数

从密码生成可以在其他加密算法中使用的密钥,比如对称加密算法。
存储登录凭证时不可直接存储明文密码。

正确的方法是针对每个用户随机生成一个盐 salt = random(), 并存储盐,以及密钥生成函数对连接了盐的明文密码生成的哈希值KDF(password + salt)。
在验证登录请求时,使用输入的密码连接存储的盐重新计算哈希值KDF(input + salt),并与存储的哈希值对比

对称加密

1
2
3
4
keygen() -> key  (这是一个随机方法)

encrypt(plaintext: array<byte>, key) -> array<byte> (输出密文)
decrypt(ciphertext: array<byte>, key) -> array<byte> (输出明文)

常用的对称加密系统:AES

应用:加密不信任的云服务上存储的文件。对称加密和密钥生成函数配合起来,就可以使用密码加密文件: 将密码输入密钥生成函数生成密钥 key = KDF(passphrase),然后存储encrypt(file, key)

非对称加密

非对称加密的“非对称”代表在其环境中,使用两个具有不同功能的密钥: 一个是私钥(private key),不向外公布;另一个是公钥(public key),公布公钥不像公布对称加密的共享密钥那样可能影响加密体系的安全性。

1
2
3
4
5
6
7
keygen() -> (public key, private key)  (这是一个随机方法)

encrypt(plaintext: array<byte>, public key) -> array<byte> (输出密文)
decrypt(ciphertext: array<byte>, private key) -> array<byte> (输出明文)

sign(message: array<byte>, private key) -> array<byte> (生成签名)
verify(message: array<byte>, signature: array<byte>, public key) -> bool (验证签名是否是由和这个公钥相关的私钥生成的)

对称加密有密钥即可加解密,非对称加密任何拥有公钥的人都可以加密,但只有私钥的人可解密。

在不知道 私钥 的情况下,不管需要签名的信息为何,很难计算出一个可以使 verify(message, signature, public key) 返回为真的签名。
对于使用私钥签名的信息,验证方法验证和私钥相对应的公钥时一定返回为真: verify(message, sign(message, private key), public key) = true

应用
PGP电子邮件加密:用户可以将所使用的公钥在线发布,比如:PGP密钥服务器或 Keybase。任何人都可以向他们发送加密的电子邮件。
聊天加密:像 Signal 和 Keybase 使用非对称密钥来建立私密聊天。
软件签名:Git 支持用户对提交(commit)和标签(tag)进行GPG签名。任何人都可以使用软件开发者公布的签名公钥验证下载的已签名软件。

密钥分发

非对称加密面对的主要挑战是,如何分发公钥并对应现实世界中存在的人或组织。

Signal的信任模型是,信任用户第一次使用时给出的身份(trust on first use),同时支持用户线下(out-of-band)、面对面交换公钥(Signal里的safety number)。

PGP使用的是信任网络。简单来说,如果我想加入一个信任网络,则必须让已经在信任网络中的成员对我进行线下验证,比如对比证件。验证无误后,信任网络的成员使用私钥对我的公钥进行签名。这样我就成为了信任网络的一部分。只要我使用签名过的公钥所对应的私钥就可以证明“我是我”。

Keybase主要使用社交网络证明 (social proof),和一些别的精巧设计

案例分析

密码管理器
密码管理器会帮助你对每个网站生成随机且复杂(表现为高熵)的密码,并使用你指定的主密码配合密钥生成函数来对称加密它们

两步验证(双因子验证)
两步验证(2FA)要求用户同时使用密码(“你知道的信息”)和一个身份验证器(“你拥有的物品”,比如YubiKey)来消除密码泄露或者钓鱼攻击的威胁。

全盘加密
对笔记本电脑的硬盘进行全盘加密是防止因设备丢失而信息泄露的简单且有效方法。 Linux的cryptsetup + LUKS, Windows的BitLocker,或者macOS的FileVault都使用一个由密码保护的对称密钥来加密盘上的所有信息。

聊天加密
Signal和Keybase使用非对称加密对用户提供端到端(End-to-end)安全性。

获取联系人的公钥非常关键。为了保证安全性,应使用线下方式验证Signal或者Keybase的用户公钥,或者信任Keybase用户提供的社交网络证明。

SSH
我们在之前的一堂课讨论了SSH和SSH密钥的使用。那么我们今天从密码学的角度来分析一下它们。

当你运行ssh-keygen命令,它会生成一个非对称密钥对:公钥和私钥(public_key, private_key)。 生成过程中使用的随机数由系统提供的熵决定。这些熵可以来源于硬件事件(hardware events)等。 公钥最终会被分发,它可以直接明文存储。 但是为了防止泄露,私钥必须加密存储。ssh-keygen命令会提示用户输入一个密码,并将它输入密钥生成函数 产生一个密钥。最终,ssh-keygen使用对称加密算法和这个密钥加密私钥。

在实际运用中,当服务器已知用户的公钥(存储在.ssh/authorized_keys文件中,一般在用户HOME目录下),尝试连接的客户端可以使用非对称签名来证明用户的身份——这便是挑战应答方式。 简单来说,服务器选择一个随机数字发送给客户端。客户端使用用户私钥对这个数字信息签名后返回服务器。 服务器随后使用.ssh/authorized_keys文件中存储的用户公钥来验证返回的信息是否由所对应的私钥所签名。这种验证方式可以有效证明试图登录的用户持有所需的私钥

大杂烩

修改键位映射

macOS - karabiner-elements, skhd 或者 BetterTouchTool
Linux - xmodmap 或者 Autokey
Windows - 控制面板,AutoHotkey 或者 SharpKeys
QMK - 如果你的键盘支持定制固件,QMK 可以直接在键盘的硬件上修改键位映射。保留在键盘里的映射免除了在别的机器上的重复配置。

守护进程

大部分计算机都有一系列在后台保持运行,不需要用户手动运行或者交互的进程。这些进程就是守护进程。以守护进程运行的程序名一般以 d 结尾,比如 SSH 服务端 sshd,用来监听传入的 SSH 连接请求并对用户进行鉴权。

linux中的systemd是常用的配置和运行守护进程的方法
systemctl status可以看到正在运行的守护进程
用户使用 systemctl 命令和 systemd 交互来enable(启用)、disable(禁用)、start(启动)、stop(停止)、restart(重启)、或者status(检查)配置好的守护进程及系统服务。

如果你只是想定期运行一些程序,可以直接使用 cron。它是一个系统内置的,用来执行定期任务的守护进程。

FUSE

FUSE(用户空间文件系统)允许运行在用户空间上的程序实现文件系统调用,并将这些调用与内核接口联系起来。在实践中,这意味着用户可以在文件系统调用中实现任意功能。

一些有趣的 FUSE 文件系统包括:
sshfs:使用 SSH 连接在本地打开远程主机上的文件
rclone:将 Dropbox、Google Drive、Amazon S3、或者 Google Cloud Storage 一类的云存储服务挂载为本地文件系统
gocryptfs:覆盖在加密文件上的文件系统。文件以加密形式保存在磁盘里,但该文件系统挂载后用户可以直接从挂载点访问文件的明文
kbfs:分布式端到端加密文件系统。在这个文件系统里有私密(private),共享(shared),以及公开(public)三种类型的文件夹
borgbackup:方便用户浏览删除重复数据后的压缩加密备份

备份

有效备份方案的几个核心特性是:版本控制,删除重复数据,以及安全性。

API

这些 API 大多具有类似的格式。它们的结构化 URL 通常使用 api.service.com 作为根路径,用户可以访问不同的子路径来访问需要调用的操作,以及添加查询参数使 API 返回符合查询参数条件的结果。

可使用curl获得URL,返回通常是JSON格式,可用jq等工具选取需要的部分

有些需要认证的API要求用户在请求中加入私密令牌,大多数API会使用OAuth

IFTTT(If This Then That) 这个网站可以将很多 API 整合在一起,让某 API 发生的特定事件触发在其他 API 上执行的任务。

常见命令行标志参数及模式

大部分工具支持 –help 或者类似的标志参数(flag)来显示它们的简略用法。

会造成不可撤回操作的工具一般会提供“空运行”(dry run)标志参数,这样用户可以确认工具真实运行时会进行的操作。这些工具通常也会有“交互式”(interactive)标志参数,在执行每个不可撤回的操作前提示用户确认。

–version 或者 -V 标志参数可以让工具显示它的版本信息(对于提交软件问题报告非常重要)。

基本所有的工具支持使用 –verbose 或者 -v 标志参数来输出详细的运行信息。多次使用这个标志参数,比如 -vvv,可以让工具输出更详细的信息(经常用于调试)。同样,很多工具支持 –quiet 标志参数来抑制除错误提示之外的其他输出。

大多数工具中,使用 - 代替输入或者输出文件名意味着工具将从标准输入(standard input)获取所需内容,或者向标准输出(standard output)输出结果。

会造成破坏性结果的工具一般默认进行非递归的操作,但是支持使用“递归”(recursive)标志函数(通常是 -r)。

有的时候你可能需要向工具传入一个 看上去 像标志参数的普通参数,比如:
使用 rm 删除一个叫 -r 的文件;
在通过一个程序运行另一个程序的时候(ssh machine foo),向内层的程序(foo)传递一个标志参数。
这时候你可以使用特殊参数 – 让某个程序 停止处理 – 后面出现的标志参数以及选项(以 - 开头的内容):

1
2
rm -- -r 会让 rm 将 -r 当作文件名;
ssh machine --for-ssh -- foo --for-foo 的 -- 会让 ssh 知道 --for-foo 不是 ssh 的标志参数。

窗口管理器

堆叠式管理器:拖拽式窗口,窗口可堆叠
平铺式管理器:将窗口平铺在一起,窗口不重叠,可像tmux一样用键盘进行切换等操作

VPN

VPN 只是把原本对网络供应商的信任放在了 VPN 供应商那里。有数据泄露的安全风险。

Markdown

轻量化的标记语言

用*包围的文字表示强调(斜体),或者用**表示特别强调(粗体);
以#开头的行是标题,#的数量表示标题的级别,比如:##二级标题;
以-开头代表一个无序列表的元素。一个数字加.(比如1.)代表一个有序列表元素;
反引号 `(backtick)包围的文字会以代码字体显示。如果要显示一段代码,可以在每一行前加四个空格缩进,或者使用三个反引号包围整个代码片段
如果要添加超链接,将 需要显示 的文字用方括号包围,并在后面紧接着用圆括号包围链接:显示文字

Hammerspoon (macOS 桌面自动化)

Hammerspoon 是面向 macOS 的一个桌面自动化框架。它允许用户编写和操作系统功能挂钩的 Lua 脚本,从而与键盘、鼠标、窗口、文件系统等交互。

示例应用:
绑定移动窗口到的特定位置的快捷键
创建可以自动将窗口整理成特定布局的菜单栏按钮
在你到实验室以后,通过检测所连接的 WiFi 网络自动静音扬声器
在你不小心拿了朋友的充电器时弹出警告

开机引导以及 Live USB

在你的计算机启动时,BIOS 或者 UEFI 会在加载操作系统之前对硬件系统进行初始化,这被称为引导(booting)。

Live USB 是包含了完整操作系统的闪存盘。Live USB 的用途非常广泛,包括:

  • 作为安装操作系统的启动盘;
  • 在不将操作系统安装到硬盘的情况下,直接运行 Live USB 上的操作系统;
  • 对硬盘上的相同操作系统进行修复;
  • 恢复硬盘上的数据。

Docker, Vagrant, VMs, Cloud, OpenStack

虚拟机(Virtual Machine)以及容器化(containerization)等工具可以帮助你模拟一个包括操作系统的完整计算机系统。虚拟机可以用于创建独立的测试或者开发环境,以及用作安全测试的沙盒。

Vagrant 是一个构建和配置虚拟开发环境的工具。它支持用户在配置文件中写入比如操作系统、系统服务、需要安装的软件包等描述,然后使用 vagrant up 命令在各种环境(VirtualBox,KVM,Hyper-V等)中启动一个虚拟机。Docker 是一个使用容器化概念的类似工具。

交互式记事本编程

交互式记事本可以帮助开发者进行与运行结果交互等探索性的编程。现在最受欢迎的交互式记事本环境大概是 Jupyter。它的名字来源于所支持的三种核心语言:Julia、Python、R。Wolfram Mathematica 是另外一个常用于科学计算的优秀环境。

GitHub

最受欢迎的开源软件开发平台之一。
issue,pull request,fork等等

Q & A

  1. source script.sh 和 ./script.sh 有什么区别?
    前者在当前会话中执行,而后者会新建一个会话执行

  2. 一些文件讲解
    /bin - 基本命令二进制文件
    /sbin - 基本的系统二进制文件,通常是root运行的
    /dev - 设备文件,通常是硬件设备接口文件
    /etc - 主机特定的系统配置文件
    /home - 系统用户的主目录
    /lib - 系统软件通用库
    /opt - 可选的应用软件
    /sys - 包含系统的信息和配置(第一堂课介绍的)
    /tmp - 临时文件( /var/tmp ) 通常重启时删除
    /usr/ - 只读的用户数据
    /usr/bin - 非必须的命令二进制文件
    /usr/sbin - 非必须的系统二进制文件,通常是由root运行的
    /usr/local/bin - 用户编译程序的二进制文件
    /var -变量文件 像日志或缓存

  3. 性能分析工具:
    print timing
    Valgrind的Callgrind计算时间和调用堆栈,生成带注释的代码版本,但运行速度降低一个数量级,不支持线程
    perf采样性能分析器
    Flamegraphs可视化工具
    用户内核跟踪eBPF,低级性能分析bpftrace