分类 技术 下的文章

在本文中,我们的目的是让你了解如何设置属于自己的Git服务器。

Git 是由 Linux Torvalds 开发的一个版本控制系统,现如今正在被全世界大量开发者使用。许多公司喜欢使用基于 Git 版本控制的 GitHub 代码托管。根据报道,GitHub 是现如今全世界最大的代码托管网站。GitHub 宣称已经有 920 万用户和 2180 万个仓库。许多大型公司现如今也将代码迁移到 GitHub 上。甚至于谷歌,一家搜索引擎公司,也正将代码迁移到 GitHub 上

运行你自己的 Git 服务器

GitHub 能提供极佳的服务,但却有一些限制,尤其是你是单人或是一名 coding 爱好者。GitHub 其中之一的限制就是其中免费的服务没有提供代码私有托管业务。你不得不支付每月 7 美金购买 5 个私有仓库,并且想要更多的私有仓库则要交更多的钱。

万一你想要私有仓库或需要更多权限控制,最好的方法就是在你的服务器上运行 Git。不仅你能够省去一笔钱,你还能够在你的服务器有更多的操作。在大多数情况下,大多数高级 Linux 用户已经拥有自己的服务器,并且在这些服务器上方式 Git 就像“啤酒一样免费”(LCTT 译注:指免费软件)。

在这篇教程中,我们主要讲在你的服务器上,使用两种代码管理的方法。一种是运行一个纯 Git 服务器,另一个是使用名为 GitLab 的 GUI 工具。在本教程中,我在 VPS 上运行的操作系统是 Ubuntu 14.04 LTS。

在你的服务器上安装 Git

在本篇教程中,我们考虑一个简单案例,我们有一个远程服务器和一台本地服务器,现在我们需要使用这两台机器来工作。为了简单起见,我们就分别叫它们为远程服务器和本地服务器。

首先,在两边的机器上安装 Git。你可以从依赖包中安装 Git,在本文中,我们将使用更简单的方法:

sudo apt-get install git-core

为 Git 创建一个用户。

sudo useradd git
passwd git

为了容易的访问服务器,我们设置一个免密 ssh 登录。首先在你本地电脑上创建一个 ssh 密钥:

ssh-keygen -t rsa

这时会要求你输入保存密钥的路径,这时只需要点击回车保存在默认路径。第二个问题是输入访问远程服务器所需的密码。它生成两个密钥——公钥和私钥。记下您在下一步中需要使用的公钥的位置。

现在您必须将这些密钥复制到服务器上,以便两台机器可以相互通信。在本地机器上运行以下命令:

cat ~/.ssh/id_rsa.pub | ssh git@remote-server "mkdir -p ~/.ssh && cat >> ~/.ssh/authorized_keys"

现在,用 ssh 登录进服务器并为 Git 创建一个项目路径。你可以为你的仓库设置一个你想要的目录。

现在跳转到该目录中:

cd /home/swapnil/project-1.git

现在新建一个空仓库:

git init --bare
Initialized empty Git repository in /home/swapnil/project-1.git

现在我们需要在本地机器上新建一个基于 Git 版本控制仓库:

mkdir -p /home/swapnil/git/project

进入我们创建仓库的目录:

cd /home/swapnil/git/project

现在在该目录中创建项目所需的文件。留在这个目录并启动 git

git init
Initialized empty Git repository in /home/swapnil/git/project

把所有文件添加到仓库中:

git add .

现在,每次添加文件或进行更改时,都必须运行上面的 add 命令。 您还需要为每个文件更改都写入提交消息。提交消息基本上说明了我们所做的更改。

git commit -m "message" -a
[master (root-commit) 57331ee] message
 2 files changed, 2 insertions(+)
 create mode 100644 GoT.txt
 create mode 100644 writing.txt

在这种情况下,我有一个名为 GoT(《权力的游戏》的点评)的文件,并且我做了一些更改,所以当我运行命令时,它指定对文件进行更改。 在上面的命令中 -a 选项意味着提交仓库中的所有文件。 如果您只更改了一个,则可以指定该文件的名称而不是使用 -a

举一个例子:

git commit -m "message" GoT.txt
[master e517b10] message
 1 file changed, 1 insertion(+)

到现在为止,我们一直在本地服务器上工作。现在我们必须将这些更改推送到远程服务器上,以便通过互联网访问,并且可以与其他团队成员进行协作。

git remote add origin ssh://git@remote-server/repo-<wbr< a="">>path-on-server..git

现在,您可以使用 pullpush 选项在服务器和本地计算机之间推送或拉取:

git push origin master

如果有其他团队成员想要使用该项目,则需要将远程服务器上的仓库克隆到其本地计算机上:

git clone git@remote-server:/home/swapnil/project.git

这里 /home/swapnil/project.git 是远程服务器上的项目路径,在你本机上则会改变。

然后进入本地计算机上的目录(使用服务器上的项目名称):

cd /project

现在他们可以编辑文件,写入提交更改信息,然后将它们推送到服务器:

git commit -m 'corrections in GoT.txt story' -a

然后推送改变:

git push origin master

我认为这足以让一个新用户开始在他们自己的服务器上使用 Git。 如果您正在寻找一些 GUI 工具来管理本地计算机上的更改,则可以使用 GUI 工具,例如 QGit 或 GitK for Linux。

使用 GitLab

这是项目所有者和协作者的纯命令行解决方案。这当然不像使用 GitHub 那么简单。不幸的是,尽管 GitHub 是全球最大的代码托管商,但是它自己的软件别人却无法使用。因为它不是开源的,所以你不能获取源代码并编译你自己的 GitHub。这与 WordPress 或 Drupal 不同,您无法下载 GitHub 并在您自己的服务器上运行它。

