1.文件管理命令
1.1 cd 命令
在 Linux 终端中如果想要进行工作路径的切换,需要使用 cd 命令。在进行目录的切换的时候, 我们可以使用相对路径也可以使用绝对路径。
-
进入指定目录 $ cd 目录名
-
目录名: 使用相对路径 / 绝对路径都可以,该路径必须是一个有效路径
-
特殊的目录: .. : 表示当前目录的上一级目录,使用 cd .. 或者 cd ../ 都可以 . : 表示当前目录,使用 . 或者./ 都可以,cd . 不会切换目录
-
进入家目录
每个用户 (普通用户和 root) 都有一个属于自己的目录,比如:
-
robin 是普通用户,家目录: /home/robin
-
luffy 是用户,家目录: /home/luffy
-
root 是管理员用户,家目录: /root
-
按照上边的格式来表示家目录书写起来比较麻烦,有一种相对简单的写法就是使用 ~ 表示。如果是使用 cd 命令切换到家目录,后边可以什么路径都不加,这样也可以进入到当前用户的家目录。综上所述也就是通过 cd 进入到当前用户的家目录一共有三种不同的方式。
$ cd # 方式1
$ cd ~ # 方式2
$ cd /home/用户名 # 方式3
在临近的两个目录之间切换
如果我们要频繁的在两个路径之间切换,也有相关的快捷操作,尤其是对于比较长的路径
通过cd进入到目录1: /usr/include/c++/7.5.0/ext/pb_ds/detail/list_update_map_/
通过cd进入到目录2: /home/luffy/get/onepiece/itis/a/goldfish
频繁在两个目录之间切换
$ cd -
1.2 ls 命令
ls 就是 list, 打印指定的文件信息,如果是目录,显示对应目录中有哪些子文件,语法格式如下:
$ ls [args] # 查看当前目录
$ ls [args] 目录名 # 查看指定目录
$ ls [args] 文件名 # 查看某个文件的信息
1.2.1显示所有文件
给 ls 添加 -a 参数(就是 all 的意思)就可以显示指定目录中是所有文件了,因为默认情况下具有隐藏属性的文件是不会显示出来的。那么在 Linux 中什么样是文件才能被隐藏呢?起始很简单只需要在文件名前边加一个点 (.) 文件就具有隐藏属性了, 例如: .demo, .a.txt
查看root家目录下所有非隐藏文件
[root@VM-8-14-centos ~]# ls
ace luffy
查看root家目录下所有文件 (隐藏 + 非隐藏)
[root@VM-8-14-centos ~]# ls -a
. ace .bash_logout .bashrc .config .lesshst .pip .pydistutils.cfg
.. .bash_history .bash_profile .cache .cshrc luffy .pki .ssh .viminfo
1.2.2 显示文件详细信息
给 ls 添加 -l 参数(就是 list 的意思)我们就可以看到文件的详细信息了,里边的信息量还是非常大的,其中包括: 文件类型 , 文件所有者对文件的操作权限 , 文件所属组用户对文件的操作权限 , 其他人对文件的操作权限 , 硬链接计数 , 文件所有者 , 文件所属组 , 文件大小 , 文件的修改日期 , 文件名
#显示文件的详细信息
robin@OS:~$ ls -l
total 204
-rw-rw-r-- 1 robin robin 268 Mar 22 17:32 a.c
drwxrwxr-x 2 robin robin 4096 Aug 4 2019 config
-rw-r--r-- 1 robin robin 129487 Dec 25 11:28 english.txt
drwxrwxr-x 4 robin robin 4096 Jan 15 17:48 libevent
drwxrwxr-x 17 robin robin 4096 Apr 13 22:42 Linux
drwxrwxr-x 8 robin robin 4096 Feb 5 16:57 luffy
-rw-r--r-- 1 robin robin 2223 Mar 2 14:39 main.cpp
-rw-rw-r-- 1 robin robin 2167 Dec 1 22:41 mysql_test.c
drwxrwxr-x 9 robin robin 4096 Mar 26 19:19 network
-rw-r--r-- 1 robin robin 1406 Mar 2 20:18 occi.cpp
drwxrwxr-x 3 robin robin 4096 Oct 30 12:09 oradiag_robin
drwxrwxr-x 11 robin robin 4096 Mar 26 09:40 package
drwxrwxr-x 2 robin robin 4096 Dec 29 17:11 process
drwxrwxr-x 17 robin robin 4096 Mar 29 22:31 projects
-rw-r--r-- 1 robin robin 1816 Jan 6 09:37 sidtime.c
drwxrwxr-x 2 robin robin 4096 Mar 22 11:31 socket
-rw-r--r-- 1 robin robin 583 Oct 18 17:21 test.c
-rw-r--r-- 1 robin robin 2015 Mar 1 17:05 test.cpp
-rw-rw-r-- 1 robin robin 2218 Dec 2 17:02 test_mysql.c
drwxrwxr-x 5 robin robin 4096 Jan 13 17:35 udp
#文件详细信息介绍
d rwx rwx r-x 5 robin robin 4096 Jan 13 17:35 udp
| | | | | | | | | |
文件 文件所 文件所 其他人 硬链接 文件 文件 文件大小 文件修改时间 文件名
类型 有者权限 属组权限 权限 计数 所有者 所属组
注意:在查看文件详细信息的时候,还有一种简单的写法,可以使用 ll 命令:
-
有些版本的 Linux 中 ll 等价于 ls -l;
-
有些版本的 Linux 中 ll 等价于 ls -laF;
1.2.3 文件类型
在 Linux 操作系统中,一共有 7 种文件类型,这 7 种类型是根据文件属性进行划分的,而不是根据文件后缀划分的。
-: 普通的文件,在 Linux 终端中没有执行权限的为白色,压缩包为红色,可执行程序为绿色字体
d: 目录 (directory), 在 Linux 终端中为蓝色字体,如果目录的所有权限都是开放的,有绿色的背景色
l: 软链接文件 (link), 相当于 windows 中的快捷方式,在 Linux 终端中为淡蓝色 (青色) 字体
c: 字符设备 (char), 在 Linux 终端中为黄色字体
b: 块设备 (block), 在 Linux 终端中为黄色字体
p: 管道文件 (pipe), 在 Linux 终端中为棕黄色字体
s: 本地套接字文件 (socket), 在 Linux 终端中为粉色字体
1.2.4 用户类型
在 Linux 中有三大类用户: 文件所有者 , 文件所属组用户 , 其他人 , 我们可以对同一个文件给这三种人设置不同的操作权限,用于限制用户对文件的访问。
文件所有者
-
Linux 中的所有的文件都有一个所有者,就是文件的主人
文件所属组
-
文件的主人属于哪个组,这个文件默认也就属于哪个组
-
用户组中可以有多个用户,这些组中的其他用户和所有者的权限可以是不一样的
其他人
-
这个用户既不是文件所有者也不是文件所属组中的用户,就称之为其他人
-
其他人对文件也可以拥有某些权限
1.2.5 文件权限
Linux 中不同的用户可以对文件拥有不同的操作权限,权限一共有四种: 读权限 , 写权限 , 执行权限 , 无权限。
-
读权限:使用 r 表示,即: read
-
写权限:使用 w 表示,即: write
-
执行权限:使用 x 表示,即: excute
-
没有任何权限:使用 - 表示
-
从上边的例子中可以看出:
-
文件所有者对文件有读、写、执行权限
-
文件所属组用户对文件有读、写权限, 没有执行权限
-
其他人对文件有读权限, 没有写、执行权限
-
-
1.2.6 硬链接计数
硬链接计数是一个整数,如果这个数为 N (N>=1),就说明在一个或者多个目录下一共有 N 个文件,但是这 N 个文件并不占用多块磁盘空间,他们使用的是同一块,如果通过其中一个文件修改了磁盘数据,那么其他文件中的内容也就变了。每当我们给磁盘文件创建一个硬链接(使用 ln),磁盘上就会出现一个新的文件名,硬链接计数加 1,但是这新文件并不占用任何的磁盘空间,文件名还是映射到原来的磁盘地址上。
下图中为大家展示了给文件创建硬链接,和直接进行文件拷贝的区别,创建硬链接只是多了一个新的文件名, 拷贝文件不仅多了新的文件名在磁盘上数据也进行了拷贝
1.2.7 其他属性
关于 ls -l 得到的其他属性相对比较简单,最后再给大家介绍一下:
-
文件大小 —> 单位是字节
-
如果文件是目录显示为 4096, 这是目录自身大小,不包括目录中的文件大小
-
文件日期:显示的是文件的修改日期,只要文件被更新,日期也会随之变化
-
文件名:文件自己的名字(没啥可解释的)
-
如果文件类型是软连接会这样显示: link -> /root/file/test, 后边的路径表示快捷方式链接的是哪个磁盘文件
1.3 其他参数
1.3.1 单位显示
在查看文件大小的时候,如果文件比较大对应的数组自然也就很大,我们还需要基于字节进行相关的换算,不能直观得到我们想要的结果,如果数学不好,我们可以使用一个参数 -h (human)(就是命令说人话)。
#直接查看文件大小
[root@VM-8-14-centos ~]# ls -l ipc.tar.gz
-rw-r--r-- 1 root root 122962 Apr 25 2020 ipc.tar.gz
#添加 -h 参数查看文件大小
[root@VM-8-14-centos ~]# ls -lh ipc.tar.gz
-rw-r--r-- 1 root root 121K Apr 25 2020 ipc.tar.gz
1.3.2 显示目录后缀
在查看文件信息的时候,处理通过文件类型区分该文件是不是一个目录之外,还可以通过一个参数 -F 在目录名后边显示一个 /, 这样就可以直接区分出来了。
#直接查看文件信息
[root@VM-8-14-centos ~/file]# ls -l
total 8
drwxr-xr-x 2 root root 4096 Jan 25 14:29 dir
-rw-r--r-- 1 root root 0 Jan 25 14:49 haha.tar.gz
-rwxrwxrwx 1 root root 0 Jan 25 14:49 hello
lrwxrwxrwx 1 root root 15 Jan 25 14:30 link -> /root/file/test
prw-r--r-- 1 root root 0 Jan 25 14:24 pipe-2
drwxrwxrwx 2 root root 4096 Jan 25 15:20 subdir
-rw-r--r-- 1 root root 0 Jan 25 14:23 test
#添加了 -F 参数查看文件信息
[root@VM-8-14-centos ~/file]# ls -lF
total 8
drwxr-xr-x 2 root root 4096 Jan 25 14:29 dir/
-rw-r--r-- 1 root root 0 Jan 25 14:49 haha.tar.gz
-rwxrwxrwx 1 root root 0 Jan 25 14:49 hello*
lrwxrwxrwx 1 root root 15 Jan 25 14:30 link -> /root/file/test
prw-r--r-- 1 root root 0 Jan 25 14:24 pipe-2|
drwxrwxrwx 2 root root 4096 Jan 25 15:20 subdir/
-rw-r--r-- 1 root root 0 Jan 25 14:23 test
1.4 创建删除目录
创建目录
目录的创建分为两种,一种是创建单个目录,另一种是一次性创建多层目录,使用的命令是 mkdir, 后边参数是要创建的目录的名字,如果是多层目录需要添加参数 -p。关于创建的目录所在的路径可以是相对路径, 也可以是绝对路径。
#单层目录
$ mkdir 新目录的名字
#多层目录, 需要加参数 -p
$ mkdir parent/child/baby1/baby2 -p
-
删除目录
如果要删除已经存在的路径一共有两种方式,可以使用 rmdir 或者 rm
-
rmdir: 只能删除空目录,有点 low,不好用
-
rm: 可以删除文件也可以删除目录,如果删除的是目录,需要加参数 -r, 意思是递归 (recursion)
rm 命令还有另外两个经常使用的参数:
-
-i: 删除的时候给提示
-
-f: 强制删除文件,没有提示直接删除并且不能恢复,慎用
#1. low, 矮穷矬, 只能删除空目录
$ rmdir 目录名
#2. 高大上, 可以删除目录也可以删除文件
#删除目录需要加参数 -r, 递归的意思, 删除之后是不能恢复的
$ rm 目录名 -r
1.5 cp 命令
cp 就是 copy, 拷贝,使用这个命令可以拷贝文件也可以拷贝目录
-
拷贝文件 => 文件不存在得到新文件, 文件存在就覆盖
#语法: cp 要拷贝的文件 得到的文件
#场景1: 文件A, 将A拷贝一份得到文件B
$ cp 文件A 文件B
#场景2: 文件A存在的, 文件B也是存在的, 执行下边的拷贝 ==> 文件A覆盖文件B
$ cp 文件A 文件B
-
拷贝目录 ==> 目录不存在得到新目录, 该目录被拷贝到存在的目录中
#拷贝目录需要参数 -r
#场景1: 目录A, 通过拷贝得到不存在的目录B
$ cp 目录A 目录B -r
#场景2: 目录A存在的, 目录B也是存在的, 执行下边的拷贝 ==> 目录A会被拷贝并将其放到目录B中
$ cp 目录A 目录B -r
#场景3: 把A目录里的某一个或者多个文件拷贝到B目录中
$ cp A/a.txt B # 拷贝 A目录中的 a.txt 到目录B中
$ cp A/* B -r # 拷贝 A目录中的所有文件到目录B中, 不能确定A目录中是否有子目录, 因此需要加 -r
1.6 mv 命令
mv 就是 move, 这个 Linux 命令既能移动文件所在目录也可以给文件改名。
文件的移动
# 语法: mv 要移动的文件 目录
# 有一个文件A, 移动到目录B中
# 其中A可以是文件也可以是目录, B必须是目录而且必须是存在的
$ mv A B
文件改名
# 语法: mv 要改名的文件 新名字(原来是不存在的,这点很重要)
# 其中A可以是文件也可以是目录,并且是存在的, B原来是不存在的
$ mv A B
文件覆盖
# 语法: mv 存在文件A 存在的文件B
# 其中A是文件(非目录)并且是存在的, B也是一个文件(非目录)并且也存在
# A文件中的内容覆盖B文件中的内容, A文件被删除, 只剩下B文件
$ mv A B
1.7 查看文件内容
如果想要查看文件内容方式有很多,最常用的是 vim, 下面介绍一下 vim 以外的一些方式:
-
cat
该命令可以将文件内容显示到终端,由于终端是有缓存的,因此能显示的字节数也是受限制的。 如果文件太大数据就不能完全显示出来了,因此该命令适合查看比较小的文件内容。
$ cat 文件名
-
more
该命令比 cat 要高级一点,我们可以以翻屏的方式查看文件中的内容,使用方式如下:
$ more 文件名
#快捷键
- 回车: 显示下一行
- 空格: 向下滚动一屏
- b: 返回上一屏
- q: 退出more
-
less
该命令和 more 命令差不多,我们可以以翻屏的方式查看文件中的内容,使用方式如下:
$ less 文件名
# 快捷键
- b: 向上翻页
- 空格: 向后翻页
- 回车: 显示下一行
- 上下键: 上下滚动
- q:退出
-
head
使用该命令可以查看文件头部的若干行信息,使用方式如下:
# 默认显示文件的前10行
$ head 文件名
# 指定显示头部的前多少行
$ head -行数 文件名
-
tail
使用该命令可以查看文件尾部的若干行信息,使用方式如下:
# 默认显示文件的后10行
$ tail 文件名
# 指定显示尾部的最后多少行
$ tail -行数 文件名
1.8链接的创建
链接分两种类型: 软连接和硬链接。软连接相当于 windows 中的快捷方式,硬链接前边也已经介绍过了文件并不会进行拷贝,只是多出一个新的文件名并且硬链接计数会加 1。
-
软连接
# 语法: ln -s 源文件路径 软链接文件的名字(可以带路径)
# 查看目录文件
[root@VM-8-14-centos ~/luffy]# ll
total 8
drwxr-xr-x 3 root root 4096 Jan 25 17:27 get
-rw-r--r-- 1 root root 37 Jan 25 17:26 onepiece.txt
# 给 onepiece.txt 创建软连接, 放到子目录 get 中
[root@VM-8-14-centos ~/luffy]# ln -s /root/luffy/onepiece.txt get/link.lnk
[root@VM-8-14-centos ~/luffy]# ll get
total 4
lrwxrwxrwx 1 root root 24 Jan 25 17:27 link.lnk -> /root/luffy/onepiece.txt
drwxr-xr-x 2 root root 4096 Jan 24 21:37 onepiece
在创建软链接的时候, 命令中的源文件路径建议使用绝对路径,这样才能保证创建出的软链接文件在任意目录中移动都可以访问到链接的那个源文件。
-
硬链接
# 语法: ln 源文件 硬链接文件的名字(可以带路径)
# 创建硬链接文件, 放到子目录中
[root@VM-8-14-centos ~/luffy]# ln onepiece.txt get/link.txt
# 查看链接文件和硬链接计数, 从 1 --> 2
[root@VM-8-14-centos ~/luffy]# ll get
total 8
lrwxrwxrwx 1 root root 24 Jan 25 17:27 link.lnk -> /root/luffy/onepiece.txt
-rw-r--r-- 2 root root 37 Jan 25 17:26 link.txt
drwxr-xr-x 2 root root 4096 Jan 24 21:37 onepiece
硬链接和软链接不同,它是通过文件名直接找对应的硬盘地址,而不是基于路径,因此源文件使用相对路径即可,无需为其制定绝对路径。目录是不允许创建硬链接的。
1.9文件属性
文件属性相关的命令主要是修改用户对文件的操作权限,文件所有者,文件所属组的相关信息。
1.9.1 修改文件文件权限
文件权限是针对文件所有者 , 文件所属组用户 , 其他人这三类人而言的,对应的操作指令是 chmod。设置方式也有两种,分别为文字设定法和数字设定法。
文字设定法是通过一些关键字 r, w, x, - 来描述用户对文件的操作权限。
数字设定法是通过一些数字 0, 1, 2, 4, 5, 6, 7 来描述用户对文件的操作权限。
文字设定法
#chmod
# 语法格式: chmod who [+|-|=] mod 文件名
- who:
- u: user -> 文件所有者
- g: group -> 文件所属组用户
- o: other -> 其他
- a: all, 以上是三类人 u+g+o
- 对权限的操作:
+: 添加权限
-: 去除权限
=: 权限的覆盖
- mod: 权限
r: read, 读
w: write, 写
x: execute, 执行
-: 没有权限
# 将文件所有者权限设置为读和执行, 也就是权限覆盖
robin@OS:~/Linux$ chmod u=rx b.txt
robin@OS:~/Linux$ ll b.txt
-r-xrw-r-- 2 robin robin 2929 Apr 14 18:53 b.txt*
# 给其他人添加写和执行权限
robin@OS:~/Linux$ chmod o+wx b.txt
robin@OS:~/Linux$ ll b.txt
-r-xrw-rwx 2 robin robin 2929 Apr 14 18:53 b.txt*
# 给文件所属组用户去掉读和执行权限
robin@OS:~/Linux$ chmod g-rx b.txt
robin@OS:~/Linux$ ll b.txt
-r-x-w-rwx 2 robin robin 2929 Apr 14 18:53 b.txt*
# 将文件所有者,文件所属组用户,其他人权限设置为读+写+执行
robin@OS:~/Linux$ chmod a=rwx b.txt
robin@OS:~/Linux$ ll b.txt
-rwxrwxrwx 2 robin robin 2929 Apr 14 18:53 b.txt*
数字设定法
# 语法格式: chmod [+|-|=] mod 文件名
- 对权限的操作:
+: 添加权限
-: 去除权限
=: 权限的覆盖, 等号可以不写
- mod: 权限描述, 所有权限都放开是 7
- 4: read, r
- 2: write, w
- 1: execute , x
- 0: 没有权限
# 分解: chmod 0567 a.txt
0 5 6 7
八进制 文件所有者 文件所属组用户 其他人
r + x r + w r+w+x
######################### 举例 #########################
# 查看文件 c.txt 的权限
robin@OS:~/Linux$ ll c.txt
-rwxrwxrwx 2 robin robin 2929 Apr 14 18:53 c.txt*
# 文件所有者去掉执行权限, 所属组用户去掉写权限, 其他人去掉读+写权限
robin@OS:~/Linux$ chmod -123 c.txt
robin@OS:~/Linux$ ll c.txt
-rw-r-xr-- 2 robin robin 2929 Apr 14 18:53 c.txt*
# 文件所有者添加执行权限, 所属组用户和其他人权限不变
robin@OS:~/Linux$ chmod +100 c.txt
robin@OS:~/Linux$ ll c.txt
-rwxr-xr-- 2 robin robin 2929 Apr 14 18:53 c.txt*
# 将文件所有者,文件所属组用户,其他人权限设置为读+写, 没有执行权限
robin@OS:~/Linux$ chmod 666 c.txt
robin@OS:~/Linux$ ll c.txt
-rw-rw-rw- 2 robin robin 2929 Apr 14 18:53 c.txt
1.9.2修改文件所有者
默认情况下,文件是通过哪个用户创建出来的,就属于哪个用户,这个用户属于哪个组,文件就属于哪个组。如果有特殊需求,可以修改文件所有者,对应的操作命令是 chown。因为修改文件所有者是跨用户操作,普通用户没有这个权限,需要借助管理员权限才能完成该操作。
普通用户借助管理员权限执行某些shell命令, 需要在命令前加关键字sudo, 但是普通用户默认是没有使用 sudo的资格的, 需要修改 /etc/sudoers 文件 。
普通用户添加 sudo 权限
添加 sudo 权限,这个叫做 sudoers 的文件位于 /etc 目录下,我们先切换到 /etc 目录,然后查看一下这个文件的详细信息
$ cd /etc/
$ ll sudoers
-r-------- 1 root root 4382 Jan 21 23:16 sudoers
我们惊奇的发现这个文件的所有者 root 对它也只有读权限,默认是不能修改的,作为 root 以外的其他用户对它没有任何的操作权限。
解决方案:
-
先切换到 root 用户
-
在 root 用户下修改这个文件属性,给其添加写权限
-
修改文件内容,把普通用户 sanji 添加进去,保存退出
-
将文件权限修改为原来的 400 (r--------)
-
切换到用户 sanji, 这时候就可以使用 sudo 了,权限添加成功
相关的操作命令如下:
# 1. 切换到root用户
$ su root
Password: # 输入root用户的密码
# 2. 修改文件权限, 暴力一点没有关系, 反正还需要改回去, 直接 777 就行
$ chmod 777 sudoers
# 3. 使用 vim 打开这个文件
$ vim sudoers
# 4. 在文件中找到这一行, 在文件偏尾部的位置
root ALL=(ALL) ALL
# 5. 照葫芦画瓢, 在下边新添加一行内容如下:
root ALL=(ALL) ALL # 原来的内容
sanji ALL=(ALL) ALL # 新添加的行, 将用户名指定为 sanji 即可
# 6. 保存退出 (先按esc, 然后输入 :wq)
# 7. 将文件改回原来的权限
$ chmod 400 sudoers
恭喜,权限设置成功,你的普通的用户可以使用 sudo 执行只有管理员才能操作的命令啦。
#chmod
# 语法格式: chmod who [+|-|=] mod 文件名
- who:
- u: user -> 文件所有者
- g: group -> 文件所属组用户
- o: other -> 其他
- a: all, 以上是三类人 u+g+o
- 对权限的操作:
+: 添加权限
-: 去除权限
=: 权限的覆盖
- mod: 权限
r: read, 读
w: write, 写
x: execute, 执行
-: 没有权限
# 将文件所有者权限设置为读和执行, 也就是权限覆盖
robin@OS:~/Linux$ chmod u=rx b.txt
robin@OS:~/Linux$ ll b.txt
-r-xrw-r-- 2 robin robin 2929 Apr 14 18:53 b.txt*
# 给其他人添加写和执行权限
robin@OS:~/Linux$ chmod o+wx b.txt
robin@OS:~/Linux$ ll b.txt
-r-xrw-rwx 2 robin robin 2929 Apr 14 18:53 b.txt*
# 给文件所属组用户去掉读和执行权限
robin@OS:~/Linux$ chmod g-rx b.txt
robin@OS:~/Linux$ ll b.txt
-r-x-w-rwx 2 robin robin 2929 Apr 14 18:53 b.txt*
# 将文件所有者,文件所属组用户,其他人权限设置为读+写+执行
robin@OS:~/Linux$ chmod a=rwx b.txt
robin@OS:~/Linux$ ll b.txt
-rwxrwxrwx 2 robin robin 2929 Apr 14 18:53 b.txt*
1.9.3修改文件所属组
普通用户没有修改文件所属组的权限,如果需要修改需要借助管理员权限才能完成,需要使用的命令是 chgrp。当然了这个属性的修改也可以使用 chown 命令来完成。
# 只修改文件所属的组, 普通用户没有这个权限, 借助管理员的权限
# 语法: sudo chgrp 新的组 文件名
# 查看文件所属组信息
robin@OS:~/Linux$ ll b.txt
-rw-rw-rw- 2 robin luffy 2929 Apr 14 18:53 b.txt
# 修改文件所属的组
robin@OS:~/Linux$ sudo chgrp robin b.txt
robin@OS:~/Linux$ ll b.txt
-rw-rw-rw- 2 robin robin 2929 Apr 14 18:53 b.txt
1.10其他命令
1.10.1 tree 命令
该命令的作用是以树状结构显示目录 , tree 工具默认是没有的,需要手动安装,系统版本不同安装方式也不尽相同:
ubuntu: sudo apt install tree
centos: sudo yum install tree
如果是基于管理员用户安装软件,不需要加 sudo。该命令有一个常用参数 -L, 即 (layer) 显示目录的层数。
# 语法格式
$ tree [-L n] # 查看当前目录的结构, n为显示的目录层数
$ tree 目录名 [-L n] # 查看指定目录的结构, n为显示的目录层数
# 只显示1层
[root@VM-8-14-centos ~]# tree -L 1
.
|-- ace
|-- file
|-- ipc.tar.gz
|-- link.lnk -> /root/luffy/onepiece.txt
`-- luffy
# 显示2层目录
[root@VM-8-14-centos ~]# tree -L 2
.
|-- ace
| `-- brother
|-- file
| |-- dir
| |-- haha.tar.gz
| |-- hello
| |-- link -> /root/file/test
| |-- pipe-2
| |-- subdir
| `-- test
|-- ipc.tar.gz
|-- link.lnk -> /root/luffy/onepiece.txt
`-- luffy
|-- get
`-- onepiece.txt
1.10.2 pwd 命令
pwd 命令用户当前所在的工作目录,没有参数,直接执行该命令即可。
# 查看当前用户在哪个目录中, 所在的目录一般称之为工作目录
[root@VM-8-14-centos ~/luffy/get/onepiece]# pwd
/root/luffy/get/onepiece # 当前工作目录
1.10.3 touch 命令
使用 touch 命令可以创建一个新的空文件 , 如果指定的文件是已存在的,只会更新文件的修改日期,对内容没有任何影响。
# 语法: touch 文件名
# 查看目录信息
[root@VM-8-14-centos ~/luffy]# ll
total 8
drwxr-xr-x 3 root root 4096 Jan 25 17:38 get
-rw-r--r-- 2 root root 37 Jan 25 17:26 onepiece.txt
# 创建一个新的文件 robin.txt
[root@VM-8-14-centos ~/luffy]# touch robin.txt
# 再次查看目录中的文件信息, 发现 robin.txt是空的, 大小为 0
[root@VM-8-14-centos ~/luffy]# ll
total 8
drwxr-xr-x 3 root root 4096 Jan 25 17:38 get
-rw-r--r-- 2 root root 37 Jan 25 17:26 onepiece.txt
-rw-r--r-- 1 root root 0 Jan 25 17:54 robin.txt
# touch 后的参数指定一个已经存在的文件名
[root@VM-8-14-centos ~/luffy]# touch onepiece.txt
# 继续查看目录中的文件信息, 发现文件时间被更新了: 37 Jan 25 17:26 --> 37 Jan 25 17:54
[root@VM-8-14-centos ~/luffy]# ll
total 8
drwxr-xr-x 3 root root 4096 Jan 25 17:38 get
-rw-r--r-- 2 root root 37 Jan 25 17:54 onepiece.txt
-rw-r--r-- 1 root root 0 Jan 25 17:54 robin.txt
1.10.4 which 命令
which 命令可以查看要执行的命令所在的实际路径,命令解析器工作的时候也会搜索这个目录。需要注意的是该命令只能查看非内建的shell指令所在的实际路径, 有些命令是直接写到内核中的, 无法查看。
我们使用的大部分 shell 命令都是放在系统的 /bin 或者 /usr/bin 目录下:
# 由于使用的Linux版本不同, 得到的路径也会有不同
[root@VM-8-14-centos ~]# which ls
alias ls='ls --color=auto'
/usr/bin/ls
[root@VM-8-14-centos ~]# which date
/usr/bin/date
[root@VM-8-14-centos ~]# which cp
alias cp='cp -i'
/usr/bin/cp
[root@VM-8-14-centos ~]# which mv
alias mv='mv -i'
/usr/bin/mv
[root@VM-8-14-centos ~]# which pwd
/usr/bin/pwd
1.10.5 重定向命令
关于重定向使用最多的是就是输出重定向 , 顾名思义就是修改输出的数据的位置,通过重定向操作我们可以非常方便的进行文件的复制,或者文件内容的追加。输出重定向使用的不是某个关键字而是符号 > 或者 >>。
将文件内容写入到指定文件中,如果文件中已有数据,则会使用新数据覆盖原数据
将输出的内容追加到指定的文件尾部
# 输出的重定向举例: printf默认是要将数据打印到终端, 可以修改默认的输出位置 => 重定向到某个文件中
# 关键字 >
# 执行一个shell指令, 获得一个输出, 这个输出默认显示到终端, 如果要将其保存到文件中, 就可以使用重定向
# 如果当前目录下test.txt不存在, 会被创建, 如果存在, 内容被覆盖
$ date > test.txt
# 日期信息被写入到文件 test.txt中
robin@OS:~/Linux$ cat test.txt
Wed Apr 15 09:37:52 CST 2020
# 如果不希望文件被覆盖, 而是追加, 需要使用 >>
in@OS:~/Linux$ date >> test.txt
# 日期信息被追加到 test.txt中
robin@OS:~/Linux$ cat test.txt
Wed Apr 15 09:37:52 CST 2020
Wed Apr 15 09:38:44 CST 2020
# 继续追加信息
robin@OS:~/Linux$ date >> test.txt
robin@OS:~/Linux$ cat test.txt
Wed Apr 15 09:37:52 CST 2020
Wed Apr 15 09:38:44 CST 2020
Wed Apr 15 09:39:03 CST 2020
2.用户管理命令
2.1切换用户
Linux 是一个多用户的操作系统,可以同时登陆多个用户,因此很多时候需要在多个用户之间切换,用户切换需要使用 su 或者 su -。使用 su 只切换用户,当前的工作目录不会变化,但是使用 su - 不仅会切换用户也会切换工作目录,工作目录切换为当前用户的家目录。
从用户 A 切换到用户 B, 如果还想再切换回用户 A,可以直接使用 exit。
# 只切换用户, 工作目录不变
$ su 用户名
# 举例:
robin@OS:~/Linux$ su luffy
Password: # 需要输入luffy用户的密码
luffy@OS:/home/robin/Linux$ # 工作目录不变
# 切换用户和工作目录, 会自动跳转到当前用户的家目录中
$ su - 用户名
# 举例:
robin@OS:~/Linux$ su - luffy
Password: # 需要输入luffy用户的密码
luffy@OS:~$ pwd
/home/luffy # 工作目录变成了luffy的家目录
# 回到原来的用户
$ exit
2.2 添加删除用户
作为一个普通用户是没有给系统添加新用户这个权限的,如果想要添加新用户可以先切换到 root 用户,或者基于普通用户为其添加管理员权限来完成新用户的添加。添加新用户需要使用 adduser/useradd 命令来完成。
普通用户没有添加 / 删除用户的权限,需要授权。
2.2.1 添加新用户
# 添加用户
# sudo -> 使用管理员权限执行这个命令
$ sudo adduser 用户名
# centos
$ sudo useradd 用户名
# ubuntu
$ sudo useradd -m -s /bin/bash 用户名
# 在使用 adduser 添加新用户的时候,有的Linux版本执行完命令就结束了,有的版本会提示设置密码等用户信息
robin@OS:~/Linux$ sudo adduser lisi
Adding user `lisi' ...
Adding new group `lisi' (1004) ...
Adding new user `lisi' (1004) with group `lisi' ...
Creating home directory `/home/lisi' ...
Copying files from `/etc/skel' ...
Enter new UNIX password:
Retype new UNIX password:
passwd: password updated successfully
Changing the user information for lisi
Enter the new value, or press ENTER for the default
Full Name []:
Room Number []:
Work Phone []:
Home Phone []:
Other []:
Is the information correct? [Y/n] y
当新用户添加完毕之后,我们可以切换到新添加的用户下,用来检测是否真的添加成功了,另外我们也可以使用其他方式来检验,首先在 /home 目录中会出现一个和用户名同名的目录,这就是新创建的用户的家目录,另外我们还可以查看 /etc/passwd 文件,里边记录着新添加的用户的更加详细的信息:
2.2.2 删除用户
删除用户并不是将 /home 下的用户家目录删除就完事儿了,我们需要使用 userdle 命令才能删除用户在系统中的用户 ID 和所属组 ID 等相关信息,但是需要注意的是在某些Linux版本中用户虽然被删除了, 但是它的家目录却没有被删除,需要我们手动将其删除。
# 删除用户, 添加参数 -r 就可以一并删除用户的家目录了
$ sudo userdel 用户名 -r
# 删除用户 lisi
$ sudo userdel lisi -r
# 使用deluser不能添加参数-r, 家目录不能被删除, 需要使用 rm 命令删除用户的家目录, 比如:
$ sudo rm /home/用户名 -r
由于 Linux 的版本很多,删除用户对应的操作指令不是唯一的,经测试在 CentOS 版本只支持 userdel命令 , 在 Ubuntu中既支持 userdel 也支持 deluser命令。
2.3添加删除用户组
默认情况下,只要创建新用户就会得到一个同名的用户组,并且这个用户属于这个组。一般情况下不需要创建新的用户组,如果有需求可以使用 groupadd 添加用户组,使用 groupdel 删除用户组。
由于普通用户没有添加删除用户组权限,因此需要在管理员(root)用户下操作,或者在普通用户下借助管理员权限完成该操作。
# 基于普通用户创建新的用户组
$ sudo groupadd 组名
# 基于普通用户删除已经存在的用户组
$ sudo groupdel 组名
如果验证用户组是否添加成功了,可以查看 /etc/group 文件,里边有用户组相关的信息:
在 Ubuntu 中添加删除用户组可以使用 addgroup/groupadd 和 delgroup/groupdel
在 CentOS 中添加和删除用户只能使用 groupadd 和 groupdel
我们只需要通过 which 命令名 查看,就能知道该 Linux 版本是不是支持使用该命令了。
2.4修改密码
Linux 中设置用户密码和修改用户密码的方式是一样的,修改用户密码又分几种情况: 修改当前用户密码 , 普通用户A修改其他普通用户密码 , 普通用户A修改root用户密码 , root用户修改普通用户密码。修改密码需要使用 passwd 命令。当创建了一个普通用户却没有提示指定密码,或者忘记了用户密码都可以通过该命令来实现自己重置密码的需求。
当前用户修改自己的密码,默认是有权限操作的 当前普通用户修改其他用户密码,默认没有权限,需要借助管理员权限才能完成操作 当前普通用户修改 root 用户密码,默认没有权限,需要借助管理员权限才能完成操作 root 用户修改其他普通用户密码,默认有权限,可以直接修改
# passwd
# 修改当前用户
$ passwd
# 修改非当前用户密码
$ sudo passwd 用户名
# 修改root
$ sudo passwd root
通过以上介绍的相关命令我们可以知道,如果让一个普通用户可以使用管理员权限执行一些指令其实是非常危险的的, 因此普通用户默认是没有使用 sudo 的权限的,必须授权才能使用,工作场景中授权操作一定要慎重,要三思。
3.压缩命令
不管是在 Linux 还是其他操作系统中,关于数据的压缩和解压缩操作是经常被用到的。由于在 windows 平台文件的相关操作被傻瓜化了,到了 Linux 平台让好多小伙伴感觉有点束手无策,本篇文章中主要为大家讲解基于 Linux 的常用压缩包操作,格式包含:tar.gz, .tgz, .tar.bz2, .zip, .rar, .tar.xz。
3.1tar
在 Linux 操作系统中默认自带两个原始的压缩工具分别是 gzip 和 bzip2, 但是它们都有先天的缺陷,不能打包压缩文件, 每个文件都会生成一个单独的压缩包, 并且压缩之后不会保留原文件, 这是一件叔能忍婶也不能忍的事情。
Linux 中自带一个打包工具,叫做 tar, 默认情况下该工具是不能进行压缩操作的,在这种情况下 tar 和 gzip, bzip2 就联姻了,各自发挥各自的优势,Linux 下最强大的打包压缩工具至此诞生。
我们在使用 tar 进行压缩和解压缩的时候,只需要指定相对用的参数,在其内部就会调用对应的压缩工具 gzip 或者 bzip2 完成指定的操作。
3.1.1 压缩 (.tar.gz .tar.bz2 .tgz)
如果使用 tar 完成文件压缩,涉及的参数如下,在使用过程中参数没有先后顺序:
c: 创建压缩文件 z: 使用 gzip 的方式进行文件压缩 j: 使用 bzip2 的方式进行文件压缩 v: 压缩过程中显示压缩信息,可以省略不写 f: 指定压缩包的名字
一般认为 .tgz 文件就等同于 .tar.gz 文件,因此它们的压缩方式是相同的。
# 语法:
$ tar 参数 生成的压缩包的名字 要压缩的文件(文件或者目录)
# 备注: 关于生成的压缩包的名字, 建议使用标准后缀, 方便识别:
- 压缩使用 gzip 方式, 标准后缀格式为: .tar.gz
- 压缩使用 bzip2 方式, 标准后缀格式为: .tar.bz2
举例:使用 gzip 的方式进行文件压缩
# 查看目录内容
[root@VM-8-14-centos ~/luffy]# ls
get onepiece.txt robin.txt
# 压缩目录中所有文件, 如果要压缩某几个文件, 直接指定文件名即可
[root@VM-8-14-centos ~/luffy]# tar zcvf all.tar.gz *
get/ # ....... 压缩信息
get/link.lnk # ....... 压缩信息
get/onepiece/ # ....... 压缩信息
get/onepiece/haha.txt
get/link.txt
onepiece.txt
robin.txt
# 查看目录文件, 多了一个压缩文件 all.tar.gz
[root@VM-8-14-centos ~/luffy]# ls
all.tar.gz get onepiece.txt robin.txt
举例:使用 bzip2 的方式进行文件压缩
# 查看目录内容
[root@VM-8-14-centos ~/luffy]# ls
all.tar.gz get onepiece.txt robin.txt
# 压缩目录中除 all.tar.gz 的文件和目录
[root@VM-8-14-centos ~/luffy]# tar jcvf part.tar.bz2 get onepiece.txt robin.txt
get/ # ....... 压缩信息
get/link.lnk # ....... 压缩信息
get/onepiece/ # ....... 压缩信息
get/onepiece/haha.txt
get/link.txt
onepiece.txt
robin.txt
# 查看目录信息, 多了一个压缩文件 part.tar.bz2
[root@VM-8-14-centos ~/luffy]# ls
all.tar.gz get onepiece.txt part.tar.bz2 robin.txt
3.1.2 解压缩 (.tar.gz .tar.bz2 .tgz)
如果使用 tar 进行文件的解压缩,涉及的参数如下,在使用过程中参数没有先后顺序:
x: 释放压缩文件内容 z: 使用 gzip 的方式进行文件压缩,压缩包后缀为.tar.gz j: 使用 bzip2 的方式进行文件压缩,压缩包后缀为.tar.bz2 v: 解压缩过程中显示解压缩信息 f: 指定压缩包的名字
使用以上参数是将压缩包解压到当前目录,如果需要解压到指定目录,需要指定参数 -C。 一般认为 .tgz 文件就等同于 .tar.gz 文件,解压缩方式是相同的。解压的语法格式如下:
# 语法1: 解压到当前目录中
$ tar 参数 压缩包名
# 语法2: 解压到指定目录中
$ tar 参数 压缩包名 -C 解压目录
举例:使用 gzip 的方式进行文件解压缩
# 查看目录文件信息
[root@VM-8-14-centos ~/luffy]# ls
all.tar.gz get onepiece.txt part.tar.bz2 robin.txt temp
# 将 all.tar.gz 压缩包解压缩到 temp 目录中
[root@VM-8-14-centos ~/luffy]# tar zxvf all.tar.gz -C temp
get/ # 解压缩文件信息
get/link.lnk # 解压缩文件信息
get/onepiece/ # 解压缩文件信息
get/onepiece/haha.txt # 解压缩文件信息
get/link.txt
onepiece.txt
robin.txt
# 查看temp目录内容, 都是从压缩包中释放出来的
[root@VM-8-14-centos ~/luffy]# ls temp/
get onepiece.txt robin.txt
举例:使用 bzip2 的方式进行文件解压缩
# 删除 temp 目录中的所有文件
[root@VM-8-14-centos ~/luffy]# rm temp/* -rf
# 查看 luffy 目录中的文件信息
[root@VM-8-14-centos ~/luffy]# ls
all.tar.gz get onepiece.txt part.tar.bz2 robin.txt temp
# 将 part.tar.bz2 中的文件加压缩到 temp 目录中
[root@VM-8-14-centos ~/luffy]# tar jxvf part.tar.bz2 -C temp
get/ # 解压缩文件信息
get/link.lnk # 解压缩文件信息
get/onepiece/ # 解压缩文件信息
get/onepiece/haha.txt # 解压缩文件信息
get/link.txt
onepiece.txt
robin.txt
# 查看 temp 目录中的文件信息
[root@VM-8-14-centos ~/luffy]# ls temp/
get onepiece.txt robin.txt
3.2 zip
zip 格式的压缩包在 Linux 中也是很常见的,在某些版本中需要安装才能使用
Ubuntu
$ sudo apt install zip # 压缩
$ sudo apt install unzip # 解压缩
CentOS
# 因为 centos 可以使用 root 用户登录, 基于 root 用户安装软件, 不需要加 sudo
$ sudo yum install zip # 压缩
$ sudo yum install unzip # 解压缩
3.2.1 压缩 (.zip)
使用 zip 压缩目录需要注意一点,必须要添加参数 -r, 这样才能将子目录中的文件一并压缩,如果要压缩的文件中没有目录,该参数就可以不写了。
另外使用 zip 压缩文件,会自动生成文件后缀.zip, 因此就不需要额外指定了。
# 语法: 后自动添加压缩包后缀 .zip, 如果要压缩目录, 需要添加参数 r
$ zip [-r] 压缩包名 要压缩的文件
举例:
# 查看目录文件信息
[root@VM-8-14-centos ~/luffy]# ls
get onepiece.txt robin.txt temp
# 压缩目录 get 和文件 onepiece.txt robin.txt 得到压缩包 all.zip(不需要指定后缀, 自动添加)
[root@VM-8-14-centos ~/luffy]# zip all onepiece.txt robin.txt get/ -r
adding: onepiece.txt (stored 0%)
adding: robin.txt (stored 0%)
adding: get/ (stored 0%)
adding: get/link.lnk (stored 0%) # 子目录中的文件也被压缩进去了
adding: get/onepiece/ (stored 0%) # 子目录中的文件也被压缩进去了
adding: get/onepiece/haha.txt (stored 0%) # 子目录中的文件也被压缩进去了
adding: get/link.txt (stored 0%) # 子目录中的文件也被压缩进去了
# 查看目录文件信息, 多了一个压缩包文件 all.zip
[root@VM-8-14-centos ~/luffy]# ls
all.zip get onepiece.txt robin.txt temp
3.2.2 解压缩 (.zip)
对应 zip 格式的文件解压缩,必须要使用 unzip 命令,和压缩的时候使用的命令是不一样的。如果压缩包中的文件要解压到指定目录需要指定参数 -d, 默认是解压缩到当前目录中。
# 语法1: 解压到当前目录中
$ unzip 压缩包名
# 语法: 解压到指定目录, 需要添加参数 -d
$ unzip 压缩包名 -d 解压目录
举例:
# 查看目录文件信息
[root@VM-8-14-centos ~/luffy]# ls
all.zip get onepiece.txt robin.txt temp
# 删除 temp 目录中的所有文件
[root@VM-8-14-centos ~/luffy]# rm temp/* -rf
# 将 all.zip 解压缩到 temp 目录中
[root@VM-8-14-centos ~/luffy]# unzip all.zip -d temp/
Archive: all.zip
extracting: temp/onepiece.txt # 释放压缩的子目录中的文件
extracting: temp/robin.txt # 释放压缩的子目录中的文件
creating: temp/get/
extracting: temp/get/link.lnk
creating: temp/get/onepiece/
extracting: temp/get/onepiece/haha.txt # 释放压缩的子目录中的文件
extracting: temp/get/link.txt
# 查看 temp 目录中的文件信息
[root@VM-8-14-centos ~/luffy]# ls temp/
get onepiece.txt robin.txt
3.3 rar
3.4 xz
4.查找命令
在使用 Linux 系统的时候,我们经常会需要查找某些文件,但是大多数情况下我们并不能确定这些文件的具体位置,这样的话就非常浪费我们的时间。Linux 为我们提供了很多的用于文件搜索的命令,如果需求比较简单可以使用 locate,which,whereis 来完成搜索,如果需求复杂可以使用 find, grep 进行搜索。其中 which 在前边已经介绍过了, 使用方法和功能就直接略过了,whereis 局限性太大,不常用这里也就不介绍了。
4.1find
find 是 Linux 中一个搜索功能非常强大的工具,它的主要功能是根据文件的属性,查找对应的磁盘文件,比如说我们常用的一些属性,文件名 , 文件类型 , 文件大小 , 文件的目录深度等,下面基于这些常用数据来讲解一些具体的使用方法。
如果想用通过属性对文件进行搜索, 只需要指定出属性对应的参数就可以了, 下面将依次进行介绍。
4.1.1 文件名 (-name)
根据文件名进行搜索有两种方式: 精确查询和模糊查询。关于模糊查询必须要使用对应的通配符,最常用的有两个, 分别为 * 和 ?。其中 * 可以匹配零个或者多个字符, ?用于匹配单个字符。
如果我们进行模糊查询,建议(非必须)将带有通配符的文件名写到引号中(单引号或者双引号都可以),这样可以避免搜索命令执行失败(如果不加引号,某些情况下会这样)。
如果需要根据文件名进行搜索,需要使用参数 -name。
# 语法格式: 根据文件名搜索
$ find 搜索的路径 -name 要搜索的文件名
根据文件名搜索举例:
# 模式搜索
# 搜索 root 家目录下文件后缀为 txt 的文件
[root@VM-8-14-centos ~]# find /root -name "*.txt"
/root/luffy/get/onepiece/haha.txt
/root/luffy/get/onepiece/onepiece.txt
/root/luffy/get/onepiece.txt
/root/luffy/get/link.txt
/root/luffy/robin.txt
/root/luffy/onepiece.txt
/root/ace/brother/finally/die.txt
/root/onepiece.txt
##################################################
# 精确搜索
# 搜索 root 家目录下文件名为 onepiece.txt 的文件
[root@VM-8-14-centos ~]# find /root -name "onepiece.txt"
/root/luffy/get/onepiece/onepiece.txt
/root/luffy/get/onepiece.txt
/root/luffy/onepiece.txt
/root/onepiece.txt
4.1.2 文件类型 (-type)
在前边文章中已经介绍过 Linux 中有 7 中文件类型 , 如果有去求我们可以通过 find 对指定类型的文件进行搜索,该属性对应的参数为 -type。其中每种类型都有对应的关键字,如下表:
# 语法格式:
$ find 搜索的路径 -type 文件类型
根据文件类型搜索举例:
# 搜索 root 用户家目录下, 软连接类型的文件
[root@VM-8-14-centos ~]# find /root -type l
/root/link.lnk
/root/luffy/get/link.lnk
/root/file/link
4.1.3 文件大小 (-size)
如果需要根据文件大小进行搜索,需要使用参数 -size。关于文件大小的单位有很多,可以根据实际需求选择,常用的分别有 k(小写), M(大写), G(大写)。
在进行文件大小判断的时候,需要指定相应的范围,涉及的符号有两个分别为:加号 (+) 和 减号 (-),下面具体说明其使用方法:
# 语法格式:
$ find 搜索的路径 -size [+|-]文件大小
- 文件大小需要加单位:
- k (小写)
- M (大写)
- G (大写)
-
关于文件大小的区间划分非常重要,请仔细阅读,并思考,可以自己画个图,这里以 4k 来举例:
-size 4k 表示的区间为 (4-1k,4k], 表示一个区间,大于 3k, 小于等于 4k -size -4k: [0k, 4-1k], 表示一个区间,大于等于 0 并且 小于等于 3k -size +4k: (4k, 正无穷), 表示搜索大于 4k 的文件
根据文件大小搜索举例:
# 搜索当前目录下 大于1M的所有文件 (file>3M)
$ find ./ -size +3M
# 搜索当前目录下 大于等于0M并且小于等于2M的文件 (0M <= file <=2M)
$ find ./ -size -3M
# 搜索当前目录下 大于2M并且小于等于3M的文件 (2M < file <=3M)
$ find ./ -size 3M
# 搜索当前目录下 大于1M 并且 小于等于 3M 的文件
$ find ./ -size +1M -size -4M
4.1.4 目录层级
因为 Linux 的目录是树状结构,所有目录可能有很多层,在搜索某些属性的时候可以指定只搜索某几层目录,相关的参数有两个,分别为: -maxdepth 和 -mindepth。
这两个参数不能单独使用, 必须和其他属性一起使用,也就是搜索某几层目录中满足条件的文件。
-maxdepth: 最多搜索到第多少层目录, -mindepth: 至少从第多少层开始搜索 下面通过 find 搜索某几层目录中文件名满足条件的文件:
# 查找文件, 从根目录开始, 最多搜索5层, 这个文件叫做 *.txt (1 <= 层数 <= 5)
$ sudo find / -maxdepth 5 -name "*.txt"
# 查找文件, 从根目录开始, 至少从第5层开始搜索, 这个文件叫做 *.txt (层数>=5层)
$ sudo find / -mindepth 5 -name "*.txt"
4.1.5 同时执行多个操作
在搜索文件的时候如果想在一个 find 执行多个操作,通过使用管道 (|) 的方式是行不通的,比如下面的操作:
# 比如: 通过find搜索最多两层目录中后缀为 .txt 的文件, 然后再查看这些满足条件的文件的详细信息
# 在find操作中直接通过管道操作多个指令, 最终输出的结果是有问题, 因此不能直接这样使用
$ find ./ -maxdepth 2 -name "*.txt" | ls -l
total 612
drwxr-xr-x 2 root root 4096 Jan 26 18:11 a
-rw-r--r-- 1 root root 269 Jan 26 17:44 a.c
drwxr-xr-x 3 root root 4096 Jan 26 18:39 ace
drwxr-xr-x 4 root root 4096 Jan 25 15:21 file
lrwxrwxrwx 1 root root 24 Jan 25 17:27 link.lnk -> /root/luffy/onepiece.txt
drwxr-xr-x 4 root root 4096 Jan 26 18:39 luffy
-r--r--r-- 1 root root 37 Jan 26 16:50 onepiece.txt
-rw-r--r-- 1 root root 598314 Dec 2 02:07 rarlinux-x64-6.0.0.tar.gz
如果想要实现上面的需求,需要在 find 中使用 exec, ok, xargs, 这样就可以在 find 命令执行完毕之后,再执行其他的子命令了。
4.1.5.1 exec
-exec 是 find 的参数,可以在exec参数后添加其他需要被执行的shell命令。
find 添加了 exec 参数之后,命令的尾部需要加一个后缀 {} \;, 注意 {} 和 \ 之间需要有一个空格。
在参数 -exec 后添加的 shell 命令处理的是 find 搜索之后的结果,find 的结果会作为 新添加的 shell 命令 的输入,最后在终端上输出最终的处理结果。
# 语法:
$ find 路径 参数 参数值 -exec shell命令2 {} \;
命令的使用效果演示:
# 搜索最多两层目录, 文件名后缀为 .txt的文件
$ find ./ -maxdepth 2 -name "*.txt"
./luffy/robin.txt
./luffy/onepiece.txt
./onepiece.txt
# 搜索到满足条件的文件之后, 再继续查看文件的详细属性信息
$ find ./ -maxdepth 2 -name "*.txt" -exec ls -l {} \;
-rw-r--r-- 1 root root 0 Jan 25 17:54 ./luffy/robin.txt
-r--r--r-- 2 root root 37 Jan 25 17:54 ./luffy/onepiece.txt
-r--r--r-- 1 root root 37 Jan 26 16:50 ./onepiece.txt
4.1.5.2 ok
-ok 和 -exec 都是 find 命令的参数,使用方式类似,但是这个参数是交互式的,在处理 find 的结果的时候,会向用户发起询问,比如在删除搜索结果的时候,为了保险起见,就需要询问机制了。
语法格式如下:
# 语法: 其实就是将 -exec 替换为 -ok, 其他都不变
$ find 路径 参数 参数值 -ok shell命令2 {} \;
命令效果演示:
# 搜索到了2个满足条件的文件
$ find ./ -maxdepth 1 -name "*.txt"
./aaaaa.txt
./english.txt
# 查找并显示文件详细信息
$ find ./ -maxdepth 1 -name "*.txt" -ok ls -l {} \;
< ls ... ./aaaaa.txt > ? y # 同意显示文件详细信息
-rw-rw-r-- 1 robin robin 10 Apr 17 11:34 ./aaaaa.txt
< ls ... ./english.txt > ? n # 不同意显示文件详细信息, 会跳过显示该条信息
# 什么时候需要交互呢? ---> 删除文件的时候
$ find ./ -maxdepth 1 -name "*.txt" -ok rm -rf {} \;
< rm ... ./aaaaa.txt > ? y # 同意删除
< rm ... ./english.txt > ? n # 不同意删除
# 删除一个文件之后再次进行相同的搜索
$ find ./ -maxdepth 1 -name "*.txt"
./english.txt # 只剩下了一个.txt 文件
4.1.5.3 xargs
在使用 find 的 -exec 参数的时候,需要在指定的子命令尾部添加几个特殊字符 {} \;,一不小心就容易写错,有一种看起来更加直观、书写更加简便的方式,我们可以使用 xargs 替换掉 -exec 参数,而且在处理数据的时候 xargs更高效。有了 xargs 的加持我们就可以在 find 命令中直接使用管道完成前后命令的数据传递,使用方法如下:
# 在find 中 使用 xargs 关键字我们就可以使用管道了, 否则使用管道也不会起作用
# 将 find 搜索的结果通过管道传递给后边的shell命令继续处理
$ find 路径 参数 参数值 | xargs shell命令2
命令效果演示:
# 查找文件
$ find ./ -maxdepth 1 -name "*.cpp"
./occi.cpp
./main.cpp
./test.cpp
# 查找文件, 并且显示文件的详细信息
robin@OS:~$ find ./ -maxdepth 1 -name "*.cpp" | xargs ls -l
-rw-r--r-- 1 robin robin 2223 Mar 2 2020 ./main.cpp
-rw-r--r-- 1 robin robin 1406 Mar 2 2020 ./occi.cpp
-rw-r--r-- 1 robin robin 2015 Mar 1 2020 ./test.cpp
# xargs的效率比使用 -exec 效率高
-exec: 将find查询的结果逐条传递给后边的shell命令
-xargs: 将find查询的结果一次性传递给后边的shell命令
4.2 grep
和 find 不同 grep 命令用于查找文件里符合条件的字符串。grep 命令中有几个常用参数,下面介绍一下:
-r: 如果需要搜索目录中的文件内容,需要进行递归操作,必须指定该参数 -i: 对应要搜索的关键字,忽略字符大小写的差别 -n: 在显示符合样式的那一行之前,标示出该行的列数编号
# 语法格式:
$ grep "搜索的内容" 搜索的路径/文件 参数
对应要搜索的文件内容,建议放到引号中,因为关键字中可能有特殊字符,或者有空格,从而导致解析错误。关于引号, 单双都可以,可根据自己的需求选择。
搜索举例:
# 搜索指定文件中是否有字符串 include
[root@VM-8-14-centos ~]# grep "include" a.c
#include <stdio.h>
#include <unistd.h>
#include <fcntl.h>
# 不区分大小写进行搜索
[root@VM-8-14-centos ~]# grep "INCLUDE" a.c
[root@VM-8-14-centos ~]# grep "INCLUDE" a.c -i
#include <stdio.h>
#include <unistd.h>
#include <fcntl.h>
# 搜索指定目录中哪些文件中包含字符串 include 并且显示关键字所在的行号
[root@VM-8-14-centos ~]# grep "include" ./ -rn
./a.c:1:#include <stdio.h>
./a.c:2:#include <unistd.h>
./a.c:3:#include <fcntl.h>
./luffy/get/e.c:1:#include <stdio.h>
./luffy/get/e.c:2:#include <unistd.h>
./luffy/get/e.c:3:#include <fcntl.h>
./luffy/c.c:1:#include <stdio.h>
./luffy/c.c:2:#include <unistd.h>
./luffy/c.c:3:#include <fcntl.h>
./ace/b.c:1:#include <stdio.h>
./ace/b.c:2:#include <unistd.h>
./ace/b.c:3:#include <fcntl.h>
./.bash_history:1449:grep "include" ./
./.bash_history:1451:grep "include" ./ -r
./.bash_history:1465:grep "include" a.c
4.3locate
我们可以将 locate 看作是一个简化版的 find, 使用这个命令我们可以根据文件名搜索本地的磁盘文件 , 但是 locate的效率比find要高很多。原因在于它不搜索具体目录,而是搜索一个本地的数据库文件,这个数据库中含有本地所有文件信息。Linux 系统自动创建这个数据库,并且每天自动更新一次,所以使用 locate 命令查不到最新变动过的文件。为了避免这种情况,可以在使用locate之前,先使用updatedb命令,手动更新数据库。
# 使用管理员权限更新本地数据库文件, root用户这样做
$ updatedb
# 非root用户需要加 sudo
$ sudo updatedb
locate 有一些常用参数,使用之前先来介绍一下:
1.搜索所有目录下以某个关键字开头的文件
$ locate test # 搜索所有目录下以 test 开头的文件
2.$ locate test # 搜索所有目录下以 test 开头的文件
$ locate /home/robin/test # 指定搜索目录为 /home/robin/, 文件以 test 开头
3.$ locate /home/robin/test # 指定搜索目录为 /home/robin/, 文件以 test 开头
$ locate TEST -i # 文件名以小写的test为前缀的文件也能被搜索到
4.列出前 N 个匹配到的文件名称或路径名称,使用参数 -n
$ locate test -n 5 # 搜索文件前缀为 test 的文件, 并且只显示5条信息
5.基于正则表达式进行文件名匹配,查找符合条件的文件,使用参数 -r
# 使用该参数, 需要有正则表达式基础
$ locate -r "\.cpp$" # 搜索以 .cpp 结尾的文件
正则表达式小科普:
-
在正则表达式中 . 可以匹配任意一个 非 \n 的单字符
-
上边的命令中使用转译字符 \ 对特殊字符. 转译,就得到了普通的字符.
-
在正则表达式中 $ 放到字符尾部,表示字符串必须以这个字符结尾,上边的命令中修饰的是字符 p
-
正则表达式中的 字符 c 和后边的字符 p 需要进行字节匹配,没有特殊含义
-
通过上面的解释就能明白 .cpp$ 说的就是以 .cpp 结尾的字符串
5.Vim的使用
Vim 是 Linux 操作系统中一款功能强大的文本编辑器,支持安装各种插件。但是 vim 和 windows 中的文件编辑器所不同的是它没有 UI 界面,所有的操作都是通过键盘快捷键操作完成的,因此要想熟练使用 vim 在 Linux 中进行文本编辑是有成本的,需要花费一定的时间去练习。如果我们拿到了一个纯净版的 Linux, 里边是没有 vim 的,但是有一个类似的文本编辑器叫做 Vi。vi 编辑器的功不是很强,可以这样理解 vim 就是 vi 的增强版。
5.1 vim 的模式
在 vim 中一共有三种模式,分别是 命令模式 , 末行模式 , 编辑模式,当我们打开 vim 之后默认进入的是命令模式。
命令模式:在该模式下我们可以进行查看文件内容 , 修改文件 , 关键的搜索等操作。 编辑模式:在该模式下主要对文件内容进行修改和内容添加。 末行模式:在该模式下可以进行 执行Linux命令 , 保存文件 , 进行行的跳转 , 窗口分屏等操作。 介绍的以上三种模式之间是可以相互切换的:
命令模式 -> 编辑模式 -> 命令模式 命令模式 -> 末行模式 -> 命令模式 编辑模式和末行模式之间是不能相互直接切换的
5.2 命令模式下的操作
通过 vim 打开一个文件,如果文件不存在,在退出的时候进行了保存,文件就会被创建出来
# 打开一个文件
$ vim 文件名
5.2.1 保存退出
直接在键盘上操作,通过键盘按两个连续的大写的 Z (此处是大写的Z, 需要先按住 Shift 再操作哦)
# 先按住 shift 键, 然后连续按两次 z
ZZ
5.2.2 代码格式化
在编码过程中,为了便于阅读和代码维护,代码都需要按照样式对齐,如果代码格式凌乱,可以在命令模式下快速进行代码的格式化,让其看起来更加美观,这个操作需要在键盘上连续输入多个字符。
# 假设写的c/c+代码没有对齐, 通过该命令可以对齐代码
# 一定要注意最后一个字符是 大写的 G, 因此需要先按 shift
gg=G
5.2.3 光标移动
在 vim 中可以使用键盘上的方向键 (↑, ↓, ←, →) 来移动光标,这种操作相对比较麻烦, 有一种更加简便的操作方式, 就是使用键盘上的 h, j, k, l。
# 标准的移动光标的方法: 使用 h, j, k, l
光标上移
↑
|
光标左移 <-- h j k l --> 光标右移
|
↓
光标下移
除此之外我们还可以使用一些快捷键实现光标的快速跳转,常用的有:
5.2.4 删除命令
在 vim 中是没有删除操作的,其实所谓的删除就是剪切,被删除的数据都可被粘贴到文档的任意位置,即便如此我们还是习惯性的将剪切操作称之为删除,常用的删除操作如下表所示:
5.2.5撤销和反撤销
撤销和反撤销对应 windows 中的 ctrl+z 和 ctrl+y, 但是在 vim 中使用这两个快捷键是不行的。
5.2.6 复制和粘贴
前边已经介绍了,在 vim 中做删除操作就相当于剪切,剪切或者复制之后的数据都可以用来做粘贴操作,在 vim 中对应的快捷键如下:
5.2.7 可视模式
在编辑文件的过程中,有时候需要删除或者需要复制的数据不整行的,而是一行中的某一部分,这时候可以使用可视模式进行文本的选择,之后再通过相关的快捷键对所选中的数据块进行复制或者删除操作。
有三种方式可以切换到可视模式:
v: 进入的字符可视化模式(Characterwise visual mode),文本选择是以字符为单位的。 V :进入的行可视化模式(Linewise visual mode),文本选择是以行为单位的。 ctrl-v: 进入的块可视化模式(Blockwise visual mode),可以选择一个矩形内的文本。 进入到可视模式之后,就可以进行文本块的选择和复制以及删除了
(1)字符可视模式
控制光标方向用来选择文件中的不规则数据块,可以对选中的文本信息进行复制和删除
# 进入到字符可视模式,直接在键盘上按 v 即可:
v
通过 v 切换到字符可视模式之后, 在窗口的最下方会看到 -- VISUAL-- 字样。
(2) 行可视模式
向下移动光标可以选择一整行,向上移动光标可以取消整行选择
# 进入行可视模式, 键盘上按 shift+v
V
通过 V 切换到行可视模式之后, 在窗口的最下方会看到 -- VISUAL LINE -- 字样。
(3) 块可视化模式
通过向上,下移动光标控制矩形文本块的高度,通过向左,右移动光标控制矩形文本块的宽度。
# 进入块可视模式, 选择一个矩形文本块
ctrl+v
通过 ctrl+v 切换到块可视模式之后, 在窗口的最下方会看到 -- VISUAL BLOCK -- 字样。
(4) 代码注释
代码块注释可以使用块可视模式,具体操作步骤如下:
1.通过 ctrl+v 进入块可视模式
2.移动光标上移(k)或者下移(j),选中多个代码行的开头,如下图所示
3.选择完毕后,按大写的的 I 键,此时下方会提示进入 “insert” 模式,输入你要插入的注释符,例如: //
4.最后按 ESC 键,你就会发现选中的多行代码已经被注释了
删除多行注释的方法,同样 Ctrl+v 进入列选择模式,移动光标把要删除的注释符选中,按下 d,注释就被删除了。
5.2.8 替换
命令模式下的替换功能并不强,常用于某个单字符的替换。
5.2.9 查找
在 vim 的命令模式下一共有三种查找方式,首先需要在键盘上输入对应的字符,然后按回车键 vim 会进行关键字匹配,之后就可以通过 n 或者 N 进行关键字之间的切换了。
关于 ? 和 # 都需要使用组合键,这点要注意一下。
下面总结一下这三种搜索方式:
1.使用 / 或者 ? 搜索效果一样,只是遍历关键字的时候的顺序是相反的 2.使用 # 必须先从被搜索的文件中找到要搜索的关键字,好处就是搜索的内容不需要通过键盘输入 3.以上两种搜索方式各有优劣,请根据实际情况选择使用。
5.2.10 查看 man 文档
man 文档,是 Linux 中默认自带的帮助文档,作为程序猿可以通过这个文档来查询 shell 命令或者标准 API 函数或者系统自带的配置文件格式的说明等信息。
man 文档一共有 9 个章节, 具体如下:
# 打开 man 文档首页
$ man man
# 退出 man 文档,直接按键盘上的 q 即可
q
那么,我们如何通过 man 文档查询相关的 shell 命令或者函数等信息呢?
# 下边举几个例子:
# 查询第一章的shell命令
$ man 1 cp
# 查询第二章的系统函数 (如: read, write, open 等)
$ man 2 read
# 查询第三章的标准的库函数 (如: fread, fwrite, fopen 等)
$ man 3 fread
# 查询第五章的特殊的配置文件说明, 比如: /etc/passwd 或者 /etc/group
$ man 5 passwd
查询的时候章节号是可以省略的,只是查到的结果不精确。如果不写章节号,从第一章开始搜索查询的关键字,如果查询到了,直接就结束了。也就是说如果查询的是函数,但是这个函数和某个命令的名字是相同的,查询到第一章搜索就结束了。
如果当前是在 vim 的命令模式下,我们可以直接跳转到 man 文档:
-
找到要查看的函数,然后将光标放到该函数上
-
在键盘上依次输入:章节号 (可选) + K(shift+k)(大写的k),就会自动调整到 man 文档中了
5.2.11 切换到编辑模式
如果要编辑文件,需要从命令模式切换到文件编辑模式,切换模式的快捷键有很多,不同的快捷键对应的效果有所不同,效果如下表所示:
文件编辑完成之后,从编辑模式回到命令模式只需要按键盘上的 Esc 即可。
5.3 末行模式下的操作
5.3.1 命令模式到末行模式
从命令模式切换到末行模式只需要在键盘上输入一个 :,同时这个符号也会出现在窗口的最下端,这时候我们就可以在最后一行输入我们执行的命令了。
# 命令模式切换到末行模式
在命令模式下键盘输入一个 冒号 -> :
# 从末行模式 -> 命令模式
1. 按两次esc
2. 在末行模式下执行一个完整指令, 执行完毕, 自动回到命令模式
从末行模式切换回命令模式有两种方式:
-
按两次 Esc
-
在末行模式下执行一个完整指令,执行完毕,自动回到命令模式
5.3.2 保存退出
使用 vim 对文件编辑完成之后,需要保存或者退出 vim 一般都是在末行模式下完成的,不管是进行那种操作都有对应的操作命令,如下表:
5.3.3 替换
末行模式下的替换比命令模式下的替换功能要强大的多,在末行模式下可以指定将什么样的内容替换为什么样的内容,并且可以指定替换某一行或者某几行或者是全文替换。
替换对应的命令是 s 并且可以给其指定参数,默认情况下只替换相关行的第一个满足条件的关键字, 如果需要整行替换需要加参数 /g。
5.3.4 分屏
分屏就是将当前屏幕中的窗口以水平或者垂直的方式拆分成多个,在不同的子窗口中可以显示同一个文件或者不同文件中的内容,下边介绍一下相关的分屏命令:
除了在命令模式下分屏,我们也可以在使用 vim 打开文件的时候直接分屏,下边是需要用到的参数:
-o: 水平分屏 -o: 垂直分屏
# 在vim打开文件的时候指定打开多个文件和分屏方式
# 水平分屏
$ vim -o 文件1, 文件2, 文件3 ...
# 垂直分屏
$ vim -O 文件1, 文件2, 文件3 ...
5.3.5 行跳转
在 vim 中不仅可以在命令模式下进行行的跳转,也可以在末行模式下进行行跳转,末行模式下指定哪一行光标就可以跳转到哪一行。
:行号 # 输入完行号之后敲回车
5.3.6 执行 shell 命令
在使用 vim 编辑文件的过程中,还可以在末行模式下执行需要的 shell 命令,在执行 shell 命令之前需要在前边加上一个叹号 !。
# 语法:
:!shell命令
# 举例
:!ls # 回车即可
5.4 vim 配置文件
vim 是一个文本编辑器工具,这个工具也是有配置文件的,文件的名字叫做 vimrc,在里边可以设置样式,功能 , 快捷键等属性 。对应的配置文件分为两种 用户级别和系统级别。
-
用户级别的配置文件(~/.vimrc)只对当前用户有效
-
系统级别的配置文件(/ect/vim/vimrc)对所有 Linux 用户都有效
-
如果两个配置文件都设置了,用户级别的配置文件起作用(用户级别优先级高)
6.GCC
GCC 是 Linux 下的编译工具集,是 GNU Compiler Collection 的缩写,包含 gcc、g++ 等编译器。这个工具集不仅包含编译器,还包含其他工具集,例如 ar、nm 等。
GCC 工具集不仅能编译 C/C++ 语言,其他例如 Objective-C、Pascal、Fortran、Java、Ada 等语言均能进行编译。GCC 在可以根据不同的硬件平台进行编译,即能进行交叉编译,在 A 平台上编译 B 平台的程序,支持常见的 X86、ARM、PowerPC、mips 等,以及 Linux、Windows 等软件平台。
6.1 gcc 工作流程
GCC 编译器对程序的编译下图所示,分为 4 个阶段:预处理(预编译)、编译和优化、汇编和链接。GCC 的编译器可以将这 4 个步骤合并成一个。 先介绍一下每个步骤都分别做了些什么事儿:
预处理:在这个阶段主要做了三件事: 展开头文件 、宏替换 、去掉注释行。这个阶段需要 GCC 调用预处理器来完成,最终得到的还是源文件,文本格式。
编译:这个阶段需要 GCC 调用编译器对文件进行编译,最终得到一个汇编文件。
汇编:这个阶段需要 GCC 调用汇编器对文件进行汇编,最终得到一个二进制文件。
链接:这个阶段需要 GCC 调用链接器对程序需要调用的库进行链接,最终得到一个可执行的二进制文件。
在 Linux 下使用 GCC 编译器编译单个文件十分简单,直接使用 gcc 命令后面加上要编译的 C 语言的源文件,GCC 会自动生成文件名为 a.out 的可执行文件(也可以通过参数 -o 指定生成的文件名),也就是通过一个简单的命令上边提到的 4 个步骤就全部执行完毕了。但是如果想要单步执行也是没问题的, 下边基于这段示例程序给大家演示一下。
第一步:对源文件进行预处理,需要使用的 gcc 参数为 -E
# 1. 预处理, -o 指定生成的文件名
$ gcc -E test.c -o test.i
第二步:编译预处理之后的文件,需要使用的 gcc 参数为 -S
# 2. 编译, 得到汇编文件
$ gcc -S test.i -o test.s
第三步:对得到的汇编文件进行汇编,需要使用的 gcc 参数为 -c
# 3. 汇编
$ gcc -c test.s -o test.o
第四步:将得到的二进制文件和标准库进制链接,得到可执行的二进制文件,不需要任何参数
# 4. 链接
$ gcc test.o -o test
最后再次强调,在使用 gcc 编译程序的时候可以通过参数控制内部自动执行几个步骤:
# 参数 -c 是进行文件的汇编, 汇编之前的两步会自动执行
$ gcc test.c -c -o app.o
# 该命令是直接进行链接生成可执行程序, 链接之前的三步会自动执行
$ gcc test.c -o app
6.2 gcc 常用参数
下面的表格中列出了常用的一些 gcc 参数,这些参数在 gcc命令中没有位置要求,只需要编译程序的时候将需要的参数指定出来即可。
6.2.1 指定生成的文件名 (-o)
该参数用于指定原文件通过 gcc 处理之后生成的新文件的名字,有两种写法,原文件可以写在参数 -o 前边后缀写在后边。
# 参数 -o的用法 , 原材料 test.c 最终生成的文件名为 app
# test.c 写在 -o 之前
$ gcc test.c -o app
# test.c 写在 -o 之后
$ gcc -o app test.c
6.2.2 搜索头文件 (-I)
如果在程序中包含了一些头文件,但是包含的一些头文件在程序预处理的时候因为找不到无法被展开,导致程序编译失败,这时候我们可以在 gcc 命令中添加 -I 参数重新指定要引用的头文件路径,保证编译顺利完成。
# -I, 指定头文件目录
$ tree
.
├── add.c
├── div.c
├── include
│ └── head.h
├── main.c
├── mult.c
└── sub.c
# 编译当前目录中的所有源文件,得到可执行程序
$ gcc *.c -o calc
main.c:2:18: fatal error: head.h: No such file or directory
compilation terminated.
sub.c:2:18: fatal error: head.h: No such file or directory
compilation terminated.
通过编译得到的错误信息可以知道,源文件中包含的头文件无法被找到。通过提供的目录结构可以得知头文件 head.h 在 include 目录中,因此可以在编译的时候重新指定头文件位置,具体操作如下:
# 可以在编译的时候重新指定头文件位置 -I 头文件目录
$ gcc *.c -o calc -I ./include
6.2.3 指定一个宏 (-D)
在程序中我们可以使用宏定义一个常量,也可以通过宏控制某段代码是否能够被执行。在下面这段程序中第 8 行判断是否定义了一个叫做 DEBUG 的宏,如果没有定义第 9 行代码就不会被执行了,通过阅读代码能够知道这个宏是没有在程序中被定义的。
// test.c
#include <stdio.h>
#define NUMBER 3
int main()
{
int a = 10;
#ifdef DEBUG
printf("我是一个程序猿, 我不会爬树...\n");
#endif
for(int i=0; i<NUMBER; ++i)
{
printf("hello, GCC!!!\n");
}
return 0;
}
如果不想在程序中定义这个宏, 但是又想让它存在,通过 gcc 的参数 -D 就可以实现了,编译器会认为参数后边指定的宏在程序中是存在的。
# 在编译命令中定义这个 DEBUG 宏,
$ gcc test.c -o app -D DEBUG
# 执行生成的程序, 可以看到程序第9行的输出
$ ./app
我是一个程序猿, 我不会爬树...
hello, GCC!!!
hello, GCC!!!
hello, GCC!!!
-D 参数的应用场景: 在发布程序的时候,一般都会要求将程序中所有的 log 输出去掉,如果不去掉会影响程序的执行效率,很显然删除这些打印 log 的源代码是一件很麻烦的事情,解决方案是这样的:
-
将所有的打印 log 的代码都写到一个宏判定中,可以模仿上边的例子
-
在编译程序的时候指定 -D 就会有 log 输出
-
在编译程序的时候不指定 -D, log 就不会输出
6.3 多文件编译
GCC 可以自动编译链接多个文件,不管是目标文件还是源文件,都可以使用同一个命令编译到一个可执行文件中。
6.3.1 多文件编译
GCC 可以自动编译链接多个文件,不管是目标文件还是源文件,都可以使用同一个命令编译到一个可执行文件中。
-
头文件
#ifndef _STRING_H_
#define _STRING_H_
int strLength(char *string);
#endif // _STRING_H_
-
源文件 string.c
#include "string.h"
int strLength(char *string)
{
int len = 0;
while(*string++ != '\0') // 当*string 的值为'\0'时, 停止计算
{
len++;
}
return len; // 返回字符串长度
}
-
源文件 main.c
#include <stdio.h>
#include "string.h"
int main(void)
{
char *src = "Hello, I'am Monkey·D·Luffy!!!";
printf("string length is: %d\n", strLength(src));
return 0;
}
6.3.2 编译运行
因为头文件是包含在源文件中的,因此在使用 gcc 编译程序的时候不需要指定头文件的名字(在头文件无法被找到的时候需要使用参数 -I 指定其具体路径而不是名字)。我们可以通过一个 gcc 命令将多个源文件编译并生成可执行程序,也可以分多步完成这个操作。
-
直接链接生成可执行程序
# 直接生成可执行程序 test
$ gcc -o test string.c main.c
# 运行可执行程序
$ ./test
-
先将源文件编成目标文件,然后进行链接得到可执行程序
# 汇编生成二进制目标文件, 指定了 -c 参数之后, 源文件会自动生成 string.o 和 main.o
$ gcc –c string.c main.c
# 链接目标文件, 生成可执行程序 test
$ gcc –o test string.o main.o
# 运行可执行程序
$ ./test
6.4 gcc 与 g++
关于对 gcc 和 g++ 很多人的理解都是比较片面的或者是对二者的理解有一些误区,下边从三个方面介绍一下二者的区别:
1.在代码编译阶段(第二个阶段):
-
后缀为 .c 的,gcc 把它当作是 C 程序,而 g++ 当作是 C++ 程序
-
后缀为.cpp 的,两者都会认为是 C++ 程序,C++ 的语法规则更加严谨一些
-
g++ 会调用 gcc,对于 C++ 代码,两者是等价的,也就是说 gcc 和 g++ 都可以编译 C/C++ 代码
2.在链接阶段(最后一个阶段):
-
gcc 和 g++ 都可以自动链接到标准 C 库
-
g++ 可以自动链接到标准 C++ 库,gcc 如果要链接到标准 C++ 库需要加参数 -lstdc++
3.关于 __cplusplus 宏的定义
-
g++ 会自动定义__cplusplus 宏,但是这个不影响它去编译 C 程序
-
gcc 需要根据文件后缀判断是否需要定义 __cplusplus 宏 (规则参考第一条)
综上所述:
-
不管是 gcc 还是 g++ 都可以编译 C 程序,编译程序的规则和参数都相同
-
g++ 可以直接编译 C++ 程序, gcc 编译 C++ 程序需要添加额外参数 -lstdc++
-
不管是 gcc 还是 g++ 都可以定义 __cplusplus 宏
# 编译 c 程序
$ gcc test.c -o test # 使用gcc
$ g++ test.c -o test # 使用g++
# 编译 c++ 程序
$ g++ test.cpp -o test # 使用g++
$ gcc test.cpp -lstdc++ -o test # 使用gcc
7.静态库和动态库(mxw)
不管是 Linux 还是 Windows 中的库文件其本质和工作模式都是相同的,只不过在不同的平台上库对应的文件格式和文件后缀不同。程序中调用的库有两种静态库和动态库,不管是哪种库文件本质是还是源文件,只不过是二进制格式只有计算机能够识别,作为一个普通人就无能为力了。
在项目中使用库一般有两个目的,一个是为了使程序更加简洁不需要在项目中维护太多的源文件,另一方面是为了源代码保密,毕竟不是所有人都想把自己编写的程序开源出来。
当我们拿到了库文件(动态库、静态库)之后要想使用还必须有这些库中提供的 API 函数的声明,也就是头文件,把这些都添加到项目中,就可以快乐的写代码了。
7.1 静态库
在 Linux 中静态库由程序 ar 生成,现在静态库已经不像之前那么普遍了,这主要是由于程序都在使用动态库。关于静态库的命名规则如下:
在 Linux 中静态库以 lib 作为前缀,以.a 作为后缀,中间是库的名字自己指定即可,即: libxxx.a 在 Windows 中静态库一般以 lib 作为前缀,以 lib 作为后缀,中间是库的名字需要自己指定,即: libxxx.lib
7.1.1 生成静态链接库
生成静态库,需要先对源文件进行汇编操作 (使用参数 -c) 得到二进制格式的目标文件 (.o 格式), 然后在通过 ar 工具将目标文件打包就可以得到静态库文件了 (libxxx.a)。
使用 ar 工具创建静态库的时候需要三个参数:
-
参数c:创建一个库,不管库是否存在,都将创建。
-
参数s:创建目标文件索引,这在创建较大的库时能加快时间。
-
参数r:在库中插入模块 (替换)。默认新的成员添加在库的结尾处,如果模块名已经在库中存在,则替换同名的模块。
生成静态链接库的具体步骤如下:
1.需要将源文件进行汇编,得到 .o 文件,需要使用参数 -c
# 执行如下操作, 默认生成二进制的 .o 文件
# -c 参数位置没有要求
$ gcc 源文件(*.c) -c
2.将得到的 .o 进行打包,得到静态库
$ ar rcs 静态库的名字(libxxx.a) 原材料(*.o)
3.发布静态库
# 发布静态库
1. 提供头文件 **.h
2. 提供制作出来的静态库 libxxx.a
7.2 动态库
动态链接库是程序运行时加载的库,当动态链接库正确部署之后,运行的多个程序可以使用同一个加载到内存中的动态库,因此在 Linux 中动态链接库也可称之为共享库。
动态链接库是目标文件的集合,目标文件在动态链接库中的组织方式是按照特殊方式形成的。库中函数和变量的地址使用的是相对地址(静态库中使用的是绝对地址),其真实地址是在应用程序加载动态库时形成的。
关于动态库的命名规则如下:
-
在 Linux 中动态库以 lib 作为前缀,以.so 作为后缀,中间是库的名字自己指定即可,即: libxxx.so
-
在 Windows 中动态库一般以 lib 作为前缀,以 dll 作为后缀,中间是库的名字需要自己指定,即: libxxx.dll
7.2.1 生成动态链接库
生成动态链接库是直接使用 gcc 命令并且需要添加 -fPIC(-fpic) 以及 -shared 参数。
-
-fPIC 或 -fpic 参数的作用是使得 gcc 生成的代码是与位置无关的,也就是使用相对位置。
-
-shared参数的作用是告诉编译器生成一个动态链接库。
生成动态链接库的具体步骤如下:
1.将源文件进行汇编操作,需要使用参数 -c, 还需要添加额外参数 -fpic /-fPIC
# 得到若干个 .o文件
$ gcc 源文件(*.c) -c -fpic
2.将得到的.o 文件打包成动态库,还是使用 gcc, 使用参数 -shared 指定生成动态库 (位置没有要求)
$ gcc -shared 与位置无关的目标文件(*.o) -o 动态库(libxxx.so)
3.发布动态库和头文件
# 发布
1. 提供头文件: xxx.h
2. 提供动态库: libxxx.so
7.3 优缺点
7.3.1 静态库
优点:
-
静态库被打包到应用程序中加载速度快
-
发布程序无需提供静态库,移植方便
缺点:
-
相同的库文件数据可能在内存中被加载多份,消耗系统资源,浪费内存。
-
库文件更新需要重新编译项目文件,生成新的可执行程序,浪费时间。
7.3.2 动态库
优点:
-
可实现不同进程间的资源共享
-
动态库升级简单,只需要替换库文件,无需重新编译应用程序
-
程序猿可以控制何时加载动态库,不调用库函数动态库不会被加载
缺点:
-
加载速度比静态库慢,以现在计算机的性能可以忽略
-
发布程序需要提供依赖的动态库
8.Makefile(mxw)
使用 GCC 的命令行进行程序编译在单个文件下是比较方便的,当工程中的文件逐渐增多,甚至变得十分庞大的时候,使用 GCC 命令编译就会变得力不从心。这种情况下我们需要借助项目构造工具 make 帮助我们完成这个艰巨的任务。 make 是一个命令工具,是一个解释 makefile 中指令的命令工具,一般来说,大多数的 IDE 都有这个命令,比如:Visual C++ 的 nmake,QtCreator 的 qmake 等。
make 工具在构造项目的时候需要加载一个叫做 makefile 的文件,makefile 关系到了整个工程的编译规则。一个工程中的源文件不计数,其按类型、功能、模块分别放在若干个目录中,makefile 定义了一系列的规则来指定哪些文件需要先编译,哪些文件需要后编译,哪些文件需要重新编译,甚至于进行更复杂的功能操作,因为 makefile 就像一个 Shell 脚本一样,其中也可以执行操作系统的命令。
makefile 带来的好处就是 ——“自动化编译”,一旦写好,只需要一个 make 命令,整个工程完全自动编译,极大的提高了软件开发的效率。
makefile 文件有两种命名方式 makefile 和 Makefile,构建项目的时候在哪个目录下执行构建命令 make 这个目录下的 makefile 文件就会别加载,因此在一个项目中可以有多个 makefile 文件,分别位于不同的项目目录中。
8.1 规则
Makefile 的框架是由规则构成的。make 命令执行时先在 Makefile 文件中查找各种规则,对各种规则进行解析后运行规则。规则的基本格式为:
# 每条规则的语法格式:
target1,target2...: depend1, depend2, ...
command
......
......
每条规则由三个部分组成分别是目标(target), 依赖(depend) 和命令(command)。
命令(command): 当前这条规则的动作,一般情况下这个动作就是一个 shell 命令
例如:通过某个命令编译文件、生成库文件、进入目录等。 动作可以是多个,每个命令前必须有一个Tab缩进并且独占占一行。
依赖(depend): 规则所必需的依赖条件,在规则的命令中可以使用这些依赖。
例如:生成可执行文件的目标文件(*.o)可以作为依赖使用 如果规则的命令中不需要任何依赖,那么规则的依赖可以为空 当前规则中的依赖可以是其他规则中的某个目标,这样就形成了规则之间的嵌套 依赖可以根据要执行的命令的实际需求,指定很多个
目标(target): 规则中的目标,这个目标和规则中的命令是对应的
通过执行规则中的命令,可以生成一个和目标同名的文件 规则中可以有多个命令,因此可以通过这多条命令来生成多个目标,所有目标也可以有很多个 通过执行规则中的命令,可以只执行一个动作,不生成任何文件,这样的目标被称为伪目标 关于上面的解释可能有些晦涩,下面通过一个例子来阐述一下:
关于上面的解释可能有些晦涩,下面通过一个例子来阐述一下:
# 举例: 有源文件 a.c b.c c.c head.h, 需要生成可执行程序 app
################# 例1 #################
app:a.c b.c c.c
gcc a.c b.c c.c -o app
################# 例2 #################
# 有多个目标, 多个依赖, 多个命令
app,app1:a.c b.c c.c d.c
gcc a.c b.c -o app
gcc c.c d.c -o app1
################# 例3 #################
# 规则之间的嵌套
app:a.o b.o c.o
gcc a.o b.o c.o -o app
# a.o 是第一条规则中的依赖
a.o:a.c
gcc -c a.c
# b.o 是第一条规则中的依赖
b.o:b.c
gcc -c b.c
# c.o 是第一条规则中的依赖
c.o:c.c
gcc -c c.c
8.2 工作原理
在此主要为大家剖析一下通过提供的 makefile 文件,构建工具 make 什么时候编译项目中的所有文件,什么时候只选择更新项目中的某几个文件。另外再研究一下如果 makefile 里边有多个规则它们之间是如何配合工作的,我们基于下边的例子,依次进行讲解。
8.2.1 工作原理
在此主要为大家剖析一下通过提供的 makefile 文件,构建工具 make 什么时候编译项目中的所有文件,什么时候只选择更新项目中的某几个文件。另外再研究一下如果 makefile 里边有多个规则它们之间是如何配合工作的,我们基于下边的例子,依次进行讲解。
(1)规则的执行
在调用 make 命令编译程序的时候,make 会首先找到 Makefile 文件中的第 1 个规则,分析并执行相关的动作。但是需要注意的是,好多时候要执行的动作(命令)中使用的依赖是不存在的,如果使用的依赖不存在,这个动作也就不会被执行。
对应的解决方案是先将需要的依赖生成出来,我们就可以在 makefile 中添加新的规则,将不存在的依赖作为这个新的规则中的目标,当这条新的规则对应的命令执行完毕,对应的目标就被生成了,同时另一条规则中需要的依赖也就存在了。
这样,makefile 中的某一条规则在需要的时候,就会被其他的规则调用,直到 makefile 中的第一条规则中的所有的依赖全部被生成,第一条规则中的命令就可以基于这些依赖生成对应的目标,make 的任务也就完成了。
9.GDB调试(mxw)
gdb 是由 GNU 软件系统社区提供的调试器,同 gcc 配套组成了一套完整的开发环境,可移植性很好,支持非常多的体系结构并被移植到各种系统中(包括各种类 Unix 系统与 Windows 系统里的 MinGW 和 Cygwin )。此外,除了 C 语言之外,gcc/gdb 还支持包括 C++、Objective-C、Ada 和 Pascal 等各种语言后端的编译和调试。 gcc/gdb 是 Linux 和许多类 Unix 系统中的标准开发环境,Linux 内核也是专门针对 gcc 进行编码的。 GDB 是一套字符界面的程序集,可以使用命令 gdb 加载要调试的程序。 下面为大家介绍一些常用的 GDB 调试命令。
9.1 调试准备
项目程序如果是为了进行调试而编译时, 必须要打开调试选项 (-g)。另外还有一些可选项,比如:在尽量不影响程序行为的情况下关掉编译器的优化选项 (-O0),-Wall 选项打开所有 warning,也可以发现许多问题,避免一些不必要的 bug。
-g 选项的作用是在可执行文件中加入源代码的信息,比如可执行文件中第几条机器指令对应源代码的第几行,但并不是把整个源文件嵌入到可执行文件中,所以在调试时必须保证 gdb 能找到源文件。
习惯上如果是 c程序就使用 gcc 编译,如果是 c++ 程序就使用 g++ 编译,编译命令中添加上边提到的参数即可。
假设有一个文件 args.c, 要对其进行gdb调试,编译的时候必须要添加参数 -g,加入了源代码信息的可执行文件比不加之前要大一些。
# -g 将调试信息写入到可执行程序中
$ gcc -g args.c -o app
# 编译不添加 -g 参数
$ gcc args.c -o app1
# 查看生成的两个可执行程序的大小
$ ll
-rwxrwxr-x 1 robin robin 9816 Apr 19 09:25 app* # 可以用于gdb调试
-rwxrwxr-x 1 robin robin 8608 Apr 19 09:25 app1* # 不能用于gdb调试
9.2 启动和退出 gdb
9.2.1 启动 gdb
gdb 是一个用于应用程序调试的进程,需要先将其打开,一定要注意 gdb进程启动之后, 需要的被调试的应用程序是没有执行的。打开 Linux 终端,切换到要调试的可执行程序所在路径,执行如下命令就可以启动 gdb 了。
# 在终端中执行如下命令
# gdb程序启动了, 但是可执行程序并没有执行
$ gdb 可执行程序的名字
# 使用举例:
$ gdb app
(gdb) # gdb等待输入调试的相关命令
9.2.2 命令行传参
有些程序在启动的时候需要传递命令行参数,如果要调试这类程序,这些命令行参数必须要在应用程序启动之前通过调试程序的 gdb 进程传递进去。下面是一段带命令行参数的程序: