洋蔥

耳不闻人是非,目不视人之短,口不言人之过。

[toc]

3. 运行时数据区及程序计数器

3.1. 运行时数据区

3.1.1. 概述

本节主要讲的是运行时数据区,也就是下图这部分,它是在类加载完成后的阶段

image-20200705111640511

当我们通过前面的:类的加载-> 验证 -> 准备 -> 解析 -> 初始化 这几个阶段完成后,就会用到执行引擎对我们的类进行使用,同时执行引擎将会使用到我们运行时数据区

image-20200705111843003

内存是非常重要的系统资源,是硬盘和 CPU 的中间仓库及桥梁,承载着操作系统和应用程序的实时运行 JVM 内存布局规定了 Java 在运行过程中内存申请、分配、管理的策略,保证了 JVM 的高效稳定运行。不同的 JVM 对于内存的划分方式和管理机制存在着部分差异。结合 JVM 虚拟机规范,来探讨一下经典的 JVM 内存布局。

image-20210509174724223

我们把大厨后面的东西(切好的菜,刀,调料),比作是运行时数据区。而厨师可以类比于执行引擎,将通过准备的东西进行制作成精美的菜品

image-20210509174543026

我们通过磁盘或者网络 IO 得到的数据,都需要先加载到内存中,然后 CPU 从内存中获取数据进行读取,也就是说内存充当了 CPU 和磁盘之间的桥梁

image-20200705112416101

Java 虚拟机定义了若干种程序运行期间会使用到的运行时数据区,其中有一些会随着虚拟机启动而创建,随着虚拟机退出而销毁。另外一些则是与线程一一对应的,这些与线程对应的数据区域会随着线程开始和结束而创建和销毁。

灰色的为单独线程私有的,红色的为多个线程共享的。即:

  • 每个线程:独立包括程序计数器、栈、本地栈。
  • 线程间共享:堆、堆外内存(永久代或元空间、代码缓存)

image-20200705112601211

每个 JVM 只有一个 Runtime 实例。即为运行时环境,相当于内存结构的中间的那个框框:运行时环境。

image-20210509173410373

3.1.2. 线程

线程是一个程序里的运行单元。JVM 允许一个应用有多个线程并行的执行。 在 Hotspot JVM 里,每个线程都与操作系统的本地线程直接映射。

当一个 Java 线程准备好执行以后,此时一个操作系统的本地线程也同时创建。Java 线程执行终止后,本地线程也会回收。

操作系统负责所有线程的安排调度到任何一个可用的 CPU 上。一旦本地线程初始化成功,它就会调用 Java 线程中的 run()方法。

3.1.3. JVM 系统线程

如果你使用 console 或者是任何一个调试工具,都能看到在后台有许多线程在运行。这些后台线程不包括调用public static void main(String[] args)的 main 线程以及所有这个 main 线程自己创建的线程。

这些主要的后台系统线程在 Hotspot JVM 里主要是以下几个:

  • 虚拟机线程:这种线程的操作是需要 JVM 达到安全点才会出现。这些操作必须在不同的线程中发生的原因是他们都需要 JVM 达到安全点,这样堆才不会变化。这种线程的执行类型包括”stop-the-world”的垃圾收集,线程栈收集,线程挂起以及偏向锁撤销。
  • 周期任务线程:这种线程是时间周期事件的体现(比如中断),他们一般用于周期性操作的调度执行。
  • GC 线程:这种线程对在 JVM 里不同种类的垃圾收集行为提供了支持。
  • 编译线程:这种线程在运行时会将字节码编译成到本地代码。
  • 信号调度线程:这种线程接收信号并发送给 JVM,在它内部通过调用适当的方法进行处理。

3.2. 程序计数器(PC 寄存器)

JVM 中的程序计数寄存器(Program Counter Register)中,Register 的命名源于 CPU 的寄存器,寄存器存储指令相关的现场信息。CPU 只有把数据装载到寄存器才能够运行。这里,并非是广义上所指的物理寄存器,或许将其翻译为 PC 计数器(或指令计数器)会更加贴切(也称为程序钩子),并且也不容易引起一些不必要的误会。JVM 中的 PC 寄存器是对物理 PC 寄存器的一种抽象模拟

image-20200705155551919

作用

PC 寄存器用来存储指向下一条指令的地址,也即将要执行的指令代码。由执行引擎读取下一条指令。

image-20200705155728557

它是一块很小的内存空间,几乎可以忽略不记。也是运行速度最快的存储区域

在 JVM 规范中,每个线程都有它自己的程序计数器,是线程私有的,生命周期与线程的生命周期保持一致

任何时间一个线程都只有一个方法在执行,也就是所谓的当前方法。程序计数器会存储当前线程正在执行的 Java 方法的 JVM 指令地址;或者,如果是在执行 native 方法,则是未指定值(undefined)。

它是程序控制流的指示器,分支、循环、跳转、异常处理、线程恢复等基础功能都需要依赖这个计数器来完成。

字节码解释器工作时就是通过改变这个计数器的值来选取下一条需要执行的字节码指令。

它是唯一一个在 Java 虚拟机规范中没有规定任何 OutofMemoryError 情况的区域。

举例说明

1
2
3
4
5
public int minus(){
intc = 3;
intd = 4;
return c - d;
}

字节码文件:

1
2
3
4
5
6
7
8
0: iconst_3
1: istore_1
2: iconst_4
3: istore_2
4: iload_1
5: iload_2
6: isub
7: ireturn

使用 PC 寄存器存储字节码指令地址有什么用呢?为什么使用 PC 寄存器记录当前线程的执行地址呢?

因为 CPU 需要不停的切换各个线程,这时候切换回来以后,就得知道接着从哪开始继续执行。

JVM 的字节码解释器就需要通过改变 PC 寄存器的值来明确下一条应该执行什么样的字节码指令。

image-20200705161409533

PC 寄存器为什么被设定为私有的?

我们都知道所谓的多线程在一个特定的时间段内只会执行其中某一个线程的方法,CPU 会不停地做任务切换,这样必然导致经常中断或恢复,如何保证分毫无差呢?为了能够准确地记录各个线程正在执行的当前字节码指令地址,最好的办法自然是为每一个线程都分配一个 PC 寄存器,这样一来各个线程之间便可以进行独立计算,从而不会出现相互干扰的情况。

由于 CPU 时间片轮限制,众多线程在并发执行过程中,任何一个确定的时刻,一个处理器或者多核处理器中的一个内核,只会执行某个线程中的一条指令。

这样必然导致经常中断或恢复,如何保证分毫无差呢?每个线程在创建后,都会产生自己的程序计数器和栈帧,程序计数器在各个线程之间互不影响。

CPU 时间片

CPU 时间片即 CPU 分配给各个程序的时间,每个线程被分配一个时间段,称作它的时间片。

在宏观上:俄们可以同时打开多个应用程序,每个程序并行不悖,同时运行。

但在微观上:由于只有一个 CPU,一次只能处理程序要求的一部分,如何处理公平,一种方法就是引入时间片,每个程序轮流执行。

image-20200705161849557

[toc]

2. 类加载子系统

2.1. 内存结构概述

  • Class 文件
  • 类加载子系统
  • 运行时数据区
    • 方法区
    • 程序计数器
    • 虚拟机栈
    • 本地方法栈
  • 执行引擎
  • 本地方法接口
  • 本地方法库

image-20200705080719531

image-20200705080911284

如果自己想手写一个 Java 虚拟机的话,主要考虑哪些结构呢?

  • 类加载器
  • 执行引擎

2.2. 类加载器与类的加载过程

类加载器子系统作用

image-20200705081813409

  • 类加载器子系统负责从文件系统或者网络中加载 Class 文件,class 文件在文件开头有特定的文件标识。
  • ClassLoader 只负责 class 文件的加载,至于它是否可以运行,则由 Execution Engine 决定。
  • 加载的类信息存放于一块称为方法区的内存空间。除了类的信息外,方法区中还会存放运行时常量池信息,可能还包括字符串字面量和数字常量(这部分常量信息是 Class 文件中常量池部分的内存映射)

类加载器 ClasLoader 角色

image-20200705081913538

  • class file 存在于本地硬盘上,可以理解为设计师画在纸上的模板,而最终这个模板在执行的时候是要加载到 JVM 当中来根据这个文件实例化出 n 个一模一样的实例。
  • class file 加载到 JVM 中,被称为 DNA 元数据模板,放在方法区。
  • 在.class 文件->JVM->最终成为元数据模板,此过程就要一个运输工具(类装载器 Class Loader),扮演一个快递员的角色。

类的加载过程

1
2
3
4
5
6
7
8
/**
*示例代码
*/
public class HelloLoader {
public static void main(String[] args) {
System.out.println("Hello World!");
}
}

用流程图表示上述示例代码:

image-20200705082255746

加载阶段

image-20200705082601441

    1. 通过一个类的全限定名获取定义此类的二进制字节流
    1. 将这个字节流所代表的静态存储结构转化为方法区的运行时数据结构
    1. 在内存中生成一个代表这个类的 java.lang.Class 对象,作为方法区这个类的各种数据的访问入口

补充:加载 class 文件的方式

  • 本地系统中直接加载
  • 通过网络获取,典型场景:Web Applet
  • 从 zip压缩包中读取,成为日后 jar、war 格式的基础
  • 运行时计算生成,使用最多的是:动态代理技术
  • 由其他文件生成,典型场景:JSP 应用
  • 从专有数据库中提取.class 文件,比较少见
  • 加密文件中获取,典型的防 Class 文件被反编译的保护措施

链接阶段

  • 验证(Verify)
    • 目的在子确保 Class 文件的字节流中包含信息符合当前虚拟机要求,保证被加载类的正确性,不会危害虚拟机自身安全。
    • 主要包括四种验证,文件格式验证,元数据验证,字节码验证,符号引用验证。
  • 准备(Prepare)
    • 为类变量分配内存并且设置该类变量的默认初始值,即零值。
    • 这里不包含用 final 修饰的 static,因为 final 在编译的时候就会分配了,准备阶段会显式初始化;
    • 这里不会为实例变量分配初始化,类变量会分配在方法区中,而实例变量是会随着对象一起分配到 Java 堆中。
  • 解析(Resolve)
    • 将常量池内的符号引用转换为直接引用的过程。
    • 事实上,解析操作往往会伴随着 JVM 在执行完初始化之后再执行。
    • 符号引用就是一组符号来描述所引用的目标。符号引用的字面量形式明确定义在《java 虚拟机规范》的 Class 文件格式中。直接引用就是直接指向目标的指针、相对偏移量或一个间接定位到目标的句柄。
    • 解析动作主要针对类或接口、字段、类方法、接口方法、方法类型等。对应常量池中的 CONSTANT_Class_info,CONSTANT_Fieldref_info、CONSTANT_Methodref_info 等。

初始化阶段

  • 初始化阶段就是执行类构造器方法<clinit>()的过程。
  • 此方法不需定义,是 javac 编译器自动收集类中的所有类变量的赋值动作和静态代码块中的语句合并而来。
  • 构造器方法中指令按语句在源文件中出现的顺序执行。
  • <clinit>()不同于类的构造器。(关联:构造器是虚拟机视角下的<init>())
  • 若该类具有父类,JVM 会保证子类的<clinit>()执行前,父类的<clinit>()已经执行完毕。
  • 虚拟机必须保证一个类的<clinit>()方法在多线程下被同步加锁。

2.3. 类加载器分类

JVM 支持两种类型的类加载器 。分别为引导类加载器(Bootstrap ClassLoader)自定义类加载器(User-Defined ClassLoader)

从概念上来讲,自定义类加载器一般指的是程序中由开发人员自定义的一类类加载器,但是 Java 虚拟机规范却没有这么定义,而是将所有派生于抽象类 ClassLoader 的类加载器都划分为自定义类加载器

无论类加载器的类型如何划分,在程序中我们最常见的类加载器始终只有 3 个,如下所示:

image-20200705094149223

这里的四者之间的关系是包含关系。不是上层下层,也不是子父类的继承关系。

2.3.1. 虚拟机自带的加载器

启动类加载器(引导类加载器,Bootstrap ClassLoader)

  • 这个类加载使用 C/C++语言实现的,嵌套在 JVM 内部。
  • 它用来加载 Java 的核心库(JAVA_HOME/jre/lib/rt.jar、resources.jar 或 sun.boot.class.path 路径下的内容),用于提供 JVM 自身需要的类
  • 并不继承自 ava.lang.ClassLoader,没有父加载器。
  • 加载扩展类和应用程序类加载器,并指定为他们的父类加载器。
  • 出于安全考虑,Bootstrap 启动类加载器只加载包名为 java、javax、sun 等开头的类

扩展类加载器(Extension ClassLoader)

  • Java 语言编写,由 sun.misc.Launcher$ExtClassLoader 实现。
  • 派生于 ClassLoader 类
  • 父类加载器为启动类加载器
  • 从 java.ext.dirs 系统属性所指定的目录中加载类库,或从 JDK 的安装目录的 jre/1ib/ext 子目录(扩展目录)下加载类库。如果用户创建的 JAR 放在此目录下,也会自动由扩展类加载器加载。

应用程序类加载器(系统类加载器,AppClassLoader)

  • java 语言编写,由 sun.misc.LaunchersAppClassLoader 实现
  • 派生于 ClassLoader 类
  • 父类加载器为扩展类加载器
  • 它负责加载环境变量 classpath 或系统属性 java.class.path 指定路径下的类库
  • 该类加载是程序中默认的类加载器,一般来说,Java 应用的类都是由它来完成加载
  • 通过 ClassLoader#getSystemclassLoader() 方法可以获取到该类加载器

2.3.2. 用户自定义类加载器

在 Java 的日常应用程序开发中,类的加载几乎是由上述 3 种类加载器相互配合执行的,在必要时,我们还可以自定义类加载器,来定制类的加载方式。 为什么要自定义类加载器?

  • 隔离加载类
  • 修改类加载的方式
  • 扩展加载源
  • 防止源码泄漏

用户自定义类加载器实现步骤:

  1. 开发人员可以通过继承抽象类 ava.lang.ClassLoader 类的方式,实现自己的类加载器,以满足一些特殊的需求
  2. 在 JDK1.2 之前,在自定义类加载器时,总会去继承 ClassLoader 类并重写 loadClass() 方法,从而实现自定义的类加载类,但是在 JDK1.2 之后已不再建议用户去覆盖 loadclass() 方法,而是建议把自定义的类加载逻辑写在 findClass()方法中
  3. 在编写自定义类加载器时,如果没有太过于复杂的需求,可以直接继承 URLClassLoader 类,这样就可以避免自己去编写 findClass() 方法及其获取字节码流的方式,使自定义类加载器编写更加简洁。

2.4. ClassLoader 的使用说明

ClassLoader 类是一个抽象类,其后所有的类加载器都继承自 ClassLoader(不包括启动类加载器)

image-20200705103516138

sun.misc.Launcher 它是一个 java 虚拟机的入口应用

image-20200705103636003

获取 ClassLoader 的途径

  • 方式一:获取当前 ClassLoader

    1
    clazz.getClassLoader()
  • 方式二:获取当前线程上下文的 ClassLoader

    1
    Thread.currentThread().getContextClassLoader()
  • 方式三:获取系统的 ClassLoader

    1
    ClassLoader.getSystemClassLoader()
  • 方式四:获取调用者的 ClassLoader

    1
    DriverManager.getCallerClassLoader()

2.5. 双亲委派机制

Java 虚拟机对 class 文件采用的是按需加载的方式,也就是说当需要使用该类时才会将它的 class 文件加载到内存生成 class 对象。而且加载某个类的 class 文件时,Java 虚拟机采用的是双亲委派模式,即把请求交由父类处理,它是一种任务委派模式。

工作原理

  • 1)如果一个类加载器收到了类加载请求,它并不会自己先去加载,而是把这个请求委托给父类的加载器去执行;
  • 2)如果父类加载器还存在其父类加载器,则进一步向上委托,依次递归,请求最终将到达顶层的启动类加载器;
  • 3)如果父类加载器可以完成类加载任务,就成功返回,倘若父类加载器无法完成此加载任务,子加载器才会尝试自己去加载,这就是双亲委派模式。

image-20200705105151258

举例

当我们加载 jdbc.jar 用于实现数据库连接的时候,首先我们需要知道的是 jdbc.jar 是基于 SPI 接口进行实现的,所以在加载的时候,会进行双亲委派,最终从根加载器中加载 SPI 核心类,然后在加载 SPI 接口类,接着在进行反向委派,通过线程上下文类加载器进行实现类 jdbc.jar 的加载。

image-20200705105810107

优势

  • 避免类的重复加载
  • 保护程序安全,防止核心 API 被随意篡改
    • 自定义类:java.lang.String
    • 自定义类:java.lang.ShkStart(报错:阻止创建 java.lang 开头的类)

沙箱安全机制

自定义 String 类,但是在加载自定义 String 类的时候会率先使用引导类加载器加载,而引导类加载器在加载的过程中会先加载 jdk 自带的文件(rt.jar 包中 java\lang\String.class),报错信息说没有 main 方法,就是因为加载的是 rt.jar 包中的 string 类。这样可以保证对 java 核心源代码的保护,这就是沙箱安全机制。

2.6. 其他

如何判断两个 class 对象是否相同

在 JVM 中表示两个 class 对象是否为同一个类存在两个必要条件:

  • 类的完整类名必须一致,包括包名。
  • 加载这个类的 ClassLoader(指 ClassLoader 实例对象)必须相同。

换句话说,在 JVM 中,即使这两个类对象(class 对象)来源同一个 Class 文件,被同一个虚拟机所加载,但只要加载它们的 ClassLoader 实例对象不同,那么这两个类对象也是不相等的。

对类加载器的引用

JVM 必须知道一个类型是由启动加载器加载的还是由用户类加载器加载的。如果一个类型是由用户类加载器加载的,那么 JVM 会将这个类加载器的一个引用作为类型信息的一部分保存在方法区中。当解析一个类型到另一个类型的引用的时候,JVM 需要保证这两个类型的类加载器是相同的。

类的主动使用和被动使用

Java 程序对类的使用方式分为:主动使用和被动使用。

主动使用,又分为七种情况:

  • 创建类的实例

  • 访问某个类或接口的静态变量,或者对该静态变量赋值

  • 调用类的静态方法

  • 反射(比如:Class.forName(”com.atguigu.Test”))

  • 初始化一个类的子类

  • Java 虚拟机启动时被标明为启动类的类

  • JDK 7 开始提供的动态语言支持:

    java.lang.invoke.MethodHandle 实例的解析结果

    REF_getStatic、REF_putStatic、REF_invokeStatic 句柄对应的类没有初始化,则初始化

