分类 技术 下的文章

对于 Linux 管理员来说这是一个重要(美妙)的话题,所以每个人都必须知道,并练习怎样才能更高效的使用它们。

在 Linux 中,无论何时当你安装任何带有服务和守护进程的包,系统默认会把这些服务的初始化及 systemd 脚本添加进去,不过此时它们并没有被启用。

我们需要手动的开启或者关闭那些服务。Linux 中有三个著名的且一直在被使用的初始化系统。

什么是初始化系统?

在以 Linux/Unix 为基础的操作系统上,init (初始化的简称) 是内核引导系统启动过程中第一个启动的进程。

init 的进程 id (pid)是 1,除非系统关机否则它将会一直在后台运行。

init 首先根据 /etc/inittab 文件决定 Linux 运行的级别,然后根据运行级别在后台启动所有其他进程和应用程序。

BIOS、MBR、GRUB 和内核程序在启动 init 之前就作为 Linux 的引导程序的一部分开始工作了。

下面是 Linux 中可以使用的运行级别(从 0~6 总共七个运行级别):

  • 0:关机
  • 1:单用户模式
  • 2:多用户模式(没有NFS)
  • 3:完全的多用户模式
  • 4:系统未使用
  • 5:图形界面模式
  • 6:重启

下面是 Linux 系统中最常用的三个初始化系统:

  • System V(Sys V)
  • Upstart
  • systemd

什么是 System V(Sys V)?

System V(Sys V)是类 Unix 系统第一个也是传统的初始化系统。init 是内核引导系统启动过程中第一支启动的程序,它是所有程序的父进程。

大部分 Linux 发行版最开始使用的是叫作 System V(Sys V)的传统的初始化系统。在过去的几年中,已经发布了好几个初始化系统以解决标准版本中的设计限制,例如:launchd、Service Management Facility、systemd 和 Upstart。

但是 systemd 已经被几个主要的 Linux 发行版所采用,以取代传统的 SysV 初始化系统。

什么是 Upstart?

Upstart 是一个基于事件的 /sbin/init 守护进程的替代品,它在系统启动过程中处理任务和服务的启动,在系统运行期间监视它们,在系统关机的时候关闭它们。

它最初是为 Ubuntu 而设计,但是它也能够完美的部署在其他所有 Linux系统中,用来代替古老的 System-V。

Upstart 被用于 Ubuntu 从 9.10 到 Ubuntu 14.10 和基于 RHEL 6 的系统,之后它被 systemd 取代。

什么是 systemd?

systemd 是一个新的初始化系统和系统管理器,它被用于所有主要的 Linux 发行版,以取代传统的 SysV 初始化系统。

systemd 兼容 SysV 和 LSB 初始化脚本。它可以直接替代 SysV 初始化系统。systemd 是被内核启动的第一个程序,它的 PID 是 1。

systemd 是所有程序的父进程,Fedora 15 是第一个用 systemd 取代 upstart 的发行版。systemctl 用于命令行,它是管理 systemd 的守护进程/服务的主要工具,例如:(开启、重启、关闭、启用、禁用、重载和状态)

systemd 使用 .service 文件而不是 bash 脚本(SysVinit 使用的)。systemd 将所有守护进程添加到 cgroups 中排序,你可以通过浏览 /cgroup/systemd 文件查看系统等级。

如何使用 chkconfig 命令启用或禁用引导服务?

chkconfig 实用程序是一个命令行工具,允许你在指定运行级别下启动所选服务,以及列出所有可用服务及其当前设置。

此外,它还允许我们从启动中启用或禁用服务。前提是你有超级管理员权限(root 或者 sudo)运行这个命令。

所有的服务脚本位于 /etc/rd.d/init.d文件中

如何列出运行级别中所有的服务

--list 参数会展示所有的服务及其当前状态(启用或禁用服务的运行级别):

# chkconfig --list
NetworkManager     0:off    1:off    2:on    3:on    4:on    5:on    6:off
abrt-ccpp          0:off    1:off    2:off    3:on    4:off    5:on    6:off
abrtd              0:off    1:off    2:off    3:on    4:off    5:on    6:off
acpid              0:off    1:off    2:on    3:on    4:on    5:on    6:off
atd                0:off    1:off    2:off    3:on    4:on    5:on    6:off
auditd             0:off    1:off    2:on    3:on    4:on    5:on    6:off
.
.

如何查看指定服务的状态

如果你想查看运行级别下某个服务的状态,你可以使用下面的格式匹配出需要的服务。

比如说我想查看运行级别中 auditd 服务的状态

# chkconfig --list| grep auditd
auditd             0:off    1:off    2:on    3:on    4:on    5:on    6:off

如何在指定运行级别中启用服务

使用 --level 参数启用指定运行级别下的某个服务,下面展示如何在运行级别 3 和运行级别 5 下启用 httpd 服务。

# chkconfig --level 35 httpd on

如何在指定运行级别下禁用服务

同样使用 --level 参数禁用指定运行级别下的服务,下面展示的是在运行级别 3 和运行级别 5 中禁用 httpd 服务。

# chkconfig --level 35 httpd off

如何将一个新服务添加到启动列表中

-–add 参数允许我们添加任何新的服务到启动列表中,默认情况下,新添加的服务会在运行级别 2、3、4、5 下自动开启。

# chkconfig --add nagios

如何从启动列表中删除服务

可以使用 --del 参数从启动列表中删除服务,下面展示的是如何从启动列表中删除 Nagios 服务。

# chkconfig --del nagios

如何使用 systemctl 命令启用或禁用开机自启服务?

systemctl 用于命令行,它是一个用来管理 systemd 的守护进程/服务的基础工具,例如:(开启、重启、关闭、启用、禁用、重载和状态)。

所有服务创建的 unit 文件位与 /etc/systemd/system/

如何列出全部的服务

使用下面的命令列出全部的服务(包括启用的和禁用的)。

# systemctl list-unit-files --type=service
UNIT FILE                                     STATE
arp-ethers.service                            disabled
auditd.service                                enabled
[email protected]                               enabled
blk-availability.service                      disabled
brandbot.service                              static
[email protected]                        static
chrony-wait.service                           disabled
chronyd.service                               enabled
cloud-config.service                          enabled
cloud-final.service                           enabled
cloud-init-local.service                      enabled
cloud-init.service                            enabled
console-getty.service                         disabled
console-shell.service                         disabled
[email protected]                      static
cpupower.service                              disabled
crond.service                                 enabled
.
.
150 unit files listed.

使用下面的格式通过正则表达式匹配出你想要查看的服务的当前状态。下面是使用 systemctl 命令查看 httpd 服务的状态。

# systemctl list-unit-files --type=service | grep httpd
httpd.service disabled

如何让指定的服务开机自启

使用下面格式的 systemctl 命令启用一个指定的服务。启用服务将会创建一个符号链接,如下可见:

# systemctl enable httpd
Created symlink from /etc/systemd/system/multi-user.target.wants/httpd.service to /usr/lib/systemd/system/httpd.service.

运行下列命令再次确认服务是否被启用。

# systemctl is-enabled httpd
enabled

如何禁用指定的服务

运行下面的命令禁用服务将会移除你启用服务时所创建的符号链接。

# systemctl disable httpd
Removed symlink /etc/systemd/system/multi-user.target.wants/httpd.service.

运行下面的命令再次确认服务是否被禁用。

# systemctl is-enabled httpd
disabled

如何查看系统当前的运行级别

使用 systemctl 命令确认你系统当前的运行级别,runlevel 命令仍然可在 systemd 下工作,不过,运行级别对于 systemd 来说是一个历史遗留的概念。所以我建议你全部使用 systemctl 命令。

我们当前处于运行级别 3, 它等同于下面显示的 multi-user.target

# systemctl list-units --type=target
UNIT                  LOAD   ACTIVE SUB    DESCRIPTION
basic.target          loaded active active Basic System
cloud-config.target   loaded active active Cloud-config availability
cryptsetup.target     loaded active active Local Encrypted Volumes
getty.target          loaded active active Login Prompts
local-fs-pre.target   loaded active active Local File Systems (Pre)
local-fs.target       loaded active active Local File Systems
multi-user.target     loaded active active Multi-User System
network-online.target loaded active active Network is Online
network-pre.target    loaded active active Network (Pre)
network.target        loaded active active Network
paths.target          loaded active active Paths
remote-fs.target      loaded active active Remote File Systems
slices.target         loaded active active Slices
sockets.target        loaded active active Sockets
swap.target           loaded active active Swap
sysinit.target        loaded active active System Initialization
timers.target         loaded active active Timers

via: https://www.2daygeek.com/how-to-enable-or-disable-services-on-boot-in-linux-using-chkconfig-and-systemctl-command/

作者:Prakash Subramanian 选题:lujun9972 译者:way-ww 校对:wxy

本文由 LCTT 原创编译,Linux中国 荣誉推出

用这个简单的工具生成带有多表的大型数据库,让你更好地用 SQL 研究数据科学。

在研究数据科学的过程中,最麻烦的往往不是算法或者技术,而是如何获取到一批原始数据。尽管网上有很多真实优质的数据集可以用于机器学习,然而在学习 SQL 时却不是如此。

对于数据科学来说,熟悉 SQL 的重要性不亚于了解 Python 或 R 编程。如果想收集诸如姓名、年龄、信用卡信息、地址这些信息用于机器学习任务,在 Kaggle 上查找专门的数据集比使用足够大的真实数据库要容易得多。

如果有一个简单的工具或库来帮助你生成一个大型数据库,表里还存放着大量你需要的数据,岂不美哉?

不仅仅是数据科学的入门者,即使是经验丰富的软件测试人员也会需要这样一个简单的工具,只需编写几行代码,就可以通过随机(但是是假随机)生成任意数量但有意义的数据集。

因此,我要推荐这个名为 pydbgen 的轻量级 Python 库。在后文中,我会简要说明这个库的相关内容,你也可以阅读它的文档详细了解更多信息。

pydbgen 是什么

pydbgen 是一个轻量的纯 Python 库,它可以用于生成随机但有意义的数据记录(包括姓名、地址、信用卡号、日期、时间、公司名称、职位、车牌号等等),存放在 Pandas Dataframe 对象中,并保存到 SQLite 数据库或 Excel 文件。

如何安装 pydbgen

目前 1.0.5 版本的 pydbgen 托管在 PyPI( Python 包索引存储库 Python Package Index repository )上,并且对 Faker 有依赖关系。安装 pydbgen 只需要执行命令:

pip install pydbgen

已经在 Python 3.6 环境下测试安装成功,但在 Python 2 环境下无法正常安装。

如何使用 pydbgen

在使用 pydbgen 之前,首先要初始化 pydb 对象。

import pydbgen
from pydbgen import pydbgen
myDB=pydbgen.pydb()

随后就可以调用 pydb 对象公开的各种内部函数了。可以按照下面的例子,输出随机的美国城市和车牌号码:

myDB.city_real()
>> 'Otterville'
for _ in range(10):
    print(myDB.license_plate())
>> 8NVX937
   6YZH485
   XBY-564
   SCG-2185
   XMR-158
   6OZZ231
   CJN-850
   SBL-4272
   TPY-658
   SZL-0934

另外,如果你输入的是 city() 而不是 city_real(),返回的将会是虚构的城市名。

print(myDB.gen_data_series(num=8,data_type='city'))
>>
New Michelle
Robinborough
Leebury
Kaylatown
Hamiltonfort
Lake Christopher
Hannahstad
West Adamborough

生成随机的 Pandas Dataframe

你可以指定生成数据的数量和种类,但需要注意的是,返回结果均为字符串或文本类型。

testdf=myDB.gen_dataframe(5,['name','city','phone','date'])
testdf

最终产生的 Dataframe 类似下图所示。

生成数据库表

你也可以指定生成数据的数量和种类,而返回结果是数据库中的文本或者变长字符串类型。在生成过程中,你可以指定对应的数据库文件名和表名。

myDB.gen_table(db_file='Testdb.DB',table_name='People',

fields=['name','city','street_address','email'])

上面的例子种生成了一个能被 MySQL 和 SQLite 支持的 .db 文件。下图则显示了这个文件中的数据表在 SQLite 可视化客户端中打开的画面。

生成 Excel 文件

和上面的其它示例类似,下面的代码可以生成一个具有随机数据的 Excel 文件。值得一提的是,通过将 phone_simple 参数设为 False ,可以生成较长较复杂的电话号码。如果你想要提高自己在数据提取方面的能力,不妨尝试一下这个功能。

myDB.gen_excel(num=20,fields=['name','phone','time','country'],
phone_simple=False,filename='TestExcel.xlsx')

