MIT 的 missing-semester 课程学习笔记,学一些常用的计算机工具,总时长大概 1111 个小时,不算太长。

Lecture 1: The Shell

查询时间

date # Sat Jul  1 16:38:09 CST 2023

打印

echo hello # hello

查询程序地址

which echo # /usr/bin/echo

查询当前目录

pwd # /mnt/c/Users/屋顶上的小丑

改变当前目录

cd /xxx # 打开 xxx 目录
cd ./xxx # 打开当前目录下的 xxx
cd .. # 回到父目录
cd - # 回到前一个所在的目录
cd ~ # 打开主目录

查看当前目录文件

ls # 当前目录下的文件:xxx...
ls .. # 父目录下的文件:xxx...
ls -l # 给出当前目录下文件的更多信息
# -a 查看所有文件 -h 以人类可以理解的格式输出,例如M和G,-t 以最近访问顺序排序,--color,以彩色文本输出

第一位字符中 d 表示该文件是一个目录。后面九个字符每三个字符为一组,分别代表文件所有者,用户组及其他所有人的权限。三个字符分别为 rwx,代表读入,写入与执行的权限。对于文件来说很好理解,对于目录来说 r 代表能否查看目录下的文件,w 代表能否重命名,创建和删除目录下的文件,x 代表能否搜索(如果要进入某个目录,需要对路径上的所有目录都有执行权限)。- 代表没有对应的权限。

查看帮助

xxx --help # 给出 xxx 指令的用法

重命名或移动文件

mv xxx yyy # 将文件 xxx 重命名为 yyy,或者将文件/目录 xxx 移动到 yyy 目录下

复制文件

cp xxx yyy # 将文件 xxx 复制到目录 yyy 下

删除文件

rm xxx # 删除文件 xxx

删除目录

rm -r xxx # 递归删除目录 xxx 及其下面的所有文件
rmdir xxx # 删除目录 xxx,当且仅当 xxx 为空

创建目录

mkdir xxx # 创建目录 xxx

查询手册

man xxx # 查询命令 xxx 的手册

可以用 tldr 看更简洁的手册。

清空命令行

ctrl+L # 清空命令行

输入输出流

echo xx > file # 覆盖输出 xx 到文件 file 中(不存在就新建)
echo xx >> file # 追加输出 xx 到文件 file 中

输出文件内容

cat file # 输出 file 中的内容
cat < file # 把 file 中的内容当作输入,由于此时未指定输出,因此默认为控制台,也会把内容输出到命令行中
cat < file > file2 # 把 file 中的内容当作输入输出到 file2 中

管道

| # 将 | 左侧的程序输出作为右侧程序的输入
ls -l / | tail -n1 # 将 ls -l / 当作输入,并输出最后一行到命令行中

进入 root 模式

sudo xxx # 以 root 的身份执行 xxx
sudo su # 切换用户为 root
exit # root 登出

/sys 目录写入内容只有 root 能办到。

多重定向

xxx tee yyy # 把输入 xxx 输出到 yyy 中,同时在屏幕上输出,即同时打印内容和写入文件

创建文件

touch xxx # 创建文件 xxx

Lecture 2: Shell Tools and Scripting

变量赋值

foo=bar # 把 foo 变量的值赋为 bar,不能有空格
echo $foo # bar

export 可以设置环境变量,全局 shell 有效。

字符串输出

echo '$foo' # 输出 $foo,单引号不转义
echo "$foo" # 输出 bar,双引号转义

特殊参数

  • $0 - 脚本名

  • $1$9 - 脚本的参数。 $1 是第一个参数,依此类推。

  • $@ - 所有参数

  • $# - 参数个数

  • $? - 前一个命令的返回值

  • $$ - 当前脚本的进程识别码

  • !! - 完整的上一条命令,包括参数。常见应用:当你因为权限不足执行命令失败时,可以使用 sudo !!再尝试一次。

  • $_ - 上一条命令的最后一个参数。如果你正在使用的是交互式 shell,你可以通过按下 Esc 之后键入 . 来获取这个值。

逻辑运算符

如果指令正常执行则返回 00,否则说明有错误发生。true 的返回值一直是 00false 的返回值一直是 11。以上可以通过 && || 连接,都是短路运算符。

命令替换

foo=$(pwd) # foo 变量存储着 pwd 这个指令,因此通过 $foo 得到的是 pwd 的返回值
echo "We are in $(pwd)" # 会将 pwd 的返回值替换 $(pwd) 再输出