除了以上七种情况,其他使用 Java 类的方式都被看作是对类的被动使用,都不会导致类的初始化

推荐链接:

菜鸟 Linux 磁盘管理

Linux磁盘管理—-分区格式化挂载fdisk、mkfs、mount

相关命令:

菜鸟 df 命令df 命令df (Unix) - 维基百科

菜鸟 du 命令du 命令du (Unix) - 维基百科

菜鸟 fdisk 命令fdisk 命令 —————————— parted 命令GNU Parted - 维基百科

菜鸟 mkfs 命令mkfs 命令

菜鸟 mount 命令mount 命令mount (Unix) - 维基百科 —> 临时挂载

菜鸟 umount命令umount 命令

菜鸟 fsck 命令fsck 命令fsck - 维基百科

lsblk 命令

partprobe 命令

resize2fs 命令

阅读全文 »

软件包管理

软件包管理系统dpkgRPM包管理员

软件包管理系统前端:APTYUM

软件包及依赖查询网站

Ubuntu

1
2
3
# 查看依赖
apt-cache depends gnome-core # 正向依赖(查看某个包依赖哪些包)
apt-cache rdepends gnome-core # 反向依赖(查看哪些包依赖这个包)

Centos

1
2
3
# 查看依赖
dnf deplist perl # 正向依赖(查看某个包依赖哪些包)
repoquery --whatrequires perl # 反向依赖(查看哪些包依赖这个包,需要安装 dnf install -y yum-utils

dpkg

https://man.archlinux.org/man/dpkg

1
2
dpkg --help
dpkg -L openssh-server # 显示指定软件包在系统中安装的所有文件和目录的完整路径列表。

apt-get、apt-cache、apt

https://man.archlinux.org/man/apt-get

https://man.archlinux.org/man/apt-cache

https://man.archlinux.org/man/apt

简单来说:

  • apt-get:处理包的安装、升级和移除,是传统的、面向脚本的工具。
  • apt-cache:用于查询本地软件包信息(数据库),例如搜索、查看依赖。
  • apt:是较新的前端工具,结合了 apt-getapt-cache 的最佳功能,并提供了更美观、更友好的用户体验。

三个 APT 工具的详细对比

工具 主要功能/定位 典型命令示例 特点总结
apt-get 核心操作:负责从软件源获取和安装软件包,处理实际的系统更改。传统工具,适合脚本编写。 apt-get install <package> apt-get remove <package> apt-get update apt-get upgrade 面向底层:输出信息相对简单,没有进度条。是 APT 工具家族中历史最悠久、功能最稳定的工具。
apt-cache 查询操作:负责查询本地缓存的软件包数据库(Metadata)。它不会更改系统或下载软件包。 apt-cache search <keyword> apt-cache show <package> apt-cache depends <package> 信息获取:仅用于搜索和显示软件包信息、依赖关系等。是用户了解软件包情况的强大工具。
apt 用户界面:结合了 apt-getapt-cache 的常用功能。推荐日常使用的现代工具。 apt install <package> apt remove <package> apt update apt upgrade apt search <keyword> 用户友好:提供美观的进度条、颜色高亮和更简洁的输出。它不是要取代 apt-get,而是提供一个更好的用户界面。

rpm

https://man.archlinux.org/man/rpm

1
rpm -ql openssh-server # 列出软件包中的文件。显示指定软件包在系统中安装的所有文件和目录的完整路径列表。

dnf/yum

https://man.archlinux.org/man/extra/dnf/dnf4

https://man.archlinux.org/man/extra/dnf5/dnf5

dnfyum 的改进和取代者。

1
dnf --version

查看系统安装了哪些软件包

查看软件包依赖了哪些包

查看软件包依赖了哪些动态库

查看软件包安装了哪些文件

查看文件来自哪个软件包

iptables

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
# REDIRECT 是 DNAT 的一种特例,用于将流量“重定向”到本机的某个端口。它会把数据包的 目标 IP 改为本机 IP,并可选地修改目标端口。方向:外部 → 本机。常用场景:本机透明代理、流量重定向/端口转发、本机端口劫持。
# DNAT 改变数据包的目标地址和目标端口,使外部访问被转发到内网主机(入站端口映射)。方向:外部 → 内部。常见场景:外部访问内网服务(端口映射)、公网访问转发到私网服务器。
# SNAT(Source NAT)用于修改数据包的源IP(和可选的源端口),使外部服务器认为请求来自指定的公网IP。方向:内部 → 外部。常见场景:内网主机访问外网时共享一个公网IP、多网卡服务器的流量伪装、NAT 路由器出站流量转换。

# 若忽略出站入栈网卡,则作用于所有网卡流量
iptables -t nat -A PREROUTING -i ens33 -p tcp --dport 8989 -j REDIRECT --to-port 22 #
iptables -t nat -A PREROUTING -i ens33 -p tcp --dport 8989 -j DNAT --to-destination 192.168.0.7:22 #
iptables -t nat -A POSTROUTING -o ens33 -s 192.168.0.0/24 -j SNAT --to-source 114.114.114.114 # 内网 192.168.0.0/24 子网的主机访问外网时,源地址被替换为公网IP 114.114.114.114。

# 查看 NAT 表 (nat table) 中 PREROUTING 链 的所有规则,并以数字形式显示(不反查 DNS / 服务名)。
# -t nat 指定 NAT 表(专门处理地址转换,DNAT/SNAT/REDIRECT)
# -L 列出(List)该表中指定链的所有规则
# PREROUTING 指定要查看的链(chain)。PREROUTING 用于入站数据包,包到达本机前触发。
# -n 以数字方式显示 IP 和端口,不反查域名或服务名(更快更准确)
# --line-numbers 给每条规则显示一个编号,便于删除或修改特定规则
iptables -t nat -L PREROUTING -n --line-numbers

iptables -t nat -D PREROUTING 1 # 根据行号删除规则
iptables -t nat -D PREROUTING -i ens33 -p tcp --dport 8989 -j REDIRECT --to-port 22 # 按规则内容删除规则

流编辑器——sed

Sed 是一款非常强大且常用的 流编辑器(Stream Editor),非常适合做批量的替换、删除、插入、打印等文本变换。

简介与工作原理

  • 流编辑器: Sed 一次只处理一行内容。
  • 工作空间: Sed 处理文件时,会将当前处理的行读入一个临时缓冲区,称为 “模式空间” (pattern space)
  • 处理流程: Sed 命令会对模式空间中的内容进行处理,处理完成后,默认会将模式空间的内容输出到标准输出(屏幕)。接着清空模式空间,读取下一行,重复上述过程,直到文件末尾。
  • 非破坏性: 默认情况下,Sed 不会修改原文件内容,而是将结果输出到标准输出,除非使用重定向或特定的选项(如 -i)来保存更改。

sed 常用语法格式

最基本的 Sed 命令格式如下:

1
2
3
sed [选项] 'sed命令' 文件名
#
sed [选项] -f 脚本文件 文件名

常用选项 (Options)

选项 作用
-n 静默模式(或安静模式)。默认 Sed 会打印所有行,使用此选项后,只有被显式命令(如 p 打印命令)处理的行才会被打印。
-e 允许多个 Sed 命令/脚本。例如:sed -e 'command1' -e 'command2' file
-f 指定 Sed 脚本文件。例如:sed -f script.sed file
-i 直接修改原文件。这个选项非常重要,但使用时要小心,最好先备份文件。

地址(Address)

Sed 命令可以指定一个地址或一个地址范围,以限制命令的作用范围。

地址格式 作用 示例
无地址 作用于文件中的所有行 's/old/new/'
单行号 作用于指定的行 '5d' (删除第 5 行)
$ 代表文件的最后一行 '$d' (删除最后一行)
/regex/ 作用于匹配正则表达式的行。这是查找命令 '/error/d' (删除包含 “error” 的行)
地址范围 作用于从起始地址到结束地址之间的行(包含边界)。
addr1,addr2 1,5d (删除第 1 到 5 行)
addr1,/regex/ 3,/end/d (删除第 3 行到第一个匹配 /end/ 的行)
/regex1/,/regex2/ /start/,/end/d (删除第一个匹配 /start/ 到第一个匹配 /end/ 的行)

sed 常用命令 (Commands)

sed 命令通常紧跟在地址后面。

替换:s (substitute)

这是 sed 最常用、最强大的功能。

  • 格式: [地址]s/旧字符串/新字符串/标志
  • 示例:
    • s/foo/bar/:将每行中第一个 foo 替换为 bar
    • s/foo/bar/g全局替换,将行中所有 foo 替换为 bar
    • s/foo/bar/p:打印发生替换的行。通常配合 -n 选项使用。
    • s/foo/bar/w output.txt:将发生替换的行写入 output.txt 文件。
    • s#/#\\/#g:当替换内容或目标字符串中包含 / 时,可以使用其他分隔符,例如 #详见下文 sed 命令分隔符

删除:d (delete)

  • 格式: [地址]d
  • 示例:
    • 3d:删除第 3 行。
    • 1,5d:删除第 1 到 5 行。
    • /^#/d:删除以 # 开头的行 (常用于删除注释行)。
    • '/^$/d':删除所有空行。