最终的结果类似下图所示:

生成随机电子邮箱地址

pydbgen 内置了一个 realistic_email 方法,它基于种子来生成随机的电子邮箱地址。如果你不想在网络上使用真实的电子邮箱地址时,这个功能可以派上用场。

for _ in range(10):
    print(myDB.realistic_email('Tirtha Sarkar'))
>>
[email protected]
[email protected]
[email protected]
[email protected]
[email protected]
[email protected]
[email protected]
[email protected]
[email protected]
[email protected]

未来的改进和用户贡献

目前的版本中并不完美。如果你发现了 pydbgen 的 bug 导致它在运行期间发生崩溃,请向我反馈。如果你打算对这个项目贡献代码,也随时欢迎你。当然现在也还有很多改进的方向:

  • pydbgen 作为随机数据生成器,可以集成一些机器学习或统计建模的功能吗?
  • pydbgen 是否会添加可视化功能?

一切皆有可能!

如果你有任何问题或想法想要分享,都可以通过 [email protected] 与我联系。如果你像我一样对机器学习和数据科学感兴趣,也可以添加我的 LinkedIn 或在 Twitter 上关注我。另外,还可以在我的 GitHub 上找到更多 Python、R 或 MATLAB 的有趣代码和机器学习资源。

本文以 CC BY-SA 4.0 许可在 Towards Data Science 首发。


via: https://opensource.com/article/18/11/pydbgen-random-database-table-generator

作者:Tirthajyoti Sarkar 选题:lujun9972 译者:HankChow 校对:wxy

本文由 LCTT 原创编译,Linux中国 荣誉推出

大多数系统管理员在遇到性能问题时会检查 CPU 和内存利用率。Linux 中有许多实用程序可以用于检查物理内存。这些命令有助于我们检查系统中存在的物理内存,还允许用户检查各种方面的内存利用率。

我们大多数人只知道很少的命令,在本文中我们试图包含所有可能的命令。

你可能会想,为什么我想知道所有这些命令,而不是知道一些特定的和例行的命令呢。

不要觉得没用或对此有负面的看法,因为每个人都有不同的需求和看法,所以,对于那些在寻找其它目的的人,这对于他们非常有帮助。

什么是 RAM

计算机内存是能够临时或永久存储信息的物理设备。RAM 代表随机存取存储器,它是一种易失性存储器,用于存储操作系统,软件和硬件使用的信息。

有两种类型的内存可供选择:

  • 主存
  • 辅助内存

主存是计算机的主存储器。CPU 可以直接读取或写入此内存。它固定在电脑的主板上。

  • RAM:随机存取存储器是临时存储。关闭计算机后,此信息将消失。
  • ROM: 只读存储器是永久存储,即使系统关闭也能保存数据。

方法-1:使用 free 命令

free 显示系统中空闲和已用的物理内存和交换内存的总量,以及内核使用的缓冲区和缓存。它通过解析 /proc/meminfo 来收集信息。

建议阅读: free – 在 Linux 系统中检查内存使用情况统计(空闲和已用)的标准命令

$ free -m
              total        used        free      shared  buff/cache   available
Mem:           1993        1681          82          81         228         153
Swap:         12689        1213       11475

$ free -g
              total        used        free      shared  buff/cache   available
Mem:              1           1           0           0           0           0
Swap:            12           1          11

方法-2:使用 /proc/meminfo 文件

/proc/meminfo 是一个虚拟文本文件,它包含有关系统 RAM 使用情况的大量有价值的信息。

它报告系统上的空闲和已用内存(物理和交换)的数量。

$ grep MemTotal /proc/meminfo
MemTotal:        2041396 kB

$ grep MemTotal /proc/meminfo | awk '{print $2 / 1024}'
1993.55

$ grep MemTotal /proc/meminfo | awk '{print $2 / 1024 / 1024}'
1.94683

方法-3:使用 top 命令

top 命令是 Linux 中监视实时系统进程的基本命令之一。它显示系统信息和运行的进程信息,如正常运行时间、平均负载、正在运行的任务、登录的用户数、CPU 数量和 CPU 利用率,以及内存和交换信息。运行 top 命令,然后按下 E 来使内存利用率以 MB 为单位显示。

建议阅读: TOP 命令示例监视服务器性能

$ top

top - 14:38:36 up  1:59,  1 user,  load average: 1.83, 1.60, 1.52
Tasks: 223 total,   2 running, 221 sleeping,   0 stopped,   0 zombie
%Cpu(s): 48.6 us, 11.2 sy,  0.0 ni, 39.3 id,  0.3 wa,  0.0 hi,  0.5 si,  0.0 st
MiB Mem : 1993.551 total,   94.184 free, 1647.367 used,  252.000 buff/cache
MiB Swap: 12689.58+total, 11196.83+free, 1492.750 used.  306.465 avail Mem 

  PID USER      PR  NI    VIRT    RES    SHR S  %CPU %MEM     TIME+ COMMAND                                                                                                                                        
 9908 daygeek   20   0 2971440 649324  39700 S  55.8 31.8  11:45.74 Web Content                                                                                                                                    
21942 daygeek   20   0 2013760 308700  69272 S  35.0 15.1   4:13.75 Web Content                                                                                                                                    
 4782 daygeek   20   0 3687116 227336  39156 R  14.5 11.1  16:47.45 gnome-shell

方法-4:使用 vmstat 命令

vmstat 是一个漂亮的标准工具,它报告 Linux 系统的虚拟内存统计信息。vmstat 报告有关进程、内存、分页、块 IO、陷阱和 CPU 活动的信息。它有助于 Linux 管理员在故障检修时识别系统瓶颈。

建议阅读: vmstat – 一个报告虚拟内存统计信息的标准且漂亮的工具

$ vmstat -s | grep "total memory"
      2041396 K total memory
      
$ vmstat -s -S M | egrep -ie 'total memory'
         1993 M total memory

$ vmstat -s | awk '{print $1 / 1024 / 1024}' | head -1
1.94683

方法-5:使用 nmon 命令

nmon 是另一个很棒的工具,用于在 Linux 终端上监视各种系统资源,如 CPU、内存、网络、磁盘、文件系统、NFS、top 进程、Power 的微分区和资源(Linux 版本和处理器)。

只需按下 m 键,即可查看内存利用率统计数据(缓存、活动、非活动、缓冲、空闲,以 MB 和百分比为单位)。

建议阅读: nmon – Linux 中一个监视系统资源的漂亮的工具

┌nmon─14g──────[H for help]───Hostname=2daygeek──Refresh= 2secs ───07:24.44─────────────────┐
│ Memory Stats ─────────────────────────────────────────────────────────────────────────────│
│                RAM     High      Low     Swap    Page Size=4 KB                           │
│ Total MB     32079.5     -0.0     -0.0  20479.0                                           │
│ Free  MB     11205.0     -0.0     -0.0  20479.0                                           │
│ Free Percent    34.9%   100.0%   100.0%   100.0%                                          │
│             MB                  MB                  MB                                    │
│                      Cached= 19763.4     Active=  9617.7                                  │
│ Buffers=   172.5 Swapcached=     0.0  Inactive = 10339.6                                  │
│ Dirty  =     0.0 Writeback =     0.0  Mapped   =    11.0                                  │
│ Slab   =   636.6 Commit_AS =   118.2 PageTables=     3.5                                  │
│───────────────────────────────────────────────────────────────────────────────────────────│
│                                                                                           │
│                                                                                           │
│                                                                                           │
│                                                                                           │
│                                                                                           │
│                                                                                           │
└───────────────────────────────────────────────────────────────────────────────────────────┘

方法-6:使用 dmidecode 命令

dmidecode 是一个读取计算机 DMI 表内容的工具,它以人类可读的格式显示系统硬件信息。(DMI 意即桌面管理接口,也有人说是读取的是 SMBIOS —— 系统管理 BIOS)

此表包含系统硬件组件的描述,以及其它有用信息,如序列号、制造商信息、发布日期和 BIOS 修改等。

建议阅读: Dmidecode – 获取 Linux 系统硬件信息的简便方法

# dmidecode -t memory | grep  Size:
        Size: 8192 MB
        Size: No Module Installed
        Size: No Module Installed
        Size: 8192 MB
        Size: No Module Installed
        Size: No Module Installed
        Size: No Module Installed
        Size: No Module Installed
        Size: No Module Installed
        Size: No Module Installed
        Size: No Module Installed
        Size: No Module Installed
        Size: 8192 MB
        Size: No Module Installed
        Size: No Module Installed
        Size: 8192 MB
        Size: No Module Installed
        Size: No Module Installed
        Size: No Module Installed
        Size: No Module Installed
        Size: No Module Installed
        Size: No Module Installed
        Size: No Module Installed
        Size: No Module Installed

只打印已安装的 RAM 模块。

# dmidecode -t memory | grep  Size: | grep -v "No Module Installed"
        Size: 8192 MB
        Size: 8192 MB
        Size: 8192 MB
        Size: 8192 MB     

汇总所有已安装的 RAM 模块。

# dmidecode -t memory | grep  Size: | grep -v "No Module Installed" | awk '{sum+=$2}END{print sum}'
32768

方法-7:使用 hwinfo 命令

hwinfo 意即硬件信息,它是另一个很棒的实用工具,用于探测系统中存在的硬件,并以人类可读的格式显示有关各种硬件组件的详细信息。

它报告有关 CPU、RAM、键盘、鼠标、图形卡、声音、存储、网络接口、磁盘、分区、BIOS 和网桥等的信息。

建议阅读: hwinfo(硬件信息)– 一个在 Linux 系统上检测系统硬件信息的好工具

$ hwinfo --memory
01: None 00.0: 10102 Main Memory
  [Created at memory.74]
  Unique ID: rdCR.CxwsZFjVASF
  Hardware Class: memory
  Model: "Main Memory"
  Memory Range: 0x00000000-0x7a4abfff (rw)
  Memory Size: 1 GB + 896 MB
  Config Status: cfg=new, avail=yes, need=no, active=unknown

方法-8:使用 lshw 命令

lshw(代表 Hardware Lister)是一个小巧的工具,可以生成机器上各种硬件组件的详细报告,如内存配置、固件版本、主板配置、CPU 版本和速度、缓存配置、USB、网卡、显卡、多媒体、打印机、总线速度等。

它通过读取 /proc 目录和 DMI 表中的各种文件来生成硬件信息。

建议阅读: LSHW (Hardware Lister) – 一个在 Linux 上获取硬件信息的好工具

$ sudo lshw -short -class memory
[sudo] password for daygeek: 
H/W path      Device       Class       Description
==================================================
/0/0                       memory      128KiB BIOS
/0/1                       memory      1993MiB System memory

方法-9:使用 inxi 命令

inxi 是一个很棒的工具,它可以检查 Linux 上的硬件信息,并提供了大量的选项来获取 Linux 系统上的所有硬件信息,这些特性是我在 Linux 上的其它工具中从未发现的。它是从 locsmif 编写的古老的但至今看来都异常灵活的 infobash 演化而来的。

inxi 是一个脚本,它可以快速显示系统硬件、CPU、驱动程序、Xorg、桌面、内核、GCC 版本、进程、RAM 使用情况以及各种其它有用的信息,还可以用于论坛技术支持和调试工具。

建议阅读: inxi – 一个检查 Linux 上硬件信息的好工具

$ inxi -F | grep "Memory"
Info:      Processes: 234 Uptime: 3:10 Memory: 1497.3/1993.6MB Client: Shell (bash) inxi: 2.3.37 

方法-10:使用 screenfetch 命令

screenfetch 是一个 bash 脚本。它将自动检测你的发行版,并在右侧显示该发行版标识的 ASCII 艺术版本和一些有价值的信息。

建议阅读: ScreenFetch – 以 ASCII 艺术标志在终端显示 Linux 系统信息

