北航os操作系统lab0

Post Cover

写在前面:

本人的lab0,死了,o(╥﹏╥)o

写的有点多了,废话连篇,点击右下角目录可以跳转到需要的地方

Lab 0 初识操作系统

知识梳理

Linux 基本操作命令

一. 在终端输入 wsl 进入 Ubuntu下的终端,按 ctrl+d 返回 windows

或者可以输入 ssh os-lab ,进入 os 提供的实验环境的命令行界面。

二. 一些特殊的目录

  • / 表示根目录
  • ~ 表示家目录
  • .. 表示上一级目录
  • . 表示当前目录

三. 常用命令:

  1. ls :
    • 作用:知道当前目录中包含哪些文件。
    • 用法:ls [选项] [文件]
    • 选项:
      • -a:显示所有文件,包括隐藏文件(以 . 开头的文件)。
      • -l:以长格式显示文件信息,包括权限、所有者、大小和修改时间,并且每行显示一个文件。
    • ls -al:显示当前目录下的所有文件(包括隐藏文件)以及它们的详细信息。
  2. touch :
    • 作用:创建一个新的空文件,或者更新一个已经存在的文件的访问和修改时间。
    • 用法:touch [选项] 文件名
    • touch file.txt:创建一个名为 file.txt 的新文件,如果该文件已经存在,则更新其访问和修改时间。
  3. mkdir :
    • 作用:创建一个新的目录。
    • 用法:mkdir [选项] 目录名
    • mkdir new_folder:创建一个名为 new_folder 的新目录。
  4. cd :
    • 作用:改变当前工作目录。
    • 用法:cd [目录]
    • cd /home/user/Documents:将当前工作目录更改为 /home/user/Documents。
    • cd ..:返回上一级目录。
    • cd .:保持在当前目录。
    • cd - : 跳转到上一次访问的目录。
  5. pwd :
    • 作用:显示当前工作目录的完整路径。
    • 用法:pwd
    • pwd:输出当前工作目录的绝对路径,例如 /home/user/Documents。