打印:p (print)

  • 格式: [地址]p
  • 用途: 配合 -n 选项,只打印指定的行或匹配的行。
  • 示例:
    • sed -n '5p' file:只打印第 5 行。
    • sed -n '/config/p' file:只打印包含 config 关键字的行。
    • sed -n '1,10p' file:只打印前 10 行。

追加/插入/更改:a / i / c

  • a (append): 在匹配行之后追加新文本。
    • 示例: '/error/a\--- An Error Occurred ---' (在包含 error 的行后面追加文本)
  • i (insert): 在匹配行之前插入新文本。
    • 示例: '1i\# This is a header' (在文件第一行前插入一行)
  • c (change): 用新文本替换匹配行或匹配范围的所有内容。
    • 示例: '3c\Replacement Text' (用新文本替换第 3 行)

注意: aic 命令后的文本必须换行书写(在命令行中通常用 \ 来转义换行符,或者直接跟在命令后,用 \ 隔开)。

sed 命令分隔符

分隔符(delimiter)在 sed 命令中主要用于 替换命令查找命令 / ,尤其是涉及到正则表达式的部分。默认是 /

替换命令 (s)

这是最常见的使用自定义分隔符的场景。

  • 基本格式: s/查找模式/替换字符串/标志
  • 使用自定义分隔符的格式: sX查找模式X替换字符串X标志
    • 这里的 X 可以是任何非空格、非换行符的单个字符。
示例命令 分隔符 场景说明
s#/var/log#/tmp/log#g # 当查找或替换的内容中包含 / 时(例如文件路径),使用 # 可以避免对 / 进行转义。
s@^#@ @ @ 当查找或替换的内容中包含 # 时(例如配置文件中的注释),可以使用 @

查找/地址命令 (/regex/)

分隔符也可以用于定义地址(即指定 Sed 命令作用的行范围)。当您需要查找的模式中包含默认分隔符 / 时,可以自定义分隔符。

  • 基本格式: /正则表达式/命令
  • 使用自定义分隔符的格式: \X正则表达式X命令
    • 注意: 在自定义分隔符前需要加上反斜杠 \
示例命令 分隔符 场景说明
/\//d / (默认) 删除包含默认分隔符 / 的行,但需要转义 (\/)。
sed '\#/home/#d' file # (自定义) 删除包含 /home/ 路径的行,无需转义路径中的 /,但需在开头用 \ 声明自定义分隔符。

进阶示例

组合多个命令

使用 -e 选项或分号 ;

1
2
3
4
# 删除空行,并将所有的 'old' 替换为 'new'
sed -e '/^$/d' -e 's/old/new/g' file.txt
# 或者使用分号
sed '/^$/d ; s/old/new/g' file.txt

括号分组和后向引用

在替换命令 s 中,可以使用括号 () 对匹配模式的一部分进行分组,并在替换字符串中用 \1, \2, … 进行引用。注意: 括号需要用 \ 进行转义,即 \(\)

  • 示例: 交换行中两个单词的位置。
1
2
3
# 假设一行内容是 "apple banana"
# 将其转换为 "banana apple"
echo "apple banana" | sed 's/\(apple\) \(banana\)/\2 \1/'
  • \(apple\) 匹配并捕获第一个单词,标记为 \1
  • \(banana\) 匹配并捕获第二个单词,标记为 \2
  • 替换字符串 \2 \1 将它们的位置交换。

文件定位

whereis命令locate/slocate命令find命令which命令rpm命令

以 nginx 安装目录为例:

1
2
3
4
5
6
7
8
9
10
11
12
[root@localhost ~]# whereis nginx
[root@localhost ~]# whereis -b nginx # 定位指令的二进制程序、源代码文件和man手册页等相关文件的路径
[root@localhost ~]# locate nginx # 搜索一个数据库/var/lib/locatedb,查找目录与文件,但查不到最新的变动,为避免这种情况应先使用updatedb命令
[root@localhost ~]# find / | grep nginx # 在指定目录下查找子目录与文件

[root@localhost ~]# which nginx # 查找并显示给定命令(可执行程序)的绝对路径。查看某个系统命令是否存在并显示命令位置。

# rpm命令是RPM软件包的管理工具
[root@localhost ~]# rpm -qa | grep nginx # 列出安装过的软件包,且包含字符串nginx
[root@localhost ~]# rpm -q nginx # 获取nginx软件包的全名
[root@localhost ~]# rpm -ql nginx # rpm包中文件的安装位置
[root@localhost ~]# rpm -qal | grep nginx

网络状态

1
2
3
4
5
6
7
8
9
10
11
12
13
14
yum install -y lsof
lsof -i:8080 # 查看端口是否被占用,有列头,推荐使用

yum install -y net-tools
netstat -ntlp # 查看当前所有tcp端口
netstat -nulp # 查看当前所有udp端口
netstat -an | grep 80 # 查看所有包含80的端口使用情况
netstat -ntulp | grep 80 # 查看所有包含80的端口使用情况,包括PID(进程ID)、进程名。
netstat -tunlp | grep 80 # 查看所有包含80的端口使用情况,包括PID(进程ID)、进程名。
# -t : 指明显示TCP端口
# -u : 指明显示UDP端口
# -l : 仅显示监听套接字(所谓套接字就是使应用程序能够读写与收发通讯协议(protocol)与资料的程序)
# -p : 显示进程标识符和程序名称,每一个套接字/端口都属于一个程序。
# -n : 不进行DNS轮询,显示IP(可以加速操作)

进程状态

Linux ps 命令 - 菜鸟

1
2
3
4
5
6
7
8
[root@localhost ~]# ps aux
[root@localhost ~]# ps aux | grep nginx

[root@localhost ~]# ps -f
[root@localhost ~]# ps -ef
[root@localhost ~]# ps -ef | grep nginx

ps axw -o pid,ppid,user,%cpu,vsz,wchan,command | egrep '(nginx|PID)'

输出头(标题)

Linux head 命令 - 菜鸟

在使用Linux命令时,如果命令中有管道 | ,则输出的信息中,头(标题)信息丢失,要想看每一列代表什么意思很不方便。

例如 ps auxw

1
2
3
4
5
6
7
8
9
10
11
$ ps axuw
USER PID %CPU %MEM VSZ RSS TTY STAT START TIME COMMAND
root 1 0.0 0.3 193984 6556 ? Ss 08:51 0:03 /usr/lib/systemd/systemd --switched-root --system --deserialize 22
root 2 0.0 0.0 0 0 ? S 08:51 0:00 [kthreadd]
root 4 0.0 0.0 0 0 ? S< 08:51 0:00 [kworker/0:0H]
root 5 0.0 0.0 0 0 ? S 08:51 0:00 [kworker/u128:0]
root 6 0.0 0.0 0 0 ? S 08:51 0:00 [ksoftirqd/0]
root 7 0.0 0.0 0 0 ? S 08:51 0:01 [migration/0]
root 8 0.0 0.0 0 0 ? S 08:51 0:00 [rcu_bh]
root 9 0.0 0.0 0 0 ? S 08:51 0:03 [rcu_sched]
root 10 0.0 0.0 0 0 ? S< 08:51 0:00 [lru-add-drain]

再加上管道符后

1
2
3
4
5
$ ps axuw | grep redis
esuser 2636 0.0 0.0 192 4 ? Ss 08:52 0:00 /usr/bin/dumb-init -- /redis-commander/docker/entrypoint.sh
polkitd 2654 0.2 0.4 52956 9020 ? Ssl 08:52 0:09 redis-server *:6379
esuser 2810 0.0 1.9 272292 36548 ? Ssl 08:52 0:01 /usr/bin/node ./bin/redis-commander
root 39925 0.0 0.0 112824 980 pts/0 S+ 10:05 0:00 grep --color=auto redis

可以看到头(标题)已经丢失。

一个简单的办法,通过 2 条命令叠加,获取头和内容:

1
2
3
4
5
6
$ ps axuw | head -1;ps axuw | grep redis
USER PID %CPU %MEM VSZ RSS TTY STAT START TIME COMMAND
esuser 2636 0.0 0.0 192 4 ? Ss 08:52 0:00 /usr/bin/dumb-init -- /redis-commander/docker/entrypoint.sh
polkitd 2654 0.2 0.4 52956 9020 ? Ssl 08:52 0:09 redis-server *:6379
esuser 2810 0.0 1.9 272292 36548 ? Ssl 08:52 0:01 /usr/bin/node ./bin/redis-commander
root 40905 0.0 0.0 112824 984 pts/0 S+ 10:07 0:00 grep --color=auto redis

也就是先用命令本身加 | head -1 取到头(标题),然后再使用该命令输出内容,两者叠加输出即得到所要结果。

排序——sort

Linux sort命令 - 菜鸟

排序命令——sort

按列排序,数字大的在前:

1
2
3
4
5
6
7
8
$ ps auxw | sort -rnk 2
root 44271 0.0 0.0 126840 928 pts/0 S+ 10:14 0:00 sort -rnk 2
root 44270 0.0 0.0 155448 1868 pts/0 R+ 10:14 0:00 ps auxw
root 44269 0.0 0.0 108052 612 ? S 10:14 0:00 sleep 1
201 44263 0.0 0.0 1560 248 ? SN 10:14 0:00 sleep 1
201 33288 0.0 0.0 2412 1400 ? SN 09:52 0:01 bash /usr/libexec/netdata/plugins.d/tc-qos-helper.sh 1
root 8204 0.0 0.0 0 0 ? S< 09:02 0:00 [kworker/11:1H]
......