像往常一样,在开源世界中,是没有终结的尽头。GitLab 是一个非常优秀的项目。这是一个开源项目,允许用户在自己的服务器上运行类似于 GitHub 的项目管理系统。

您可以使用 GitLab 为团队成员或公司运行类似于 GitHub 的服务。您可以使用 GitLab 在公开发布之前开发私有项目。

GitLab 采用传统的开源商业模式。他们有两种产品:免费的开源软件,用户可以在自己的服务器上安装,以及类似于 GitHub 的托管服务。

可下载版本有两个版本,免费的社区版和付费企业版。企业版基于社区版,但附带针对企业客户的其他功能。它或多或少与 WordPress.org 或 Wordpress.com 提供的服务类似。

社区版具有高度可扩展性,可以在单个服务器或群集上支持 25000 个用户。GitLab 的一些功能包括:Git 仓库管理,代码评论,问题跟踪,活动源和维基。它配备了 GitLab CI,用于持续集成和交付。

Digital Ocean 等许多 VPS 提供商会为用户提供 GitLab 服务。 如果你想在你自己的服务器上运行它,你可以手动安装它。GitLab 为不同的操作系统提供了软件包。 在我们安装 GitLab 之前,您可能需要配置 SMTP 电子邮件服务器,以便 GitLab 可以在需要时随时推送电子邮件。官方推荐使用 Postfix。所以,先在你的服务器上安装 Postfix:

sudo apt-get install postfix

在安装 Postfix 期间,它会问你一些问题,不要跳过它们。 如果你一不小心跳过,你可以使用这个命令来重新配置它:

sudo dpkg-reconfigure postfix

运行此命令时,请选择 “Internet Site”并为使用 Gitlab 的域名提供电子邮件 ID。

我是这样输入的:

[email protected]

用 Tab 键并为 postfix 创建一个用户名。接下来将会要求你输入一个目标邮箱。

在剩下的步骤中,都选择默认选项。当我们安装且配置完成后,我们继续安装 GitLab。

我们使用 wget 来下载软件包(用 最新包 替换下载链接):

wget https://downloads-packages.s3.amazonaws.com/ubuntu-14.04/gitlab_7.9.4-omnibus.1-1_amd64.deb

然后安装这个包:

sudo dpkg -i gitlab_7.9.4-omnibus.1-1_amd64.deb

现在是时候配置并启动 GitLab 了。

sudo gitlab-ctl reconfigure

您现在需要在配置文件中配置域名,以便您可以访问 GitLab。打开文件。

nano /etc/gitlab/gitlab.rb

在这个文件中编辑 external_url 并输入服务器域名。保存文件,然后从 Web 浏览器中打开新建的一个 GitLab 站点。

默认情况下,它会以系统管理员的身份创建 root,并使用 5iveL!fe 作为密码。 登录到 GitLab 站点,然后更改密码。

密码更改后,登录该网站并开始管理您的项目。

GitLab 有很多选项和功能。最后,我借用电影“黑客帝国”中的经典台词:“不幸的是,没有人知道 GitLab 可以做什么。你必须亲自尝试一下。”


via: https://www.linux.com/learn/how-run-your-own-git-server

作者:Swapnil Bhartiya 选题:lujun9972 译者:wyxplus 校对:wxy

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

用 Perlbrew 在你系统上安装多个版本的 Perl。

有比在系统上安装了 Perl 更好的事情吗?那就是在系统中安装了多个版本的 Perl。使用 Perlbrew 你可以做到这一点。但是为什么呢,除了让你包围在 Perl 下之外,有什么好处吗?

简短的回答是,不同版本的 Perl 是......不同的。程序 A 可能依赖于较新版本中不推荐使用的行为,而程序 B 需要去年无法使用的新功能。如果你安装了多个版本的 Perl,则每个脚本都可以使用最适合它的版本。如果您是开发人员,这也会派上用场,你可以针对多个版本的 Perl 测试你的程序,这样无论你的用户运行什么,你都知道它能否工作。

安装 Perlbrew

另一个好处是 Perlbrew 会安装 Perl 到用户的家目录。这意味着每个用户都可以管理他们的 Perl 版本(以及相关的 CPAN 包),而无需与系统管理员联系。自助服务意味着为用户提供更快的安装,并为系统管理员提供更多时间来解决难题。

第一步是在你的系统上安装 Perlbrew。许多 Linux 发行版已经在包仓库中拥有它,因此你只需要 dnf install perlbrew(或者适用于你的发行版的命令)。你还可以使用 cpan App::perlbrew 从 CPAN 安装 App::perlbrew 模块。或者你可以在 install.perlbrew.pl 下载并运行安装脚本。

要开始使用 Perlbrew,请运行 perlbrew init

安装新的 Perl 版本

假设你想尝试最新的开发版本(撰写本文时为 5.27.11)。首先,你需要安装包:

perlbrew install 5.27.11

切换 Perl 版本

现在你已经安装了新版本,你可以将它用于该 shell:

perlbrew use 5.27.11

或者你可以将其设置为你帐户的默认 Perl 版本(假设你按照 perlbrew init 的输出设置了你的配置文件):

perlbrew switch 5.27.11

运行单个脚本

你也可以用特定版本的 Perl 运行单个命令:

perlberew exec 5.27.11 myscript.pl

或者,你可以针对所有已安装的版本运行命令。如果你想针对各种版本运行测试,这尤其方便。在这种情况下,请指定版本为 perl

