王道C++班级参考资料
——
Linux部分卷1Linux基础
节3常用shell命令

最新版本V5.0
王道C++团队
COPYRIGHT ⓒ 2021-2024. 王道版权所有

王道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命令学会。

Shell命令的组成

Gn!

Shell指令通常由几个基本的组成部分构成,这些部分共同定义了它们的行为和执行的方式。一个Shell命令的大体由以下几部分组成:

  1. 命令(Command):

    1. 这是Shell指令的核心部分,必不可少。它指定要执行的操作,比如lsmvcpmkdir等。

    2. 命令可以是shell进程本身内置的shell命令,也可以是外部程序。最常见的shell内置命令就是cd,外部程序则多了(which命令就用于查找外部程序)。

    3. 以C语言程序员的视角看,命令其实就是一个可执行程序的名字,它表示会实现某个功能。

  2. 选项(Options):

    1. 选项通常以单个连字符-开始(短选项),或者以两个连字符--开始(长选项,长选项大多可以省略为短选项)。

    2. 选项不能单独存在,要和命令一起连用,用于改变命令的最终行为。

    3. 例如,ls -l 中的 -l 是一个选项,它告诉ls命令以长格式列出目录内容。

    4. 以C语言程序员的视角看,选项其实就是一个可执行程序启动时的特殊命令行参数,用于修改可执行程序的最终行为。

  3. 参数(Arguments):

    1. 参数是传递给命令的额外数据。

    2. 例如,在cp source.txt dest.txt命令中,source.txtdest.txt是参数,它们指定了cp命令的源文件和目标文件。

    3. 以C语言程序员的视角看,参数其实就是一个可执行程序启动时所需要的参数,用于传递可执行程序执行时所需要的数据。

对于大多数Shell指令而言,就仅有上述三部分组成,而一些shell命令可能还会多以下组成部分:

  1. 重定向(Redirection):

    1. 重定向用于改变命令的标准输入和输出。

    2. 使用><>>等符号可以将命令的输出发送到文件,或者从文件中获取输入。

    3. 例如,ls > files.txt会将ls命令的输出保存到files.txt文件中。

  2. 管道(Pipes):

    1. 管道符号|用于将一个命令的输出直接作为另一个命令的输入。

    2. 例如,cat file.txt | grep "search string"会查找file.txt中包含"search string"的所有行。

重定向和管道都是比较重要的指令组成部分,在本章节的后面我们将学习它们。

关于内置指令和外部可执行程序的补充

Linux的默认shell版本是bash,shell命令当中有些是bash进程的内建指令,有些则是外部可执行程序。

内建指令比较常见的有:

  1. alias - 定义或显示别名。

  2. cd - 更改当前工作目录。

  3. echo - 显示一行文本。

  4. exit - 退出 shell。

  5. history - 显示命令历史记录。

  6. pwd - 打印工作目录。

  7. type - 显示一个命令的类型(是内置命令、外部程序还是别名)。

对于一个shell指令,可以使用type指令来确定,如下图所示:

type指令使用示例-图

当然我们还可以用which来查询可执行程序的位置,比如:

type指令使用示例-图2

这说明像echopwd这样的shell指令,它们既有内建版本,也具有外部应用程序版本。在这种情况下,Bash 通常会优先使用它们的内置版本,因为内置版本执行更快且直接与 shell 的内部结构集成。

除了上述补充外,这里再补充一个关于一个shell命令中——空格,双引号,单引号的作用:

shell命令中空格,双引号以及单引号的作用

在shell命令中,空格、双引号和单引号各自有着重要的作用:

  1. 空格(或多个空格)

    1. 空格在shell命令中通常用作参数或命令之间的分隔符。

    2. 例如,在命令 ls -l /etc 中,空格分隔了命令 ls、选项 -l 和目录参数 /etc

    3. 如果参数中包含空格,则必须使用引号将整个参数括起来,否则shell会将其作为多个参数解释。

    4. 实际上,大多数shell命令背后就是一个C可执行程序,既然是这样,shell命令里的空格就类似于C可执行程序里输入多个命令行参数时的空格。

  2. 双引号 ("..."):

    1. 双引号用于创建包含空格、制表符或换行等特殊字符的参数或变量值

    2. 当你想在参数中包含这些特殊字符时,可以使用双引号来确保shell命令正确理解它们。

    3. 例子1:当你想在终端输出"hello world!"时,由于该字符串包含一个空格,所以需要用双引号或单引号括起来。这个指令可以写作:

      1. echo "hello world!"

      2. 也可以是echo 'hello world!'

    4. 那么双引号和单引号有什么区别呢?一个比较常用的区别是:双引号可以用于识别shell的内置变量。

      1. 指令cd -的执行需要依赖Linux的系统环境变量env当中的变量OLDPWD

      2. 你可以用echo "$OLDPWD"输出当前该变量的取值,但echo '$OLDPWD'会向终端输出字符串"$OLDPWD",因为单引号内的内容会被视为字面量字符串。

  3. 单引号 ('...'):

    1. 单引号的作用类似于双引号,它们也用于确保字符串中的空格被正确处理,不被shell分割。

    2. 与双引号不同的是,单引号内的内容会被视为字面量字符串,shell不会进行任何变量替换或命令替换。

    3. 例如,echo '$VARIABLE' 会原样输出字符串 $VARIABLE,而不是输出变量 VARIABLE 的值。

简单来说,空格用来分隔命令的不同部分,双引号用来防止分隔带有空格的字符串并允许输出变量的取值,单引号用来防止分隔带有空格的字符串并且引号内的内容都视为字符串处理。

man命令

Gn!

Linux系统内置了一套帮助手册(manual),来帮助使用者使用Linux操作系统以及查阅各类相关的官方标准资料。

该手册分为很多卷:

第一卷是用来查看 shell 命令的;第二卷是用来查看系统调用相关信息的;第三卷是用来查看库函数信息的...

具体而言如下:

卷数卷名称解释
1用户命令主要是shell命令和可执行程序的文档
2系统调用主要包含系统调用和系统相关函数的文档(以C语言风格对外暴露的接口函数)
3库调用主要包含与C语言函数库相关的文档,包括ISO-C和POSIX标准的C语言库函数。
4特殊文件描述系统上的特殊文件,通常指的是位于 /dev 目录下的设备文件和接口
5文件格式和规范包含各种文件格式、配置文件、协议等的文档。
6游戏包含游戏等娱乐软件的文档。
7杂项包含不易分类的各种文档,如宏包、约定等。
8系统管理命令包含系统管理员用于管理整个系统的命令和守护进程的文档。这些命令通常需要较高的权限才能执行。
9内核例程和内核开发相关的文档与手册(非标准,有时没有)

该手册内容很多,但我们最常用和查看的是前三卷。

该命令的使用方式如下:

其中[手册编号]是可选的,若不指定手册的编号那么默认会从1号手册开始进行查找。

以下是一些具体的使用案例:

进入帮助界面后,我们可以按下面按键浏览帮助信息:

man手册中内容主要以英文为主,如果你英文不是特别好,可以执行下列指令安装中文包:

但一般还是不建议搞这种中文包,意义不大,纯看英文原汁原味会更好。

关机和重启命令

Gn!

注意:关闭主机之前,请务必先关闭虚拟机!否则,可能会损坏虚拟机文件,导致不能启动虚拟机。

关机和重启命令,都需要管理员权限。所以:

  1. 要么以root用户登录

  2. 要么在指令前加上sudo表示用管理员权限执行命令。

命令如下:

具体而言可以这么用:

用户子系统命令

Gn!

Linux操作系统是多用户的操作系统,而且具有以下特点:

  1. 单个用户可以(同时)多次登录同一台Linux机器

  2. 多个用户也可以(同时)登录同一个Linux系统,这与我们常用的Windows桌面版系统不太一样。

这是因为Linux系统多用于服务器系统,一台服务器显然不太可能仅供一个人使用。使用Linux系统就像是入住一间豪宅,豪宅有它的主人,家人和客人。

Linux系统用户分类

Gn!

类似地,Linux系统的用户大致也可以分为两类:

  1. 特权用户,也叫超级用户或管理员用户。

    1. 特权用户的名字只有一个,固定为root。

    2. 在系统中,root 用户的 UID(用户标识符)是 0

    3. Linux 系统中只有一个 root 用户

    4. root用户拥有系统的最高权限,可以使用系统的所有功能——包括删除其它用户这样的敏感操作。

  2. 普通用户

    1. 普通用户是为日常工作和活动创建的用户账户。

    2. 普通用户可以有很多个,它们只能使用系统的部分功能,名字可以多种多样。

    3. 普通用户的权限受到限制,不能执行可能会影响系统稳定性和安全性的操作。