该例子,将第 2 列进行排序,最大的数排前面。

若只想看前10条的内容:

1
$ ps auxw | sort -rnk 2 | head -10

将实际内存消耗最大的10个进程显示出来:

1
2
$ ps auxw | head -1; ps auxw | sort -rnk 6 | head -10
$ ps auxw --sort=-rss | head -11

统计——wc

统计命令——wc

切分——cut

切分命令——cut

去重——uniq

去重命令——uniq

强大的文本分析命令——awk

awk 命令 - 菜鸟
强大的文本分析命令——awk

1
ps -ef | grep mar-service.jar:60002 | grep -v grep | awk '{ print }'

服务管理

systemctl命令 :是系统服务管理器指令,它实际上将 servicechkconfig 这两个命令组合到一起。

1
2
3
4
5
6
7
8
9
10
11
# 查看所有可用的单元文件
[root@localhost ~]# systemctl list-unit-files | grep ''
# 查看所有已安装服务
[root@localhost ~]# systemctl list-unit-files --type=service | grep ''

# 输出激活的unit,下面两个命令等效
[root@localhost ~]# systemctl | grep ''
[root@localhost ~]# systemctl list-units | grep ''
# 输出激活的类型为service的unit
[root@localhost ~]# systemctl list-units --type=service | grep ''
[root@localhost ~]# systemctl list-units --type=service | grep nginx

vim 中查找和替换

vi / vim 键位图: https://www.runoob.com/linux/linux-vim.html

https://harttle.land/2016/08/08/vim-search-in-file.html

https://blog.csdn.net/ballack_linux/article/details/53187283

清空历史命令

history命令

.bash_history 默认可记录 500 条历史命令。

只有在正常退出当前 shell 时,在当前 shell 中运行的命令才会保存至 .bash_history 文件中。

1
2
3
[root@localhost ~]# history 10               # 显示最近使用的10条历史命令
[root@localhost ~]# history -c # 清空当前shell历史命令记录
[root@localhost ~]# rm -rf ~/.bash_history # 删除'.bash_history'文件

若想在每次登录后都清空历史记录,可以在登录后登出前执行 rm -rf ~/.bash_history 即可。

Wget 和 cURL

百科: Wgetwget命令cURLcurl命令

curl 与 wget

wget命令 用来从指定的URL下载文件。

curl命令 是一个利用URL规则在命令行下工作的文件传输工具。curl URL 默认将下载文件输出到stdout,将进度信息输出到stderr(默认也是终端),可以使用 -O(使用原文件名)或 -o(指定输出文件名)指定输出位置。curl支持更多的协议,还支持cookies、认证、限速、文件大小等特征。

1
2
[root@localhost ~]# wget -c URL
[root@localhost ~]# curl URL -O --progress

删除文件/文件夹

1
2
3
4
rm -rf # 文件或文件夹名
# -i 删除前逐一询问确认。
# -f 即使原档案属性设为唯读,亦直接删除,无需逐一确认。
# -r 将目录及以下之档案亦逐一删除。

解压缩

tar

1
2
3
# 解压
tar -zxvf jdk-7u80-linux-i586.tar.gz
tar -zxvf /mnt/hgfs/SharedFolders/openjdk-11+28_linux-x64_bin.tar.gz -C /data/

zip

1
2
3
4
5
6
7
8
9
10
11
12
# 安装zip、unzip应用
yum install zip unzip # Centos
apt install zip unzip # Ubuntu

# 解压
unzip /mnt/hgfs/SharedFolders/linuxx64_12201_database.zip -d /data
# 将 /home/html/ 这个目录下所有文件和文件夹打包为当前目录下的 html.zip
zip -q -r html.zip /home/html
# 如果在我们在 /home/html 目录下,可以执行该命令
zip -q -r html.zip *
# 从压缩文件 cp.zip 中删除文件 a.c
zip -dv cp.zip a.c

7z

Linux有问必答:Linux 中如何安装 7zip

支持 7Z,ZIP,Zip64,TAR,RAR,CAB,ARJ,GZIP,BZIP2,CPIO,RPM,ISO,DEB 压缩文件格式

1
2
3
4
5
6
7
8
9
10
11
12
13
14
# 安装
$ yum install p7zip p7zip-plugins
$ apt install p7zip-full p7zip-rar

# 将01.jpg和02.png压缩成一个7z包
$ 7z a pkg.7z 01.jpg 02.png
# 将所有的.jpg文件压缩成一个7z包
$ 7z a pkg.7z *.jpg
# 将文件夹folder压缩成一个7z包
$ 7z a pkg.7z folder
# 将pkg.7z中的所有文件解压出来,e是解压到当前路径
$ 7z e pkg.7z # 不实用
# 将pkg.7z中的所有文件解压出来,x是解压到压缩包命名的目录下
$ 7z x pkg.7z # 正确的解压方法

nohup

相关链接: 百科Linux nohup 命令 - 菜鸟shell中的特殊字符大全

nohup命令可以将程序以忽略挂起信号的方式运行起来,被运行的程序的输出信息将不会显示到终端。 如果不将 nohup 命令的输出重定向,输出将追加到当前目录的 nohup.out 文件中。

nohup command >/dev/null 2>&1 & 意思就是,将command保持在后台运行,并且将输出的日志忽略。

1
2
# 启动java服务
nohup java -jar -Dserver.port=9088 gateway.jar >/dev/null 2>&1 &

备份脚本

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

# 日志文件的存放路径。$0 代表当前脚本的路径,dirname "$0" 会提取出脚本所在的目录。这意味着该日志文件将始终生成在与该脚本相同的目录下。
LOG_FILE=$(dirname "$0")/backup.log

# 日志记录函数。$1 代表传入的参数。tee指令会从标准输入读取数据,将其内容传输到标准输出(屏幕),同时保存成文件,-a (append):追加到现有文件的末尾,而不是覆盖它。
log() {
echo "[$(date '+%Y-%m-%d %H:%M:%S')] $1" | tee -a "$LOG_FILE"
}

# 核心备份函数
backup() {
local src="$1"
local dest="$2"
log "Starting backup: $src -> $dest"

if rsync -avz --progress --exclude='*.log' "$src" "$dest"; then
# 如果 rsync 成功执行(返回码为 0),则记录成功日志。
log "Backup succeeded: $src -> $dest"
else
# 如果失败,记录错误日志,并执行 exit 1。这非常重要,意味着只要有一个目录备份失败,整个脚本就会立刻终止,不会继续尝试备份后面的目录。
log "ERROR: Backup failed: $src -> $dest"
exit 1
fi
}

# 备份的目标地址,需要提前配置SSH信任
TARGET="192.168.0.79:/vg/backup/cleansource"

# --------------------------- 执行备份任务

# 备份SCA Web
backup "/ssd1" "$TARGET"
# 备份Minio
backup "/vg/cleansource/minio" "$TARGET"
# 备份知识库
backup "/vg/cleansource/kb" "$TARGET"

# 备份数据库正确的做法是:1、先使用数据库自带的导出工具(如 mysqldump 或 pg_dump)生成一个逻辑备份文件;2、然后再用 rsync 同步备份文件。

log "All backups completed successfully."

Ubuntu Server 24.04 LTS

以下是部分安装步骤,其他步骤请直接点击【Done】继续。

网络配置

配置ubuntu存档镜像

自定义存储布局

Ubuntu最佳分区方式:只需要两个分区 /swap 。swap 一般设置为 2 倍或 1.5 倍的 RAM。

下面创建根分区(/),不创建 swap 分区:

配置用户名密码

跳过 Ubuntu Pro

Ubuntu Pro 是 Canonical 推出的 企业级付费支持和增强服务,在普通 Ubuntu 的基础上增加了安全更新、合规性工具和长期支持等功能,主要面向企业或专业用户。

个人用户 / 小团队

  • Ubuntu Pro 对个人用户有免费额度(通常支持 5 台机器免费激活)。

企业用户 / 大规模部署

  • 需要付费订阅,可享受官方 SLA 支持、更多机器数量和附加安全服务。

开启ssh远程

常用的snap包

安装完成后重启

重启

删除安装介质

重启后,Ubuntu 会提示移除安装镜像。

具体操作:取消 虚拟机设置 中的设备 启动时连接,然后按 Enter。

安装中文环境

安装英文版ubuntu,在打开含有中文字符文件时会乱码,有需要给Ubuntu Server装中文环境

1、安装之前,执行 echo $LANG
屏幕显示:en_US.UTF-8
说明现在是英语环境,需要切换到中文环境。

2、安装中文语言包

apt-get update && apt-get install language-pack-zh-hans

3、vim /etc/default/locale
把原来英语 US 的都换成如下的内容,并且注意配置文件中不能有多余的空格

1
2
3
4
5
6
7
8
9
10
11
12
LANG="zh_CN.UTF-8"
LANGUAGE="zh_CN:zh"
LC_NUMERIC="zh_CN"
LC_TIME="zh_CN"
LC_MONETARY="zh_CN"
LC_PAPER="zh_CN"
LC_NAME="zh_CN"
LC_ADDRESS="zh_CN"
LC_TELEPHONE="zh_CN"
LC_MEASUREMENT="zh_CN"
LC_IDENTIFICATION="zh_CN"
LC_ALL="zh_CN.UTF-8"

