BUAA-OS-lab0

Dawn

lab0主要侧重工具链的教学,一定程度上培养了同学们读项目的能力。

上机考点可以大致分为 Makefilebash 文件编写,26年恰好把它们分成了两部分,exam只考了 Makefile ,extra只考了 bash

当然如果你不幸被分到了新主机房且恰巧上机时段网络不稳定,可能还会考察一点 git 相关知识。

课下学习

文件操作

ls

  • -l 长格式(权限、属主、大小、时间)
  • -a 显示隐藏
  • -d 只显示目录
  • -R 递归列出子目录
  • -S 按文件大小排序
  • -r 反向

touch

  • 可以批量创建 touch file1 file2 file3
  • 只更新修改时间 touch -m file

mkdir

  • 递归创建父目录 mkdir -p dir
  • 一次创建多个 mkdir dir1 dir2 dir3
  • 设置权限 mkdir -m 755 dir
  • 补充:
    • 不加 -p 时,父目录不存在会报错
    • 目录已存在时,mkdir dir 报错;mkdir -p dir 不报错

cd

  • . 当前目录
  • .. 上级目录

rmdir

删除空目录

rm

  • -r 递归删除
  • -f 强制删除

pwd

显示完整目录

mv

  • 递归复制不加 -r
  • 重命名 mv old.txt new.txt
  • 重命名目录 mv folder1 folder2
  • 移动到目录 mv file folder/
  • 移动多个文件到目录 mv file1 file2 file3 folder/
  • 不覆盖已存在文件 mv -n a.txt /folder

cp

  • 复制多个文件到目录 cp file1 file2 file3 /folder

  • 递归复制目录 cp -r /folder1 /folder2

  • -i 复制前确认

  • -f 强制

  • -n 不覆盖已存在

  • -u 仅当源文件较新时复制

rm

  • 可同时删除多个
  • -r 递归
  • -f 强制
  • -i 删除前确认
  • -v 显示删除过程

tree

  • tree 显示当前目录树 tree dir 显示指定目录树
  • tree -L 2 显示层级
  • tree -d 只显示目录
  • tree -a 显示隐藏文件
  • tree -h tree -p 显示文件大小和权限
  • tree -f 显示完整路径

cat

  • 查看多个文件,顺序拼接 cat file1 file2
  • 带行号 cat -n file
  • 仅给非空行编号 cat -b file
  • 显示不可见字符 cat -A file
  • 显示行尾$ cat -e file
  • 显示tab cat -t file

echo

输出变量/文本到终端

  • 输出字符串 echo "hello"
  • 输出变量的值 name="dawn";echo "$$name"(在Makefile里要用$$获得变量的值)
  • 常见选项
    • -n 不换行
    • -e 启用转义 echo -e "a\nb\tc"
    • -E 禁用转义 echo -E "a\nb\tc"
  • -n 显示前x行内容
  • -c 显示前x字节内容

tail

  • -n 显示后x行内容
  • -c 显示后x字节内容
  • -f 输出后续添加内容

查找操作

find

find FileDir

  • -name 按文件名 ,不区分大小写
  • -iname 按文件名区分大小写
  • -type f 只看文件
  • -type d 只看文件夹
  • -maxdepth 2 最大搜索层次
  • -mindepth 1 最小搜索层次
  • -perm 644 按权限搜索
  • -path ./xxx/file 指定文件夹搜索

grep

grep content Dir

  • -i 忽略大小写

  • -n 显示行号

  • -r 递归搜索

    如果Dir位置是一个文件名,默认省略文件名前缀,只显示 行号:内容

    强制显示要带 -H

    如果Dir位置真的是Dir,显示结果为 Dir/…/file:行号:内容

    注意这里 ./DirDir 亦有区别

  • -w 整词搜索

  • -x 整行匹配(一模一样)

  • -c 统计content在文件夹下每个文件内出现次数

wc

  • 统计行数 wc -l file
  • 统计字符数 wc -m file
  • 统计单词数 wc -w file
  • 统计字节数 wc -c file
  • 只想获得数字可搭配重定向 wc -l < file

