分类 软件开发 下的文章

大家好!今天,我开始进行我的 ruby 堆栈跟踪项目,我发觉我现在了解了一些关于 gdb 内部如何工作的内容。

最近,我使用 gdb 来查看我的 Ruby 程序,所以,我们将对一个 Ruby 程序运行 gdb 。它实际上就是一个 Ruby 解释器。首先,我们需要打印出一个全局变量的地址:ruby_current_thread

获取全局变量

下面展示了如何获取全局变量 ruby_current_thread 的地址:

$ sudo gdb -p 2983
(gdb) p & ruby_current_thread
$2 = (rb_thread_t **) 0x5598a9a8f7f0 <ruby_current_thread>

变量能够位于的地方有 heap stack 或者程序的 文本段 text 。全局变量是程序的一部分。某种程度上,你可以把它们想象成是在编译的时候分配的。因此,我们可以很容易的找出全局变量的地址。让我们来看看,gdb 是如何找出 0x5598a9a87f0 这个地址的。

我们可以通过查看位于 /proc 目录下一个叫做 /proc/$pid/maps 的文件,来找到这个变量所位于的大致区域。

$ sudo cat /proc/2983/maps | grep bin/ruby
5598a9605000-5598a9886000 r-xp 00000000 00:32 323508                     /home/bork/.rbenv/versions/2.1.6/bin/ruby
5598a9a86000-5598a9a8b000 r--p 00281000 00:32 323508                     /home/bork/.rbenv/versions/2.1.6/bin/ruby
5598a9a8b000-5598a9a8d000 rw-p 00286000 00:32 323508                     /home/bork/.rbenv/versions/2.1.6/bin/ruby

所以,我们看到,起始地址 5598a96050000x5598a9a8f7f0 很像,但并不一样。哪里不一样呢,我们把两个数相减,看看结果是多少:

(gdb) p/x 0x5598a9a8f7f0 - 0x5598a9605000
$4 = 0x48a7f0

你可能会问,这个数是什么?让我们使用 nm 来查看一下程序的符号表。

sudo nm /proc/2983/exe | grep ruby_current_thread
000000000048a7f0 b ruby_current_thread

我们看到了什么?能够看到 0x48a7f0 吗?是的,没错。所以,如果我们想找到程序中一个全局变量的地址,那么只需在符号表中查找变量的名字,然后再加上在 /proc/whatever/maps 中的起始地址,就得到了。

所以现在,我们知道 gdb 做了什么。但是,gdb 实际做的事情更多,让我们跳过直接转到…

解引用指针

(gdb) p ruby_current_thread
$1 = (rb_thread_t *) 0x5598ab3235b0

我们要做的下一件事就是解引用 ruby_current_thread 这一指针。我们想看一下它所指向的地址。为了完成这件事,gdb 会运行大量系统调用比如:

ptrace(PTRACE_PEEKTEXT, 2983, 0x5598a9a8f7f0, [0x5598ab3235b0]) = 0

你是否还记得 0x5598a9a8f7f0 这个地址?gdb 会问:“嘿,在这个地址中的实际内容是什么?”。2983 是我们运行 gdb 这个进程的 ID。gdb 使用 ptrace 这一系统调用来完成这一件事。

好极了!因此,我们可以解引用内存并找出内存地址中存储的内容。有一些有用的 gdb 命令,比如 x/40w 变量x/40b 变量 分别会显示给定地址的 40 个字/字节。

描述结构

一个内存地址中的内容可能看起来像下面这样。可以看到很多字节!

(gdb) x/40b ruby_current_thread
0x5598ab3235b0: 16  -90 55  -85 -104    85  0   0
0x5598ab3235b8: 32  47  50  -85 -104    85  0   0
0x5598ab3235c0: 16  -64 -55 115 -97 127 0   0
0x5598ab3235c8: 0   0   2   0   0   0   0   0
0x5598ab3235d0: -96 -83 -39 115 -97 127 0   0

这很有用,但也不是非常有用!如果你是一个像我一样的人类并且想知道它代表什么,那么你需要更多内容,比如像这样:

(gdb) p *(ruby_current_thread)
$8 = {self = 94114195940880, vm = 0x5598ab322f20, stack = 0x7f9f73c9c010,
    stack_size = 131072, cfp = 0x7f9f73d9ada0, safe_level = 0,    raised_flag = 0,
    last_status = 8, state = 0, waiting_fd = -1, passed_block = 0x0,
    passed_bmethod_me = 0x0, passed_ci = 0x0,    top_self = 94114195612680,
    top_wrapper = 0, base_block = 0x0, root_lep = 0x0, root_svar = 8, thread_id =
    140322820187904,

太好了。现在就更加有用了。gdb 是如何知道这些所有域的,比如 stack_size ?是从 DWARF 得知的。DWARF 是存储额外程序调试数据的一种方式,从而像 gdb 这样的调试器能够工作的更好。它通常存储为二进制的一部分。如果我对我的 Ruby 二进制文件运行 dwarfdump 命令,那么我将会得到下面的输出:

(我已经重新编排使得它更容易理解)

DW_AT_name                  "rb_thread_struct"
DW_AT_byte_size             0x000003e8
DW_TAG_member
  DW_AT_name                  "self"
  DW_AT_type                  <0x00000579>
  DW_AT_data_member_location  DW_OP_plus_uconst 0
DW_TAG_member
  DW_AT_name                  "vm"
  DW_AT_type                  <0x0000270c>
  DW_AT_data_member_location  DW_OP_plus_uconst 8
DW_TAG_member
  DW_AT_name                  "stack"
  DW_AT_type                  <0x000006b3>
  DW_AT_data_member_location  DW_OP_plus_uconst 16
DW_TAG_member
  DW_AT_name                  "stack_size"
  DW_AT_type                  <0x00000031>
  DW_AT_data_member_location  DW_OP_plus_uconst 24
DW_TAG_member
  DW_AT_name                  "cfp"
  DW_AT_type                  <0x00002712>
  DW_AT_data_member_location  DW_OP_plus_uconst 32
DW_TAG_member
  DW_AT_name                  "safe_level"
  DW_AT_type                  <0x00000066>

所以,ruby_current_thread 的类型名为 rb_thread_struct,它的大小为 0x3e8 (即 1000 字节),它有许多成员项,stack_size 是其中之一,在偏移为 24 的地方,它有类型 3131 是什么?不用担心,我们也可以在 DWARF 信息中查看。

< 1><0x00000031>    DW_TAG_typedef
                      DW_AT_name                  "size_t"
                      DW_AT_type                  <0x0000003c>
< 1><0x0000003c>    DW_TAG_base_type
                      DW_AT_byte_size             0x00000008
                      DW_AT_encoding              DW_ATE_unsigned
                      DW_AT_name                  "long unsigned int"

所以,stack_size 具有类型 size_t,即 long unsigned int,它是 8 字节的。这意味着我们可以查看该栈的大小。

如果我们有了 DWARF 调试数据,该如何分解:

  1. 查看 ruby_current_thread 所指向的内存区域
  2. 加上 24 字节来得到 stack_size
  3. 读 8 字节(以小端的格式,因为是在 x86 上)
  4. 得到答案!

在上面这个例子中是 131072(即 128 kb)。

对我来说,这使得调试信息的用途更加明显。如果我们不知道这些所有变量所表示的额外的元数据,那么我们无法知道存储在 0x5598ab325b0 这一地址的字节是什么。

这就是为什么你可以为你的程序单独安装程序的调试信息,因为 gdb 并不关心从何处获取这些额外的调试信息。

DWARF 令人迷惑

我最近阅读了大量的 DWARF 知识。现在,我使用 libdwarf,使用体验不是很好,这个 API 令人迷惑,你将以一种奇怪的方式初始化所有东西,它真的很慢(需要花费 0.3 秒的时间来读取我的 Ruby 程序的所有调试信息,这真是可笑)。有人告诉我,来自 elfutils 的 libdw 要好一些。

同样,再提及一点,你可以查看 DW_AT_data_member_location 来查看结构成员的偏移。我在 Stack Overflow 上查找如何完成这件事,并且得到这个答案。基本上,以下面这样一个检查开始:

dwarf_whatform(attrs[i], &form, &error);
    if (form == DW_FORM_data1 || form == DW_FORM_data2
        form == DW_FORM_data2 || form == DW_FORM_data4
        form == DW_FORM_data8 || form == DW_FORM_udata) {

继续往前。为什么会有 800 万种不同的 DW_FORM_data 需要检查?发生了什么?我没有头绪。

不管怎么说,我的印象是,DWARF 是一个庞大而复杂的标准(可能是人们用来生成 DWARF 的库稍微不兼容),但是我们有的就是这些,所以我们只能用它来工作。

我能够编写代码并查看 DWARF ,这就很酷了,并且我的代码实际上大多数能够工作。除了程序崩溃的时候。我就是这样工作的。

展开栈路径

在这篇文章的早期版本中,我说过,gdb 使用 libunwind 来展开栈路径,这样说并不总是对的。

有一位对 gdb 有深入研究的人发了大量邮件告诉我,为了能够做得比 libunwind 更好,他们花费了大量时间来尝试如何展开栈路径。这意味着,如果你在程序的一个奇怪的中间位置停下来了,你所能够获取的调试信息又很少,那么你可以对栈做一些奇怪的事情,gdb 会尝试找出你位于何处。

gdb 能做的其他事

我在这儿所描述的一些事请(查看内存,理解 DWARF 所展示的结构)并不是 gdb 能够做的全部事情。阅读 Brendan Gregg 的昔日 gdb 例子,我们可以知道,gdb 也能够完成下面这些事情:

  • 反汇编
  • 查看寄存器内容

在操作程序方面,它可以:

  • 设置断点,单步运行程序
  • 修改内存(这是一个危险行为)

了解 gdb 如何工作使得当我使用它的时候更加自信。我过去经常感到迷惑,因为 gdb 有点像 C,当你输入 ruby_current_thread->cfp->iseq,就好像是在写 C 代码。但是你并不是在写 C 代码。我很容易遇到 gdb 的限制,不知道为什么。

知道使用 DWARF 来找出结构内容给了我一个更好的心智模型和更加正确的期望!这真是极好的!


via: https://jvns.ca/blog/2016/08/10/how-does-gdb-work/

作者:Julia Evans 译者:ucasFL 校对:wxy

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

用一些简单的脚本,可以很容易地清理文档和其它大量的 HTML 文件。但是首先你需要解析它们。

作为 Scribus 文档团队的长期成员,我要随时了解最新的源代码更新,以便对文档进行更新和补充。 我最近在刚升级到 Fedora 27 系统的计算机上使用 Subversion 进行检出操作时,对于下载该文档所需要的时间我感到很惊讶,文档由 HTML 页面和相关图像组成。 我恐怕该项目的文档看起来比项目本身大得多,并且怀疑其中的一些内容是“僵尸”文档——不再使用的 HTML 文件以及 HTML 中无法访问到的图像。

我决定为自己创建一个项目来解决这个问题。 一种方法是搜索未使用的现有图像文件。 如果我可以扫描所有 HTML 文件中的图像引用,然后将该列表与实际图像文件进行比较,那么我可能会看到不匹配的文件。

这是一个典型的图像标签:

<img src="images/edit_shapes.png" ALT="Edit examples" ALIGN=left>

我对 src= 之后的第一组引号之间的部分很感兴趣。 在寻找了一些解决方案后,我找到一个名为 BeautifulSoup 的 Python 模块。 脚本的核心部分如下所示:

soup = BeautifulSoup(all_text, 'html.parser')
match = soup.findAll("img")
if len(match) > 0:
    for m in match:
        imagelist.append(str(m))

我们可以使用这个 findAll 方法来挖出图片标签。 这是一小部分输出:

<img src="images/pdf-form-ht3.png"/><img src="images/pdf-form-ht4.png"/><img src="images/pdf-form-ht5.png"/><img src="images/pdf-form-ht6.png"/><img align="middle" alt="GSview - Advanced Options Panel" src="images/gsadv1.png" title="GSview - Advanced Options Panel"/><img align="middle" alt="Scribus External Tools Preferences" src="images/gsadv2.png" title="Scribus External Tools Preferences"/>

到现在为止还挺好。我原以为下一步就可以搞定了,但是当我在脚本中尝试了一些字符串方法时,它返回了有关标记的错误而不是字符串的错误。 我将输出保存到一个文件中,并在 KWrite 中进行编辑。 KWrite 的一个好处是你可以使用正则表达式(regex)来做“查找和替换”操作,所以我可以用 \n<img 替换 <img,这样可以看得更清楚。 KWrite 的另一个好处是,如果你用正则表达式做了一个不明智的选择,你还可以撤消。

但我认为,肯定有比这更好的东西,所以我转而使用正则表达式,或者更具体地说 Python 的 re 模块。 这个新脚本的相关部分如下所示:

match = re.findall(r'src="(.*)/>', all_text)
if len(match)>0:
    for m in match:
        imagelist.append(m)

它的一小部分输出如下所示:

images/cmcanvas.png" title="Context Menu for the document canvas" alt="Context Menu for the document canvas" /></td></tr></table><br images/eps-imp1.png" title="EPS preview in a file dialog" alt="EPS preview in a file dialog" images/eps-imp5.png" title="Colors imported from an EPS file" alt="Colors imported from an EPS file" images/eps-imp4.png" title="EPS font substitution" alt="EPS font substitution" images/eps-imp2.png" title="EPS import progress" alt="EPS import progress" images/eps-imp3.png" title="Bitmap conversion failure" alt="Bitmap conversion failure"

乍一看,它看起来与上面的输出类似,并且附带有去除图像的标签部分的好处,但是有令人费解的是还夹杂着表格标签和其他内容。 我认为这涉及到这个正则表达式 src="(.*)/>,这被称为贪婪,意味着它不一定停止在遇到 /> 的第一个实例。我应该补充一点,我也尝试过 src="(.*)",这真的没有什么更好的效果,我不是一个正则表达式专家(只是做了这个),找了各种方法来改进这一点但是并没什么用。

做了一系列的事情之后,甚至尝试了 Perl 的 HTML::Parser 模块,最终我试图将这与我为 Scribus 编写的一些脚本进行比较,这些脚本逐个字符的分析文本内容,然后采取一些行动。 为了最终目的,我终于想出了所有这些方法,并且完全不需要正则表达式或 HTML 解析器。 让我们回到展示的那个 img 标签的例子。

<img src="images/edit_shapes.png" ALT="Edit examples" ALIGN=left>

我决定回到 src= 这一块。 一种方法是等待 s 出现,然后看下一个字符是否是 r,下一个是 c,下一个是否 =。 如果是这样,那就匹配上了! 那么两个双引号之间的内容就是我所需要的。 这种方法的问题在于需要连续识别上面这样的结构。 一种查看代表一行 HTML 文本的字符串的方法是:

for c in all_text:

但是这个逻辑太乱了,以至于不能持续匹配到前面的 c,还有之前的字符,更之前的字符,更更之前的字符。

最后,我决定专注于 = 并使用索引方法,以便我可以轻松地引用字符串中的任何先前或将来的字符。 这里是搜索部分:

    index = 3
    while index < linelength:
        if (all_text[index] == '='):
            if (all_text[index-3] == 's') and (all_text[index-2] == 'r') and (all_text[index-1] == 'c'):
                imagefound(all_text, imagelist, index)
                index += 1
            else:
                index += 1
        else:
            index += 1

我用第四个字符开始搜索(索引从 0 开始),所以我在下面没有出现索引错误,并且实际上,在每一行的第四个字符之前不会有等号。 第一个测试是看字符串中是否出现了 =,如果没有,我们就会前进。 如果我们确实看到一个等号,那么我们会看前三个字符是否是 src。 如果全都匹配了,就调用函数 imagefound

def imagefound(all_text, imagelist, index):
    end = 0
    index += 2
    newimage = ''
    while end == 0:
        if (all_text[index] != '"'):
            newimage = newimage + all_text[index]
            index += 1
        else:
            newimage = newimage + '\n'
            imagelist.append(newimage)
            end = 1
            return

我们给函数发送当前索引,它代表着 =。 我们知道下一个字符将会是 ",所以我们跳过两个字符,并开始向名为 newimage 的控制字符串添加字符,直到我们发现下一个 ",此时我们完成了一次匹配。 我们将字符串加一个换行符(\n)添加到列表 imagelist 中并返回(return),请记住,在剩余的这个 HTML 字符串中可能会有更多图片标签,所以我们马上回到搜索循环中。

以下是我们的输出现在的样子:

images/text-frame-link.png
images/text-frame-unlink.png
images/gimpoptions1.png
images/gimpoptions3.png
images/gimpoptions2.png
images/fontpref3.png
images/font-subst.png
images/fontpref2.png
images/fontpref1.png
images/dtp-studio.png

啊,干净多了,而这只花费几秒钟的时间。 我本可以将索引前移 7 步来剪切 images/ 部分,但我更愿意把这个部分保存下来,以确保我没有剪切掉图像文件名的第一个字母,这很容易用 KWrite 编辑成功 —— 你甚至不需要正则表达式。 做完这些并保存文件后,下一步就是运行我编写的另一个脚本 sortlist.py

#!/usr/bin/env python
# -*- coding: utf-8  -*-
# sortlist.py

import os

imagelist = []
for line in open('/tmp/imagelist_parse4.txt').xreadlines():
    imagelist.append(line)

imagelist.sort()

outfile = open('/tmp/imagelist_parse4_sorted.txt', 'w')
outfile.writelines(imagelist)
outfile.close()

这会读取文件内容,并存储为列表,对其排序,然后另存为另一个文件。 之后,我可以做到以下几点:

ls /home/gregp/development/Scribus15x/doc/en/images/*.png > '/tmp/actual_images.txt'

然后我需要在该文件上运行 sortlist.py,因为 ls 方法的排序与 Python 不同。 我原本可以在这些文件上运行比较脚本,但我更愿意以可视方式进行操作。 最后,我成功找到了 42 个图像,这些图像没有来自文档的 HTML 引用。

这是我的完整解析脚本:

#!/usr/bin/env python
# -*- coding: utf-8  -*-
# parseimg4.py

import os

def imagefound(all_text, imagelist, index):
    end = 0
    index += 2
    newimage = ''
    while end == 0:
        if (all_text[index] != '"'):
            newimage = newimage + all_text[index]
            index += 1
        else:
            newimage = newimage + '\n'
            imagelist.append(newimage)
            end = 1
            return

htmlnames = []
imagelist = []
tempstring = ''
filenames = os.listdir('/home/gregp/development/Scribus15x/doc/en/')
for name in filenames:
    if name.endswith('.html'):
        htmlnames.append(name)
#print htmlnames
for htmlfile in htmlnames:
    all_text = open('/home/gregp/development/Scribus15x/doc/en/' + htmlfile).read()
    linelength = len(all_text)
    index = 3
    while index < linelength:
        if (all_text[index] == '='):
            if (all_text[index-3] == 's') and (all_text[index-2] == 'r') and
(all_text[index-1] == 'c'):
                imagefound(all_text, imagelist, index)
                index += 1
            else:
                index += 1
        else:
            index += 1

outfile = open('/tmp/imagelist_parse4.txt', 'w')
outfile.writelines(imagelist)
outfile.close()
imageno = len(imagelist)
print str(imageno) + " images were found and saved"

脚本名称为 parseimg4.py,这并不能真实反映我陆续编写的脚本数量(包括微调的和大改的以及丢弃并重新开始写的)。 请注意,我已经对这些目录和文件名进行了硬编码,但是很容易变得通用化,让用户输入这些信息。 同样,因为它们是工作脚本,所以我将输出发送到 /tmp 目录,所以一旦重新启动系统,它们就会消失。

这不是故事的结尾,因为下一个问题是:僵尸 HTML 文件怎么办? 任何未使用的文件都可能会引用图像,不能被前面的方法所找出。 我们有一个 menu.xml 文件作为联机手册的目录,但我还需要考虑 TOC(LCTT 译注:TOC 是 table of contents 的缩写)中列出的某些文件可能引用了不在 TOC 中的文件,是的,我确实找到了一些这样的文件。

最后我可以说,这是一个比图像搜索更简单的任务,而且开发的过程对我有很大的帮助。

关于作者

Greg Pittman 是 Kentucky 州 Louisville 市的一名退休的神经学家,从二十世纪六十年代的 Fortran IV 语言开始长期以来对计算机和编程有着浓厚的兴趣。 当 Linux 和开源软件出现的时候,Greg 深受启发,去学习更多知识,并实现最终贡献的承诺。 他是 Scribus 团队的成员。更多关于我


via: https://opensource.com/article/18/1/parsing-html-python

作者:Greg Pittman 译者:Flowsnow 校对:wxy

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

Plumbum:Shell 组合器

你是否曾希望将 shell 脚本紧凑地融入到真正的编程语言里面? 那么可以了解下 Plumbum Shell 组合器。Plumbum (lead 的拉丁语,以前用来制作管道)是一个小型但功能丰富的类库,用于以 Python 进行类似 shell 脚本编程。该库的理念是 “永远不要再写 shell 脚本”,因此它试图合理地模仿 shell 语法(shell 组合器),同时保持 Python 特性和跨平台

除了类似 shell 的语法便捷的快捷方式之外,该库还提供本地和远程命令执行(通过 SSH)、本地和远程文件系统路径、简单的工作目录和环境操作、快捷访问 ANSI 颜色,以及编程命令行接口(CLI)应用程序工具包。现在让我们看一些代码!

其最新版本 1.6.6 发布于 2018 年 2 月 12 日。

快捷使用指南

基本使用

>>> from plumbum import local
>>> ls = local["ls"]
>>> ls
LocalCommand(<LocalPath /bin/ls>)
>>> ls()
u'build.py\ndist\ndocs\nLICENSE\nplumbum\nREADME.rst\nsetup.py\ntests\ntodo.txt\n'
>>> notepad = local["c:\\windows\\notepad.exe"]
>>> notepad()                                   # Notepad window pops up
u''                                             # Notepad window is closed by user, command returns

不需要为每个你想使用的命令写 xxx = local["xxx"],你可以导入命令行

>>> from plumbum.cmd import grep, wc, cat, head
>>> grep
LocalCommand(<LocalPath /bin/grep>)

参见本地命令行

管道

>>> chain = ls["-a"] | grep["-v", "\\.py"] | wc["-l"]
>>> print chain
/bin/ls -a | /bin/grep -v '\.py' | /usr/bin/wc -l
>>> chain()
u'13\n'

参见管道

重定向

>>> ((cat < "setup.py") | head["-n", 4])()
u'#!/usr/bin/env python\nimport os\n\ntry:\n'
>>> (ls["-a"] > "file.list")()
u''
>>> (cat["file.list"] | wc["-l"])()
u'17\n'

参见输入/输出重定向

工作目录操作

>>> local.cwd
<Workdir /home/tomer/workspace/plumbum>
>>> with local.cwd(local.cwd / "docs"):
...     chain()
...
u'15\n'

参见路径本地对象

前台后和后台执行

>>> from plumbum import FG, BG
>>> (ls["-a"] | grep["\\.py"]) & FG         # The output is printed to stdout directly
build.py
.pydevproject
setup.py
>>> (ls["-a"] | grep["\\.py"]) & BG         # The process runs "in the background"
<Future ['/bin/grep', '\\.py'] (running)>

参见前台和后台

命令行嵌套

>>> from plumbum.cmd import sudo
>>> print sudo[ifconfig["-a"]]
/usr/bin/sudo /sbin/ifconfig -a
>>> (sudo[ifconfig["-a"]] | grep["-i", "loop"]) & FG
lo        Link encap:Local Loopback
          UP LOOPBACK RUNNING  MTU:16436  Metric:1

参见命令行嵌套

远程命令(通过 SSH)

>>> from plumbum import SshMachine
>>> remote = SshMachine("somehost", user = "john", keyfile = "/path/to/idrsa")
>>> r_ls = remote["ls"]
>>> with remote.cwd("/lib"):
...     (r_ls | grep["0.so.0"])()
...
u'libusb-1.0.so.0\nlibusb-1.0.so.0.0.0\n'

参见远程

CLI 应用程序

import logging
from plumbum import cli

class MyCompiler(cli.Application):
    verbose = cli.Flag(["-v", "--verbose"], help = "Enable verbose mode")
    include_dirs = cli.SwitchAttr("-I", list = True, help = "Specify include directories")

    @cli.switch("-loglevel", int)
    def set_log_level(self, level):
        """Sets the log-level of the logger"""
        logging.root.setLevel(level)

    def main(self, *srcfiles):
        print "Verbose:", self.verbose
        print "Include dirs:", self.include_dirs
        print "Compiling:", srcfiles

if __name__ == "__main__":
    MyCompiler.run()

输出样例:

$ python simple_cli.py -v -I foo/bar -Ispam/eggs x.cpp y.cpp z.cpp
Verbose: True
Include dirs: ['foo/bar', 'spam/eggs']
Compiling: ('x.cpp', 'y.cpp', 'z.cpp')

参见命令行应用程序

颜色和风格

from plumbum import colors
with colors.red:
    print("This library provides safe, flexible color access.")
    print(colors.bold | "(and styles in general)", "are easy!")
print("The simple 16 colors or",
      colors.orchid & colors.underline | '256 named colors,',
      colors.rgb(18, 146, 64) | "or full rgb colors" ,
      'can be used.')
print("Unsafe " + colors.bg.dark_khaki + "color access" + colors.bg.reset + " is available too.")

输出样例:

This library provides safe color access.
Color (and styles in general) are easy!
The simple 16 colors, 256 named colors, or full hex colors can be used.
Unsafe color access is available too.

参见颜色

开发和安装

该库在 Github 上开发,非常乐意接受来自用户的补丁。请使用 GitHub 的内置 issue 跟踪器报告您遇到的任何问题或提出功能上的需求。该库在 IMIT 许可下发布。

要求

Plumbum 支持 Python 2.6-3.6 和 PyPy,并且通过 Travis CIAppveyor 持续地在 Linux,Mac 和 Windows 机器上测试。Plumbum 在任何类 Unix 的机器都应该可以正常工作,但是在 Windows 上,你也许需要安装一个合适的 coreutils 环境并把其加入到你的PATH环境变量中。我推荐 mingw(与 Windows Git 捆绑在一起),但是 cygwin 应该也可以。如果你仅仅是使用 Plumbum 代替 Popen 来运行 Windows 程序,那么就不需要 Unix 工具了。 注意远程命令的执行,你需要一个 openSHH 兼容的客户端(同样与 Windows Git 捆绑在一起)和一个 bash 兼容的 shell,也需要在主机上有一个 coreutils 环境。

下载

你可以在 Python Package Index (多种格式)下载该库,或者直接运行 pip install plumbum。如果你使用 Anaconda,你可以使用 conda install -c conda-forge plumbumconda-forge 通道获取。

用户指南

用户指南涵盖了 Plumbum 大部分功能,拥有大量的代码片段,你可以不用花多少时间即可开始使用。该指南逐渐介绍概念和"语法",因此推荐你按照顺序阅读。一个有效的快速参考指南略。。。

关于

Plumburn 最初的目的是让本地和远程程序轻松地执行,假设没有比老的 ssh 更时髦的东西了。在此基础上,设计了一个文件系统抽象层,以便能够无缝地处理本地和远程文件。 我有这个想法一段时间了,直到我必须要个给我当前工作的项目写一个构建脚本,我决定使用 shell 脚本,现在是实现它的时候了。Plumbum 诞生自 Path 类的片段和我为 RPyC 写的 SshContextSshTunnel 类。Path 类是我为前面说的构建系统写的。当我将两者与 shell 组合器(因为 shell 脚本在这里确实有优势)组合在一起时,奇迹就发生了,便产生了Plumbun。

致谢

该项目受到了 Andrew MoffatPBS(现在被称作 sh)启发,并且借用了他的一些思想(即像函数一样看待程序,导入命令行的技巧)。然而我感觉在 PBS 中有太多的神秘的东西,当我编写类 shell 程序时,语法不是我想要的。关于这个问题我联系了 Andrew,但是他想让 PBS 保持这种状态。除此之外,两个库走不同的方向,Plumbum 试图提供一种更合理的方法。

Plumbum 也向 Rotem Yaari 致敬,他为特定的目的建议了一个代号为 pyplatform 的库,但是尚未实现过。

人生苦短,我用 Python,Python 是非常棒的快速构建应用程序的编程语言。在这篇文章中我们将学习如何使用 Python 去构建一个 RSS 提示系统,目标是使用 Fedora 快乐地学习 Python。如果你正在寻找一个完整的 RSS 提示应用程序,在 Fedora 中已经准备好了几个包。

Fedora 和 Python —— 入门知识

Python 3.6 在 Fedora 中是默认安装的,它包含了 Python 的很多标准库。标准库提供了一些可以让我们的任务更加简单完成的模块的集合。例如,在我们的案例中,我们将使用 sqlite3 模块在数据库中去创建表、添加和读取数据。在这个案例中,我们试图去解决的是这样的一个特定问题,在标准库中没有包含,而有可能已经有人为我们开发了这样一个模块。最好是使用像大家熟知的 PyPI Python 包索引去搜索一下。在我们的示例中,我们将使用 feedparser 去解析 RSS 源。

因为 feedparser 并不是标准库,我们需要将它安装到我们的系统上。幸运的是,在 Fedora 中有这个 RPM 包,因此,我们可以运行如下的命令去安装 feedparser:

$ sudo dnf install python3-feedparser

我们现在已经拥有了编写我们的应用程序所需的东西了。

存储源数据

我们需要存储已经发布的文章的数据,这样我们的系统就可以只提示新发布的文章。我们要保存的数据将是用来辨别一篇文章的唯一方法。因此,我们将存储文章的标题和发布日期。

因此,我们来使用 Python sqlite3 模块和一个简单的 SQL 语句来创建我们的数据库。同时也添加一些后面将要用到的模块(feedparse,smtplib,和 email)。

创建数据库

#!/usr/bin/python3
import sqlite3
import smtplib
from email.mime.text import MIMEText

import feedparser

db_connection = sqlite3.connect('/var/tmp/magazine_rss.sqlite')
db = db_connection.cursor()
db.execute(' CREATE TABLE IF NOT EXISTS magazine (title TEXT, date TEXT)')

这几行代码创建一个名为 magazine_rss.sqlite 文件的新 sqlite 数据库,然后在数据库创建一个名为 magazine 的新表。这个表有两个列 —— titledate —— 它们能存诸 TEXT 类型的数据,也就是说每个列的值都是文本字符。

检查数据库中的旧文章

由于我们仅希望增加新的文章到我们的数据库中,因此我们需要一个功能去检查 RSS 源中的文章在数据库中是否存在。我们将根据它来判断是否发送(有新文章的)邮件提示。Ok,现在我们来写这个功能的代码。

def article_is_not_db(article_title, article_date):
    """ Check if a given pair of article title and date
    is in the database.
    Args:
        article_title (str): The title of an article
        article_date  (str): The publication date of an article
    Return:
        True if the article is not in the database
        False if the article is already present in the database
    """
    db.execute("SELECT * from magazine WHERE title=? AND date=?", (article_title, article_date))
    if not db.fetchall():
        return True
    else:
        return False

这个功能的主要部分是一个 SQL 查询,我们运行它去搜索数据库。我们使用一个 SELECT 命令去定义我们将要在哪个列上运行这个查询。我们使用 * 符号去选取所有列(titledate)。然后,我们使用查询的 WHERE 条件 article_titlearticle_date 去匹配标题和日期列中的值,以检索出我们需要的内容。

最后,我们使用一个简单的返回 True 或者 False 的逻辑来表示是否在数据库中找到匹配的文章。

在数据库中添加新文章

现在我们可以写一些代码去添加新文章到数据库中。

def add_article_to_db(article_title, article_date):
    """ Add a new article title and date to the database
    Args:
        article_title (str): The title of an article
        article_date (str): The publication date of an article
    """
    db.execute("INSERT INTO magazine VALUES (?,?)", (article_title, article_date))
    db_connection.commit()

这个功能很简单,我们使用了一个 SQL 查询去插入一个新行到 magazine 表的 article_titlearticle_date 列中。然后提交它到数据库中永久保存。

这些就是在数据库中所需要的东西,接下来我们看一下,如何使用 Python 实现提示系统和发送电子邮件。

发送电子邮件提示

我们使用 Python 标准库模块 smtplib 来创建一个发送电子邮件的功能。我们也可以使用标准库中的 email 模块去格式化我们的电子邮件信息。

def send_notification(article_title, article_url):
    """ Add a new article title and date to the database

    Args:
        article_title (str): The title of an article
        article_url (str): The url to access the article
    """

    smtp_server = smtplib.SMTP('smtp.gmail.com', 587)
    smtp_server.ehlo()
    smtp_server.starttls()
    smtp_server.login('[email protected]', '123your_password')
    msg = MIMEText(f'\nHi there is a new Fedora Magazine article : {article_title}. \nYou can read it here {article_url}')
    msg['Subject'] = 'New Fedora Magazine Article Available'
    msg['From'] = '[email protected]'
    msg['To'] = '[email protected]'
    smtp_server.send_message(msg)
    smtp_server.quit()

在这个示例中,我使用了谷歌邮件系统的 smtp 服务器去发送电子邮件,在你自己的代码中你需要将它更改为你自己的电子邮件服务提供者的 SMTP 服务器。这个功能是个样板,大多数的内容要根据你的 smtp 服务器的参数来配置。代码中的电子邮件地址和凭证也要更改为你自己的。

如果在你的 Gmail 帐户中使用了双因子认证,那么你需要配置一个密码应用程序为你的这个应用程序提供一个唯一密码。可以看这个 帮助页面

读取 Fedora Magazine 的 RSS 源

我们已经有了在数据库中存储文章和发送提示电子邮件的功能,现在来创建一个解析 Fedora Magazine RSS 源并提取文章数据的功能。

def read_article_feed():
    """ Get articles from RSS feed """
    feed = feedparser.parse('https://fedoramagazine.org/feed/')
    for article in feed['entries']:
        if article_is_not_db(article['title'], article['published']):
            send_notification(article['title'], article['link'])
            add_article_to_db(article['title'], article['published'])

if __name__ == '__main__':
    read_article_feed()
    db_connection.close()

在这里我们将使用 feedparser.parse 功能。这个功能返回一个用字典表示的 RSS 源,对于 feedparser 的完整描述可以参考它的 文档

RSS 源解析将返回最后的 10 篇文章作为 entries,然后我们提取以下信息:标题、链接、文章发布日期。因此,我们现在可以使用前面定义的检查文章是否在数据库中存在的功能,然后,发送提示电子邮件并将这个文章添加到数据库中。

当运行我们的脚本时,最后的 if 语句运行我们的 read_article_feed 功能,然后关闭数据库连接。

运行我们的脚本

给脚本文件赋于正确运行权限。接下来,我们使用 cron 实用程序去每小时自动运行一次我们的脚本。cron 是一个作业计划程序,我们可以使用它在一个固定的时间去运行一个任务。

$ chmod a+x my_rss_notifier.py
$ sudo cp my_rss_notifier.py /etc/cron.hourly

为了使该教程保持简单,我们使用了 cron.hourly 目录每小时运行一次我们的脚本,如果你想学习关于 cron 的更多知识以及如何配置 crontab,请阅读 cron 的 wikipedia 页面

总结

在本教程中,我们学习了如何使用 Python 去创建一个简单的 sqlite 数据库、解析一个 RSS 源、以及发送电子邮件。我希望通过这篇文章能够向你展示,使用 Python 和 Fedora 构建你自己的应用程序是件多么容易的事。

这个脚本在 GitHub 上可以找到。


via: https://fedoramagazine.org/never-miss-magazines-article-build-rss-notification-system/

作者:Clément Verna 译者:qhwdw 校对:wxy

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

开始之前,说一下本文中的代码视频可以在我的 GitHub 上找到。

那么,让我们开始吧!如果你糊涂了,我建议你在单独的选项卡中打开下面的视频。

开始 (先决条件)

首先在你的操作系统上安装 Anaconda (Python)。你可以从官方网站下载 anaconda 并自行安装,或者你可以按照以下这些 anaconda 安装教程进行安装。

  • 在 Windows 上安装 Anaconda: 链接[5
  • 在 Mac 上安装 Anaconda: 链接
  • 在 Ubuntu (Linux) 上安装 Anaconda:链接

打开一个 Jupyter Notebook

打开你的终端(Mac)或命令行,并输入以下内容(请参考视频中的 1:16 处)来打开 Jupyter Notebook:

jupyter notebook

打印语句/Hello World

在 Jupyter 的单元格中输入以下内容并按下 shift + 回车来执行代码。

# This is a one line comment
print('Hello World!')

打印输出 “Hello World!”

字符串和字符串操作

字符串是 Python 类的一种特殊类型。作为对象,在类中,你可以使用 .methodName() 来调用字符串对象的方法。字符串类在 Python 中默认是可用的,所以你不需要 import 语句来使用字符串对象接口。

# Create a variable
# Variables are used to store information to be referenced
# and manipulated in a computer program.
firstVariable = 'Hello World'
print(firstVariable)

输出打印变量 firstVariable

# Explore what various string methods
print(firstVariable.lower())
print(firstVariable.upper())
print(firstVariable.title())

使用 .lower()、.upper() 和 title() 方法输出

# Use the split method to convert your string into a list
print(firstVariable.split(' '))

使用 split 方法输出(此例中以空格分隔)

# You can add strings together.
a = "Fizz" + "Buzz"
print(a)

字符串连接

查询方法的功能

对于新程序员,他们经常问你如何知道每种方法的功能。Python 提供了两种方法来实现。

1、(在不在 Jupyter Notebook 中都可用)使用 help 查询每个方法的功能。

查询每个方法的功能

2.(Jupyter Notebook 专用)你也可以通过在方法之后添加问号来查找方法的功能。

# To look up what each method does in jupyter (doesnt work outside of jupyter)
firstVariable.lower?

在 Jupyter 中查找每个方法的功能

结束语

如果你对本文或在 YouTube 视频的评论部分有任何疑问,请告诉我们。文章中的代码也可以在我的 GitHub 上找到。本系列教程的第 2 部分是简单的数学操作


via: https://www.codementor.io/mgalarny/python-hello-world-and-string-manipulation-gdgwd8ymp

作者:Michael 译者:geekpi 校对:wxy

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

我喜欢 Vue。当我在 2016 年第一次接触它时,也许那时我已经对 JavaScript 框架感到疲劳了,因为我已经具有Backbone、Angular、React 等框架的经验,没有太多的热情去尝试一个新的框架。直到我在 Hacker News 上读到一份评论,其描述 Vue 是类似于“新 jQuery” 的 JavaScript 框架,从而激发了我的好奇心。在那之前,我已经相当满意 React 这个框架,它是一个很好的框架,建立于可靠的设计原则之上,围绕着视图模板、虚拟 DOM 和状态响应等技术。而 Vue 也提供了这些重要的内容。

在这篇文章中,我旨在解释为什么 Vue 适合我,为什么在上文中那些我尝试过的框架中选择它。也许你将同意我的一些观点,但至少我希望能够给大家使用 Vue 开发现代 JavaScript 应用一些灵感。

1、 极少的模板语法

Vue 默认提供的视图模板语法是极小的、简洁的和可扩展的。像其他 Vue 部分一样,可以很简单的使用类似 JSX 一样语法,而不使用标准的模板语法(甚至有官方文档说明了如何做),但是我觉得没必要这么做。JSX 有好的方面,也有一些有依据的批评,如混淆了 JavaScript 和 HTML,使得很容易导致在模板中出现复杂的代码,而本来应该分开写在不同的地方的。

Vue 没有使用标准的 HTML 来编写视图模板,而是使用极少的模板语法来处理简单的事情,如基于视图数据迭代创建元素。

<template>
  <div id="app">
    <ul>
      <li v-for='number in numbers' :key='number'>{{ number }}</li>
    </ul>
    <form @submit.prevent='addNumber'>
      <input type='text' v-model='newNumber'>
      <button type='submit'>Add another number</button>
    </form>
  </div>
</template>

<script>
export default {
  name: 'app',
  methods: {
    addNumber() {
      const num = +this.newNumber;
      if (typeof num === 'number' && !isNaN(num)) {
        this.numbers.push(num);
      }
    }
  },
  data() {
    return {
      newNumber: null,
      numbers: [1, 23, 52, 46]
    };
  }
}
</script>

<style lang="scss">
ul {
  padding: 0;
  li {
    list-style-type: none;
    color: blue;
  }
}
</style>

我也喜欢 Vue 提供的简短绑定语法,: 用于在模板中绑定数据变量,@ 用于绑定事件。这是一个细节,但写起来很爽而且能够让你的组件代码简洁。

2、 单文件组件

大多数人使用 Vue,都使用“单文件组件”。本质上就是一个 .vue 文件对应一个组件,其中包含三部分(CSS、HTML和JavaScript)。

这种技术结合是对的。它让人很容易在一个单独的地方了解每个组件,同时也非常好的鼓励了大家保持每个组件代码的简短。如果你的组件中 JavaScript、CSS 和 HTML 代码占了很多行,那么就到了进一步模块化的时刻了。

在使用 Vue 组件中的 <style> 标签时,我们可以添加 scoped 属性。这会让整个样式完全的封装到当前组件,意思是在组件中如果我们写了 .name 的 css 选择器,它不会把样式应用到其他组件中。我非常喜欢这种方式来应用样式而不是像其他主要框架流行在 JS 中编写 CSS 的方式。

关于单文件组件另一个好处是 .vue 文件实际上是一个有效的 HTML 5 文件。<template><script><style> 都是 w3c 官方规范的标签。这就表示很多如 linters (LCTT 译注:一种代码检查工具插件)这样我们用于开发过程中的工具能够开箱即用或者添加一些适配后使用。

3、 Vue “新的 jQuery”

事实上,这两个库不相似而且用于做不同的事。让我提供给你一个很精辟的类比(我实际上非常喜欢描述 Vue 和 jQuery 之间的关系):披头士乐队和齐柏林飞船乐队(LCTT 译注:两个都是英国著名的乐队)。披头士乐队不需要介绍,他们是 20 世纪 60 年代最大的和最有影响力的乐队。但很难说披头士乐队是 20 世纪 70 年代最大的乐队,因为有时这个荣耀属于是齐柏林飞船乐队。你可以说两个乐队之间有着微妙的音乐联系或者说他们的音乐是明显不同的,但两者一些先前的艺术和影响力是不可否认的。也许 21 世纪初 JavaScript 的世界就像 20 世纪 70 年代的音乐世界一样,随着 Vue 获得更多关注使用,只会吸引更多粉丝。

一些使 jQuery 牛逼的哲学理念在 Vue 中也有呈现:非常容易的学习曲线但却具有基于现代 web 标准构建牛逼 web 应用所有你需要的功能。Vue 的核心本质上就是在 JavaScript 对象上包装了一层。

4、 极易扩展

正如前述,Vue 默认使用标准的 HTML、JS 和 CSS 构建组件,但可以很容易插入其他技术。如果我们想使用pug(LCTT译注:一款功能丰富的模板引擎,专门为 Node.js 平台开发)替换 HTML 或者使用 Typescript(LCTT译注:一种由微软开发的编程语言,是 JavaScript 的一个超集)替换 js 或者 Sass (LCTT 译注:一种 CSS 扩展语言)替换 CSS,只需要安装相关的 node 模块和在我们的单文件组件中添加一个属性到相关的标签即可。你甚至可以在一个项目中混合搭配使用 —— 如一些组件使用 HTML 其他使用 pug ——然而我不太确定这么做是最好的做法。

5、 虚拟 DOM

虚拟 DOM 是很好的技术,被用于现如今很多框架。其意味着这些框架能够做到根据我们状态的改变来高效的完成 DOM 更新,减少重新渲染,从而优化我们应用的性能。现如今每个框架都有虚拟 DOM 技术,所以虽然它不是什么独特的东西,但它仍然很出色。

6、 Vuex 很棒

对于大多数应用,管理状态成为一个棘手的问题,单独使用一个视图库不能解决这个问题。Vue 使用 Vuex 库来解决这个问题。Vuex 很容易构建而且和 Vue 集成的很好。熟悉 redux(另一个管理状态的库)的人学习 Vuex 会觉得轻车熟路,但是我发现 Vue 和 Vuex 集成起来更加简洁。最新 JavaScript 草案中(LCTT 译注:应该是指 ES7)提供了对象展开运算符(LCTT 译注:符号为 ...),允许我们在状态或函数中进行合并,以操纵从 Vuex 到需要它的 Vue 组件中的状态。

7、 Vue 的命令行界面(CLI)

Vue 提供的命令行界面非常不错,很容易用 Vue 搭建一个基于 Webpack(LCTT 译注:一个前端资源加载/打包工具)的项目。单文件组件支持、babel(LCTT 译注:js 语法转换器)、linting(LCTT译注:代码检查工具)、测试工具支持,以及合理的项目结构,都可以在终端中一行命令创建。

然而有一个命令,我在 CLI 中没有找到,那就是 vue build

如:

echo '<template><h1>Hello World!</h1></template>' > Hello.vue && vue build Hello.vue -o

vue build 命令构建和运行组件并在浏览器中测试看起来非常简单。很不幸这个命令后来在 Vue 中删除了,现在推荐使用 Poi。Poi 本质上是在 Webpack 工具上封装了一层,但我不认我它像推特上说的那样简单。

8、 重新渲染优化

使用 Vue,你不必手动声明 DOM 的哪部分应该被重新渲染。我从来都不喜欢操纵 React 组件的渲染,像在shouldComponentUpdate 方法中停止整个 DOM 树重新渲染这种。Vue 在这方面非常巧妙。

9、 容易获得帮助

Vue 已经达到了使用这个框架来构建各种各样的应用的一种群聚效应。开发文档非常完善。如果你需要进一步的帮助,有多种渠道可用,每个渠道都有很多活跃开发者:stackoverflow、discord、twitter 等。相对于其他用户量少的框架,这就应该给你更多的信心来使用Vue构建应用。

10、 多机构维护

我认为,一个开源库,在发展方向方面的投票权利没有被单一机构操纵过多,是一个好事。就如同 React 的许可证问题(现已解决),Vue 就不可能涉及到。

总之,作为你接下来要开发的任何 JavaScript 项目,我认为 Vue 都是一个极好的选择。Vue 可用的生态圈比我博客中涉及到的其他库都要大。如果想要更全面的产品,你可以关注 Nuxt.js。如果你需要一些可重复使用的样式组件你可以关注类似 Vuetify 的库。

Vue 是 2017 年增长最快的库之一,我预测在 2018 年增长速度不会放缓。

如果你有空闲的 30 分钟,为什么不尝试下 Vue,看它可以给你提供什么呢?

P.S. — 这篇文档很好的展示了 Vue 和其他框架的比较:https://vuejs.org/v2/guide/comparison.html


via: https://medium.com/@dalaidunc/10-things-i-love-about-vue-505886ddaff2

作者:Duncan Grant 译者:yizhuoyan 校对:wxy

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