普通用户实际上可以进一步的细分为两类:

  1. sudo用户,也称之为"sudoer",即英文单词"superuser do"的缩写。

    1. sudoer可以使用sudo命令来临时提高自己的权限(假传圣旨),从而可以执行一些(而不是所有的)特权命令。

    2. 比如一个普通用户,若想要使用apt install命令在系统上安装软件或者使用shutdown命令关机,都需要加上sudo开头等等。

  2. 非sudo用户。不具备使用"sudo"临时提升权限的能力。

普通用户的shell命令提示符是$,root的shell命令提示符是#,如下图所示:

root用户和普通用户-图

关于普通用户当中的sudoer用户,可以查看下面的补充内容:

如何查询和创建一个sudoer用户?

我们把可以用sudo提升权限的普通用户称之为sudoer,你可以通过指令查看当前系统中的sudoer用户:

该指令的作用是:在"/etc/group"文本文件中搜索包含"sudo"这个字符串的行。

"/etc/group"文本文件是一个Linux系统配置文件,包含了该Linux系统上的用户组信息。

我们在安装Ubuntu时创建的初始用户就是一个拥有sudo权限的普通用户,就是一个sudoer

当然并不是所有的普通用户都是sudoer,如果你希望将某个用户添加为sudoer,可以使用指令:

激活root用户

Gn!

Ubuntu刚安装时,默认是不激活root用户的,创建的第一个用户只是一个具有sudo权限的普通用户。

若想激活root用户,需要使用以下指令,给root用户设置密码:

注意输入密码时和Windows不一样,它不会显示键入了多少字符。但你不用担心,只管输入后回车即可。

若激活了root用户,也请你把密码记录一下避免忘记密码。

查看所有用户

Gn!

Linux系统下的所有用户,在 passwd 配置文件中都有一条相关记录。我们可以通过下列指令打开这个配置文件:

比如我们可以找到我们创建的用户的信息:

wgd:x:1000:1000:WGD:/home/wgd:/bin/bash

每一行对应一条记录,每条记录有多个字段,字段之间以 : 分割。我们可以在5号手册中查看 passwd 文件的格式和规范:

以下内容复制粘贴自man手册:

"/etc/passwd"文件中每个用户账户包含一行,包含使用冒号 (“:”) 分隔的七个字段,分别是:

  1. 登录名

  2. 登录密码(现已被x代替,避免明文存储密码导致安全问题)

  3. 用户UID

  4. 用户组GID

  5. 用户名以及可能存在的用于解释用户名的注释字段

  6. 用户的家目录

  7. 用户登录的shell程序版本

wgd用户的家目录是:/home/wgd,注意/home本身不是家目录。

wgd用户使用的shell版本是:bin/bash,这里就是一个可执行程序的路径名。

为了安全考虑,Ubuntu会把密码加密后存储在/etc/shadow文件中,你可以用下列指令查看这些加密密码:

你可能还发现一个细节:查看passwd文件,出现了很多不认识不了解的用户,那些是什么用户呢?

其实这些用户都属于系统用户,也称为伪用户,这些用户的存在,为不同的系统服务和进程提供了不同的身份和权限,是Linux系统的一种安全机制,当然它们都不能用于登录操作。

useradd添加用户

Gn!

我们可以使用 useradd 命令来添加用户,格式如下:

比如我们可以这样用:

注意:

-s /bin/bash必须放在一起,但它和-m的顺序是无所谓的。

细节:

执行这些命令用于创建一个新用户,我们会发现一个现象:指令执行完毕后,没有任何反馈信息,没有创建用户成功这样的提示。

这里就涉及到Linux的设计哲学了:

指令执行,没有反馈没有消息,就是最好的反馈最好的消息。

所以当你输入一条指令执行,没有给出任何反馈,那就说明执行成功了。相反,如果给出反馈信息,那你就需要注意了,因为:

  1. 这有可能是指令执行失败了,反馈是错误信息。

  2. 也有可能是给出的一些重要的提示。

总之Linux的这种设计旨在简化命令行的输出,只在必要时才提醒用户,从而提升效率和清晰度。

userdel删除用户

Gn!

我们可以使用 userdel 命令来删除用户,格式如下:

比如我们可以这样用:

passwd设置密码

Gn!

我们可以使用 passwd 命令来给用户设置密码,普通用户通常只更改其自己账户的密码,而超级用户和sudo可以更改任何账户的密码。

比如我们可以这样用:

需要注意的是:

利用sudo提升权限,passwd指令可以修改任意用户的密码,即便你忘记了此用户的密码也没有关系。

su切换用户

Gn!

我们可以使用 su(switch user) 命令切换到另一个用户,格式如下:

比如我们可以这样用:

注意,使用"su"指令时若不添加sudo一般需要输入对应用户的密码才能够登录,使用sudo权限则可以不输入密码即可登录任意用户。

exit退出当前用户

Gn!

"exit 命令"可以用来退出当前登录用户,转而切换到上一次登录的用户。

从实际上来说,当你使用su命令切换到另一个用户时,实际上是在当前终端会话中启动了一个新的 shell 进程,该进程以目标用户的身份运行。

当你使用exit命令退出当前登录用户时,实际上就是结束了当前 shell 进程,并将控制权还给之前的 shell 进程。

整个suexit的过程比较复杂,牵扯到操作系统进程的管理,但我们可以粗略、通俗的将这个过程理解成栈模型的应用:

  1. 执行su命令,这个过程可以看成是新用户会话进栈的过程,栈顶用户会话就是当前登录用户

  2. 执行exit命令,这个过程可以看成栈顶用户会话出栈的过程,那么前一个登录的用户就会成为新的当前登录用户。

可以用下图来理解:

切换用户栈-图

既然使用一个这样的栈结构来维护用户登录,那么当某个用户存在栈结构中时,该用户是无法被删除的。

总结

Gn!

用户子系统命令并不是特别重要,我们以后从事的方向是开发,而不是运维。

在大多数情况,开发能获取的Linux账号只是测试环境下的非sudoer普通用户,这种账号一般只能从事创建、编辑这样的日常开发工作,往往连删除权限都没有,而用户管理分配这样的sudo操作就更无法执行了。

但我们还是建议大家了解一下这些指令:

  1. 一方面,也不是所有公司开发和运维都严格区分,有些小公司开发会兼职运维。

  2. 另一方面,了解它们对提升个人技能,增强对Linux系统的理解都是有帮助的。

Linux文件管理系统(重点)

Gn!

在Linux系统中,程序员最常使用的命令肯定是和文件管理系统相关的指令,即"文件子系统命令"。但在具体讲解,学习这些指令之前,我们需要对Linux文件系统有一个基本的了解。

这一小节虽然基本不涉及任何Shell命令,但属于Linux系统的基本原理,是重点中的重点,一定要认真学习和理解!!!

Linux文件管理系统的特点

Gn!

Linux的文件管理系统和Windows有很大的区别:

  1. Linux是以树形结构来管理文件系统的,树的根结点就是根目录,用/表示。但Windows操作系统中,每一个盘符都可以视为一棵树,所以 Windows 下的文件系统是一种"森林"模式。

  2. Linux是以分门别类的方式来管理文件的,也就是说,我们会将功能相似的文件放到同一个目录下进行管理。但Windows操作系统中,更习惯于将同一套内容直接集中放在一起,而不是按功能分类存放。

    1. 比如在Linux安装QQ,那么可执行程序文件、配置文件、用户数据等可能放在不同的目录下。

    2. 而在Windows当中安装QQ,往往就是QQ文件夹,所有相关内容都放在一起。

Linux常见目录的功能

既然Linux是以分门别类的方式来管理文件的,那我们就需要知道,以下常见的Linux目录以及它们存放的内容:

Linux常见目录的功能-表
目录名目录的功能作用
/bin(binary)存放可执行程序或脚本文件
/sys(system)存放和系统相关的文件
/dev(device)存放设备文件
/etc一般用来存放配置文件和启动脚本
/lib(library)存放系统库文件
/var(variable)存放变化很快的文件,比如日志文件
/proc(process)存放进程相关的数据
/rootroot用户的家目录
/home/{username}普通用户的家目录
/usr用于存放用户可执行程序、库、文档等资源。

bin目录下存放的是Linux的可执行文件或脚本文件,在Linux中可以用命令./文件名执行该可执行文件或脚本文件(当然,前提是该文件具有执行权限)。

关于执行权限可以通过以下命令添加:

/etc目录,主要用来存放配置文件以及启动脚本。比如我们之前见过的Linux用户配置文件:/etc/passwd

lib是library的缩写,/lib目录用于存放系统的库文件,库文件和头文件不是一回事。头文件仍属于人能看懂的,作用于预处理阶段,而库文件是作用于链接阶段的文件,库文件都是机器二进制指令,人眼就看不懂了。

最后我们再谈一下家目录:

  1. 首先还是要强调/home目录不是家目录,/home/{username}才是普通用户的家目录。

  2. 家目录的具体目录和~是等价的,一回事。

  3. 对于普通用户而言,大多数情况下,只能在自己的家目录中创建文件!我们以后写代码,也会在家目录下创建文件。

除了上述知识概念外,还有两个我们需要记住的系统目录:

  1. 系统的标准库文件目录(包含系统动态库和静态库文件的目录)是:/usr/lib

  2. 系统的标准头文件目录(C语言的标准头文件存放的目录)是:/usr/include,该目录下的头文件允许使用<>包含。

Linux虚拟文件系统

Gn!

首先我们理解一个概念:虚拟文件管理系统(Virtual File System),简称VFS

既然有虚拟的文件管理系统,那么对应的就有"真正的、真实的"物理文件管理系统,物理文件管理系统涵盖了硬盘上的文件存储、目录结构和数据组织方式等物理层面的细节。

如果让用户直接使用这种"物理文件系统",有什么问题呢?

问题显而易见:

不同的操作系统平台、不同的存储设备(机械硬盘、固态硬盘等),物理文件系统肯定会有所不同,那么用户就需要理解这些差异,增加了操作的复杂性,出错的概率也大大增加。(当然最重要的是用户才懒得学习这些计算机偏底层的内容)

那怎么办呢?

为了屏蔽这些硬件层面带来的差异性,简化用户学习成本,提高用户使用体验,我们就在物理文件系统上加一层:虚拟文件管理系统

虚拟文件管理系统-图1

用户不直接使用真实文件系统,而使用虚拟文件管理系统。

通过这种方式,用户与文件系统的交互被统一和简化,因为他们现在面对的是统一的虚拟文件系统界面,而非多种不同的实际文件系统。

在Linux平台下,虚拟文件管理系统是纯粹的树形结构,如下图所示:

虚拟文件管理系统-图2

Windows操作系统的虚拟文件管理系统也和Linux类型,只不过Windows系统中的一个盘符就是一棵树,Windows系统允许多个盘符同时存在,可以理解成"森林"模式。

了解:

现在比较流行的物理文件管理系统:

  1. Windows,主流的目前是NTFS

  2. Linux,有ext2、ext3、ext4等,其中ext4是最主流的默认选择。可以通过指令df -Th指令查看。

硬链接和硬链接数

Gn!

类比虚拟内存空间和物理内存地址的映射,虚拟文件系统(VFS)中的文件和物理文件系统中存储的数据也存在映射的关系。

基于这样的一种映射关系,完全可能出现多个VFS中的文件,映射到硬盘上的同一块区域。如下图所示:

硬链接-图示

这种"多对一"的映射关系,就是硬链接:

硬链接允许VFS当中的多个文件可以映射到物理文件系统的同一文件中,也就是说,尽管在VFS中,这些文件或目录看起来可能位于不同的位置,或者有不同的名字,但它们实际上都代表同一份数据和信息。

那么什么是硬链接数呢?

硬链接数指的是一个文件拥有的硬链接数量,类似于"有多个指针指向了同一片内存空间",于是"指针的总数量"就被称之为"硬链接数",即VFS中有几个文件指向同一份数据。

比如上图的"文件A",VFS中的file1和file2文件都指向它,它的硬链接数就是2个。同时file1file2的硬链接数也是2个。

为了加深大家对硬链接的理解,我们在dir目录下,新建一共目录test。此时原本dir目录的硬链接数应该如何变化呢?

硬链接-图示2

答案:原本dir目录的硬链接数量是2,新增test目录后硬链接数变成了3!

为什么?

原本的硬链接数是2,原因如下图所示:

硬链接-图示3

创建test目录后变成了3,原因如下图所示:

硬链接-图示4

所以我们实际上可以得出一个结论:Linux的文件系统实际上就是依靠文件、目录以及硬链接等概念组合起来的。

补充:

硬链接数的设计,是一种"引用计数法",它是内存管理当中非常重要的一种算法策略。

在文件系统中,进行文件的删除,实际上执行的操作就是将"硬链接数--"

当硬链接数减少到0时,文件系统会认为该区域不再被任何文件/目录引用,此时它才会释放该区域内存空间。

inode的概念

Gn!

inode是词组index node的缩写,即"索引结点"。

inode是物理文件系统中的概念,你可以将它视为物理文件系统中文件的"入口或起始位置"。它是物理文件系统中某个文件的唯一性标识,索引。

VFS中的文件通过硬链接链接到物理文件系统中的文件,就是通过链接到inode完成的。

我们可以用下图来描绘inode这个概念:

inode-图示

inode除了视为文件的入口,还会存储该文件的元数据信息,比如:权限、所有者、大小、时间戳等。

所以:

  1. 硬链接实际上是VFS中的文件对物理文件系统中特定inode的引用。

  2. 硬链接数实际上是链接到某个inode上文件的个数。

  3. 当链接到inode上的文件数量等于0时,文件的数据才会真正被释放。

目录相关命令

Gn!

文件子系统命令是我们学习shell命令的重点,重中之重。文件子系统命令,再进行细分,可以分为两大类:

  1. 目录相关命令

  2. 文件相关命令

在本小节,我们先来看一些和目录相关的文件系统shell命令。

pwd查看当前工作目录

Gn!

命令pwd可以用来查看当前工作目录的完整路径,它的英文全称是 "Print Working Directory",意思是打印当前工作目录。

使用示例如下:

这样可以输出当前目录:

/home/wgd

非常简单。

cd改变当前工作目录

Gn!

cd命令是一个非常常用的shell命令,它的作用是改变当前工作目录。cd 是 "Change Directory" 的缩写。

该指令的格式如下:

该指令有以下常用方式:

cd - 这个命令并不常用,但需要注意的是它并不是栈结构管理目录跳转的,而仅是用环境变量中的OLDPWD属性来保存上一次目录。

我们可以通过env命令来查看Linux系统环境变量。

依据以上特点,如果频繁使用cd -命令实际上会在两个目录之间"反复横跳"

mkdir创建新目录

Gn!

mkdir命令用于创建目录(文件夹),它的英文全称是 "make directory"。

该指令的格式如下:

常用方式:

注意:

  1. mkdir只能用于创建目录,它不会创建文件。

  2. 不要用后缀名来区分文件和文件夹(目录):

    1. 有后缀名的也可以是目录名。比如一个目录叫"a.txt",这虽然不太正常,但完全可以。

    2. 没有后缀名的也可以是一个文件名。比如一个文件叫"a",在Linux系统中,可执行文件都是没有后缀名的。

  3. 在创建多级目录时,如果父目录不存在,则必须加上选项-p,否则会创建失败!

rmdir删除空目录

Gn!

rmdir命令用于删除空目录(文件夹),它的英文全称是 "remove directory"。

注意:rmdir命令只能用于删除空目录!!!

该指令的格式如下:

常用方式:

对于指令rmdir -p a/b/c,带上-p选项后,指令的作用是:

  1. 它会先检查a/b/c目录,若目录为空,则删除该目录。

  2. 然后递归检查a/b目录,若目录为空,则删除该目录。

  3. 最后递归检查a目录,若目录为空,则删除该目录。

  4. 在这个过程中,发现任一目录非空,则删除停止。

通配符

Gn!

Linux系统的通配符(wildcard character)是一种用于模式匹配的特殊字符,常用于指令中的文件名匹配和搜索操作。通配符允许你指定一个模式,然后搜索符合该模式的文件名或其他字符串。

通配符非常强大好用,希望大家熟练掌握它。

比较常用的通配符(模式)有:

  1. *:表示匹配0个或者多个任意字符

  2. ?:表示匹配1个任意字符

  3. [...]:根据中括号内的字符进行匹配。

    1. [characters] 表示匹配中括号内的任意一个字符。

    2. [!characters] 表示匹配中括号外的任意一个字符。

我们可以在某个目录下使用指令:

执行以下指令:

效果是什么?会删除目录:dira dirb dirc

执行以下指令:

效果是什么?会删除目录:dir1 dir2 dir3 dirx diry dirz。但不会删除dir目录,因为要匹配1个字符

再次执行以下指令:

效果是什么?会删除目录:dir diraaa dirbbb dirccc,此时可以匹配0个字符。

最后执行指令:

所有刚才新建的目录都会被删除。

ls查看目录/文件的内容信息(重要)

Gn!

ls同样是一个非常常用的命令。英文全称是 "list directory contents",即展示目录内容,但实际上它有两个作用:

  1. 如果输入文件名,该指令会展示该文件的相关信息

  2. 如果输入目录名,该指令会展示该目录下的内容和相关信息。

它的基本使用格式如下:

一些比较常见的使用方式:

下面解释一些可能产生疑惑的地方。

-a 选项

Gn!

当我们使用ls -a指令展示目录内容时,会发现任何目录(包含空目录)中一定会存在...,它们存在的目的是什么呢?

ls指令a选项-图

实际上,它们两的存在是为了配合cd命令:

  1. .中存储的是当前工作目录的路径

  2. ..中存储的是当前工作目录的父目录的路径

所以我们通过命令:

  1. cd .会继续留在当前工作目录。

  2. cd ..会跳到当前工作目录的上级目录。

-i选项

Gn!

当我们使用ls -i指令时,会在文件名的前面显示一个整数值,这个整数值是什么呢?

ls选项i-图示

这个整数值就是文件的inode编号,当两个文件的inode是同一个时,表示它们指向物理文件系统中的同一个文件。

比如:

ls选项i-图示2

上图中的两个目录的inode编号是一样的。为什么呢?

-l选项(重点)

Gn!

ls的所有选项中,最重要的选项是"-l",在某个目录下执行ls -l指令。得到如下图所示的内容:

虚拟文件管理系统-图3

下面逐一解释这些内容:

  1. 总用量大体上描述当前目录下所有文件和子目录占用的磁盘空间的总和(磁盘块的数量),数字越大占用空间越大,但这个数值往往不是真实磁盘空间占用。

  2. 第一列数据,即第一列的字符,诸如ld-等。它们的含义是:

    1. -表示它就是一个普通文件。

    2. d表示它是一个目录文件夹。d实际上是directory的缩写。

    3. l表示它是一个符号链接(也叫软链接),即指向另一个文件的引用。通俗的说,它就是Windows操作系统的快捷方式。字符l是词组"symbolic link"的缩写。

    4. c表示一个字符设备文件。需要注意的是,Linux的设计哲学中一切皆文件,会把那些计算机的物理设备也当成文件进行处理。所谓字符设备文件,就是逐字符数据传输处理的硬件设备。典型的有:鼠标、键盘、显示器等。c实际上是单词character的缩写。

    5. b表示一个块设备文件。所谓块设备文件指的是以固定大小的块为单位进行数据传输的设备,典型的就是硬盘。早期的机械硬盘一次性最少访问512个字节,也就是一个块是512个字节,到了现在固态硬盘一次性访问的块普遍是4096字节或者更大b是单词block的缩写。

    6. s表示本地套接字。它主要用于进程间通信,这个我们后面系统编程会讲到。s是单词socket的缩写。

    7. p表示有名管道(也叫具名管道),它也主要用于进程间通信。p是词组named pipe的缩写。

  3. 从第二列开始,到第十列,一共九列数据,也就是9个字符,把它们分为三组:

    1. 前三列:表示拥有者的读、写、执行的权限。若有权限则分别用r、w、x表示,若没有权限则用-表示。

    2. 中间三列:表示拥有者组的读、写、执行的权限。若有权限则分别用r、w、x表示,若没有权限则用-表示。

    3. 后三列:表示既不是拥有者也不是拥有组的其它用户的读、写、执行的权限。若有权限则分别用r、w、x表示,若没有权限则用-表示。

    4. r是单词"read"的缩写,w是单词"write"的缩写。可能有些同学对可执行权限表示的字母x存在疑惑,x其实不是任何单词的首字母缩写,它取自单词"execute"的第二个字母"x"。之所以不取首字母e,是因为e在很多地方都被占用了。

紧接着上面一堆wxr-这些权限的标记后,会存在一个数字:1、2等。这个数字是什么意思呢?

这个数字的意思是该文件/目录的硬链接数,关于硬链接数的概念上面已经讲解过了。这里不再赘述。

再往后面,ls -l指令输出的内容就比较简单了:

  1. 第一个wgd表示此文件的拥有者用户名

  2. 第二个wgd表示此文件的拥有组的组名

  3. 第三个数字表示文件的大小,默认以字节为单位。如果你希望用更大、更好理解的单位表示大小,则可以添加上"-h"选项,即使用命令"ls - lh"。如果你还希望展示隐藏文件,可以使用命令"ls -alh"。

  4. 再往后就是文件的最后修改时间,注意不是创建时间。

  5. 最后是文件的名字。

以上,最后再强调一下ls -l真的特别特别常用和重要,一定要把它输出的内容意义全部记住!!!!

tree以树状结构显示目录内容

Gn!

如果你希望更直观、更清晰看到某个目录文件系统的树形结构,可以使用指令tree,效果如下图所示:

tree命令-图1

tree 命令通常不是 Linux 系统默认自带的shell命令,使用之前,可以使用以下命令安装 tree

tree命令的使用格式如下:

常用方式:

tree命令展示内容的末尾还会显示目录下的总文件夹数,以及总文件数。

cp复制文件或目录

Gn!

cp(copy)命令用于复制文件或目录。它是一个非常常用的命令,可以将文件目录复制到另一个位置,类似于Windows操作系统中的"复制 - 粘贴"操作。

该命令的使用格式如下:

常用方式举例:

特别需要注意的是:

  1. 使用 cp 命令复制创建新文件时,实际上是在创建一个内容相同但是具有不同inode编号的新文件:

    1. 尽管新旧文件的内容相同,但它们在文件系统中被视为两个独立的实体

    2. 并且各自拥有不同的物理存储空间和inode编号。

  2. 使用 cp 命令覆盖目标文件时,会直接覆盖目标文件的内容,目标文件的inode并不会发生改变。这个过程一般不会修改文件的元数据(但最后修改时间、大小等这些内容会发生变化),而是修改了文件的内容。

  3. 当复制的源是一个目录文件夹时,必须要加上-r选项,否则指令会直接执行失败报错。

mv移动文件和目录

Gn!

mv(move) 命令可以用来移动文件和目录,除此之外,我们还可以用 mv 命令对文件和目录重命名。

其使用的格式如下:

常用方式举例:

几个小细节:

  1. Linux系统中文件的重命名不要简单理解成修改文件名,修改文件路径也属于修改文件名的一种。所以mv [选项] SOURCE DEST,总是在重命名文件,即便改变了文件路径名。

  2. mv移动文件时(DEST是一个普通文件时)的覆盖和cp指令显然不同,mv不是文件内容数据上的覆盖,而是直接删除DEST文件,然后将源文件重命名。

    这一过程,可以通过查看inode编号来理解,如下图所示:

    mv命令的覆盖-图

    被覆盖文件实际上在VFS中已经被删除了,源文件则通过重命名"霸占"了它的位置。

    所以mv命令的覆盖更彻底,也更危险,要小心使用。

    cp和mv这两个指令,覆盖文件的过程,可以根据下面两张图来理解:

    mv命令的覆盖-图2

    cp指令的覆盖要修改文件内容,涉及到物理文件系统的操作。

    mv命令的覆盖-图3

    mv指令的覆盖仅涉及VFS,不涉及物理文件系统。所以mv命令执行的效率会更高。

  3. 最后思考一个问题:cp复制目录时需要加上-r选项表示递归复制子目录,为什么mv指令移动目录就不需要呢?

如果你能够理解上面两张图讲解的cpmv命令的差异,那这个问题将非常容易回答:

cp复制目录涉及到整个目录下所有文件内容的复制操作,必然需要递归深入到每一个子目录才能够操作完成。

mv指令只不过是重命名罢了,相当于"改变了指针的指向"。被移动目录下的所有内容都不需要改变,自然不需要递归。

mv命令移动目录-图

以上。

rm删除文件和目录

Gn!

rm(remove)命令可以用于删除文件和目录,也是比较常用的命令。

它的基本格式如下:

常用方式举例:

注意:

  1. Linux没有Windows操作系统"回收站"的概念,一旦rm删除文件几乎不可能恢复,所以使用该命令要小心谨慎。

  2. if选项不要同时使用,因为f会屏蔽i选项

  3. rmdir只可以用于删除空目录,rm则更强大。只需要添加选项-r就可以递归的删除非空目录,-rf混用则可以无提示的删除一个目录。

著名的恶作剧指令:rm -rf /