plperlbrew exec perl myscriptpl

安装 CPAN 模块

如果你想安装 CPAN 模块,cpanm 包是一个易于使用的界面,可以很好地与 Perlbrew 一起使用。用下面命令安装它:

perlbrew install-cpanm

然后,你可以使用 cpanm 命令安装 CPAN 模块:

cpanm CGI::simple

但是等下,还有更多!

本文介绍了基本的 Perlbrew 用法。还有更多功能和选项可供选择。从查看 perlbrew help 的输出开始,或查看App::perlbrew 文档。你还喜欢 Perlbrew 的其他什么功能?让我们在评论中知道。


via: https://opensource.com/article/18/7/perlbrew

作者:Ben Cotton 选题:lujun9972 译者:geekpi 校对:wxy

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

3 个可以使你的 Python 代码更优雅、可读、直观和易于维护的工具。

Python 提供了一组独特的工具和语言特性来使你的代码更加优雅、可读和直观。为正确的问题选择合适的工具,你的代码将更易于维护。在本文中,我们将研究其中的三个工具:魔术方法、迭代器和生成器,以及方法魔术。

魔术方法

魔术方法可以看作是 Python 的管道。它们被称为“底层”方法,用于某些内置的方法、符号和操作。你可能熟悉的常见魔术方法是 __init__(),当我们想要初始化一个类的新实例时,它会被调用。

你可能已经看过其他常见的魔术方法,如 __str____repr__。Python 中有一整套魔术方法,通过实现其中的一些方法,我们可以修改一个对象的行为,甚至使其行为类似于内置数据类型,例如数字、列表或字典。

让我们创建一个 Money 类来示例:

class Money:

    currency_rates = {
        '$': 1,
        '€': 0.88,
    }

    def __init__(self, symbol, amount):
        self.symbol = symbol
        self.amount = amount

    def __repr__(self):
        return '%s%.2f' % (self.symbol, self.amount)

    def convert(self, other):
        """ Convert other amount to our currency """
        new_amount = (
            other.amount / self.currency_rates[other.symbol]
            * self.currency_rates[self.symbol])

        return Money(self.symbol, new_amount)

该类定义为给定的货币符号和汇率定义了一个货币汇率,指定了一个初始化器(也称为构造函数),并实现 __repr__,因此当我们打印这个类时,我们会看到一个友好的表示,例如 $2.00 ,这是一个带有货币符号和金额的 Money('$', 2.00) 实例。最重要的是,它定义了一种方法,允许你使用不同的汇率在不同的货币之间进行转换。

打开 Python shell,假设我们已经定义了使用两种不同货币的食品的成本,如下所示:

>>> soda_cost = Money('$', 5.25)
>>> soda_cost
    $5.25

>>> pizza_cost = Money('€', 7.99)
>>> pizza_cost
    €7.99

我们可以使用魔术方法使得这个类的实例之间可以相互交互。假设我们希望能够将这个类的两个实例一起加在一起,即使它们是不同的货币。为了实现这一点,我们可以在 Money 类上实现 __add__ 这个魔术方法:

class Money:

    # ... previously defined methods ...

    def __add__(self, other):
        """ Add 2 Money instances using '+' """
        new_amount = self.amount + self.convert(other).amount
        return Money(self.symbol, new_amount)

现在我们可以以非常直观的方式使用这个类:

>>> soda_cost = Money('$', 5.25)
>>> pizza_cost = Money('€', 7.99)
>>> soda_cost + pizza_cost
    $14.33
>>> pizza_cost + soda_cost
    €12.61

当我们将两个实例加在一起时,我们得到以第一个定义的货币符号所表示的结果。所有的转换都是在底层无缝完成的。如果我们想的话,我们也可以为减法实现 __sub__,为乘法实现 __mul__ 等等。阅读模拟数字类型魔术方法指南来获得更多信息。

我们学习到 __add__ 映射到内置运算符 +。其他魔术方法可以映射到像 [] 这样的符号。例如,在字典中通过索引或键来获得一项,其实是使用了 __getitem__ 方法:

>>> d = {'one': 1, 'two': 2}
>>> d['two']
2
>>> d.__getitem__('two')
2

一些魔术方法甚至映射到内置函数,例如 __len__() 映射到 len()

class Alphabet:
    letters = 'ABCDEFGHIJKLMNOPQRSTUVWXYZ'

    def __len__(self):
        return len(self.letters)

>>> my_alphabet = Alphabet()
>>> len(my_alphabet)
    26

自定义迭代器

对于新的和经验丰富的 Python 开发者来说,自定义迭代器是一个非常强大的但令人迷惑的主题。

许多内置类型,例如列表、集合和字典,已经实现了允许它们在底层迭代的协议。这使我们可以轻松地遍历它们。

>>> for food in ['Pizza', 'Fries']:

         print(food + '. Yum!')

Pizza. Yum!
Fries. Yum!

我们如何迭代我们自己的自定义类?首先,让我们来澄清一些术语。

  • 要成为一个可迭代对象,一个类需要实现 __iter__()
  • __iter__() 方法需要返回一个迭代器
  • 要成为一个迭代器,一个类需要实现 __next__()(或在 Python 2中是 next()),当没有更多的项要迭代时,必须抛出一个 StopIteration 异常。

呼!这听起来很复杂,但是一旦你记住了这些基本概念,你就可以在任何时候进行迭代。

我们什么时候想使用自定义迭代器?让我们想象一个场景,我们有一个 Server 实例在不同的端口上运行不同的服务,如 httpssh。其中一些服务处于 active 状态,而其他服务则处于 inactive 状态。