4、vim /etc/environment
原来有一行 PATH=.. 不要动这一行
另起一行,复制粘贴以下内容,并且注意配置文件中不能有多余的空格

1
2
3
4
5
6
7
8
9
10
11
12
LANG="zh_CN.UTF-8"
LANGUAGE="zh_CN:zh"
LC_NUMERIC="zh_CN"
LC_TIME="zh_CN"
LC_MONETARY="zh_CN"
LC_PAPER="zh_CN"
LC_NAME="zh_CN"
LC_ADDRESS="zh_CN"
LC_TELEPHONE="zh_CN"
LC_MEASUREMENT="zh_CN"
LC_IDENTIFICATION="zh_CN"
LC_ALL="zh_CN.UTF-8"

5、重启机器 reboot

CentOS-8.1.1911

以下是部分安装步骤,其他步骤可以根据实际需求自行选择。

软件选择

安装目标位置(自定义分区)

网络和主机名

root密码

安装完成后重启

重启

删除安装介质

重启完成后操作:取消 虚拟机设置 中的设备 启动时连接

解锁root用户

1
2
3
4
5
6
7
sudo passwd -S root # 查看 root 状态
sudo passwd -l root # -l → Locked(锁定用户)
sudo passwd -u root # -u → unlock(解锁用户)
sudo passwd -d root # 删除 root 的密码(删除 /etc/shadow 里的加密串)
sudo passwd root # 设置root用户的密码

su root # 测试是否可以进入 root 用户

开启root用户远程登录

安装SSH

先检查是否安装

1
2
dpkg -l | grep ssh
rpm -qa | grep ssh

开放本机 SSH 服务,请安装服务端:

1
2
apt install openssh-server
dnf install openssh-server

连接远程主机,请安装客户端:

1
2
apt install openssh-client
dnf install openssh-client

查看SSH服务

1
2
3
ps -ef | grep ssh
systemctl status ssh # Ubuntu 上服务名是 ssh
systemctl status sshd # CentOS 上服务名是 sshd

SSH配置文件

ssh 有两个主要的配置文件,它们的作用和针对的对象不同

特性 ssh_config sshd_config
角色 客户端配置 服务端配置
进程 ssh (当使用 ssh 远程连接时会看到该进程) sshd (SSH Daemon 是一个守护进程,它一直在监听连接请求)
控制 客户端如何发起连接 服务器如何接受连接
位置 /etc/ssh/ssh_config~/.ssh/config 通常是 /etc/ssh/sshd_config
生效 立即生效(客户端读取) 需要重启服务 (sshd)

客户端配置手册:https://manpages.debian.org/trixie/openssh-client/ssh_config.5.en.html

服务端配置手册:https://manpages.debian.org/trixie/openssh-server/sshd_config.5.en.html

客户端常用配置项

配置项 作用 常用值/说明
Host 定义一个别名或模式,其后的配置将对该别名生效。 别名如 myserver,模式如 *192.168.1.*
HostName 远程主机的实际地址(IP 或域名)。 如果与 Host 别名不同时才需要设置。
User 连接远程主机时默认使用的用户名。 省略则使用本地用户名。
Port 远程主机的端口。 如果远程服务器端口不是默认的 22,则在此设置。
IdentityFile 用于公钥认证的私钥文件路径。 例如 ~/.ssh/id_rsa。可以为不同主机指定不同的密钥。
ServerAliveInterval 客户端向服务器发送“保活”消息的间隔时间(秒)。 设为 60 可防止本地网络闲置导致的连接中断。
ConnectTimeout SSH 连接建立的最大等待时间(秒)。 避免因网络问题导致长时间卡死。
ProxyCommand 【高级】 通过执行一个外部命令来建立与远程服务器的连接。 例如:ProxyCommand ssh -p 22 -W %h:%p jumpbox
常用于通过 SOCKS 代理或老版本跳板机。
-p 22:连接跳板机的port。
-W %h:%p转发选项,告诉 SSH 客户端:建立连接后,请在远程主机上执行一个转发操作到 %h:%p%h 是目标主机,%p 是目标端口。
jumpbox:连接的跳板机的IP
ProxyJump 【推荐】 指定一个或多个跳板机(Bastion Host)。 例如:ProxyJump jump.example.com。功能上取代了大部分 ProxyCommand 的跳板用途。

服务端常用配置项

配置项 作用 常用值/说明
Port 指定 SSH 服务监听的端口。 默认为 22。为了安全,常改为其他端口(如 2222)。
ListenAddress 指定 SSH 服务绑定的网络接口 IP。 默认为 0.0.0.0(所有接口)。可设为特定 IP 以限制访问。
PermitRootLogin 是否允许 root 用户直接通过 SSH 登录。 推荐设为 no。应使用普通用户登录后再切换到 rootsusudo)。
PasswordAuthentication 是否允许使用密码进行身份验证。 推荐设为 no。当使用公钥认证时,禁用密码可以大大提高安全性。
PubkeyAuthentication 是否允许使用 公钥 进行身份验证。 通常设为 yes。这是最安全的登录方式。
AllowUsers / DenyUsers 明确允许或拒绝某些用户登录。 例如:AllowUsers user1 user2@192.168.1.10
MaxAuthTries 每个连接允许的最大认证尝试次数。 默认为 6。较低的数值可以阻止暴力破解。
ClientAliveInterval 服务器向客户端发送“保活”消息的间隔时间(秒)。 默认为 0(不发送)。设为 3060 可防止连接因超时而被关闭。

允许Root用户远程登录

编辑服务端配置文件:vim /etc/ssh/sshd_config

找到并修改:

1
2
3
4
#PermitRootLogin prohibit-password
PermitRootLogin yes # 允许root用户登录

#PasswordAuthentication yes # 指定是否使用密码认证。此关键字的参数必须为 yes(默认值)或 no

重新加载配置:

1
2
3
systemctl reload ssh   # 仅重新加载配置,不中断现有会话
#
systemctl restart ssh # 重启服务,会断开现有会话

测试登录

从远程机器执行:

1
ssh root@服务器IP

卸载无用的系统服务

1
2
3
4
# 查看所有正在运行的服务
systemctl list-units --type=service --state=running
# 查看开机启动的服务
systemctl list-unit-files --type=service --state=enabled

禁用swap分区

https://man.archlinux.org/man/swapoff

https://man.archlinux.org/man/swapon

临时禁用(立即生效,重启失效)

1
2
3
4
cat /proc/swaps
swapoff -a # 禁用 /proc/swaps 中的所有 swaps
swapon --show
free -h

永久禁用 Swap 自动挂载

1
2
3
4
# 编辑 /etc/fstab 文件
vim /etc/fstab
# 找到包含 swap 关键字的行,并注释掉。
#/swap.img none swap sw 0 0

删除Swap分区

略…

删除Swap文件(针对 Swap 文件用户)

1
2
3
# 如果您的Swap不是一个分区,而是一个文件(例如 /swap.img),您还需要删除该文件。
# 如果Swap是一个分区,您也可以尝试删除它,但这可能需要更复杂的LVM 操作。
rm -rf /swap.img

换源

https://developer.aliyun.com/mirror/ubuntu

https://developer.aliyun.com/mirror/centos

ubuntu 查看源

