2018年1月

本文的主角是容器,一种类似虚拟机但更轻量级的构造。你可以轻易地在你的 Ubuntu 桌面系统中创建一堆容器!

虚拟机会虚拟出整个电脑让你来安装客户机操作系统。相比之下,容器复用了主机的 Linux 内核,只是简单地 包容 了我们选择的根文件系统(也就是运行时环境)。Linux 内核有很多功能可以将运行的 Linux 容器与我们的主机分割开(也就是我们的 Ubuntu 桌面)。

Linux 本身需要一些手工操作来直接管理他们。好在,有 LXD(读音为 Lex-deeh),这是一款为我们管理 Linux 容器的服务。

我们将会看到如何:

  1. 在我们的 Ubuntu 桌面上配置容器,
  2. 创建容器,
  3. 安装一台 web 服务器,
  4. 测试一下这台 web 服务器,以及
  5. 清理所有的东西。

设置 Ubuntu 容器

如果你安装的是 Ubuntu 16.04,那么你什么都不用做。只要安装下面所列出的一些额外的包就行了。若你安装的是 Ubuntu 14.04.x 或 Ubuntu 15.10,那么按照 LXD 2.0 系列(二):安装与配置 来进行一些操作,然后再回来。

确保已经更新了包列表:

sudo apt update
sudo apt upgrade

安装 lxd 包:

sudo apt install lxd

若你安装的是 Ubuntu 16.04,那么还可以让你的容器文件以 ZFS 文件系统的格式进行存储。Ubuntu 16.04 的 Linux kernel 包含了支持 ZFS 必要的内核模块。若要让 LXD 使用 ZFS 进行存储,我们只需要安装 ZFS 工具包。没有 ZFS,容器会在主机文件系统中以单独的文件形式进行存储。通过 ZFS,我们就有了写入时拷贝等功能,可以让任务完成更快一些。

安装 zfsutils-linux 包(若你安装的是 Ubuntu 16.04.x):

sudo apt install zfsutils-linux

安装好 LXD 后,包安装脚本应该会将你加入 lxd 组。该组成员可以使你无需通过 sudo 就能直接使用 LXD 管理容器。根据 Linux 的习惯,你需要先登出桌面会话然后再登录 才能应用 lxd 的组成员关系。(若你是高手,也可以通过在当前 shell 中执行 newgrp lxd 命令,就不用重登录了)。

在开始使用前,LXD 需要初始化存储和网络参数。

运行下面命令:

$ sudo lxd init
Name of the storage backend to use (dir or zfs): zfs
Create a new ZFS pool (yes/no)? yes
Name of the new ZFS pool: lxd-pool
Would you like to use an existing block device (yes/no)? no
Size in GB of the new loop device (1GB minimum): 30
Would you like LXD to be available over the network (yes/no)? no
Do you want to configure the LXD bridge (yes/no)? yes 
> You will be asked about the network bridge configuration. Accept all defaults and continue.
Warning: Stopping lxd.service, but it can still be activated by:
 lxd.socket
 LXD has been successfully configured.
$ _

我们在一个(单独)的文件而不是块设备(即分区)中构建了一个文件系统来作为 ZFS 池,因此我们无需进行额外的分区操作。在本例中我指定了 30GB 大小,这个空间取之于根(/) 文件系统中。这个文件就是 /var/lib/lxd/zfs.img

行了!最初的配置完成了。若有问题,或者想了解其他信息,请阅读 https://www.stgraber.org/2016/03/15/lxd-2-0-installing-and-configuring-lxd-212/

创建第一个容器

所有 LXD 的管理操作都可以通过 lxc 命令来进行。我们通过给 lxc 不同参数来管理容器。

lxc list

可以列出所有已经安装的容器。很明显,这个列表现在是空的,但这表示我们的安装是没问题的。

lxc image list

列出可以用来启动容器的(已经缓存的)镜像列表。很明显这个列表也是空的,但这也说明我们的安装是没问题的。

lxc image list ubuntu:

列出可以下载并启动容器的远程镜像。而且指定了显示 Ubuntu 镜像。

lxc image list images:

列出可以用来启动容器的(已经缓存的)各种发行版的镜像列表。这会列出各种发行版的镜像比如 Alpine、Debian、Gentoo、Opensuse 以及 Fedora。

让我们启动一个 Ubuntu 16.04 容器,并称之为 c1

$ lxc launch ubuntu:x c1
Creating c1
Starting c1
$ 

我们使用 launch 动作,然后选择镜像 ubuntu:xx 表示 Xenial/16.04 镜像),最后我们使用名字 c1 作为容器的名称。

让我们来看看安装好的首个容器,

$ lxc list

+---------|---------|----------------------|------|------------|-----------+
| NAME | STATE | IPV4 | IPV6 | TYPE | SNAPSHOTS |
+---------|---------|----------------------|------|------------|-----------+
| c1 | RUNNING | 10.173.82.158 (eth0) | | PERSISTENT | 0 |
+---------|---------|----------------------|------|------------|-----------+

我们的首个容器 c1 已经运行起来了,它还有自己的 IP 地址(可以本地访问)。我们可以开始用它了!

安装 web 服务器

我们可以在容器中运行命令。运行命令的动作为 exec

$ lxc exec c1 -- uptime
 11:47:25 up 2 min,0 users,load average:0.07,0.05,0.04
$ _

exec 后面,我们指定容器、最后输入要在容器中运行的命令。该容器的运行时间只有 2 分钟,这是个新出炉的容器:-)。

命令行中的 -- 跟我们 shell 的参数处理过程有关。若我们的命令没有任何参数,则完全可以省略 -

$ lxc exec c1 -- df -h

这是一个必须要 - 的例子,由于我们的命令使用了参数 -h。若省略了 -,会报错。

然后我们运行容器中的 shell 来更新包列表。

$ lxc exec c1 bash
root@c1:~# apt update
Ign http://archive.ubuntu.com trusty InRelease
Get:1 http://archive.ubuntu.com trusty-updates InRelease [65.9 kB]
Get:2 http://security.ubuntu.com trusty-security InRelease [65.9 kB]
...
Hit http://archive.ubuntu.com trusty/universe Translation-en 
Fetched 11.2 MB in 9s (1228 kB/s) 
Reading package lists... Done
root@c1:~# apt upgrade
Reading package lists... Done
Building dependency tree 
...
Processing triggers for man-db (2.6.7.1-1ubuntu1) ...
Setting up dpkg (1.17.5ubuntu5.7) ...
root@c1:~# _

我们使用 nginx 来做 web 服务器。nginx 在某些方面要比 Apache web 服务器更酷一些。

root@c1:~# apt install nginx
Reading package lists... Done
Building dependency tree
...
Setting up nginx-core (1.4.6-1ubuntu3.5) ...
Setting up nginx (1.4.6-1ubuntu3.5) ...
Processing triggers for libc-bin (2.19-0ubuntu6.9) ...
root@c1:~# _

让我们用浏览器访问一下这个 web 服务器。记住 IP 地址为 10.173.82.158,因此你需要在浏览器中输入这个 IP。

lxd-nginx

让我们对页面文字做一些小改动。回到容器中,进入默认 HTML 页面的目录中。

root@c1:~# cd /var/www/html/
root@c1:/var/www/html# ls -l
total 2
-rw-r--r-- 1 root root 612 Jun 25 12:15 index.nginx-debian.html
root@c1:/var/www/html#

使用 nano 编辑文件,然后保存:

lxd-nginx-nano

之后,再刷一下页面看看,

lxd-nginx-modified

清理

让我们清理一下这个容器,也就是删掉它。当需要的时候我们可以很方便地创建一个新容器出来。

$ lxc list
+---------+---------+----------------------+------+------------+-----------+
| NAME | STATE   | IPV4                 | IPV6 | TYPE       | SNAPSHOTS    |
+---------+---------+----------------------+------+------------+-----------+
| c1   | RUNNING | 10.173.82.169 (eth0) |      | PERSISTENT | 0            |
+---------+---------+----------------------+------+------------+-----------+
$ lxc stop c1
$ lxc delete c1
$ lxc list
+---------+---------+----------------------+------+------------+-----------+
| NAME | STATE   | IPV4                 | IPV6 | TYPE       | SNAPSHOTS    |
+---------+---------+----------------------+------+------------+-----------+
+---------+---------+----------------------+------+------------+-----------+

我们停止(关闭)这个容器,然后删掉它了。

本文至此就结束了。关于容器有很多玩法。而这只是配置 Ubuntu 并尝试使用容器的第一步而已。


via: https://blog.simos.info/trying-out-lxd-containers-on-our-ubuntu/

作者:Simos Xenitellis 译者:lujun9972 校对:wxy

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

本文是关于 fork 和 exec 是如何在 Unix 上工作的。你或许已经知道,也有人还不知道。几年前当我了解到这些时,我惊叹不已。

我们要做的是启动一个进程。我们已经在博客上讨论了很多关于系统调用的问题,每当你启动一个进程或者打开一个文件,这都是一个系统调用。所以你可能会认为有这样的系统调用:

start_process(["ls", "-l", "my_cool_directory"])

这是一个合理的想法,显然这是它在 DOS 或 Windows 中的工作原理。我想说的是,这并不是 Linux 上的工作原理。但是,我查阅了文档,确实有一个 posix\_spawn 的系统调用基本上是这样做的,不过这不在本文的讨论范围内。

fork 和 exec

Linux 上的 posix_spawn 是通过两个系统调用实现的,分别是 forkexec(实际上是 execve),这些都是人们常常使用的。尽管在 OS X 上,人们使用 posix_spawn,而 forkexec 是不提倡的,但我们将讨论的是 Linux。

Linux 中的每个进程都存在于“进程树”中。你可以通过运行 pstree 命令查看进程树。树的根是 init,进程号是 1。每个进程(init 除外)都有一个父进程,一个进程都可以有很多子进程。

所以,假设我要启动一个名为 ls 的进程来列出一个目录。我是不是只要发起一个进程 ls 就好了呢?不是的。

我要做的是,创建一个子进程,这个子进程是我(me)本身的一个克隆,然后这个子进程的“脑子”被吃掉了,变成 ls

开始是这样的:

my parent
    |- me

然后运行 fork(),生成一个子进程,是我(me)自己的一份克隆:

my parent
    |- me
       |-- clone of me

然后我让该子进程运行 exec("ls"),变成这样:

my parent
    |- me
       |-- ls

当 ls 命令结束后,我几乎又变回了我自己:

my parent
    |- me
       |-- ls (zombie)

在这时 ls 其实是一个僵尸进程。这意味着它已经死了,但它还在等我,以防我需要检查它的返回值(使用 wait 系统调用)。一旦我获得了它的返回值,我将再次恢复独自一人的状态。

my parent
    |- me

fork 和 exec 的代码实现

如果你要编写一个 shell,这是你必须做的一个练习(这是一个非常有趣和有启发性的项目。Kamal 在 Github 上有一个很棒的研讨会:https://github.com/kamalmarhubi/shell-workshop)。

事实证明,有了 C 或 Python 的技能,你可以在几个小时内编写一个非常简单的 shell,像 bash 一样。(至少如果你旁边能有个人多少懂一点,如果没有的话用时会久一点。)我已经完成啦,真的很棒。

这就是 forkexec 在程序中的实现。我写了一段 C 的伪代码。请记住,fork 也可能会失败哦。

int pid = fork();
// 我要分身啦
// “我”是谁呢?可能是子进程也可能是父进程
if (pid == 0) {
    // 我现在是子进程
    // “ls” 吃掉了我脑子,然后变成一个完全不一样的进程
    exec(["ls"])
} else if (pid == -1) {
    // 天啊,fork 失败了,简直是灾难!
} else {
    // 我是父进程耶
    // 继续做一个酷酷的美男子吧
    // 需要的话,我可以等待子进程结束
}

上文提到的“脑子被吃掉”是什么意思呢?

进程有很多属性:

  • 打开的文件(包括打开的网络连接)
  • 环境变量
  • 信号处理程序(在程序上运行 Ctrl + C 时会发生什么?)
  • 内存(你的“地址空间”)
  • 寄存器
  • 可执行文件(/proc/$pid/exe
  • cgroups 和命名空间(与 Linux 容器相关)
  • 当前的工作目录
  • 运行程序的用户
  • 其他我还没想到的

当你运行 execve 并让另一个程序吃掉你的脑子的时候,实际上几乎所有东西都是相同的! 你们有相同的环境变量、信号处理程序和打开的文件等等。

唯一改变的是,内存、寄存器以及正在运行的程序,这可是件大事。

为何 fork 并非那么耗费资源(写入时复制)

你可能会问:“如果我有一个使用了 2GB 内存的进程,这是否意味着每次我启动一个子进程,所有 2 GB 的内存都要被复制一次?这听起来要耗费很多资源!”

事实上,Linux 为 fork() 调用实现了 写时复制 copy on write ,对于新进程的 2GB 内存来说,就像是“看看旧的进程就好了,是一样的!”。然后,当如果任一进程试图写入内存,此时系统才真正地复制一个内存的副本给该进程。如果两个进程的内存是相同的,就不需要复制了。

为什么你需要知道这么多

你可能会说,好吧,这些细节听起来很厉害,但为什么这么重要?关于信号处理程序或环境变量的细节会被继承吗?这对我的日常编程有什么实际影响呢?

有可能哦!比如说,在 Kamal 的博客上有一个很有意思的 bug。它讨论了 Python 如何使信号处理程序忽略了 SIGPIPE。也就是说,如果你从 Python 里运行一个程序,默认情况下它会忽略 SIGPIPE!这意味着,程序从 Python 脚本和从 shell 启动的表现会有所不同。在这种情况下,它会造成一个奇怪的问题。

所以,你的程序的环境(环境变量、信号处理程序等)可能很重要,都是从父进程继承来的。知道这些,在调试时是很有用的。


via: https://jvns.ca/blog/2016/10/04/exec-will-eat-your-brain/

作者:Julia Evans 译者:jessie-pang 校对:wxy

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

内存管理是操作系统的核心任务;它对程序员和系统管理员来说也是至关重要的。在接下来的几篇文章中,我将从实践出发着眼于内存管理,并深入到它的内部结构。虽然这些概念很通用,但示例大都来自于 32 位 x86 架构的 Linux 和 Windows 上。这第一篇文章描述了在内存中程序如何分布。

在一个多任务操作系统中的每个进程都运行在它自己的内存“沙箱”中。这个沙箱是一个 虚拟地址空间 virtual address space ,在 32 位的模式中它总共有 4GB 的内存地址块。这些虚拟地址是通过内核 页表 page table 映射到物理地址的,并且这些虚拟地址是由操作系统内核来维护,进而被进程所消费的。每个进程都有它自己的一组页表,但是这里有点玄机。一旦虚拟地址被启用,这些虚拟地址将被应用到这台电脑上的 所有软件包括内核本身。因此,一部分虚拟地址空间必须保留给内核使用:

Kernel/User Memory Split

但是,这并不是说内核就使用了很多的物理内存,恰恰相反,它只使用了很少一部分可用的地址空间映射到其所需要的物理内存。内核空间在内核页表中被标记为独占使用于 特权代码 (ring 2 或更低),因此,如果一个用户模式的程序尝试去访问它,将触发一个页面故障错误。在 Linux 中,内核空间是始终存在的,并且在所有进程中都映射相同的物理内存。内核代码和数据总是可寻址的,准备随时去处理中断或者系统调用。相比之下,用户模式中的地址空间,在每次进程切换时都会发生变化:

Process Switch Effects on Virtual Memory

蓝色的区域代表映射到物理地址的虚拟地址空间,白色的区域是尚未映射的部分。在上面的示例中,众所周知的内存“饕餮” Firefox 使用了大量的虚拟内存空间。在地址空间中不同的条带对应了不同的内存段,像 heap stack 等等。请注意,这些段只是一系列内存地址的简化表示,它与 Intel 类型的段 并没有任何关系 。不过,这是一个在 Linux 进程的标准段布局:

Flexible Process Address Space Layout In Linux

当计算机还是快乐、安全的时代时,在机器中的几乎每个进程上,那些段的起始虚拟地址都是完全相同的。这将使远程挖掘安全漏洞变得容易。漏洞利用经常需要去引用绝对内存位置:比如在栈中的一个地址,一个库函数的地址,等等。远程攻击可以闭着眼睛选择这个地址,因为地址空间都是相同的。当攻击者们这样做的时候,人们就会受到伤害。因此,地址空间随机化开始流行起来。Linux 会通过在其起始地址上增加偏移量来随机化内存映射段、以及。不幸的是,32 位的地址空间是非常拥挤的,为地址空间随机化留下的空间不多,因此 妨碍了地址空间随机化的效果

在进程地址空间中最高的段是栈,在大多数编程语言中它存储本地变量和函数参数。调用一个方法或者函数将推送一个新的 栈帧 stack frame 到这个栈。当函数返回时这个栈帧被删除。这个简单的设计,可能是因为数据严格遵循 后进先出(LIFO) 的次序,这意味着跟踪栈内容时不需要复杂的数据结构 —— 一个指向栈顶的简单指针就可以做到。推入和弹出也因此而非常快且准确。也可能是,持续的栈区重用往往会在 CPU 缓存 中保持活跃的栈内存,这样可以加快访问速度。进程中的每个线程都有它自己的栈。

向栈中推送更多的而不是刚合适的数据可能会耗尽栈的映射区域。这将触发一个页面故障,在 Linux 中它是通过 expand_stack() 来处理的,它会去调用 acct_stack_growth() 来检查栈的增长是否正常。如果栈的大小低于 RLIMIT_STACK 的值(一般是 8MB 大小),那么这是一个正常的栈增长和程序的合理使用,否则可能是发生了未知问题。这是一个栈大小按需调节的常见机制。但是,栈的大小达到了上述限制,将会发生一个栈溢出,并且,程序将会收到一个 段故障 Segmentation Fault 错误。当映射的栈区为满足需要而扩展后,在栈缩小时,映射区域并不会收缩。就像美国联邦政府的预算一样,它只会扩张。

动态栈增长是 唯一例外的情况 ,当它去访问一个未映射的内存区域,如上图中白色部分,是允许的。除此之外的任何其它访问未映射的内存区域将触发一个页面故障,导致段故障。一些映射区域是只读的,因此,尝试去写入到这些区域也将触发一个段故障。

在栈的下面,有内存映射段。在这里,内核将文件内容直接映射到内存。任何应用程序都可以通过 Linux 的 mmap() 系统调用( 代码实现)或者 Windows 的 CreateFileMapping().aspx) / MapViewOfFile().aspx) 来请求一个映射。内存映射是实现文件 I/O 的方便高效的方式。因此,它经常被用于加载动态库。有时候,也被用于去创建一个并不匹配任何文件的匿名内存映射,这种映射经常被用做程序数据的替代。在 Linux 中,如果你通过 malloc() 去请求一个大的内存块,C 库将会创建这样一个匿名映射而不是使用堆内存。这里所谓的“大”表示是超过了MMAP_THRESHOLD 设置的字节数,它的缺省值是 128 kB,可以通过 mallopt() 去调整这个设置值。