指令起别名

Gn!

在C语言中,我们学习过typedef关键字给类型起别名,在Linux操作系统中,命令也是可以起别名的。

有些shell命令明显比较长,自然我们就想给这些命令起一个简短的别名,方便以后使用,alias 命令就是用来做这个事情的,这个单词翻译过来就是别名、外号的意思。

其基本的使用格式如下:

常见用法示例如下:

有一个别名大家需要记住:ll,它实际上是ls -alF指令的别名,它是Linux系统默认存在的指令别名。这个ll的别名提供了一个快速的方式来显示当前目录下的所有文件和目录的详细列表,包括隐藏文件(以.开始的文件)。

其中的 -F 这个选项在列出的每个文件名后添加一个字符来指示该文件的类型。例如:

  1. / 表示目录。

  2. \* 表示可执行文件。

  3. @ 表示符号链接,也就是软链接文件。

  4. | 表示有名管道文件。

  5. = 表示套接字。

  6. ....

alias指令定义的别名只在这一次连接会话中生效,若想定义的别名持续生效,可以去.bashrc配置文件中进行修改。

而且Linux系统允许指令别名和本名是一致的,比如bash配置文件中自带的指令别名:

这也是我们使用ls指令,不同类型文件的文件名会变色的原因。

改变文件权限指令

Gn!

关于文件的权限,我们在前面ls -l命令中已经讲解过了。某个用户对一个文件(目录)具有三种权限:

  1. 可读,用r表示,不可读用-表示

  2. 可写,用w表示,不可写用-表示

  3. 可执行,用x表示,不可执行用-表示

那么如何对一个文件的权限进行修改呢?我们可以使用shell命令——chmod指令。它是词组"change mode"的缩写,允许用户设置或更改文件或目录的权限。

为了更加清晰的讲解清楚这个指令,我们还需要了解一些目录以及权限相关的基本原理知识。

目录究竟是个什么东西?

Gn!

对于普通的计算机用户而言,他们会觉得目录(文件夹)和文件是完全不同的两个概念。

在上面讲解指令时,我们也依照大家以往的习惯,将指令的作用根据目录和文件做了区分,但实际上在Linux系统下目录也是文件的一种,或者说目录是一种特殊的文件

比如:ls -l展示的信息中d就表示该文件是一个目录文件。

既然目录是一个特殊文件,那么目录文件中存储的是什么数据呢?(注意:这里指的是物理文件系统中的目录文件实际存储的内容)

目录文件中存储的是当前目录下其他文件和子目录的信息,这些信息被称之为目录项(directory entries)。

目录项中主要存储这个目录/文件的文件名以及inode编号,目录项不会直接存储文件内容数据!!

而诸如:文件类型、权限、硬链接数、文件大小、时间戳等文件元数据信息则存储在inode中。

在逻辑上,目录文件中的目录项是以链表的形式(某些文件系统还可能采用B树或哈希表等结构)链接起来的。

比如一个dir1目录,树形图如下所示:

目录原理讲解-图1

将目录下所有的内容画出来(包含隐藏文件.和..),树形结构如下所示:

目录原理讲解-图2

那么dir1和dir2两个目录存储的数据,也就是目录项,在逻辑上可以画成以下图:

目录原理讲解-图3

为什么我们要知道这个原理呢?

那自然是因为了解这些内容,对后续的学习都非常有帮助啊。

目录权限

Gn!

既然目录是一个特殊文件,存储的是目录项数据,那么目录权限对目录操作的影响就很直观了:

  1. 读权限(r):

    1. 对于目录,读权限允许用户列出该目录内的文件和子目录名。

    2. 目录的读权限,就是读目录文件中目录项数据的权限,如果没有该权限,自然就不能展示当前目录信息了。

    3. 目录的读权限影响ls这样罗列目录信息的指令。

  2. 写权限(w):

    1. 对于目录,拥有写权限允许用户在该目录内新增、删除或重命名文件和子目录。

    2. 目录的写权限,就是修改目录文件中目录项数据的权限,如果没有该权限,就无法修改、删除这些数据项了。具体表现出来就是:无法创建新目录,无法删除旧目录、不能重命名等。

    3. 思考一下:这种情况下当前目录下的文件,内容能不能修改呢?当然可以,目录项中不存储文件内容!

    4. 目录的写权限影响的命令很多,如:mkdir、cp、mv、rm等

  3. 执行权限(x):

    1. 什么是目录的执行权限呢?通俗的说,拥有目录的执行权限就是可以进入该目录。所以目录的执行权限会影响cd命令。

    2. 目录的执行权限是目录最基本的权限。没有执行权限的目录是"不可进入、不可找到"的目录,那么目录将不可读也不可写。

    3. 目录的读写权限都依赖执行权限,没有执行权限,目录即便有读写权限也没有意义。

普通文件权限

Gn!

理解了目录权限,我们再来一起看一看普通文件的权限,对文件操作的影响:

  1. 读权限(r):

    1. 对于普通文件而言,读权限允许用户查看文件的内容。

    2. 思考一下:读某个目录下的文件,需要该目录的读权限吗?

    3. 当然不需要,文件内容并不是存储在目录项中的,查看文件内容仅需要目录有执行权限就可以了。

  2. 写权限(w):

    1. 对于普通文件而言,写权限允许用户修改文件的内容。

    2. 需要注意的是:文件的删除、重命名等权限在于目录的写权限,而不在于文件本身。

    3. 思考一下:如果一个文件是只读的(即没有写权限),能不能删除它?当然可以,前提是目录具有写和执行权限。

    4. 当然文件的写权限只是文件系统级别的权限限制,在操作系统层面仍然有很多种方式可以绕过这种权限,强行修改一个只读文件。比如利用vim指令就可以强行修改一个只读文件,再比如root(sudo)超级用户权限,也可以完全无视这种限制。

  3. 执行权限(x):

    1. 执行权限允许用户运行文件作为一个程序或脚本。

    2. 如果你没有对文件的执行权限,即使文件是一个正确的可执行程序或脚本,你也无法直接运行它。

chmod指令

Gn!

chmod指令用于修改文件的权限,它有两种比较常用的方式:

文字设定法

Gn!

利用文字设定法直观的设定一个文件的权限,这种方式在指令执行时是比较常用的,因为它很直观好理解。但是到了Linux系统编程时,写代码改变文件权限的时候,这种方式就不是很常用了。

其基本格式如下:

其中用户类别有四种:

  1. u:表示改变文件拥有者对文件的权限

  2. g:表示改变文件拥有者组对文件的权限

  3. o:表示改变其它用户对文件的权限

  4. a:表示改变所有用户对文件的权限

+-=分别表示:

  1. +:给目标用户添加指定权限

  2. -:给目标用户删除指定权限

  3. =:给目标用户直接指定权限

rwx则很简单,分别表示可读、可写、可执行三种权限。

使用举例:

最后一条指令的意思是:

  1. u=rwx:表示将文件text1的所有者用户的权限设置为可读、可写、可执行。

  2. g=rx:表示将文件text1的所属组的权限设置为可读和可执行,但不可写。

  3. o=r:表示将文件text1的其他用户的权限设置为只读,不可写,不可执行。

数字设定法

Gn!

什么是权限的数字设定法呢?我们观察一下某个文件的权限:

数字设定法-图1

一共有9个字符,于是我们用1表示对应位置有权限,0表示没有权限,上述dir目录的权限就可以用二进制表示成:111 111 101

这就是一种数字设定法,用于设置权限。但二进制数毕竟有点长,于是我们把它转换成八进制:775

所谓权限的设定法,就是用这样的八进制数来表示一个权限。这种表示方式显然不如文字表示法直观易理解,但它在编码过程中使用会更方便,是程序员日常表示权限更常用的方式。

以下是一些常见的数字权限:

  1. 775:775意味着拥有者和拥有组用户权限全开,其它用户则没有写权限。一般新建的目录或可执行文件就拥有这个权限。

  2. 664:664意味着拥有者和拥有组都可读可写,其它用户则只读。一般新建的文件就拥有这个权限。

  3. 700:除了拥有者外其它用户没有权限,可用于私人文件。

  4. 777:所有用户的所有权限都打开,一般不会这么做。

  5. 666:所有用户读写权限打开,一般也不要这么做。

这些权限数字都不用记,因为用多了见多了自然就记住了,就算记不住现场推也不会花很多时间。

这里我们,仅希望大家记住一个重要结论:

数字表示法表示权限时,三个整数中的某个数字如果是奇数,那么对应用户一定具有执行权限。反之若是偶数,则一定没有执行权限。