class Server:

    services = [
        {'active': False, 'protocol': 'ftp', 'port': 21},
        {'active': True, 'protocol': 'ssh', 'port': 22},
        {'active': True, 'protocol': 'http', 'port': 80},
    ]

当我们遍历 Server 实例时,我们只想遍历那些处于 active 的服务。让我们创建一个 IterableServer 类:

class IterableServer:
    def __init__(self):
        self.current_pos = 0
    def __next__(self):
        pass  # TODO: 实现并记得抛出 StopIteration

首先,我们将当前位置初始化为 0。然后,我们定义一个 __next__() 方法来返回下一项。我们还将确保在没有更多项返回时抛出 StopIteration。到目前为止都很好!现在,让我们实现这个 __next__() 方法。

class IterableServer:
    def __init__(self):
        self.current_pos = 0.  # 我们初始化当前位置为 0
    def __iter__(self):  # 我们可以在这里返回 self,因为实现了 __next__
        return self
    def __next__(self):
        while self.current_pos < len(self.services):
            service = self.services[self.current_pos]
            self.current_pos += 1
            if service['active']:
                return service['protocol'], service['port']
        raise StopIteration
    next = __next__  # 可选的 Python2 兼容性

我们对列表中的服务进行遍历,而当前的位置小于服务的个数,但只有在服务处于活动状态时才返回。一旦我们遍历完服务,就会抛出一个 StopIteration 异常。

因为我们实现了 __next__() 方法,当它耗尽时,它会抛出 StopIteration。我们可以从 __iter__() 返回 self,因为 IterableServer 类遵循 iterable 协议。

现在我们可以遍历一个 IterableServer 实例,这将允许我们查看每个处于活动的服务,如下所示:

>>> for protocol, port in IterableServer():

        print('service %s is running on port %d' % (protocol, port))

service ssh is running on port 22

service http is running on port 21

太棒了,但我们可以做得更好!在这样类似的实例中,我们的迭代器不需要维护大量的状态,我们可以简化代码并使用 generator(生成器) 来代替。

class Server:
    services = [
        {'active': False, 'protocol': 'ftp', 'port': 21},
        {'active': True, 'protocol': 'ssh', 'port': 22},
        {'active': True, 'protocol': 'http', 'port': 21},
    ]
    def __iter__(self):
        for service in self.services:
            if service['active']:
                yield service['protocol'], service['port']

yield 关键字到底是什么?在定义生成器函数时使用 yield。这有点像 return,虽然 return 在返回值后退出函数,但 yield 会暂停执行直到下次调用它。这允许你的生成器的功能在它恢复之前保持状态。查看 yield 的文档以了解更多信息。使用生成器,我们不必通过记住我们的位置来手动维护状态。生成器只知道两件事:它现在需要做什么以及计算下一个项目需要做什么。一旦我们到达执行点,即 yield 不再被调用,我们就知道停止迭代。

这是因为一些内置的 Python 魔法。在 Python 关于 __iter__() 的文档中我们可以看到,如果 __iter__() 是作为一个生成器实现的,它将自动返回一个迭代器对象,该对象提供 __iter__()__next__() 方法。阅读这篇很棒的文章,深入了解迭代器,可迭代对象和生成器

方法魔法

由于其独特的方面,Python 提供了一些有趣的方法魔法作为语言的一部分。

其中一个例子是别名功能。因为函数只是对象,所以我们可以将它们赋值给多个变量。例如:

>>> def foo():
       return 'foo'
>>> foo()
'foo'
>>> bar = foo
>>> bar()
'foo'

我们稍后会看到它的作用。

Python 提供了一个方便的内置函数称为 getattr(),它接受 object, name, default 参数并在 object 上返回属性 name。这种编程方式允许我们访问实例变量和方法。例如:

>>> class Dog:
        sound = 'Bark'
        def speak(self):
            print(self.sound + '!', self.sound + '!')

>>> fido = Dog()

>>> fido.sound
'Bark'
>>> getattr(fido, 'sound')
'Bark'

>>> fido.speak
<bound method Dog.speak of <__main__.Dog object at 0x102db8828>>
>>> getattr(fido, 'speak')
<bound method Dog.speak of <__main__.Dog object at 0x102db8828>>


>>> fido.speak()
Bark! Bark!
>>> speak_method = getattr(fido, 'speak')
>>> speak_method()
Bark! Bark!

这是一个很酷的技巧,但是我们如何在实际中使用 getattr 呢?让我们看一个例子,我们编写一个小型命令行工具来动态处理命令。

class Operations:
    def say_hi(self, name):
        print('Hello,', name)
    def say_bye(self, name):
        print ('Goodbye,', name)
    def default(self, arg):
        print ('This operation is not supported.')

if __name__ == '__main__':
    operations = Operations()
    # 假设我们做了错误处理
    command, argument = input('> ').split()
    func_to_call = getattr(operations, command, operations.default)
    func_to_call(argument)

脚本的输出是:

$ python getattr.py
> say_hi Nina
Hello, Nina
> blah blah
This operation is not supported.

接下来,我们来看看 partial。例如,functool.partial(func, *args, **kwargs) 允许你返回一个新的 partial 对象,它的行为类似 func,参数是 argskwargs。如果传入更多的 args,它们会被附加到 args。如果传入更多的 kwargs,它们会扩展并覆盖 kwargs。让我们通过一个简短的例子来看看:

>>> from functools import partial
>>> basetwo = partial(int, base=2)
>>> basetwo
<functools.partial object at 0x1085a09f0>
>>> basetwo('10010')
18