进程替换

cat <(ls) <(ls ..) # 输出是当前目录和父目录下的文件的拼接。因为 <(ls) 会将 ls 的输出存入某个临时文件,然后再用这个临时文件替换掉 <(ls)。
diff <(ls foo) <(ls bar) # 显示这 foo 和 bar 两个文件夹的区别

实例

#!/bin/bash

echo "Starting program at $(date)" # date会被替换成日期和时间

echo "Running program $0 with $# arguments with pid $$"

for file in "$@"; do
    grep foobar "$file" > /dev/null 2> /dev/null
    # 如果模式没有找到,则grep退出状态为 1
    # 我们将标准输出流和标准错误流重定向到Null,因为我们并不关心这些信息
    if [[ $? -ne 0 ]]; then
        echo "File $file does not have any foobar, adding one"
        echo "# foobar" >> "$file"
    fi
done

作用是扫描参数提供的所有文件,如果没找到 foobar 就在文件末尾追加一个。

其中 2> 为错误输出流,> 为正常输出流,即 strerrstdout,可以将两者结合为 &>-ne 即不等号

通配

? 匹配一个字符,* 匹配任意个字符。

{} 会把括号里的内容扩展开。例如 convert a.txt a.inconvert a.{txt,in} 是一样的。

shebang

#!/usr/local/bin/python
#!/usr/local/bin/env python
import sys
for arg in reversed(sys.argv[1:]):
    print(arg)

第一行第二行即为 shebang,代表了 shell 去用这一行路径的东西来运行脚本。第二行使用了 env,即环境变量。

报错

要先安装 shellcheck 包。

shellcheck xxx.sh # 检查 xxx.sh 的错误并报告

查找文件

# 查找所有名称为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 \;

-empty 可以查找空文件和目录。-mtime 代表文件上次修改的时间,-ctime 代表文件创造的时间,-atime 代表文件上次访问的时间。-print0 会直接将文件名后加 \0 一起输出,默认则是输出一个文件换行再输出。

可以用 fd 替代,fd PATTERN 代表以 PATTERN 模式搜索。用 locate 能更加高效,因为它依靠索引来查找,类似数据库,但是只能通过文件名查找。

查找代码

grep "xxx" yyy # 查找文件 yyy 中所有包含子串"xxx" 的行并输出,-i 表示不区分大小写,-n 可以打印行数,--color 可以高亮关键字,-C num 代表查找结果上下文 num 行,-v 将结果反选,-R 会递归进入子目录查找所有文件,-c 只输出行数

ripgrep(rg) 更加快速。

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

查找 shell 命令

history 能输出输入的历史命令。能结合 | 查找特定的命令。用 ctrl+R 也能查找历史,可以配合模糊查找工具 fzf 使用。

文件夹导航

ls -R 就能递归输出目录内的文件结构,用 tree 或者 broot 能够输出目录树,或者用更完整的文件管理器 nnn 或者 ranger

fasd 或者 autojump 可以查找最常用或最近使用的文件和目录。

分支与循环

if [[ condition ]]; then
	#statements
fi

while [[ condition ]]; do
	#statements
done

until [[ condition ]]; do
	#statements
done

for (( i = 0; i < 10; i++ )); do
	#statements
done

统计数量

wc -c # 统计字节数
wc -l # 统计行数
wc -w # 统计词数
wc -m # 统计字符数

xargs

xargs 可以将输入分割成批,每个批中有很多分割片段,然后将这些片段按批交给 xargs 后面的命令进行处理。简而言之,它可以将标准输入中的内容作为参数传递。

-d 可以自定义一个分割符,以这个符作为分割成多个命令行参数传递给后面指令。特别的,如果分割符是 null,可以直接用 -0

压缩

# 压缩与解压 .tar 文件
tar -cvf xxx.tar ...
tar -xvf xxx.tar
# 压缩与解压 .gz 文件
tar -czvf xxx.tar.gz ...
tar -xzvd xxx.tar.gz
# 压缩与解压 .bz2 文件
tar -cjvf xxx.tar.bz2 ...
tar -xjvf xxx.tar.bz2 ...
# 压缩与解压 .zip 文件
tar -czvf xxx.zip ...
tar -xzvf xxx.zip
# 压缩与解压 .rar 文件
rar a xxx.rar ...
rar x xxx.rar