比如:

  1. 看到775这样的权限数字,也许你不能反应过来所有的权限,但你应该立刻想到:所有用户都有执行权限

  2. 看到664这样的权限数字,你应该立刻想到:所有用户都没有执行权限

  3. 看到766这样的权限数字,你应该立刻想到:只有拥有者具有执行权限,其它用户都没有。

思考一下为什么?

因为:在二进制表示中,一个整数是奇数的条件是它的二进制表示的最低有效位必须为1。(&运算判断奇偶性还记得吗?)

可执行权限是所有权限当中最重要的,把它放在三种权限的末尾,它就具有了上述结论性质,非常nice~

使用举例:

作用是将text1文件的权限设置为:rw-rw-r--

补充:文件权限掩码

Gn!

在类Unix操作系统中,文件权限掩码(umask,user file creation mode mask)是一个用于设置新文件和新目录默认权限的系统设置。

当你在Linux系统中创建一个新文件或目录时,Linux会根据特定的默认权限来赋予新文件/目录的权限:

  1. 对于文件而言,默认权限通常是666(可读可写无执行权限)

  2. 对于目录而言,默认权限通常是777(可读可写可执行,权限全开)

文件权限掩码umask的值,则决定了从默认权限中"掩去"(即清除)哪些权限。

具体的计算公式是:

(1)实际权限=默认权限&umask

即:

想要计算新文件、目录的实际权限,需要先将umask按位取反,然后再与默认权限做按位与运算得到。

举例:

Ubuntu默认的umask一般是0002(八进制),即需要移除其它用户的写权限。

实际权限计算过程如下:

  1. 对新创建的文件:

    1. 默认权限:666 -> 110 110 110(rw-rw-rw-)

    2. umask:0002 -> 000 000 010

    3. ~umask:111 111 101

    4. 实际权限:110 110 100 -> 664(rw-rw-r--),这意味着所有者和所属组有读写权限,而其他用户只有读权限。

  2. 对新创建的目录:

    1. 默认权限:777 -> 111 111 111(rwxrwxrwx)

    2. umask:0002 -> 000 000 010

    3. ~umask:111 111 101

    4. 实际权限:111 111 101 -> 775(rwxrwxr-x),这意味着所有者和所属组有完全的读写执行权限,而其他用户有读和执行权限。

通过合理设置 umask,系统管理员可以控制新文件和目录的访问级别,防止未经授权的用户访问敏感信息。这种方式确保了系统中新创建的文件和目录不会意外地获得过高的权限,从而增强了系统的安全性。

文件相关命令

Gn!

下面我们来一起学习文件相关的shell命令,这些命令同样很常用也很重要。

创建文件

Gn!

在 Linux 下创建文件的方式有多种,最常用的是以下三种方式:

我们来逐一讲解一下:

  1. echo单词有回响、余音的意思,它可以直接echo 字符串表示将字符串输出到标准输出(一般是屏幕)中。上面指令中的>字符是一个重定向操作符,它表示不再输出到标准输出中,而是输出到文件text中。若文件不存在,则会创建文件。

  2. touch单词是触摸的意思,touch 文件名...作用是文件不存在则创建文件,若存在则将文件的最后修改时间更新。

  3. vim是后续我们最常用的方式,它也可以用于创建多个文件。关于vim的详细使用,我们在后续章节继续学习。

which查找可执行文件

Gn!

shell命令的背后往往需要可执行程序的支持,which指令就用于查找某个命令背后支持它执行的可执行文件的完整路径。

其基本的使用格式如下:

常用方式:

which 是根据 PATH 环境变量中的路径依次去查找的,然后显示第一个匹配项,或者显示所有匹配项。

我们可以使用 env查看PATH环境变量

什么是"PATH 环境变量"?

PATH环境变量是一个包含一系列目录路径的变量,用于告诉操作系统 shell 在哪些目录中查找可执行文件。(Windows也有类似的)

当你在终端输入一个命令时,系统会按照PATH环境变量中指定的顺序搜索这些目录,直到找到匹配的可执行文件为止。

在以往我们给大家演示过一个指令:./hello.py

该指令用于执行一个可执行文件,那么你现在就可以思考一下这个指令为什么不能写成hello.py直接执行这个文件呢?

为什么非要加一个表示当前工作目录的./呢?

这是因为直接写hello.py,Linux会把整体当成一个指令去PATH环境变量下寻找可执行程序,但很明显这是找不到的,所以会报错:

hello.py:未找到命令

而使用了./就不会去PATH环境变量下寻找了,而是直接在当前工作目录下,找到当前工作目录下的可执行程序进而执行。

find命令(重要)

Gn!

find 命令可以在一个目录或者多个目录中,递归地中查找符合指定条件的文件或者目录。

该命令只需要使用者大体上知道自己要查找什么,就可以很快速的找到符合要求的内容,展示出来。这个指令很强大,也很常用。

注意,该指令可以用于查找文件,也可以用于查找目录。

它的基本使用格式如下:

常用方式举例:

注意:在查找当前目录时,建议使用.来表示当前目录,虽然也可以直接省略。

find命令的选项很多,而且允许组合起来进行查找,此时需要使用选项-a表示并且,-o表示或者,-!表示否定。

注意当find指令中出现多个选项需要进行条件组合时,-a即并且是默认的选择,此时-a可加可不加!

cat查看文件内容

Gn!

查看文件内容的方式有很多,包括我们可以使用vim指令来查看文件内容。如果就希望简简单单的查看文件的内容,可以使用cat指令。

它的基本格式如下:

常见用法举例:

head和tail查看文件内容首尾

Gn!

使用 head 命令查看文件内容的前几行:

常见用法举例:

使用 tail 命令查看文件内容的后几行:

常见用法举例:

尤其需要处理-F选项,添加后tail会处于持续执行监听的状态,若文件末尾进行了数据追加则会实时显示出来。这个指令选项常用于查看日志文件,比较常用。

less查看文件内容

Gn!

使用 less 命令进行单页浏览文件内容。

其使用的格式如下:

进入浏览界面后,可以使用下列方式进行翻页操作:

less命令是more命令的升级版,这种升级来源自英文谚语"less is more.",很有趣。

重定向指令

Gn!

在Linux系统中,重定向允许改变命令的标准输入流的输入源以及标准输出和标准错误流的输出目的地。这对于保存输出结果到文件、从文件中读取输入或将一个命令的输出作为另一个命令的输入等操作非常有用。

重定向的示意图如下所示:

重定向-示意图

需要注意的是,重定向的含义是断开缓冲区和默认关联设备的链接,转而链接到另一个文件中,这点在图中应该很容易理解。

注意:重定向不是改变进程和缓冲区之间的关联,而是改变缓冲区的源和目的地!!!

在讲重定向之前,我们需要先引入文件描述符的概念,这个概念具体的内容我们会等到后面再详细讲解。现在我们只需要知道:

  1. 它是一个非负整数

  2. 文件描述符不同,重定向符号会有所不同。

标准流文件描述符重定向符号
stdin0<
stdout1> 和 >>
stderr22> 和 2>>

解释一下这些重定向符号:

  1. <:将文件的内容重定向到命令的输入,一般比较少使用。

  2. >:将命令的标准输出重定向到一个文件中,如果文件已经存在,则从头覆盖文件内容。

  3. >>:将命令的标准输出追加到一个文件的末尾,如果文件t不存在,则创建文件。

  4. 2> 和 2>>类似标准输出流的重定向,只不过改成了标准错误输出。

重定向往往配合cat、head、tail、echo等命令一起使用,一些常见的使用示例如下:

我们可以使用wc命令进一步的来理解重定向。

wc指令和重定向

wc指令是一个用于统计输入数据中行数、单词书和字符数的指令,它是词组"word count"的缩写(不是上厕所的意思)。

wc后面如果不跟任何文件名,它会从标准输入(默认是键盘)中读取数据然后进行统计。例如:

wc指令示意图

注:在命令行输入ctrl + D表示输入EOF,结束键盘录入。

wc指令有一些常用的选项:

  1. -l:仅显示行数。

  2. -w:仅显示单词数。注:wc指令依靠空格、制表等符号的间隔来划分统计单词数,只是一个粗略的统计。

  3. -c:仅显示字符数。

该指令如果紧跟文件名,表示从文件中读取数据,然后输出统计的结果。

比如:wc textwc < text,它们两个从效果上来说基本是等价的,如下图所示:

wc指令示意图-2

都是统计文件text中的内容,功能是一样的,区别是wc text多输出了文件名,为什么呢?这就涉及到它们原理上的差异了:

wc指令示意图-3

