Missing Semester

2022-12-08 14:55:11 浏览数 (1)

Missing Semester

于2022年3月17日2022年3月17日由Sukuna发布

Missing Semester

topic1:git

为什么说版本控制系统非常有用?即使您只是一个人进行编程工作,它也可以帮您创建项目的快照,记录每个改动的目的、基于多分支并行开发等等。和别人协作开发时,它更是一个无价之宝,您可以看到别人对代码进行的修改,同时解决由于并行开发引起的冲突。

基础

  • 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 和目前的分支

分支和合并

git branch: 显示分支

git branch <name>: 创建分支

代码语言:javascript复制
git checkout -b <name>

: 创建分支并切换到该分支

  • 相当于 git branch <name>; git checkout <name>

git merge <revision>: 合并到当前分支

git mergetool: 使用工具来处理合并冲突

git rebase: 将一系列补丁变基(rebase)为新的基线

远端操作

  • 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: 从远端下载仓库

撤销

  • git commit --amend: 编辑提交的内容或信息
  • git reset HEAD <file>: 恢复暂存的文件
  • git checkout -- <file>: 丢弃修改

Git 高级操作

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

topic2: Vim

编辑模式

Vim的设计以大多数时间都花在阅读、浏览和进行少量编辑改动为基础,因此它具有多种操作模式:

  • 正常模式:在文件中四处移动光标进行修改
  • 插入模式:插入文本
  • 替换模式:替换文本
  • 可视化(一般,行,块)模式:选中文本块
  • 命令模式:用于执行命令

在不同的操作模式下,键盘敲击的含义也不同。比如,x 在插入模式会插入字母x,但是在正常模式 会删除当前光标所在的字母,在可视模式下则会删除选中文块。

在默认设置下,Vim会在左下角显示当前的模式。 Vim启动时的默认模式是正常模式。通常你会把大部分 时间花在正常模式和插入模式。

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

插入文本

在正常模式,键入 i 进入插入模式。现在 Vim 跟很多其他的编辑器一样,直到你键入<ESC> 返回正常模式。 你只需要掌握这一点和上面介绍的所有基础知识就可以使用 Vim 来编辑文件了 (虽然如果你一直停留在插入模式内不一定高效)。

缓存, 标签页, 窗口

Vim 会维护一系列打开的文件,称为“缓存”。一个 Vim 会话包含一系列标签页,每个标签页包含 一系列窗口(分隔面板)。每个窗口显示一个缓存。跟网页浏览器等其他你熟悉的程序不一样的是, 缓存和窗口不是一一对应的关系;窗口只是视角。一个缓存可以在多个窗口打开,甚至在同一 个标签页内的多个窗口打开。这个功能其实很好用,比如在查看同一个文件的不同部分的时候。

Vim 默认打开一个标签页,这个标签也包含一个窗口。

命令行

在正常模式下键入 : 进入命令行模式。 在键入 : 后,你的光标会立即跳到屏幕下方的命令行。 这个模式有很多功能,包括打开,保存,关闭文件,以及 退出 Vim。

  • :q 退出(关闭窗口)
  • :w 保存(写)
  • :wq 保存然后退出
  • :e {文件名} 打开要编辑的文件
  • :ls 显示打开的缓存
  • :help {标题}打开帮助文档
    • :help :w 打开 :w 命令的帮助文档
    • :help w 打开 w 移动的帮助文档

移动

多数时候你会在正常模式下,使用移动命令在缓存中导航。在 Vim 里面移动也被称为 “名词”, 因为它们指向文字块。

  • 基本移动: hjkl (左, 下, 上, 右)
  • 词: w (下一个词), b (词初), e (词尾)
  • 行: 0 (行初), ^ (第一个非空格字符), $ (行尾)
  • 屏幕: H (屏幕首行), M (屏幕中间), L (屏幕底部)
  • 翻页: Ctrl-u (上翻), Ctrl-d (下翻)
  • 文件: gg (文件头), G (文件尾)
  • 行数: :{行数}<CR> 或者 {行数}G ({行数}为行数)
  • 杂项: % (找到配对,比如括号或者 /* */ 之类的注释对)
  • 查找:f{字符}, t{字符}, F{字符},T{字符}
    • 查找/到 向前/向后 在本行的{字符}
    • , / ; 用于导航匹配
  • 搜索: /{正则表达式}, n / N 用于导航匹配

编辑