# 这等同于
>>> int('10010', base=2)

让我们看看在我喜欢的一个名为 agithub 的库中的一些示例代码中,这个方法魔术是如何结合在一起的,这是一个(名字起得很 low 的) REST API 客户端,它具有透明的语法,允许你以最小的配置快速构建任何 REST API 原型(不仅仅是 GitHub)。我发现这个项目很有趣,因为它非常强大,但只有大约 400 行 Python 代码。你可以在大约 30 行配置代码中添加对任何 REST API 的支持。agithub 知道协议所需的一切(RESTHTTPTCP),但它不考虑上游 API。让我们深入到它的实现中。

以下是我们如何为 GitHub API 和任何其他相关连接属性定义端点 URL 的简化版本。在这里查看完整代码

class GitHub(API):
    def __init__(self, token=None, *args, **kwargs):
        props = ConnectionProperties(api_url = kwargs.pop('api_url', 'api.github.com'))
        self.setClient(Client(*args, **kwargs))
        self.setConnectionProperties(props)

然后,一旦配置了访问令牌,就可以开始使用 GitHub API

>>> gh = GitHub('token')
>>> status, data = gh.user.repos.get(visibility='public', sort='created')
>>> # ^ 映射到 GET /user/repos
>>> data
... ['tweeter', 'snipey', '...']

请注意,你要确保 URL 拼写正确,因为我们没有验证 URL。如果 URL 不存在或出现了其他任何错误,将返回 API 抛出的错误。那么,这一切是如何运作的呢?让我们找出答案。首先,我们将查看一个 API的简化示例:

class API:
    # ... other methods ...
    def __getattr__(self, key):
        return IncompleteRequest(self.client).__getattr__(key)
    __getitem__ = __getattr__

API 类上的每次调用都会调用 IncompleteRequest作为指定的 key

class IncompleteRequest:
    # ... other methods ...
    def __getattr__(self, key):
        if key in self.client.http_methods:
            htmlMethod = getattr(self.client, key)
            return partial(htmlMethod, url=self.url)
        else:
            self.url += '/' + str(key)
            return self
    __getitem__ = __getattr__

class Client:
    http_methods = ('get')  # 还有 post, put, patch 等等。
    def get(self, url, headers={}, **params):
        return self.request('GET', url, None, headers)

如果最后一次调用不是 HTTP 方法(如 getpost 等),则返回带有附加路径的 IncompleteRequest。否则,它从Client获取 HTTP 方法对应的正确函数,并返回 partial

如果我们给出一个不存在的路径会发生什么?

>>> status, data = this.path.doesnt.exist.get()
>>> status
... 404

因为 __getattr__ 别名为 __getitem__

>>> owner, repo = 'nnja', 'tweeter'
>>> status, data = gh.repos[owner][repo].pulls.get()
>>> # ^ Maps to GET /repos/nnja/tweeter/pulls
>>> data
.... # {....}

这真心是一些方法魔术!

了解更多

Python 提供了大量工具,使你的代码更优雅,更易于阅读和理解。挑战在于找到合适的工具来完成工作,但我希望本文为你的工具箱添加了一些新工具。而且,如果你想更进一步,你可以在我的博客 nnja.io 上阅读有关装饰器、上下文管理器、上下文生成器和命名元组的内容。随着你成为一名更好的 Python 开发人员,我鼓励你到那里阅读一些设计良好的项目的源代码。RequestsFlask 是两个很好的起步的代码库。


via: https://opensource.com/article/18/4/elegant-solutions-everyday-python-problems

作者:Nina Zakharenko 选题:lujun9972 译者:MjSeven 校对:wxy

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

使用 Linux 中的 dd 工具安全、可靠地制作一个驱动器、分区和文件系统的完整镜像。

这篇文章节选自 Manning 出版社出版的图书 Linux in Action的第 4 章。

你是否正在从一个即将损坏的存储驱动器挽救数据,或者要把本地归档进行远程备份,或者要把一个别处的活动分区做个完整的副本,那么你需要懂得如何安全而可靠的复制驱动器和文件系统。幸运的是,dd 是一个可以使用的简单而又功能强大的镜像复制命令,从现在到未来很长的时间内,也许直到永远都不会出现比 dd 更好的工具了。

对驱动器和分区做个完整的副本

仔细研究后,你会发现你可以使用 dd 做各种任务,但是它最重要的功能是处理磁盘分区。当然,你可以使用 tar 命令或者 scp 命令从一台计算机复制整个文件系统的文件,然后把这些文件原样粘贴在另一台刚刚安装好 Linux 操作系统的计算机中。但是,因为那些文件系统归档不是完整的映像文件,所以在复制文件的过程中需要计算机操作系统的运行作为基础。

另一方面,使用 dd 可以对任何数字信息完美的进行逐个字节的镜像。但是不论何时何地,当你要对分区进行操作时,我要告诉你早期的 Unix 管理员曾开过这样的玩笑:“ dd 的意思是 磁盘毁灭者 disk destroyer ”(LCTT 译注:dd 原意是 磁盘复制 disk dump )。 在使用 dd 命令的时候,如果你输入了哪怕是一个字母,也可能立即永久性的擦除掉整个磁盘驱动器里的所有重要的数据。因此,一定要注意命令的拼写格式规范。

记住: 在按下回车键执行 dd 命令之前,暂时停下来仔细的认真思考一下。

dd 命令的基本操作