wc < text直接重定向了stdin缓冲区,wc进程只知道数据是从stdin缓冲区来的,但它不知道数据源已经从键盘变成了文件,自然就更不知道文件的名字了,所以wc < text不可能输出文件的名字。

wc指令示意图-4

wc text的数据来源于文件text的文件缓冲区,于是wc进程就知道数据源文件的名字,就可以输出文件的名字。

重定向stdin是比较少见的操作,因为我们往往更喜欢直接用文件自己的文件缓冲区,这样可以获取更多的信息。

grep搜索文件内容(重点)

Gn!

grep是词组"globally search for a regular expression"的缩写,意为根据一个正则表达式进行全局搜索,它通常用于对文件内容进行搜索,功能十分强大,也很好用,要作为重点去学习和练习!

具体而言,该指令搜索的逻辑是:

按正则表达式去搜索匹配文件内容,如果文件中某一行匹配指定的正则表达式,grep命令则会显示这一行。

关于正则表达式,我们放到后面再具体讲解,这里我们先学习一下grep指令的基本使用——直接匹配查找目标字符串出现的行。

该指令的基本格式如下:

常见用法:

如果只是简单的根据字符串内容直接匹配查找,功能未免过于弱小。grep之所以强大,主要还是体现在正则表达式上,下面我们一起来学习一下正则表达式的基本语法。

正则表达式(重要)

Gn!

正则表达式的语法还是比较复杂的,严格来说它算一门单独的课程内容,而且正则表达式的语法还存在不统一、不一致的情况。

不过好在,对于一般的程序员而言,几乎不用自己手写正则表达式,你可以通过各种正则表达式生成网站、AI工具等帮助你编写复杂的正则表达式。

但完全不懂正则表达式的语法,完全看不懂正则表达式也不行,所以我们在这里讲解一些正则表达式的基本语法、核心语法,以帮助大家能够看懂基础的正则表达式,这就是我们对大家的要求。

正则表达式的基础语法基于三个核心的概念:

  1. 基本单位

  2. 基本操作

  3. 基本单位出现的位置

下面逐一解释这三个概念:

基本单位

Gn!

正则表达式的基本单位包含:

  1. 单个字符:直接匹配文本中的特定字符。例如,a 匹配字符 "a"。

  2. .字符(点字符):匹配除换行符以外的任何单个字符。例如,a.b 可以匹配 "acb"、"a&b"、"a9b" 等。

  3. *字符(星号字符):匹配前一个字符零次或多次。例如,ho* 会匹配 'h', 'ho', 'hoo', 'hooo' 等。

  4. 转义字符\:通过在特殊字符前加反斜线 \ 来取消这些字符的特殊含义,使其能够被直接匹配。例如\. 匹配实际的点字符 "."。

  5. 集合,比如[abc]、[^abc],使用 [...] 来匹配指定集合内的任意单个字符或者^取反表示任何集合外的单个字符。

  6. 分组(expr),将 expr 视为一个单独的单位,允许对整个表达式应用重复操作等。例如,(abc)+ 匹配一个或多个连续的 "abc"。

任何复杂的正则表达式都是由以上基本单位组成的。

基本操作

Gn!

基于基本单位,可以执行基本操作,从而获取更复杂的匹配逻辑。基本操作比较常用的有两个:

连接:简单地将两个或更多的基本单位放在一起,表示按照顺序匹配。例如:

  1. "abc"这个正则表达式就是匹配字符串"abc",它连接了多个单个字符。

  2. "[abc]x"集合和单个字符连接,表示可以匹配"ax"、"bx"以及"cx"。

  3. ".txt"是什么意思呢?是匹配所有以.txt结尾的字符串吗?

    1. 当然不是,不要挖个坑就跳下去了。

    2. .字符的作用是匹配除换行符之外的任何单个字符。

    3. 所以这里是匹配诸如"atxt"、"btxt"、"ctxt"等这样的字符串。

    4. 思考一下,若想获取匹配".txt"的正则表达式,怎么办?答:\.txt

重复:指定一个基本单位重复出现的次数。例如:

  1. +:表示至少出现一次。例如:

    1. a+ 匹配 "a"、"aa"、"aaa" 等。

    2. abc+表示单个字符c至少出现一次。于是可以匹配"abc"、"abcc"、"abccc" 等。

    3. [abc]+表示集合[abc]至少出现一次,这样匹配的内容就很多了,只要是包含一个或多个 "a"、"b" 或 "c" 的字符串都可以。比如 "a"、"ab"、"bca"、"cba"、"cccbbaaa" 等。

    4. (abc)+表示分组(abc)至少出现一次。所以就匹配"abc"、"abcabc"、"abcabcabc"等这样的固定abc序列。

  2. ?:重复零次或一次。例如:

    1. abc?即匹配"ab"和"abc"

    2. [abc]?即匹配""、"a"、"b"或者"c"

    3. (abc)?即匹配""或者"abc"

  3. *:重复任意次数,可以是零次,一次或多次。例如:

    1. abc*即匹配"ab"、"abc"以及"abcc"....

    2. [abc]*即匹配""、"a"、"b"、"c"以及包含多个 "a"、"b" 或 "c" 的字符串

    3. (abc)*即匹配""或者"abc"以及更多abc组成的序列

    4. .*表示匹配任意文本内容。

  4. 你还可以在基本单位后面加{m}以更精确的控制重复次数:

    1. {m}:重复 m 次。

    2. {m,n}:重复 [m, n] 次。

    3. {n,}:至少重复 n 次,即重复[n, 正无穷)次。

基本单位出现的位置

Gn!

明确基本单位是什么,也搞清楚基本单位出现的规则和次数,那么接下来只需要搞清楚基本单位出现的位置,一个具有复杂匹配功能的正则表达式就诞生了。

grep指令是一行一行匹配的,所以重要的、常见的位置有下面情况:

  1. ^:匹配行的开始,行首。例如,^abc 匹配任何以 "abc" 开始的行首。

  2. $:匹配行的结束,行尾。例如,xyz$ 匹配任何以 "xyz" 结尾的行尾。

  3. \<\>:分别匹配一个单词的开始和结束。单词同样是基于空格、制表符间隔去判断的。

比如我想匹配一行,该行以".txt"结尾,怎么办?

答:\.txt$

grep使用正则表达式

Gn!

创建一个text文件,其文件内容如下:

执行以下grep指令,分析结果:

实际结果如下:

  1. "abc" 匹配包含子字符串 "abc" 的所有行。

  2. -v "abc" 匹配不包含子字符串 "abc" 的所有行。

  3. "^abc" 匹配以 "abc" 开头的行。

  4. "abc$" 匹配以 "abc" 结尾的行。

  5. "^abc$" 匹配以 "abc" 开头且以它为结尾的行,即此行仅有内容"abc"。

  6. "\<abc\>" 匹配作为独立单词的 "abc"。

  7. "x[yz]"匹配x字符后面紧跟y或者z的行。

  8. "(abc){2,}"匹配"abc"最少出现两次的行。

  9. "^x.*z$"匹配所有以x开头,且以z结尾的行。

grep课堂练习

Gn!

请编写正则表达式,实现以下匹配功能:

匹配所有以f或F开头,以t结尾的单词。

参考答案

正则表达式直接写成\<[fF].*t\>对吗?其中.*表示匹配任意字符

但这样写很明显不正确,因为它会匹配空格和制表符,会跨单词匹配。

为了实现匹配单词的效果,可以写成:

\<[fF][^ \t]*t\>

这样就能够实现我们想要的匹配效果。

命令的组合(重要)

Gn!

shell命令是可以进行组合使用的,基于命令的组合,可以增强一些指令的作用。比如grep,就可以利用组合命令,大大增强它的功能。

命令的组合主要有三种方式:

  1. cmd ; cmd2,即两条指令间使用分号隔开,这个组合很简单就是单纯的先执行命令1,然后执行命令2。例如:

    1. mkdir dir; cd dir

    2. 先创建dir目录,然后cd进入该目录

  2. cmd1 | cmd2,其中"|"是管道,整个命令表示将cmd1指令输出的结果,作为cmd2指令的输入。

  3. cmd1 | xargs cmd2,表示将cmd1输出结果的每一行作为cmd2指令执行的输入参数。

第一种组合方式比较简单,不再赘述,下面两种方式我们专门讲解一下。

管道

Gn!

|是字符竖线,不是大写字母I,它表示管道,管道是进程间通信的一种方式,从效果上看:

cmd1 | cmd2就表示将cmd1指令执行的输出结果,作为cmd2指令的输入。

管道在这里是什么原理呢?

