Demo
2020-04-08
- 使用 RMarkdown 的
child
参数,进行文档拼接。 - 这样拼接以后的笔记方便复习。
- 相关问题提交到 Issue
1 Makefile
基本转载教程Makefile CSDN ## Makefile的规则 在讲述这个Makefile之前,还是让我们先来粗略地看一看Makefile的规则。
target... : prerequisites ...
command
target也就是一个目标文件,可以是Object File,也可以是执行文件。还可以是一个标签(Label),对于标签这种特性,在后续的“伪目标”章节中会有叙述。prerequisites就是,要生成那个target所需要的文件或是目标。command也就是make需要执行的命令。(任意的Shell命令)这是一个文件的依赖关系,也就是说,target这一个或多个的目标文件依赖于prerequisites中的文件,其生成规则定义在command中。说白一点就是说,prerequisites中如果有一个以上的文件比target文件要新的话,command所定义的命令就会被执行。这就是Makefile的规则。也就是Makefile中最核心的内容。
1.1 一个示例
正如前面所说的,如果一个工程有3个头文件,和8个C文件,我们为了完成前面所述的那三个规则,我们的Makefile应该是下面的这个样子的。
edit : main.o kbd.o command.o display.o \
insert.o search.o files.o utils.o
cc -o edit main.o kbd.o command.o display.o \
insert.o search.o files.o utils.o
main.o : main.c defs.h
cc -c main.c
kbd.o : kbd.c defs.h command.h
cc -c kbd.c
command.o : command.c defs.h command.h
cc -c command.c
display.o : display.c defs.h buffer.h
cc -c display.c
insert.o : insert.c defs.h buffer.h
cc -c insert.c
search.o : search.c defs.h buffer.h
cc -c search.c
files.o : files.c defs.h buffer.h command.h
cc -c files.c
utils.o : utils.c defs.h
cc -c utils.c
clean :
rm edit main.o kbd.o command.o display.o \
insert.o search.o files.o utils.o
我们可以把这个内容保存在文件为“Makefile”或“makefile”的文件中,然后在该目录下直接输入命令“make”就可以生成执行文件edit。如果要删除执行文件和所有的中间目标文件,那么,只要简单地执行一下“make clean”就可以了。在这个makefile中,目标文件(target)包含:执行文件edit和中间目标文件(*.o),依赖文件(prerequisites)就是冒号后面的那些 .c 文件和 .h文件。每一个 .o 文件都有一组依赖文件,而这些 .o 文件又是执行文件 edit 的依赖文件。依赖关系的实质上就是说明了目标文件是由哪些文件生成的,换言之,目标文件是哪些文件更新的。
在定义好依赖关系后,后续的那一行定义了如何生成目标文件的操作系统命令,一定要以一个Tab键作为开头。记住,make并不管命令是怎么工作的,他只管执行所定义的命令。make会比较targets文件和prerequisites文件的修改日期,如果prerequisites文件的日期要比targets文件的日期要新,或者target不存在的话,那么,make就会执行后续定义的命令。
这里要说明一点的是,clean不是一个文件,它只不过是一个动作名字,有点像C语言中的lable一样,其冒号后什么也没有,那么,make就不会自动去找文件的依赖性,也就不会自动执行其后所定义的命令。要执行其后的命令,就要在make命令后明显得指出这个lable的名字。这样的方法非常有用,我们可以在一个makefile中定义不用的编译或是和编译无关的命令,比如程序的打包,程序的备份,等等。
1.2 make是如何工作的
在默认的方式下,也就是我们只输入make命令。那么,make会在当前目录下找名字叫“Makefile”或“makefile”的文件。如果找到,它会找文件中的第一个目标文件(target),在上面的例子中,他会找到“edit”这个文件,并把这个文件作为最终的目标文件。 如果edit文件不存在,或是edit所依赖的后面的 .o 文件的文件修改时间要比edit这个文件新,那么,他就会执行后面所定义的命令来生成edit这个文件。 如果edit所依赖的.o文件也存在,那么make会在当前文件中找目标为.o文件的依赖性,如果找到则再根据那一个规则生成.o文件。(这有点像一个堆栈的过程) 当然,你的C文件和H文件是存在的啦,于是make会生成 .o 文件,然后再用 .o 文件声明make的终极任务,也就是执行文件edit了。 这就是整个make的依赖性,make会一层又一层地去找文件的依赖关系,直到最终编译出第一个目标文件。在找寻的过程中,如果出现错误,比如最后被依赖的文件找不到,那么make就会直接退出,并报错,而对于所定义的命令的错误,或是编译不成功,make根本不理。make只管文件的依赖性,即,如果在我找了依赖关系之后,冒号后面的文件还是不在,那么对不起,我就不工作啦。 通过上述分析,我们知道,像clean这种,没有被第一个目标文件直接或间接关联,那么它后面所定义的命令将不会被自动执行,不过,我们可以显示要make执行。即命令——“make clean”,以此来清除所有的目标文件,以便重编译。
于是在我们编程中,如果这个工程已被编译过了,当我们修改了其中一个源文件,比如file.c,那么根据我们的依赖性,我们的目标file.o会被重编译(也就是在这个依性关系后面所定义的命令),于是file.o的文件也是最新的啦,于是file.o的文件修改时间要比edit要新,所以edit也会被重新链接了(详见edit目标文件后定义的命令)。
而如果我们改变了“command.h”,那么,kdb.o、command.o和files.o都会被重编译,并且,edit会被重链接。
1.3 makefile中使用变量
在上面的例子中,先让我们看看edit的规则:
edit : main.o kbd.o command.o display.o \
insert.o search.o files.o utils.o
cc -o edit main.o kbd.o command.o display.o \
insert.o search.o files.o utils.o
我们可以看到[.o]文件的字符串被重复了两次,如果我们的工程需要加入一个新的[.o]文件,那么我们需要在两个地方加(应该是三个地方,还有一个地方在clean中)。当然,我们的makefile并不复杂,所以在两个地方加也不累,但如果makefile变得复杂,那么我们就有可能会忘掉一个需要加入的地方,而导致编译失败。所以,为了makefile的易维护,在makefile中我们可以使用变量。makefile的变量也就是一个字符串,理解成C语言中的宏可能会更好。
比如,我们声明一个变量,叫objects, OBJECTS, objs, OBJS, obj, 或是 OBJ,反正不管什么啦,只要能够表示obj文件就行了。我们在makefile一开始就这样定义:
objects = main.o kbd.o command.o display.o \
insert.o search.o files.o utils.o
于是,我们就可以很方便地在我们的makefile中以“$(objects)”的方式来使用这个变量了,于是我们的改良版makefile就变成下面这个样子:
objects = main.o kbd.o command.o display.o \
insert.osearch.o files.o utils.o
edit : $(objects)
cc -o edit $(objects)
main.o : main.c defs.h
cc -c main.c
kbd.o : kbd.c defs.h command.h
cc -c kbd.c
command.o : command.c defs.h command.h
cc -c command.c
display.o : display.c defs.h buffer.h
cc -c display.c
insert.o : insert.c defs.h buffer.h
cc -c insert.c
search.o : search.c defs.h buffer.h
cc -c search.c
files.o : files.c defs.h buffer.h command.h
cc -c files.c
utils.o : utils.c defs.h
cc -c utils.c
clean :
rm edit $(objects)
于是如果有新的 .o 文件加入,我们只需简单地修改一下 objects 变量就可以了。
关于变量更多的话题,我会在后续给你一一道来。
1.4 让make自动推导
GNU的make很强大,它可以自动推导文件以及文件依赖关系后面的命令,于是我们就没必要去在每一个[.o]文件后都写上类似的命令,因为,我们的make会自动识别,并自己推导命令。
只要make看到一个[.o]文件,它就会自动的把[.c]文件加在依赖关系中,如果make找到一个whatever.o,那么whatever.c,就会是whatever.o的依赖文件。并且 cc -c whatever.c 也会被推导出来,于是,我们的makefile再也不用写得这么复杂。我们的是新的makefile又出炉了。
objects = main.o kbd.o command.o display.o \
insert.o search.o files.o utils.o
edit : $(objects)
cc -o edit $(objects)
main.o : defs.h
kbd.o : defs.h command.h
command.o : defs.h command.h
display.o : defs.h buffer.h
insert.o : defs.h buffer.h
search.o : defs.h buffer.h
files.o : defs.h buffer.h command.h
utils.o : defs.h
.PHONY : clean
clean :
rm edit $(objects)
这种方法,也就是make的“隐晦规则”。上面文件内容中,“.PHONY”表示,clean是个伪目标文件。
1.5 清空目标文件的规则
每个Makefile中都应该写一个清空目标文件(.o和执行文件)的规则,这不仅便于重编译,也很利于保持文件的清洁。这是一个“修养”(呵呵,还记得我的《编程修养》吗)。一般的风格都是:
clean:
rm edit $(objects)
更为稳健的做法是:
.PHONY : clean
clean :
-rm edit $(objects)
前面说过,.PHONY意思表示clean是一个“伪目标”,。而在rm命令前面加了一个小减号的意思就是,也许某些文件出现问题,但不要管,继续做后面的事。当然,clean的规则不要放在文件的开头,不然,这就会变成make的默认目标,相信谁也不愿意这样。不成文的规矩是——“clean从来都是放在文件的最后”。
上面就是一个makefile的概貌,也是makefile的基础,下面还有很多makefile的相关细节。
1.6 Makefile的文件名
默认的情况下,make命令会在当前目录下按顺序找寻文件名为“GNUmakefile”、“makefile”、“Makefile”的文件,找到了解释这个文件。在这三个文件名中,最好使用“Makefile”这个文件名,因为,这个文件名第一个字符为大写,这样有一种显目的感觉。最好不要用“GNUmakefile”,这个文件是GNU的make识别的。有另外一些make只对全小写的“makefile”文件名敏感,但是基本上来说,大多数的make都支持“makefile”和“Makefile”这两种默认文件名。
当然,你可以使用别的文件名来书写Makefile,比如:“Make.Linux”,“Make.Solaris”,“Make.AIX”等,如果要指定特定的Makefile,你可以使用make的“-f”和“–file”参数,如:make -f Make.Linux或make –file Make.AIX。
1.7 使用变量
在 Makefile中的定义的变量,就像是C/C++语言中的宏一样,他代表了一个文本字串,在Makefile中执行的时候其会自动原模原样地展开在所使用的地方。其与C/C++所不同的是,你可以在Makefile中改变其值。在Makefile中,变量可以使用在“目标”,“依赖目标”,“命令”或是 Makefile的其它部分中。变量的命名字可以包含字符、数字,下划线(可以是数字开头),但不应该含有“:”、“#”、“=”或是空字符(空格、回车等)。变量是大小写敏感的,“foo”、“Foo”和“FOO”是三个不同的变量名。传统的Makefile的变量名是全大写的命名方式,但我推荐使用大小写搭配的变量名,如:MakeFlags。这样可以避免和系统的变量冲突,而发生意外的事情。有一些变量是很奇怪字串,如“\(<”、“\)@”等,这些是自动化变量,我会在后面介绍。
1.7.1 一、变量的基础
变量在声明时需要给予初值,而在使用时,需要给在变量名前加上“$”符号,但最好用小括号“()”或是大括号“{}”把变量给包括起来。如果你要使用真实的“$”字符,那么你需要用“$$”来表示。变量可以使用在许多地方,如规则中的“目标”、“依赖”、“命令”以及新的变量中。
先看一个例子:
objects = program.o foo.o utils.o
program : $(objects)
cc -o program $(objects)
$(objects) : defs.h
变量会在使用它的地方精确地展开,就像C/C++中的宏一样,例如:
foo = c
prog.o : prog.$(foo)
$(foo)$(foo) -$(foo) prog.$(foo)
展开后得到:
prog.o : prog.c
cc -c prog.c
1.7.2 变量赋值
这种方法使用的是“:=”操作符,如:
x := foo
y := $(x) bar
x := later
其等价于:
y := foo bar
x := later
值得一提的是,这种方法,前面的变量不能使用后面的变量,只能使用前面已定义好了的变量。如果是这样:
y := $(x) bar
x := foo
那么,y的值是“bar”,而不是“foo bar”。
1.7.3 自动化变量
关于自动化变量可以理解为由 Makefile 自动产生的变量。在模式规则中,规则的目标和依赖的文件名代表了一类的文件。规则的命令是对所有这一类文件的描述。我们在 Makefile 中描述规则时,依赖文件和目标文件是变动的,显然在命令中不能出现具体的文件名称,否则模式规则将失去意义。那么模式规则命令中该如何表示文件呢?就需要使用“自动化变量”,自动化变量的取值根据执行的规则来决定,取决于执行规则的目标文件和依赖文件。下面是对所有的自动化变量进行的说明:
- $@ 表示规则的目标文件名。如果目标是一个文档文件(Linux 中,一般成 .a 文件为文档文件,也成为静态的库文件), 那么它代表这个文档的文件名。在多目标模式规则中,它代表的是触发规则被执行的文件名。
- $% 当目标文件是一个静态库文件时,代表静态库的一个成员名。
- $< 规则的第一个依赖的文件名。如果是一个目标文件使用隐含的规则来重建,则它代表由隐含规则加入的第一个依赖文件。
- $? 所有比目标文件更新的依赖文件列表,空格分隔。如果目标文件时静态库文件,代表的是库文件(.o 文件)。
- $^ 代表的是所有依赖文件列表,使用空格分隔。如果目标是静态库文件,它所代表的只能是所有的库成员(.o 文件)名。 一个文件可重复的出现在目标的依赖中,变量“$”只记录它的第一次引用的情况。就是说变量“$”会去掉重复的依赖文件。
- $+ 类似“$^”,但是它保留了依赖文件中重复出现的文件。主要用在程序链接时库的交叉引用场合。 $*| 在模式规则和静态模式规则中,代表“茎”。“茎”是目标模式中“%”所代表的部分(当文件名中存在目录时, “茎”也包含目录部分)。 下面我们就自动化变量的使用举几个例子。
实例1:
test:test.o test1.o test2.o
gcc -o $@ $^
test.o:test.c test.h
gcc -o $@ $<
test1.o:test1.c test1.h
gcc -o $@ $<
test2.o:test2.c test2.h
gcc -o $@ $<
这个规则模式中用到了 “$@” 、“$<” 和 “$^” 这三个自动化变量,对比之前写的 Makefile 中的命令,我们可以发现 “$@” 代表的是目标文件test,“$^”代表的是依赖的文件,“$<”代表的是依赖文件中的第一个。我们在执行 make 的时候,make 会自动识别命令中的自动化变量,并自动实现自动化变量中的值的替换,这个类似于编译C语言文件的时候的预处理的作用。
实例2:
lib:test.o test1.o test2.o
ar r $?
假如我们要做一个库文件,库文件的制作依赖于这三个文件。当修改了其中的某个依赖文件,在命令行执行 make 命令,库文件 “lib” 就会自动更新。“$?” 表示修改的文件。
2 linux.sh语法
2.1 开头
程序必须以下面的行开始(必须放在文件的第一行):
#!/bin/sh
符号#!用来告诉系统它后面的参数是用来执行该文件的程序。在这个例子中我们使用/bin/sh来执行程序。 当编写脚本完成时,如果要执行该脚本,还必须使其可执行。 要使编写脚本可执行: 编译 chmod +x filename 这样才能用./filename 来运行
2.2 注释
在进行shell编程时,以#开头的句子表示注释,直到这一行的结束。我们真诚地建议您在程序中使用注释。 如果您使用了注释,那么即使相当长的时间内没有使用该脚本,您也能在很短的时间内明白该脚本的作用及工作原理。
2.3 变量
在其他编程语言中您必须使用变量。在shell编程中,所有的变量都由字符串组成,并且您不需要对变量进行声明。要赋值给一个变量,您可以这样写:
#!/bin/sh
#对变量赋值:
a=”hello world”
# 现在打印变量a的内容:
echo “A is:”
echo $a
有时候变量名很容易与其他文字混淆,比如:
num=2
echo “this is the $numnd”
这并不会打印出”this is the 2nd”,而仅仅打印”this is the “,因为shell会去搜索变量numnd的值,但是这个变量时没有值的。可以使用花括号来告诉shell我们要打印的是num变量:
num=2
echo “this is the ${num}nd”
这将打印: this is the 2nd
2.4 环境变量
由export关键字处理过的变量叫做环境变量。我们不对环境变量进行讨论,因为通常情况下仅仅在登录脚本中使用环境变量。
2.5 Shell命令和流程控制
在shell脚本中可以使用三类命令:
2.5.1 1. Unix 命令:
虽然在shell脚本中可以使用任意的unix命令,但是还是由一些相对更常用的命令。这些命令通常是用来进行文件和文字操作的。 常用命令语法及功能:
- echo “some text”: 将文字内容打印在屏幕上
- ls: 文件列表
- wc –l file wc -w file wc -c file: 计算文件行数;计算文件中的单词数;计算文件中的字符数
- cp sourcefile destfile: 文件拷贝
- mv oldname newname : 重命名文件或移动文件
- rm file: 删除文件
- grep ‘pattern’ file: 在文件内搜索字符串比如:grep ’searchstring’ file.txt
- cut -b colnum file: 指定欲显示的文件内容范围,并将它们输出到标准输出设备比如:输出每行第5个到第9个字符cut -b5-9 file.txt千万不要和cat命令混淆, 这是两个完全不同的命令
- cat file.txt: 输出文件内容到标准输出设备(屏幕)上
- file somefile: 得到文件类型
- read var: 提示用户输入,并将输入赋值给变量
- sort file.txt: 对file.txt文件中的行进行排序
- uniq: 删除文本文件中出现的重复行,比如: sort file.txt | uniq
- expr: 进行数学运算Example: add 2 and 3 为 expr 2 “+” 3
- find: 搜索文件比如:根据文件名搜索find . -name filename -print
- tee: 将数据输出到标准输出设备(屏幕) 和文件比如:somecommand | tee outfile
- basename file: 返回不包含路径的文件名比如: basename /bin/tux将返回 tux
- dirname file: 返回文件所在路径比如:dirname /bin/tux将返回 /bin
- head file: 打印文本文件开头几行
- tail file : 打印文本文件末尾几行
- sed: Sed是一个基本的查找替换程序。可以从标准输入(比如命令管道)读入文本,并将 结果输出到标准输出(屏幕)。该命令采用正则表达式(见参考)进行搜索。不要和shell中的通配符相混淆。比如:将linuxfocus替换为 LinuxFocus :
cat text.file | sed ’s/linuxfocus/LinuxFocus/’ >newtext.file
- awk: awk 用来从文本文件中提取字段。缺省地,字段分割符是空格,可以使用-F指定其他分割符。
cat file.txt | awk -F, ‘{print \$1 “,” \$3}’
这里我们使用,作为字段分割符,同时打印第一个和第三个字段。如果该文件内容如下: Adam Bor, 34, IndiaKerryMiller, 22, USA命令输出结果为:Adam Bor, IndiaKerry Miller, USA
2.5.2 2. 概念: 管道, 重定向和 backtick
这些不是系统命令,但是他们真的很重要。
管道 (|) 将一个命令的输出作为另外一个命令的输入。
grep “hello” file.txt | wc -l
在file.txt中搜索包含有”hello”的行并计算其行数。
在这里grep命令的输出作为wc命令的输入。当然您可以使用多个命令。
重定向:将命令的结果输出到文件,而不是标准输出(屏幕)。
> 写入文件并覆盖旧文件
>> 追加到文件的尾部,保留旧文件内容。
反短斜线
使用反短斜线可以将一个命令的输出作为另外一个命令的一个命令行参数。
命令:
find . -mtime -1 -type f -print
用来查找过去24小时(-mtime –2则表示过去48小时)内修改过的文件。如果您想将所有查找到的文件打一个包,则可以使用以下linux 脚本:
#!/bin/sh # The ticks are backticks (`) not normal quotes (‘): tar -zcvf lastmod.tar.gz `find . -mtime -1 -type f -print`
2.5.3 3. 流程控制
1.if
“if” 表达式 如果条件为真则执行then后面的部分:
if ….; then
….
elif ….; then
….
else
….
fi
大多数情况下,可以使用测试命令来对条件进行测试。比如可以比较字符串、判断文件是否存在及是否可读等等…
通常用” [ ] “来表示条件测试。注意这里的空格很重要。要确保方括号的空格。
[ -f "somefile" ] :判断是否是一个文件
[ -x "/bin/ls" ] :判断/bin/ls是否存在并有可执行权限
[ -n "$var" ] :判断$var变量是否有值
[ "$a" = "$b" ] :判断$a和$b是否相等
执行man test可以查看所有测试表达式可以比较和判断的类型。
直接执行以下脚本:
\#!/bin/sh if [ "\$SHELL" = "/bin/bash" ]; then echo “your login shell is the bash (bourne again shell)” else echo “your login shell is not bash but $SHELL” fi
这里有个例子:
#!/bin/sh mailfolder=/var/spool/mail/james [ -r "$mailfolder" ]‘ ‘{ echo “Can not read $mailfolder” ; exit 1; } echo “$mailfolder has mail from:” grep “^From ” $mailfolder
该脚本首先判断mailfolder是否可读。如果可读则打印该文件中的”From” 一行。如果不可读则或操作生效,打印错误信息后脚本退出。这里有个问题,那就是我们必须有两个命令:
◆打印错误信息
◆退出程序
我们使用花括号以匿名函数的形式将两个命令放到一起作为一个命令使用。一般函数将在下文提及。
不用与和或操作符,我们也可以用if表达式作任何事情,但是使用与或操作符会更便利很多。
## 例子
# # sac & td3
for exp in summon
do
# for version in pow-averse pow-seeking wang-averse wang-seeking neutral cpw-averse cpw-seeking cvar-averse cvar-seeking
# for version in cpw-seeking cvar-seeking # pow-seeking wang-seeking
for version in neutral cpw-averse cvar-averse pow-averse wang-averse
do
for seed in 2
do
mkdir -p "logs/dsac-${exp}-${version}"
python dsac_v2.py --config "configs/highway/${exp}-${version}.yaml" --gpu 2 --seed ${seed} >> "logs/dsac-${exp}-${version}/dsac-${exp}-${version}--s-${seed}" 2>&1 &
done
done
done