现在你已经得到了适当的提醒,我们将从简单的事情开始。假设你要对代号为 /dev/sda 的整个磁盘数据创建精确的映像,你已经插入了一块空的磁盘驱动器 (理想情况下具有与代号为 /dev/sda 的磁盘驱动器相同的容量)。语法很简单: if= 定义源驱动器,of= 定义你要将数据保存到的文件或位置:

# dd if=/dev/sda of=/dev/sdb

接下来的例子将要对 /dev/sda 驱动器创建一个 .img 的映像文件,然后把该文件保存的你的用户帐号家目录:

# dd if=/dev/sda of=/home/username/sdadisk.img

上面的命令针对整个驱动器创建映像文件,你也可以针对驱动器上的单个分区进行操作。下面的例子针对驱动器的单个分区进行操作,同时使用了一个 bs 参数用于设置单次拷贝的字节数量 (此例中是 4096)。设定 bs 参数值可能会影响 dd 命令的整体操作速度,该参数的理想设置取决于你的硬件配置和其它考虑。

# dd if=/dev/sda2 of=/home/username/partition2.img bs=4096

数据的恢复非常简单:通过颠倒 ifof 参数可以有效的完成任务。在此例中,if= 使用你要恢复的映像,of= 使用你想要写入映像的目标驱动器:

# dd if=sdadisk.img of=/dev/sdb

你也可以在一条命令中同时完成创建和拷贝任务。下面的例子中将使用 SSH 从远程驱动器创建一个压缩的映像文件,并把该文件保存到你的本地计算机中:

# ssh [email protected] "dd if=/dev/sda | gzip -1 -" | dd of=backup.gz

你应该经常测试你的归档,确保它们可正常使用。如果它是你创建的启动驱动器,将它粘贴到计算机中,看看它是否能够按预期启动。如果它是普通分区的数据,挂载该分区,确保文件都存在而且可以正常的访问。

使用 dd 擦除磁盘数据

多年以前,我的一个负责政府海外大使馆安全的朋友曾经告诉我,在他当时在任的时候, 政府会给每一个大使馆提供一个官方版的锤子。为什么呢? 一旦大使馆设施可能被不友善的人员侵占,就会使用这个锤子毁坏所有的硬盘.

为什么要那样做?为什么不是删除数据就好了?你在开玩笑,对吧?所有人都知道从存储设备中删除包含敏感信息的文件实际上并没有真正移除这些数据。除非使用锤子彻底的毁坏这些存储介质,否则,只要有足够的时间和动机, 几乎所有的内容都可以从几乎任何数字存储介质重新获取。

但是,你可以使用 dd 命令让坏人非常难以获得你的旧数据。这个命令需要花费一些时间在 /dev/sda1 分区的每个扇区写入数百万个 0(LCTT 译注:是指 0x0 字节,意即 NUL ,而不是数字 0 ):

# dd if=/dev/zero of=/dev/sda1

还有更好的方法。通过使用 /dev/urandom 作为源文件,你可以在磁盘上写入随机字符:

# dd if=/dev/urandom of=/dev/sda1

监控 dd 的操作

由于磁盘或磁盘分区的归档可能需要很长的时间,因此你可能需要在命令中添加进度查看器。安装管道查看器(在 Ubuntu 系统上安装命令为 sudo apt install pv),然后把 pv 命令和 dd 命令结合在一起。使用 pv,最终的命令是这样的:

# dd if=/dev/urandom | pv | dd of=/dev/sda1

4,14MB 0:00:05 [ 98kB/s] [      <=>                  ]

想要推迟备份和磁盘管理工作?有了 dd 工具,你不会有太多的借口。它真的非常简单,但是要小心。祝你好运!


via:https://opensource.com/article/18/7/how-use-dd-linux

作者:David Clinton 选题:lujun9972 译者:SunWave 校对:wxy

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

将用户添加到现有组是 Linux 管理员的常规活动之一。这是一些在大环境中工作的管理员的日常活动。

甚至我会因为业务需求而在我的环境中每天都在进行这样的活动。它是帮助你识别环境中现有组的重要命令之一。

此外,这些命令还可以帮助你识别用户所属的组。所有用户都列在 /etc/passwd 中,组列在 /etc/group 中。

无论我们使用什么命令,都将从这些文件中获取信息。此外,每个命令都有其独特的功能,可帮助用户单独获取所需的信息。

什么是 /etc/passwd?

/etc/passwd 是一个文本文件,其中包含登录 Linux 系统所必需的每个用户信息。它维护有用的用户信息,如用户名、密码、用户 ID、组 ID、用户 ID 信息、家目录和 shell。passwd 每行包含了用户的详细信息,共有如上所述的 7 个字段。

$ grep "daygeek" /etc/passwd
daygeek:x:1000:1000:daygeek,,,:/home/daygeek:/bin/bash

什么是 /etc/group?

/etc/group 是一个文本文件,用于定义用户所属的组。我们可以将多个用户添加到单个组中。它允许用户访问其他用户文件和文件夹,因为 Linux 权限分为三类:用户、组和其他。它维护有关组的有用信息,例如组名、组密码,组 ID(GID)和成员列表。每个都在一个单独的行。组文件每行包含了每个组的详细信息,共有 4 个如上所述字段。

这可以通过使用以下方法来执行。

  • groups: 显示一个组的所有成员。
  • id: 打印指定用户名的用户和组信息。
  • lid: 显示用户的组或组的用户。
  • getent: 从 Name Service Switch 库中获取条目。
  • grep: 代表“ 全局正则表达式打印 global regular expression print ”,它能打印匹配的模式。

什么是 groups 命令?

groups 命令打印每个给定用户名的主要组和任何补充组的名称。