1
2
3
4
5
cat /etc/apt/sources.list                                   # 主配置文件
cat /etc/apt/sources.list.d/ubuntu.sources # 主配置文件,24.04 之后
ll /etc/apt/sources.list.d/*.sources # 主配置文件+额外软件源(第三方 PPA、软件商提供的源)
grep -rh "URIs:" /etc/apt/sources.list.d/*.sources # 查看已启用的软件源文件
apt-cache policy # 查看已配置的所有源(启用+禁用)

centos 查看源

1
2
3
4
ll /etc/yum.repos.d/*.repo  # 仓库配置文件
dnf repolist --help
dnf repolist --enabled # 显示已经启用的仓库(默认)
dnf repolist --all # 显示所有的软件仓库(启用+禁用)

更新和升级

ubuntu

1
2
3
4
5
6
7
8
9
10
11
12
apt-get update               # 从配置的软件源获取最新的软件包列表信息(元数据),更新本地的软件包索引和缓存。

apt-get upgrade # 只更新已安装的软件包。会安装新的依赖包。如果升级某个包需要移除其他已安装的包,会跳过升级,避免破坏现有系统环境。
apt-get dist-upgrade # 升级时智能处理依赖关系。会安装新的依赖包、移除不再需要/冲突的包、更换核心软件包。用于跨版本升级或内核升级。

apt-get autoclean # 清除旧的已安装的包缓存文件
apt-get autoremove --purge # 移除不再需要的软件包及其配置文件。--purge 参数的作用是同时删除残留配置文件
apt-get clean # 清除所有已下载的包缓存文件

# apt 选项 --purge:可和 remove、autoremove 等组合,用于卸载软件并删除残留配置文件
# apt 子命令 purge:卸载软件并删除残留配置文件
apt purge pkg 等价于 apt remove --purge pkg

centos

1
2
3
4
5
6
7
8
9
dnf makecache           # 下载软件包仓库中的软件包列表和其他元数据到本地缓存,以便快速查找和安装软件。是一个准备操作

dnf check-upgrade # 查询系统已安装的软件包在仓库中是否有新版本。不下载、不安装,只显示更新信息。
dnf -y update # 更新系统中已经安装的软件包到可用的最新版本。默认只升级已经安装的软件,不会删除任何包。请使用 upgrade。
dnf -y upgrade # 与 update 不同的是,它会处理包依赖变化,有时会自动移除不需要的旧包。“更彻底的更新”。推荐!!!

dnf clean packages # 删除所有缓存的软件包文件(通常是 .rpm 文件)。
dnf clean metadata # 删除所有仓库的元数据文件(XML 格式,包含软件包列表、版本、依赖关系等信息)。
dnf clean all # 清理所有缓存文件,包括软件包、元数据 等等。

发行版

/etc/*-release 文件,它们用来 识别和描述操作系统发行版信息

1
2
find /etc | grep -E ".*-release"
cat /etc/os-release # 现代 Linux 统一标准文件(systemd 之后几乎都有)

内核

1
2
3
4
# 查看当前使用的内核版本
uname -r # 仅显示内核版本号(最常用)。
uname -a # 显示所有内核信息,包括内核版本、操作系统、主机名、架构等。
cat /proc/version # 这个文件记录了 Linux 内核的版本、用于编译内核的 gcc 的版本、内核编译的时间,以及内核编译者的用户名。

内核清理

ubuntu

1
2
3
4
5
6
7
8
# 查看系统已安装的内核
dpkg -l | grep linux-image
dpkg --get-selections | grep linux-image

# 删除旧内核
apt-get autoremove --purge # 系统一般会保留 2~3 个旧内核以防万一
# 手动删除
apt-get purge linux-image-6.8.0-45-generic linux-headers-6.8.0-45-generic

centos

1
2
3
4
5
6
7
8
9
# 查询系统已安装的内核
rpm -qa | grep kernel
dnf list installed | grep kernel

# 删除旧内核
dnf autoremove
dnf remove --oldinstallonly # 一键删除旧内核
# 手动删除
dnf remove kernel-modules-core-5.14.0-522.el9.x86_64 kernel-core-5.14.0-522.el9.x86_64

网关、IP、DNS

1
2
3
4
5
6
7
8
9
ip route                                  # 查看网关地址
ip addr show # 显示系统中所有网络接口的IP地址和相关配置信息,包括MAC地址、接口状态和网络配置。

# resolvectl 是一个在较新的Linux系统中用于管理和查询系统DNS解析配置的命令行工具,它是 systemd-resolved 服务的一部分。
resolvectl status # 显示每个网络接口(网卡)的详细配置。全面状态
resolvectl dns # 是 resolvectl status 输出内容的子集。聚焦DNS服务器
resolvectl dns <interface> <DNS server> # 为指定的网络接口(例如 ens33)设置静态DNS 服务器地址(例如 8.8.8.8)
resolvectl query www.baidu.com # 执行DNS查询,获取百度的IP地址,它会报告查询使用的协议和网络接口。
resolvectl flush-caches # 清除DNS缓存。当DNS记录发生变化时,使用此命令清楚本地DNS缓存,强制系统重新查询DNS服务器。

从DHCP改为固定IP

使用 ip addr 命令查看您的网络接口名称(例如 eth0ens33)。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
root@localhost:~# ip addr
1: lo: <LOOPBACK,UP,LOWER_UP> mtu 65536 qdisc noqueue state UNKNOWN group default qlen 1000
link/loopback 00:00:00:00:00:00 brd 00:00:00:00:00:00
inet 127.0.0.1/8 scope host lo
valid_lft forever preferred_lft forever
inet6 ::1/128 scope host noprefixroute
valid_lft forever preferred_lft forever
2: ens33: <BROADCAST,MULTICAST,UP,LOWER_UP> mtu 1500 qdisc fq_codel state UP group default qlen 1000
link/ether 00:0c:29:7d:6c:a0 brd ff:ff:ff:ff:ff:ff
altname enp2s1
inet 192.168.0.102/24 metric 100 brd 192.168.0.255 scope global dynamic ens33
valid_lft 6189sec preferred_lft 6189sec
inet6 fe80::20c:29ff:fe7d:6ca0/64 scope link
valid_lft forever preferred_lft forever
root@localhost:~#
root@localhost:~# ls /etc/netplan
50-cloud-init.yaml

备份原配置 sudo cp /etc/netplan/50-cloud-init.yaml /etc/netplan/50-cloud-init.yaml.bak

编辑 Netplan 配置文件 sudo vim /etc/netplan/50-cloud-init.yaml 内容如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
# 保持缩进正确:Netplan 使用 YAML 格式,缩进必须用空格,不能用 Tab
network:
version: 2
ethernets:
ens33: # 网卡名称
dhcp4: no
addresses:
- "192.168.0.7/24" # /24 是CIDR表示法,表示子网掩码,也就是该IP所在网络的大小。将IPv4转换为32位整数值,前24位是网络号,剩下的8位是主机号。
routes:
- to: "default" # default 是 0.0.0.0/0 的简写,表示 IPv4 默认路由,“匹配所有目的地址的流量”,即所有未匹配到其他路由的包都走这里”
via: "192.168.0.101" # 查看网关 ping tplogin.cn
metric: 100 # 路由优先级,数字越小优先级越高。如果系统存在多条默认路由,内核会根据 metric 选择哪条路由发送流量。默认写 100 就可以,一般不需要改动,除非你有 多网卡 / 多路由策略。
nameservers:
addresses:
- 114.114.114.114
- 114.114.115.115
search: [] # 默认DNS搜索域

保存文件后执行

1
2
3
4
# 系统会应用新的网络设置,可通过 ip addr 或 ping -c 4 www.runoob.com 测试网络是否可用
netplan apply
# 如果 apply 出错,请重启网络服务
systemctl restart systemd-networkd

修改hostname

主机名是用于标识网络上设备的标签,在同一网络上,你不应有两台或更多台具有相同主机名的计算机。

hostnamectl

hostnamectl 是 systemd 系统和服务管理器套件的一部分,用于查询和修改系统主机名。

Linux 系统通常涉及三种不同类型的主机名,hostnamectl 可以分别或同时进行管理:

  • 静态主机名 (Static hostname): 这是保存在配置文件(通常是 /etc/hostname)中的标准主机名。它在系统启动时被初始化。永久的。
  • 瞬态主机名 (Transient hostname): 由内核在运行时维护的主机名。DHCP 客户端、云平台(比如 OpenStack、AWS)分配主机名时常用它。临时的,重启失效。默认与静态主机名相同
  • “美观”主机名 (Pretty hostname): 一个更友好、供人类阅读的主机名,可以包含空格和特殊字符(例如:”My Laptop”)。保存在 /etc/machine-info

你可以使用 --static--transient--pretty 选项来单独查询或设置这些主机名。

1
2
3
4
5
6
7
hostnamectl --help 
hostnamectl status # 查看系统主机名

hostnamectl set-hostname halbin-dev-zhaolq.halbin.mars.com # 会同时更新 静态主机名 和 瞬态主机名
hostnamectl set-hostname halbin-dev-zhaolq.halbin.mars.com --static # 静态主机名,永久的
hostnamectl set-hostname halbin-dev-zhaolq.halbin.mars.com --transient # 瞬态主机名,临时的,重启失效
reboot

hostname 是一个传统的命令行工具,属于 inetutilsnet-tools 包。它修改的是 瞬态主机名

1
2
hostname                                      # 查看系统主机名
hostname halbin-dev-zhaolq.halbin.mars.com # 瞬态主机名,临时的,重启失效

修改配置文件(老方法)

1
2
3
cat /etc/hostname
cat /etc/hosts
reboot

防火墙

192.168.0.0/24:表示一个包含 256 个地址(从 192.168.0.0192.168.0.255)的子网,其中 192.168.0.0 是网络地址,192.168.0.255 是广播地址,192.168.0.1192.168.0.254 是可分配的设备 IP 地址。

192.168.0.1/24:表示子网 192.168.0.0/24 中的第一个有效 IP 地址,通常被分配给网关或路由器。

Ubuntu

在 Ubuntu 系统中,最常用和推荐的防火墙管理工具是 UFW (Uncomplicated Firewall)

UFW 是一个对标准 Linux 防火墙工具 iptables 进行封装的前端工具,它大大简化了配置规则的复杂性,让用户可以轻松管理网络连接。

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
#************************************ UFW 状态检查与管理
ufw status # 查看 UFW 的当前状态(active 或 inactive)和已配置的规则。
ufw status verbose # 显示更详细的信息,包括默认策略和日志级别。
ufw enable # 启用 UFW,系统启动时会自动运行。注意:在启用前,请确保您已允许 SSH 端口,以防失去远程连接。
ufw disable # 完全禁用 UFW。
ufw reset # 清除所有规则,将默认策略设置为拒绝 (deny),并将状态设置为禁用 (inactive)。谨慎使用!

#************************************ 配置默认策略
ufw default deny incoming # 拒绝所有传入的连接(入站规则)。服务器和安全桌面的标准配置。这意味着除了您明确允许的端口(如 SSH、HTTP、HTTPS)外,所有外部或内部尝试连接您的机器的请求都会被拒绝。
ufw default allow incoming
ufw default deny outgoing # 拒绝所有传出的连接(出站规则)。严格安全环境或特定应用服务器。这会阻止您的机器主动发起任何网络连接,除非您明确允许(例如,允许您的服务器连接到外部 DNS 或更新服务器)。这通常会使日常桌面使用变得困难。
ufw default allow outgoing # 允许所有传出的连接(出站规则)。服务器和安全桌面的标准配置。

#************************************ 添加允许(Allow)规则(拒绝请使用deny)
# 基于 服务名、端口号、协议 来允许流量通过。
ufw allow ssh # 按服务名。允许 SSH 连接(自动解析为 TCP 端口 22)。
ufw allow 80 # 按端口号。允许所有协议(TCP/UDP)的 80 端口(HTTP)流量。
ufw allow http # 允许 80/tcp
ufw allow https # 允许 443/tcp
ufw allow 443/tcp # 按端口和协议。仅允许 TCP 协议的 443 端口(HTTPS)流量。
ufw allow 6000:6007/udp # 按端口范围。允许 6000 到 6007 之间的 UDP 端口。
ufw allow from 192.168.0.0/24 # 特定子网。允许来自 192.168.0.0/24 子网的所有传入(Incoming)连接。
ufw allow from 192.168.0.100 # 特定IP地址。允许来自特定 IP 地址的所有流量。
ufw allow from 192.168.0.100 to any port 25 # IP和端口。仅允许该 IP 地址访问本机的 25 端口(SMTP)。

#************************************ 添加拒绝(Deny)和限制(Limit)规则
ufw deny 23 # 拒绝所有尝试连接到 23 端口(Telnet)的流量。
ufw limit ssh # 允许 SSH 连接,但在 30 秒内如果同一 IP 尝试连接超过 6 次,则阻止该 IP 地址。这是防止暴力破解的推荐做法。
ufw reject 143/tcp # 拒绝连接请求,但发送一个拒绝响应(如 ICMP port unreachable),而不是直接丢弃数据包(deny行为)。

#************************************ 删除规则
# 按规则内容删除 (推荐):直接在命令前加上 delete 关键字,内容与添加规则时完全相同。
ufw delete allow 80/tcp # 删除允许 80 端口的规则
# 按编号删除 (适用于规则多且复杂的情况)
ufw status numbered # 先查看带有编号的规则列表
sudo ufw delete 2 # 然后使用编号删除

Centos

CentOS 7及更高版本默认使用 firewalld 作为防火墙管理工具,它采用**区域(Zone)**的概念来管理网络连接和接口的信任级别。

以下是使用 firewall-cmd 命令管理防火墙的一些常用操作:

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
#************************************ firewalld 服务管理
systemctl status firewalld # 检查 firewalld 服务是否正在运行。
systemctl start firewalld # 启动 firewalld 服务。
systemctl stop firewalld # 停止 firewalld 服务。
systemctl enable firewalld # 设置 firewalld 开机自启。
systemctl disable firewalld # 禁止 firewalld 开机自启。

#************************************ 常用防火墙配置命令
firewall-cmd --help # 查看帮助
firewall-cmd --version # 查看版本
firewall-cmd --get-default-zone # 查看当前默认的区域(通常是 public)。
firewall-cmd --get-active-zones # 查看区域信息
firewall-cmd --get-zone-of-interface=eth0 # 查看指定接口所属区域信息
firewall-cmd --list-all # 查看默认区域的运行时所有规则(服务、端口等)。
firewall-cmd --zone=public --list-all # 查看 public 区域的所有运行时规则。
firewall-cmd --reload # 应用永久配置,使更改立即生效,且不中断现有连接。

#************************************ 区域(Zone)概念
# 区域是 firewalld 的核心概念,用于根据网络环境定义不同的信任级别:
# 区域名称 信任级别 默认行为
# trusted(信任) 最高 允许所有网络连接。
# home(家庭) 中高 信任网络上的大多数计算机,允许少量入站连接。
# work(工作) 中 信任工作环境中的计算机,允许少量入站连接。
# public(公共) 低 默认区域,不信任网络上的其他计算机,只接受选定的入站连接(例如您手动开放的服务)。
# drop(丢弃) 最低 丢弃所有入站数据包,不回复任何信息,只允许出站连接。
firewall-cmd --set-default-zone=public # 修改默认区域

#************************************ 开放/关闭端口或服务,默认情况在 public 区域进行配置
# firewalld 的配置分为 运行时(runtime)和永久(permanent) 两种。
# 运行时配置立即生效,但在服务重启或系统重启后会丢失。
# 永久配置需要使用 --permanent 标志,并且需要重新加载防火墙才能生效。!!!!!!
firewall-cmd --zone=public --add-service=ssh --permanent # 添加服务(永久生效)
firewall-cmd --zone=public --remove-service=ssh --permanent # 关闭服务(永久生效)
firewall-cmd --zone=public --list-ports # 查看指定区域所有开放的端口号
firewall-cmd --zone=public --add-port=80/tcp --permanent # 开放端口(永久生效)
firewall-cmd --zone=public --remove-port=80/tcp --permanent # 关闭端口(永久生效)
firewall-cmd --zone=public --add-port=18881:65534/tcp --permanent # 开放指定范围的端口号

#************************************ 紧急模式
# 紧急模式是一个极端的安全措施,用于在系统检测到攻击或严重威胁时,立即丢弃(drop)所有网络数据包。
firewall-cmd --panic-on # 开启紧急模式。立即丢弃所有传入和传出的网络数据包。通常在系统遭受拒绝服务(DoS)攻击或检测到严重入侵企图时使用,以完全隔离服务器。
firewall-cmd --panic-off # 关闭紧急模式。停止丢弃所有数据包,恢复到正常运行时的防火墙规则。在安全威胁解除后,用于恢复正常的网络连接。
firewall-cmd --query-panic # 查询紧急模式状态。检查 firewalld 当前是否处于紧急模式。返回 yes 或 no。

设置交互式shell的环境变量

系统升级会覆盖 /etc/profile 吗?

答案是:有风险,但不一定会直接抹掉。

Linux 的处理机制:

  • 当你修改了 /etc/profile,系统在升级核心软件包(如 bash 或 base-files)时,包管理器(如 apt 或 dnf)通常会检测到该文件已被手动修改。

常见的处理方式:

  • Debian/Ubuntu:会弹出提示,询问你“使用发行版维护者的版本”还是“保留本地修改版本”。

  • CentOS/RHEL:可能会保留你的文件,但将新版本重命名为 /etc/profile.rpmnew;或者反过来,覆盖你的文件并将旧版本存为 /etc/profile.rpmsave。

结论:

  • 虽然有保护机制,但手动合并代码非常麻烦。不建议直接修改 /etc/profile 主文件。

最佳实践(为了方便,我并没有这么做):

  • 使用 /etc/profile.d/ 目录。
  • 几乎所有主流 Linux 发行版的 /etc/profile 脚本里都有一段逻辑:遍历并执行 /etc/profile.d/ 目录下所有以 .sh 结尾的文件。
  • 创建文件: touch /etc/profile.d/my_env.sh,默认权限是 644,不需要 755(在 644 的基础上 chmod +x ),因为大多数发行版的 /etc/profile 只是读取(source)这些文件,并不强制要求执行权限,但赋予 +x 也是常见的做法。
  • 在 crontab 定时任务脚本中执行 source /etc/profile

Git环境变量

参考官网,编译安装

1
2
3
4
5
grep -qxF 'export PATH=/data/local/git/bin:$PATH' /etc/profile || \
echo 'export PATH=/data/local/git/bin:$PATH' >> /etc/profile

source /etc/profile
git -v

JDK环境变量

1
2
3
4
5
6
7
8
9
10
11
12
13
# 若存在 JAVA_HOME 行就替换,不存在就添加
grep -q "^export JAVA_HOME=" /etc/profile \
&& sed -i 's|^export JAVA_HOME=.*|export JAVA_HOME=/data/local/java/jdk-17.0.14+7|' /etc/profile \
|| echo 'export JAVA_HOME=/data/local/java/jdk-17.0.14+7' >> /etc/profile

# -q: 找到匹配项后不会向标准输出打印任何内容。
# -x: 强制仅匹配整行。这意味着模式必须匹配从行首到行尾的全部内容,该行的前后不会有任何其他内容。
# -F: 确保搜索模式中的所有字符(特别是 $、/、: 等)都按字面意思匹配,而不是被误认为是正则表达式的特殊含义。
grep -qxF 'export PATH=$JAVA_HOME/bin:$PATH' /etc/profile || \
echo 'export PATH=$JAVA_HOME/bin:$PATH' >> /etc/profile

source /etc/profile
java -version

Maven环境变量

1
2
3
4
5
grep -qxF 'export PATH=/data/local/apache-maven-3.9.11/bin:$PATH' /etc/profile || \
echo 'export PATH=/data/local/apache-maven-3.9.11/bin:$PATH' >> /etc/profile

source /etc/profile
mvn -v

Gradle环境变量

https://gradle.org/install/

1
2
3
4
5
grep -qxF 'export PATH=/data/local/gradle-7.6.6/bin:$PATH' /etc/profile || \
echo 'export PATH=/data/local/gradle-7.6.6/bin:$PATH' >> /etc/profile

source /etc/profile
gradle -v

让交互式shell执行某个脚本

1
2
3
4
5
6
7
grep -qxF 'source /data/local/env/ideps-env.sh' /etc/profile || \
echo 'source /data/local/env/ideps-env.sh' >> /etc/profile

grep -qxF '. /data/local/env/ideps-env.sh' /etc/profile || \
echo '. /data/local/env/ideps-env.sh' >> /etc/profile

source /etc/profile
0%