接下来讲的是“堆”,就在我们接下来的地址空间中,堆提供运行时内存分配,像栈一样,但又不同于栈的是,它分配的数据生存期要长于分配它的函数。大多数编程语言都为程序提供了堆管理支持。因此,满足内存需要是编程语言运行时和内核共同来做的事情。在 C 中,堆分配的接口是 malloc() 一族,然而在支持垃圾回收的编程语言中,像 C#,这个接口使用 new 关键字。

如果在堆中有足够的空间可以满足内存请求,它可以由编程语言运行时来处理内存分配请求,而无需内核参与。否则将通过 brk() 系统调用(代码实现)来扩大堆以满足内存请求所需的大小。堆管理是比较 复杂的,在面对我们程序的混乱分配模式时,它通过复杂的算法,努力在速度和内存使用效率之间取得一种平衡。服务一个堆请求所需要的时间可能是非常可观的。实时系统有一个 特定用途的分配器 去处理这个问题。堆也会出现 碎片化 ,如下图所示:

Fragmented Heap

最后,我们抵达了内存的低位段:BSS、数据、以及程序文本。在 C 中,静态(全局)变量的内容都保存在 BSS 和数据中。它们之间的不同之处在于,BSS 保存 未初始化的 静态变量的内容,它的值在源代码中并没有被程序员设置。BSS 内存区域是 匿名 的:它没有映射到任何文件上。如果你在程序中写这样的语句 static int cntActiveUserscntActiveUsers 的内容就保存在 BSS 中。

反过来,数据段,用于保存在源代码中静态变量 初始化后 的内容。这个内存区域是 非匿名 的。它映射了程序的二进值镜像上的一部分,包含了在源代码中给定初始化值的静态变量内容。因此,如果你在程序中写这样的语句 static int cntWorkerBees = 10,那么,cntWorkerBees 的内容就保存在数据段中,并且初始值为 10。尽管可以通过数据段映射到一个文件,但是这是一个私有内存映射,意味着,如果改变内存,它并不会将这种变化反映到底层的文件上。必须是这样的,否则,分配的全局变量将会改变你磁盘上的二进制文件镜像,这种做法就太不可思议了!

用图去展示一个数据段是很困难的,因为它使用一个指针。在那种情况下,指针 gonzo内容(一个 4 字节的内存地址)保存在数据段上。然而,它并没有指向一个真实的字符串。而这个字符串存在于文本段中,文本段是只读的,它用于保存你的代码中的类似于字符串常量这样的内容。文本段也会在内存中映射你的二进制文件,但是,如果你的程序写入到这个区域,将会触发一个段故障错误。尽管在 C 中,它比不上从一开始就避免这种指针错误那么有效,但是,这种机制也有助于避免指针错误。这里有一个展示这些段和示例变量的图:

ELF Binary Image Mapped Into Memory

你可以通过读取 /proc/pid_of_process/maps 文件来检查 Linux 进程中的内存区域。请记住,一个段可以包含很多的区域。例如,每个内存映射的文件一般都在 mmap 段中的它自己的区域中,而动态库有类似于 BSS 和数据一样的额外的区域。下一篇文章中我们将详细说明“ 区域 area ”的真正含义是什么。此外,有时候人们所说的“ 数据段 data segment ”是指“ 数据 data + BSS + 堆”。

你可以使用 nmobjdump 命令去检查二进制镜像,去显示它们的符号、地址、段等等。最终,在 Linux 中上面描述的虚拟地址布局是一个“弹性的”布局,这就是这几年来的缺省情况。它假设 RLIMIT_STACK 有一个值。如果没有值的话,Linux 将恢复到如下所示的“经典” 布局:

Classic Process Address Space Layout In Linux

这就是虚拟地址空间布局。接下来的文章将讨论内核如何对这些内存区域保持跟踪、内存映射、文件如何读取和写入、以及内存使用数据的意义。


via: http://duartes.org/gustavo/blog/post/anatomy-of-a-program-in-memory/

作者:Gustavo Duarte 译者:qhwdw 校对:wxy

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

诸如 grepack-grep 之类的命令行工具对于搜索匹配指定正则表达式的纯文本非常有用。但是你有没有试过使用这些工具在 PDF 中搜索?不要这么做!由于这些工具无法读取PDF文件,因此你不会得到任何结果。它们只能读取纯文本文件。