$ groups daygeek
daygeek : daygeek adm cdrom sudo dip plugdev lpadmin sambashare

如果要检查与当前用户关联的组列表。只需运行 groups 命令,无需带任何用户名。

$ groups
daygeek adm cdrom sudo dip plugdev lpadmin sambashare

什么是 id 命令?

id 代表 “ 身份 identity ”。它打印真实有效的用户和组 ID。打印指定用户或当前用户的用户和组信息。

$ id daygeek
uid=1000(daygeek) gid=1000(daygeek) groups=1000(daygeek),4(adm),24(cdrom),27(sudo),30(dip),46(plugdev),118(lpadmin),128(sambashare)

如果要检查与当前用户关联的组列表。只运行 id 命令,无需带任何用户名。

$ id
uid=1000(daygeek) gid=1000(daygeek) groups=1000(daygeek),4(adm),24(cdrom),27(sudo),30(dip),46(plugdev),118(lpadmin),128(sambashare)

什么是 lid 命令?

它显示用户的组或组的用户。显示有关包含用户名的组或组名称中包含的用户的信息。此命令需要管理员权限。

$ sudo lid daygeek
 adm(gid=4)
 cdrom(gid=24)
 sudo(gid=27)
 dip(gid=30)
 plugdev(gid=46)
 lpadmin(gid=108)
 daygeek(gid=1000)
 sambashare(gid=124)

什么是 getent 命令?

getent 命令显示 Name Service Switch 库支持的数据库中的条目,它们在 /etc/nsswitch.conf 中配置。

$ getent group | grep daygeek
adm:x:4:syslog,daygeek
cdrom:x:24:daygeek
sudo:x:27:daygeek
dip:x:30:daygeek
plugdev:x:46:daygeek
lpadmin:x:118:daygeek
daygeek:x:1000:
sambashare:x:128:daygeek

如果你只想打印关联的组名称,请在上面的命令中使用 awk

$ getent group | grep daygeek | awk -F: '{print $1}'
adm
cdrom
sudo
dip
plugdev
lpadmin
daygeek
sambashare

运行以下命令仅打印主群组信息。

$ getent group daygeek
daygeek:x:1000:

什么是 grep 命令?

grep 代表 “ 全局正则表达式打印 global regular expression print ”,它能打印文件匹配的模式。

$ grep "daygeek" /etc/group
adm:x:4:syslog,daygeek
cdrom:x:24:daygeek
sudo:x:27:daygeek
dip:x:30:daygeek
plugdev:x:46:daygeek
lpadmin:x:118:daygeek
daygeek:x:1000:
sambashare:x:128:daygeek

如果你只想打印关联的组名称,请在上面的命令中使用 awk

$ grep "daygeek" /etc/group | awk -F: '{print $1}'
adm
cdrom
sudo
dip
plugdev
lpadmin
daygeek
sambashare

via: https://www.2daygeek.com/how-to-check-which-groups-a-user-belongs-to-on-linux/

作者:Prakash Subramanian 选题:lujun9972 译者:geekpi 校对:wxy

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

你也许用过调速器检查过你的代码,但你知道它们是如何做到的吗?

调试器是大多数(即使不是每个)开发人员在软件工程职业生涯中至少使用过一次的那些软件之一,但是你们中有多少人知道它们到底是如何工作的?我在悉尼 linux.conf.au 2018 的演讲中,将讨论从头开始编写调试器……使用 Rust

在本文中,术语 调试器 debugger 跟踪器 tracer 可以互换。 “ 被跟踪者 Tracee ”是指正在被跟踪器跟踪的进程。

ptrace 系统调用

大多数调试器严重依赖称为 ptrace(2) 的系统调用,其原型如下:

long ptrace(enum __ptrace_request request, pid_t pid, void *addr, void *data);

这是一个可以操纵进程几乎所有方面的系统调用;但是,在调试器可以连接到一个进程之前,“被跟踪者”必须以请求 PTRACE_TRACEME 调用 ptrace。这告诉 Linux,父进程通过 ptrace 连接到这个进程是合法的。但是……我们如何强制一个进程调用 ptrace?很简单!fork/execve 提供了在 fork 之后但在被跟踪者真正开始使用 execve 之前调用 ptrace 的简单方法。很方便地,fork 还会返回被跟踪者的 pid,这是后面使用 ptrace 所必需的。

现在被跟踪者可以被调试器追踪,重要的变化发生了:

  • 每当一个信号被传送到被跟踪者时,它就会停止,并且一个可以被 wait 系列的系统调用捕获的等待事件被传送给跟踪器。
  • 每个 execve 系统调用都会导致 SIGTRAP 被传递给被跟踪者。(与之前的项目相结合,这意味着被跟踪者在一个 execve 完全发生之前停止。)

这意味着,一旦我们发出 PTRACE_TRACEME 请求并调用 execve 系统调用来实际在被跟踪者(进程上下文)中启动程序时,被跟踪者将立即停止,因为 execve 会传递一个 SIGTRAP,并且会被跟踪器中的等待事件捕获。我们如何继续?正如人们所期望的那样,ptrace 有大量的请求可以用来告诉被跟踪者可以继续:

  • PTRACE_CONT:这是最简单的。 被跟踪者运行,直到它接收到一个信号,此时等待事件被传递给跟踪器。这是最常见的实现真实世界调试器的“继续直至断点”和“永远继续”选项的方式。断点将在下面介绍。
  • PTRACE_SYSCALL:与 PTRACE_CONT 非常相似,但在进入系统调用之前以及在系统调用返回到用户空间之前停止。它可以与其他请求(我们将在本文后面介绍)结合使用来监视和修改系统调用的参数或返回值。系统调用追踪程序 strace 很大程度上使用这个请求来获知进程发起了哪些系统调用。
  • PTRACE_SINGLESTEP:这个很好理解。如果您之前使用过调试器(你会知道),此请求会执行下一条指令,然后立即停止。