所有你需要用鼠标做的事, 你现在都可以用键盘:采用编辑命令和移动命令的组合来完成。 这就是 Vim 的界面开始看起来像一个程序语言的时候。Vim 的编辑命令也被称为 “动词”, 因为动词可以施动于名词。

  • i 进入插入模式
    • 但是对于操纵/编辑文本,不单想用退格键完成
  • O / o 在之上/之下插入行
  • d{移动命令}删除 {移动命令}
    • 例如, dw 删除词, d$ 删除到行尾, d0 删除到行头。
  • c{移动命令}改变 {移动命令}
    • 例如, cw 改变词
    • 比如 d{移动命令}i
  • x 删除字符(等同于 dl
  • s 替换字符(等同于 xi
  • 可视化模式 操作
    • 选中文字, d 删除 或者 c 改变
  • u 撤销, <C-r> 重做
  • y 复制 / “yank” (其他一些命令比如 d 也会复制)
  • p 粘贴
  • 更多值得学习的: 比如 ~ 改变字符的大小写

计数

你可以用一个计数来结合“名词”和“动词”,这会执行指定操作若干次。

  • 3w 向前移动三个词
  • 5j 向下移动5行
  • 7dw 删除7个词

修饰语

你可以用修饰语改变“名词”的意义。修饰语有 i,表示“内部”或者“在内“,和 a, 表示”周围“。

  • ci( 改变当前括号内的内容
  • ci[ 改变当前方括号内的内容
  • da' 删除一个单引号字符串, 包括周围的单引号

topic 3:Linux

一般来说就是由这几个部分组成的.

  • /bin: bin 是 Binaries (二进制文件) 的缩写, 这个目录存放着最经常使用的命令。比如说很多个Shell的程序都会放在/bin文件夹里面。
  • /boot: 这里存放的是启动 Linux 时使用的一些核心文件,包括一些连接文件以及镜像文件。
  • /dev : dev 是 Device(设备) 的缩写, 该目录下存放的是 Linux 的外部设备,在 Linux 中访问设备的方式和访问文件的方式是相同的。
  • /etc: etc 是 Etcetera(等等) 的缩写,这个目录用来存放所有的系统管理所需要的配置文件和子目录。
  • /home: 用户的主目录,在 Linux 中,每个用户都有一个自己的目录,一般该目录名是以用户的账号命名的,如上图中的 alice、bob 和 eve。
  • /lib: lib 是 Library(库) 的缩写这个目录里存放着系统最基本的动态连接共享库,其作用类似于 Windows 里的 DLL 文件。几乎所有的应用程序都需要用到这些共享库。
  • /lost found: 这个目录一般情况下是空的,当系统非法关机后,这里就存放了一些文件。
  • /media: linux 系统会自动识别一些设备,例如U盘、光驱等等,当识别后,Linux 会把识别的设备挂载到这个目录下。
  • /mnt: 系统提供该目录是为了让用户临时挂载别的文件系统的,我们可以将光驱挂载在 /mnt/ 上,然后进入该目录就可以查看光驱里的内容了。
  • /opt: opt 是 optional(可选) 的缩写,这是给主机额外安装软件所摆放的目录。比如你安装一个ORACLE数据库则就可以放到这个目录下。默认是空的。
  • /proc: proc 是 Processes(进程) 的缩写,/proc 是一种伪文件系统(也即虚拟文件系统),存储的是当前内核运行状态的一系列特殊文件,这个目录是一个虚拟的目录,它是系统内存的映射,我们可以通过直接访问这个目录来获取系统信息。
  • /root: 该目录为系统管理员,也称作超级权限者的用户主目录。
  • /sbin: s 就是 Super User 的意思,是 Superuser Binaries (超级用户的二进制文件) 的缩写,这里存放的是系统管理员使用的系统管理程序。
  • /selinux: 这个目录是 Redhat/CentOS 所特有的目录,Selinux 是一个安全机制,类似于 windows 的防火墙,但是这套机制比较复杂,这个目录就是存放selinux相关的文件的。
  • /srv: 该目录存放一些服务启动之后需要提取的数据。
  • /sys: 这是 Linux2.6 内核的一个很大的变化。该目录下安装了 2.6 内核中新出现的一个文件系统 sysfs 。 sysfs 文件系统集成了下面3种文件系统的信息:针对进程信息的 proc 文件系统、针对设备的 devfs 文件系统以及针对伪终端的 devpts 文件系统。 该文件系统是内核设备树的一个直观反映。 当一个内核对象被创建的时候,对应的文件和目录也在内核对象子系统中被创建。
  • /tmp: tmp 是 temporary(临时) 的缩写这个目录是用来存放一些临时文件的。
  • /usr: usr 是 unix shared resources(共享资源) 的缩写,这是一个非常重要的目录,用户的很多应用程序和文件都放在这个目录下,类似于 windows 下的 program files 目录。
  • /usr/bin: 系统用户使用的应用程序。
  • /usr/sbin: 超级用户使用的比较高级的管理程序和系统守护程序。
  • /usr/src: 内核源代码默认的放置目录。
  • /var: var 是 variable(变量) 的缩写,这个目录中存放着在不断扩充着的东西,我们习惯将那些经常被修改的目录放在这个目录下。包括各种日志文件。
  • /run: 是一个临时文件系统,存储系统启动以来的信息。当系统重启时,这个目录下的文件应该被删掉或清除。如果你的系统上有 /var/run 目录,应该让它指向 run。

很多个文件夹都不能随意更改:/etc,/bin,/sbin,/usr/bin,这里面存放着配置文件和一些预设的执行文件的目录.

SSH登陆:

安装ssh:

代码语言:javascript复制
yum install ssh

启动ssh:

代码语言:javascript复制
service sshd start

登录远程服务器:

代码语言:javascript复制
ssh -p 50022 my@127.0.0.1
输入密码:
my@127.0.0.1:

-p 后面是端口

my 是服务器用户名

127.0.0.1 是服务器 ip

回车输入密码即可登录

用户权限:

Linux 系统是一种典型的多用户系统,不同的用户处于不同的地位,拥有不同的权限。

为了保护系统的安全性,Linux 系统对不同的用户访问同一文件(包括目录文件)的权限做了不同的规定。

在 Linux 中我们通常使用以下两个命令来修改文件或目录的所属用户与权限:

  • chown (change owner) : 修改所属用户与组。chown [–R] 属主名 文件名
  • chmod (change mode) : 修改用户的权限。chmod [-R] xyz 文件或目录,其中xyz表示三个0~7之内的数字.
  • xyz : 就是刚刚提到的数字类型的权限属性,为 rwx 属性数值的相加。
  • -R : 进行递归(recursive)的持续变更,以及连同次目录下的所有文件都会变更。

其中我们可以使用ls -l指令来查看文件所属的用户和组.

其中这九个权限是三个三个一组的!其中,我们可以使用数字来代表各个权限,各权限的分数对照表如下:如果权限的数字对应是6,那么就代表可读可写.其中文件的权限由三组数字组成,分别代表本人,本人所属组和其他人的权限.其中这数字可以转换.Linux用这几种数字来进行文件权限的标记.

  • r:4
  • w:2
  • x:1

每种身份(owner/group/others)各自的三个权限(r/w/x)分数是需要累加的,例如当权限为: -rwxrwx— 分数则是:

  • owner = rwx = 4 2 1 = 7
  • group = rwx = 4 2 1 = 7
  • others= — = 0 0 0 = 0

所以等一下我们设定权限的变更时,该文件的权限数字就是 770。变更权限的指令 chmod 的语法是这样的:

chmod 770 xxx

文件目录常用命令:

ls (列出目录)

在Linux系统当中, ls 命令可能是最常被运行的。

语法:

代码语言:javascript复制
[Sukuna@Sukuna]# ls [-aAdfFhilnrRSt] 目录名称
[Sukuna@Sukuna]# ls [--color={never,auto,always}] 目录名称
[Sukuna@Sukuna]# ls [--full-time] 目录名称

选项与参数:

  • -a :全部的文件,连同隐藏文件( 开头为 . 的文件) 一起列出来(常用)
  • -d :仅列出目录本身,而不是列出目录内的文件数据(常用)
  • -l :长数据串列出,包含文件的属性与权限等等数据;(常用)

cd (切换目录)

cd是Change Directory的缩写,这是用来变换工作目录的命令。因为Linux寻找文件是在工作目录为基准然后进行路径查找

语法:

代码语言:javascript复制
 cd [相对路径或绝对路径]

pwd (显示目前所在的目录)

pwd 是 Print Working Directory 的缩写,也就是显示目前所在目录的命令。

代码语言:javascript复制
[Sukuna@Sukuna]# pwd [-P]

选项与参数:

  • -P :显示出确实的路径,而非使用连结 (link) 路径。

mkdir (创建新目录)

如果想要创建新的目录的话,那么就使用mkdir (make directory)吧。

语法:

代码语言:javascript复制
mkdir [-mp] 目录名称

选项与参数:

  • -m :配置文件的权限喔!直接配置,不需要看默认权限 (umask) 的脸色~ mkdir -m 711 test2
  • -p :帮助你直接将所需要的目录(包含上一级目录)递归创建起来!这可以帮助你一次性生成多级的目录

rmdir (删除空的目录)

语法:

代码语言:javascript复制
 rmdir [-p] 目录名称

选项与参数:

  • -p :从该目录起,一次删除多级空目录.和上面的mkdir类似,可以直接一次性删除多级的目录.
代码语言:javascript复制
[Sukuna@Sukuna tmp]# ls -l   <==看看有多少目录存在?
drwxr-xr-x  3 root  root 4096 Jul 18 12:50 test
drwxr-xr-x  3 root  root 4096 Jul 18 12:53 test1
drwx--x--x  2 root  root 4096 Jul 18 12:54 test2
[Sukuna@Sukuna tmp]# rmdir test   <==可直接删除掉,没问题
[Sukuna@Sukuna tmp]# rmdir test1  <==因为尚有内容,所以无法删除!
rmdir: `test1': Directory not empty
[Sukuna@Sukuna tmp]# rmdir -p test1/test2/test3/test4 <=可以一次性删除子目录.
[Sukuna@Sukuna tmp]# ls -l        <==您看看,底下的输出中test与test1不见了!
drwx--x--x  2 root  root 4096 Jul 18 12:54 test2

cp (复制文件或目录)

cp 即拷贝文件和目录。

语法:

代码语言:javascript复制
[Sukuna@Sukuna]# cp [-dfilprsu] 来源档(source) 目标档(destination)
//把若干个文件放到directory位置/
[Sukuna@Sukuna]# cp [options] source1 source2 source3 .... directory

选项与参数:

  • -d:若来源档为链接文件的属性(link file),则复制连结档属性而非文件本身;
  • -f:为强制(force)的意思,若目标文件已经存在且无法开启,则移除后再尝试一次;
  • -i:若目标档(destination)已经存在时,在覆盖时会先询问动作的进行(常用)
  • -l:进行硬式连结(hard link)的连结档创建,而非复制文件本身;
  • -p:连同文件的属性一起复制过去,而非使用默认属性(备份常用);
  • -r:递归持续复制,用於目录的复制行为;(常用)
  • -s:复制成为符号连结档 (symbolic link),亦即『捷径』文件;
  • -u:若 destination 比 source 旧才升级 destination !

用 root 身份,将 root 目录下的 .bashrc 复制到 /tmp 下,并命名为 bashrc.

代码语言:javascript复制
[Sukuna@Sukuna]# cp ~/.bashrc /tmp/bashrc

rm (移除文件或目录)

语法:

代码语言:javascript复制
 rm [-fir] 文件或目录

选项与参数:

  • -f :就是 force 的意思,忽略不存在的文件,不会出现警告信息;
  • -i :互动模式,在删除前会询问使用者是否动作
  • -r :递归删除.(不建议使用)

mv (移动文件与目录,或修改名称)

语法:

代码语言:javascript复制
[Sukuna@Sukuna]# mv [-fiu] source destination
[Sukuna@Sukuna]# mv [options] source1 source2 source3 .... directory

选项与参数:

  • -f :force 强制的意思,如果目标文件已经存在,不会询问而直接覆盖;
  • -i :若目标文件 (destination) 已经存在时,就会询问是否覆盖!
  • -u :若目标文件已经存在,且 source 比较新,才会升级 (update)

cat

由第一行开始显示文件内容

语法:

代码语言:javascript复制
cat [-AbEnTv]

选项与参数:

  • -A :可列出一些特殊字符而不是空白而已;
  • -b :列出行号,仅针对非空白行做行号显示,空白行不标行号!
  • -E :将结尾的断行字节 $ 显示出来;
  • -n :列印出行号,连同空白行也会有行号,与 -b 的选项不同;
  • -T :将 [tab] 按键以 ^I 显示出来;
  • -v :列出一些看不出来的特殊字符

nl

显示行号的cat

语法:

代码语言:javascript复制
nl [-bnw] 文件

选项与参数:

  • -b :指定行号指定的方式,主要有两种: -b a :表示不论是否为空行,也同样列出行号(类似 cat -n); -b t :如果有空行,空的那一行不要列出行号(默认值);
  • -n :列出行号表示的方法,主要有三种: -n ln :行号在荧幕的最左方显示; -n rn :行号在自己栏位的最右方显示,且不加 0 ; -n rz :行号在自己栏位的最右方显示,且加 0 ;

more

一页一页翻动,类似于cat

代码语言:javascript复制
[Sukuna@Sukuna]# more /etc/man_db.config 
#
# Generated automatically from man.conf.in by the
# configure script.
#
# man.conf from man-1.6d
....(中间省略)....
--More--(28%)  <== 重点在这一行喔!你的光标也会在这里等待你的命令

在 more 这个程序的运行过程中,你有几个按键可以按的:

  • 空白键 (space):代表向下翻一页;
  • Enter :代表向下翻『一行』;
  • /字串 :代表在这个显示的内容当中,向下搜寻『字串』这个关键字;
  • :f :立刻显示出档名以及目前显示的行数;
  • q :代表立刻离开 more ,不再显示该文件内容。
  • b 或 [ctrl]-b :代表往回翻页,不过这动作只对文件有用,对管线无用。

apt 常用命令

  • 列出所有可更新的软件清单命令:sudo apt update
  • 升级软件包:sudo apt upgrade 列出可更新的软件包及版本信息:apt list –upgradeable 升级软件包,升级前先删除需要更新软件包:sudo apt full-upgrade
  • 安装指定的软件命令:sudo apt install <package_name> 安装多个软件包:sudo apt install <package_1> <package_2> <package_3>
  • 更新指定的软件命令:sudo apt update <package_name>
  • 显示软件包具体信息,例如:版本号,安装大小,依赖关系等等:sudo apt show <package_name>
  • 删除软件包命令:sudo apt remove <package_name>
  • 清理不再使用的依赖和库文件: sudo apt autoremove
  • 移除软件包及配置文件: sudo apt purge <package_name>
  • 查找软件包命令: sudo apt search
  • 列出所有已安装的包:apt list –installed
  • 列出所有已安装的包的版本信息:apt list –all-versions

添加用户

代码语言:javascript复制
useradd 选项 用户名

参数说明:

  • 选项:
    • -c comment 指定一段注释性描述。
    • -d 目录 指定用户主目录,如果此目录不存在,则同时使用-m选项,可以创建主目录。
    • -g 用户组 指定用户所属的用户组。
    • -G 用户组,用户组 指定用户所属的附加组。
    • -s Shell文件 指定用户的登录Shell。
    • -u 用户号 指定用户的用户号,如果同时有-o选项,则可以重复使用其他用户的标识号。
  • 用户名: 指定新账号的登录名。

删除帐号

如果一个用户的账号不再使用,可以从系统中删除。删除用户账号就是要将/etc/passwd等系统文件中的该用户记录删除,必要时还删除用户的主目录。

删除一个已有的用户账号使用userdel命令,其格式如下:

代码语言:javascript复制
userdel 选项 用户名

常用的选项是 -r,它的作用是把用户的主目录一起删除。

topic 4:Shell

我们关注的是 Bash,也就是 Bourne Again Shell,由于易用和免费,Bash 在日常工作中被广泛使用。同时,Bash 也是大多数Linux 系统默认的 Shell。

运行 Shell 脚本有两种方法:

1、作为可执行程序

将上面的代码保存为 test.sh,并 cd 到相应目录:

代码语言:javascript复制
chmod  x ./test.sh  #使脚本具有执行权限
./test.sh  #执行脚本

注意,一定要写成 ./test.sh,而不是 test.sh,运行其它二进制的程序也一样,直接写 test.sh,linux 系统会去 PATH 里寻找有没有叫 test.sh 的,而只有 /bin, /sbin, /usr/bin,/usr/sbin 等在 PATH 里,你的当前目录通常不在 PATH 里,所以写成 test.sh 是会找不到命令的,要用 ./test.sh 告诉系统说,就在当前目录找。Linux维护了一个环境变量PATH,Linux的程序寻找数据和可执行的程序会默认在PATH这个路径下面找.

2、作为解释器参数

这种运行方式是,直接运行解释器,其参数就是 shell 脚本的文件名,如:

代码语言:javascript复制
/bin/sh test.sh
/bin/php test.php

变量:

定义变量:Sukuna="qwq"

注意,变量名和等号之间不能有空格,这可能和你熟悉的所有编程语言都不一样。同时,变量名的命名须遵循如下规则:

  • 命名只能使用英文字母,数字和下划线,首个字符不能以数字开头。
  • 中间不能有空格,可以使用下划线 _
  • 不能使用标点符号。
  • 不能使用bash里的关键字(可用help命令查看保留关键字)。

使用变量,只需要在变量名前面加美元符号即可,为了方便机器识别变量,我们可以加上一个花括号.

代码语言:javascript复制
your_name="Sukuna"
echo $your_name
echo ${your_name}

删除变量:使用unset命令即可unset Sukuna.

只读变量:在声明下一行添加一句:readonly Sukuna

字符串

推荐使用双引号引用起来.双引号的字符串允许有转义符号,还允许添加变量.比如说:str="Hello, I know you are "$your_name"! n",转义符号的用法同C语言,添加变量的用法和使用变量一样.

当然,用双引号引起来也是可以的:str="Hello, I know you are "

获取字符串的长度:${#string}

提取子字符串:${string:n:m}提取第n 1到第m个字符.

数组

定义数组:array_name=(value0 value1 value2 value3),当然一个一个地声明也是允许的:array_name[0]=value0.

读取数组:valuen={array_name[n]}.也可以使用@符号来使用数组中的所有元素{array_name[@]}.

获取数组的长度:length=${#array_name[@]}.

学习完字符串我们可以了解echo命令:

Shell 的 echo 指令与 PHP 的 echo 指令类似,都是用于字符串的输出。命令格式:

代码语言:javascript复制
echo string

1.显示普通字符串:

代码语言:javascript复制
echo "It is a test"

这里的双引号完全可以省略,以下命令与上面实例效果一致:

代码语言:javascript复制
echo It is a test

2.显示转义字符

代码语言:javascript复制
echo ""It is a test""

结果将是:

代码语言:javascript复制
"It is a test"

同样,双引号也可以省略

3.显示变量

read 命令从标准输入中读取一行,并把输入行的每个字段的值指定给 shell 变量

代码语言:javascript复制
#!/bin/sh
read name 
echo "$name It is a test"

以上代码保存为 test.sh,name 接收标准输入的变量,结果将是:

代码语言:javascript复制
[root@www ~]# sh test.sh
OK                     #标准输入
OK It is a test        #输出

4.显示换行

代码语言:javascript复制
echo -e "OK! n" # -e 开启转义
echo "It is a test"

输出结果:

代码语言:javascript复制
OK!

It is a test

5.显示不换行

代码语言:javascript复制
#!/bin/sh
echo -e "OK! c" # -e 开启转义 c 不换行
echo "It is a test"

输出结果:

代码语言:javascript复制
OK! It is a test

6.显示结果定向至文件

代码语言:javascript复制
echo "It is a test" > myfile

7.原样输出字符串,不进行转义或取变量(用单引号)

代码语言:javascript复制
echo '$name"'

输出结果:

代码语言:javascript复制
$name"

8.显示命令执行结果

代码语言:javascript复制
echo `date`

注意: 这里使用的是反引号 `, 而不是单引号 '

结果将显示当前日期

代码语言:javascript复制
Thu Jul 24 10:08:46 CST 2014

当然我们还可以了解printf命令,其printf的命令除了调用不需要加()后面的参数不需要加逗号外和C语言几乎一样.

printf 命令的语法:

代码语言:javascript复制
printf  format-string  [arguments...]

注释

用#开头的行就是注释.

传递参数.

代码语言:javascript复制
$ chmod  x test.sh 
$ ./test.sh 1 2 3

可以给脚本类似于Shell的形式传递参数.就像上面的例子一样,程序执行./test.sh,然后传递三个参数,一个是1,一个是2,一个是3.在Shell脚本中分别代表1,2,3.其中脚本的文件名存储在0这个变量名中.

代码语言:javascript复制
echo "file name $0"
echo "first parameter: $1"

另外,还有几个特殊字符用来处理参数:

参数处理

说明

$#

传递到脚本的参数个数

$*

以一个单字符串显示所有向脚本传递的参数。 如"用「」括起来的情况、以*"用「"」括起来的情况、以"1 2 … n"的形式输出所有参数。

$$

脚本运行的当前进程ID号

$!

后台运行的最后一个进程的ID号

$@

与$*相同,但是使用时加引号,并在引号中返回每个参数。 如"$@"用「"」括起来的情况、以"​$1" "​$2" … "$n" 的形式输出所有参数。

与* 与 @ 区别:

  • 相同点:都是引用所有参数。
  • 不同点:只有在双引号中体现出来。假设在脚本运行时写了三个参数 1、2、3,,则 " * " 等价于 "1 2 3"(传递了一个参数),而 "@" 等价于 "1" "2" "3"(传递了三个参数)。

运算符:

算术运算符:

下表列出了常用的算术运算符,假定变量 a 为 10,变量 b 为 20:

运算符

说明

举例

加法

expr $a $b 结果为 30。

减法

expr $a - $b 结果为 -10。

*

乘法

expr $a * $b 结果为 200。

/

除法

expr $b / $a 结果为 2。

%

取余

expr $b % $a 结果为 0。

=

赋值

a=$b 把变量 b 的值赋给 a。

==

相等。用于比较两个数字,相同则返回 true。

[ a == b ] 返回 false。

!=

不相等。用于比较两个数字,不相同则返回 true。

[ a != b ] 返回 true。

注意:条件表达式要放在方括号之间,并且要有空格,例如: [a==​b] 是错误的,必须写成 [ a == b ]。

关系运算符:

下表列出了常用的关系运算符,假定变量 a 为 10,变量 b 为 20:

运算符

说明

举例

-eq

检测两个数是否相等,相等返回 true。

[ $a -eq $b ] 返回 false。

-ne

检测两个数是否不相等,不相等返回 true。

[ $a -ne $b ] 返回 true。

-gt

检测左边的数是否大于右边的,如果是,则返回 true。

[ $a -gt ​$b ] 返回 false。

-lt

检测左边的数是否小于右边的,如果是,则返回 true。

[ $a -lt ​$b ] 返回 true。

-ge

检测左边的数是否大于等于右边的,如果是,则返回 true。

[ $a -ge ​$b ] 返回 false。

-le

检测左边的数是否小于等于右边的,如果是,则返回 true。

[ $a -le ​$b ] 返回 true。

Boolean运算符:

下表列出了常用的布尔运算符,假定变量 a 为 10,变量 b 为 20:

运算符

说明

举例

!

非运算,表达式为 true 则返回 false,否则返回 true。

[ ! false ] 返回 true。

-o

或运算,有一个表达式为 true 则返回 true。

[ a -lt 20 -o b -gt 100 ] 返回 true。

-a

与运算,两个表达式都为 true 才返回 true。

[ a -lt 20 -a b -gt 100 ] 返回 false。

字符串运算符:

下表列出了常用的字符串运算符,假定变量 a 为 "abc",变量 b 为 "efg":

运算符

说明

举例

=

检测两个字符串是否相等,相等返回 true。

[ a = b ] 返回 false。

!=

检测两个字符串是否不相等,不相等返回 true。

[ a != b ] 返回 true。

-z

检测字符串长度是否为0,为0返回 true。

[ -z $a ] 返回 false。

-n

检测字符串长度是否不为 0,不为 0 返回 true。

[ -n "$a" ] 返回 true。

$

检测字符串是否为空,不为空返回 true。

[ $a ] 返回 true。

文件测试运算符.

属性检测描述如下:其中file参数是一个字符串,但是存储了文件的路径

操作符

说明

举例

-b file

检测文件是否是块设备文件,如果是,则返回 true。

[ -b $file ] 返回 false。

-c file

检测文件是否是字符设备文件,如果是,则返回 true。

[ -c $file ] 返回 false。

-d file

检测文件是否是目录,如果是,则返回 true。

[ -d $file ] 返回 false。

-f file

检测文件是否是普通文件(既不是目录,也不是设备文件),如果是,则返回 true。

[ -f $file ] 返回 true。

-g file

检测文件是否设置了 SGID 位,如果是,则返回 true。

[ -g $file ] 返回 false。

-k file

检测文件是否设置了粘着位(Sticky Bit),如果是,则返回 true。

[ -k $file ] 返回 false。

-p file

检测文件是否是有名管道,如果是,则返回 true。

[ -p $file ] 返回 false。

-u file

检测文件是否设置了 SUID 位,如果是,则返回 true。

[ -u $file ] 返回 false。

-r file

检测文件是否可读,如果是,则返回 true。

[ -r $file ] 返回 true。

-w file

检测文件是否可写,如果是,则返回 true。

[ -w $file ] 返回 true。

-x file

检测文件是否可执行,如果是,则返回 true。

[ -x $file ] 返回 true。

-s file

检测文件是否为空(文件大小是否大于0),不为空返回 true。

[ -s $file ] 返回 true。

-e file

检测文件(包括目录)是否存在,如果是,则返回 true。

[ -e $file ] 返回 true。

流程控制:

if else

fi

if 语句语法格式:

代码语言:javascript复制
if condition
then
    command1 
    command2
    ...
    commandN 
fi

写成一行(适用于终端命令提示符):

代码语言:javascript复制
if [ $(ps -ef | grep -c "ssh") -gt 1 ]; then echo "true"; fi

末尾的 fi 就是 if 倒过来拼写,后面还会遇到类似的。

if else

if else 语法格式:

代码语言:javascript复制
if condition
then
    command1 
    command2
    ...
    commandN
else
    command
fi

for循环:

for循环一般格式为:

代码语言:javascript复制
for var in item1 item2 ... itemN
do
    command1
    command2
    ...
    commandN
done

in后面可以是迭代器,换句话说可以是字符串、数组等.

while循环

while 循环用于不断执行一系列命令,也用于从输入文件中读取数据。其语法格式为:当condition成立的时候就做下面的command

代码语言:javascript复制
while condition
do
    command
done

until循环

until 循环执行一系列命令直至条件为 true 时停止。

until 循环与 while 循环在处理方式上刚好相反。

一般 while 循环优于 until 循环,但在某些时候—也只是极少数情况下,until 循环更加有用。

until 语法格式:

代码语言:javascript复制
until condition
do
    command
done

函数

shell中函数的定义格式如下:

代码语言:javascript复制
[ function ] funname [()]

{

    action;

    [return int;]

}

说明:

  • 1、可以带function fun() 定义,也可以直接fun() 定义,不带任何参数。
  • 2、参数返回,可以显示加:return 返回,如果不加,将以最后一条命令运行结果,作为返回值。

函数也可以接受参数,参数的个数和类型不需要在定义的时候声明.在函数体内调用函数即可.

代码语言:javascript复制
funWithParam(){
    echo "第一个参数为 $1 !"
    echo "第二个参数为 $2 !"
    echo "第十个参数为 $10 !"
    echo "第十个参数为 ${10} !"
    echo "第十一个参数为 ${11} !"
    echo "参数总数有 $# 个!"
    echo "作为一个字符串输出所有参数 $* !"
}

Shell的读取参数.

使用下面这条语句即可

代码语言:javascript复制
read parameter_name

文件重定向.

重定向命令列表如下:

命令

说明

command > file

将输出重定向到 file。

command < file

将输入重定向到 file。

topic 5: 数据整理.

1、可以使用grep -i xxx找到包含xxx的输出,只要包含即可.

这样一条命令 journalctl | grep -i intel,它会找到所有包含intel(不区分大小写)的系统日志。

比如说下面这个程序:注意,这里我们使用管道将一个远程服务器上的文件传递给本机的 grep程序!

代码语言:javascript复制
ssh myserver 'journalctl | grep sshd | grep "Disconnected from"' | less

多出来的引号是什么作用呢?这么说吧,我们的日志是一个非常大的文件,把这么大的文件流直接传输到我们本地的电脑上再进行过滤是对流量的一种浪费。因此我们采取另外一种方式,我们先在远端机器上过滤文本内容,然后再将结果传输到本机。 less 为我们创建来一个文件分页器,使我们可以通过翻页的方式浏览较长的文本。为了进一步节省流量,我们甚至可以将当前过滤出的日志保存到文件中,这样后续就不需要再次通过网络访问该文件了:当然,当你需要多个grep过滤器的时候,这个时候需要用引号引起来,然后使用>符号把输出重定向到ssh.log文件中.

代码语言:javascript复制
$ ssh myserver 'journalctl | grep sshd | grep "Disconnected from"' > ssh.log
$ less ssh.log

2、正则表达式:

让我们从这一句正则表达式开始学习: /.*Disconnected from /。正则表达式通常以(尽管并不总是) /开始和结束。如果遇到了/,很有可能前面的是一个正则表达式.其中正则表达式会用下面这些符号来进行代替.

  • . 除换行符之外的”任意单个字符”
  • * 匹配前面字符零次或多次,比如说a*代表匹配a这个字符0~n次,这个可以代指0~n个和前面字符一样的字符.
  • 匹配前面字符一次或多次比如说a 代表匹配a这个字符1~n次,这个可以代指1~n个和前面字符一样的字符.
  • [abc] 匹配 a, bc 中的任意一个
  • (RX1|RX2)? 任何能够匹配RX1RX2的结果
  • ^ 行首
  • $ 行尾

回过头我们再看/.*Disconnected from /,我们会发现这个正则表达式可以匹配任何以若干任意字符开头,并接着包含”Disconnected from “的字符串。这也正式我们所希望的。

当然,正则表达式会如何匹配?* 在默认情况下是贪婪模式,也就是说,它们会尽可能多的匹配文本。对于某些正则表达式的实现来说,您可以给 * 增加一个? 后缀使其变成非贪婪模式。

这里我们需要做的是匹配一整行

代码语言:javascript复制
 | sed -E 's/.*Disconnected from (invalid |authenticating )?user .* [^ ]  port [0-9] ( [preauth])?$//'

让我们借助正则表达式在线调试工具regex debugger 来理解这段表达式。OK,开始的部分和以前是一样的,随后,我们匹配两种类型的“user”(在日志中基于两种前缀区分)。再然后我们匹配属于用户名的所有字符。接着,再匹配任意一个单词([^ ] 会匹配任意非空且不包含空格的序列)。紧接着后面匹配单“port”和它后面的一串数字,以及可能存在的后缀[preauth],最后再匹配行尾。

当然我们可以使用“捕获组(capture groups)”来完成记录之。被圆括号内的正则表达式匹配到的文本,都会被存入一系列以编号区分的捕获组中。捕获组的内容可以在替换字符串时使用(有些正则表达式的引擎甚至支持替换表达式本身),例如123等等,因此可以使用如下命令:命令使用的2代表使用第2个参数

代码语言:javascript复制
 | sed -E 's/.*Disconnected from (invalid |authenticating )?user (.*) [^ ]  port [0-9] ( [preauth])?$/2/'

3、sed

sed 是一个基于文本编辑器ed构建的”流编辑器” 。在 sed 中,您基本上是利用一些简短的命令来修改文件,而不是直接操作文件的内容(尽管您也可以选择这样做)。相关的命令行非常多,但是最常用的是 s,即替换命令.

s 命令的语法如下:s/REGEX/SUBSTITUTION/, 其中 REGEX 部分是我们需要使用的正则表达式,而 SUBSTITUTION 是用于替换匹配结果的文本.就是把REGEX对应的内容匹配,把整个REGEX的内容替换成SUBSTITUTION对应的内容.

4、更多的数据整理技巧.

现在我们可以通过:(|是一个管道,|左边的指令执行完之后会把输出传递给|右边的指令,右边的指令会对左边指令传递的数据进行处理得到一个输出),比如说ssh myserver journalctl这个指令会把输出交给grep sshd指令处理,grep sshd指令执行完之后会有一个输出.

代码语言:javascript复制
ssh myserver journalctl
 | grep sshd
 | grep "Disconnected from"
 | sed -E 's/.*Disconnected from (invalid |authenticating )?user (.*) [^ ]  port [0-9] ( [preauth])?$/2/'

sort 会对其输入数据进行排序。uniq -c 会把连续出现的行折叠为一行并使用出现次数作为前缀。sort -n 会按照数字顺序对输入进行排序(默认情况下是按照字典序排序-k1,1 则表示“仅基于以空格分割的第一列进行排序”.

5、awk

它是一个比较贴近于脚本的语言,主要由这几个部分组成

代码语言:javascript复制
BEGIN { 一开始做什么 }
对于每一行的数据,我们又做什么.
END { 最后做什么 }

比如说:

代码语言:javascript复制
BEGIN { rows = 0 }
$1 == 1 && $2 ~ /^c[^ ]*e$/ { rows  = $1 }
END { print rows }

对于每一行的数据的处理我们可以分成两个部分:

条件 {条件成立了该怎么做}

其中每一行可以被awk解释称用空格分成的几个部分,$1表示从左到右第一个部分.

代码语言:javascript复制
2 this is a test

这个时候1=2,2=this

6、可以使用gnuplop来画图表.

topic 6: Command Line环境.

1、程序的退出:

程序的退出有三种方法,分别是传递三种信号.

第一个是SIGINT,就是Ctrl-C.

第二个就是SIGQUIT,就是Ctrl-.

第三个就是使用kill命令来传递SIGTERM,这个kill命令语法就是kill -TERM <PID>,其中PID就是进程号.当然还可以使用kill -9 <PID>强制杀死.当然kill指令中-后面的信号其实是可以指定的.制定-6也可以-STOP也可以.当然不指定信号类型(kill <PID>)直接使用kill就是退出,

2、程序的暂停.

使用SIGSTOP信号,就是Ctrl-Z

当然,还可以使用fg %numbg %num来恢复暂停的工作,它们分别表示在前台继续或在后台继续。

jobs 命令会列出当前终端会话中尚未完成的全部任务。您可以使用 pid 引用这些任务(也可以用 pgrep 找出 pid)。更加符合直觉的操作是您可以使用百分号 任务编号(jobs 会打印任务编号)来选取该任务。如果要选择最近的一个任务,可以使用 $! 这一特殊参数。

代码语言:javascript复制
$ jobs
[1]    suspended  sleep 1000
[2]  - running    nohup sleep 2000

其中1和2就任务编号.

代码语言:javascript复制
$ bg %1
[1]  - 18653 continued  sleep 1000

这一条语句就是让任务序号1继续执行.不过就是在后台悄悄地运行.

让已经在运行的进程转到后台运行,您可以键入Ctrl-Z ,然后紧接着再输入bg。注意,后台的进程仍然是您的终端进程的子进程,一旦您关闭终端(会发送另外一个信号SIGHUP),这些后台的进程也会终止。为了防止这种情况发生,您可以使用 nohup (一个用来忽略 SIGHUP 的封装) 来运行程序。

代码语言:javascript复制
$ sleep 1000
^Z
[1]    18653 suspended  sleep 1000
先进入暂停状态
$ nohup sleep 2000 &
[2] 18745
appending output to nohup.out
新造一个新的任务
$ jobs
[1]    suspended  sleep 1000
[2]  - running    nohup sleep 2000
显示
$ bg %1
[1]  - 18653 continued  sleep 1000
让一个程序停止暂停放入后台运行
$ jobs
[1]  - running    sleep 1000
[2]    running    nohup sleep 2000

$ kill -STOP %1
[1]    18653 suspended (signal)  sleep 1000
使用kill指令强行传递STOP信号给第一个任务编号对应的任务.
$ jobs
[1]    suspended (signal)  sleep 1000
[2]  - running    nohup sleep 2000

$ kill -SIGHUP %1
[1]    18653 hangup     sleep 1000
第一个任务被挂起了
$ jobs
[2]    running    nohup sleep 2000
第二个任务屏蔽挂起
$ kill -SIGHUP %2

$ jobs
[2]    running    nohup sleep 2000

$ kill %2
[2]    18745 terminated  nohup sleep 2000
强制杀死任务编号为2的任务.
$ jobs

3、终端多路复用:

终端多路复用器提供了一个帮助我们用一个cmd窗口执行两个任务的思路.

我们一般使用tmux来进行终端的多路复用

tmux 的快捷键需要我们掌握,它们都是类似 <C-b> x 这样的组合,即需要先按下Ctrl b,松开后再按下 xtmux 中对象的继承结构如下:

  • 会话-每个会话都是一个独立的工作区,其中包含一个或多个窗口,每一个会话包括多个窗口.
    • tmux 开始一个新的会话
    • tmux new -s NAME 以指定名称开始一个新的会话
    • tmux ls 列出当前所有会话
    • tmux 中输入 <C-b> d ,将当前会话分离
    • tmux a 重新连接最后一个会话。您也可以通过 -t 来指定具体的会话
  • 窗口-相当于编辑器或是浏览器中的标签页,从视觉上将一个会话分割为多个部分
    • <C-b> c 创建一个新的窗口,使用 <C-d>关闭
    • <C-b> N 跳转到第 N 个窗口,注意每个窗口都是有编号的
    • <C-b> p 切换到前一个窗口
    • <C-b> n 切换到下一个窗口
    • <C-b> , 重命名当前窗口
    • <C-b> w 列出当前所有窗口
  • 面板-像 vim 中的分屏一样,面板使我们可以在一个屏幕里显示多个 shell
    • <C-b> " 水平分割
    • <C-b> % 垂直分割
    • <C-b> <方向> 切换到指定方向的面板,<方向> 指的是键盘上的方向键
    • <C-b> z 切换当前面板的缩放
    • <C-b> [ 开始往回卷动屏幕。您可以按下空格键来开始选择,回车键复制选中的部分
    • <C-b> <空格> 在不同的面板排布间切换

会话包含若干个窗口->窗口包含若干个面板.这样我们可以灵活地处理Command Line环境了.

4、别名:

输入一长串包含许多选项的命令会非常麻烦。因此,大多数 shell 都支持设置别名。shell 的别名相当于一个长命令的缩写,shell 会自动将其替换成原本的命令。例如,bash 中的别名语法如下:

代码语言:javascript复制
alias alias_name="command_to_alias arg1 arg2"

注意, =两边是没有空格的,因为 alias 是一个 shell 命令,它只接受一个参数。

5、配置文件:

配置文件是一种特殊的文件,一般来说是一种文本文件,很多程序的配置都是通过纯文本格式的被称作点文件的配置文件来完成的(之所以称为点文件,是因为它们的文件名以 . 开头,例如 ~/.vimrc。也正因为此,它们默认是隐藏文件,ls并不会显示它们)。

对于 bash来说,在大多数系统下,您可以通过编辑 .bashrc.bash_profile 来进行配置。在文件中您可以添加需要在启动时执行的命令,例如上文我们讲到过的别名,或者是您的环境变量。

还有一些其他的工具也可以通过点文件进行配置:

  • bash~/.bashrc, ~/.bash_profile
  • git~/.gitconfig
  • vim~/.vimrc~/.vim 目录
  • ssh~/.ssh/config
  • tmux~/.tmux.conf

我们应该如何管理这些配置文件呢,它们应该在它们的文件夹下,并使用版本控制系统进行管理,然后通过脚本将其 符号链接 到需要的地方。这么做有如下好处:

6、SSH连接:

通过如下命令,您可以使用 ssh 连接到其他服务器:

代码语言:javascript复制
ssh foo@bar.mit.edu

这里我们尝试以用户名 foo 登录服务器 bar.mit.edu。服务器可以通过 URL 指定(例如bar.mit.edu),也可以使用 IP 指定(例如foobar@192.168.1.42)。

ssh 的一个经常被忽视的特性是它可以直接远程执行命令。 ssh foobar@server ls 可以直接在用foobar的命令下执行 ls 命令。

当然我们也可以使用scp指令把一些文件复制到服务器中,也可以把某些文件从服务器扒下来.

从本地复制到远程

命令格式:

代码语言:javascript复制
scp local_file remote_username@remote_ip:remote_folder 
或者 
scp local_file remote_username@remote_ip:remote_file 
或者 
scp local_file remote_ip:remote_folder 
或者 
scp local_file remote_ip:remote_file 
  • 第1,2个指定了用户名,命令执行后需要再输入密码,第1个仅指定了远程的目录,文件名字不变,第2个指定了文件名; 这个文件名是要具体到某个目录中的,不能只是一个文件名,是文件目录和文件名的组合.
  • 第3,4个没有指定用户名,命令执行后需要输入用户名和密码,第3个仅指定了远程的目录,文件名字不变,第4个指定了文件名;

应用实例:

代码语言:javascript复制
scp /home/space/music/1.mp3 root@www.runoob.com:/home/root/others/music 
scp /home/space/music/1.mp3 root@www.runoob.com:/home/root/others/music/001.mp3 
scp /home/space/music/1.mp3 www.runoob.com:/home/root/others/music 
scp /home/space/music/1.mp3 www.runoob.com:/home/root/others/music/001.mp3 

复制目录命令格式:

代码语言:javascript复制
scp -r local_folder remote_username@remote_ip:remote_folder 
或者 
scp -r local_folder remote_ip:remote_folder 
  • 第1个指定了用户名,命令执行后需要再输入密码;
  • 第2个没有指定用户名,命令执行后需要输入用户名和密码;

应用实例:

代码语言:javascript复制
scp -r /home/space/music/ root@www.runoob.com:/home/root/others/ 
scp -r /home/space/music/ www.runoob.com:/home/root/others/ 

上面命令将本地 music 目录复制到远程 others 目录下。

2、从远程复制到本地

从远程复制到本地,只要将从本地复制到远程的命令的后2个参数调换顺序即可,如下实例

应用实例:

代码语言:javascript复制
scp root@www.runoob.com:/home/root/others/music /home/space/music/1.mp3 
scp -r www.runoob.com:/home/root/others/ /home/space/music/

topic 7:构建系统:

对于大多数系统(或者是软件、亦或是程序)来说,不论其是否包含代码,都会包含一个“构建过程”。有时,您需要执行一系列操作。通常,这一过程包含了很多步骤,很多分支。执行一些命令来生成图表,然后执行另外的一些命令生成结果,然后再执行其他的命令来生成最终的论文。有很多事情需要我们完成,您并不是第一个因此感到苦恼的人,幸运的是,有很多工具可以帮助我们完成这些操作。这些工具通常被称为 “构建系统”.

一般我们使用make作为我们的构建系统,当我们执行 make 时,它会去参考当前目录下名为 Makefile 的文件。所有构建目标、相关依赖和规则都需要在该文件中定义,它看上去是这样的:

代码语言:javascript复制
paper.pdf: paper.tex plot-data.png
	pdflatex paper.tex

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

这个文件中的指令,即如何使用右侧文件构建左侧文件的规则。或者,换句话说,冒号左侧的是构建目标,冒号右侧的是构建它所需的依赖。缩进的部分是从依赖构建目标时需要用到的一段程序。在 make 中,第一条指令还指明了构建的目的,如果您使用不带参数的 make,这便是我们最终的构建结果。或者,您可以使用这样的命令来构建其他目标:make plot-data.png

举个例子,在上面的MAKEFILE中,生成paper.pdf需要paper.tex和plot-data.png,生成这个文件需要执行pdflatex paper.tex,所以说MAKEFILE提供了一些信息,我能生成什么文件,生成这个文件需要什么,怎么样生成这个文件.

就您的项目来说,它的依赖可能本身也是其他的项目。您也许会依赖某些程序(例如 python)、系统包 (例如 openssl)或相关编程语言的库(例如 matplotlib)。 现在,大多数的依赖可以通过某些软件仓库来获取,这些仓库会在一个地方托管大量的依赖,我们则可以通过一套非常简单的机制来安装依赖。例如 Ubuntu 系统下面有Ubuntu软件包仓库,您可以通过apt 这个工具来访问, RubyGems 则包含了 Ruby 的相关库,PyPi 包含了 Python 库.每个软件都是会更新版本的,假如我的库要发布一个新版本,在这个版本里面我重命名了某个函数。如果有人在我的库升级版本后,仍希望基于它构建新的软件,那么很可能构建会失败,因为它希望调用的函数已经不复存在了。所以说软件的版本会构造一个版本号.

这种版本号具有不同的语义,它的格式是这样的:主版本号.次版本号.补丁号。相关规则有:

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

这可以保证那么只要最新版本的主版本号只要没变就是安全的 ,次版本号不低于之前我们使用的版本即可。可以防止使用库的用户因为代码版本更新了而不能正常使用.

topic 8 代码调试和测试.

代码调试

1、打印调试法:在合适的位置上使用打印语句进行调试.

2、使用日志:

日志较普通的打印语句有如下的一些优势:

  • 您可以将日志写入文件、socket 或者甚至是发送到远端服务器而不仅仅是标准输出;
  • 日志可以支持严重等级(例如 INFO, DEBUG, WARN, ERROR等),这使您可以根据需要过滤日志;
  • 对于新发现的问题,很可能您的日志中已经包含了可以帮助您定位问题的足够的信息。

例如,执行 echo -e "e[38;2;255;0;0mThis is rede[0m" 会打印红色的字符串:This is red 。其中前面的数据是38、2和RGB值,这几个数字用分号间隔开来.

3、第三方日志系统:

我们在使用大型软件系统,往往会要用到一些依赖,有些依赖会作为程序运行,如 Web 服务器、数据库、消息代理或者一些库或者系统包都是此类常见的第三方依赖。这些依赖也会产生日志.对于 UNIX 系统来说,程序的日志通常存放在 /var/log。例如, NGINX web 服务器就将其日志存放于/var/log/nginx

目前,各种操作系统开始使用system log来保存日志.大多数(但不是全部的)Linux 系统都会使用 systemd,这是一个系统守护进程,它会控制您系统中的很多东西,例如哪些服务应该启动并运行。systemd 会将日志以某种特殊格式存放于/var/log/journal,您可以使用 journalctl 命令显示这些消息。也就是说可以从journalctl这个指令去读systemd这个守护进程的日志.对于MacOS,对应地使用log show.

对于大多数的 UNIX 系统,您也可以使用dmesg 命令来读取内核的日志。

我们还可以使用logger指令把数据放到系统日志中.

4、调试器:

调试器是一种可以允许我们和正在执行的程序进行交互的程序,它可以做到:

  • 当到达某一行时将程序暂停;
  • 一次一条指令地逐步执行程序;
  • 程序崩溃后查看变量的值;
  • 满足特定条件时暂停程序;
  • 其他高级功能。

下面介绍一下gdb调试器:

可以不带任何参数或选项执行gdb命令,但是最常用的启动gdb的方式是带一个或者两个参数,指定一个可执行文件来作为参数:注意一定是可执行文件

代码语言:javascript复制
gdb program(gdb 可执行文件名称)

break [file:]function or b function(or symbol):

设置一个断点在函数中(在文件中)

bt Backtrace:

显示堆栈

print expr

显示表达式的值

c

继续执行你的程序(程序停住后,例如:在断点处停止)

next or n

执行程序的下一行代码(程序停止以后);跨国任何当前行的函数调用。

ni

下一条汇编指令.

edit [file:]function

查看当前程序停在哪。

list [file:]function

显示程序当前停住的代码行附近的代码

step 单步调试

执行程序的下一行(程序停住后),进入当前行的函数调用的内部

help [name]

显示gdb命令的相关信息。

quit

退出gdb

5、二进制测试:

使用strace调用可以获得二进制程序执行了什么系统调用.(注意filename为可执行文件,一定要加上.)例如strace .a.out

代码语言:javascript复制
strace file_name

strace command

执行名称为command的命令或程序并跟踪系统调用

strace -p procid

跟踪ID为的procid的进程系统调用情况

strace -c -p procid

统计ID为的procid的进程系统调用次数与用时,按CTRL C结束统计,执行结果如下:

6、网络介绍

有些情况下,我们需要查看网络数据包才能定位问题。像 tcpdump 和 Wireshark 这样的网络数据包分析工具可以帮助您获取网络数据包的内容并基于不同的条件进行过滤。

比如说tcpdump:

代码语言:javascript复制
tcpdump [ -DenNqvX ] [ -c count ] [ -F file ] [ -i interface ] [ -r file ]
        [ -s snaplen ] [ -w file ] [ expression ]

抓包选项:

-c:指定要抓取的包数量。

-i interface:指定tcpdump需要监听的接口。默认会抓取第一个网络接口

-n:对地址以数字方式显式,否则显式为主机名,也就是说-n选项不做主机名解析。

-nn:除了-n的作用外,还把端口显示为数值,否则显示端口服务名。

-P:指定要抓取的包是流入还是流出的包。可以给定的值为"in"、"out"和"inout",默认为"inout"。

-s len:设置tcpdump的数据包抓取长度为len,如果不设置默认将会是65535字节。

输出选项:

-e:输出的每行中都将包括数据链路层头部信息,例如源MAC和目标MAC。

-q:快速打印输出。即打印很少的协议相关信息,从而输出行都比较简短。

-X:输出包的头部数据,会以16进制和ASCII两种方式同时输出。

-XX:输出包的头部数据,会以16进制和ASCII两种方式同时输出,更详细。

-v:当分析和打印的时候,产生详细的输出。

-vv:产生比-v更详细的输出。 -vvv:产生比-vv更详细的输出。

还可以添加expression保证获取你需要的输出.

对于表达式语法,参考 pcap-filter 【pcap-filter – packet filter syntax】

  • 类型 type

host, net, port, portrange

例如:host 192.168.201.128 , net 128.3, port 20, portrange 6000-6008'

  • 目标 dir

src, dst, src or dst, src and dst

  • 协议 proto

tcp, udp , icmp,若未给定协议类型,则匹配所有可能的类型

不同的表达式还可以使用and和or连接在一起:定义了表达式我们可以只输出符合这个表达式的信息.

7、静态分析,可以使用静态分析工具帮助你分析代码,对于代码的风格和质量,也有很多工具帮助你写出来的代码和别人差不多.

性能分析

我们不能单凭打两个计时点的方式来计算时间,对于工具来说,需要区分真实时间、用户时间和系统时间。通常来说,用户时间 系统时间代表了您的进程所消耗的实际 CPU.时间分成三部分:等待 执行用户态代码 执行系统态代码:

  • 真实时间 – 从程序开始到结束流失掉的真实时间,包括其他进程的执行时间以及阻塞消耗的时间(例如等待 I/O或网络);
  • User – CPU 执行用户代码所花费的时间;
  • Sys – CPU 执行系统内核代码所花费的时间。

当然还可以了解CPU、内存和I/O的使用频率来评估性能.

topic 9:杂项.

1、修改键位映射:

一个很常见的配置是修改键位映射。通常这个功能由在计算机上运行的软件实现。当某一个按键被按下,软件截获键盘发出的按键事件(keypress event)并使用另外一个事件取代。比如:

  • 将 Caps Lock 映射为 Ctrl 或者 Escape:Caps Lock 使用了键盘上一个非常方便的位置而它的功能却很少被用到,所以我们(讲师)非常推荐这个修改;
  • 将 PrtSc 映射为播放/暂停:大部分操作系统支持播放/暂停键;
  • 交换 Ctrl 和 Meta 键(Windows 的徽标键或者 Mac 的 Command 键)。

你也可以将键位映射为任意常用的指令。软件监听到特定的按键组合后会运行设定的脚本。

  • 打开一个新的终端或者浏览器窗口;
  • 输出特定的字符串;
  • 使计算机或者显示器进入睡眠模式。

甚至更复杂的修改也可以通过软件实现:

  • 映射按键顺序,比如:按 Shift 键五下切换大小写锁定;
  • 区别映射单点和长按,比如:单点 Caps Lock 映射为 Escape,而长按 Caps Lock 映射为 Ctrl;

对于Linux可以使用Autokey,MacOS使用karabiner-elements, skhd 或者 BetterTouchTool.

2、守护进程.

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

Linux 中的 systemd(the system daemon)是最常用的配置和运行守护进程的方法。运行 systemctl status 命令可以看到正在运行的所有守护进程。这里面有很多可能你没有见过,但是掌管了系统的核心部分的进程:管理网络、DNS解析、显示系统的图形界面等等。用户使用 systemctl 命令和 systemd 交互来enable(启用)、disable(禁用)、start(启动)、stop(停止)、restart(重启)、或者status(检查)配置好的守护进程及系统服务。

3、FUSE.

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

FUSE 可以用于实现如:一个将所有文件系统操作都使用 SSH 转发到远程主机,由远程主机处理后返回结果到本地计算机的虚拟文件系统。这个文件系统里的文件虽然存储在远程主机,对于本地计算机上的软件而言和存储在本地别无二致。sshfs就是一个实现了这种功能的 FUSE 文件系统

4、API(应用程序接口)

大多数线上服务提供的 API(应用程序接口)让你可以通过编程方式来访问这些服务的数据。我们的程序可以通过某种方式(建立连接?)与线上服务提供的接口进行连接,然后从接口种获取数据.这些数据往往是JSON格式,我们可以使用一些工具来解析json格式.

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

一般来说很多线上网站实现功能都是调用API,我们可以尝试调用QQ的API来获取腾讯提供给我们的部分服务.

5、常见的命令行标志参数以及其格式.

  • 有的时候你可能需要向工具传入一个看上去像标志参数的普通参数,比如:
    • 使用 rm 删除一个叫 -r 的文件;
    • 在通过一个程序运行另一个程序的时候(ssh machine foo),向内层的程序(foo)传递一个标志参数。

    这时候你可以使用特殊参数 -- 让某个程序 停止处理 -- 后面出现的标志参数以及选项(以 - 开头的内容):

    • rm -- -r 会让 rm-r 当作文件名;
    • ssh machine --for-ssh -- foo --for-foo-- 会让 ssh 知道 --for-foo 不是 ssh 的标志参数。
  • 大多数工具中,使用 - 代替输入或者输出文件名意味着工具将从标准输入(standard input)获取所需内容,或者向标准输出(standard output)输出结果。
  • 基本所有的工具支持使用 --verbose 或者 -v 标志参数来输出详细的运行信息。
  • 会造成破坏性结果的工具一般默认进行非递归的操作,但是支持使用“递归”(recursive)标志函数(通常是 -r)。
  • 基本上所有工具都支持–help和–version(或者-V)

6、访问国外网站.

使用了 V** 的你对于互联网而言,最好的情况下也就是换了一个网络供应商(ISP)。所有你发出的流量看上去来源于 V** 供应商的网络而不是你的“真实”地址,而你实际接入的网络只能看到加密的流量。

虽然这听上去非常诱人,但是你应该知道使用 V** 只是把原本对网络供应商的信任放在了 V** 供应商那里——网络供应商 能看到的,V** 供应商 也都能看到。所以说选择V**之前要慎重,如果V**的提供者没有使用加密或者加密比较弱,你的敏感信息就在互联网世界上裸奔了.

所以本质上,V**就是帮你换了一个外衣,你的身份变成了V**提供者的身份,你接入的ISP也变成了V**提供者的ISP.

7、HammerSpoon(mac OS桌面自动化)

Hammerspoon 是面向 macOS 的一个桌面自动化框架。它允许用户编写和操作系统功能挂钩的 Lua 脚本,从而与键盘、鼠标、窗口、文件系统等交互。可以利用这个软件配合mac OS完成很棒的功能.

  • Getting Started with Hammerspoon:Hammerspoon 官方教程
  • Sample configurations:Hammerspoon 官方示例配置

8、GitHub

GitHub 是最受欢迎的开源软件开发平台之一。我们课程中提到的很多工具,从 vim 到 Hammerspoon,都托管在 Github 上。向你每天使用的开源工具作出贡献其实很简单,下面是两种贡献者们经常使用的方法:

  • 创建一个议题(issue)。 议题可以用来反映软件运行的问题或者请求新的功能。创建议题并不需要创建者阅读或者编写代码,所以它是一个轻量化的贡献方式。高质量的问题报告对于开发者十分重要。在现有的议题发表评论也可以对项目的开发作出贡献。
  • 使用拉取请求(pull request)提交代码更改。由于涉及到阅读和编写代码,提交拉取请求总的来说比创建议题更加深入。拉取请求是请求别人把你自己的代码拉取(且合并)到他们的仓库里。很多开源项目仅允许认证的管理者管理项目代码,所以一般需要复刻(fork)这些项目的上游仓库(upstream repository),在你的 Github 账号下创建一个内容完全相同但是由你控制的复刻仓库。这样你就可以在这个复刻仓库自由创建新的分支并推送修复问题或者实现新功能的代码。完成修改以后再回到开源项目的 Github 页面创建一个拉取请求。然后认证的管理者审批你的拉取请求,审批通过了你就可以把你的修改上传到开源仓库了.

附录1 :从底层角度看git

快照

Git 将顶级目录中的文件和文件夹作为集合,并通过一系列快照来管理其历史记录。在Git的术语里,文件被称作Blob对象(数据对象),也就是一组数据。目录则被称之为“树”,存储了这个目录下所有Blob和目录的信息。快照则是被追踪的最顶层的树,也就是最顶层的目录。换句话说,这个目录的所有文件的在某一个时间的状态就被称为一个快照. 例如,一个树看起来可能是这样的:

代码语言:javascript复制
<root> (tree)
|
 - foo (tree)
|  |
|    bar.txt (blob, contents = "hello world")
|
 - baz.txt (blob, contents = "git is wonderful")

Git靠维护一系列的快照来追溯历史的版本,具体来说,快照是用有向无环图来进行快照的追踪的,这代表 Git 中的每个快照都有一系列的“父辈”,理解就是父快照中的文件经过了修改再进行提交就是子快照.在Git中,这些快照又可以称为被称为“commit”(提交),每一次提交git并不会保存所有文件的拷贝,git每次存储会存储每次提交改动的内容.所以说每个快照都要保存就是父辈的相关信息,快照的时间和提交信息等,还有当前目录下所有文件的状态.

代码语言:javascript复制
o <-- o <-- o <-- o
            ^  
             
              --- o <-- o

Git 中的提交是不可改变的。但这并不代表错误不能被修改,只不过这种“修改”实际上是创建了一个全新的提交记录。而引用则被更新为指向这些新的提交。

数据模型及其伪代码表示

以伪代码的形式来学习 Git 的数据模型,可能更加清晰:

代码语言:javascript复制
// 文件就是一组数据
type blob = array<byte>

// 一个包含文件和目录的目录
type tree = map<string, tree | blob>

// 每个提交都包含一个父辈,元数据和一个树,这个树一般是代表git仓库目录的顶层树
type commit = struct {
    parent: commit
    author: string
    message: string
    snapshot: tree
}

所以在这里面可以看到,git中一个commit其实就是一个快照,这个commit保存了当前所有目录所有文件的信息,实际的git中维护的不是文件本身而是改动.

所以说git的数据模型你可以简单理解,就是由很多个快照串联在一起的结构,快照是由普通文件和目录文件组成的.快照之间靠类似于链表之间的关系来维护的.

内存模型

Git 中的对象可以是 blob、树或提交:我们了解了git中抽象数据的结构,现在我们了解这些抽象数据是怎么存储在存储系统中的,总的来说git是靠一个索引来维护这些对象的.

代码语言:javascript复制
type object = blob | tree | commit

Git 在储存数据时,所有的对象都会基于它们的SHA1哈希进行寻址。Blobs、树和提交都一样,它们都是对象。当它们引用其他对象时,它们并没有真正的在硬盘上保存这些对象,而是仅仅保存了它们的哈希值作为引用。

代码语言:javascript复制
100644 blob 4448adbf7ecd394f42ae135bbeed9676e894af85    baz.txt
040000 tree c68d233a33c5c06e0340e4c224f0afca87c8ce87    foo

当然我们还可以自定义指针来引用哈希值,Git 可以使用诸如 “master” 这样人类可读的名称来表示历史记录中某个特定的提交,而不需要在使用一长串十六进制字符了。

git的区域

Git 中还包括一个和数据模型完全不相关的概念,但它确是创建提交的接口的一部分。

就上面介绍的快照系统来说,您也许会期望它的实现里包括一个 “创建快照” 的命令,该命令能够基于当前工作目录的当前状态创建一个全新的快照。有些版本控制系统确实是这样工作的,但 Git 不是。我们希望简洁的快照,而且每次从当前状态创建快照可能效果并不理想。例如,考虑如下场景,您开发了两个独立的特性,然后您希望创建两个独立的提交,其中第一个提交仅包含第一个特性,而第二个提交仅包含第二个特性。或者,假设您在调试代码时添加了很多打印语句,然后您仅仅希望提交和修复 bug 相关的代码而丢弃所有的打印语句。

Git 处理这些场景的方法是使用一种叫做 “暂存区(staging area)”的机制,它允许您指定下次快照中要包括那些改动。

首先我们要把git文件先交付到暂存区,然后在commit到git的仓库中.

总结

  • git是维护快照的软件,也就是维护目录下所有文件在某一个时间的状态.
  • 在git中每一次commit都会保存一个快照.
  • git维护快照并不会把所有文件都拷贝一遍,git维护主要是维护文件的改变信息.
  • git的所有提交、目录和普通文件都有一个SHA1索引表示,当然我们还可以给SHA1索引起别名.
  • git可以有很多个分支,每个分支都有一个名字,这个名字相对应的其实是一个提交对象.
  • git的空间由用户的世界空间和git维护的暂存区和提交区的空间组成.

0 人点赞