顾名思义,pdfgrep 是一个可以在不打开文件的情况下搜索 PDF 中的文本的小命令行程序。它非常快速 —— 比几乎所有 PDF 浏览器提供的搜索更快。greppdfgrep 的最大区别在于 pdfgrep 对页进行操作,而 grep 对行操作。grep 如果在一行上找到多个匹配项,它也会多次打印单行。让我们看看如何使用该工具。

安装

对于 Ubuntu 和其他基于 Ubuntu 的 Linux 发行版来说,这非常简单:

sudo apt install pdfgrep

对于其他发行版,只要在包管理器里输入 “pdfgrep” 查找,它就应该能够安装它。万一你想浏览其代码,你也可以查看项目的 GitLab 页面

测试运行

现在你已经安装了这个工具,让我们去测试一下。pdfgrep 命令采用以下格式:

pdfgrep [OPTION...] PATTERN [FILE...]
  • OPTION 是一个额外的属性列表,给出诸如 -i--ignore-case 这样的命令,这两者都会忽略匹配正则中的大小写。
  • PATTERN 是一个扩展正则表达式。
  • FILE 如果它在相同的工作目录就是文件的名称,或文件的路径。

我对 Python 3.6 官方文档运行该命令。下图是结果。

 title=

红色高亮显示所有遇到单词 “queue” 的地方。在命令中加入 -i 选项将会匹配单词 “Queue”。请记住,当加入 -i 时,大小写并不重要。

其它

pdfgrep 有相当多的有趣的选项。不过,我只会在这里介绍几个。

  • -c 或者 --count:这会抑制匹配的正常输出。它只显示在文件中遇到该单词的次数,而不是显示匹配的长输出。
  • -p 或者 --page-count:这个选项打印页面上匹配的页码和页面上的该匹配模式出现次数。
  • -m 或者 --max-count [number]:指定匹配的最大数目。这意味着当达到匹配次数时,该命令停止读取文件。

所支持的选项的完整列表可以在 man 页面或者 pdfgrep 在线文档中找到。如果你在批量处理一些文件,不要忘记,pdfgrep 可以同时搜索多个文件。可以通过更改 GREP_COLORS 环境变量来更改默认的匹配高亮颜色。

总结

下一次你想在 PDF 中搜索一些东西。请考虑使用 pdfgrep。该工具会派上用场,并且节省你的时间。


via: https://www.maketecheasier.com/search-pdf-files-pdfgrep/

作者:Bruno Edoh 译者:geekpi 校对:wxy

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

我们都对 history 命令很熟悉。它将终端上 bash 执行过的所有命令存储到 .bash_history 文件中,来帮助我们复查用户之前执行过的命令。

默认情况下 history 命令直接显示用户执行的命令而不会输出运行命令时的日期和时间,即使 history 命令记录了这个时间。

运行 history 命令时,它会检查一个叫做 HISTTIMEFORMAT 的环境变量,这个环境变量指明了如何格式化输出 history 命令中记录的这个时间。

若该值为 null 或者根本没有设置,则它跟大多数系统默认显示的一样,不会显示日期和时间。

HISTTIMEFORMAT 使用 strftime 来格式化显示时间(strftime - 将日期和时间转换为字符串)。history 命令输出日期和时间能够帮你更容易地追踪问题。

  • %T: 替换为时间(%H:%M:%S)。
  • %F: 等同于 %Y-%m-%d (ISO 8601:2000 标准日期格式)。

下面是 history 命令默认的输出。

# history
 1 yum install -y mysql-server mysql-client
 2 service mysqld start
 3 sysdig proc.name=sshd
 4 sysdig -c topprocs_net
 5 sysdig proc.name=sshd
 6 sysdig proc.name=sshd | more
 7 sysdig fd.name=/var/log/auth.log | more
 8 sysdig fd.name=/var/log/mysqld.log
 9 sysdig -cl
 10 sysdig -i httplog
 11 sysdig -i proc_exec_time
 12 sysdig -i topprocs_cpu
 13 sysdig -c topprocs_cpu
 14 sysdig -c tracers_2_statsd
 15 sysdig -c topfiles_bytes
 16 sysdig -c topprocs_cpu
 17 sysdig -c topprocs_cpu "fd.name contains sshd"
 18 sysdig -c topprocs_cpu "proc.name contains sshd"
 19 csysdig
 20 sysdig -c topprocs_cpu
 21 rpm --import https://s3.amazonaws.com/download.draios.com/DRAIOS-GPG-KEY.public
 22 curl -s -o /etc/yum.repos.d/draios.repo http://download.draios.com/stable/rpm/draios.repo
 23 yum install -y epel-release
 24 yum update
 25 yum makecache
 26 yum -y install kernel-devel-$(uname -r)
 27 yum -y install sysdig
 28 sysdig
 29 yum install httpd mysql
 30 service httpd start