我们可以通过各种各样的请求停止进程,但我们如何获得被调试者的状态?进程的状态大多是通过其寄存器捕获的,所以当然 ptrace 有一个请求来获得(或修改)寄存器:

  • PTRACE_GETREGS:这个请求将给出被跟踪者刚刚被停止时的寄存器的状态。
  • PTRACE_SETREGS:如果跟踪器之前通过调用 PTRACE_GETREGS 得到了寄存器的值,它可以在参数结构中修改相应寄存器的值,并使用 PTRACE_SETREGS 将寄存器设为新值。
  • PTRACE_PEEKUSERPTRACE_POKEUSER:这些允许从被跟踪者的 USER 区读取信息,这里保存了寄存器和其他有用的信息。 这可以用来修改单一寄存器,而避免使用更重的 PTRACE_{GET,SET}REGS 请求。

在调试器仅仅修改寄存器是不够的。调试器有时需要读取一部分内存,甚至对其进行修改。GDB 可以使用 print 得到一个内存位置或变量的值。ptrace 通过下面的方法实现这个功能:

  • PTRACE_PEEKTEXTPTRACE_POKETEXT:这些允许读取和写入被跟踪者地址空间中的一个字。当然,使用这个功能时被跟踪者要被暂停。

真实世界的调试器也有类似断点和观察点的功能。 在接下来的部分中,我将深入体系结构对调试器支持的细节。为了清晰和简洁,本文将只考虑 x86。

体系结构的支持

ptrace 很酷,但它是如何工作? 在前面的部分中,我们已经看到 ptrace 跟信号有很大关系:SIGTRAP 可以在单步跟踪、execve 之前以及系统调用前后被传送。信号可以通过一些方式产生,但我们将研究两个具体的例子,以展示信号可以被调试器用来在给定的位置停止程序(有效地创建一个断点!):

  • 未定义的指令:当一个进程尝试执行一个未定义的指令,CPU 将产生一个异常。此异常通过 CPU 中断处理,内核中相应的中断处理程序被调用。这将导致一个 SIGILL 信号被发送给进程。 这依次导致进程被停止,跟踪器通过一个等待事件被通知,然后它可以决定后面做什么。在 x86 上,指令 ud2 被确保始终是未定义的。
  • 调试中断:前面的方法的问题是,ud2 指令需要占用两个字节的机器码。存在一条特殊的单字节指令能够触发一个中断,它是 int $3,机器码是 0xCC。 当该中断发出时,内核向进程发送一个 SIGTRAP,如前所述,跟踪器被通知。

这很好,但如何我们才能胁迫被跟踪者执行这些指令? 这很简单:利用 ptracePTRACE_POKETEXT 请求,它可以覆盖内存中的一个字。 调试器将使用 PTRACE_PEEKTEXT 读取该位置原来的值并替换为 0xCC ,然后在其内部状态中记录该处原来的值,以及它是一个断点的事实。 下次被跟踪者执行到该位置时,它将被通过 SIGTRAP 信号自动停止。 然后调试器的最终用户可以决定如何继续(例如,检查寄存器)。

好吧,我们已经讲过了断点,那观察点呢? 当一个特定的内存位置被读或写,调试器如何停止程序? 当然你不可能为了能够读或写内存而去把每一个指令都覆盖为 int $3。有一组调试寄存器为了更有效的满足这个目的而被设计出来:

  • DR0DR3:这些寄存器中的每个都包含一个地址(内存位置),调试器因为某种原因希望被跟踪者在那些地址那里停止。 其原因以掩码方式被设定在 DR7 寄存器中。
  • DR4DR5:这些分别是 DR6DR7 过时的别名。
  • DR6:调试状态。包含有关 DR0DR3 中的哪个寄存器导致调试异常被引发的信息。这被 Linux 用来计算与 SIGTRAP 信号一起传递给被跟踪者的信息。
  • DR7:调试控制。通过使用这些寄存器中的位,调试器可以控制如何解释 DR0DR3 中指定的地址。位掩码控制监视点的尺寸(监视1、2、4 或 8 个字节)以及是否在执行、读取、写入时引发异常,或在读取或写入时引发异常。

由于调试寄存器是进程的 USER 区域的一部分,调试器可以使用 PTRACE_POKEUSER 将值写入调试寄存器。调试寄存器只与特定进程相关,因此在进程抢占并重新获得 CPU 控制权之前,调试寄存器会被恢复。

冰山一角

我们已经浏览了一个调试器的“冰山”:我们已经介绍了 ptrace,了解了它的一些功能,然后我们看到了 ptrace 是如何实现的。 ptrace 的某些部分可以用软件实现,但其它部分必须用硬件来实现,否则实现代价会非常高甚至无法实现。

当然有很多我们没有涉及。例如“调试器如何知道变量在内存中的位置?”等问题由于空间和时间限制而尚未解答,但我希望你从本文中学到了一些东西;如果它激起你的兴趣,网上有足够的资源可以了解更多。

想要了解更多,请查看 linux.conf.au 中 Levente Kurusa 的演讲 Let's Write a Debugger!,于一月 22-26 日在悉尼举办。


via: https://opensource.com/article/18/1/how-debuggers-really-work

作者:Levente Kurusa 译者:stephenxs 校对:wxy

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