V5.0
王道C++班级参考资料<br />——<br />Linux部分卷1Linux基础<br/>节3常用shell命令<br/><br/>最新版本V5.0
<br>王道C++团队<br/>COPYRIGHT ⓒ 2021-2024. 王道版权所有概述Shell命令的组成man命令关机和重启命令用户子系统命令Linux系统用户分类激活root用户查看所有用户useradd添加用户userdel删除用户passwd设置密码su切换用户exit退出当前用户总结Linux文件管理系统(重点)Linux文件管理系统的特点Linux虚拟文件系统硬链接和硬链接数inode的概念目录相关命令pwd查看当前工作目录cd改变当前工作目录mkdir创建新目录rmdir删除空目录通配符ls查看目录/文件的内容信息(重要)-a 选项-i选项-l选项(重点)tree以树状结构显示目录内容cp复制文件或目录mv移动文件和目录rm删除文件和目录指令起别名改变文件权限指令目录究竟是个什么东西?目录权限普通文件权限chmod指令文字设定法数字设定法补充:文件权限掩码文件相关命令创建文件which查找可执行文件find命令(重要)cat查看文件内容head和tail查看文件内容首尾less查看文件内容重定向指令grep搜索文件内容(重点)正则表达式(重要)基本单位基本操作基本单位出现的位置grep使用正则表达式grep课堂练习命令的组合(重要)管道管道 "|" 应用举例管道 "| xargs" 应用举例"| xargs"的原理(扩展)创建硬/软链接创建硬链接创建软链接远程复制指令(了解)归档压缩指令(了解)The End
Gn!
shell是用户和Linux系统交互的接口,也是程序员和系统内核交互的手段之一。
在本小节我们将学习一些基础的、常用的shell命令,以实现基本的功能。作为程序员(而不是运维),并不需要将所有的shell命令掌握,鉴于时间有限,建议大家先把该节列出的shell命令学会。
Gn!
Shell指令通常由几个基本的组成部分构成,这些部分共同定义了它们的行为和执行的方式。一个Shell命令的大体由以下几部分组成:
命令(Command):
这是Shell指令的核心部分,必不可少。它指定要执行的操作,比如
ls
、mv
、cp
、mkdir
等。命令可以是shell进程本身内置的shell命令,也可以是外部程序。最常见的shell内置命令就是
cd
,外部程序则多了(which命令就用于查找外部程序)。以C语言程序员的视角看,命令其实就是一个可执行程序的名字,它表示会实现某个功能。
选项(Options):
选项通常以单个连字符
-
开始(短选项),或者以两个连字符--
开始(长选项,长选项大多可以省略为短选项)。选项不能单独存在,要和命令一起连用,用于改变命令的最终行为。
例如,
ls -l
中的-l
是一个选项,它告诉ls
命令以长格式列出目录内容。以C语言程序员的视角看,选项其实就是一个可执行程序启动时的特殊命令行参数,用于修改可执行程序的最终行为。
参数(Arguments):
参数是传递给命令的额外数据。
例如,在
cp source.txt dest.txt
命令中,source.txt
和dest.txt
是参数,它们指定了cp
命令的源文件和目标文件。以C语言程序员的视角看,参数其实就是一个可执行程序启动时所需要的参数,用于传递可执行程序执行时所需要的数据。
对于大多数Shell指令而言,就仅有上述三部分组成,而一些shell命令可能还会多以下组成部分:
重定向(Redirection):
重定向用于改变命令的标准输入和输出。
使用
>
、<
或>>
等符号可以将命令的输出发送到文件,或者从文件中获取输入。例如,
ls > files.txt
会将ls
命令的输出保存到files.txt
文件中。管道(Pipes):
管道符号
|
用于将一个命令的输出直接作为另一个命令的输入。例如,
cat file.txt | grep "search string"
会查找file.txt
中包含"search string"的所有行。重定向和管道都是比较重要的指令组成部分,在本章节的后面我们将学习它们。
关于内置指令和外部可执行程序的补充
Linux的默认shell版本是bash,shell命令当中有些是bash进程的内建指令,有些则是外部可执行程序。
内建指令比较常见的有:
alias
- 定义或显示别名。
cd
- 更改当前工作目录。
echo
- 显示一行文本。
exit
- 退出 shell。
history
- 显示命令历史记录。
pwd
- 打印工作目录。
type
- 显示一个命令的类型(是内置命令、外部程序还是别名)。对于一个shell指令,可以使用
type
指令来确定,如下图所示:当然我们还可以用
which
来查询可执行程序的位置,比如:这说明像
echo
和pwd
这样的shell指令,它们既有内建版本,也具有外部应用程序版本。在这种情况下,Bash 通常会优先使用它们的内置版本,因为内置版本执行更快且直接与 shell 的内部结构集成。除了上述补充外,这里再补充一个关于一个shell命令中——空格,双引号,单引号的作用:
shell命令中空格,双引号以及单引号的作用
在shell命令中,空格、双引号和单引号各自有着重要的作用:
空格(或多个空格):
空格在shell命令中通常用作参数或命令之间的分隔符。
例如,在命令
ls -l /etc
中,空格分隔了命令ls
、选项-l
和目录参数/etc
。如果参数中包含空格,则必须使用引号将整个参数括起来,否则shell会将其作为多个参数解释。
实际上,大多数shell命令背后就是一个C可执行程序,既然是这样,shell命令里的空格就类似于C可执行程序里输入多个命令行参数时的空格。
双引号 (
"..."
):
双引号用于创建包含空格、制表符或换行等特殊字符的参数或变量值。
当你想在参数中包含这些特殊字符时,可以使用双引号来确保shell命令正确理解它们。
例子1:当你想在终端输出"hello world!"时,由于该字符串包含一个空格,所以需要用双引号或单引号括起来。这个指令可以写作:
echo "hello world!"
也可以是
echo 'hello world!'
那么双引号和单引号有什么区别呢?一个比较常用的区别是:双引号可以用于识别shell的内置变量。
指令
cd -
的执行需要依赖Linux的系统环境变量env
当中的变量OLDPWD
你可以用
echo "$OLDPWD"
输出当前该变量的取值,但echo '$OLDPWD'
会向终端输出字符串"$OLDPWD",因为单引号内的内容会被视为字面量字符串。单引号 (
'...'
):
单引号的作用类似于双引号,它们也用于确保字符串中的空格被正确处理,不被shell分割。
与双引号不同的是,单引号内的内容会被视为字面量字符串,shell不会进行任何变量替换或命令替换。
例如,
echo '$VARIABLE'
会原样输出字符串$VARIABLE
,而不是输出变量VARIABLE
的值。简单来说,空格用来分隔命令的不同部分,双引号用来防止分隔带有空格的字符串并允许输出变量的取值,单引号用来防止分隔带有空格的字符串并且引号内的内容都视为字符串处理。
Gn!
Linux系统内置了一套帮助手册(manual),来帮助使用者使用Linux操作系统以及查阅各类相关的官方标准资料。
该手册分为很多卷:
第一卷是用来查看 shell 命令的;第二卷是用来查看系统调用相关信息的;第三卷是用来查看库函数信息的...
具体而言如下:
卷数 卷名称 解释 1 用户命令 主要是shell命令和可执行程序的文档 2 系统调用 主要包含系统调用和系统相关函数的文档(以C语言风格对外暴露的接口函数) 3 库调用 主要包含与C语言函数库相关的文档,包括ISO-C和POSIX标准的C语言库函数。 4 特殊文件 描述系统上的特殊文件,通常指的是位于 /dev
目录下的设备文件和接口5 文件格式和规范 包含各种文件格式、配置文件、协议等的文档。 6 游戏 包含游戏等娱乐软件的文档。 7 杂项 包含不易分类的各种文档,如宏包、约定等。 8 系统管理命令 包含系统管理员用于管理整个系统的命令和守护进程的文档。这些命令通常需要较高的权限才能执行。 9 内核例程 和内核开发相关的文档与手册(非标准,有时没有) 该手册内容很多,但我们最常用和查看的是前三卷。
该命令的使用方式如下:
xxxxxxxxxx
11man [手册编号] 待查询指令/函数名等字符串
其中[手册编号]是可选的,若不指定手册的编号那么默认会从1号手册开始进行查找。
以下是一些具体的使用案例:
xxxxxxxxxx
31man man #查看man命令本身的帮助手册
2man mkdir #默认查询手册1中的shell命令mkdir
3man 3 printf #查看库调用卷中的mkdir
进入帮助界面后,我们可以按下面按键浏览帮助信息:
xxxxxxxxxx
51d(down) #往下翻半页
2u(up) #往上翻半页
3f(forward) #往下翻一整页
4b(backward) #往上翻一整页
5q(quit) #退出
man手册中内容主要以英文为主,如果你英文不是特别好,可以执行下列指令安装中文包:
xxxxxxxxxx
11sudo apt install manpages-zh
但一般还是不建议搞这种中文包,意义不大,纯看英文原汁原味会更好。
Gn!
注意:关闭主机之前,请务必先关闭虚拟机!否则,可能会损坏虚拟机文件,导致不能启动虚拟机。
关机和重启命令,都需要管理员权限。所以:
要么以
root
用户登录要么在指令前加上
sudo
表示用管理员权限执行命令。命令如下:
xxxxxxxxxx
71shutdown
2# 挂起,关机或重启计算机
3常用选项:
4-H, --halt: 挂起
5-P, --poweroff: 关机(默认)
6-r, --reboot: 重启
7-c(cancel): 取消
具体而言可以这么用:
xxxxxxxxxx
31sudo shutdown #广播关机消息给所有用户,并于一分钟后关机。
2sudo shutdown now #立刻关机
3sudo shutdown -r now #立刻重启
Gn!
Linux操作系统是多用户的操作系统,而且具有以下特点:
单个用户可以(同时)多次登录同一台Linux机器
多个用户也可以(同时)登录同一个Linux系统,这与我们常用的Windows桌面版系统不太一样。
这是因为Linux系统多用于服务器系统,一台服务器显然不太可能仅供一个人使用。使用Linux系统就像是入住一间豪宅,豪宅有它的主人,家人和客人。
Gn!
类似地,Linux系统的用户大致也可以分为两类:
特权用户,也叫超级用户或管理员用户。
特权用户的名字只有一个,固定为root。
在系统中,root 用户的 UID(用户标识符)是 0
Linux 系统中只有一个 root 用户
root用户拥有系统的最高权限,可以使用系统的所有功能——包括删除其它用户这样的敏感操作。
普通用户
普通用户是为日常工作和活动创建的用户账户。
普通用户可以有很多个,它们只能使用系统的部分功能,名字可以多种多样。
普通用户的权限受到限制,不能执行可能会影响系统稳定性和安全性的操作。
普通用户实际上可以进一步的细分为两类:
sudo用户,也称之为"sudoer",即英文单词"superuser do"的缩写。
sudoer可以使用sudo命令来临时提高自己的权限(假传圣旨),从而可以执行一些(而不是所有的)特权命令。
比如一个普通用户,若想要使用
apt install
命令在系统上安装软件或者使用shutdown命令关机,都需要加上sudo开头等等。非sudo用户。不具备使用"sudo"临时提升权限的能力。
普通用户的shell命令提示符是$,root的shell命令提示符是#,如下图所示:
关于普通用户当中的sudoer用户,可以查看下面的补充内容:
如何查询和创建一个sudoer用户?
我们把可以用
sudo
提升权限的普通用户称之为sudoer
,你可以通过指令查看当前系统中的sudoer用户:xxxxxxxxxx
11grep sudo /etc/group #查询sudo组的用户,该组用户就是sudoer
该指令的作用是:在"/etc/group"文本文件中搜索包含"sudo"这个字符串的行。
"/etc/group"文本文件是一个Linux系统配置文件,包含了该Linux系统上的用户组信息。
我们在安装Ubuntu时创建的初始用户就是一个拥有
sudo
权限的普通用户,就是一个sudoer
。当然并不是所有的普通用户都是
sudoer
,如果你希望将某个用户添加为sudoer,可以使用指令:xxxxxxxxxx
31sudo adduser test sudo #将test用户添加为sudoer
2sudo whoami #显示当前正在执行该命令的用户的用户名,如果当前用户是sudoer,会输出root
3sudo deluser test sudo #将test用户移除sudo组
Gn!
Ubuntu刚安装时,默认是不激活root用户的,创建的第一个用户只是一个具有sudo权限的普通用户。
若想激活root用户,需要使用以下指令,给root用户设置密码:
xxxxxxxxxx
11sudo passwd root
注意输入密码时和Windows不一样,它不会显示键入了多少字符。但你不用担心,只管输入后回车即可。
若激活了root用户,也请你把密码记录一下避免忘记密码。
Gn!
Linux系统下的所有用户,在 passwd 配置文件中都有一条相关记录。我们可以通过下列指令打开这个配置文件:
xxxxxxxxxx
11sudo cat /etc/passwd #cat指令用于将文件打印出来
比如我们可以找到我们创建的用户的信息:
wgd:x:1000:1000:WGD:/home/wgd:/bin/bash
每一行对应一条记录,每条记录有多个字段,字段之间以 : 分割。我们可以在5号手册中查看 passwd 文件的格式和规范:
xxxxxxxxxx
11$ man 5 passwd
以下内容复制粘贴自man手册:
"/etc/passwd"文件中每个用户账户包含一行,包含使用冒号 (“:”) 分隔的七个字段,分别是:
登录名
登录密码(现已被x代替,避免明文存储密码导致安全问题)
用户UID
用户组GID
用户名以及可能存在的用于解释用户名的注释字段
用户的家目录
用户登录的shell程序版本
wgd用户的家目录是:
/home/wgd
,注意/home
本身不是家目录。wgd用户使用的shell版本是:
bin/bash
,这里就是一个可执行程序的路径名。为了安全考虑,Ubuntu会把密码加密后存储在
/etc/shadow
文件中,你可以用下列指令查看这些加密密码:xxxxxxxxxx
11sudo cat /etc/shadow
你可能还发现一个细节:查看
passwd
文件,出现了很多不认识不了解的用户,那些是什么用户呢?其实这些用户都属于系统用户,也称为伪用户,这些用户的存在,为不同的系统服务和进程提供了不同的身份和权限,是Linux系统的一种安全机制,当然它们都不能用于登录操作。
Gn!
我们可以使用 useradd 命令来添加用户,格式如下:
xxxxxxxxxx
91格式:
2useradd [选项] username
3常用选项:
4-m, --create-home
5#如果不添加该选项,则不会自动创建家目录文件夹
6#添加上该选项,则会在创建用户时自动创建家目录文件夹
7-s, --shell SHELL程序路径
8#如果不添加该选项,新建用户的默认为/bin/sh
9#添加上该选项,则可以指定用户登录的shell程序,比如/bin/bash
比如我们可以这样用:
xxxxxxxxxx
31sudo useradd test1 #创建用户test1,但不会创建它的家目录文件夹,使用的shell默认为sh
2sudo useradd -m test2 #创建用户test2,会创建它的家目录文件夹,使用的shell默认为sh
3sudo useradd -m -s /bin/bash test3 #创建用户test3,会创建它的家目录文件夹,并且指定登录shell是/bin/bash
注意:
-s /bin/bash
必须放在一起,但它和-m
的顺序是无所谓的。细节:
执行这些命令用于创建一个新用户,我们会发现一个现象:指令执行完毕后,没有任何反馈信息,没有创建用户成功这样的提示。
这里就涉及到Linux的设计哲学了:
指令执行,没有反馈没有消息,就是最好的反馈最好的消息。
所以当你输入一条指令执行,没有给出任何反馈,那就说明执行成功了。相反,如果给出反馈信息,那你就需要注意了,因为:
这有可能是指令执行失败了,反馈是错误信息。
也有可能是给出的一些重要的提示。
总之Linux的这种设计旨在简化命令行的输出,只在必要时才提醒用户,从而提升效率和清晰度。
Gn!
我们可以使用 userdel 命令来删除用户,格式如下:
xxxxxxxxxx
61格式:
2userdel [选项] username
3常用选项:
4-r, --remove
5#如果不添加该选项,删除用户时不会连带删除家目录文件夹以及邮箱
6#添加上该选项,删除用户时会连带删除家目录文件夹以及邮箱
比如我们可以这样用:
xxxxxxxxxx
21sudo userdel test1 #删除用户test1,不会删除用户的家目录和用户的邮箱。
2sudo userdel -r test2 #删除用户test2,会把用户的家目录和用户的邮箱一起删除。
Gn!
我们可以使用 passwd 命令来给用户设置密码,普通用户通常只更改其自己账户的密码,而超级用户和sudo可以更改任何账户的密码。
xxxxxxxxxx
41格式:
2passwd [选项] [username]
3#该指令没什么比较常用的选项,所以这里就不列出了
4#username也是可选的,如果不填用户名,默认就是修改当前登录用户的密码
比如我们可以这样用:
xxxxxxxxxx
31passwd #修改当前登录用户的密码
2sudo passwd test1 #修改用户test1的密码
3sudo passwd root #修改超级用户root的密码
需要注意的是:
利用
sudo
提升权限,passwd
指令可以修改任意用户的密码,即便你忘记了此用户的密码也没有关系。
Gn!
我们可以使用 su(switch user) 命令切换到另一个用户,格式如下:
xxxxxxxxxx
41格式:
2su [选项] [username]
3#该指令没什么比较常用的选项
4#username也是可选的,如果不填用户名,默认会切换到root用户
比如我们可以这样用:
xxxxxxxxxx
31sudo su #切换到root用户
2sudo su test1 #切换到test1用户
3su test2 #不带sudo一般需要输入密码才能够登录
注意,使用"su"指令时若不添加sudo一般需要输入对应用户的密码才能够登录,使用sudo权限则可以不输入密码即可登录任意用户。
Gn!
"exit 命令"可以用来退出当前登录用户,转而切换到上一次登录的用户。
xxxxxxxxxx
11exit #退出当前登录用户
从实际上来说,当你使用
su
命令切换到另一个用户时,实际上是在当前终端会话中启动了一个新的 shell 进程,该进程以目标用户的身份运行。当你使用
exit
命令退出当前登录用户时,实际上就是结束了当前 shell 进程,并将控制权还给之前的 shell 进程。整个
su
和exit
的过程比较复杂,牵扯到操作系统进程的管理,但我们可以粗略、通俗的将这个过程理解成栈模型
的应用:
执行
su
命令,这个过程可以看成是新用户会话进栈的过程,栈顶用户会话就是当前登录用户执行
exit
命令,这个过程可以看成栈顶用户会话出栈的过程,那么前一个登录的用户就会成为新的当前登录用户。可以用下图来理解:
既然使用一个这样的栈结构来维护用户登录,那么当某个用户存在栈结构中时,该用户是无法被删除的。
Gn!
用户子系统命令并不是特别重要,我们以后从事的方向是开发,而不是运维。
在大多数情况,开发能获取的Linux账号只是测试环境下的非
sudoer
普通用户,这种账号一般只能从事创建、编辑这样的日常开发工作,往往连删除权限都没有,而用户管理分配这样的sudo操作就更无法执行了。但我们还是建议大家了解一下这些指令:
一方面,也不是所有公司开发和运维都严格区分,有些小公司开发会兼职运维。
另一方面,了解它们对提升个人技能,增强对Linux系统的理解都是有帮助的。
Gn!
在Linux系统中,程序员最常使用的命令肯定是和文件管理系统相关的指令,即"文件子系统命令"。但在具体讲解,学习这些指令之前,我们需要对Linux文件系统有一个基本的了解。
这一小节虽然基本不涉及任何Shell命令,但属于Linux系统的基本原理,是重点中的重点,一定要认真学习和理解!!!
Gn!
Linux的文件管理系统和Windows有很大的区别:
Linux是以树形结构来管理文件系统的,树的根结点就是根目录,用
/
表示。但Windows操作系统中,每一个盘符都可以视为一棵树,所以 Windows 下的文件系统是一种"森林"模式。Linux是以分门别类的方式来管理文件的,也就是说,我们会将功能相似的文件放到同一个目录下进行管理。但Windows操作系统中,更习惯于将同一套内容直接集中放在一起,而不是按功能分类存放。
比如在Linux安装QQ,那么可执行程序文件、配置文件、用户数据等可能放在不同的目录下。
而在Windows当中安装QQ,往往就是
Linux常见目录的功能
既然Linux是以分门别类的方式来管理文件的,那我们就需要知道,以下常见的Linux目录以及它们存放的内容:
Linux常见目录的功能-表
目录名 目录的功能作用 /bin(binary) 存放可执行程序或脚本文件 /sys(system) 存放和系统相关的文件 /dev(device) 存放设备文件 /etc 一般用来存放配置文件和启动脚本 /lib(library) 存放系统库文件 /var(variable) 存放变化很快的文件,比如日志文件 /proc(process) 存放进程相关的数据 /root root用户的家目录 /home/{username} 普通用户的家目录 /usr 用于存放用户可执行程序、库、文档等资源。
bin
目录下存放的是Linux的可执行文件或脚本文件,在Linux中可以用命令./文件名
执行该可执行文件或脚本文件(当然,前提是该文件具有执行权限)。关于执行权限可以通过以下命令添加:
xxxxxxxxxx
11chmod a+x file_name #为此文件的所有用户添加执行权限
/etc
目录,主要用来存放配置文件以及启动脚本。比如我们之前见过的Linux用户配置文件:/etc/passwd
lib
是library的缩写,/lib
目录用于存放系统的库文件,库文件和头文件不是一回事。头文件仍属于人能看懂的,作用于预处理阶段,而库文件是作用于链接阶段的文件,库文件都是机器二进制指令,人眼就看不懂了。最后我们再谈一下家目录:
首先还是要强调
/home
目录不是家目录,/home/{username}
才是普通用户的家目录。家目录的具体目录和
~
是等价的,一回事。对于普通用户而言,大多数情况下,只能在自己的家目录中创建文件!我们以后写代码,也会在家目录下创建文件。
除了上述知识概念外,还有两个我们需要记住的系统目录:
系统的标准库文件目录(包含系统动态库和静态库文件的目录)是:
/usr/lib
系统的标准头文件目录(C语言的标准头文件存放的目录)是:
/usr/include
,该目录下的头文件允许使用<>
包含。
Gn!
首先我们理解一个概念:虚拟文件管理系统(Virtual File System),简称VFS。
既然有虚拟的文件管理系统,那么对应的就有"真正的、真实的"物理文件管理系统,物理文件管理系统涵盖了硬盘上的文件存储、目录结构和数据组织方式等物理层面的细节。
如果让用户直接使用这种"物理文件系统",有什么问题呢?
问题显而易见:
不同的操作系统平台、不同的存储设备(机械硬盘、固态硬盘等),物理文件系统肯定会有所不同,那么用户就需要理解这些差异,增加了操作的复杂性,出错的概率也大大增加。(当然最重要的是用户才懒得学习这些计算机偏底层的内容)
那怎么办呢?
为了屏蔽这些硬件层面带来的差异性,简化用户学习成本,提高用户使用体验,我们就在物理文件系统上加一层:虚拟文件管理系统
用户不直接使用真实文件系统,而使用虚拟文件管理系统。
通过这种方式,用户与文件系统的交互被统一和简化,因为他们现在面对的是统一的虚拟文件系统界面,而非多种不同的实际文件系统。
在Linux平台下,虚拟文件管理系统是纯粹的树形结构,如下图所示:
Windows操作系统的虚拟文件管理系统也和Linux类型,只不过Windows系统中的一个盘符就是一棵树,Windows系统允许多个盘符同时存在,可以理解成"森林"模式。
了解:
现在比较流行的物理文件管理系统:
Windows,主流的目前是NTFS
Linux,有ext2、ext3、ext4等,其中ext4是最主流的默认选择。可以通过指令
df -Th
指令查看。
Gn!
类比虚拟内存空间和物理内存地址的映射,虚拟文件系统(VFS)中的文件和物理文件系统中存储的数据也存在映射的关系。
基于这样的一种映射关系,完全可能出现多个VFS中的文件,映射到硬盘上的同一块区域。如下图所示:
这种"多对一"的映射关系,就是硬链接:
硬链接允许VFS当中的多个文件可以映射到物理文件系统的同一文件中,也就是说,尽管在VFS中,这些文件或目录看起来可能位于不同的位置,或者有不同的名字,但它们实际上都代表同一份数据和信息。
那么什么是硬链接数呢?
硬链接数指的是一个文件拥有的硬链接数量,类似于"有多个指针指向了同一片内存空间",于是"指针的总数量"就被称之为"硬链接数",即VFS中有几个文件指向同一份数据。
比如上图的"文件A",VFS中的file1和file2文件都指向它,它的硬链接数就是2个。同时
file1
和file2
的硬链接数也是2个。为了加深大家对硬链接的理解,我们在
dir
目录下,新建一共目录test
。此时原本dir目录的硬链接数应该如何变化呢?答案:原本dir目录的硬链接数量是2,新增test目录后硬链接数变成了3!
为什么?
原本的硬链接数是2,原因如下图所示:
创建test目录后变成了3,原因如下图所示:
所以我们实际上可以得出一个结论:Linux的文件系统实际上就是依靠文件、目录以及硬链接等概念组合起来的。
补充:
硬链接数的设计,是一种"引用计数法",它是内存管理当中非常重要的一种算法策略。
在文件系统中,进行文件的删除,实际上执行的操作就是将"硬链接数--"
当硬链接数减少到0时,文件系统会认为该区域不再被任何文件/目录引用,此时它才会释放该区域内存空间。
Gn!
inode是词组
index node
的缩写,即"索引结点"。inode是物理文件系统中的概念,你可以将它视为物理文件系统中文件的"入口或起始位置"。它是物理文件系统中某个文件的唯一性标识,索引。
VFS中的文件通过硬链接链接到物理文件系统中的文件,就是通过链接到inode完成的。
我们可以用下图来描绘
inode
这个概念:inode除了视为文件的入口,还会存储该文件的元数据信息,比如:权限、所有者、大小、时间戳等。
所以:
硬链接实际上是VFS中的文件对物理文件系统中特定inode的引用。
硬链接数实际上是链接到某个inode上文件的个数。
当链接到inode上的文件数量等于0时,文件的数据才会真正被释放。
Gn!
文件子系统命令是我们学习shell命令的重点,重中之重。文件子系统命令,再进行细分,可以分为两大类:
目录相关命令
文件相关命令
在本小节,我们先来看一些和目录相关的文件系统shell命令。
Gn!
命令
pwd
可以用来查看当前工作目录的完整路径,它的英文全称是 "Print Working Directory",意思是打印当前工作目录。使用示例如下:
xxxxxxxxxx
11pwd #查看当前工作目录
这样可以输出当前目录:
/home/wgd
非常简单。
Gn!
cd
命令是一个非常常用的shell命令,它的作用是改变当前工作目录。cd
是 "Change Directory" 的缩写。该指令的格式如下:
xxxxxxxxxx
71$ man cd
2cd — change the working directory
3#切换当前工作目录
4格式:
5cd [选项] [directory]
6#该命令没有什么常用的选项。
7#directory也是可选的,如果不输入,则回到家目录
该指令有以下常用方式:
xxxxxxxxxx
71$ cd # 什么都不输入,切换到用户家目录
2$ cd /usr/lib # 切换到/usr/lib目录
3$ cd / # 切换到 / 目录
4$ cd ~ # 切换到用户家目录
5$ cd . # 切换到当前工作目录(不切换)
6$ cd .. # 切换到父目录,上级目录
7$ cd - # 切换到上一次目录
cd -
这个命令并不常用,但需要注意的是它并不是栈结构管理目录跳转的,而仅是用环境变量中的OLDPWD
属性来保存上一次目录。我们可以通过
env
命令来查看Linux系统环境变量。依据以上特点,如果频繁使用
cd -
命令实际上会在两个目录之间"反复横跳"。
Gn!
mkdir
命令用于创建目录(文件夹),它的英文全称是 "make directory"。该指令的格式如下:
xxxxxxxxxx
101$ man mkdir
2mkdir - make directories
3#复数形式,表示它可以创建多级目录
4格式:
5mkdir [选项] directory...
6#...表示可以写多个目录,表示创建多个新目录
7常用选项:
8-p, --parents
9#如果父目录不存在,则创建父目录
10#该选项添加后,可以用于创建多级目录
常用方式:
xxxxxxxxxx
31$ mkdir dir #在当前工作目录下创建一个新文件夹dir
2$ mkdir dir1 dir2 dir3 #在当前工作目录下创建一个新文件夹dir1、dir2以及dir3
3$ mkdir -p a/b/c #允许创建多级目录a/b/c,即便上级目录不存在
注意:
mkdir
只能用于创建目录,它不会创建文件。不要用后缀名来区分文件和文件夹(目录):
有后缀名的也可以是目录名。比如一个目录叫"a.txt",这虽然不太正常,但完全可以。
没有后缀名的也可以是一个文件名。比如一个文件叫"a",在Linux系统中,可执行文件都是没有后缀名的。
在创建多级目录时,如果父目录不存在,则必须加上选项
-p
,否则会创建失败!
Gn!
rmdir
命令用于删除空目录(文件夹),它的英文全称是 "remove directory"。注意:rmdir命令只能用于删除空目录!!!
该指令的格式如下:
xxxxxxxxxx
91$ man rmdir
2rmdir - remove empty directories
3#复数形式,表示它可以删除多级空目录
4格式:
5rmdir [选项] dirctory...
6#...表示可以写多个目录,表示删除多个空目录
7常用选项:
8-p, --parents
9#表示递归的删除空目录和空的父目录
常用方式:
xxxxxxxxxx
31$ rmdir dir #删除空目录dir
2$ rmdir dir1 dir2 dir3 #删除空目录dir1、dir2以及dir3
3$ rmdir -p a/b/c #递归的删除目录a/b/c
对于指令
rmdir -p a/b/c
,带上-p
选项后,指令的作用是:
它会先检查
a/b/c
目录,若目录为空,则删除该目录。然后递归检查
a/b
目录,若目录为空,则删除该目录。最后递归检查
a
目录,若目录为空,则删除该目录。在这个过程中,发现任一目录非空,则删除停止。
Gn!
Linux系统的通配符(wildcard character)是一种用于模式匹配的特殊字符,常用于指令中的文件名匹配和搜索操作。通配符允许你指定一个模式,然后搜索符合该模式的文件名或其他字符串。
通配符非常强大好用,希望大家熟练掌握它。
比较常用的通配符(模式)有:
*
:表示匹配0个或者多个任意字符
?
:表示匹配1个任意字符
[...]
:根据中括号内的字符进行匹配。
[characters] 表示匹配中括号内的任意一个字符。
[!characters] 表示匹配中括号外的任意一个字符。
我们可以在某个目录下使用指令:
xxxxxxxxxx
11mkdir dir dir1 dir2 dir3 dira dirb dirc diraaa dirbbb dirccc a b c dirx diry dirz
执行以下指令:
xxxxxxxxxx
11rmdir dir[abc]
效果是什么?会删除目录:dira dirb dirc
执行以下指令:
xxxxxxxxxx
11rmdir dir[!abc]
效果是什么?会删除目录:dir1 dir2 dir3 dirx diry dirz。但不会删除dir目录,因为要匹配1个字符
再次执行以下指令:
xxxxxxxxxx
11rmdir dir*
效果是什么?会删除目录:dir diraaa dirbbb dirccc,此时可以匹配0个字符。
最后执行指令:
xxxxxxxxxx
11rmdir *
所有刚才新建的目录都会被删除。
Gn!
ls
同样是一个非常常用的命令。英文全称是 "list directory contents",即展示目录内容,但实际上它有两个作用:
如果输入文件名,该指令会展示该文件的相关信息
如果输入目录名,该指令会展示该目录下的内容和相关信息。
它的基本使用格式如下:
xxxxxxxxxx
191$ man ls
2ls - list directory contents
3格式:
4ls [选项] [FILE]...
5#如果不添加任何选项、目录/文件名的话,默认展示当前工作目录的内容
6#如果输入目录名,会展示该目录下的内容
7#如果输入文件名,会展示该文件的相关信息
8#[FILE]...意味着可以输入多个文件/目录名,展示多个内容,但基本不会这么用
9
10常用选项:
11-a, --all
12#显示所有的内容,包括以.开头的文件和目录,也就是Linux系统下的隐藏文件
13-i, --inode
14#显示文件的inode编号(inode是物理文件系统中文件的唯一标识)。
15-l
16#以长格式的形式显示目录中的内容
17-h, --human-readable
18#一般和-l选项一起使用,以人类可读的方式显示文件的大小。
19#默认以字节展示文件大小, 添加该选型后会自动换算成kb, Mb等
一些比较常见的使用方式:
xxxxxxxxxx
51$ ls # 查看当前工作目录下的内容和信息
2$ ls dir # 查看指定dir目录下的内容和信息
3$ ls dir1 dir2 dir3 # 查看dir1,dir2,dir3
4$ ls -a dir # 查看dir中的所有内容,包括以.开头的文件和目录
5$ ls -alh dir # 显示包含隐藏文件在内的详细信息,并以人类更好理解的方式显示文件的大小
下面解释一些可能产生疑惑的地方。
Gn!
当我们使用
ls -a
指令展示目录内容时,会发现任何目录(包含空目录)中一定会存在.
和..
,它们存在的目的是什么呢?实际上,它们两的存在是为了配合
cd
命令:
.
中存储的是当前工作目录的路径
..
中存储的是当前工作目录的父目录的路径所以我们通过命令:
cd .
会继续留在当前工作目录。
cd ..
会跳到当前工作目录的上级目录。
Gn!
当我们使用
ls -i
指令时,会在文件名的前面显示一个整数值,这个整数值是什么呢?这个整数值就是文件的inode编号,当两个文件的inode是同一个时,表示它们指向物理文件系统中的同一个文件。
比如:
上图中的两个目录的inode编号是一样的。为什么呢?
Gn!
在
ls
的所有选项中,最重要的选项是"-l",在某个目录下执行ls -l
指令。得到如下图所示的内容:下面逐一解释这些内容:
总用量大体上描述当前目录下所有文件和子目录占用的磁盘空间的总和(磁盘块的数量),数字越大占用空间越大,但这个数值往往不是真实磁盘空间占用。
第一列数据,即第一列的字符,诸如
l
、d
、-
等。它们的含义是:
-
表示它就是一个普通文件。
d
表示它是一个目录文件夹。d实际上是directory的缩写。
l
表示它是一个符号链接(也叫软链接),即指向另一个文件的引用。通俗的说,它就是Windows操作系统的快捷方式。字符l
是词组"symbolic link"的缩写。
c
表示一个字符设备文件。需要注意的是,Linux的设计哲学中一切皆文件,会把那些计算机的物理设备也当成文件进行处理。所谓字符设备文件,就是逐字符数据传输处理的硬件设备。典型的有:鼠标、键盘、显示器等。c实际上是单词character的缩写。
b
表示一个块设备文件。所谓块设备文件指的是以固定大小的块为单位进行数据传输的设备,典型的就是硬盘。早期的机械硬盘一次性最少访问512个字节,也就是一个块是512个字节,到了现在固态硬盘一次性访问的块普遍是4096字节或者更大。b是单词block的缩写。
s
表示本地套接字。它主要用于进程间通信,这个我们后面系统编程会讲到。s是单词socket的缩写。
p
表示有名管道(也叫具名管道),它也主要用于进程间通信。p是词组named pipe的缩写。从第二列开始,到第十列,一共九列数据,也就是9个字符,把它们分为三组:
前三列:表示拥有者的读、写、执行的权限。若有权限则分别用r、w、x表示,若没有权限则用
-
表示。中间三列:表示拥有者组的读、写、执行的权限。若有权限则分别用r、w、x表示,若没有权限则用
-
表示。后三列:表示既不是拥有者也不是拥有组的其它用户的读、写、执行的权限。若有权限则分别用r、w、x表示,若没有权限则用
-
表示。r是单词"read"的缩写,w是单词"write"的缩写。可能有些同学对可执行权限表示的字母
x
存在疑惑,x其实不是任何单词的首字母缩写,它取自单词"execute"的第二个字母"x"。之所以不取首字母e
,是因为e在很多地方都被占用了。紧接着上面一堆
wxr-
这些权限的标记后,会存在一个数字:1、2等。这个数字是什么意思呢?这个数字的意思是该文件/目录的硬链接数,关于硬链接数的概念上面已经讲解过了。这里不再赘述。
再往后面,
ls -l
指令输出的内容就比较简单了:
第一个
wgd
表示此文件的拥有者用户名第二个
wgd
表示此文件的拥有组的组名第三个数字表示文件的大小,默认以字节为单位。如果你希望用更大、更好理解的单位表示大小,则可以添加上"-h"选项,即使用命令"ls - lh"。如果你还希望展示隐藏文件,可以使用命令"ls -alh"。
再往后就是文件的最后修改时间,注意不是创建时间。
最后是文件的名字。
以上,最后再强调一下ls -l真的特别特别常用和重要,一定要把它输出的内容意义全部记住!!!!
Gn!
如果你希望更直观、更清晰看到某个目录文件系统的树形结构,可以使用指令
tree
,效果如下图所示:tree 命令通常不是 Linux 系统默认自带的shell命令,使用之前,可以使用以下命令安装
tree
:xxxxxxxxxx
11sudo apt install tree
tree命令的使用格式如下:
xxxxxxxxxx
81$ man tree
2tree - list contents of directories in a tree-like format.
3#以树状格式展示出目录的内容。
4
5格式:
6tree [选项] [directory]...
7#该指令没什么比较常用的选项
8#可以什么选项目录都不加表示展示当前工作目录
常用方式:
xxxxxxxxxx
31tree # 以树状结构显示当前工作目录的内容
2tree dir # 以树状结构显示目录dir中的内容
3tree dir1 dir2 dir3 # 以树状结构显示目录dir1, dir2, dir3中的内容
tree命令展示内容的末尾还会显示目录下的总文件夹数,以及总文件数。
Gn!
cp(copy)命令用于复制文件或目录。它是一个非常常用的命令,可以将文件目录复制到另一个位置,类似于Windows操作系统中的"复制 - 粘贴"操作。
该命令的使用格式如下:
xxxxxxxxxx
171$ man cp
2cp - copy files and directories
3#可以同时复制多个文件/目录
4格式:
5cp [选项] SOURCE DEST
6#将一个目录复制到另一个目录下或者将源文件复制到目的文件中,这里DEST可以是文件也可以是目录
7cp [选项] SOURCE... DEST
8#将多个文件或者目录复制到目标目录下,此时DEST只能是目录
9常用选项:
10-n, --no-clobber
11#若在复制的过程中发现文件已存在,则不会覆盖。不添加该选项,默认会覆盖已存在文件
12-i, --interactive
13#若在复制的过程中发现文件已存在,则会询问用户是否覆盖,选择权力交给用户。
14#输入y/Y表示覆盖,其它任意字符表示不覆盖
15-R, -r, --recursive
16#当SOURCE是一个目录时,添加该选项,用于递归复制目录下所有的内容,即包括所有子目录和文件。
17#复制目录时,应该总是添加上该选项
常用方式举例:
xxxxxxxxxx
51$ cp text1 text2 # 将text1复制到text2中;如果text2存在,则覆盖。
2$ cp text1 text2 dir # 将text1,text2复制到目录dir中;如果文件已存在,则覆盖。
3$ cp -n text1 text2 # 将text1复制到text2中;如果text2存在,不覆盖。
4$ cp -i text1 text2 text3 dir # 将text1,text2,text3复制到目录dir中;如果文件已存在,则提示用户是否覆盖。
5$ cp -r dir1 dir2 # 递归地将目录dir1复制到目录dir2
特别需要注意的是:
使用
cp
命令复制创建新文件时,实际上是在创建一个内容相同但是具有不同inode编号的新文件:
尽管新旧文件的内容相同,但它们在文件系统中被视为两个独立的实体
并且各自拥有不同的物理存储空间和inode编号。
使用
cp
命令覆盖目标文件时,会直接覆盖目标文件的内容,目标文件的inode
并不会发生改变。这个过程一般不会修改文件的元数据(但最后修改时间、大小等这些内容会发生变化),而是修改了文件的内容。当复制的源是一个目录文件夹时,必须要加上
-r
选项,否则指令会直接执行失败报错。
Gn!
mv(move) 命令可以用来移动文件和目录,除此之外,我们还可以用 mv 命令对文件和目录重命名。
其使用的格式如下:
x1$ man mv
2mv - move (rename) files
3格式:
4mv [选项] SOURCE DEST
5mv [选项] SOURCE... DEST #若使用这种格式,则DEST只能是目录
6Rename SOURCE to DEST, or move SOURCE(s) to DIRECTORY.
7#如果DEST是一个普通文件,那么mv的作用就都是重命名(为什么?)
8#如果DEST是一个目录且SRC也是一个已存在的目录或文件,那么mv的作用是将文件/目录移动到DEST目录下。
9#如果DEST是一个不存在的目录且SRC是一个目录,那么mv的作用是重命名目录。
10常用选项:
11-n, --no-clobber
12#若在复制的过程中发现文件已存在,则不会覆盖。不添加该选项,默认会覆盖已存在文件
13-i, --interactive
14#若在复制的过程中发现文件已存在,则会询问用户是否覆盖,将选择权力交给用户。
15#输入y/Y表示覆盖,其它任意字符表示不覆盖
常用方式举例:
xxxxxxxxxx
51$ mv text1 text2 # 将text1重命名为text2;如果text2存在,则覆盖。
2$ mv dir1 dir2 # 将dir1重命名为dir2
3$ mv -n text1 text2 # 将text1重命名为text2;如果text2存在,则不覆盖。
4$ mv text1 text2 text3 dir # 将text1,text2,text3移动到目录dir中;如果文件已存在,则覆盖。
5$ mv -i text1 text2 text3 dir # 将text1,text2,text3移动到目录dir中;如果文件已存在,则提示用户是否覆盖。
几个小细节:
Linux系统中文件的重命名不要简单理解成修改文件名,修改文件路径也属于修改文件名的一种。所以
mv [选项] SOURCE DEST
,总是在重命名文件,即便改变了文件路径名。
mv
移动文件时(DEST是一个普通文件时)的覆盖和cp指令显然不同,mv不是文件内容数据上的覆盖,而是直接删除DEST文件,然后将源文件重命名。这一过程,可以通过查看inode编号来理解,如下图所示:
被覆盖文件实际上在VFS中已经被删除了,源文件则通过重命名"霸占"了它的位置。
所以
mv
命令的覆盖更彻底,也更危险,要小心使用。cp和mv这两个指令,覆盖文件的过程,可以根据下面两张图来理解:
cp指令的覆盖要修改文件内容,涉及到物理文件系统的操作。
mv指令的覆盖仅涉及VFS,不涉及物理文件系统。所以mv命令执行的效率会更高。
最后思考一个问题:cp复制目录时需要加上
-r
选项表示递归复制子目录,为什么mv指令移动目录就不需要呢?如果你能够理解上面两张图讲解的
cp
和mv
命令的差异,那这个问题将非常容易回答:cp复制目录涉及到整个目录下所有文件内容的复制操作,必然需要递归深入到每一个子目录才能够操作完成。
但
mv
指令只不过是重命名罢了,相当于"改变了指针的指向"。被移动目录下的所有内容都不需要改变,自然不需要递归。以上。
Gn!
rm(remove)命令可以用于删除文件和目录,也是比较常用的命令。
它的基本格式如下:
xxxxxxxxxx
121$ man rm
2rm - remove files or directories
3格式:
4rm [选项] FILE...
5#可以用于删除多个文件或目录
6常用选项:
7-f, --force
8#忽略不存在的文件,永远不提示
9-i
10#在每次删除前,都询问用户是否删除
11-r, -R, --recursive
12#递归删除目录
常用方式举例:
xxxxxxxxxx
41$ rm text1 # 删除文件text1
2$ rm text1 text2 text3 # 删除文件text1, text2, text3
3$ rm -i *.txt # 删除当前目录下所有以.txt结尾的文件,并提示用户是否删除
4$ rm -rf dir # 递归删除目录dir, 不给出任何提示
注意:
Linux没有Windows操作系统"回收站"的概念,一旦
rm
删除文件几乎不可能恢复,所以使用该命令要小心谨慎。
i
和f
选项不要同时使用,因为f
会屏蔽i选项
。
rmdir
只可以用于删除空目录,rm
则更强大。只需要添加选项-r
就可以递归的删除非空目录,-rf
混用则可以无提示的删除一个目录。著名的恶作剧指令:
rm -rf /
Gn!
在C语言中,我们学习过
typedef
关键字给类型起别名,在Linux操作系统中,命令也是可以起别名的。有些shell命令明显比较长,自然我们就想给这些命令起一个简短的别名,方便以后使用,
alias
命令就是用来做这个事情的,这个单词翻译过来就是别名、外号的意思。其基本的使用格式如下:
xxxxxxxxxx
61$ man alias
2alias — define or display aliases
3#定义或显示别名
4格式:
5alias [别名=命令]...
6#若直接使用alias命令则会打印当前会话的指令别名有哪些
常见用法示例如下:
xxxxxxxxxx
81$ alias # 查看别名
2#一般会打印以下内容
3#alias g++11='g++ -std=c++11'
4#alias ll='ls -alF'
5#alias ls='ls --color=auto'
6#...
7$ alias h=history # 设置别名
8$ alias la='ls -a' # 设置别名
有一个别名大家需要记住:
ll
,它实际上是ls -alF
指令的别名,它是Linux系统默认存在的指令别名。这个ll
的别名提供了一个快速的方式来显示当前目录下的所有文件和目录的详细列表,包括隐藏文件(以.
开始的文件)。其中的
-F
这个选项在列出的每个文件名后添加一个字符来指示该文件的类型。例如:
/
表示目录。
\*
表示可执行文件。
@
表示符号链接,也就是软链接文件。
|
表示有名管道文件。
=
表示套接字。....
用
alias
指令定义的别名只在这一次连接会话中生效,若想定义的别名持续生效,可以去.bashrc
配置文件中进行修改。而且Linux系统允许指令别名和本名是一致的,比如bash配置文件中自带的指令别名:
xxxxxxxxxx
11alias ls='ls --color=auto'
这也是我们使用
ls
指令,不同类型文件的文件名会变色的原因。
Gn!
关于文件的权限,我们在前面
ls -l
命令中已经讲解过了。某个用户对一个文件(目录)具有三种权限:
可读,用r表示,不可读用-表示
可写,用w表示,不可写用-表示
可执行,用x表示,不可执行用-表示
那么如何对一个文件的权限进行修改呢?我们可以使用shell命令——
chmod
指令。它是词组"change mode"的缩写,允许用户设置或更改文件或目录的权限。为了更加清晰的讲解清楚这个指令,我们还需要了解一些目录以及权限相关的基本原理知识。
Gn!
对于普通的计算机用户而言,他们会觉得目录(文件夹)和文件是完全不同的两个概念。
在上面讲解指令时,我们也依照大家以往的习惯,将指令的作用根据目录和文件做了区分,但实际上在Linux系统下目录也是文件的一种,或者说目录是一种特殊的文件。
比如:
ls -l
展示的信息中d
就表示该文件是一个目录文件。既然目录是一个特殊文件,那么目录文件中存储的是什么数据呢?(注意:这里指的是物理文件系统中的目录文件实际存储的内容)
目录文件中存储的是当前目录下其他文件和子目录的信息,这些信息被称之为目录项(directory entries)。
目录项中主要存储这个目录/文件的文件名以及inode编号,目录项不会直接存储文件内容数据!!
而诸如:文件类型、权限、硬链接数、文件大小、时间戳等文件元数据信息则存储在inode中。
在逻辑上,目录文件中的目录项是以链表的形式(某些文件系统还可能采用B树或哈希表等结构)链接起来的。
比如一个dir1目录,树形图如下所示:
将目录下所有的内容画出来(包含隐藏文件.和..),树形结构如下所示:
那么dir1和dir2两个目录存储的数据,也就是目录项,在逻辑上可以画成以下图:
为什么我们要知道这个原理呢?
那自然是因为了解这些内容,对后续的学习都非常有帮助啊。
Gn!
既然目录是一个特殊文件,存储的是目录项数据,那么目录权限对目录操作的影响就很直观了:
读权限(r):
对于目录,读权限允许用户列出该目录内的文件和子目录名。
目录的读权限,就是读目录文件中目录项数据的权限,如果没有该权限,自然就不能展示当前目录信息了。
目录的读权限影响
ls
这样罗列目录信息的指令。写权限(w):
对于目录,拥有写权限允许用户在该目录内新增、删除或重命名文件和子目录。
目录的写权限,就是修改目录文件中目录项数据的权限,如果没有该权限,就无法修改、删除这些数据项了。具体表现出来就是:无法创建新目录,无法删除旧目录、不能重命名等。
思考一下:这种情况下当前目录下的文件,内容能不能修改呢?当然可以,目录项中不存储文件内容!
目录的写权限影响的命令很多,如:mkdir、cp、mv、rm等
执行权限(x):
什么是目录的执行权限呢?通俗的说,拥有目录的执行权限就是可以进入该目录。所以目录的执行权限会影响
cd
命令。目录的执行权限是目录最基本的权限。没有执行权限的目录是"不可进入、不可找到"的目录,那么目录将不可读也不可写。
目录的读写权限都依赖执行权限,没有执行权限,目录即便有读写权限也没有意义。
Gn!
理解了目录权限,我们再来一起看一看普通文件的权限,对文件操作的影响:
读权限(r):
对于普通文件而言,读权限允许用户查看文件的内容。
思考一下:读某个目录下的文件,需要该目录的读权限吗?
当然不需要,文件内容并不是存储在目录项中的,查看文件内容仅需要目录有执行权限就可以了。
写权限(w):
对于普通文件而言,写权限允许用户修改文件的内容。
需要注意的是:文件的删除、重命名等权限在于目录的写权限,而不在于文件本身。
思考一下:如果一个文件是只读的(即没有写权限),能不能删除它?当然可以,前提是目录具有写和执行权限。
当然文件的写权限只是文件系统级别的权限限制,在操作系统层面仍然有很多种方式可以绕过这种权限,强行修改一个只读文件。比如利用
vim
指令就可以强行修改一个只读文件,再比如root(sudo)超级用户权限,也可以完全无视这种限制。执行权限(x):
执行权限允许用户运行文件作为一个程序或脚本。
如果你没有对文件的执行权限,即使文件是一个正确的可执行程序或脚本,你也无法直接运行它。
Gn!
chmod指令用于修改文件的权限,它有两种比较常用的方式:
Gn!
利用文字设定法直观的设定一个文件的权限,这种方式在指令执行时是比较常用的,因为它很直观好理解。但是到了Linux系统编程时,写代码改变文件权限的时候,这种方式就不是很常用了。
其基本格式如下:
xxxxxxxxxx
11chmod 用户类别[+=-][rwx] file
其中用户类别有四种:
u:表示改变文件拥有者对文件的权限
g:表示改变文件拥有者组对文件的权限
o:表示改变其它用户对文件的权限
a:表示改变所有用户对文件的权限
+-=
分别表示:
+:给目标用户添加指定权限
-:给目标用户删除指定权限
=:给目标用户直接指定权限
rwx
则很简单,分别表示可读、可写、可执行三种权限。使用举例:
xxxxxxxxxx
31$ chmod o+w text1 #表示将文件text1的其他用户(Others)的写权限设置为开启
2$ chmod a-r dir1/ #表示将目录dir1/的所有用户的读权限(Read)设置为关闭。
3$ chmod u=rwx,g=rx,o=r text1
最后一条指令的意思是:
u=rwx
:表示将文件text1
的所有者用户的权限设置为可读、可写、可执行。
g=rx
:表示将文件text1
的所属组的权限设置为可读和可执行,但不可写。
o=r
:表示将文件text1
的其他用户的权限设置为只读,不可写,不可执行。
Gn!
什么是权限的数字设定法呢?我们观察一下某个文件的权限:
一共有9个字符,于是我们用1表示对应位置有权限,0表示没有权限,上述dir目录的权限就可以用二进制表示成:
111 111 101
这就是一种数字设定法,用于设置权限。但二进制数毕竟有点长,于是我们把它转换成八进制:
775
所谓权限的设定法,就是用这样的八进制数来表示一个权限。这种表示方式显然不如文字表示法直观易理解,但它在编码过程中使用会更方便,是程序员日常表示权限更常用的方式。
以下是一些常见的数字权限:
775
:775意味着拥有者和拥有组用户权限全开,其它用户则没有写权限。一般新建的目录或可执行文件就拥有这个权限。
664
:664意味着拥有者和拥有组都可读可写,其它用户则只读。一般新建的文件就拥有这个权限。
700
:除了拥有者外其它用户没有权限,可用于私人文件。
777
:所有用户的所有权限都打开,一般不会这么做。
666
:所有用户读写权限打开,一般也不要这么做。这些权限数字都不用记,因为用多了见多了自然就记住了,就算记不住现场推也不会花很多时间。
这里我们,仅希望大家记住一个重要结论:
数字表示法表示权限时,三个整数中的某个数字如果是奇数,那么对应用户一定具有执行权限。反之若是偶数,则一定没有执行权限。
比如:
看到
775
这样的权限数字,也许你不能反应过来所有的权限,但你应该立刻想到:所有用户都有执行权限看到
664
这样的权限数字,你应该立刻想到:所有用户都没有执行权限看到
766
这样的权限数字,你应该立刻想到:只有拥有者具有执行权限,其它用户都没有。思考一下为什么?
因为:在二进制表示中,一个整数是奇数的条件是它的二进制表示的最低有效位必须为1。(&运算判断奇偶性还记得吗?)
可执行权限是所有权限当中最重要的,把它放在三种权限的末尾,它就具有了上述结论性质,非常nice~
使用举例:
xxxxxxxxxx
11$ chmod 664 text1
作用是将text1文件的权限设置为:
rw-rw-r--
Gn!
在类Unix操作系统中,文件权限掩码(umask,user file creation mode mask)是一个用于设置新文件和新目录默认权限的系统设置。
当你在Linux系统中创建一个新文件或目录时,Linux会根据特定的默认权限来赋予新文件/目录的权限:
对于文件而言,默认权限通常是666(可读可写无执行权限)
对于目录而言,默认权限通常是777(可读可写可执行,权限全开)
文件权限掩码umask的值,则决定了从默认权限中"掩去"(即清除)哪些权限。
具体的计算公式是:
即:
想要计算新文件、目录的实际权限,需要先将umask按位取反,然后再与默认权限做按位与运算得到。
举例:
Ubuntu默认的umask一般是0002(八进制),即需要移除其它用户的写权限。
实际权限计算过程如下:
对新创建的文件:
默认权限:
666
->110 110 110
(rw-rw-rw-)umask:
0002
->000 000 010
~umask:
111 111 101
实际权限:
110 110 100
->664
(rw-rw-r--),这意味着所有者和所属组有读写权限,而其他用户只有读权限。对新创建的目录:
默认权限:
777
->111 111 111
(rwxrwxrwx)umask:
0002
->000 000 010
~umask:
111 111 101
实际权限:
111 111 101
->775
(rwxrwxr-x),这意味着所有者和所属组有完全的读写执行权限,而其他用户有读和执行权限。通过合理设置
umask
,系统管理员可以控制新文件和目录的访问级别,防止未经授权的用户访问敏感信息。这种方式确保了系统中新创建的文件和目录不会意外地获得过高的权限,从而增强了系统的安全性。
Gn!
下面我们来一起学习文件相关的shell命令,这些命令同样很常用也很重要。
Gn!
在 Linux 下创建文件的方式有多种,最常用的是以下三种方式:
xxxxxxxxxx
31$ echo "Hello World!" > text # 创建文件text, 并且文件中包含内容"Hello World!" >表示重定向输出流
2$ touch text1 text2 text3 # 创建空文件text1, text2, text3 (要求text1,text2,text3不存在)
3$ vim hello.c # 编辑hello.c, 按:wq退出。
我们来逐一讲解一下:
echo
单词有回响、余音的意思,它可以直接echo 字符串
表示将字符串输出到标准输出(一般是屏幕)中。上面指令中的>
字符是一个重定向操作符,它表示不再输出到标准输出中,而是输出到文件text
中。若文件不存在,则会创建文件。
touch
单词是触摸的意思,touch 文件名...
作用是文件不存在则创建文件,若存在则将文件的最后修改时间更新。
vim
是后续我们最常用的方式,它也可以用于创建多个文件。关于vim的详细使用,我们在后续章节继续学习。
Gn!
shell命令的背后往往需要可执行程序的支持,which指令就用于查找某个命令背后支持它执行的可执行文件的完整路径。
其基本的使用格式如下:
xxxxxxxxxx
71$ man which
2which - locate a command
3格式:
4which [-a] cmd...
5选项:
6-a
7#显示所有匹配的路径
常用方式:
xxxxxxxxxx
31$ which bash # 查看bash的路径
2$ which ls tree # 查看命令ls和tree的路径
3$ which -a vim # 查看vim的所有路径 (我们可能装了多个版本的vim)
which 是根据 PATH 环境变量中的路径依次去查找的,然后显示第一个匹配项,或者显示所有匹配项。
我们可以使用
env
查看PATH环境变量:xxxxxxxxxx
41$ env
2...
3PATH=/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin:/usr/games:/usr/local/games:/snap/bin
4...
什么是"PATH 环境变量"?
PATH环境变量是一个包含一系列目录路径的变量,用于告诉操作系统 shell 在哪些目录中查找可执行文件。(Windows也有类似的)
当你在终端输入一个命令时,系统会按照PATH环境变量中指定的顺序搜索这些目录,直到找到匹配的可执行文件为止。
在以往我们给大家演示过一个指令:
./hello.py
该指令用于执行一个可执行文件,那么你现在就可以思考一下这个指令为什么不能写成
hello.py
直接执行这个文件呢?为什么非要加一个表示当前工作目录的
./
呢?这是因为直接写
hello.py
,Linux会把整体当成一个指令去PATH环境变量下寻找可执行程序,但很明显这是找不到的,所以会报错:hello.py:未找到命令
而使用了
./
就不会去PATH环境变量下寻找了,而是直接在当前工作目录下,找到当前工作目录下的可执行程序进而执行。
Gn!
find 命令可以在一个目录或者多个目录中,递归地中查找符合指定条件的文件或者目录。
该命令只需要使用者大体上知道自己要查找什么,就可以很快速的找到符合要求的内容,展示出来。这个指令很强大,也很常用。
注意,该指令可以用于查找文件,也可以用于查找目录。
它的基本使用格式如下:
xxxxxxxxxx
721$ man find
2find - search for files in a directory hierarchy
3#在目录层次结构中搜索文件
4格式:
5find [起始点...] 查找条件选项
6# 起始点表示查找文件从哪个目录开始,或者哪几个目录开始
7# 起始点是可以省略的,省略后会从当前工作目录开始进行查找工作
8# 建议不要省略起始点,若从当前目录开始查找时,可以使用 . 来表示当前目录
9常用选项:
10-name pattern
11#查找符合pattern名字的文件或目录
12#pattern名字最好要使用""包裹起来,这样可读性更好也能避免执行出错
13#可以使用我们以往学习的通配符,使用通配符时必须用""包裹
14
15-type c
16#查找类型为c的文件
17#常见的文件类型表示字符如下:
18#b(block): 块设备文件
19#c(character): 字符设备文件
20#d(directory): 目录
21#p(named pipe): 有名管道
22#f(file): 普通文件(和ls -l时用-表示有区别,这里要记住)
23#l(symbolic link): 符号链接
24#s(socket): 套接字
25
26-size n[单位]
27#根据一个文件的大小进行查找
28#n必须是一个非负数
29#单位即内存大小的单位,如下:
30#c: 字节
31#w: 两个字节,即1个字
32#b: 512个字节 (注意如果不添加单位的话,b是默认值)
33#k: kb, 1024个字节
34#M: Mb, 1024kb
35#G: Gb, 1024Mb
36#当仅填入一个非负数时,表示文件大小必须精确匹配指定的大小.如: -size 100c 表示精确查找100个字节的文件
37#当填入一个非负数且在n前面添加正负号时, +正号表示查找大于指定大小的文件, -负号表示查找小于指定大小的文件
38# -size +1k 表示文件大小必须大于1kb
39# -size -100M 表示文件大小必须小于100Mb
40#不要用这个选项查找目录, 目录文件的大小不是目录下所有文件和子目录大小之和,这一点上面已经讲解过了
41
42-empty
43#查找空的文件或空的目录文件
44
45-user username
46-uid uid
47#根据用户名或者UID查找
48#可以通过 cat /etc/passwd 命令来查看Linux用户信息,如UID
49#也通过组名和组ID也可以查找, 但目前组基本上没人用, 这里就不赘述了
50
51-perm 权限数字
52#根据权限查找
53#比较常见的权限数字,比如775,664等
54
55#根据时间查找的选项
56-(时间类型)(min/time) 正整数
57#时间类型指查找依据的时间类型
58#a(access): 文件访问的时间
59#c(change): 文件元数据发生改变的时间
60#m(modify): 文件内容发生改变的时间
61#min:时间以分钟为单位
62#time: 以天为单位
63#n是一个整数表示对应时间,可以在n前面添加'+'和'-',表示大于和小于
64
65-maxdepth n
66#指定递归搜索的深度为n
67#若只想在当前目录下搜索,可以设定-maxdepth 1
68
69#组合查找:
70-a(and), -o(or), !(not)
71#分别表示组合查找的与(并且)、或、非
72#注意当find指令中出现多个选项需要进行条件组合时,-a即并且是默认的选择,此时-a可加可不加!
常用方式举例:
xxxxxxxxxx
141$ find /usr/include -name "stdio.h" # 在/usr/include目录下查找stdio.h文件
2$ find . -name "*.c" # 在当前工作目录下查找所有以.c结尾的文件
3$ find /dev -type b # 在/dev目录下查找所有的块设备文件
4$ find . -size 5M # 在当前工作目录下查找所有大小为5M的文件
5$ find . -size +5M # 在当前工作目录下查找所有大于5M的文件
6$ find dir1 dir2 dir3 -empty # 在dir1,dir2,dir3目录下查找所有空的文件和空的文件夹
7$ find . -user he # 在当前工作目录下查找he用户拥有的文件
8$ find . -gid 0 # 在当前工作目录下查找root组(gid=0)拥有的文件
9$ find . -perm 664 # 在当前工作目录下查找权限为664(rw-rw-r--)的文件
10$ find . -mtime 1 # 在当前工作目录下查找在[1, 2)天前内容发生修改的文件 (find会省略小数部分)
11$ find . -mtime +2 # 在当前工作目录下查找在[3, +)天前内容发生修改的文件
12$ find /dev -type c -a -name "tty*" # 在/dev目录下查找以tty开头的字符设备文件
13$ find /dev -type b -o -name "tty*" # 在/dev目录下查找块设备文件或者是以tty开头的文件
14$ find /dev -type c -a ! -name "tty*" # 在/dev目录下查找不以tty开头的字符设备文件
注意:在查找当前目录时,建议使用
.
来表示当前目录,虽然也可以直接省略。
find
命令的选项很多,而且允许组合起来进行查找,此时需要使用选项-a
表示并且,-o
表示或者,-!
表示否定。注意当find指令中出现多个选项需要进行条件组合时,-a即并且是默认的选择,此时-a可加可不加!
Gn!
查看文件内容的方式有很多,包括我们可以使用
vim
指令来查看文件内容。如果就希望简简单单的查看文件的内容,可以使用cat
指令。它的基本格式如下:
xxxxxxxxxx
111$ man cat
2cat - concatenate files and print on the standard output
3#cat指令并不是猫的意思,它和strcat函数名一样,是拼接的意思
4#该指令会拼接文件的所有内容并输出到标准输出(默认是屏幕)上打印
5格式:
6cat [选项] [file]...
7#可以将多个文件的内容拼接到一起然后打印输出
8#允许不输入文件名,此时cat将从标准输入(默认是键盘)中读取数据打印,但这种做法一般没有实际意义
9常见选项:
10-n, --number
11#对每一行进行编号,若不添加该选项默认不会进行行编号
常见用法举例:
xxxxxxxxxx
31$ cat /etc/passwd #在屏幕上打印用户相关的信息
2$ cat -n /etc/passwd #在屏幕上打印用户相关的信息,显示行数
3$ sudo cat /etc/passwd /etc/shadow #在屏幕上打印用户的密码,但密码也都是加密存储的
Gn!
使用 head 命令查看文件内容的前几行:
xxxxxxxxxx
101$ man head
2head - output the first part of files
3#该指令会找到文件的前几行并输出到标准输出(一般是屏幕)中打印
4#默认输出文件内容的前十行
5格式:
6head [选项] [file]...
7#允许不输入文件名,此时head将从标准输入(默认是键盘)中读取数据打印,但这种做法没有实际意义
8常见选项:
9-n NUM
10#显示前NUM行;若在NUM前面添加'-'号,则显示除了后NUM行的所有行
常见用法举例:
xxxxxxxxxx
41$ head text1 #显示text1的前10行
2$ head text1 text2 text3 #显示text1,text2,text3的前10行
3$ head -n 5 text1 #显示text1的前5行
4$ head -n -5 text1 #显示除了最后5行外的所有行
使用 tail 命令查看文件内容的后几行:
xxxxxxxxxx
121$ man tail
2tail - output the last part of files
3#该指令会找到文件的后几行并输出到标准输出(一般是屏幕)中打印
4#默认输出文件内容的后十行
5格式:
6tail [选项] [file]...
7#允许不输入文件名,此时tail将从标准输入(默认是键盘)中读取数据打印,但这种做法没有实际意义
8常见选项:
9-n NUM
10#显示后NUM行;若在NUM前面添加'+'号,则从第NUM开始显示到末尾
11-F
12#显示后十行内容,但会实时显示追加的数据。这个选项经常用于查看日志文件。
常见用法举例:
xxxxxxxxxx
51$ tail text1 # 显示text1的后10行
2$ tail text1 text2 text3 # 显示text1,text2,text3的后10行
3$ tail -n 5 text1 # 显示text1的后5行
4$ tail -n +5 text1 # 从第5行开始显示,直到文件末尾
5$ tail -F error.log # 查看错误日志文件,显示后10行,并且会实时显示后续追加的数据
尤其需要处理
-F
选项,添加后tail会处于持续执行监听的状态,若文件末尾进行了数据追加则会实时显示出来。这个指令选项常用于查看日志文件,比较常用。
Gn!
使用 less 命令进行单页浏览文件内容。
其使用的格式如下:
xxxxxxxxxx
61$ man less
2less - file perusal filter for crt viewing
3#说人话就是单页浏览一个文件的内容
4格式:
5less [选项] file...
6-N, --line-numbers:显示行号。
进入浏览界面后,可以使用下列方式进行翻页操作:
xxxxxxxxxx
71f/空格(forward) #往后翻一页
2b(backward) #往前翻一页
3d: #向下翻半页。
4u: #向上翻半页。
5g: #跳转到文件的第一行。
6G: #跳转到文件的最后一行。
7q(quit) #退出浏览界面
less命令是
more
命令的升级版,这种升级来源自英文谚语"less is more.",很有趣。
Gn!
在Linux系统中,重定向允许改变命令的标准输入流的输入源以及标准输出和标准错误流的输出目的地。这对于保存输出结果到文件、从文件中读取输入或将一个命令的输出作为另一个命令的输入等操作非常有用。
重定向的示意图如下所示:
需要注意的是,重定向的含义是断开缓冲区和默认关联设备的链接,转而链接到另一个文件中,这点在图中应该很容易理解。
注意:重定向不是改变进程和缓冲区之间的关联,而是改变缓冲区的源和目的地!!!
在讲重定向之前,我们需要先引入文件描述符的概念,这个概念具体的内容我们会等到后面再详细讲解。现在我们只需要知道:
它是一个非负整数
文件描述符不同,重定向符号会有所不同。
标准流 文件描述符 重定向符号 stdin 0 < stdout 1 > 和 >> stderr 2 2> 和 2>> 解释一下这些重定向符号:
<:将文件的内容重定向到命令的输入,一般比较少使用。
>:将命令的标准输出重定向到一个文件中,如果文件已经存在,则从头覆盖文件内容。
>>:将命令的标准输出追加到一个文件的末尾,如果文件t不存在,则创建文件。
2> 和 2>>
类似标准输出流的重定向,只不过改成了标准错误输出。重定向往往配合cat、head、tail、echo等命令一起使用,一些常见的使用示例如下:
xxxxxxxxxx
101$ who > users #将原本输出到屏幕上的当前用户的信息输出到文件users中
2$ echo "faker is a fake" >> users #将原本输出到屏幕上的字符串信息追加写入到文件users中
3$ wc -l < users #将默认键盘输入重定向从文件users读取数据,它从效果上基本等价于wc users
4
5# 在file1不存在的前提下执行下列命令
6# 此时cat指令会将users的内容输出到stdout,默认打印到屏幕
7# 并且还会将file1不存在的错误信息输出到stdout,默认打印到屏幕
8$ cat users file1 > text # 重定向stdout,将users文件内容打印到文件text中
9$ cat users file1 2> text # 重定向stderr,将file1文件不存在的错误信息打印到文件text中
10$ cat users file1 >& text # 同时重定向stdout和stderr,将文件内容和错误信息同步输出到文件text中
我们可以使用
wc
命令进一步的来理解重定向。wc指令和重定向
wc指令是一个用于统计输入数据中行数、单词书和字符数的指令,它是词组"word count"的缩写(不是上厕所的意思)。
wc后面如果不跟任何文件名,它会从标准输入(默认是键盘)中读取数据然后进行统计。例如:
注:在命令行输入ctrl + D表示输入EOF,结束键盘录入。
wc指令有一些常用的选项:
-l
:仅显示行数。
-w
:仅显示单词数。注:wc指令依靠空格、制表等符号的间隔来划分统计单词数,只是一个粗略的统计。
-c
:仅显示字符数。该指令如果紧跟文件名,表示从文件中读取数据,然后输出统计的结果。
比如:
wc text
和wc < text
,它们两个从效果上来说基本是等价的,如下图所示:都是统计文件text中的内容,功能是一样的,区别是
wc text
多输出了文件名,为什么呢?这就涉及到它们原理上的差异了:
wc < text
直接重定向了stdin缓冲区,wc进程只知道数据是从stdin缓冲区来的,但它不知道数据源已经从键盘变成了文件,自然就更不知道文件的名字了,所以wc < text
不可能输出文件的名字。
wc text
的数据来源于文件text的文件缓冲区,于是wc进程就知道数据源文件的名字,就可以输出文件的名字。重定向stdin是比较少见的操作,因为我们往往更喜欢直接用文件自己的文件缓冲区,这样可以获取更多的信息。
Gn!
grep是词组"globally search for a regular expression"的缩写,意为根据一个正则表达式进行全局搜索,它通常用于对文件内容进行搜索,功能十分强大,也很好用,要作为重点去学习和练习!
具体而言,该指令搜索的逻辑是:
按正则表达式去搜索匹配文件内容,如果文件中某一行匹配指定的正则表达式,grep命令则会显示这一行。
关于正则表达式,我们放到后面再具体讲解,这里我们先学习一下grep指令的基本使用——直接匹配查找目标字符串出现的行。
该指令的基本格式如下:
xxxxxxxxxx
261$ man grep
2grep - print lines matching a pattern
3#逐行遍历文件内容,然后打印那些能够匹配正则表达式的行
4
5格式:
6grep [选项] pattern [file]...
7常见选项:
8-E, --extended-regexp
9#使用扩展的正则表达式语法
10#建议使用正则表达式搜索时加上该选项
11-n, --line-number
12#显示匹配数据在源数据中的行号
13-i, --ignore-case
14#搜索时忽略大小写
15-v, --invert-match
16#显示不匹配正则表达式的行
17-c, --count
18#不再展示匹配行的内容,而是单单统计匹配行的行数
19-o
20#只输出匹配到的部分,而不是整行内容
21-C num
22#显示匹配行及其周围的行(上下文)。num 指定要显示的上下文行数。
23#该选项常用于展示日志内容
24-r
25#如果不添加该选项那么grep只能直接搜索文件内容,只能在后面添加普通文件名作为参数。
26#添加-r选项后表示在指定目录及其子目录中递归地搜索文件。
常见用法:
xxxxxxxxxx
61grep -nE "firmament" The_Holy_Bible.txt # 显示包含"firmament"的所有行内容,并显示行号
2grep -niE "GOD" The_Holy_Bible.txt # 忽略大小写的显示包含"GOD"的所有行内容,并显示行号。
3grep -nvE "god" The_Holy_Bible.txt # 显示不包含"god"的所有行内容,并显示行号。
4grep -cE "god" The_Holy_Bible.txt # 统计文件中包含"god"的总行数,打印出来。不打印任何文件内容
5grep -cvE "god" The_Holy_Bible.txt # 统计文件中不包含"god"的总行数,打印出来。不打印任何文件内容
6grep -rnE "恐怖如斯" . # 在当前目录下,递归查找文本"恐怖如斯"
如果只是简单的根据字符串内容直接匹配查找,功能未免过于弱小。grep之所以强大,主要还是体现在正则表达式上,下面我们一起来学习一下正则表达式的基本语法。
Gn!
正则表达式的语法还是比较复杂的,严格来说它算一门单独的课程内容,而且正则表达式的语法还存在不统一、不一致的情况。
不过好在,对于一般的程序员而言,几乎不用自己手写正则表达式,你可以通过各种正则表达式生成网站、AI工具等帮助你编写复杂的正则表达式。
但完全不懂正则表达式的语法,完全看不懂正则表达式也不行,所以我们在这里讲解一些正则表达式的基本语法、核心语法,以帮助大家能够看懂基础的正则表达式,这就是我们对大家的要求。
正则表达式的基础语法基于三个核心的概念:
基本单位
基本操作
基本单位出现的位置
下面逐一解释这三个概念:
Gn!
正则表达式的基本单位包含:
单个字符:直接匹配文本中的特定字符。例如,
a
匹配字符 "a"。
.
字符(点字符):匹配除换行符以外的任何单个字符。例如,a.b
可以匹配 "acb"、"a&b"、"a9b" 等。
*
字符(星号字符):匹配前一个字符零次或多次。例如,ho*
会匹配 'h', 'ho', 'hoo', 'hooo' 等。转义字符
\
:通过在特殊字符前加反斜线\
来取消这些字符的特殊含义,使其能够被直接匹配。例如\.
匹配实际的点字符 "."。集合,比如[abc]、[^abc],使用
[...]
来匹配指定集合内的任意单个字符或者^
取反表示任何集合外的单个字符。分组(expr),将
expr
视为一个单独的单位,允许对整个表达式应用重复操作等。例如,(abc)+
匹配一个或多个连续的 "abc"。任何复杂的正则表达式都是由以上基本单位组成的。
Gn!
基于基本单位,可以执行基本操作,从而获取更复杂的匹配逻辑。基本操作比较常用的有两个:
连接:简单地将两个或更多的基本单位放在一起,表示按照顺序匹配。例如:
"abc"这个正则表达式就是匹配字符串"abc",它连接了多个单个字符。
"[abc]x"集合和单个字符连接,表示可以匹配"ax"、"bx"以及"cx"。
".txt"是什么意思呢?是匹配所有以.txt结尾的字符串吗?
当然不是,不要挖个坑就跳下去了。
.
字符的作用是匹配除换行符之外的任何单个字符。所以这里是匹配诸如"atxt"、"btxt"、"ctxt"等这样的字符串。
思考一下,若想获取匹配".txt"的正则表达式,怎么办?答:
\.txt
重复:指定一个基本单位重复出现的次数。例如:
+
:表示至少出现一次。例如:
a+
匹配 "a"、"aa"、"aaa" 等。
abc+
表示单个字符c
至少出现一次。于是可以匹配"abc"、"abcc"、"abccc" 等。
[abc]+
表示集合[abc]
至少出现一次,这样匹配的内容就很多了,只要是包含一个或多个 "a"、"b" 或 "c" 的字符串都可以。比如 "a"、"ab"、"bca"、"cba"、"cccbbaaa" 等。
(abc)+
表示分组(abc)
至少出现一次。所以就匹配"abc"、"abcabc"、"abcabcabc"等这样的固定abc序列。
?
:重复零次或一次。例如:
abc?
即匹配"ab"和"abc"
[abc]?
即匹配""、"a"、"b"或者"c"
(abc)?
即匹配""或者"abc"
*
:重复任意次数,可以是零次,一次或多次。例如:
abc*
即匹配"ab"、"abc"以及"abcc"....
[abc]*
即匹配""、"a"、"b"、"c"以及包含多个 "a"、"b" 或 "c" 的字符串
(abc)*
即匹配""或者"abc"以及更多abc组成的序列
.*
表示匹配任意文本内容。你还可以在基本单位后面加
{m}
以更精确的控制重复次数:
{m}
:重复 m 次。
{m,n}
:重复 [m, n] 次。
{n,}
:至少重复 n 次,即重复[n, 正无穷)次。
Gn!
明确基本单位是什么,也搞清楚基本单位出现的规则和次数,那么接下来只需要搞清楚基本单位出现的位置,一个具有复杂匹配功能的正则表达式就诞生了。
grep
指令是一行一行匹配的,所以重要的、常见的位置有下面情况:
^
:匹配行的开始,行首。例如,^abc
匹配任何以 "abc" 开始的行首。
$
:匹配行的结束,行尾。例如,xyz$
匹配任何以 "xyz" 结尾的行尾。
\<
、\>
:分别匹配一个单词的开始和结束。单词同样是基于空格、制表符间隔去判断的。比如我想匹配一行,该行以".txt"结尾,怎么办?
答:
\.txt$
Gn!
创建一个text文件,其文件内容如下:
xxxxxxxxxx
141abc xyz abc hello
2xyz abc hello abc
3abc abc hello xyz
4xyz hello xabcabc
5abc
6hello abc
7hello abc goodbye
8xyzabcxyz
9xyzxyz
10yyyabcabcabc
11xzy
12xyz
13abcxyzabc
14aaabbbcc
执行以下grep指令,分析结果:
xxxxxxxxxx
91grep -nE "abc" text
2grep -nEv "abc" text
3grep -nE "^abc" text
4grep -nE "abc$" text
5grep -nE "^abc$" text
6grep -nE "\<abc\>" text
7grep -nE "x[yz]" text
8grep -E "(abc){2,}" text
9grep -nE "^x.*z$" text
实际结果如下:
"abc"
匹配包含子字符串 "abc" 的所有行。
-v "abc"
匹配不包含子字符串 "abc" 的所有行。
"^abc"
匹配以 "abc" 开头的行。
"abc$"
匹配以 "abc" 结尾的行。
"^abc$"
匹配以 "abc" 开头且以它为结尾的行,即此行仅有内容"abc"。
"\<abc\>"
匹配作为独立单词的 "abc"。
"x[yz]"
匹配x字符后面紧跟y或者z的行。
"(abc){2,}"
匹配"abc"最少出现两次的行。
"^x.*z$"
匹配所有以x开头,且以z结尾的行。
Gn!
请编写正则表达式,实现以下匹配功能:
匹配所有以f或F开头,以t结尾的单词。
参考答案
正则表达式直接写成
\<[fF].*t\>
对吗?其中.*
表示匹配任意字符但这样写很明显不正确,因为它会匹配空格和制表符,会跨单词匹配。
为了实现匹配单词的效果,可以写成:
\<[fF][^ \t]*t\>
这样就能够实现我们想要的匹配效果。
Gn!
shell命令是可以进行组合使用的,基于命令的组合,可以增强一些指令的作用。比如grep,就可以利用组合命令,大大增强它的功能。
命令的组合主要有三种方式:
cmd ; cmd2
,即两条指令间使用分号隔开,这个组合很简单就是单纯的先执行命令1,然后执行命令2。例如:
mkdir dir; cd dir
先创建dir目录,然后cd进入该目录
cmd1 | cmd2
,其中"|"是管道,整个命令表示将cmd1指令输出的结果,作为cmd2指令的输入。
cmd1 | xargs cmd2
,表示将cmd1输出结果的每一行作为cmd2指令执行的输入参数。第一种组合方式比较简单,不再赘述,下面两种方式我们专门讲解一下。
Gn!
|
是字符竖线,不是大写字母I,它表示管道,管道是进程间通信的一种方式,从效果上看:
cmd1 | cmd2
就表示将cmd1指令执行的输出结果,作为cmd2指令的输入。管道在这里是什么原理呢?
cmd1
和cmd2
指令的执行实际上是启动了两个进程,整个管道的原理如下图所示:管道是操作系统内核提供一种系统功能,它可以被想象为一个缓冲区队列,用于存储从管道一端输入的数据,直到另一端的进程读取它。
管道允许数据从一个进程流向另一个进程,是进程间通信的重要手段。
具体到
cmd1 | cmd2
指令:cmd1指令的进程会将stdout输出重定向到管道中,也就是cmd1进程的输出不再打印到屏幕上,而是将数据放入管道;
cmd2指令的进程会将stdin输入重定向到同一管道中,也就是cmd1进程的输入不再是从键盘接收录入,而是从管道读取数据。
于是
cmd1 | cmd2
指令就实现了:将cmd1指令的输出结果,作为cmd2指令的输入数据。
Gn!
history
指令会将当前会话输入的shell指令的历史输出到stdout中,进而打印在屏幕上。
tail -n 5
指令在不输入文件名的情况下会从stdin接收数据(默认是从键盘),然后将后5行输出到stdout中(默认是输出到屏幕中)。现在把它们用管道组合起来,得到组合指令:
history | tail -n 5
效果是什么呢?
很简单:
history
指令的输出作为tail -n 5
指令的输入,总的效果就是打印shell指令历史的后5行。再举一些例子:
ls -lh | grep -E "test"
:列出当前目录的详细列表,并通过grep
搜索包含"test"的行。
ll | head
:有些时候目录下的内容很多,可以用这种方式只看目录下的前十个文件内容。当然你可以使用tail,显示后十行。
tail -f server.log | grep -niE "error"
:持续显示系统日志文件中最新的内容,并且只显示包含"error"的行。这个指令可以使用:echo "This is an error test" >> server.log
来模拟日志文件追加写入,然后进行测试。
Gn!
xargs 是词组"extended arguments"的缩写,意为扩展的参数。
组合命令
cmd1 | xargs cmd2
的含义是:将cmd1指令输出的每一行(或者以空格、制表符分割),作为参数,然后传递给cmd2指令进程。使用举例:
比如现在的需求是:查找当前目录下所有的.c文件,然后找到文件内容中存在main函数的行。
这明显是一个组合需求:
查找当前目录下所有的.c文件:可以用指令
find . -name "*.c"
查找某个.c文件中存在main函数的行:可以用指令
grep -nE "\<main\(" xxx.c
利用xargs和管道,我们可以将这两个指令组合,得到指令:
find . -name "*.c" | xargs grep -nE "\<main\("
就可以实现需求了。
那么思考一下:
find . -name "*.c" | xargs grep -nE "\<main\("
和
find . -name "*.c" | grep -nE "\<main\("
有什么区别呢?
后者实际上是一个误用,它会导致
find
的所有输出都作为grep
的一个输入参数,这是不行的。再举一些例子:
| xargs
和rm
连用,可以实现根据条件删除目标文件,比如:
find ~ -type f -size +100M | xargs rm
:删除家目录下所有大于100Mb的普通文件
find . -type f -name "*.c" | xargs rm
:删除当前目录下所有的".c"结尾的普通文件
Gn!
首先要搞清楚一个事情:"xargs"是什么?
实际上"xargs"也是一个shell指令,直接执行这个shell指令会从标准输入stdin中读取数据,并将这些数据转换成一个一个的参数,并输出到标准输出当中。具体而言,会以空白字符(空格、换行以及制表符)为间隔划分参数,输出到标准输出中。
该指令的演示效果如下图所示:
所以,"cmd1 | xargs"实际上就已经是两个进程间利用管道通信:
cmd1指令的进程会向标准输出stdout中输出数据,现在将标准输出重定向到管道
xargs进程会从标准输入stdin中读数据,现在将标准输入重定向到管道
这样,cmd1进程的输出结果就成为了xargs指令进程的输入,示意图如下:
xargs进程得到cmd1进程的输出作为输入后,执行什么样的操作呢?
或者说,xagrs进程和cmd2进程之间是什么关系呢?它们之间还是基于管道的进程间通信吗?
实际上,xagrs进程和cmd2进程之间是没有进程间通信的,因为它们中间没有"|",没有使用管道。xagrs进程和cmd2进程之间,是一种"父子进程"的关系:
xargs
通过管道从另一个命令cmd1
中获得数据输入,接着xargs
进程解析这些数据,基于空格、制表符或换行符将数据分割成多个部分。这样操作后,xargs进程就得到了分割好的数据参数,xargs进程会将这些数据参数构造成命令行参数(就是程序入口函数接受的命令行参数)。
基于这个命令行参数,xargs进程会将参数传递并启动cmd2进程。
在这个过程中,由于cmd2进程由xargs传参启动,cmd2进程就是xargs进程的子进程。(Linux的shell指令背后的可执行程序基本上都是C程序)
基于以上原理,我们可以详细的分析一下指令:
find . -name "*.c" | xargs grep -nE "\<main\(
的执行过程:
find . -name "\*.c"
此命令在当前目录及其所有子目录中,递归搜索所有扩展名为
.c
的文件。找到的每个文件路径名都会输出到标准输出中,并且一行输出一个文件路径名。
| xargs
find
命令的输出本身会默认输出到显示器终端上,但管道操作符使得这个输出被重定向到了管道中
xargs
进程本身要从标准输入中读取数据,默认从键盘接收输入,但管道操作符使得这个输入被重定向到了管道中于是
find
进程输出的文件名列表,就作为了进程xargs
的输入
xargs
会把接收到的每一行文件路径名,分割好然后构建成命令行参数。
grep -nE "\<main\(
该指令本身会启动执行
grep
进程,从标准输入中查找符合正则表达式规则的内容。但由于
| xargs
结构的影响,xargs
进程会把构建好的文件路径名的命令行参数传递给grep程序,然后启动grep进程于是,这个
grep
进程会将find
进程搜索到的结果文件路径名的每一行,都视为一个需要搜索的文件,挨个完成搜索。最终,这个命令组合的含义是:查找当前目录下所有的.c文件,然后找到文件内容中存在main函数的行。
以上。
Gn!
我们可以用
ln
指令创建硬链接和符号连接(软链接),它是单词link
的简写。下面分别讲解一下这两种语法:
Gn!
创建硬链接的指令格式如下:
xxxxxxxxxx
11ln target_file link_name #创建一个目标文件的硬链接
解释:
target_file
是你要硬链接的原始文件的名字
link_name
是硬链接的名字举个例子:
xxxxxxxxxx
11ln test link_test #创建test文件的硬链接link_test
表示为test文件创建一个硬链接link_test,此时这两个文件会共用同一个inode。
注意:
硬链接直接指向文件的磁盘位置(inode),因此原始文件和硬链接实际上指向相同的物理位置。此时修改任意文件内容,其余文件内容会一起被修改。
硬链接不允许链接向目录,这是为了防止破坏目录结构的设计以及避免出现目录访问死循环。
为文件创建硬链接后,文件的硬链接数就会增加。当硬链接数为 0 的时候,才会真正删除磁盘上的文件。
Gn!
创建软链接的指令格式如下:
xxxxxxxxxx
11ln -s target_file slink_name #添加一个选项-s,表示soft软链接
解释:
target_file
是你要硬链接的原始文件的名字
slink_name
是软链接的名字软链接,也就符号链接,可以视为Windows系统中快捷方式或者C程序中的指针,指向另一个文件或目录。
举个例子:
xxxxxxxxxx
11ln -s test slink_test
表示为test文件创建一个软链接slink_test,它是独立的软链接文件,有自己独立的inode,此时访问slink_test就是访问原文件test。
如果删除软链接文件,原文件不受影响。
如果删除原文件,那么软链接文件就会指向一个不存在的文件,成为一个"死链接",类似空指针。
软链接可以链接到目录或者文件。
硬链接和符号链接的原理如下图所示:
对于大多数命令(rm除外),如果参数是符号链接,其实操作的是符号链接指向的文件(类似指针的解引用操作):
xxxxxxxxxx
41$ echo "I love xixi\n" > text
2$ ln -s text s_link
3$ cat s_link #输出结果是:I love xixi
4$ rm s_link # 删除符号链接文件本身,而不是它指向的文件
符号链接要使用的比硬链接多得多。
Gn!
scp
(Secure Copy)是一种在本地和远程计算机之间进行安全传输文件的工具。它基于 SSH(Secure Shell)协议,提供与 SSH 相同的安全性和认证特性,确保传输过程中的数据加密和安全。在Windows和Linux都可以使用该指令进行远程的上传和下载:
上传:将本地的文件复制到远程
下载:将远程的文件复制到本地
scp指令的使用和cp指令非常类似,本质上都是把src复制到目标dest,具体格式如下:
xxxxxxxxxx
91$ man scp
2scp -- secure copy (remote file copy program)
3#远程的安全的加密复制
4格式:
5scp [选项] SRC... DEST
6#本地路径:可以用绝对路径,也可以用相对路径
7#远程路径:用户名@IP:路径
8常用选项:
9-r #递归复制整个目录
一些使用举例:
xxxxxxxxxx
21scp wgd@IP:~/text1 . # 将wgd用户家目录下的text1文件下载到当前工作目录, 在Windows的cmd命令行中执行
2scp ./file wgd@IP:~ # 将当前工作目录下的file文件上传到wgd用户的家目录下, 在Windows的cmd命令行中执行
Gn!
tar指令一开始是用于创建、维护、修改以及提取 tar 归档文件的指令,所谓归档文件是一种将多个文件和目录(文件夹)组合成一个单一文件的方式。
但归档这种合并文件的操作使得它天然就适合同时进行压缩操作,所以
但到如今,你完全可以直接将
tar
指令理解成压缩和解压缩指令,我们日常使用该指令也是用于压缩和解压缩的。其使用的格式如下(这里只列出打包压缩相关的):
xxxxxxxxxx
11tar [主选项+辅选项] 包名 [文件或目录]..
解释:
主选项(有且只能选择其中一个):
c: 创建:创建新的归档文件。如果归档文件已存在,则此选项将覆盖原有文件。
r: 追加:追加文件到已存在的归档文件末尾。如果归档文件不存在,则会报错。注意若归档文件已压缩,需要先释放再追加。
x: 释放:释放(解压)归档文件中的内容。这会根据归档中的结构恢复文件和目录。
t: 查看:查看归档内容,列出归档文件中包含的文件和目录,但不提取它们。
辅选项:
f: 指定包文件的名称:该辅助项选择一般是必加的,用于指令下一个参数是归档文件的名字。否则tar无法得到哪些属于被归档文件,哪个是归档的结果。
v: 显示处理过程的详细信息。如果是查看归档文件,则显示文件的详细信息。该指令一般也建议都加上。
z: 使用gzip算法压缩或解压缩归档文件。
常见用法举例:
xxxxxxxxxx
61tar cfv package.tar test* #归档所有以test开头的文件/目录,但不压缩
2tar tfv package.tar #查看归档文件package.tar中的文件详细信息
3tar rfv package.tar The_Holy_Bible.txt #向归档文件package.tar中追加一个文件
4tar cfvz package.tar.gz test* #归档并压缩所有以test开头的文件/目录,得到的归档文件会比上面得到的更小
5mv package.tar.gz dir #将压缩后的归档文件移到到一个新目录
6tar xzvf package.tar.gz #释放(解压缩)package.tar.gz压缩文件
注意:
未压缩的归档文件建议命名为
.tar
结尾,而使用使用gzip算法压缩的归档文件建议命名为.tar.gz
结尾。已经压缩的归档文件不能直接使用
tar r xxx
追加新文件,需要先解压缩后重新压缩生成新文件。