cmd1cmd2指令的执行实际上是启动了两个进程,整个管道的原理如下图所示:

管道-示意图

管道是操作系统内核提供一种系统功能,它可以被想象为一个缓冲区队列,用于存储从管道一端输入的数据,直到另一端的进程读取它。

管道允许数据从一个进程流向另一个进程,是进程间通信的重要手段。

具体到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来模拟日志文件追加写入,然后进行测试。

管道 "| xargs" 应用举例

Gn!

xargs 是词组"extended arguments"的缩写,意为扩展的参数。

组合命令cmd1 | xargs cmd2的含义是:将cmd1指令输出的每一行(或者以空格、制表符分割),作为参数,然后传递给cmd2指令进程。

使用举例:

比如现在的需求是:查找当前目录下所有的.c文件,然后找到文件内容中存在main函数的行。

这明显是一个组合需求:

  1. 查找当前目录下所有的.c文件:可以用指令find . -name "*.c"

  2. 查找某个.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的一个输入参数,这是不行的。

再举一些例子:

| xargsrm连用,可以实现根据条件删除目标文件,比如:

find ~ -type f -size +100M | xargs rm:删除家目录下所有大于100Mb的普通文件

find . -type f -name "*.c" | xargs rm:删除当前目录下所有的".c"结尾的普通文件

"| xargs"的原理(扩展)

Gn!

首先要搞清楚一个事情:"xargs"是什么?

实际上"xargs"也是一个shell指令,直接执行这个shell指令会从标准输入stdin中读取数据,并将这些数据转换成一个一个的参数,并输出到标准输出当中。具体而言,会以空白字符(空格、换行以及制表符)为间隔划分参数,输出到标准输出中。

该指令的演示效果如下图所示:

"| xargs"的原理-图1

所以,"cmd1 | xargs"实际上就已经是两个进程间利用管道通信:

  1. cmd1指令的进程会向标准输出stdout中输出数据,现在将标准输出重定向到管道

  2. xargs进程会从标准输入stdin中读数据,现在将标准输入重定向到管道

这样,cmd1进程的输出结果就成为了xargs指令进程的输入,示意图如下:

"| xargs"的原理-图2

xargs进程得到cmd1进程的输出作为输入后,执行什么样的操作呢?

或者说,xagrs进程和cmd2进程之间是什么关系呢?它们之间还是基于管道的进程间通信吗?

实际上,xagrs进程和cmd2进程之间是没有进程间通信的,因为它们中间没有"|",没有使用管道。xagrs进程和cmd2进程之间,是一种"父子进程"的关系:

  1. xargs 通过管道从另一个命令cmd1中获得数据输入,接着xargs进程解析这些数据,基于空格、制表符或换行符将数据分割成多个部分。

  2. 这样操作后,xargs进程就得到了分割好的数据参数,xargs进程会将这些数据参数构造成命令行参数(就是程序入口函数接受的命令行参数)

  3. 基于这个命令行参数,xargs进程会将参数传递并启动cmd2进程。

  4. 在这个过程中,由于cmd2进程由xargs传参启动,cmd2进程就是xargs进程的子进程。(Linux的shell指令背后的可执行程序基本上都是C程序)

基于以上原理,我们可以详细的分析一下指令:find . -name "*.c" | xargs grep -nE "\<main\(的执行过程:

  1. find . -name "\*.c"

    1. 此命令在当前目录及其所有子目录中,递归搜索所有扩展名为 .c 的文件。

    2. 找到的每个文件路径名都会输出到标准输出中,并且一行输出一个文件路径名。

  2. | xargs

    1. find命令的输出本身会默认输出到显示器终端上,但管道操作符使得这个输出被重定向到了管道中

    2. xargs进程本身要从标准输入中读取数据,默认从键盘接收输入,但管道操作符使得这个输入被重定向到了管道中

    3. 于是find进程输出的文件名列表,就作为了进程xargs的输入

    4. xargs 会把接收到的每一行文件路径名,分割好然后构建成命令行参数。

  3. grep -nE "\<main\(

    1. 该指令本身会启动执行grep进程,从标准输入中查找符合正则表达式规则的内容。

    2. 但由于| xargs结构的影响,xargs进程会把构建好的文件路径名的命令行参数传递给grep程序,然后启动grep进程

    3. 于是,这个grep进程会将find进程搜索到的结果文件路径名的每一行,都视为一个需要搜索的文件,挨个完成搜索。

  4. 最终,这个命令组合的含义是:查找当前目录下所有的.c文件,然后找到文件内容中存在main函数的行。

以上。

创建硬/软链接

Gn!

我们可以用 ln 指令创建硬链接和符号连接(软链接),它是单词link的简写。下面分别讲解一下这两种语法:

创建硬链接

Gn!

创建硬链接的指令格式如下:

解释:

  1. target_file 是你要硬链接的原始文件的名字

  2. link_name 是硬链接的名字

举个例子:

表示为test文件创建一个硬链接link_test,此时这两个文件会共用同一个inode。

注意:

  1. 硬链接直接指向文件的磁盘位置(inode),因此原始文件和硬链接实际上指向相同的物理位置。此时修改任意文件内容,其余文件内容会一起被修改。

  2. 硬链接不允许链接向目录,这是为了防止破坏目录结构的设计以及避免出现目录访问死循环。

  3. 为文件创建硬链接后,文件的硬链接数就会增加。当硬链接数为 0 的时候,才会真正删除磁盘上的文件。

创建软链接

Gn!

创建软链接的指令格式如下:

解释:

  1. target_file 是你要硬链接的原始文件的名字

  2. slink_name 是软链接的名字

软链接,也就符号链接,可以视为Windows系统中快捷方式或者C程序中的指针,指向另一个文件或目录。

举个例子:

表示为test文件创建一个软链接slink_test,它是独立的软链接文件,有自己独立的inode,此时访问slink_test就是访问原文件test。

如果删除软链接文件,原文件不受影响。

如果删除原文件,那么软链接文件就会指向一个不存在的文件,成为一个"死链接",类似空指针。

软链接可以链接到目录或者文件。

硬链接和符号链接的原理如下图所示:

软硬链接的原理-图

对于大多数命令(rm除外),如果参数是符号链接,其实操作的是符号链接指向的文件(类似指针的解引用操作):

符号链接要使用的比硬链接多得多。

远程复制指令(了解)

Gn!

scp(Secure Copy)是一种在本地和远程计算机之间进行安全传输文件的工具。它基于 SSH(Secure Shell)协议,提供与 SSH 相同的安全性和认证特性,确保传输过程中的数据加密和安全。

在Windows和Linux都可以使用该指令进行远程的上传和下载:

  1. 上传:将本地的文件复制到远程

  2. 下载:将远程的文件复制到本地

scp指令的使用和cp指令非常类似,本质上都是把src复制到目标dest,具体格式如下:

一些使用举例:

归档压缩指令(了解)

Gn!

tar指令一开始是用于创建、维护、修改以及提取 tar 归档文件的指令,所谓归档文件是一种将多个文件和目录(文件夹)组合成一个单一文件的方式。

但归档这种合并文件的操作使得它天然就适合同时进行压缩操作,所以

但到如今,你完全可以直接将tar指令理解成压缩和解压缩指令,我们日常使用该指令也是用于压缩和解压缩的。

其使用的格式如下(这里只列出打包压缩相关的):

解释:

  1. 主选项(有且只能选择其中一个):

    1. c: 创建:创建新的归档文件。如果归档文件已存在,则此选项将覆盖原有文件。

    2. r: 追加:追加文件到已存在的归档文件末尾。如果归档文件不存在,则会报错。注意若归档文件已压缩,需要先释放再追加。

    3. x: 释放:释放(解压)归档文件中的内容。这会根据归档中的结构恢复文件和目录。

    4. t: 查看:查看归档内容,列出归档文件中包含的文件和目录,但不提取它们。

  2. 辅选项:

    1. f: 指定包文件的名称:该辅助项选择一般是必加的,用于指令下一个参数是归档文件的名字。否则tar无法得到哪些属于被归档文件,哪个是归档的结果。

    2. v: 显示处理过程的详细信息。如果是查看归档文件,则显示文件的详细信息。该指令一般也建议都加上。

    3. z: 使用gzip算法压缩或解压缩归档文件。

常见用法举例:

注意:

  1. 未压缩的归档文件建议命名为.tar结尾,而使用使用gzip算法压缩的归档文件建议命名为.tar.gz 结尾。

  2. 已经压缩的归档文件不能直接使用tar r xxx追加新文件,需要先解压缩后重新压缩生成新文件。

The End