在需要键入文件名或目录名时,可以使用 Tab 键补足全名。
当有多种补足时,双击Tab键可以显示所有可能选项。
在屏幕上输入cd /h然后按下Tab, 就会自动补全为cd /home,如果输入的是cd /,再按两次Tab,会看到所有可选项,和 ls 类似

  1. rmdir :
    • 作用:删除一个空目录。
    • 用法:rmdir [选项] 目录名
    • rmdir old_folder:删除一个名为 old_folder 的目录(前提是该目录必须为空)。
      rm -r old_folder:递归删除一个名为 old_folder 的目录及其所有内容。
  2. rm :
    • 作用:删除文件或目录。
    • 用法:rm [选项] 文件或目录
    • 选项:
      • -r:递归删除目录及其内容。
      • -f:强制删除,不提示确认。
      • -i:交互式删除,系统会要求逐一确定是否要删除。这时,必须输入Y并按回车键, 才能删除文件。
    • rm file.txt:删除名为 file.txt 的文件。
    • rm -rf old_folder:强制递归删除名为 old_folder 的目录及其所有内容。
    1. rm 等命令支持对多个文件的操作,典型的方法是使用通配符 * 来匹配任意长度的字符串。rm -rf *rm -rf ./*可以删除当前所在目录的所有文件。

    2. 在家目录下建一个类似回收站的目录,如果要使用rm命令,先把要删除的文件 移到这个目录里,然后再进行rm

  3. cp :
    • 作用:复制文件或目录。
    • 用法:cp [选项] 源文件路径 目标文件路径
    • 选项:
      • -r:递归复制目录及其内容。
      • -i:系统会要求逐一确定是否要覆盖。 这时,必须输入Y并按回车键, 才能覆盖文件。
    • cp file.txt /home/user/Documents/:将 file.txt 复制到 /home/user/Documents/ 目录下。
  4. mv :
    • 作用:移动或重命名文件或目录。
    • 用法:mv [选项] 源文件路径 目标文件路径
    • 选项:
      • -v:显示详细的操作信息。
      • -i:进行交互式操作,在覆盖前询问。
      • -u: 仅在源文件较新,或目标文件不存在时,才执行移动操作。
    • 如 mv old_name.txt new_name.txt:将 old_name.txt 重命名为 new_name.txt。
    • mv file.txt /home/user/Documents/:将 file.txt 移动到 / home/user/Documents/ 目录下。
  5. echo :
    • 作用:在终端输出文本或变量的值。回显命令。
    • 用法:echo [选项] 字符串
    • echo "Hello, World!":在终端输出 Hello, World!。
    • echo $HOME:输出当前用户的家目录路径。
  6. man :
    • 作用:查看命令的使用说明和帮助文档。
    • 用法:man 命令
    • man ls:查看 ls 命令的使用说明和帮助文档。
  7. cat :
    • 作用:连接文件并将内容输出到标准输出(通常是终端)。
    • 用法:cat [选项] 文件
    • cat file.txt:显示 file.txt 文件的内容。
    • cat file1.txt file2.txt:将 file1.txt 和 file2.txt 的内容连接起来并显示。

四. 常用快捷键

  • Ctrl + C : 终止当前正在运行的命令或程序。(复制粘贴的时候要用右键)
  • Ctrl + D : 退出当前的 shell 会话,或者在输入时表示文件结束(EOF)。
  • Ctrl + Z : 将当前正在运行的命令或程序暂停,并将其放入后台。
    Ctrl+Z 挂起程序后会显示该程序挂起编号,若想要恢复该程序可以使用fg [job_spec]。job_spec 即为挂起编号,不输入时默认为最近挂起进程。
  • Ctrl + L : 清屏,类似于执行 clear 命令。
  • Ctrl + S : 暂停该终端(不是保存)。误触后Ctrl + Q可以继续进行

五. 进阶命令

  1. find:在指定目录下查找文件或目录。

    • 语法:find [路径] [选项] [表达式]
    • find /home/user/Documents -name "*.txt":在 /home/user/Documents 目录下查找所有以 .txt 结尾的文件。
  2. grep:在文件中搜索指定的字符串。

    • 语法:grep [选项] 模式 文件
    • 选项:
      • -i:忽略大小写。
      • -r:递归搜索目录及其子目录中的文件。
      • -a:将二进制文件视为文本文件进行搜索。
      • -n:显示匹配行的行号。
    • grep "hello" file.txt:在 file.txt 文件中搜索字符串 “hello”。
  3. tree:以树状结构显示目录和文件。

    • 语法:tree [选项] [目录]
    • 选项:
      • -a:显示所有文件,包括隐藏文件。
      • -d:仅显示目录,不显示文件。
    • tree -a /home/user/Documents:以树状结构显示 /home/user/Documents 目录下的所有文件和目录。
  4. chmod:修改文件或目录的权限。

    • 语法:chmod [选项] 模式 文件
    • 模式:
      • u:表示文件所有者(user)。
      • g:表示文件所属的用户组(group)。
      • o:表示其他用户(others)。
      • a:表示所有用户(all)。
      • +:添加权限。
      • -:删除权限。
      • =:设置权限。
      • r:读权限。
      • w:写权限。
      • x:执行权限。
      • X: 仅当文件是子目录或者已经具有执行权限时,才添加执行权限。
    • 此外 chmod 也可以用数字表示权限:
      chmod ugo 中 ugo分别为一个三位的二进制数在十进制下的数值,这三个数字分别表示拥有者,群组,其他人的权限。三位二进制从高位到低位分别表示rwx的权限是否打开
    • chmod u+x script.sh:为 script.sh 文件的所有者添加执行权限。
    • 此外 chmod 755 script.sh:将 script.sh 文件的权限设置为 rwxr-xr-x,即所有者具有读、写和执行权限,群组和其他用户具有读和执行权限。
    • chmod -w file.txt:删除所有人对 file.txt 文件的写权限。
  5. diff:比较两个文件的内容并显示差异。

    • 语法:diff [选项] 文件1 文件2
    • 选项:
      • -b:忽略空白字符的变化。
      • -B:忽略空行的变化。
      • -q:仅显示是否存在差异,不显示具体差异内容。
    • diff file1.txt file2.txt:比较 file1.txt 和 file2.txt 的内容并显示差异。
  6. sed:流编辑器,用于对文本进行替换、删除、插入等操作。

    • 语法:sed [选项] '命令' 文件
    • 选项:
      • -i:直接修改文件内容,而不是输出到标准输出。
      • -n:不自动打印模式空间的内容,通常与 p 命令一起使用来控制输出。
      • -e:允许在同一行里执行多条命令。
    • 命令:
      • [行号]a\[内容] 新增,在行号后新增一行相应内容。行号可以是“数字”,在这一行之后新增,也可以是“起始行,终止行”,在其中的每一行后新增。当不写行号时,在每一行之后新增。使用$表示最后一行。后面的命令同理。
      • [行号]c\[内容]取代。用内容取代相应行的文本。
      • [行号]i\[内容]插入。在当前行的上面插入一行文本。
      • [行号]d 删除当前行的内容。
      • [行号]p 输出选择的内容。通常与选项-n一起使用。
      • s/re/string/ 将re(正则表达式)匹配的内容替换为string
    • 如:
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    #输出my.txt的第三行
    sed-n'3p' my.txt

    #删除my.txt文件的第二行
    sed'2d' my.txt

    #删除my.txt文件的第二行到最后一行($符号表示到最末尾)
    sed'2,$d'my.txt

    #在整行范围内把str1替换为str2。
    #如果没有g标记,则只有每行第一个匹配的str1被替换成str2
    sed's/str1/str2/g' my.txt

    #-e选项允许在同一行里执行多条命令。例子的第一条是第四行后添加一个str,
    #第二个命令是将str替换为aaa。命令的执行顺序对结果有影响。
    sed-e'4a\str'-e's/str/aaa/'my.txt
  7. awk:文本处理工具,用于对文本进行模式匹配和数据提取。

    • 语法:awk [选项] '程序' 文件
    • 选项:
      • -F:指定输入字段分隔符,默认为空格。
    • 程序:由模式和动作组成的代码块,格式为 pattern { action }。当输入行匹配模式时,执行相应的动作。
    • $n:表示输入行的第 n 个字段,字段默认以空格分隔。$0 表示整行文本。
    • awk '$1>2 {print $0}' file.txt:打印 file.txt 文件中第一列大于 2 的行。
      * awk -F, '{print $2}' file.csv:打印 file.csv 文件中每行的第二个字段,以逗号作为分隔符。

VIM

vim是一种文本编辑器。

  1. touch helloworld.c
  2. vim helloworld.c
  3. 按 i 进入插入模式,输入代码
  4. 完成文件修改以后,按 esc 退出插入模式回到命令模式,输入 : 进入底线命令模式,输入“w”(write),并
    按下回车,文件便得到了保存;再进入底线命令模式,输入“q”(quit)便可以关闭文件,回到命令行界面,也可以由:wq一条命令完成
  5. vim 自定义配置:vim ~/.vimrc

GCC

  1. 语法:gcc [选项]...[参数]...

  2. 选项(常用):
    -o 指定生成的输出文件
    -S 将C代码转换为汇编代码
    -Wall 显示一些警告信息
    -c 仅执行编译操作,不进行链接操作
    -M 列出依赖
    -I<path> 编译时指定头文件目录,使用标准库时不需要指定目录,-I参数可以用相对路径,比如头文件在当前目录,可以用-I.来指定

  3. 参数:
    C源文件:指定C语言源代码文件
    e.g. $ gcc test.c -o test #使用-o选项生成名为test的可执行文件

  4. 过编译之前完成的helloworld.c

    • 使用gcc helloworld.c -o helloworld命令,便可以创建由
      helloworld.c文件编译成的helloworld的可执行文件
    • 使用./helloworld命令运行生成的可执行文件,输出Hello, World!。
  5. c语言是如何编译成可执行文件的:

    • 预处理:gcc 会先对源代码进行预处理,处理包括头文件包含、宏定义等预处理指令。如果写了 #include <stdio.h>,预处理器就会去找 stdio.h 这个文件,然后把它里面的内容全部复制粘贴到代码里。如果写了 #define PI 3.14,预处理器会把代码里所有的 PI 都替换成 3.14。生成 .i 文件。
    • 编译:语法检查,并将 .i 文件编译成汇编代码,生成 .s 文件。
    • 汇编:将 .s 文件汇编成目标文件,即将汇编代码转换为0和1组成的机器码,生成 .o 文件。
    • 链接:将 .o 文件与所需的库文件链接,生成最终的可执行文件。如,用了 printf("Hello"),链接器会将 printf 函数的实现代码链接到可执行文件中。如果写了多个 .c 文件,链接器也会在这一步把它们全缝合在一起。
  6. 如果想要同时编译多个文件,可以直接用 -o 选项将多个文件进行编译连接:gcc testfun.c test.c -o test,也可以先使用 -c 选项将每个文件单独编译成 .o 文件,再用 -o 选项将多个 .o 文件进行链接:gcc -c testfun.c && gcc -c test.c && gcc testfun.o test.o -o test,两者等价。

Makefile

  1. 作用:自动化构建工具,简化编译和构建过程。(感觉有点像宏定义)

  2. 语法:

    1
    2
    3
    4
    5
    target: dependencies
    command 1
    command 2
    ...
    command n
    • target:要生成的目标文件或可执行文件。
    • dependencies:生成目标所依赖的文件或其他目标。
    • command:生成目标所需执行的命令,必须以Tab键开头。
  3. 注意:

    • Makefile 文件必须命名为 “Makefile” 或 “makefile”。
    • 每个命令行必须以 Tab键 开头,不能使用空格。
    • 可以使用变量来简化 Makefile 的编写,例如 CC=gcc 定义了一个变量 CC,后续可以使用 $(CC) 来引用它。
  4. 示例:

    1
    gcc -o hello_world hello_world.c

    可以先生成 makefile 文件,内容为

    1
    2
    3
    CC=gcc
    hello_world: hello_world.c
    $(CC) -o hello_world hello_world.c

    然后在终端输入 make hello_world 就会自动执行 $(CC) -o hello_world hello_world.c 命令,生成 hello_world 可执行文件。

ctags

  1. 作用:生成代码标签,方便代码导航和跳转。

  2. 语法:ctags [选项] 文件

  3. 选项:
    -R:递归生成标签文件,包含当前目录及其子目录中的所有文件。
    -f <file>:指定生成的标签文件名,默认为 “tags”。

  4. 在.vimrc中添加以下配置:

    1
    2
    set tags=./tags;
    set autochdir
    • set tags=./tags;:指定标签文件的位置,这里是当前目录下的 tags 文件。
    • set autochdir:启用自动切换工作目录功能,使 Vim 在打开文件时自动切换到该文件所在的目录。
  5. 使用 ctags 生成标签文件:

    1
    ctags -R *
  6. 在 Vim 中使用标签功能:

    • 使用 :tag <tagname> 命令跳转到指定标签的位置。
    • 使用 :tags 命令查看当前标签列表。
    • 使用 Ctrl-] 快捷键跳转到光标所在单词的定义位置。
    • 使用 Ctrl-TCtrl-O 快捷键返回上一个标签位置。

Git

  1. 作用:分布式版本控制系统,帮助开发者管理代码版本和协作开发。

  2. 常用命令:

    • git init:初始化一个新的 Git 仓库。
    • git add <file>:将文件添加到暂存区。新建文件后要 git add,修改文件后也需要git add。
    • git commit -m "message":提交更改并添加提交信息。
    • git status:查看当前仓库的状态,包括未跟踪、已修改和已暂存的文件。
    • git log:查看提交历史记录。
  3. 版本退回相关命令:

    • git rm --cached <file>:从暂存区移除不想再跟踪的文件,但保留在工作区。
    • git checkout -- <file>:将暂存区指定的文件替换工作区的文件,丢弃工作区的修改。
    • git restore <file>:我们在修改一个文件之后,可能想要放弃这个修改。当这个文件还没有通过 git add 加入暂存区时,我们可以使用 git restore <filename> 来撤销对这个文件的修改,使其退回到上一个 commit 的状态。如果这个文件已经加入了暂存区,我们可以通过 git restore --staged <filename> 来取消暂存。
    • git checkout HEAD -- <file>:将版本库中HEAD指向的目录树中的文件替换暂存区和工作区的文件,丢弃工作区的修改。
    • git clean <file> -f:从工作区删除未跟踪的文件。
    • git reset HEAD <file>:暂存区的目录树会被重写,由master分支指向的目录树所替换,但是工作区不受影响。相当于撤销了一次git add操作。

    以下是git reset的更多用法:

    • git reset --hard
      • 将当前分支重置到指定的提交,并且丢弃所有未提交的更改。工作区也会变为之前的提交。
      • git reset --hard HEAD~50 会将当前分支重置到上50个版本的提交,并且丢弃所有未提交的更改。
      • git reset --hard HEAD^ 会将当前分支重置到上一个提交,并且丢弃所有未提交的更改。HEAD^^ 会将当前分支重置到上两个提交,并且丢弃所有未提交的更改。
      • git reset --hard <commit> 会将当前分支重置到指定的提交,并且丢弃所有未提交的更改。commit 可以是提交的哈希值、分支名称或标签名称。
  4. Git 分支管理:

    • git branch <branch_name>:创建一个新的分支。相当于把当前分支的内容拷贝一份到新的分支里去
    • git checkout <branch_name>:切换到指定的分支。
    • git branch -d <branch_name>:删除指定的分支。(-D是强制删除)
    • git branch -a:列出所有分支,包括本地和远程分支。
  5. Git 远程仓库管理:

    • git remote add <remote_name> <remote_url>:添加一个新的远程仓库。
    • git push <remote_name> <branch_name>:将本地分支推送到远程仓库。(已经 commit 的部分)。这条命令可以将我们本地创建的分支推送到远程仓库中,在远程仓库建立一个同名的本地追踪的远程分支。
    • git pull <remote_name> <branch_name>:从远程仓库拉取并合并指定分支的更改到当前分支。
  • git clone <URL>:从远程仓库“克隆”一个已有的储存库到当前目录下
  1. Git 配置:

    • git config --global user.email "you@example.com"
    • git config --global user.name "YourName"
      用来设置全局的用户名和邮箱,这些信息会被记录在每次提交中,帮助识别提交者的身份。
  2. Git 文件状态:

    • Untracked(未跟踪):表示没有跟踪( add )某个文件的变化,使用git add即可跟踪文件。
    • Modified(已修改):表示修改了某个文件,但还没有加入(add)到暂存区中。
    • Staged(已暂存):文件已被添加到暂存区,准备提交。
    • unmodified(未修改)(已经被 commit):表示某文件在跟踪后一直没有改动过或者改动已经被提交。
    image-20260226221306391
    image-20260226221306391

    add the file: git add <file>
    edit the file: vim <file>
    stage the file: git add <file>
    commit: git commit -m "message"
    remove the file: git rm <file>

  3. 实例:

    • 创建并进入名为 learn_git 的目录,使用git init命令初始化一个新的 Git 仓库。
    • 创建一个名为 readme.md 的文件,并使用git add readme.md命令将其添加到暂存区。
    • 使用git commit 命令提交更改,并添加提交信息。
    • 在弹出的窗口使用 i 键进入编辑模式,输入提交信息,完成后按esc键退出编辑模式,输入:wq保存并退出。
      git commit --amend命令可以修改上一次提交的信息,使用方法与git commit相同)
  4. Git 三棵树

    • 工作区(Working Directory):包含当前正在编辑的文件,是开发者进行代码修改的地方。
    • 暂存区(Staging Area):也称为索引(Index),是一个临时区域,用于保存即将提交的文件快照。只有被添加到暂存区的文件才会被提交到版本库中。
    • 版本库(Repository):包含所有提交历史记录和版本信息的地方,Git 将每次提交的快照保存在版本库中,以便后续查看和回滚。

    image-20260226225518298
    image-20260226225518298

    • objects:Git 中的对象库
      index:Git 中的索引文件,记录了暂存区的状态
      master:Git 中的默认分支名称,表示主分支,此时HEAD指向master分支。
    • git add:当对工作区修改(或新增)的文件执行git add命令时,暂存区的目录树被更新,同时工作区修改(或新增)的文件内容被写入到对象库中的一个新的对象中,而该对象的ID被记录在暂存区的文件索引中。
    • git commit:当执行提交操作(git commit)时,会将暂存区的目录树写到版本库(对象库)中,master 分支会做相应的更新。即HEAD 指向的目录树就是提交时暂存区的目录树。
    • git rm --cached <file>:直接从暂存区删除文件,工作区则不做出改变。
    • git reset HEAD:暂存区的目录树会被重写,由master分支指向的目录树所替换,但是工作区不受影响。
    • git checkout -- <file>:用暂存区指定的文件替换工作区的文件。这个操作很危险,会清除工作区中未添加到暂存区的改动。
    • git checkout HEAD <file> :用 HEAD 指向的master 分支中的指定文件来替换暂存区和以及工作区中的文件。

tmux

  1. 作用:终端复用工具,允许在一个终端窗口中同时运行多个会话和窗口。
  2. 常用命令:
    • 窗格操作:
      • Ctrl + b + %:水平分割当前窗格。左右两屏
      • Ctrl + b + ":垂直分割当前窗格。上下两屏
      • Ctrl + b + o:在窗格之间切换。
      • Ctrl + b + up/down/left/right:在窗格之间切换。
      • Ctrl + b + Space:切换到下一个布局。
      • Ctrl + b + z:切换当前窗格的最大化状态。
      • Ctrl + b + x:关闭当前窗格。
    • 窗口操作:
      • Ctrl + b + c:创建一个新的窗口。
      • Ctrl + b + n:切换到下一个窗口。
      • Ctrl + b + p:切换到上一个窗口。
      • Ctrl + b + 0-9:切换到指定编号的窗口。
      • Ctrl + b + w:列出所有窗口并选择要切换的窗口。
      • Ctrl + b + &:关闭当前窗口。
    • 会话操作:
      • tmux new -s <session_name>:创建一个新的会话并命名。
      • Ctrl + b + d:分离当前会话,返回到主终端。
      • tmux ls:列出所有会话。
      • tmux a -t <session_name>:重新连接到指定的会话。
      • tmux kill-session -t <session_name>:关闭指定的会话。

shell脚本

  1. 作用:自动化执行一系列命令的脚本文件,常用于系统管理和任务自动化。

  2. 语法:
    (1) vim my.sh:创建一个新的 shell 脚本文件并进入编辑模式。
    (2)

    1
    2
    3
    #!/bin/bash
    # 注释
    echo "Hello, World!"

    在脚本文件中编写 shell 命令,第一行的 #!/bin/bash 以保证直接执行我们的脚本时使用bash作为解释器
    (3) bash my.sh:使用 bash 解释器执行 shell 脚本。 或者 4.5.
    (4) chmod +x my.sh:为 shell 脚本添加可执行权限。
    (5) ./my.sh:直接执行 shell 脚本(前提是当前目录下有 my.sh 文件,并且该文件具有可执行权限)。

  3. 参数与函数
    (1) 参数:在 shell 脚本中,可以使用位置参数来传递命令行参数。例如,$1 表示第一个参数,$2 表示第二个参数,以此类推。$0 表示命令本身。

    • $# 表示传递给脚本的参数个数。

    • $* 表示传递给脚本的所有参数,作为一个单词。

    • $? 表示上一个命令的退出状态,0表示成功,非0表示失败。

    • my.sh文件内容为:

      1
      2
      3
      #!/bin/bash
      echo "The first argument is: $1"
      echo "The second argument is: $2"

      执行命令 bash my.sh arg1 arg2,输出将会是:

      1
      2
      The first argument is: arg1
      The second argument is: arg2

      这说明我们成功地将命令行参数传递给了 shell 脚本,并在脚本中使用了这些参数。
      (2) shell中的函数也用类似的方式传递参数。

      1
      2
      3
      function_name() {
      # 函数体
      }

      如:

      1
      2
      3
      4
      5
      my_function() {
      echo "This is a $1"
      }

      my_function arg1 #调用

      ./my.sh文件内容为上面代码,执行命令。
      输出将会是:

      1
      This is a arg1

      在函数调用的后面需要使用 $? 获取返回值

  4. 分支语句

    1
    2
    3
    4
    5
    6
    7
    if < condition >; then
    # 条件为真时执行的命令
    elif < condition >; then
    # 另一个条件为真时执行的命令
    else
    # 条件都不满足时执行的命令
    fi

    注意:

    • 等号两边必须无空格。 a = 1会理解成一个名为a的命令,并给它传入参数=和1。

    • condition 的位置也是命令,条件表达式的结果决定了命令的退出状态,0表示条件为真,非0表示条件为假。

    • [是一种常用作条件的命令,其参数是一个条件表达式和末尾的]。该命令在关系成立时返回0,在关系不成立时返回非0。

    • 条件表达式需要放在方括号 [ ] 中,并且左中括号后和右中括号前一定需要空格。

    • 条件表达式中的关系运算符:

      • -eq:等于
      • -ne:不等于
      • -gt:大于
      • -lt:小于
      • -ge:大于或等于
      • -le:小于或等于
    • 如:

      1
      2
      3
      4
      5
      if ! diff file1.txt file2.txt ; then
      echo "The files are different."
      else
      echo "The files are the same."
      fi
      1
      2
      a=1
      if [ $a -ne 1 ]; then echo ok; fi #$a表示变量a的值,-ne表示不等于,如果a的值不等于1,则输出ok
  5. 循环语句

    1
    2
    3
    while < condition >; do
    # 条件为真时执行的命令
    done

    如:

    1
    2
    3
    4
    5
    6
    7
    a=1
    while [ $a -lt 10 ]
    do
    mkdir file$a
    a=$[$a+1] #在 Shell(如 Bash)中,变量默认都是“字符串(String)”,而不是数字。$a+1 表示将变量 a 的值与数字 1 进行字符串连接,而不是进行数学运算。为了让 Shell 将 $a+1 解释为数学表达式,我们需要使用 $(( )) 或者 $[ ] 来进行算术扩展。
    #a=$((a+1)) 或者 a=$[a+1] 都可以实现将变量 a 的值加 1 的效果。
    done
  6. 括号分析:

    • 美元符与单小括号、反引号:

      • $():命令替换,执行括号内的命令,并将输出结果作为字符串返回,替换至原处。

      • ``:也是命令替换的另一种语法,但不支持嵌套使用,且在某些情况下可能需要转义。
        如:

        1
        2
        3
        4
        5
        6
        7
        #!/bin/bash

        if [ -z "$(diff file1.txt file2.txt)" ]; then
        echo "same"
        else
        echo "different"
        fi

        [命令 -z为选项,判断是否为空字符

    • 双小括号:

      • $(( )):算术扩展,用于执行数学运算,并返回结果。括号内可以包含变量和算术表达式。

      • (( )):也是算术扩展的另一种语法,但不返回结果,而是直接执行表达式,通常用于条件测试或循环控制。
        如:

        1
        2
        3
        4
        5
        6
        7
        8
        9
        10
        11
        12
        a=5
        b=3
        result=$((a + b)) # result 的值将会是 8
        echo $result # 输出 8

        if ((a > b)); then #执行了((a > b))这个命令
        echo "a is greater than b"
        else
        echo "a is not greater than b"
        fi

        ((a = 10)) # 将 a 的值设置为 10,是一条命令
    • 单种括号 [ ]:用于条件测试,通常与 if 语句一起使用。方括号内的表达式会被解析为条件判断。

    • 双方括号 [[ ]]:也是用于条件测试,但提供了更多的功能和更好的语法支持。双方括号内的表达式可以使用更多的操作符,并且不需要对某些特殊字符进行转义。如:[[$a-gt1&&$a-lt100]],等同于单中括号[$a-gt1]&&[$a-lt100]

  7. 重定向和管道

    • 三种流:

      • 标准输入(stdin):默认从键盘输入,文件描述符为0。
      • 标准输出(stdout):默认输出到终端,文件描述符为1。
      • 标准错误(stderr):默认输出到终端,文件描述符为2。
    • 重定向:

      • >:将命令的输出重定向到一个文件,覆盖原有内容。

      • >>:将命令的输出重定向到一个文件,追加到原有内容的末尾。

      • <:将一个文件的内容作为命令的输入。

      • 2>:将命令的错误输出重定向到一个文件,覆盖原有内容。

      • 2>>:将命令的错误输出重定向到一个文件,追加到原有内容的末尾。

      • 如:

        1
        command < input.txt 1>output.txt 2>err.txt
    • 管道( | ):将一个命令的输出直接作为另一个命令的输入。

      • 如:ls -l | grep "txt":将 ls -l 命令的输出通过管道传递给 grep 命令,grep 命令会过滤出包含 “txt” 的行并显示出来。
    • 可混合使用:

      1
      cat < my.sh | grep "Hello" > output.txt

      cat < my.sh输出的是 my.sh 文件的内容,grep "Hello"过滤出包含 “Hello” 的行,> output.txt将结果重定向到 output.txt 文件中

思考题

Thinking 0.1

思考下列有关Git的问题:

在前述已初始化的 ~/learnGit 目录下,创建一个名为 README.txt 的文件。

执行命令git status > Untracked.txt

在 README.txt 文件中添加任意文件内容,然后使用add命令,再执行命令git status>Stage.txt

提交 README.txt ,并在提交说明里写入自己的学号。 执行命令 cat Untracked.txtcat Stage.txt ,对比两次运行的结果,体会 README.txt 两次所处位置的不同。 修改 README.txt 文件,再执行命令git status > Modified.txt。 执行命令 cat Modified.txt ,观察其结果和第一次执行 add 命令之前的 status 是否一样,并思考原因。

执行结果如下:

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
60
61
62
git@2437xxxx:~ $ mkdir ~/learnGit
git@2437xxxx:~ $ cd learnGit/
git@2437xxxx:~/learnGit $ git init
提示:使用 'master' 作为初始分支的名称。这个默认分支名称可能会更改。要在新仓库中
提示:配置使用初始分支名,并消除这条警告,请执行:
提示:
提示: git config --global init.defaultBranch <名称>
提示:
提示:除了 'master' 之外,通常选定的名字有 'main''trunk''development'
提示:可以通过以下命令重命名刚创建的分支:
提示:
提示: git branch -m <name>
已初始化空的 Git 仓库于 /home/git/learnGit/.git/
git@2437xxxx:~/learnGit $ touch README.txt
git@2437xxxx:~/learnGit $ git status > Untracked.txt
git@2437xxxx:~/learnGit $ vim README.txt
git@2437xxxx:~/learnGit $ git add README.txt
git@2437xxxx:~/learnGit $ git status > Stage.txt
git@2437xxxx:~/learnGit $ git commit -m"2437xxxx"
[master (根提交) 79b5ba6] 2437xxxx
1 file changed, 1 insertion(+)
create mode 100644 README.txt
git@2437xxxx:~/learnGit (master)$ cat Untracked.txt
位于分支 master

尚无提交

未跟踪的文件:
(使用 "git add <文件>..." 以包含要提交的内容)
README.txt
Untracked.txt

提交为空,但是存在尚未跟踪的文件(使用 "git add" 建立跟踪)
git@2437xxxx:~/learnGit (master)$ cat Stage.txt
位于分支 master

尚无提交

要提交的变更:
(使用 "git rm --cached <文件>..." 以取消暂存)
新文件: README.txt

未跟踪的文件:
(使用 "git add <文件>..." 以包含要提交的内容)
Stage.txt
Untracked.txt
git@2437xxxx:~/learnGit (master)$ vim README.txt #改变README.txt的内容
git@2437xxxx:~/learnGit (master)$ git status > Modified.txt
git@2437xxxx:~/learnGit (master)$ cat Modified.txt
位于分支 master
尚未暂存以备提交的变更:
(使用 "git add <文件>..." 更新要提交的内容)
(使用 "git restore <文件>..." 丢弃工作区的改动)
修改: README.txt

未跟踪的文件:
(使用 "git add <文件>..." 以包含要提交的内容)
Modified.txt
Stage.txt
Untracked.txt

修改尚未加入提交(使用 "git add" 和/或 "git commit -a"
  • git status会查看当前文件状态,并通过重定向输出到 Untracked.txt 、 Stage.txt 、 Modified.txt 中
  • 执行 git status > Untracked.txt 时,还未使用 git add 跟踪 README.txt ,README.txt 文件处于未跟踪状态(untracked)
  • 执行 git status > Stage.txt 时,还已使用 git add 跟踪 README.txt ,README.txt 文件放在下次提交(commit)时要保存的清单中,还未提交,处于未跟踪已暂存状态(staged)
  • 执行 git status > Modified.txt 时,README.txt 文件已经提交了一次并且发生了修改, 还未使用 git add 加入到暂存区,README.txt 文件处于已修改状态(modified)

Thinking 0.2

仔细看看下图,思考一下箭头中的 add the file、stage the file 和 commit 分别对应的是Git里的哪些命令呢?

image-20260319101600685
image-20260319101600685
  • add the file:git add
  • stage the file:git add
  • commit:git commit

Thinking 0.3

思考下列问题:

  1. 代码文件 print.c 被错误删除时,应当使用什么命令将其恢复?

  2. 代码文件 print.c 被错误删除后,执行了 git rm print.c 命令,此时应当 使用什么命令将其恢复?

  3. 无关文件 hello.txt 已经被添加到暂存区时,如何在不删除此文件的前提下 将其移出暂存区

  1. 使用git checkout -- print.c 或者 git restore print.c,将暂存区的文件恢复到工作区
  2. 使用git reset HEAD print.c 撤销暂存区的修改,然后使用 git checkout -- print.c 或者 git restore print.c将暂存区的文件恢复到工作区
  3. 使用git rm --cached hello.txt 从暂存区中删除文件

Thinking 0.4

思考下列有关Git的问题:

找到在 /home/22xxxxxx/learnGit 下刚刚创建的 README.txt 文件,若不存在则新建该文件。

在文件里加入 Testing 1,git addgit commit,提交说明记为 1。

模仿上述做法,把1分别改为2和3,再提交两次。

使用git log命令查看提交日志,看是否已经有三次提交,记下提交说明为3的哈希值。

进行版本回退。执行命令git reset --hard HEAD^后,再执行git log,观察其变化。

找到提交说明为1的哈希值,执行命令git reset --hard <hash>后,再执行git log,观察其变化。

现在已经回到了旧版本,为了再次回到新版本,执行git reset --hard <hash>,再执行git log,观察其变化。

执行情况如下:

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
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
git@2437xxxx:~/2437xxxx/learnGit (master)$ vim README.txt #内容是 Testing 1
git@2437xxxx:~/2437xxxx/learnGit (master)$ git add README.txt
git@2437xxxx:~/2437xxxx/learnGit (master)$ git commit -m"1"
[master 30b5a2e] 1
1 file changed, 1 insertion(+), 1 deletion(-)
git@2437xxxx:~/2437xxxx/learnGit (master)$ vim README.txt #内容是 Testing 2
git@2437xxxx:~/2437xxxx/learnGit (master)$ git add README.txt
git@2437xxxx:~/2437xxxx/learnGit (master)$ git commit -m"2"
[master a95aa94] 2
1 file changed, 1 insertion(+), 1 deletion(-)
git@2437xxxx:~/2437xxxx/learnGit (master)$ vim README.txt #内容是 Testing 3
git@2437xxxx:~/2437xxxx/learnGit (master)$ git add README.txt
git@2437xxxx:~/2437xxxx/learnGit (master)$ git commit -m"3"
[master 0614b86] 3
1 file changed, 1 insertion(+), 1 deletion(-)
git@2437xxxx:~/2437xxxx/learnGit (master)$ git log
commit 0614b868bd5d655ea7cd16e8346f2303a8552c12 (HEAD -> master)
Author: xxxx <2437xxxx@buaa.edu.cn>
Date: Thu Mar 19 10:36:41 2026 +0800

3

commit a95aa94efe8710519c7f8b9dfb0628b79c6bd914
Author: xxxx <24371366@buaa.edu.cn>
Date: Thu Mar 19 10:36:14 2026 +0800

2

commit 30b5a2ef9533e0eb72e799de2615c01608fee6d9
Author: xxxx <2437xxxx@buaa.edu.cn>
Date: Thu Mar 19 10:35:26 2026 +0800

1

commit 79b5ba601cc3246a0645f71fa52058781585eefb
Author: 张博雅 <2437xxxx@buaa.edu.cn>
Date: Thu Mar 19 09:46:35 2026 +0800

2437xxxx
git@2437xxxx:~/2437xxxx/learnGit (master)$ git reset --hard HEAD^
HEAD 现在位于 a95aa94 2
git@2437xxxx:~/2437xxxx/learnGit (master)$ git log
commit a95aa94efe8710519c7f8b9dfb0628b79c6bd914 (HEAD -> master)
Author: xxxx <2437xxxx@buaa.edu.cn>
Date: Thu Mar 19 10:36:14 2026 +0800

2

commit 30b5a2ef9533e0eb72e799de2615c01608fee6d9
Author: xxxx <2437xxxx@buaa.edu.cn>
Date: Thu Mar 19 10:35:26 2026 +0800

1

commit 79b5ba601cc3246a0645f71fa52058781585eefb
Author: xxxx <2437xxxx@buaa.edu.cn>
Date: Thu Mar 19 09:46:35 2026 +0800

2437xxxx
git@2437xxxx:~/2437xxxx/learnGit (master)$ git reset --hard 79b5ba
HEAD 现在位于 79b5ba6 24371366
git@2437xxxx:~/2437xxxx/learnGit (master)$ git log
commit 79b5ba601cc3246a0645f71fa52058781585eefb (HEAD -> master)
Author: xxxx <2437xxxx@buaa.edu.cn>
Date: Thu Mar 19 09:46:35 2026 +0800

2437xxxx
git@2437xxxx:~/2437xxxx/learnGit (master)$ git reset --hard 0614b8
HEAD 现在位于 0614b86 3
git@2437xxxx:~/2437xxxx/learnGit (master)$ git log
commit 0614b868bd5d655ea7cd16e8346f2303a8552c12 (HEAD -> master)
Author: xxxx <2437xxxx@buaa.edu.cn>
Date: Thu Mar 19 10:36:41 2026 +0800

3

commit a95aa94efe8710519c7f8b9dfb0628b79c6bd914
Author: xxxx <2437xxxx@buaa.edu.cn>
Date: Thu Mar 19 10:36:14 2026 +0800

2

commit 30b5a2ef9533e0eb72e799de2615c01608fee6d9
Author: xxxx <2437xxxx@buaa.edu.cn>
Date: Thu Mar 19 10:35:26 2026 +0800

1

commit 79b5ba601cc3246a0645f71fa52058781585eefb
Author: xxxx <2437xxxx@buaa.edu.cn>
Date: Thu Mar 19 09:46:35 2026 +0800

2437xxxx
  • 第一次git log,所处的是提交说明为 3 的版本,README.txt 中内容是 Testing 3
  • 第二次git log,所处的是提交说明为 2 的版本,README.txt 中内容是 Testing 2
  • 第三次git log,所处的是提交说明为 1 的版本,README.txt 中内容是 Testing 1
  • 最后一次git log,所处的是提交说明为 3 的版本,README.txt 中内容是 Testing 3

Thinking 0.5

执行如下命令, 并查看结果

  • echo first
  • echo second > output.txt
  • echo third > output.txt
  • echo forth >> output.txt

执行结果如下:

1
2
3
4
5
6
7
8
9
10
11
12
git@2437xxxx:~ $ echo first
first
git@2437xxxx:~ $ echo second > output.txt
git@2437xxxx:~ $ cat output.txt
second
git@2437xxxx:~ $ echo third > output.txt
git@2437xxxx:~ $ cat output.txt
third
git@2437xxxx:~ $ echo forth >> output.txt
git@2437xxxx:~ $ cat output.txt
third
forth

Thinking 0.6

使用你知道的方法(包括重定向)创建下图内容的文件(文件命名为 test),将创建该文件的命令序列保存在 command 文件中,并将 test 文件作为批处理文件运行,将运行结果输出至 result 文件中。给出 command 文件和 result 文件的内容,并对最后的结果进行解释说明(可以从 test 文件的内容入手). 具体实现的过程中思考下列问题: echo echo Shell Start 与 echo `echo Shell Start` 效果是否有区别; echo echo $c>file1 与echo `echo $c>file1` 效果是否有区别.

command 文件:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
 1 #!/bin/bash
2 #create test
3
4 touch test
5 echo 'echo Shell Start...' > test
6 echo 'echo set a = 1' >> test
7 echo 'a=1' >> test
8 echo 'echo set b = 2' >> test
9 echo 'b=2' >> test
10 echo 'echo set c = a+b' >> test
11 echo 'c=$[$a+$b]' >> test
12 echo 'echo c = $c' >> test
13 echo 'echo save c to ./file1' >> test
14 echo 'echo $c>file1' >> test
15 echo 'echo save b to ./file2' >> test
16 echo 'echo $b>file2' >> test
17 echo 'echo save a to ./file3' >> test
18 echo 'echo $a>file3' >> test
19 echo 'echo save file1 file2 file3 to file4' >> test
20 echo 'cat file1>file4' >> test
21 echo 'cat file2>>file4' >> test
22 echo 'cat file3>>file4' >> test
23 echo 'echo save file4 to ./result' >> test
24 echo 'cat file4>>result' >> test

执行 test:

1
2
3
4
5
6
7
8
9
10
11
$ bash test
Shell Start...
set a = 1
set b = 2
set c = a+b
c = 3
save c to ./file1
save b to ./file2
save a to ./file3
save file1 file2 file3 to file4
save file4 to ./result

test文件中,a 被设置为 1,b 被设置为 2,c 是 a 和 b 之和,即 3。随后,利用重定向,c 的值被保存入 file1 中,b 的值被保存入 file2 中,a 的值被保存入 file3 中,然后再次利用重定向将 file1 中内容保存进 file4 中,将 file2 中内容追加进 file4 中,将 file3 中内容追加进 file4 中,将 file4 中内容追加进 result 中。于是,result 中即是 3 2 1(每个数字一排)

result 文件:

1
2
3
3
2
1
  • echo echo Shell Start 直接把“echo Shell Start” 作为字符串输出;

    echo `echo Shell Start`将 “echo Shell Start” 的输出作为外层 echo 的输入

    echo ‘echo Shell Start’ 直接把“echo Shell Start” 作为字符串输出;

    1
    2
    3
    4
    5
    6
    $ echo echo Shell Start
    echo Shell Start
    $ echo `echo Shell Start`
    Shell Start
    $ echo 'echo Shell Start'
    echo Shell Start
  • 设置 test.sh:

    1
    2
    3
    4
    5
    6
    7
    8
    1 #!/bin/bash
    2 c=3
    3 echo echo $c>file1
    4 cat file1
    5 echo 'echo $c>file1'
    6 cat file1
    7 echo `echo $c>file1`
    8 cat file1

    执行

    1
    2
    3
    4
    5
    6
    $ bash test.sh 
    echo 3
    echo $c>file1
    echo 3

    3

    echo echo $c>file1 中,echo 直接把其后 echo 3 作为输出给了 file1;

    echo ‘echo $c>file1’中,直接把“echo $c>file1” 作为字符串输出,此时>file1无效,file1 的值没有改变

    echo echo $c>file1中,将 “echo $c>file1” 的输出作为外层 echo 的输入,故输出空,file1 中存入了 3 。

实验反思

exam

exam是一个Makefile补全,比较简单

其主要结构如下:

1
2
3
4
5
6
7
8
9
.
├── calc1.c
├── calc2.c
├── casegen.c
├── common.h
├── main.c
├── Makefile
├── makefile.inc
└── post_calc.c

make 规则如下:

  • 有共同文件 main.c、 post_calc.c、 common.h

  • ver1:编译链接共同文件和 calc1.c,生成可执行文件

  • ver2:编译链接共同文件和 calc2.c,生成可执行文件

  • casegen:编译链接 casegen.c,生成可执行文件

  • all:生成可执行文件 ver1、ver2、casegen

  • run:运行可执行文件 ver1、ver2、casegen

  • clean:清除 casegen、ver1、ver2,以及过程中生成的所有 .txt

注意:

  • make run 和 make 作用相同

  • 运行可执行文件 ver1、ver2、casegen 的相关文件为 makefile.inc 已经提供

    makefile.inc:

    1
    2
    1 CASE_NUM = 50 
    2 INPUT_FILE = input.txt

    运行可执行文件 ver1、ver2、casegen 的运行逻辑是

    1
    2
    3
    ./casegen CASE_NUM INPUT_FILE
    ./ver1 INPUT_FILE
    ./ver2 INPUT_FILE
  • 题目要求,若当前目录下有一个名为 clean、run 的文件,命令仍然执行。 即 clean、run 为伪目标

    使用.PHONY可将一个或多个目标定义为伪目标,其语法为.PHONY:targets,其中 targets可以是一个或多个目标名,以空格分隔。

最终 Makefile 文件如下:

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
 1 include makefile.inc
2
3 COMMON_FILE = main.c post_calc.c common.h
4
5 .PHONY: run clean
6
7 run: casegen ver1 ver2
8 ./casegen $(CASE_NUM) $(INPUT_FILE)
9 ./ver1 $(INPUT_FILE)
10 ./ver2 $(INPUT_FILE)
11
12
13 all: casegen ver1 ver2
14
15 ver1: $(COMMON_FILE) calc1.c
16 gcc $(COMMON_FILE) calc1.c -o ver1
17
18 ver2: $(COMMON_FILE) calc2.c
19 gcc $(COMMON_FILE) calc2.c -o ver2
20
21 casegen: casegen.c
22 gcc casegen.c -o casegen
23
24 clean:
25 rm -f casegen
26 rm -f ver1
27 rm -f ver2
28 rm -f *.txt

extra

其实硬要说,extra也不是很难,但是让我抱怨一下没有天时地利人和,前几天为冯如杯答辩熬了几个通宵,几乎睡了两三个小时,当天中午答辩紧张一天没吃饭,下午oo强测结果出来备受打击。 好吧,确实是没有好好掌握。

题目中包含两种日志文件,分别是 access.log 和 error.log

access.log 记录访问日志,格式样例是:

1
172.16.0.3 - - [2025-10-22:00:06:23 +0800] PUT /about HTTP/1.1 500 0

error.log 记录错误日志,格式样例是:

1
2025-10-22 00:06:39 [FATAL]: Critical service unavailable

题目的 tree 是:

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
.
├── logs
│ ├── 2025-04-06
│ │ ├── access.log
│ │ └── error.log
│ ├── 2025-04-15
│ │ ├── access.log
│ │ └── error.log
│ ├── 2025-05-18
│ │ ├── access.log
│ │ └── error.log
│ ├── 2025-06-14
│ │ ├── access.log
│ │ └── error.log
│ ├── 2025-06-20
│ │ ├── access.log
│ │ └── error.log
│ ├── 2025-06-23
│ │ ├── access.log
│ │ └── error.log
│ ├── 2025-07-12
│ │ ├── access.log
│ │ └── error.log
│ ├── 2025-07-22
│ │ ├── access.log
│ │ └── error.log
│ ├── 2025-08-08
│ │ ├── access.log
│ │ └── error.log
│ ├── 2025-10-11
│ │ ├── access.log
│ │ └── error.log
│ ├── 2025-10-22
│ │ ├── access.log
│ │ └── error.log
│ ├── 2025-11-02
│ │ ├── access.log
│ │ └── error.log
│ ├── 2025-12-05
│ │ ├── access.log
│ │ └── error.log
│ ├── 2025-12-07
│ │ ├── access.log
│ │ └── error.log
│ └── 2025-12-16
│ ├── access.log
│ └── error.log
├── out

我们要完成 analyze.sh ,要求如下:

  1. 要求传入一个参数,表示时间格式为YYYY-MM-DD

    如果没有该参数,输出 analyze.sh: No date provided

    • 这个用 if 语句即可实现

      1
      2
      3
      4
      5
      6 if (($# == 0)); then
      7 echo "analyze.sh: No date provided"
      8 elif (($# == 1))
      9 then
      ……
  2. 创建 reports ,以后生成的所有文件都在其中,所有生成的目录的权限为rwxrwxr-x,其他权限为rw-rw-r–。

    • 创建目录使用mkdir

    • 创建其他文件使用touch

    • 修改权限使用chmod

      目录使用chmod 775 ...

      其他文件使用chmod 664 ...

  3. 在 reports 中创建以输入参数为名字的目录 reports./YYYY-MM-DD

  4. 在 log 中找到相应以输入参数为名字的目录,将其中 error.log 中有 ERROR的行,生成到 reports./YYYY-MM-DD/error.log 中,若没有含有 ERROR 的行,不生成 error.log

    • 查找用 grep即可

    • 这里需要一个判断是否含有 ERROR 的行,这里有两种办法:

      • if grep ERROR ./logs/$1/error.log
        
        1
        2
        3
        4
        5
        6
        7
        8
        9

        联系指导书中的 `if diff file1 file2 `可以想到这种解法,通过尝试可以知道 `grep` 在找到时返回0,未找到时返回1。而 shell脚本中,0为真,1为假,所以我们只需要

        ```sh
        if grep ERROR ./logs/$1/error.log
        then
        touch ./reports/$1/error.log
        ...
        fi
        即可解决
      • 还有一个办法是,先将 grep 的结果存为变量,在通过变量是否为空来判断

        1
        2
        3
        4
        errors=$(grep ERROR ./logs/$1/error.log)
        if [ -z "${errors}" ]; then
        ...
        fi

        当然这个语法超纲了

      • 还有一个办法是,创造一个空文件,比较生成的 error.log 和其是否相等,相等则删掉空文件和 error.log,不相等就只删除空文件

      一个办法都没有想出来,遗憾离场

  5. 将其中 logs/YYYY-MM-DD/access.log 中的 /127.0.0.1,改为 /localhost,生成到 reports/YYYY-MM-DD/access.log 中

    • 利用 sed 和重定向即可完成

      1
      sed 's|/127\.0\.0\.1|/localhost|g' ./logs/$1/access.log > ./reports/$1/access.log

      注意:

      • 由于原本替换部分有 “/“,所以分割符我们要用其他的,sed会默认 ‘s 后面的符号是分隔符
      • . 要能在引号中表示原有的字符,需要变成\.
  6. 统计 logs/YYYY-MM-DD/access.log 中每个 IP 的访问次数,从高到低排列,然后输出到 reports/YYYY-MM-DD/summary.txt 中,格式为“IP地址 访问次数”

    题目提示:

    1. sort 可以进行从小到大的排序,一般采用字典序,-r 可以使其倒序,-n 可以使其按照数字顺序排序

    2. uniq 可以合并附近内容一样的行,-c 可以将出现次数生成在第一列

    • 于是,根据题目提示,再加上 awk 的知识,我们就可以写出答案

      1
      sed 's|/127\.0\.0\.1|/localhost|g' ./logs/$1/access.log > ./reports/$1/access.log
    • 这里要注意,uniq 只是合并附近的,并不是将所有一样的行都合并,所以在使用 uniq 之前我们需要先进行一次排序。

      本人上机又没注意到,又丢了一堆数据点

    一些其他技巧,我们要多次用到冗长的./logs/$1/./reports/$1/可以存成变量,好看,防止写错,也减少麻烦。这些变量不可以设置在判断参数个数之前,不然会报错。

还有一些pre的知识,补充在后面啦,点击展开

GDB:程序的解剖术

准备工作

1
$ gcc -g adds.c -o adds

需要 -g 生成需要的文件

1
$ gdb

运行 GDB

指令 用途
file <executable> 加载程序
run 运行程序
set args [arg]... 设置程序的参数
run [arg]... 以设定的参数运行程序
quit 退出 GDB

也可以在命令行中加载程序,传入参数

1
2
$ gdb <executable>
$ gdb --args ./<executable> [arg]...

追踪程序工作

指令 用途
start 启动程序并使其停在 main 函数起始处
start [arg]... 以设定的参数启动程序并使其停在 main 函数起始处
step 执行下一步程序,包括进入函数
finish 跳出当前函数
next 执行单行程序,不进入函数
continue 继续执行函数
kill 杀死程序进程

设置断点

指令 用途
list [position] 显示指定位置源代码
break [position] 在指定位置设置断点
info breakpoints 显示所有断点信息
disable breakpoints [num]... 禁用指定断点
enable breakpoints [num]... 启用指定断点
delete breakpoints [num]... 删除指定断点
tbreak [position] 在指定位置设置临时断点
break [position] if <condition> 在指定位置设置条件断点
tbreak [position] if <condition> 在指定位置设置条件临时断点
condition <num> <condition> 修改断点的条件
ignore <num> <times> 忽略断点前指定次数的访问
clear <position> 删除指定位置的所有断点
watch <expression> 为指定表达式设置观察点
rwatch <expression> 为指定表达式设置读观察点
awatch <expression> 为指定表达式设置访问观察点

list [position] 指令的使用形式包括 list(显示当前运行位置的源代码)、list <line nunmber>list <file>:<line number>list <function name>list <file>:<function name>

break也有这五种使用形式

条件断点:<condition> 部分应该是一个在当前上下文中符合语义的表达式,如 a >= b + 1,其中 ab 均已定义。一个比较简单的判断方法是如果在代码中添加 if (<condition>){},不会出现编译或运行时错误,则该表达式就可以使用。

程序的机理:查看运行时数据

指令 用途
backtrace 查看调用栈信息
up [steps] 向调用者方向移动栈帧
down [steps] 向被调用者方向移动栈帧
frame [level] 移动到指定的栈帧
print <expression> 查看表达式的值
disassemble [position] 查看对应位置的汇编码

高级调试:修改运行时程序

指令 用途
set variable <variable> = <expression> 修改变量的值
return [expression] 立即从函数中以指定的返回值返回
jump <position> 无条件跳转到程序的指定位置

打开 TUI

1
gdb -tui ./file

或者在 GDB 中Ctrl + X + A

MIPS 相关知识补充

一些directives

  1. .byte: 定义一个字节
  2. .half: 定义一个半字(2字节)
  3. .word: 定义一个字(4字节)
  4. .asciiz: 定义一个以null结尾的字符串
  5. .ascii: 定义一个字符串
  6. .align: 对齐指令,后面跟一个数字表示对齐的字节数(2^n)
  7. .globl: 定义全局符号
  8. .extern: 声明外部符号
    在对另一模块中的全局标签引用时,没有必要添加 .extern(但对另一模块中的全局变量引用时,需要添加.extern)
  9. .set:
    • .set noreorder: 禁止指令重排
    • .set reorder: 允许指令重排(默认)
      reorder 模式下汇编器会自动调度指令至延迟槽,noreorder 模式下需要手动填充延迟槽。
    • .set at: 允许使用 $at 寄存器(默认)
    • .set noat: 禁止使用 $at 寄存器

一些宏

  1. LEAF

    1
    2
    3
    4
    5
    6
    7
    8
    9
    #define LEAF(symbol)                     \ 
    .globl symbol; \ # 定义全局符号
    .align 2; \ # 对齐指令

    .type symbol,@function; \ # 定义符号类型为函数
    .ent symbol; \ # 定义符号的开始位置
    symbol: \
    .frame sp,0,ra # 定义栈帧信息

  2. NESTED

    1
    2
    3
    4
    5
    6
    7
    #define NESTED(symbol, framesize, rpc)   \
    .globl symbol; \
    .align 2; \
    .type symbol,@function; \
    .ent symbol,0; \
    symbol: \
    .frame sp, framesize, rpc
  3. END

    1
    2
    3
    #define END(function)                   \  
    .end function; \ # 定义符号的结束位置
    .size function,.-function \ # 定义符号占用储存空间的大小

C 语言拾遗

从源代码到可执行文件

  1. 预处理

    1
    $ gcc -E hello.c -o hello.i
  2. 编译

    1
    $ gcc -S hello.i -o hello.s
  3. 汇编

    1
    $ gcc -c hello.s -o hello.o
  4. 链接

C语言中变量的存储类别

  1. 存储期

    • 静态存储期:在函数中使用 static 关键字定义的变量,或者在函数外定义的变量,它们所对应的对象具有静态存储期。它们在程序执行期间一直存在,并且在程序执行过程中对象所占据的内存空间大小和对象的起始地址不会发生变化。如果不对该对象进行初始化,那么该对象的值会自动初始化为 0。
    • 自动存储期:在函数中不使用 static 关键字定义的变量所对应的对象具有自动存储期属性。程序执行到该变量声明的时候会创建变量对应的对象,在执行到该变量作用域结束时会释放掉该对象,随后该对象所占据的内存空间可做他用。如果在变量定义时不进行初始化,那么该变量的值不确定。
  2. 作用域

    • 块作用域

      块(block)是用一对花括号括起来的代码区域。定义在块中的变量具有块作用域。

    • 函数作用域

    • 文件作用域

      如果变量定义在函数的外部,那么这个变量具有文件作用域。

  3. 链接

    • 内部链接

      内部链接变量只能在定义它的文件中使用。使用 static 关键字声明的全局变量是内部链接的。

    • 外部链接

      外部链接变量可以在所有的文件中使用。全局变量默认是外部链接的。

      外部链接使用的时候要加上extern,如extern int foo

      定义式声明(defining declaration)引用式声明(referencing declaration):定义式声明会创建对象,而引用式声明不会创建对象,编译器会假设这个变量的定义在别的地方(在函数外)。

    • 无链接

      无链接其实就是这个变量没有链接属性的意思,在函数中定义的变量都是无链接的。

预处理指令

  1. 预处理器发现程序中的宏后,会用宏等价的替换文本进行替换。

    • 定义函数式宏的时候要格外小心,尤其要多加括号

    • 多用do {……} while(0)

      1
      2
      3
      4
      5
      #define MACRO_NAME(para1, para2) \
      do { \
      express1; \
      express2; \
      } while(0)
  2. # 运算符

    # 运算符会自动把传入的参数用双引号括起来称为一个字符串,并且实参中的连续多个空格会被替换成一个空格。

    1
    2
    3
    4
    #define PSQR(x)  printf(" The square of " #x " is %d",((x)*(x)))
    int y = 5
    PSQR(y)
    // 输出:the square of y is 25
  3. ## 运算符

    在宏定义中可以使用 ## 把前后两个预处理符号连接成一个预处理符号,和 # 运算符不同,## 运算符不仅限于函数式宏定义,变量式宏定义也可以用。

    1
    2
    #define CONCAT(a, b) a##b
    // CONCAT(con, cat) 展开为 concat
  4. 变参宏:...__VA_ARGS__

    函数式宏定义还可以带可变参数,具体做法是在参数列表中使用 ... 表示可变参数。在宏定义中,可变参数的部分用 __VA_ARGS__ 表示,实参中对应 ... 的几个参数可以看成一个参数替换到宏定义中 __VA_ARGS__ 所在的地方。

    1
    2
    3
    #define showlist(...) printf(#__VA_ARGS__)
    showlist(The first, second, and third items.);
    // printf("The first, second, and third items.");
  5. #undef

    #undef <macro> 用来取消对 <macro> 的定义

  6. 条件预处理指令

    #ifndef#endif#ifdef#else#if#elif#error

    每写一个头文件都要加上 Header Guard,宏定义名就用头文件名的大写形式

    如:

    1
    2
    #ifndef _STDIO_H
    #define _STDIO_H 1
本作品由 一一 于 2026-03-18 14:00:00 发布
作品地址:北航os操作系统lab0
除特别声明外,本站作品均采用 CC BY-NC-SA 4.0 许可协议,转载请注明来自 anAilurus' Blog
Logo
上一篇北航oo面向对象设计与构造第一单元总结下一篇关于睡眠!