根据需求,有三种不同的设置环境变量的方法。

  • 临时设置当前用户的环境变量
  • 永久设置当前/其他用户的环境变量
  • 永久设置所有用户的环境变量

注意: 不要忘了在最后那个单引号前加上空格,否则输出会很混乱的。

方法 1:

运行下面命令为为当前用户临时设置 HISTTIMEFORMAT 变量。这会一直生效到下次重启。

# export HISTTIMEFORMAT='%F %T '

方法 2:

HISTTIMEFORMAT 变量加到 .bashrc.bash_profile 文件中,让它永久生效。

# echo 'HISTTIMEFORMAT="%F %T "' >> ~/.bashrc
或
# echo 'HISTTIMEFORMAT="%F %T "' >> ~/.bash_profile

运行下面命令来让文件中的修改生效。

# source ~/.bashrc
或
# source ~/.bash_profile

方法 3:

HISTTIMEFORMAT 变量加入 /etc/profile 文件中,让它对所有用户永久生效。

# echo 'HISTTIMEFORMAT="%F %T "' >> /etc/profile

运行下面命令来让文件中的修改生效。

# source /etc/profile

输出结果为:

# history
 1 2017-08-16 15:30:15 yum install -y mysql-server mysql-client
 2 2017-08-16 15:30:15 service mysqld start
 3 2017-08-16 15:30:15 sysdig proc.name=sshd
 4 2017-08-16 15:30:15 sysdig -c topprocs_net
 5 2017-08-16 15:30:15 sysdig proc.name=sshd
 6 2017-08-16 15:30:15 sysdig proc.name=sshd | more
 7 2017-08-16 15:30:15 sysdig fd.name=/var/log/auth.log | more
 8 2017-08-16 15:30:15 sysdig fd.name=/var/log/mysqld.log
 9 2017-08-16 15:30:15 sysdig -cl
 10 2017-08-16 15:30:15 sysdig -i httplog
 11 2017-08-16 15:30:15 sysdig -i proc_exec_time
 12 2017-08-16 15:30:15 sysdig -i topprocs_cpu
 13 2017-08-16 15:30:15 sysdig -c topprocs_cpu
 14 2017-08-16 15:30:15 sysdig -c tracers_2_statsd
 15 2017-08-16 15:30:15 sysdig -c topfiles_bytes
 16 2017-08-16 15:30:15 sysdig -c topprocs_cpu
 17 2017-08-16 15:30:15 sysdig -c topprocs_cpu "fd.name contains sshd"
 18 2017-08-16 15:30:15 sysdig -c topprocs_cpu "proc.name contains sshd"
 19 2017-08-16 15:30:15 csysdig
 20 2017-08-16 15:30:15 sysdig -c topprocs_cpu
 21 2017-08-16 15:30:15 rpm --import https://s3.amazonaws.com/download.draios.com/DRAIOS-GPG-KEY.public
 22 2017-08-16 15:30:15 curl -s -o /etc/yum.repos.d/draios.repo http://download.draios.com/stable/rpm/draios.repo
 23 2017-08-16 15:30:15 yum install -y epel-release
 24 2017-08-16 15:30:15 yum update
 25 2017-08-16 15:30:15 yum makecache
 26 2017-08-16 15:30:15 yum -y install kernel-devel-$(uname -r)
 27 2017-08-16 15:30:15 yum -y install sysdig
 28 2017-08-16 15:30:15 sysdig
 29 2017-08-16 15:30:15 yum install httpd mysql
 30 2017-08-16 15:30:15 service httpd start

via: https://www.2daygeek.com/display-date-time-linux-bash-history-command/

作者:2daygeek 译者:lujun9972 校对:wxy

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

我的系统管理员给我提供了如下代理信息:

IP: 202.54.1.1
Port: 3128
Username: foo
Password: bar

该设置在 Google Chrome 和 Firefox 浏览器上很容易设置。但是我要怎么把它应用到 curl 命令上呢?我要如何让 curl 命令使用我在 Google Chrome 浏览器上的代理设置呢?

很多 Linux 和 Unix 命令行工具(比如 curl 命令,wget 命令,lynx 命令等)使用名为 http_proxyhttps_proxyftp_proxy 的环境变量来获取代理信息。它允许你通过代理服务器(使用或不使用用户名/密码都行)来连接那些基于文本的会话和应用。

本文就会演示一下如何让 curl 通过代理服务器发送 HTTP/HTTPS 请求。

让 curl 命令使用代理的语法

语法为:

## Set the proxy address of your uni/company/vpn network ## 
export http_proxy=http://your-ip-address:port/

## http_proxy with username and password 
export http_proxy=http://user:password@your-proxy-ip-address:port/