$ screenfetch
                          ./+o+-       daygeek@ubuntu
                  yyyyy- -yyyyyy+      OS: Ubuntu 17.10 artful
               ://+//////-yyyyyyo      Kernel: x86_64 Linux 4.13.0-37-generic
           .++ .:/++++++/-.+sss/`      Uptime: 44m
         .:++o:  /++++++++/:--:/-      Packages: 1831
        o:+o+:++.`..`` `.-/oo+++++/     Shell: bash 4.4.12
       .:+o:+o/.          `+sssoo+/    Resolution: 1920x955
  .++/+:+oo+o:`             /sssooo.   DE: GNOME 
 /+++//+:`oo+o               /::--:.   WM: GNOME Shell
 \+/+o+++`o++o               ++////.   WM Theme: Adwaita
  .++.o+++oo+:`             /dddhhh.   GTK Theme: Azure [GTK2/3]
       .+.o+oo:.          `oddhhhh+    Icon Theme: Papirus-Dark
        \+.++o+o``-````.:ohdhhhhh+     Font: Ubuntu 11
         `:o+++ `ohhhhhhhhyo++os:      CPU: Intel Core i7-6700HQ @ 2x 2.592GHz
           .o:`.syhhhhhhh/.oo++o`      GPU: llvmpipe (LLVM 5.0, 256 bits)
               /osyyyyyyo++ooo+++/     RAM: 1521MiB / 1993MiB
                   ````` +oo+++o\:    
                          `oo++.      

方法-11:使用 neofetch 命令

neofetch 是一个跨平台且易于使用的命令行(CLI)脚本,它收集你的 Linux 系统信息,并将其作为一张图片显示在终端上,也可以是你的发行版徽标,或者是你选择的任何 ascii 艺术。

建议阅读: Neofetch – 以 ASCII 分发标志来显示 Linux 系统信息

$ neofetch
            .-/+oossssoo+/-.               daygeek@ubuntu
        `:+ssssssssssssssssss+:`           --------------
      -+ssssssssssssssssssyyssss+-         OS: Ubuntu 17.10 x86_64
    .ossssssssssssssssssdMMMNysssso.       Host: VirtualBox 1.2
   /ssssssssssshdmmNNmmyNMMMMhssssss/      Kernel: 4.13.0-37-generic
  +ssssssssshmydMMMMMMMNddddyssssssss+     Uptime: 47 mins
 /sssssssshNMMMyhhyyyyhmNMMMNhssssssss/    Packages: 1832
.ssssssssdMMMNhsssssssssshNMMMdssssssss.   Shell: bash 4.4.12
+sssshhhyNMMNyssssssssssssyNMMMysssssss+   Resolution: 1920x955
ossyNMMMNyMMhsssssssssssssshmmmhssssssso   DE: ubuntu:GNOME
ossyNMMMNyMMhsssssssssssssshmmmhssssssso   WM: GNOME Shell
+sssshhhyNMMNyssssssssssssyNMMMysssssss+   WM Theme: Adwaita
.ssssssssdMMMNhsssssssssshNMMMdssssssss.   Theme: Azure [GTK3]
 /sssssssshNMMMyhhyyyyhdNMMMNhssssssss/    Icons: Papirus-Dark [GTK3]
  +sssssssssdmydMMMMMMMMddddyssssssss+     Terminal: gnome-terminal
   /ssssssssssshdmNNNNmyNMMMMhssssss/      CPU: Intel i7-6700HQ (2) @ 2.591GHz
    .ossssssssssssssssssdMMMNysssso.       GPU: VirtualBox Graphics Adapter
      -+sssssssssssssssssyyyssss+-         Memory: 1620MiB / 1993MiB
        `:+ssssssssssssssssss+:` 
            .-/+oossssoo+/-.                                       

方法-12:使用 dmesg 命令

dmesg(代表显示消息或驱动消息)是大多数类 Unix 操作系统上的命令,用于打印内核的消息缓冲区。

$ dmesg | grep "Memory"
[    0.000000] Memory: 1985916K/2096696K available (12300K kernel code, 2482K rwdata, 4000K rodata, 2372K init, 2368K bss, 110780K reserved, 0K cma-reserved)
[    0.012044] x86/mm: Memory block size: 128MB

方法-13:使用 atop 命令

atop 是一个用于 Linux 的 ASCII 全屏系统性能监视工具,它能报告所有服务器进程的活动(即使进程在间隔期间已经完成)。

它记录系统和进程活动以进行长期分析(默认情况下,日志文件保存 28 天),通过使用颜色等来突出显示过载的系统资源。它结合可选的内核模块 netatop 显示每个进程或线程的网络活动。

建议阅读: Atop – 实时监控系统性能,资源,进程和检查资源利用历史

$ atop -m

ATOP - ubuntu                                                   2018/03/31  19:34:08                                                   -------------                                                    10s elapsed
PRC | sys    0.47s  | user   2.75s  |               |              |  #proc    219 |  #trun      1 | #tslpi   802  | #tslpu     0  | #zombie    0  | clones     7 |               |               |  #exit      4 |
CPU | sys       7%  | user     22%  | irq       0%  |              |               |  idle    170% | wait      0%  |               | steal     0%  | guest     0% |               |  curf 2.59GHz |  curscal   ?% |
cpu | sys       3%  | user     11%  | irq       0%  |              |               |  idle     85% | cpu001 w  0%  |               | steal     0%  | guest     0% |               |  curf 2.59GHz |  curscal   ?% |
cpu | sys       4%  | user     11%  | irq       0%  |              |               |  idle     85% | cpu000 w  0%  |               | steal     0%  | guest     0% |               |  curf 2.59GHz |  curscal   ?% |
CPL | avg1    1.98  |               | avg5    3.56  | avg15   3.20 |               |               | csw    14894  |               | intr    6610  |              |               |  numcpu     2 |               |
MEM | tot     1.9G  | free  101.7M  | cache 244.2M  | dirty   0.2M |  buff    6.9M |  slab   92.9M | slrec  35.6M  | shmem  97.8M  | shrss  21.0M  | shswp   3.2M |  vmbal   0.0M |  hptot   0.0M |  hpuse   0.0M |
SWP | tot    12.4G  | free   11.6G  |               |              |               |               |               |               |               |              |  vmcom   7.9G |               |  vmlim  13.4G |
PAG | scan       0  | steal      0  |               | stall      0 |               |               |               |               |               |              |  swin       3 |               |  swout      0 |
DSK |          sda  | busy      0%  |               | read     114 |  write     37 |  KiB/r     21 | KiB/w      6  |               | MBr/s    0.2  | MBw/s    0.0 |  avq     6.50 |               |  avio 0.26 ms |
NET | transport     | tcpi      11  | tcpo      17  | udpi       4 |  udpo       8 |  tcpao      3 | tcppo      0  |               | tcprs      3  | tcpie      0 |  tcpor      0 |  udpnp      0 |  udpie      0 |
NET | network       | ipi       20  |               | ipo       33 |  ipfrw      0 |  deliv     20 |               |               |               |              |  icmpi      5 |               |  icmpo      0 |
NET | enp0s3    0%  | pcki      11  | pcko      28  | sp 1000 Mbps |  si    1 Kbps |  so    1 Kbps |               | coll       0  | mlti       0  | erri       0 |  erro       0 |  drpi       0 |  drpo       0 |
NET | lo      ----  | pcki       9  | pcko       9  | sp    0 Mbps |  si    0 Kbps |  so    0 Kbps |               | coll       0  | mlti       0  | erri       0 |  erro       0 |  drpi       0 |  drpo       0 |

   PID        TID     MINFLT      MAJFLT     VSTEXT      VSLIBS      VDATA      VSTACK      VSIZE       RSIZE      PSIZE       VGROW      RGROW      SWAPSZ     RUID          EUID          MEM      CMD        1/1
  2536          -        941           0       188K      127.3M     551.2M        144K       2.3G      281.2M         0K          0K       344K       6556K     daygeek       daygeek       14%      Web Content
  2464          -         75           0       188K      187.7M     680.6M        132K       2.3G      226.6M         0K          0K       212K      42088K     daygeek       daygeek       11%      firefox
  2039          -       4199           6        16K      163.6M     423.0M        132K       3.5G      220.2M         0K          0K      2936K      109.6M     daygeek       daygeek       11%      gnome-shell
 10822          -          1           0         4K      16680K     377.0M        132K       3.4G      193.4M         0K          0K         0K          0K     root          root          10%      java

方法-14:使用 htop 命令

htop 是由 Hisham 用 ncurses 库开发的用于 Linux 的交互式进程查看器。与 top 命令相比,htop 有许多特性和选项。

建议阅读: 使用 Htop 命令监视系统资源

$ htop

  1  [|||||||||||||                                                                             13.0%]   Tasks: 152, 587 thr; 1 running
  2  [|||||||||||||||||||||||||                                                                 25.0%]   Load average: 0.91 2.03 2.66 
  Mem[||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||1.66G/1.95G]   Uptime: 01:14:53
  Swp[||||||                                                                               782M/12.4G]

  PID USER      PRI  NI  VIRT   RES   SHR S CPU% MEM%   TIME+  Command
 2039 daygeek    20   0 3541M  214M 46728 S 36.6 10.8 22:36.77 /usr/bin/gnome-shell
 2045 daygeek    20   0 3541M  214M 46728 S 10.3 10.8  3:02.92 /usr/bin/gnome-shell
 2046 daygeek    20   0 3541M  214M 46728 S  8.3 10.8  3:04.96 /usr/bin/gnome-shell
 6080 daygeek    20   0  807M 37228 24352 S  2.1  1.8  0:11.99 /usr/lib/gnome-terminal/gnome-terminal-server
 2880 daygeek    20   0 2205M  164M 17048 S  2.1  8.3  7:16.50 /usr/lib/firefox/firefox -contentproc -childID 6 -isForBrowser -intPrefs 6:50|7:-1|19:0|34:1000|42:20|43:5|44:10|51:0|57:128|58:10000|63:0|65:400|66
 6125 daygeek    20   0 1916M  159M 92352 S  2.1  8.0  2:09.14 /usr/lib/firefox/firefox -contentproc -childID 7 -isForBrowser -intPrefs 6:50|7:-1|19:0|34:1000|42:20|43:5|44:10|51:0|57:128|58:10000|63:0|65:400|66
 2536 daygeek    20   0 2335M  243M 26792 S  2.1 12.2  6:25.77 /usr/lib/firefox/firefox -contentproc -childID 1 -isForBrowser -intPrefs 6:50|7:-1|19:0|34:1000|42:20|43:5|44:10|51:0|57:128|58:10000|63:0|65:400|66
 2653 daygeek    20   0 2237M  185M 20788 S  1.4  9.3  3:01.76 /usr/lib/firefox/firefox -contentproc -childID 4 -isForBrowser -intPrefs 6:50|7:-1|19:0|34:1000|42:20|43:5|44:10|51:0|57:128|58:10000|63:0|65:400|66

方法-15:使用 corefreq 实用程序

CoreFreq 是为 Intel 64 位处理器设计的 CPU 监控软件,支持的架构有 Atom、Core2、Nehalem、SandyBridge 和 superior,AMD 家族 0F。

CoreFreq 提供了一个框架来以高精确度检索 CPU 数据。

建议阅读: CoreFreq – 一个用于 Linux 系统的强大的 CPU 监控工具

$ ./corefreq-cli -k
Linux:                                                                          
|- Release                                                   [4.13.0-37-generic]
|- Version                          [#42-Ubuntu SMP Wed Mar 7 14:13:23 UTC 2018]
|- Machine                                                              [x86_64]
Memory:                                                                         
|- Total RAM                                                          2041396 KB
|- Shared RAM                                                           99620 KB
|- Free RAM                                                            108428 KB
|- Buffer RAM                                                            8108 KB
|- Total High                                                               0 KB
|- Free High                                                                0 KB

方法-16:使用 glances 命令

Glances 是用 Python 编写的跨平台基于 curses(LCTT 译注:curses 是一个 Linux/Unix 下的图形函数库)的系统监控工具。我们可以说它一应俱全,就像在最小的空间含有最大的信息。它使用 psutil 库从系统中获取信息。

Glances 可以监视 CPU、内存、负载、进程列表、网络接口、磁盘 I/O、Raid、传感器、文件系统(和文件夹)、Docker、监视器、警报、系统信息、正常运行时间、快速预览(CPU、内存、负载)等。

建议阅读: Glances (一应俱全)– 一个 Linux 的高级的实时系 统性能监控工具

$ glances

ubuntu (Ubuntu 17.10 64bit / Linux 4.13.0-37-generic) - IP 192.168.1.6/24                                                                                                                           Uptime: 1:08:40

CPU  [||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||        90.6%]   CPU -    90.6%  nice:     0.0%  ctx_sw:    4K      MEM \   78.4%  active:     942M      SWAP -    5.9%      LOAD    2-core
MEM  [|||||||||||||||||||||||||||||||||||||||||||||||||||||||||                 78.0%]   user:    55.1%  irq:      0.0%  inter:   1797      total:  1.95G  inactive:   562M      total:   12.4G      1 min:    4.35
SWAP [||||                                                                       5.9%]   system:  32.4%  iowait:   1.8%  sw_int:   897      used:   1.53G  buffers:   14.8M      used:     749M      5 min:    4.38
                                                                                         idle:     7.6%  steal:    0.0%                     free:    431M  cached:     273M      free:    11.7G      15 min:   3.38

NETWORK     Rx/s   Tx/s   TASKS 211 (735 thr), 4 run, 207 slp, 0 oth sorted automatically by memory_percent, flat view
docker0       0b   232b
enp0s3      12Kb    4Kb   Systemd          7    Services loaded: 197 active: 196 failed: 1 
lo          616b   616b
_h478e48e     0b   232b     CPU%  MEM%  VIRT   RES   PID USER        NI S     TIME+   R/s   W/s Command 
                            63.8  18.9 2.33G  377M  2536 daygeek      0 R   5:57.78     0     0 /usr/lib/firefox/firefox -contentproc -childID 1 -isForBrowser -intPrefs 6:50|7:-1|19:0|34:1000|42:20|43:5|44:10|51
DefaultGateway     83ms     78.5  10.9 3.46G  217M  2039 daygeek      0 S  21:07.46     0     0 /usr/bin/gnome-shell
                             8.5  10.1 2.32G  201M  2464 daygeek      0 S   8:45.69     0     0 /usr/lib/firefox/firefox -new-window
DISK I/O     R/s    W/s      1.1   8.5 2.19G  170M  2653 daygeek      0 S   2:56.29     0     0 /usr/lib/firefox/firefox -contentproc -childID 4 -isForBrowser -intPrefs 6:50|7:-1|19:0|34:1000|42:20|43:5|44:10|51
dm-0           0      0      1.7   7.2 2.15G  143M  2880 daygeek      0 S   7:10.46     0     0 /usr/lib/firefox/firefox -contentproc -childID 6 -isForBrowser -intPrefs 6:50|7:-1|19:0|34:1000|42:20|43:5|44:10|51
sda1       9.46M    12K      0.0   4.9 1.78G 97.2M  6125 daygeek      0 S   1:36.57     0     0 /usr/lib/firefox/firefox -contentproc -childID 7 -isForBrowser -intPrefs 6:50|7:-1|19:0|34:1000|42:20|43:5|44:10|51

方法-17 : 使用 Gnome 系统监视器

Gnome 系统监视器是一个管理正在运行的进程和监视系统资源的工具。它向你显示正在运行的程序以及耗费的处理器时间,内存和磁盘空间。


via: https://www.2daygeek.com/easy-ways-to-check-size-of-physical-memory-ram-in-linux/

作者:Ramya Nuvvula 译者:MjSeven 校对:wxy

本文由 LCTT 原创编译,Linux中国 荣誉推出

toolz 库允许你操作函数,使其更容易理解,更容易测试代码。

在这个由两部分组成的系列文章的第二部分中,我们将继续探索如何将函数式编程方法中的好想法引入到 Python中,以实现两全其美。

在上一篇文章中,我们介绍了不可变数据结构。 这些数据结构使得我们可以编写“纯”函数,或者说是没有副作用的函数,仅仅接受一些参数并返回结果,同时保持良好的性能。

在这篇文章中,我们使用 toolz 库来构建。 这个库具有操作此类函数的函数,并且它们在纯函数中表现得特别好。 在函数式编程世界中,它们通常被称为“高阶函数”,因为它们将函数作为参数,将函数作为结果返回。

让我们从这里开始:

def add_one_word(words, word):
    return words.set(words.get(word, 0) + 1)

这个函数假设它的第一个参数是一个不可变的类似字典的对象,它返回一个新的类似字典的在相关位置递增的对象:这就是一个简单的频率计数器。

但是,只有将它应用于单词流并做归纳时才有用。 我们可以使用内置模块 functools 中的归纳器。

functools.reduce(function, stream, initializer)

我们想要一个函数,应用于流,并且能能返回频率计数。

我们首先使用 toolz.curry 函数:

add_all_words = curry(functools.reduce, add_one_word)

使用此版本,我们需要提供初始化程序。但是,我们不能只将 pyrsistent.m 函数添加到 curry 函数中; 因为这个顺序是错误的。

add_all_words_flipped = flip(add_all_words)

flip 这个高阶函数返回一个调用原始函数的函数,并且翻转参数顺序。

get_all_words = add_all_words_flipped(pyrsistent.m())

我们利用 flip 自动调整其参数的特性给它一个初始值:一个空字典。

现在我们可以执行 get_all_words(word_stream) 这个函数来获取频率字典。 但是,我们如何获得一个单词流呢? Python 文件是按行供流的。

def to_words(lines):
    for line in lines:
        yield from line.split()

在单独测试每个函数后,我们可以将它们组合在一起:

words_from_file = toolz.compose(get_all_words, to_words)

在这种情况下,组合只是使两个函数很容易阅读:首先将文件的行流应用于 to_words,然后将 get_all_words 应用于 to_words 的结果。 但是文字上读起来似乎与代码执行相反。

当我们开始认真对待可组合性时,这很重要。有时可以将代码编写为一个单元序列,单独测试每个单元,最后将它们全部组合。如果有几个组合元素时,组合的顺序可能就很难理解。

toolz 库借用了 Unix 命令行的做法,并使用 pipe 作为执行相同操作的函数,但顺序相反。

words_from_file = toolz.pipe(to_words, get_all_words)

现在读起来更直观了:将输入传递到 to_words,并将结果传递给 get_all_words。 在命令行上,等效写法如下所示:

$ cat files | to_words | get_all_words

toolz 库允许我们操作函数,切片、分割和组合,以使我们的代码更容易理解和测试。


via: https://opensource.com/article/18/10/functional-programming-python-toolz

作者:Moshe Zadka 选题:lujun9972 译者:Flowsnow 校对:wxy

本文由 LCTT 原创编译,Linux中国 荣誉推出

在这个课程中你将使用两套工具:一个是 x86 模拟器 QEMU,它用来运行你的内核;另一个是编译器工具链,包括汇编器、链接器、C 编译器,以及调试器,它们用来编译和测试你的内核。本文有你需要去下载和安装你自己的副本相关信息。本课程假定你熟悉所有出现的 Unix 命令的用法。

我们强烈推荐你使用一个 Debathena 机器去做你的实验,比如 athena.dialup.mit.edu。如果你使用运行在 Linux 上的 MIT Athena 机器,那么本课程所需要的所有软件工具都在 6.828 的存储中:只需要输入 add -f 6.828 就可以访问它们。

如果你不使用 Debathena 机器,我们建议你使用一台 Linux 虚拟机。如果是这样,你可以在你的 Linux 虚拟机上构建和安装工具。我们将在下面介绍如何在 Linux 和 MacOS 计算上来构建和安装工具。

Cygwin 的帮助下,在 Windows 中运行这个开发环境也是可行的。安装 cygwin,并确保安装了 flex 和 bison 包(它们在开发 header 软件包分类下面)。

对于 6.828 中使用的工具中的有用的命令,请参考实验工具指南

编译器工具链

“编译器工具链“ 是一套程序,包括一个 C 编译器、汇编器和链接器,使用它们来将代码转换成可运行的二进制文件。你需要一个能够生成在 32 位 Intel 架构(x86 架构)上运行的 ELF 二进制格式程序的编译器工具链。

测试你的编译器工具链

现代的 Linux 和 BSD UNIX 发行版已经为 6.828 提供了一个合适的工具链。去测试你的发行版,可以输入如下的命令:

% objdump -i

第二行应该是 elf32-i386

% gcc -m32 -print-libgcc-file-name

这个命令应该会输出如 /usr/lib/gcc/i486-linux-gnu/version/libgcc.a/usr/lib/gcc/x86_64-linux-gnu/version/32/libgcc.a 这样的东西。

如果这些命令都运行成功,说明你的工具链都已安装,你不需要去编译你自己的工具链。

如果 gcc 命令失败,你可能需要去安装一个开发环境。在 Ubuntu Linux 上,输入如下的命令:

% sudo apt-get install -y build-essential gdb

在 64 位的机器上,你可能需要去安装一个 32 位的支持库。链接失败的表现是有一个类似于 “__udivdi3 not found” 和 “__muldi3 not found” 的错误信息。在 Ubuntu Linux 上,输入如下的命令去尝试修复这个问题:

% sudo apt-get install gcc-multilib

使用一个虚拟机

获得一个兼容的工具链的最容易的另外的方法是,在你的计算机上安装一个现代的 Linux 发行版。使用虚拟化平台,Linux 可以与你正常的计算环境和平共处。安装一个 Linux 虚拟机共有两步。首先,去下载一个虚拟化平台。

VirtualBox 有点慢并且灵活性欠佳,但它免费!

虚拟化平台安装完成后,下载一个你选择的 Linux 发行版的引导磁盘镜像。

这将下载一个命名类似于 ubuntu-10.04.1-desktop-i386.iso 的文件。启动你的虚拟化平台并创建一个新(32 位)的虚拟机。使用下载的 Ubuntu 镜像作为一个引导磁盘;安装过程在不同的虚拟机上有所不同,但都很简单。就像上面一样输入 objdump -i,去验证你的工具是否已安装。你将在虚拟机中完成你的工作。

构建你自己的编译器工具链

你需要花一些时间来设置,但是比起一个虚拟机来说,它的性能要稍好一些,并且让你工作在你熟悉的环境中(Unix/MacOS)。对于 MacOS 命令,你可以快进到文章的末尾部分去看。

Linux

通过将下列行添加到 conf/env.mk 中去使用你自己的工具链:

GCCPREFIX=

我们假设你将工具链安装到了 /usr/local 中。你将需要大量的空间(大约 1 GB)去编译工具。如果你空间不足,在它的 make install 步骤之后删除它们的目录。

下载下列包:

(你可能也在使用这些包的最新版本。)解包并构建。安装到 /usr/local 中,它是我们建议的。要安装到不同的目录,如 $PFX,注意相应修改。如果有问题,可以看下面。

export PATH=$PFX/bin:$PATH
export LD_LIBRARY_PATH=$PFX/lib:$LD_LIBRARY_PATH

tar xjf gmp-5.0.2.tar.bz2
cd gmp-5.0.2
./configure --prefix=$PFX
make
make install # This step may require privilege (sudo make install)
cd ..

tar xjf mpfr-3.1.2.tar.bz2
cd mpfr-3.1.2
./configure --prefix=$PFX --with-gmp=$PFX
make
make install # This step may require privilege (sudo make install)
cd ..

tar xzf mpc-0.9.tar.gz
cd mpc-0.9
./configure --prefix=$PFX --with-gmp=$PFX --with-mpfr=$PFX
make
make install # This step may require privilege (sudo make install)
cd ..


tar xjf binutils-2.21.1.tar.bz2
cd binutils-2.21.1
./configure --prefix=$PFX --target=i386-jos-elf --disable-werror
make
make install # This step may require privilege (sudo make install)
cd ..

i386-jos-elf-objdump -i
# Should produce output like:
# BFD header file version (GNU Binutils) 2.21.1
# elf32-i386
# (header little endian, data little endian)
# i386...


tar xjf gcc-core-4.6.4.tar.bz2
cd gcc-4.6.4
mkdir build # GCC will not compile correctly unless you build in a separate directory
cd build
../configure --prefix=$PFX --with-gmp=$PFX --with-mpfr=$PFX --with-mpc=$PFX \
 --target=i386-jos-elf --disable-werror \
 --disable-libssp --disable-libmudflap --with-newlib \
 --without-headers --enable-languages=c MAKEINFO=missing
make all-gcc
make install-gcc # This step may require privilege (sudo make install-gcc)
make all-target-libgcc
make install-target-libgcc # This step may require privilege (sudo make install-target-libgcc)
cd ../..

i386-jos-elf-gcc -v
# Should produce output like:
# Using built-in specs.
# COLLECT_GCC=i386-jos-elf-gcc
# COLLECT_LTO_WRAPPER=/usr/local/libexec/gcc/i386-jos-elf/4.6.4/lto-wrapper
# Target: i386-jos-elf


tar xjf gdb-7.3.1.tar.bz2
cd gdb-7.3.1
./configure --prefix=$PFX --target=i386-jos-elf --program-prefix=i386-jos-elf- \
 --disable-werror
make all
make install # This step may require privilege (sudo make install)
cd ..

Linux 排错:

Q:我不能运行 make install,因为我在这台机器上没有 root 权限。

A:我们的指令假定你是安装到了 /usr/local 目录中。但是,在你的环境中可能并不是这样做的。如果你仅能够在你的家目录中安装代码。那么在上面的命令中,使用 --prefix=$HOME 去替换 --prefix=/usr/local。你也需要修改你的 PATHLD_LIBRARY_PATH 环境变量,以通知你的 shell 这个工具的位置。例如:

export PATH=$HOME/bin:$PATH
export LD_LIBRARY_PATH=$HOME/lib:$LD_LIBRARY_PATH

在你的 ~/.bashrc 文件中输入这些行,以便于你登入后不需要每次都输入它们。

Q:我构建失败了,错误信息是 “library not found”。

A:你需要去设置你的 LD_LIBRARY_PATH。环境变量必须包含 PREFIX/lib 目录(例如 /usr/local/lib)。

MacOS

首先从 Mac OSX 上安装开发工具开始:xcode-select --install

你可以从 homebrew 上安装 qemu 的依赖,但是不能去安装 qemu,因为我们需要安装打了 6.828 补丁的 qemu。

brew install $(brew deps qemu)

gettext 工具并不能把它已安装的二进制文件添加到路径中,因此你需要去运行:

PATH=${PATH}:/usr/local/opt/gettext/bin make install

完成后,开始安装 qemu。

QEMU 模拟器

QEMU 是一个现代化的、并且速度非常快的 PC 模拟器。QEMU 的 2.3.0 版本是设置在 Athena 上的 6.828 中的 x86 机器存储中的(add -f 6.828)。

不幸的是,QEMU 的调试功能虽然很强大,但是有点不成熟,因此我们强烈建议你使用我们打过 6.828 补丁的版本,而不是发行版自带的版本。这个安装在 Athena 上的 QEMU 版本已经打过补丁了。构建你自己的、打 6.828 补丁的 QEMU 版本的过程如下:

  1. 克隆 IAP 6.828 QEMU 的 git 仓库:git clone https://github.com/mit-pdos/6.828-qemu.git qemu
  2. 在 Linux 上,你或许需要安装几个库。我们成功地在 Debian/Ubuntu 16.04 上构建 6.828 版的 QEMU 需要安装下列的库:libsdl1.2-dev、libtool-bin、libglib2.0-dev、libz-dev 和 libpixman-1-dev。
  3. 配置源代码(方括号中是可选参数;用你自己的真实路径替换 PFX

    1. Linux:./configure --disable-kvm --disable-werror [--prefix=PFX] [--target-list="i386-softmmu x86_64-softmmu"]
    2. OS X:./configure --disable-kvm --disable-werror --disable-sdl [--prefix=PFX] [--target-list="i386-softmmu x86_64-softmmu"]prefix 参数指定安装 QEMU 的位置;如果不指定,将缺省安装 QEMU 到 /usr/local 目录中。target-list 参数将简单地简化 QEMU 所支持的架构。
  4. 运行 make && make install

via: https://pdos.csail.mit.edu/6.828/2018/tools.html

作者:csail.mit 选题:lujun9972 译者:qhwdw 校对:wxy

本文由 LCTT 原创编译,Linux中国 荣誉推出

在前面的文章中,我展示了 Sed 命令的基本用法, Sed 是一个实用的流编辑器。今天,我们准备去了解关于 Sed 更多的知识,深入了解 Sed 的运行模式。这将是你全面了解 Sed 命令的一个机会,深入挖掘它的运行细节和精妙之处。因此,如果你已经做好了准备,那就打开终端吧,下载测试文件 然后坐在电脑前:开始我们的探索之旅吧!

关于 Sed 的一点点理论知识

首先我们看一下 sed 的运行模式

要准确理解 Sed 命令,你必须先了解工具的运行模式。

当处理数据时,Sed 从输入源一次读入一行,并将它保存到所谓的 模式空间 pattern space 中。所有 Sed 的变换都发生在模式空间。变换都是由命令行上或外部 Sed 脚本文件提供的单字母命令来描述的。大多数 Sed 命令都可以由一个地址或一个地址范围作为前导来限制它们的作用范围。

默认情况下,Sed 在结束每个处理循环后输出模式空间中的内容,也就是说,输出发生在输入的下一个行覆盖模式空间之前。我们可以将这种运行模式总结如下:

  1. 尝试将下一个行读入到模式空间中
  2. 如果读取成功:

    1. 按脚本中的顺序将所有命令应用到与那个地址匹配的当前输入行上
    2. 如果 sed 没有以静默模式(-n)运行,那么将输出模式空间中的所有内容(可能会是修改过的)。
    3. 重新回到 1。

因此,在每个行被处理完毕之后,模式空间中的内容将被丢弃,它并不适合长时间保存内容。基于这种目的,Sed 有第二个缓冲区: 保持空间 hold space 。除非你显式地要求它将数据置入到保持空间、或从保持空间中取得数据,否则 Sed 从不清除保持空间的内容。在我们后面学习到 exchangegethold 命令时将深入研究它。

Sed 的抽象机制

你将在许多的 Sed 教程中都会看到上面解释的模式。的确,这是充分正确理解大多数基本 Sed 程序所必需的。但是当你深入研究更多的高级命令时,你将会发现,仅这些知识还是不够的。因此,我们现在尝试去了解更深入的一些知识。

的确,Sed 可以被视为是抽象机制的实现,它的状态)由三个缓冲区 、两个寄存器和两个标志来定义的:

  • 三个缓冲区用于去保存任意长度的文本。是的,是三个!在前面的基本运行模式中我们谈到了两个:模式空间和保持空间,但是 Sed 还有第三个缓冲区: 追加队列 append queue 。从 Sed 脚本的角度来看,它是一个只写缓冲区,Sed 将在它运行时的预定义阶段来自动刷新它(一般是在从输入源读入一个新行之前,或仅在它退出运行之前)。
  • Sed 也维护两个寄存器 行计数器 line counter (LC)用于保存从输入源读取的行数,而 程序计数器 program counter (PC)总是用来保存下一个将要运行的命令的索引(就是脚本中的位置),Sed 将它作为它的主循环的一部分来自动增加 PC。但在使用特定的命令时,脚本也可以直接修改 PC 去跳过或重复执行程序的一部分。这就像使用 Sed 实现的一个循环或条件语句。更多内容将在下面的专用分支一节中描述。
  • 最后,两个标志可以修改某些 Sed 命令的行为: 自动输出 auto-print (AP)标志和<ruby替换 substitution(SF)标志。当自动输出标志 AP 被设置时,Sed 将在模式空间的内容被覆盖前自动输出(尤其是,包括但不限于,在从输入源读入一个新行之前)。当自动输出标志被清除时(即:没有设置),Sed 在脚本中没有显式命令的情况下,将不会输出模式空间中的内容。你可以通过在“静默模式”(使用命令行选项 -n 或者在第一行或脚本中使用特殊注释 #n)运行 Sed 命令来清除自动输出标志。当它的地址和查找模式与模式空间中的内容都匹配时,替换标志 SF 将被替换命令(s 命令)设置。替换标志在每个新的循环开始时、或当从输入源读入一个新行时、或获得条件分支之后将被清除。我们将在分支一节中详细研究这一话题。

另外,Sed 维护一个进入到它的地址范围(关于地址范围的更多知识将在地址范围一节详细描述)的命令列表,以及用于读取和写入数据的两个文件句柄(你将在读取和写入命令的描述中获得更多有关文件句柄的内容)。

一个更精确的 Sed 运行模式

一图胜千言,所以我画了一个流程图去描述 Sed 的运行模式。我将两个东西放在了旁边,像处理多个输入文件或错误处理,但是我认为这足够你去理解任何 Sed 程序的行为了,并且可以避免你在编写你自己的 Sed 脚本时浪费在摸索上的时间。

The Sed execution model

你可能已经注意到,在上面的流程图上我并没有描述特定的命令动作。对于命令,我们将逐个详细讲解。因此,不用着急,我们马上开始!

打印命令

打印命令(p)是用于输出在它运行那一刻模式空间中的内容。它并不会以任何方式改变 Sed 抽象机制中的状态。

The Sed <code>print</code> command

示例:

sed -e 'p' inputfile

上面的命令将输出输入文件中每一行的内容……两次,因为你一旦显式地要求使用 p 命令时,将会在每个处理循环结束时再隐式地输出一次(因为在这里我们不是在“静默模式”中运行 Sed)。

如果我们不想每个行看到两次,我们可以用两种方式去解决它:

sed -n -e 'p' inputfile # 在静默模式中显式输出
sed -e '' inputfile # 空的“什么都不做的”程序,隐式输出

注意:-e 选项是引入一个 Sed 命令。它被用于区分命令和文件名。由于一个 Sed 表达式必须包含至少一个命令,所以对于第一个命令,-e 标志不是必需的。但是,由于我个人使用习惯问题,为了与在这里的大多数的一个命令行上给出多个 Sed 表达式的更复杂的案例保持一致性,我添加了它。你自己去判断这是一个好习惯还是坏习惯,并且在本文的后面部分还将延用这一习惯。

地址

显而易见,print 命令本身并没有太多的用处。但是,如果你在它之前添加一个地址,这样它就只输出输入文件的一些行,这样它就突然变得能够从一个输入文件中过滤一些不希望的行。那么 Sed 的地址又是什么呢?它是如何来辨别输入文件的“行”呢?

行号

Sed 的地址既可以是一个行号($ 表示“最后一行”)也可以是一个正则表达式。在使用行号时,你需要记住 Sed 中的行数是从 1 开始的 —— 并且需要注意的是,它不是从 0 行开始的。

sed -n -e '1p' inputfile # 仅输出文件的第一行
sed -n -e '5p' inputfile # 仅输出第 5 行
sed -n -e '$p' inputfile # 输出文件的最后一行
sed -n -e '0p' inputfile # 结果将是报错,因为 0 不是有效的行号

根据 POSIX 规范,如果你指定了几个输出文件,那么它的行号是累加的。换句话说,当 Sed 打开一个新输入文件时,它的行计数器是不会被重置的。因此,以下的两个命令所做的事情是一样的。仅输出一行文本:

sed -n -e '1p' inputfile1 inputfile2 inputfile3
cat inputfile1 inputfile2 inputfile3 | sed -n -e '1p'

实际上,确实在 POSIX 中规定了多个文件是如何处理的:

如果指定了多个文件,将按指定的文件命名顺序进行读取并被串联编辑。

但是,一些 Sed 的实现提供了命令行选项去改变这种行为,比如, GNU Sed 的 -s 标志(在使用 GNU Sed -i 标志时,它也被隐式地应用):

sed -sn -e '1p' inputfile1 inputfile2 inputfile3

如果你的 Sed 实现支持这种非标准选项,那么关于它的具体细节请查看 man 手册页。

正则表达式

我前面说过,Sed 地址既可以是行号也可以是正则表达式。那么正则表达式是什么呢?

正如它的名字,一个正则表达式是描述一个字符串集合的方法。如果一个指定的字符串符合一个正则表达式所描述的集合,那么我们就认为这个字符串与正则表达式匹配。

正则表达式可以包含必须完全匹配的文本字符。例如,所有的字母和数字,以及大部分可以打印的字符。但是,一些符号有特定意义:

  • 它们相当于锚,像 ^$ 它们分别表示一个行的开始和结束;
  • 能够做为整个字符集的占位符的其它符号(比如圆点 . 可以匹配任意单个字符,或者方括号 [] 用于定义一个自定义的字符集);
  • 另外的是表示重复出现的数量(像 克莱尼星号(* 表示前面的模式出现 0、1 或多次);

这篇文章的目的不是给大家讲正则表达式。因此,我只粘几个示例。但是,你可以在网络上随便找到很多关于正则表达式的教程,正则表达式的功能非常强大,它可用于许多标准的 Unix 命令和编程语言中,并且是每个 Unix 用户应该掌握的技能。

下面是使用 Sed 地址的几个示例:

sed -n -e '/systemd/p' inputfile # 仅输出包含字符串“systemd”的行
sed -n -e '/nologin$/p' inputfile # 仅输出以“nologin”结尾的行
sed -n -e '/^bin/p' inputfile # 仅输出以“bin”开头的行
sed -n -e '/^$/p' inputfile # 仅输出空行(即:开始和结束之间什么都没有的行)
sed -n -e '/./p' inputfile # 仅输出包含字符的行(即:非空行)
sed -n -e '/^.$/p' inputfile # 仅输出只包含一个字符的行
sed -n -e '/admin.*false/p' inputfile # 仅输出包含字符串“admin”后面有字符串“false”的行(在它们之间有任意数量的任意字符)
sed -n -e '/1[0,3]/p' inputfile # 仅输出包含一个“1”并且后面是一个“0”或“3”的行
sed -n -e '/1[0-2]/p' inputfile # 仅输出包含一个“1”并且后面是一个“0”、“1”、“2”或“3”的行
sed -n -e '/1.*2/p' inputfile # 仅输出包含字符“1”后面是一个“2”(在它们之间有任意数量的字符)的行
sed -n -e '/1[0-9]*2/p' inputfile # 仅输出包含字符“1”后面跟着“0”、“1”、或更多数字,最后面是一个“2”的行

如果你想在正则表达式(包括正则表达式分隔符)中去除字符的特殊意义,你可以在它前面使用一个反斜杠:

# 输出所有包含字符串“/usr/sbin/nologin”的行
sed -ne '/\/usr\/sbin\/nologin/p' inputfile

并不限制你只能使用斜杠作为地址中正则表达式的分隔符。你可以通过在第一个分隔符前面加上反斜杠(\)的方式,来使用任何你认为适合你需要和偏好的其它字符作为正则表达式的分隔符。当你用地址与带文件路径的字符一起来匹配的时,是非常有用的:

# 以下两个命令是完全相同的
sed -ne '/\/usr\/sbin\/nologin/p' inputfile
sed -ne '\=/usr/sbin/nologin=p' inputfile

扩展的正则表达式

默认情况下,Sed 的正则表达式引擎仅理解 POSIX 基本正则表达式 的语法。如果你需要用到 扩展正则表达式,你必须在 Sed 命令上添加 -E 标志。扩展正则表达式在基本正则表达式基础上增加了一组额外的特性,并且很多都是很重要的,它们所要求的反斜杠要少很多。我们来比较一下:

sed -n -e '/\(www\)\|\(mail\)/p' inputfile
sed -En -e '/(www)|(mail)/p' inputfile

花括号量词

正则表达式之所以强大的一个原因是范围量词 {,}。事实上,当你写一个不太精确匹配的正则表达式时,量词 * 就是一个非常完美的符号。但是,(用花括号量词)你可以显式在它边上添加一个下限和上限,这样就有了很好的灵活性。当量词范围的下限省略时,下限被假定为 0。当上限被省略时,上限被假定为无限大:

括号速记词解释
{,}*前面的规则出现 0、1、或许多遍
{,1}?前面的规则出现 0 或 1 遍
{1,}+前面的规则出现 1 或许多遍
{n,n}{n}前面的规则精确地出现 n 遍

花括号在基本正则表达式中也是可以使用的,但是它要求使用反斜杠。根据 POSIX 规范,在基本正则表达式中可以使用的量词仅有星号(*)和花括号(使用反斜杠,如 \{m,n\})。许多正则表达式引擎都扩展支持 \?\+。但是,为什么魔鬼如此有诱惑力呢?因为,如果你需要这些量词,使用扩展正则表达式将不但易于写而且可移植性更好。

为什么我要花点时间去讨论关于正则表达式的花括号量词,这是因为在 Sed 脚本中经常用这个特性去计数字符。

sed -En -e '/^.{35}$/p' inputfile # 输出精确包含 35 个字符的行
sed -En -e '/^.{0,35}$/p' inputfile # 输出包含 35 个字符或更少字符的行
sed -En -e '/^.{,35}$/p' inputfile # 输出包含 35 个字符或更少字符的行
sed -En -e '/^.{35,}$/p' inputfile # 输出包含 35 个字符或更多字符的行
sed -En -e '/.{35}/p' inputfile # 你自己指出它的输出内容(这是留给你的测试题)

地址范围

到目前为止,我们使用的所有地址都是唯一地址。在我们使用一个唯一地址时,命令是应用在与那个地址匹配的行上。但是,Sed 也支持地址范围。Sed 命令可以应用到那个地址范围中从开始到结束的所有地址中的所有行上:

sed -n -e '1,5p' inputfile # 仅输出 1 到 5 行
sed -n -e '5,$p' inputfile # 从第 5 行输出到文件结尾
sed -n -e '/www/,/systemd/p' inputfile # 输出与正则表达式 /www/ 匹配的第一行到与接下来匹配正则表达式 /systemd/ 的行为止

(LCTT 译注:下面用的一个生成的列表例子,如下供参考:)

printf "%s\n" {a,b,c}{d,e,f} | cat -n
     1  ad
     2  ae
     3  af
     4  bd
     5  be
     6  bf
     7  cd
     8  ce
     9  cf

如果在开始和结束地址上使用了同一个行号,那么范围就缩小为那个行。事实上,如果第二个地址的数字小于或等于地址范围中选定的第一个行的数字,那么仅有一个行被选定:

printf "%s\n" {a,b,c}{d,e,f} | cat -n | sed -ne '4,4p'
     4 bd
printf "%s\n" {a,b,c}{d,e,f} | cat -n | sed -ne '4,3p'
     4 bd

下面有点难了,但是在前面的段落中给出的规则也适用于起始地址是正则表达式的情况。在那种情况下,Sed 将对正则表达式匹配的第一个行的行号和给定的作为结束地址的显式的行号进行比较。再强调一次,如果结束行号小于或等于起始行号,那么这个范围将缩小为一行:

(LCTT 译注:此处作者陈述有误,Sed 会在处理以正则表达式表示的开始行时,并不会同时测试结束表达式:从匹配开始行的正则表达式开始,直到不匹配时,才会测试结束行的表达式——无论是否是正则表达式——并在结束的表达式测试不通过时停止,并循环此测试。)

# 这个 /b/,4 地址将匹配三个单行
# 因为每个匹配的行有一个行号 >= 4
#(LCTT 译注:结果正确,但是说明不正确。4、5、6 行都会因为匹配开始正则表达式而通过,第 7 行因为不匹配开始正则表达式,所以开始比较行数: 7 > 4,遂停止。)
printf "%s\n" {a,b,c}{d,e,f} | cat -n | sed -ne '/b/,4p'
     4  bd
     5  be
     6  bf

# 你自己指出匹配的范围是多少
# 第二个例子:
printf "%s\n" {a,b,c}{d,e,f} | cat -n | sed -ne '/d/,4p'
     1  ad
     2  ae
     3  af
     4  bd
     7  cd

但是,当结束地址是一个正则表达式时,Sed 的行为将不一样。在那种情况下,地址范围的第一行将不会与结束地址进行检查,因此地址范围将至少包含两行(当然,如果输入数据不足的情况除外):

(LCTT 译注:如上译注,当满足开始的正则表达式时,并不会测试结束的表达式;仅当不满足开始的表达式时,才会测试结束表达式。)

printf "%s\n" {a,b,c}{d,e,f} | cat -n | sed -ne '/b/,/d/p'
 4 bd
 5 be
 6 bf
 7 cd

printf "%s\n" {a,b,c}{d,e,f} | cat -n | sed -ne '4,/d/p'
 4 bd
 5 be
 6 bf
 7 cd

(LCTT 译注:对地址范围的总结,当满足开始的条件时,从该行开始,并不测试该行是否满足结束的条件;从下一行开始测试结束条件,并在结束条件不满足时结束;然后对剩余的行,再从开始条件开始匹配,以此循环——也就是说,匹配结果可以是非连续的单/多行。大家可以调整上述命令行的条件以理解。)

补集

在一个地址选择行后面添加一个感叹号(!)表示不匹配那个地址。例如:

sed -n -e '5!p' inputfile # 输出除了第 5 行外的所有行
sed -n -e '5,10!p' inputfile # 输出除了第 5 到 10 之间的所有行
sed -n -e '/sys/!p' inputfile # 输出除了包含字符串“sys”的所有行

交集

(LCTT 译注:原文标题为“合集”,应为“交集”)

Sed 允许在一个块中使用花括号 {…} 组合命令。你可以利用这个特性去组合几个地址的交集。例如,我们来比较下面两个命令的输出:

sed -n -e '/usb/{
  /daemon/p
}' inputfile

sed -n -e '/usb.*daemon/p' inputfile

通过在一个块中嵌套命令,我们将在任意顺序中选择包含字符串 “usb” 和 “daemon” 的行。而正则表达式 “usb.*daemon” 将仅匹配在字符串 “daemon” 前面包含 “usb” 字符串的行。

离题太长时间后,我们现在重新回去学习各种 Sed 命令。

退出命令

退出命令(q)是指在当前的迭代循环处理结束之后停止 Sed。

The Sed quit command

q 命令是在到达输入文件的尾部之前停止处理输入的方法。为什么会有人想去那样做呢?

很好的问题,如果你还记得,我们可以使用下面的命令来输出文件中第 1 到第 5 的行:

sed -n -e '1,5p' inputfile

对于大多数 Sed 的实现方式,工具将循环读取输入文件的所有行,那怕是你只处理结果中的前 5 行。如果你的输入文件包含了几百万行(或者更糟糕的情况是,你从一个无限的数据流,比如像 /dev/urandom 中读取)将有重大影响。

使用退出命令,相同的程序可以被修改的更高效:

sed -e '5q' inputfile

由于我在这里并不使用 -n 选项,Sed 将在每个循环结束后隐式输出模式空间的内容。但是在你处理完第 5 行后,它将退出,并且因此不会去读取更多的数据。

我们能够使用一个类似的技巧只输出文件中一个特定的行。这也是从命令行中提供多个 Sed 表达式的几种方法。下面的三个变体都可以从 Sed 中接受几个命令,要么是不同的 -e 选项,要么是在相同的表达式中新起一行,或用分号(;)隔开:

sed -n -e '5p' -e '5q' inputfile

sed -n -e '
  5p
  5q
' inputfile

sed -n -e '5p;5q' inputfile

如果你还记得,我们在前面看到过能够使用花括号将命令组合起来,在这里我们使用它来防止相同的地址重复两次:

# 组合命令
sed -e '5{
  p
  q
}' inputfile

# 可以简写为:
sed '5{p;q;}' inputfile

# 作为 POSIX 扩展,有些实现方式可以省略闭花括号之前的分号:
sed '5{p;q}' inputfile

替换命令

你可以将替换命令(s)想像为 Sed 的“查找替换”功能,这个功能在大多数的“所见即所得”的编辑器上都能找到。Sed 的替换命令与之类似,但比它们更强大。替换命令是 Sed 中最著名的命令之一,在网上有大量的关于这个命令的文档。

The Sed <code>substitution</code> command

在前一篇文章中我们已经讲过它了,因此,在这里就不再重复了。但是,如果你对它的使用不是很熟悉,那么你需要记住下面的这些关键点:

  • 替换命令有两个参数:查找模式和替换字符串:sed s/:/-----/ inputfile
  • s 命令和它的参数是用任意一个字符来分隔的。这主要看你的习惯,在 99% 的时间中我都使用斜杠,但也会用其它的字符:sed s%:%-----% inputfilesed sX:X-----X inputfile 或者甚至是 sed 's : ----- ' inputfile
  • 默认情况下,替换命令仅被应用到模式空间中匹配到的第一个字符串上。你可以通过在命令之后指定一个匹配指数作为标志来改变这种情况:sed 's/:/-----/1' inputfilesed 's/:/-----/2' inputfilesed 's/:/-----/3' inputfile、…
  • 如果你想执行一个全局替换(即:在模式空间上的每个非重叠匹配上进行),你需要增加 g 标志:sed 's/:/-----/g' inputfile
  • 在字符串替换中,出现的任何一个 & 符号都将被与查找模式匹配的子字符串替换:sed 's/:/-&&&-/g' inputfilesed 's/.../& /g' inputfile
  • 圆括号(在扩展的正则表达式中的 (...) ,或者基本的正则表达式中的 \(...\))被当做 捕获组 capturing group 。那是匹配字符串的一部分,可以在替换字符串中被引用。\1 是第一个捕获组的内容,\2 是第二个捕获组的内容,依次类推:sed -E 's/(.)(.)/\2\1/g' inputfilesed -E 's/(.):x:(.):(.*)/\1:\3/' inputfile(后者之所能正常工作是因为 正则表达式中的量词星号表示尽可能多的匹配,直到不匹配为止,并且它可以匹配许多个字符)
  • 在查找模式或替换字符串时,你可以通过使用一个反斜杠来去除任何字符的特殊意义:sed 's/:/--\&--/g' inputfilesed 's/\//\\/g' inputfile

所有的这些看起来有点抽象,下面是一些示例。首先,我想去显示我的测试输入文件的第一个字段并给它在右侧附加 20 个空格字符,我可以这样写:

sed < inputfile -E -e '
 s/:/ /             # 用 20 个空格替换第一个字段的分隔符
 s/(.{20}).*/\1/    # 只保留一行的前 20 个字符
 s/.*/| & |/        # 为了输出好看添加竖条
'

第二个示例是,如果我想将用户 sonia 的 UID/GID 修改为 1100,我可以这样写:

sed -En -e '
  /sonia/{
    s/[0-9]+/1100/g
    p
 }' inputfile

注意在替换命令结束部分的 g 选项。这个选项改变了它的行为,因此它将查找全部的模式空间并替换,如果没有那个选项,它只替换查找到的第一个。

顺便说一下,这也是使用前面讲过的输出(p)命令的好机会,可以在命令运行时输出修改前后的模式空间的内容。因此,为了获得替换前后的内容,我可以这样写:

sed -En -e '
  /sonia/{
     p
     s/[0-9]+/1100/g
     p
 }' inputfile

事实上,替换后输出一个行是很常见的用法,因此,替换命令也接受 p 选项:

sed -En -e '/sonia/s/[0-9]+/1100/gp' inputfile

最后,我就不详细讲替换命令的 w 选项了,我们将在稍后的学习中详细介绍。

删除命令

删除命令(d)用于清除模式空间的内容,然后立即开始下一个处理循环。这样它将会跳过隐式输出模式空间内容的行为,即便是你设置了自动输出标志(AP)也不会输出。

The Sed <code>delete</code> command

只输出一个文件前五行的一个很低效率的方法将是:

sed -e '6,$d' inputfile

你猜猜看,我为什么说它很低效率?如果你猜不到,建议你再次去阅读前面的关于退出命令的章节,答案就在那里!

当你组合使用正则表达式和地址,从输出中删除匹配的行时,删除命令将非常有用:

sed -e '/systemd/d' inputfile

次行命令

如果 Sed 命令没有运行在静默模式中,这个命令(n)将输出当前模式空间的内容,然后,在任何情况下它将读取下一个输入行到模式空间中,并使用新的模式空间中的内容来运行当前循环中剩余的命令。

The Sed next command

用次行命令去跳过行的一个常见示例:

cat -n inputfile | sed -n -e 'n;n;p'

在上面的例子中,Sed 将隐式地读取输入文件的第一行。但是次行命令将丢弃对模式空间中的内容的输出(不输出是因为使用了 -n 选项),并从输入文件中读取下一行来替换模式空间中的内容。而第二个次行命令做的事情和前一个是一模一样的,这就实现了跳过输入文件 2 行的目的。最后,这个脚本显式地输出包含在模式空间中的输入文件的第三行的内容。然后,Sed 将启动一个新的循环,由于次行命令,它会隐式地读取第 4 行的内容,然后跳过它,同样地也跳过第 5 行,并输出第 6 行。如此循环,直到文件结束。总体来看,这个脚本就是读取输入文件然后每三行输出一行。

使用次行命令,我们也可以找到一些显示输入文件的前五行的几种方法:

cat -n inputfile | sed -n -e '1{p;n;p;n;p;n;p;n;p}'
cat -n inputfile | sed -n -e 'p;n;p;n;p;n;p;n;p;q'
cat -n inputfile | sed -e 'n;n;n;n;q'

更有趣的是,如果你需要根据一些地址来处理行时,次行命令也非常有用:

cat -n inputfile | sed -n '/pulse/p' # 输出包含 “pulse” 的行
cat -n inputfile | sed -n '/pulse/{n;p}' # 输出包含 “pulse” 之后的行
cat -n inputfile | sed -n '/pulse/{n;n;p}'  # 输出包含 “pulse” 的行的下一行的下一行

使用保持空间

到目前为止,我们所看到的命令都是仅使用了模式空间。但是,我们在文章的开始部分已经提到过,还有第二个缓冲区:保持空间,它完全由用户管理。它就是我们在第二节中描述的目标。

交换命令

正如它的名字所表示的,交换命令(x)将交换保持空间和模式空间的内容。记住,你只要没有把任何东西放入到保持空间中,那么保持空间就是空的。

The Sed exchange command

作为第一个示例,我们可使用交换命令去反序输出一个输入文件的前两行:

cat -n inputfile | sed -n -e 'x;n;p;x;p;q'

当然,在你设置保持空间之后你并没有立即使用它的内容,因为只要你没有显式地去修改它,保持空间中的内容就保持不变。在下面的例子中,我在输入一个文件的前五行后,使用它去删除第一行:

cat -n inputfile | sed -n -e '
 1{x;n} # 交换保持和模式空间
        # 保存第 1 行到保持空间中
        # 然后读取第 2 行
 5{
   p    # 输出第 5 行
   x    # 交换保持和模式空间
        # 去取得第 1 行的内容放回到模式空间
 }

 1,5p   # 输出第 2 到第 5 行
        # (并没有输错!尝试找出这个规则
        # 没有在第 1 行上运行的原因 ;)
'

保持命令

保持命令(h)是用于将模式空间中的内容保存到保持空间中。但是,与交换命令不同的是,模式空间中的内容不会被改变。保持命令有两种用法:

  • h 将复制模式空间中的内容到保持空间中,覆盖保持空间中任何已经存在的内容。
  • H 将模式空间中的内容追加到保持空间中,使用一个新行作为分隔符。

The Sed hold command

上面使用交换命令的例子可以使用保持命令重写如下:

cat -n inputfile | sed -n -e '
 1{h;n} # 保存第 1 行的内容到保持缓冲区并继续
 5{     # 在第 5 行
   x    # 交换模式和保持空间
        # (现在模式空间包含了第 1 行)
   H    # 在保持空间的第 5 行后追加第 1 行
   x    # 再次交换第 5 行和第 1 行,第 5 行回到模式空间
 }

 1,5p   # 输出第 2 行到第 5 行
        # (没有输错!尝试去找到为什么这个规则
        # 不在第 1 行上运行 ;)
'

获取命令

获取命令(g)与保持命令恰好相反:它从保持空间中取得内容并将它置入到模式空间中。同样它也有两种方式:

  • g 将复制保持空间中的内容并将其放入到模式空间,覆盖模式空间中已存在的任何内容
  • G 将保持空间中的内容追加到模式空间中,并使用一个新行作为分隔符

The Sed get command

将保持命令和获取命令一起使用,可以允许你去存储并调回数据。作为一个小挑战,我让你重写前一节中的示例,将输入文件的第 1 行放置在第 5 行之后,但是这次必须使用获取和保持命令(使用大写或小写命令的版本)而不能使用交换命令。带点小运气,可以更简单!

同时,我可以给你展示另一个示例,它能给你一些灵感。目标是将拥有登录 shell 权限的用户与其它用户分开:

cat -n inputfile | sed -En -e '
 \=(/usr/sbin/nologin|/bin/false)$= { H;d; }
            # 追回匹配的行到保持空间
            # 然后继续下一个循环
 p          # 输出其它行
 $ { g;p }  # 在最后一行上
            # 获取并打印保持空间中的内容
'

复习打印、删除和次行命令

现在你已经更熟悉使用保持空间了,我们回到打印、删除和次行命令。我们已经讨论了小写的 pdn 命令了。而它们也有大写的版本。因为每个命令都有大小写版本,似乎是 Sed 的习惯,这些命令的大写版本将与多行缓冲区有关:

  • P 将模式空间中第一个新行之前的内容输出
  • D 删除模式空间中第一个新行之前的内容(包含新行),然后不读取任何新的输入而是使用剩余的文本去重启一个循环
  • N 读取输入并追加一个新行到模式空间,用一个新行作为新旧数据的分隔符。继续运行当前的循环。

The Sed uppercase <code>Delete</code> command

The Sed uppercase <code>Next</code> command

这些命令的使用场景主要用于实现队列(FIFO 列表))。从一个输入文件中删除最后 5 行就是一个很权威的例子:

cat -n inputfile | sed -En -e '
  1 { N;N;N;N } # 确保模式空间中包含 5 行

  N             # 追加第 6 行到队列中
  P             # 输出队列的第 1 行
  D             # 删除队列的第 1 行
'

作为第二个示例,我们可以在两个列上显示输入数据:

# 输出两列
sed < inputfile -En -e '
 $!N    # 追加一个新行到模式空间
        # 除了输入文件的最后一行
        # 当在输入文件的最后一行使用 N 命令时
        # GNU Sed 和 POSIX Sed 的行为是有差异的
        # 需要使用一个技巧去处理这种情况
        # https://www.gnu.org/software/sed/manual/sed.html#N_005fcommand_005flast_005fline

        # 用空间填充第 1 行的第 1 个字段
        # 并丢弃其余行
 s/:.*\n/                    \n/
 s/:.*//            # 除了第 2 行上的第 1 个字段外,丢弃其余的行
 s/(.{20}).*\n/\1/  # 修剪并连接行
 p                  # 输出结果
'

分支

我们刚才已经看到,Sed 因为有保持空间所以有了缓存的功能。其实它还有测试和分支的指令。因为有这些特性使得 Sed 是一个图灵完备的语言。虽然它可能看起来很傻,但意味着你可以使用 Sed 写任何程序。你可以实现任何你的目的,但并不意味着实现起来会很容易,而且结果也不一定会很高效。

不过不用担心。在本文中,我们将使用能够展示测试和分支功能的最简单的例子。虽然这些功能乍一看似乎很有限,但请记住,有些人用 Sed 写了 http://www.catonmat.net/ftp/sed/dc.sed[计算器]、http://www.catonmat.net/ftp/sed/sedtris.sed [俄罗斯方块] 或许多其它类型的应用程序!

标签和分支

从某些方面,你可以将 Sed 看到是一个功能有限的汇编语言。因此,你不会找到在高级语言中常见的 “for” 或 “while” 循环,或者 “if … else” 语句,但是你可以使用分支来实现同样的功能。

The Sed branch command

如果你在本文开始部分看到了用流程图描述的 Sed 运行模型,那么你应该知道 Sed 会自动增加程序计数器(PC)的值,命令是按程序的指令顺序来运行的。但是,使用分支(b)指令,你可以通过选择执行程序中的任意命令来改变顺序运行的程序。跳转目的地是使用一个标签(:)来显式定义的。

The Sed label command

这是一个这样的示例:

echo hello | sed -ne '
  :start    # 在程序的该行上放置一个 “start” 标签
  p         # 输出模式空间内容
  b start   # 继续在 :start 标签上运行
' | less

那个 Sed 程序的行为非常类似于 yes 命令:它获取一个字符串并产生一个包含那个字符串的无限流。

切换到一个标签就像我们绕开了 Sed 的自动化特性一样:它既不读取任何输入,也不输出任何内容,更不更新任何缓冲区。它只是跳转到源程序指令顺序中下一条的另外一个指令。

值得一提的是,如果在分支命令(b)上没有指定一个标签作为它的参数,那么分支将直接切换到程序结束的地方。因此,Sed 将启动一个新的循环。这个特性可以用于去跳过一些指令并且因此可以用于作为“块”的替代者:

cat -n inputfile | sed -ne '
/usb/!b
/daemon/!b
p
'

条件分支

到目前为止,我们已经看到了无条件分支,这个术语可能有点误导嫌疑,因为 Sed 命令总是基于它们的可选地址来作为条件的。

但是,在传统意义上,一个无条件分支也是一个分支,当它运行时,将跳转到特定的目的地,而条件分支既有可能也或许不可能跳转到特定的指令,这取决于系统的当前状态。

Sed 只有一个条件指令,就是测试(t)命令。只有在当前循环的开始或因为前一个条件分支运行了替换,它才跳转到不同的指令。更多的情况是,只有替换标志被设置时,测试命令才会切换分支。

The Sed <code>test</code> command

使用测试指令,你可以在一个 Sed 程序中很轻松地执行一个循环。作为一个特定的示例,你可以用它将一个行填充到某个长度(这是使用正则表达式无法实现的):

# 居中文本
cut -d: -f1 inputfile | sed -Ee '
  :start
  s/^(.{,19})$/ \1 /    # 用一个空格填充少于 20 个字符的行的开始处
                        # 并在结束处添加另一个空格
  t start               # 如果我们已经添加了一个空格,则返回到 :start 标签
  s/(.{20}).*/| \1 |/   # 只保留一个行的前 20 个字符
                        # 以修复由于奇数行引起的差一错误
'

如果你仔细读前面的示例,你可能注意到,在将要把数据“喂”给 Sed 之前,我通过 cut 命令做了一点小修正去预处理数据。

不过,我们也可以只使用 Sed 对程序做一些小修改来执行相同的任务:

cat inputfile | sed -Ee '
  s/:.*//               # 除第 1 个字段外删除剩余字段
  t start
  :start
  s/^(.{,19})$/ \1 /    # 用一个空格填充少于 20 个字符的行的开始处
                        # 并在结束处添加另一个空格
  t start               # 如果我们已经添加了一个空格,则返回到 :start 标签
  s/(.{20}).*/| \1 |/   # 仅保留一个行的前 20 个字符
                        # 以修复由于奇数行引起的差一错误
'

在上面的示例中,你或许对下列的结构感到惊奇:

t start
:start

乍一看,在这里的分支并没有用,因为它只是跳转到将要运行的指令处。但是,如果你仔细阅读了测试命令的定义,你将会看到,如果在当前循环的开始或者前一个测试命令运行后发生了一个替换,分支才会起作用。换句话说就是,测试指令有清除替换标志的副作用。这也正是上面的代码片段的真实目的。这是一个在包含条件分支的 Sed 程序中经常看到的技巧,用于在使用多个替换命令时避免出现 误报 false positive 的情况。

通过它并不能绝对强制地清除替换标志,我同意这一说法。因为在将字符串填充到正确的长度时我使用的特定的替换命令是 幂等 idempotent 的。因此,一个多余的迭代并不会改变结果。不过,我们可以现在再次看一下第二个示例:

# 基于它们的登录程序来分类用户帐户
cat inputfile | sed -Ene '
  s/^/login=/
  /nologin/s/^/type=SERV /
  /false/s/^/type=SERV /
  t print
  s/^/type=USER /
  :print
  s/:.*//p
'

我希望在这里根据用户默认配置的登录程序,为用户帐户打上 “SERV” 或 “USER” 的标签。如果你运行它,预计你将看到 “SERV” 标签。然而,并没有在输出中跟踪到 “USER” 标签。为什么呢?因为 t print 指令不论行的内容是什么,它总是切换,替换标志总是由程序的第一个替换命令来设置。一旦替换标志设置完成后,在下一个行被读取或直到下一个测试命令之前,这个标志将保持不变。下面我们给出修复这个程序的解决方案:

# 基于用户登录程序来分类用户帐户
cat inputfile | sed -Ene '
  s/^/login=/

  t classify # clear the "substitution flag"
  :classify

  /nologin/s/^/type=SERV /
  /false/s/^/type=SERV /
  t print
  s/^/type=USER /
  :print
  s/:.*//p
'

精确地处理文本

Sed 是一个非交互式文本编辑器。虽然是非交互式的,但仍然是文本编辑器。而如果没有在输出中插入一些东西的功能,那它就不算一个完整的文本编辑器。我不是很喜欢它的文本编辑的特性,因为我发现它的语法太难用了(即便是以 Sed 的标准而言),但有时你难免会用到它。

采用严格的 POSIX 语法的只有三个命令:改变(c)、插入(i)或追加(a)一些文字文本到输出,都遵循相同的特定语法:命令字母后面跟着一个反斜杠,并且文本从脚本的下一行上开始插入:

head -5 inputfile | sed '
1i\
# List of user accounts
$a\
# end
'

插入多行文本,你必须每一行结束的位置使用一个反斜杠:

head -5 inputfile | sed '
1i\
# List of user accounts\
# (users 1 through 5)
$a\
# end
'

一些 Sed 实现,比如 GNU Sed,在初始的反斜杠后面的换行符是可选的,即便是在 --posix 模式下仍然如此。我在标准中并没有找到任何关于该替代语法的说明(如果是因为我没有在标准中找到那个特性,请在评论区留言告诉我!)。因此,如果对可移植性要求很高,请注意使用它的风险:

# 非 POSIX 语法:
head -5 inputfile | sed -e '
1i\# List of user accounts
$a\# end
'

也有一些 Sed 的实现,让初始的反斜杠完全是可选的。因此毫无疑问,它是一个厂商对 POSIX 标准进行扩展的特定版本,它是否支持那个语法,你需要去查看那个 Sed 版本的手册。

在简单概述之后,我们现在来回顾一下这些命令的更多细节,从我还没有介绍的改变命令开始。

改变命令

改变命令(c\)就像 d 命令一样删除模式空间的内容并开始一个新的循环。唯一的不同在于,当命令运行之后,用户提供的文本是写往输出的。

The Sed change command

cat -n inputfile | sed -e '
/systemd/c\
# :REMOVED:
s/:.*// # This will NOT be applied to the "changed" text
'

如果改变命令与一个地址范围关联,当到达范围的最后一行时,这个文本将仅输出一次。这在某种程度上成为 Sed 命令将被重复应用在地址范围内所有行这一惯例的一个例外情况:

cat -n inputfile | sed -e '
19,22c\
# :REMOVED:
s/:.*// # This will NOT be applied to the "changed" text
'

因此,如果你希望将改变命令重复应用到地址范围内的所有行上,除了将它封装到一个块中之外,你将没有其它的选择:

cat -n inputfile | sed -e '
19,22{c\
# :REMOVED:
}
s/:.*// # This will NOT be applied to the "changed" text
'

插入命令

插入命令(i\)将立即在输出中给出用户提供的文本。它并不以任何方式修改程序流或缓冲区的内容。

The Sed insert command

# display the first five user names with a title on the first row
sed < inputfile -e '
1i\
USER NAME
s/:.*//
5q
'

追加命令

当输入的下一行被读取时,追加命令(a\)将一些文本追加到显示队列。文本在当前循环的结束部分(包含程序结束的情况)或当使用 nN 命令从输入中读取一个新行时被输出。

The Sed append command

与上面相同的一个示例,但这次是插入到底部而不是顶部:

sed < inputfile -e '
5a\
USER NAME
s/:.*//
5q
'

读取命令

这是插入一些文本内容到输出流的第四个命令:读取命令(r)。它的工作方式与追加命令完全一样,但不同的,它不从 Sed 脚本中取得硬编码到脚本中的文本,而是把一个文件的内容写入到一个输出上。

读取命令只调度要读取的文件。当清理追加队列时,后者才被高效地读取,而不是在读取命令运行时。如果这时候对这个文件有并发的访问读取,或那个文件不是一个普通的文件(比如,它是一个字符设备或命名管道),或文件在读取期间被修改,这时可能会产生严重的后果。

作为一个例证,如果你使用我们将在下一节详细讲述的写入命令,它与读取命令共同配合从一个临时文件中写入并重新读取,你可能会获得一些创造性的结果(使用法语版的 Shiritori 游戏作为一个例证):

printf "%s\n" "Trois p'tits chats" "Chapeau d' paille" "Paillasson" |
sed -ne '
  r temp
  a\
  ----
  w temp
'

现在,在流输出中专门用于插入一些文本的 Sed 命令清单结束了。我的最后一个示例纯属好玩,但是由于我前面提到过有一个写入命令,这个示例将我们完美地带到下一节,在下一节我们将看到在 Sed 中如何将数据写入到一个外部文件。

替代的输出

Sed 的设计思想是,所有的文本转换都将写入到进程的标准输出上。但是,Sed 也有一些特性支持将数据发送到替代的目的地。你有两种方式去实现上述的输出目标替换:使用专门的写入命令(w),或者在一个替换命令(s)上添加一个写入标志。

写入命令

写入命令(w)会追加模式空间的内容到给定的目标文件中。POSIX 要求在 Sed 处理任何数据之前,目标文件能够被 Sed 所创建。如果给定的目标文件已经存在,它将被覆写。

The Sed write command

因此,即便是你从未真的写入到该文件中,但该文件仍然会被创建。例如,下列的 Sed 程序将创建/覆写这个 output 文件,那怕是这个写入命令从未被运行过:

echo | sed -ne '
  q # 立刻退出
  w output # 这个命令从未被运行
'

你可以将几个写入命令指向到同一个目标文件。指向同一个目标文件的所有写入命令将追加那个文件的内容(工作方式几乎与 shell 的重定向符 >> 相同):

sed < inputfile -ne '
  /:\/bin\/false$/w server
  /:\/usr\/sbin\/nologin$/w server
  w output
'
cat server

替换命令的写入标志

在前面,我们已经学习了替换命令(s),它有一个 p 选项用于在替换之后输出模式空间的内容。同样它也提供一个类似功能的 w 选项,用于在替换之后将模式空间的内容输出到一个文件中:

sed < inputfile -ne '
  s/:.*\/nologin$//w server
  s/:.*\/false$//w server
'
cat server

注释

我无数次使用过它们,但我从未花时间正式介绍过它们,因此,我决定现在来正式地介绍它们:就像大多数编程语言一样,注释是添加软件不去解析的自由格式文本的一种方法。Sed 的语法很晦涩,我不得不强调在脚本中需要的地方添加足够的注释。否则,除了作者外其他人将几乎无法理解它。

The Sed comment command

不过,和 Sed 的其它部分一样,注释也有它自己的微妙之处。首先并且是最重要的,注释并不是语法结构,但它是真正意义的 Sed 命令。注释虽然是一个“什么也不做”的命令,但它仍然是一个命令。至少,它是在 POSIX 中定义了的。因此,严格地说,它们只允许使用在其它命令允许使用的地方。

大多数 Sed 实现都通过允许行内命令来放松了那种要求,就像在那个文章中我到处都使用的那样。

结束那个主题之前,需要说一下 #n 注释(# 后面紧跟一个字母 n,中间没有空格)的特殊情况。如果在脚本的第一行找到这个精确注释,Sed 将切换到静默模式(即:清除自动输出标志),就像在命令行上指定了 -n 选项一样。

很少用得到的命令

现在,我们已经学习的命令能让你写出你所用到的 99.99% 的脚本。但是,如果我没有提到剩余的 Sed 命令,那么本教程就不能称为完全指南。我把它们留到最后是因为我们很少用到它。但或许你有实际使用案例,那么你就会发现它们很有用。如果是那样,请不要犹豫,在下面的评论区中把它分享给我们吧。

行数命令

这个 = 命令将向标准输出上显示当前 Sed 正在读取的行数,这个行数就是行计数器(LC)的内容。没有任何方式从任何一个 Sed 缓冲区中捕获那个数字,也不能对它进行输出格式化。由于这两个限制使得这个命令的可用性大大降低。

The Sed line number command

请记住,在严格的 POSIX 兼容模式中,当在命令行上给定几个输入文件时,Sed 并不重置那个计数器,而是连续地增长它,就像所有的输入文件是连接在一起的一样。一些 Sed 实现,像 GNU Sed,它就有一个选项可以在每个输入文件读取结束后去重置计数器。

明确打印命令

这个 l(小写的字母 l)作用类似于打印命令(p),但它是以精确的格式去输出模式空间的内容。以下引用自 POSIX 标准

在 XBD 转义序列中列出的字符和相关的动作(\\\a\b\f\r\t\v)将被写为相应的转义序列;在那个表中的 \n 是不适用的。不在那个表中的不可打印字符将被写为一个三位八进制数字(在前面使用一个反斜杠 \),表示字符中的每个字节(最重要的字节在前面)。长行应该被换行,通过写一个反斜杠后跟一个换行符来表示换行位置;发生换行时的长度是不确定的,但应该适合输出设备的具体情况。每个行应该以一个 $ 标记结束。

The Sed unambiguous print command

我怀疑这个命令是在非 8 位规则化信道 上交换数据的。就我本人而言,除了调试用途以外,也从未使用过它。

移译命令

移译 transliterate y)命令允许从一个源集到一个目标集映射模式空间的字符。它非常类似于 tr 命令,但是限制更多。

The Sed transliterate command

# The `y` c0mm4nd 1s for h4x0rz only
sed < inputfile -e '
 s/:.*//
 y/abcegio/48<3610/
'

虽然移译命令语法与替换命令的语法有一些相似之处,但它在替换字符串之后不接受任何选项。这个移译总是全局的。

请注意,移译命令要求源集和目标集之间要一一对应地转换。这意味着下面的 Sed 程序可能所做的事情并不是你乍一看所想的那样:

# 注意:这可能并不如你想的那样工作!
sed < inputfile -e '
  s/:.*//
  y/[a-z]/[A-Z]/
'

写在最后的话

# 它要做什么?
# 提示:答案就在不远处...
sed -E '
  s/.*\W(.*)/\1/
  h
  ${ x; p; }
  d' < inputfile

我们已经学习了所有的 Sed 命令,真不敢相信我们已经做到了!如果你也读到这里了,应该恭喜你,尤其是如果你花费了一些时间,在你的系统上尝试了所有的不同示例!

正如你所见,Sed 是非常复杂的,不仅因为它的语法比较零乱,也因为许多极端案例或命令行为之间的细微差别。毫无疑问,我们可以将这些归结于历史的原因。尽管它有这么多缺点,但是 Sed 仍然是一个非常强大的工具,甚至到现在,它仍然是 Unix 工具箱中为数不多的大量使用的命令之一。是时候总结一下这篇文章了,没有你们的支持我将无法做到:请节选你对喜欢的或最具创意的 Sed 脚本,并共享给我们。如果我收集到的你们共享出的脚本足够多了,我将会把这些 Sed 脚本结集发布!


via: https://linuxhandbook.com/sed-reference-guide/

作者:Sylvain Leroux 选题:lujun9972 译者:qhwdw 校对:wxy

本文由 LCTT 原创编译,Linux中国 荣誉推出