或许也可用循环做

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
统计行数:

count=0
while read -r line; do
count=$((count + 1))
done < file.txt

echo "$count"

count=0
while read -r line || [ -n "$line" ]; do
count=$((count + 1))
done < file.txt

echo "$count"

统计字符数:
count=0
while read -r line || [ -n "$line" ]; do
count=$((count + ${#line} + 1))
done < file.txt

echo "$count"

编辑操作

chmod

1
2
3
4
5
-rwxr-xr--
↑↑↑↑↑↑↑↑↑
│└──┘└──┘└──┘
│ owner group others
文件类型(- 文件, d 目录)

每组三位分别是 r(读)、w(写)、x(执行),没有权限用 - 表示

三位代表的2进制数传化成十进制

  • 数字模式赋权 chmod num filechmod 440 file1

  • 符号模式赋权

    1
    2
    3
    4
    chmod +(-)x(r\w) file	#所有人
    chmod u(g/o/a)+(-)x(r/w) file #特定人
    chmod u(g/o/a)=x(r/w) #设置
    chmod g-x a+x file # 同时修改多个
  • 递归修改整个目录 -R

  • 查看权限 ls -l file

diff

比较文件差异 diff [option] file1 file2

  • -r 递归比较目录

  • -q 仅显示文件是否不同,不显示具体差异

    diff file1 file2 > /dev/null 2>&1 (防止污染终端)

    echo $? 获取返回值

    • 0相同
    • 1不同
    • 2文件不存在(出错)
  • -i 忽略大小写

  • -w 忽略所有空白

sed

基本语法

1
2
3
sed [选项] '指令' 文件
sed [选项] '指令' << 文件 # 也可以从管道读入
cat file.txt | sed '指令'

替换s

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
26
27
28
29
# 基本替换(只替换每行第一个)
sed 's/old/new/' file.txt

# 全局替换(每行所有匹配)
sed 's/old/new/g' file.txt

# 替换第 2 个匹配
sed 's/old/new/2' file.txt

# 忽略大小写
sed 's/old/new/gi' file.txt

# 直接修改原文件(-i)
sed -i 's/old/new/g' file.txt

# 修改原文件并备份
sed -i.bak 's/old/new/g' file.txt

# 第2行替换
sed '2s/foo/bar/' file

# 包含 error 的行才替换
sed '/error/s/foo/bar/g' file

# 去掉行首空白
sed 's/^[[:space:]]\+//' input.txt

# 替换完整单词
sed 's/\<word\>/replace/g' file.txt

注意,-i在指令外面代表直接修改,在指令末尾代表忽略大小写

g代表一整行,行数的选择看s之前

删除 d

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
# 删除第 3 行
sed '3d' file.txt

# 删除最后一行
sed '$d' file.txt

# 删除第 2 到第 5 行
sed '2,5d' file.txt

# 删除第2行和第5行
sed '2d;5d' file.txt
sed -e '2d' -e '5d' file.txt

# 删除包含某字符串的行
sed '/error/d' file.txt

# 删除以 # 开头的行
sed '/^#/d' file

# 删除空行
sed '/^$/d' file.txt

打印 p(配合 -n 使用)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
# 只打印第 3 行(-n 抑制默认输出)
sed -n '3p' file.txt

# 打印第 2 到第 5 行
sed -n '2,5p' file.txt

# 打印2和5行
sed -n '2p;5p' file.txt

# 打印包含某字符串的行
sed -n '/error/p' file.txt

# 关于最后一行
sed -n "1,$ p" file.txt
sed -n "1,\$p" file.txt
sed -n '1,$p' file.txt
sed -n '1p;$p' file.txt #打印1和最后一行
#因为"会解析$,'不会解析$,所以"要带空格区分,'不用

# 从第一行开始每隔两行打印一行
sed -n '1~2p' file.txtmake

插入 i 和追加 a和代替c

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
# 在第 3 行前插入
sed '3i\新内容' file.txt

# 在第 3 行后追加
sed '3a\新内容' file.txt

# 整行替换
sed '2c\新内容' file.txt

# 在匹配行前插入
sed '/error/i\--- 发现错误 ---' file.txt

# 行首加前缀(如日志级别)
sed 's/^/[INFO] /' input.txt

# 行尾加后缀
sed 's/$/ ;/' input.txt

# 替换某行(指令中可带空格)
sed '1c replace' file

多个指令同时执行

1
2
3
4
5
# 用 -e 串联
sed -e 's/foo/bar/g' -e '/error/d' file.txt

# 或用分号
sed 's/foo/bar/g; /error/d' file.txt

实用例子

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
# 删除行首空格
sed 's/^[ \t]*//' file.txt

# 删除行尾空格
sed 's/[ \t]*$//' file.txt

# 在每行行首加 #
sed 's/^/#/' file.txt

# 打印行号
sed '=' file.txt

# 替换某几行的内容
sed '2,4s/old/new/g' file.txt

# 查看文件第 10 到 20 行
sed -n '10,20p' file.txt

常用选项

选项 含义
-i 直接修改原文件
-n 抑制默认输出
-e 指定多个指令
-r / -E 使用扩展正则表达式

awk

基本格式

awk '条件 {动作}' file

分隔符处理

分隔符可以是字符串or正则表达式

  • 多字符串分隔 awk -F"::" '{print $1,$2}' file
  • 例如“逗号或分号”都算分隔 awk -F'[,;]' '{print $1,$2}' file
1
2
3
4
# 用 : 分列
awk -F: '{print $1,$3}' /etc/passwd
# 输出用逗号分隔
awk 'BEGIN{OFS=","} {print $1,$2}' file
  • 默认空白分隔是特殊规则

  • FS=" " 时,会把连续空格/tab 当一个分隔符,并忽略行首行尾空白

  • |.* 这类正则元字符,按需转义

BEGIN/END 块

awk 'BEGIN{print "start"} {print $1} END{print "end"}' file

统计/计算

1
2
3
4
5
6
# 第一列求和
awk '{sum+=$1} END{print sum}' file
# 按第一列计数
awk '{cnt[$1]++} END{for(k in cnt) print k,cnt[k]}' file
# 第二列最大值
awk 'max<$2{max=$2} END{print max}' file

条件判断

1
2
# 第三列大于100的行
awk '$3>100 {print $1,$3}' file

常用指令

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
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
# 打印整文件
awk '{print}' file

# 打印第 1、3 列
awk '{print $1,$3}' file

# 打印最后一列
awk '{print $NF}' file

# 打印第 2 到最后列
awk '{for(i=2;i<=NF;i++) printf "%s%s",$i,(i==NF?ORS:OFS)}' file

# 显示行号+内容
awk '{print NR,$0}' file

# 只看第 10 行
awk 'NR==10' file

# 打印 10~20 行
awk 'NR>=10 && NR<=20' file

# 跳过表头(从第2行开始)
awk 'NR>1' file

# 匹配包含 error 的行
awk '/error/' file

# 不包含 error 的行
awk '!/error/' file

# 删除空行
awk 'NF' file

# 删除“空白行”(含空格/tab 也算空)
awk '$0 !~ /^[[:space:]]*$/' file

# 以冒号分隔取第1、3列
awk -F: '{print $1,$3}' file

# 输出改成逗号分隔(CSV风格)
awk 'BEGIN{OFS=","} {print $1,$2,$3}' file

# 第一列求和
awk '{sum+=$1} END{print sum}' file

# 第一列求平均
awk '{sum+=$1; n++} END{if(n) print sum/n}' file

# 求某列最大值(第3列)
awk 'NR==1{max=$3} $3>max{max=$3} END{print max}' file

# 按第一列分组计数
awk '{cnt[$1]++} END{for(k in cnt) print k,cnt[k]}' file

# 去重(按整行)
awk '!seen[$0]++' file

# 两文件按第一列关联
awk 'NR==FNR{a[$1]=$2; next} ($1 in a){print $1,a[$1],$2}' file1 file2

cut

按列切文本,如 cut -d ':' -f 1 file 为以 : 为分隔符,取第一行

  • -d 选择分隔符

  • -f 选择列数

  • -c 按字符位置截取

    cut -c 1-3 file 按字符取前三个

sort

按行排序

  • -n 数字排序

  • -r 倒序

  • -t 选择分隔符 -k 选择排列列数

    sort -t ':' -k 2 file 按第二列排序

uniq

去除相邻重复行(比较单位是行,所以最好要先切割,再排序,再去重)

  • -c 统计每行出现次数
  • -d 只显示重复的行
  • -u 只显示不重复的行
1
2
3
4
# 切割去重
cut -d ':' -f 2 file | sort | uniq
# 统计出现次数
cut -d ':' -f 2 file | sort | uniq -c

gcc

基本用法

gcc [选项] 源文件 -o 输出文件

  • -o(指定输出)
  • -S(生成汇编)
  • -Wall(显示警告)
  • -c(仅编译不链接)
  • -M(列出依赖)
  • -I(指定头文件目录)

GCC 编译过程大致可以理解成:

  1. 预处理:-E (.i)
  2. 编译成汇编:-S (.s)
  3. 汇编成目标文件:-c (.o)
  4. 链接成可执行文件

Makefile

基本规则

1
2
target:depandence
command

每一个command代表一个shell,所以如果要连续需要用 ;

  • 伪目标 .PHONY (是target,每次make都重新执行)
  • 变量
    • 定义 := 直接展开
    • 引用 $(变量名)
  • 自动变量
    • $@ 目标
    • $< 第一个依赖
    • $^ 所有依赖

Git

  • 常用命令:
    • git init:初始化仓库
    • git status:查看文件状态
    • git add:将修改加入暂存区
    • git commit:提交暂存区内容(-m 添加说明)
    • git log:查看提交历史
    • git branch:查看/创建分支(-d 删除、-D 强制删除、-a 查看所有)
    • git checkout:切换分支或恢复文件(-- <file> 丢弃工作区修改)
    • git reset:版本回退(--hardHEAD~<hash>
    • git push / git pull:与远程仓库同步
    • git rm --cached:从暂存区移除文件
    • git clean -f:清除未跟踪文件
    • git restore:撤销工作区修改(与 checkout -- 类似)
    • git config:设置用户信息(user.emailuser.name
  • 三棵树模型:工作区、暂存区、HEAD

shell脚本编程

脚本结构

#!/bin/bash 指定解释器

如果题目说用 bash xxx 运行则可以不写

  • 位置参数:$0(脚本名)、$1$2… 表示传入参数,$# 参数个数,$*$@ 所有参数
  • 特殊变量:$?(上一条命令返回值)

条件判断

if-else

1
2
3
4
5
6
7
if [ 条件 ]; then
# 执行
elif [ 条件 ]; then
# 执行
else
# 执行
fi
result C bash退出码
true !=0 0
false 0 !=0
1
2
3
4
5
6
7
8
9
10
11
12
if diff -q file1 file2 > /dev/null 2>&1 ; then
echo "same"
else
echo "diff"
fi

diff -q file1 file2 > /dev/null 2>&1
if [[ $? -eq 0 ]];then
echo "same"
else
echo "diff"
fi
  • [[ $a == foo* ]] 模式匹配
  • [[ $a == "foo*" ]] 纯字符串比较

循环

for

for 变量 in 列表; do ... done

while

while [ 条件 ]; do ... done

按行读取

1
2
3
4
while read -r line;
do
echo "$line"
done < file.txt

循环控制

continue break

算术运算

(())

算术语句/条件执行,用于流程控制、变量自增

1
2
3
4
5
6
7
i=1
((i++))
echo "$i" #2

if ((i==0)); then
//balabala
fi

$(())

把里面当“算术表达式”求值后再展开成字符串(用于赋值、输出、传参)

1
2
3
i=1
x=$((i+3))
echo "$x" #4

函数运算

  • 定义:name() { … }
  • 调用:直接写 name
  • 参数:$1、$2、$@
  • 状态码:return 0(真)/1
  • 真正传回文本:用 echo + $(…)
1
2
3
4
5
6
7
8
add() {
local a=$1
local b=$2
echo $((a + b))
}

result=$(add 3 5)
echo "$result" #8

重定向

重定向只重定向标准输出,错误信息仍显示在屏幕上,因为错误走的是 stderr

1
2
3
4
5
6
7
8
# 只重定向stdout
sed "s/REPLACE/0/g" ./origin/code/0.c > ./result/code/0.c

# 同时重定向 stdout 和 stderr
sed "s/REPLACE/0/g" ./origin/code/0.c > ./result/code/0.c 2>&1

# 只丢弃 stderr
sed "s/REPLACE/0/g" ./origin/code/0.c > ./result/code/0.c 2>/dev/null
  • > 等价于 1> ,重定向标准输出

  • &1 指代stdout当前指向的地方,所以 >&1 之前要有 >

  • /dev/null 虚拟设备,相当于丢弃

  • >:覆盖输出到文件

  • >>:追加输出到文件

  • <:从文件输入

  • 2>:重定向错误输出

  • &>:同时重定向标准输出和错误

管道符

| 将前一个命令的stdout作为后一个命令的stdin

在sed中如果对同一段文字进行多次修改,用 | 和用 -e 效果是一样的

1
2
echo "hello 123" | sed -e 's/hello/hi/' -e 's/123/456/'
echo "hello 123" | sed 's/hello/hi/; s/123/456/'

课上测试

exam

一上来给干晕字了,vim一下Makefile更是花花绿绿。本人的做题顺序是先看代码,看不懂的地方再回去看字。

考试的时候被卡的地方是编译依赖出问题,因为当时我把所有target都加入了 .PHONY 里,只保留 runallclean 就过了。试后得知此处为助教的小巧思。但很符合Makefile的复用思想,减少了重复编译对资源的浪费。总结来说就是,.PHONY 里放用来执行动作的target,不放用来生成文件的target。

考试后发现一个很有意思的点。以下是部分能够测试通过的代码。

1
2
3
4
5
6
COMMON_FILE = main.c post_calc.c common.h

ver1: $(COMMON_FILE) calc1.c
# TODO: fill the remaining dependency
gcc -I . $(COMMON_FILE) calc1.c -o ver1
# TODO: fill the remaining commands

但课下与同学交流的时候发现有同学根本没注意到要使用 -I 来链接头文件库,即这样看上去完全没有链接库的代码也可以通过。

1
2
ver1: $(COMMON_FILE) calc1.c
gcc $(COMMON_FILE) calc1.c -o ver1

询问助教之后发现此处因为.h文件和调用它的.c文件在同一级文件夹下,C编译的时候会按#include "common.h",在main.c所在当前目录搜到common.h。

extra

可能extra在要独立完成一个shell脚本吧。

感觉这道题本人爽在看题不仔细。上机时助教有给提示说可以调整if-else顺序实现退出,当时还困惑这里为什么会给提示,直到下机后听到同学吐槽不知道该exit到哪去()。

可能要注意的点如下

  • mkdir -p -m xxx 不会设置递归创建的父级文件夹的权限,需要手动设置

  • grep "ERROR" "./logs/$1/error.log">/dev/null 在为true时返回0,所以有两种写法

    1
    2
    3
    4
    if grep "ERROR" "./logs/$1/error.log">/dev/;

    grep "ERROR" "./logs/$1/error.log">/dev/null
    if [[ $? -eq 0 ]];

可能遇到的网络问题

前半小时一直在断网,本人误把 makefile.inc 文件当成Makefile的swap文件删了,这个时候就要用到伟大的git

当时的本人还不太会git,所以执行了git restore .,复原了完工大半的Makefile。重活一次本人一定要使用git restore makefile.inc 来恢复被删掉的文件。

Comments