## HTTPS version ##
export https_proxy=https://your-ip-address:port/
export https_proxy=https://user:password@your-proxy-ip-address:port/

另一种方法是使用 curl 命令的 -x 选项:

curl -x <[protocol://][user:password@]proxyhost[:port]> url
--proxy <[protocol://][user:password@]proxyhost[:port]> url
--proxy http://user:password@Your-Ip-Here:Port url
-x http://user:password@Your-Ip-Here:Port url

在 Linux 上的一个例子

首先设置 http_proxy

## proxy server, 202.54.1.1, port: 3128, user: foo, password: bar ##
export http_proxy=http://foo:[email protected]:3128/
export https_proxy=$http_proxy
## Use the curl command ##
curl -I https://www.cyberciti.biz
curl -v -I https://www.cyberciti.biz

输出为:

* Rebuilt URL to: www.cyberciti.biz/
*   Trying 202.54.1.1...
* Connected to 1202.54.1.1 (202.54.1.1) port 3128 (#0)
* Proxy auth using Basic with user 'foo'
> HEAD HTTP://www.cyberciti.biz/ HTTP/1.1
> Host: www.cyberciti.biz
> Proxy-Authorization: Basic x9VuUml2xm0vdg93MtIz
> User-Agent: curl/7.43.0
> Accept: */*
> Proxy-Connection: Keep-Alive
> 
< HTTP/1.1 200 OK
HTTP/1.1 200 OK
< Server: nginx
Server: nginx
< Date: Sun, 17 Jan 2016 11:49:21 GMT
Date: Sun, 17 Jan 2016 11:49:21 GMT
< Content-Type: text/html; charset=UTF-8
Content-Type: text/html; charset=UTF-8
< Vary: Accept-Encoding
Vary: Accept-Encoding
< X-Whom: Dyno-l1-com-cyber
X-Whom: Dyno-l1-com-cyber
< Vary: Cookie
Vary: Cookie
< Link: <http://www.cyberciti.biz/wp-json/>; rel="https://api.w.org/"
Link: <http://www.cyberciti.biz/wp-json/>; rel="https://api.w.org/"
< X-Frame-Options: SAMEORIGIN
X-Frame-Options: SAMEORIGIN
< X-Content-Type-Options: nosniff
X-Content-Type-Options: nosniff
< X-XSS-Protection: 1; mode=block
X-XSS-Protection: 1; mode=block
< X-Cache: MISS from server1
X-Cache: MISS from server1
< X-Cache-Lookup: MISS from server1:3128
X-Cache-Lookup: MISS from server1:3128
< Connection: keep-alive
Connection: keep-alive

< 
* Connection #0 to host 10.12.249.194 left intact

本例中,我来下载一个 pdf 文件:

$ export http_proxy="vivek:[email protected]:3128/"
$ curl -v -O http://dl.cyberciti.biz/pdfdownloads/b8bf71be9da19d3feeee27a0a6960cb3/569b7f08/cms/631.pdf

也可以使用 -x 选项:

curl -x 'http://vivek:[email protected]:3128' -v -O https://dl.cyberciti.biz/pdfdownloads/b8bf71be9da19d3feeee27a0a6960cb3/569b7f08/cms/631.pdf

输出为:

Fig.01:curl in action (click to enlarge)

Unix 上的一个例子

$ curl -x http://prox_server_vpn:3128/ -I https://www.cyberciti.biz/faq/howto-nginx-customizing-404-403-error-page/

socks 协议怎么办呢?

语法也是一样的:

curl -x socks5://[user:password@]proxyhost[:port]/ url
curl --socks5 192.168.1.254:3099 https://www.cyberciti.biz/

如何让代理设置永久生效?

编辑 ~/.curlrc 文件:

$ vi ~/.curlrc

添加下面内容:

proxy = server1.cyberciti.biz:3128
proxy-user = "foo:bar"

保存并关闭该文件。另一种方法是在你的 ~/.bashrc 文件中创建一个别名:

## alias for curl command
## set proxy-server and port, the syntax is
## alias curl="curl -x {your_proxy_host}:{proxy_port}"
alias curl = "curl -x server1.cyberciti.biz:3128"

记住,代理字符串中可以使用 protocol:// 前缀来指定不同的代理协议。使用 socks4://socks4a://socks5://或者 socks5h:// 来指定使用的 SOCKS 版本。若没有指定协议或者使用 http:// 表示 HTTP 协议。若没有指定端口号则默认为 1080-x 选项的值要优先于环境变量设置的值。若不想走代理,而环境变量总设置了代理,那么可以通过设置代理为空值("")来覆盖环境变量的值。详细信息请参阅 curl 的 man 页


via: https://www.cyberciti.biz/faq/linux-unix-curl-command-with-proxy-username-password-http-options/

作者:Vivek Gite 译者:lujun9972 校对:wxy

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