Skip to content Skip to main navigation Skip to footer

Linux

程序员应该避免的 5 种代码注释

你有没有这样的经历:别人审查过你的代码之后给出的注释,你认为是没有必要的?注释代码是为了提高代码的可读性,目的是为了能让其他人更容易理解你的代码。

我特别讨厌这5种注释类型以及制造它们的程序员。希望你不是其中之一。

程序员应该避免的 5 种代码注释
程序员应该避免的 5 种代码注释

1.自以为很了不得的程序员

public class Program
{
    static void Main(string[] args)
    {
        string message = "Hello World!";  // 07/24/2010 Bob
        Console.WriteLine(message); // 07/24/2010 Bob
        message = "I am so proud of this code!"; // 07/24/2010 Bob
        Console.WriteLine(message); // 07/24/2010 Bob
    }
}

这个程序员自认为写了一段很了不得的代码,所以觉得有必要用自己的名字对每行代码进行标记。实施版本控制系统(VCS)能实现对代码变更的问责,但是也不会这么明显知道谁应对此负责。

2.过时的程序员

public class Program
{
    static void Main(string[] args)
    {
        /* This block of code is no longer needed
         * because we found out that Y2K was a hoax
         * and our systems did not roll over to 1/1/1900 */
        //DateTime today = DateTime.Today;
        //if (today == new DateTime(1900, 1, 1))
        //{
        //    today = today.AddYears(100);
        //    string message = "The date has been fixed for Y2K.";
        //    Console.WriteLine(message);
        //}
    }
}

如果一段代码已不再使用(即过时),那就删除它——不要浪费时间给这些代码写注释。此外,如果你需要复制这段被删除的代码,别忘了还有版本控制系统,你完全可以从早期的版本中恢复代码。

3.多此一举的程序员

public class Program
{
    static void Main(string[] args)
    {
        /* This is a for loop that prints the
         * words "I Rule!" to the console screen
         * 1 million times, each on its own line. It
         * accomplishes this by starting at 0 and
         * incrementing by 1. If the value of the
         * counter equals 1 million the for loop
         * stops executing.*/
        for (int i = 0; i < 1000000; i++)
        {
            Console.WriteLine("I Rule!");
        }
    }
}

我们都知道基础的编程逻辑是如何工作的——所以你不需要多此一举来解释这些显而易见的工作原理,虽然说你解释得很happy,但这只是在浪费时间和空间。

4.爱讲故事的程序员

public class Program
{
    static void Main(string[] args)
    {
       /* I discussed with Jim from Sales over coffee
        * at the Starbucks on main street one day and he
        * told me that Sales Reps receive commission
        * based upon the following structure.
        * Friday: 25%
        * Wednesday: 15%
        * All Other Days: 5%
        * Did I mention that I ordered the Caramel Latte with
        * a double shot of Espresso?
       */
        double price = 5.00;
        double commissionRate;
        double commission;
        if (DateTime.Today.DayOfWeek == DayOfWeek.Friday)
        {
            commissionRate = .25;
        }
        else if (DateTime.Today.DayOfWeek == DayOfWeek.Wednesday)
        {
            commissionRate = .15;
        }
        else
        {
            commissionRate = .05;
        }
        commission = price * commissionRate;
    }
}

如果你一定要在注释里提及需求,那么不要涉及别人的名字。销售部门的Jim可能会离开公司,而且很有可能大多数程序员根本不知道这是何许人也。不要在注释里提及不相干的事实。

5.“以后再做”的程序员

public class Program
{
    static void Main(string[] args)
    {
       //TODO: I need to fix this someday - 07/24/1995 Bob
       /* I know this error message is hard coded and
        * I am relying on a Contains function, but
        * someday I will make this code print a
        * meaningful error message and exit gracefully.
        * I just don't have the time right now.
       */
       string message = "An error has occurred";
       if(message.Contains("error"))
       {
           throw new Exception(message);
       }
    }
}

这种类型的注释包含了上面所有其他类型。如果是在项目的初始开发阶段,这种待做注释是非常有用的,但如果是在几年后的产品代码——那就会出问题了。如果有什么需要修复的,立马解决,不要把它搁置一边,“以后再做”。

如果你也常常犯这样的注释错误,如果你想了解注释的最佳做法,我建议你阅读类似于Steve McConnell写的《Code Complete》这样的好书。

RHCSA 系列(十三): 在 RHEL 7 中使用 SELinux 进行强制访问控制

在本系列的前面几篇文章中,我们已经详细地探索了至少两种访问控制方法:标准的 ugo/rwx 权限(RHCSA 系列(三): 如何管理 RHEL7 的用户和组) 和访问控制列表(RHCSA 系列(七): 使用 ACL(访问控制列表) 和挂载 Samba/NFS 共享)。

RHCSA 系列(十三): 在 RHEL 7 中使用 SELinux 进行强制访问控制
RHCSA 系列(十三): 在 RHEL 7 中使用 SELinux 进行强制访问控制

RHCSA 认证:SELinux 精要和控制文件系统的访问

尽管作为第一级别的权限和访问控制机制是必要的,但它们同样有一些局限,而这些局限则可以由安全增强 LinuxSecurity Enhanced Linux,简称为 SELinux来处理。

这些局限的一种情形是:某个用户可能通过一个泛泛的 chmod 命令将文件或目录暴露出现了安全违例,从而引起访问权限的意外传播。结果,由该用户开启的任意进程可以对属于该用户的文件进行任意的操作,最终一个恶意的或有其它缺陷的软件可能会取得整个系统的 root 级别的访问权限。

考虑到这些局限性,美国国家安全局(NSA) 率先设计出了 SELinux,一种强制的访问控制方法,它根据最小权限模型去限制进程在系统对象(如文件,目录,网络接口等)上的访问或执行其他的操作的能力,而这些限制可以在之后根据需要进行修改。简单来说,系统的每一个元素只给某个功能所需要的那些权限。

在 RHEL 7 中,SELinux 被并入了内核中,且默认情况下以强制模式Enforcing开启。在这篇文章中,我们将简要地介绍有关 SELinux 及其相关操作的基本概念。

SELinux 的模式

SELinux 可以以三种不同的模式运行:

  • 强制模式Enforcing:SELinux 基于其策略规则来拒绝访问,这些规则是用以控制安全引擎的一系列准则;
  • 宽容模式Permissive:SELinux 不会拒绝访问,但对于那些如果运行在强制模式下会被拒绝访问的行为进行记录;
  • 关闭Disabled (不言自明,即 SELinux 没有实际运行).

使用 getenforce 命令可以展示 SELinux 当前所处的模式,而 setenforce 命令(后面跟上一个 1 或 0) 则被用来将当前模式切换到强制模式Enforcing宽容模式Permissive,但只对当前的会话有效。

为了使得在登出和重启后上面的设置还能保持作用,你需要编辑 /etc/selinux/config 文件并将 SELINUX 变量的值设为 enforcing,permissive,disabled 中之一:

# getenforce
# setenforce 0
# getenforce
# setenforce 1
# getenforce
# cat /etc/selinux/config

设置 SELinux 模式

设置 SELinux 模式

通常情况下,你应该使用 setenforce 来在 SELinux 模式间进行切换(从强制模式到宽容模式,或反之),以此来作为你排错的第一步。假如 SELinux 当前被设置为强制模式,而你遇到了某些问题,但当你把 SELinux 切换为宽容模式后问题不再出现了,则你可以确信你遇到了一个 SELinux 权限方面的问题。

SELinux 上下文

一个 SELinux 上下文Context由一个访问控制环境所组成,在这个环境中,决定的做出将基于 SELinux 的用户,角色和类型(和可选的级别):

  • 一个 SELinux 用户是通过将一个常规的 Linux 用户账户映射到一个 SELinux 用户账户来实现的,反过来,在一个会话中,这个 SELinux 用户账户在 SELinux 上下文中被进程所使用,以便能够明确定义它们所允许的角色和级别。
  • 角色的概念是作为域和处于该域中的 SELinux 用户之间的媒介,它定义了 SELinux 可以访问到哪个进程域和哪些文件类型。这将保护您的系统免受提权漏洞的攻击。
  • 类型则定义了一个 SELinux 文件类型或一个 SELinux 进程域。在正常情况下,进程将会被禁止访问其他进程正使用的文件,并禁止对其他进程进行访问。这样只有当一个特定的 SELinux 策略规则允许它访问时,才能够进行访问。

下面就让我们看看这些概念是如何在下面的例子中起作用的。

例 1:改变 sshd 守护进程的默认端口

RHCSA 系列(八): 加固 SSH,设定主机名及启用网络服务 中,我们解释了更改 sshd 所监听的默认端口是加固你的服务器免受外部攻击的首要安全措施。下面,就让我们编辑 /etc/ssh/sshd_config 文件并将端口设置为 9999:

Port 9999

保存更改并重启 sshd:

# systemctl restart sshd
# systemctl status sshd

更改 SSH 的端口

重启 SSH 服务

正如你看到的那样, sshd 启动失败,但为什么会这样呢?

快速检查 /var/log/audit/audit.log 文件会发现 sshd 已经被拒绝在端口 9999 上开启(SELinux 的日志信息包含单词 “AVC”,所以这类信息可以被轻易地与其他信息相区分),因为这个端口是 JBoss 管理服务的保留端口:

# cat /var/log/audit/audit.log | grep AVC | tail -1

查看 SSH 日志

查看 SSH 日志

在这种情况下,你可以像先前解释的那样禁用 SELinux(但请不要这样做!),并尝试重启 sshd,且这种方法能够起效。但是, semanage 应用可以告诉我们在哪些端口上可以开启 sshd 而不会出现任何问题。

运行:

# semanage port -l | grep ssh

便可以得到一个 SELinux 允许 sshd 在哪些端口上监听的列表:

RHCSA 系列(十三): 在 RHEL 7 中使用 SELinux 进行强制访问控制
RHCSA 系列(十三): 在 RHEL 7 中使用 SELinux 进行强制访问控制

Semanage 工具

所以让我们在 /etc/ssh/sshd_config 中将端口更改为 9998 端口,增加这个端口到 sshportt 的上下文,然后重启 sshd 服务:

# semanage port -a -t ssh_port_t -p tcp 9998
# systemctl restart sshd
# systemctl is-active sshd

Semanage 添加端口

semanage 添加端口

如你所见,这次 sshd 服务被成功地开启了。这个例子告诉我们一个事实:SELinux 用它自己的端口类型的内部定义来控制 TCP 端口号。

例 2:允许 httpd 访问 sendmail

这是一个 SELinux 管理一个进程来访问另一个进程的例子。假如在你的 RHEL 7 服务器上,你要为 Apache 配置 mod_security 和 mod_evasive,你需要允许 httpd 访问 sendmail,以便在遭受到 (D)DoS 攻击时能够用邮件来提醒你。在下面的命令中,如果你不想使得更改在重启后仍然生效,请去掉 -P 选项。

# semanage boolean -1 | grep httpd_can_sendmail
# setsebool -P httpd_can_sendmail 1
# semanage boolean -1 | grep httpd_can_sendmail

允许 Apache 发送邮件

允许 Apache 发送邮件

从上面的例子中,你可以知道 SELinux 布尔设定(或者只是布尔值)分别对应于 true 或 false,被嵌入到了 SELinux 策略中。你可以使用 semanage boolean -l 来列出所有的布尔值,也可以管道至 grep 命令以便筛选输出的结果。

例 3:在一个特定目录而非默认目录下提供一个静态站点服务

假设你正使用一个不同于默认目录(/var/www/html)的目录来提供一个静态站点服务,例如 /websites 目录(这种情形会出现在当你把你的网络文件存储在一个共享网络设备上,并需要将它挂载在 /websites 目录时)。

a). 在 /websites 下创建一个 index.html 文件并包含如下的内容:

SELinux test

假如你执行

# ls -lZ /websites/index.html

你将会看到这个 index.html 已经被标记上了 default_t SELinux 类型,而 Apache 不能访问这类文件:

检查 SELinux 文件的权限

检查 SELinux 文件的权限

b). 将 /etc/httpd/conf/httpd.conf 中的 DocumentRoot 改为 /websites,并不要忘了 更新相应的 Directory 块。然后重启 Apache。

c). 浏览 http://,则你应该会得到一个 503 Forbidden 的 HTTP 响应。

d). 接下来,递归地改变 /websites 的标志,将它的标志变为 httpd_sys_content_t 类型,以便赋予 Apache 对这些目录和其内容的只读访问权限:

# semanage fcontext -a -t httpd_sys_content_t "/websites(/.*)?"

e). 最后,应用在 d) 中创建的 SELinux 策略:

# restorecon -R -v /websites

现在重启 Apache 并再次浏览到 http://,则你可以看到被正确展现出来的 html 文件:

确认 Apache 页面

确认 Apache 页面

总结

在本文中,我们详细地介绍了 SELinux 的基础知识。请注意,由于这个主题的广泛性,在单篇文章中做出一个完全详尽的解释是不可能的,但我们相信,在这个指南中列出的基本原则将会对你进一步了解更高级的话题有所帮助,假如你想了解的话。

假如可以,请让我推荐两个必要的资源来入门 SELinux:NSA SELinux 页面针对用户和系统管理员的 RHEL 7 SELinux 指南

假如你有任何的问题或评论,请不要犹豫,让我们知晓吧。


via: http://www.tecmint.com/selinux-essentials-and-control-filesystem-access/

作者:Gabriel Cánepa 译者:FSSlc 校对:wxy

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

八大排序算法的 Python 实现

八大排序算法的 Python 实现
八大排序算法的 Python 实现

1、插入排序

插入排序的基本操作就是将一个数据插入到已经排好序的有序数据中,从而得到一个新的、个数加一的有序数据,算法适用于少量数据的排序,时间复杂度为O(n^2)。是稳定的排序方法。插入算法把要排序的数组分成两部分:第一部分包含了这个数组的所有元素,但将最后一个元素除外(让数组多一个空间才有插入的位置),而第二部分就只包含这一个元素(即待插入元素)。在第一部分排序完成后,再将这个最后元素插入到已排好序的第一部分中。

def insert_sort(lists):
    # 插入排序
    count = len(lists)
    for i in range(1, count):
        key = lists[i]
        j = i - 1
        while j >= 0:
            if lists[j] > key:
                lists[j + 1] = lists[j]
                lists[j] = key
            j -= 1
    return lists

2、希尔排序

希尔排序Shell Sort是插入排序的一种。也称缩小增量排序,是直接插入排序算法的一种更高效的改进版本。希尔排序是非稳定排序算法。该方法因DL.Shell于1959年提出而得名。 希尔排序是把记录按下标的一定增量分组,对每组使用直接插入排序算法排序;随着增量逐渐减少,每组包含的关键词越来越多,当增量减至1时,整个文件恰被分成一组,算法便终止。

def shell_sort(lists):
    # 希尔排序
    count = len(lists)
    step = 2
    group = count / step
    while group > 0:
        for i in range(0, group):
            j = i + group
            while j < count:
                k = j - group
                key = lists[j]
                while k >= 0:
                    if lists[k] > key:
                        lists[k + group] = lists[k]
                        lists[k] = key
                    k -= group
                j += group
        group /= step
    return lists

3、冒泡排序

它重复地走访过要排序的数列,一次比较两个元素,如果他们的顺序错误就把他们交换过来。走访数列的工作是重复地进行直到没有再需要交换,也就是说该数列已经排序完成。

def bubble_sort(lists):
    # 冒泡排序
    count = len(lists)
    for i in range(0, count):
        for j in range(i + 1, count):
            if lists[i] > lists[j]:
                lists[i], lists[j] = lists[j], lists[i]
    return lists

4、快速排序

通过一趟排序将要排序的数据分割成独立的两部分,其中一部分的所有数据都比另外一部分的所有数据都要小,然后再按此方法对这两部分数据分别进行快速排序,整个排序过程可以递归进行,以此达到整个数据变成有序序列。

def quick_sort(lists, left, right):
    # 快速排序
    if left >= right:
        return lists
    key = lists[left]
    low = left
    high = right
    while left < right:
        while left < right and lists[right] >= key:
            right -= 1
        lists[left] = lists[right]
        while left < right and lists[left] <= key:
            left += 1
        lists[right] = lists[left]
    lists[right] = key
    quick_sort(lists, low, left - 1)
    quick_sort(lists, left + 1, high)
    return lists

5、直接选择排序

基本思想:第1趟,在待排序记录r1 ~ r[n]中选出最小的记录,将它与r1交换;第2趟,在待排序记录r2 ~ r[n]中选出最小的记录,将它与r2交换;以此类推,第i趟在待排序记录r[i] ~ r[n]中选出最小的记录,将它与r[i]交换,使有序序列不断增长直到全部排序完毕。

def select_sort(lists):
    # 选择排序
    count = len(lists)
    for i in range(0, count):
        min = i
        for j in range(i + 1, count):
            if lists[min] > lists[j]:
                min = j
        lists[min], lists[i] = lists[i], lists[min]
    return lists

6、堆排序

堆排序Heapsort是指利用堆积树(堆)这种数据结构所设计的一种排序算法,它是选择排序的一种。可以利用数组的特点快速定位指定索引的元素。堆分为大根堆和小根堆,是完全二叉树。大根堆的要求是每个节点的值都不大于其父节点的值,即A[PARENT[i]] >= A[i]。在数组的非降序排序中,需要使用的就是大根堆,因为根据大根堆的要求可知,最大的值一定在堆顶。

# 调整堆
def adjust_heap(lists, i, size):
    lchild = 2 * i + 1
    rchild = 2 * i + 2
    max = i
    if i < size / 2:
        if lchild < size and lists[lchild] > lists[max]:
            max = lchild
        if rchild < size and lists[rchild] > lists[max]:
            max = rchild
        if max != i:
            lists[max], lists[i] = lists[i], lists[max]
            adjust_heap(lists, max, size)
# 创建堆
def build_heap(lists, size):
    for i in range(0, (size/2))[::-1]:
        adjust_heap(lists, i, size)
# 堆排序
def heap_sort(lists):
    size = len(lists)
    build_heap(lists, size)
    for i in range(0, size)[::-1]:
        lists[0], lists[i] = lists[i], lists[0]
        adjust_heap(lists, 0, i)

7、归并排序

归并排序是建立在归并操作上的一种有效的排序算法,该算法是采用分治法Divide and Conquer的一个非常典型的应用。将已有序的子序列合并,得到完全有序的序列;即先使每个子序列有序,再使子序列段间有序。若将两个有序表合并成一个有序表,称为二路归并。

归并过程为:比较a[i]和a[j]的大小,若a[i]≤a[j],则将第一个有序表中的元素a[i]复制到r[k]中,并令i和k分别加上1;否则将第二个有序表中的元素a[j]复制到r[k]中,并令j和k分别加上1,如此循环下去,直到其中一个有序表取完,然后再将另一个有序表中剩余的元素复制到r中从下标k到下标t的单元。归并排序的算法我们通常用递归实现,先把待排序区间[s,t]以中点二分,接着把左边子区间排序,再把右边子区间排序,最后把左区间和右区间用一次归并操作合并成有序的区间[s,t]。

def merge(left, right):
    i, j = 0, 0
    result = []
    while i < len(left) and j < len(right):
        if left[i] <= right[j]:
            result.append(left[i])
            i += 1
        else:
            result.append(right[j])
            j += 1
    result += left[i:]
    result += right[j:]
    return result
def merge_sort(lists):
    # 归并排序
    if len(lists) <= 1:
        return lists
    num = len(lists) / 2
    left = merge_sort(lists[:num])
    right = merge_sort(lists[num:])
    return merge(left, right)

8、基数排序

基数排序radix sort属于“分配式排序”distribution sort,又称“桶子法”bucket sort或bin sort,顾名思义,它是透过键值的部份资讯,将要排序的元素分配至某些“桶”中,藉以达到排序的作用,基数排序法是属于稳定性的排序,其时间复杂度为O (nlog(r)m),其中r为所采取的基数,而m为堆数,在某些时候,基数排序法的效率高于其它的稳定性排序法。

import math
def radix_sort(lists, radix=10):
    k = int(math.ceil(math.log(max(lists), radix)))
    bucket = [[] for i in range(radix)]
    for i in range(1, k+1):
        for j in lists:
            bucket[j/(radix**(i-1)) % (radix**i)].append(j)
        del lists[:]
        for z in bucket:
            lists += z
            del z[:]
    return lists

编码之道:取个好名字很重要

代码就是程序员的孩子,给“孩子”取个好听的名字很重要!

我们在项目开发中,接触到的变量、函数、类多数都是项目自己定义的,往往都是为了解决一些特定的领域的问题,引入了各种各样的概念,代码里面的名字就对应着问题领域或方案领域的这些概念,所以,对于一个命名良好,代码规范,设计简洁的系统,要想非常快的理解一个系统,最直接的方式就是RTFC(Read The Fucking Code)。对于一个不断演进的系统,代码的可读性至关重要,首要要解决的问题就是名字,变量名、函数名、类名等都需要仔细斟酌,认真对待,一个能够简洁,能够清晰表达概念和意图的名字就显得尤为重要。

阅读《代码整洁之道》这本书后发现其中说的内容在我们自己项目中比比皆是,随便拿出一块代码都可以当做反面教材给大家讲半天。长时间积累,导致代码发霉变质,取名也是毫无章法,信手拈来。阅读这样的代码,撞南墙的心都有了。下面结合自己项目中的问题和《代码整洁之道》谈谈关于命名相关的原则。

1. 原则:名副其实

  • 选名字是件严肃的事情,选个好名字很重要。
  • 如果名字需要注释来补充,那就不是个好名字。
  • 最重要的是要名副其实,名字能表达出概念和意图。

BAD:

int t = currentTime.elapse(e); // 消逝的时间,以毫秒计
...
if (t > timeout_value)
{
   Zebra::logger->debug("---一次循环用时 %u 毫秒-----", t);
}

GOOD:

int elapsed_ms = currentTime.elapse(e);
...
if (elapsed_ms > timeout_value)
{
   Zebra::logger->debug("-----一次循环用时 %u 毫秒---", elapsed_ms);
}

2. 原则:避免误导

  • 必须避免留下掩藏代码本意的错误线索
  • 避免使用与本意相悖的词
  • 提防使用不同之处较小的名称
  • 拼写前后不一致就是误导

BAD:

std::vector account_list; // _list就是一个误导, accounts会更好
bool sendToZoneServer(); // 和下面的函数差别很小
bool sendToZoneServers(); // sendToAllZoneServers会好点

3. 原则:做有意义的区分

  • 代码是写给人看的,仅仅是满足编译器的要求,就会引起混乱
  • 以数字系列命名(a1,a2,…),纯属误导
  • 无意义的废话: a, an, the, Info, Data

BAD:

void copy(char a1[], char a2[]) {
  for (size_t i = 0; a1[i] != '\0'; i++)
     a2[i] = a1[i];
}

GOOD:

void copy(char source[], char dest[]) {
  for (size_t i = 0; source[i] != '\0'; i++)
     dest[i] = source[i];
}

4. 原则:使用可读的名字

  • 避免过度使用缩写
  • 可读的名字交流方便

猜一猜下面的类是干什么的?和别人怎么说这几个类?

根据这些简直变态的缩写,如果没有注释基本上很难知道是干什么的,当你和别人交流的时候,你就不得不一个一个字母来念“X-L-Q-Y”、“L-T-Q Manager”,鬼知道你说的是什么?PS. XLQY-XianLvQiYuan(仙履奇缘), LTQ-LiaoTianQun(聊天群),有这样的名字也是醉了。

BAD:

class XLQY;
class FCNV;
class LTQManager;

5. 原则:使用可搜索的名字

  • 避免使用Magic Number
  • 避免使用单字母,或出现频率极高的短字母组合(注意度的把握)

BAD:

if (obj->base->id == 4661) // 4661是啥玩意?
{
   usetype = Cmd::XXXXXXX;
}
int e; // 怎么查找?
XXXX:iterator it; // 变量作用的范围比较大的时候,也不见得是个好名字

GOOD:

#define OJBECT_FEEDBACK_CARD 4661
if (OJBECT_FEEDBACK_CARD == obj->base->id)
{
   usetype = Cmd::XXXXXXX;
}

6. 原则:避免使用编码

  • 匈牙利标记法:
    • Windows API时代留下的玩意
    • 形如:wdXX, dwXXX, strXXX
    • 类型变换导致名不副实,就有可能出现明明是个DWORD,变量名却是qwNum

PS.匈牙利命名对于我们这些在Linux下摸爬滚打的好多年的来说,看着真心别扭。

  • 成员前缀:
    • 形如:m_name, m_xxx
    • 基本上都无视,为何要多次一举

PS.说到这一点,可能有些同学有不同意见了,“我这样写是为了区分成员变量和临时变量啊!”,好像这样写也没什么大不了,遵循代码规范即可。如Google的C++代码规范,私有变量形如:xxx_,加后缀_,其目的除了让你知道这货是个私有变量,还有一点就是防止有些人图省事把带私有变量直接public掉,因为谁也不喜欢在代码里面看到大量这些带把的玩意。

  • 接口和实现:
    • 接口名形如:IXXX, I-接口修饰前缀
    • 类名形如:CXXX, C-类修饰前缀
    • 这些修饰多数时候都是废话

7. 原则:名字尽量来自解决方案领域或问题领域

  • 使用解决方案领域名称:

写代码的同学多数都是都出自CS,术语、算法名、模式名、数学术语尽管用。如AccountVisitor:Visitor模式实现的Account类。

  • 使用问题领域的名称

我们代码里面多数都是这些名称,不明白找策划问问,基本上都是功能相关的名称。

8. 原则:适当使用有意义的语境

  • 良好命名的类、函数、名称空间来放置名称,给读者提供语境
  • 只有两三个变量,给名称前加前缀
  • 事不过三,变量超过三个考虑封装成概念,添加struct或class

BAD:

// 看着整齐?使用方便?
DWORD love_ensure_type_;  //当前的爱情保险类型
DWORD love_ensure_ret_; //购买爱情保险回应标示
DWORD love_ensure_total_; //现在已经盖章数目
DWORD love_ensure_..._;  //...
DWORD love_ensure_..._;  //...

最后:我们的C++命名规范

  • 文件名:

    • 首字母大写,多个词组合起来
    • 如: SceneUser.h Sept.h
  • 类名/名称空间名:

    • 首字母大写,多个词组合起来
    • 使用名词或名词词组
    • 避免使用C前缀,如:CSept
    • 如: SceneUser SeptWar
  • 函数名:

    • 首字母小写
    • 使用动词或动词词组
    • 避免使用孤立的全局函数,可以封装在类或名称空间里面
    • get, set, is前缀的使用
    • 如: fuckYou()levelup()
  • 变量名:

    • 全部字母小写,多个词以下划线分隔
    • 私有成员变量加后缀_,公有变量不用
    • 避免使用孤立的全局变量,可以封装在类或名称空间里面
    • 如: quest_idquestid_

取名是一件严肃的事情,我们需要认真对待,名字代表着一个个概念,名字代表着你想表达的意图,好名字是可读代码的首要条件:

  • 写下任何一行代码的时候,心里都要想着自己的代码是给别人看的。
  • 为函数、变量、类取个好名字,遵循规范和原则。
  • 见到不符合规范和原则的名字,确毫不留情的干掉它,特别是功能性的代码。

15 个必须知道的 chrome 开发工具技巧

15-chrome-devtools-tips-and-tricks

在Web开发者中,Google Chrome是使用最广泛的浏览器。六周一次的发布周期和一套强大的不断扩大开发功能,使其成为了web开发者必备的工具。你可能已经熟悉了它的部分功能,如使用console和debugger在线编辑CSS。在这篇文章中,我们将分享15个有助于改进你的开发流程的技巧。

一、快速切换文件

如果你使用过sublime text,那么你可能不习惯没有Go to anything这个功能的覆盖。你会很高兴听到chrome开发者功能也有这个功能,当DevTools被打开的时候,按Ctrl+P(在 mac 是cmd+p),就能快速搜寻和打开你项目的文件。

二、在源代码中搜索

如果你希望在源代码中搜索要怎么办呢?在页面已经加载的文件中搜寻一个特定的字符串,快捷键是Ctrl + Shift + F (Cmd + Opt + F),这种搜寻方式还支持正则表达式哦。

三、快速跳转到指定行

在Sources标签中打开一个文件之后,在Windows和Linux中,按Ctrl + G,(Cmd + L),然后输入行号,DevTools就会允许你跳转到文件中的任意一行。

另外一种方式是按Ctrl + O,输入:和行数,而不用去寻找一个文件。 

四、在控制台选择元素

DevTools控制台支持一些变量和函数来选择DOM元素:

  • $()document.querySelector()的简写,返回第一个和css选择器匹配的元素。例如$(‘div’)返回这个页面中第一个div元素
  • $$()document.querySelectorAll()的简写,返回一个和css选择器匹配的元素数组。
  • $0$4–依次返回五个最近你在元素面板选择过的DOM元素的历史记录,$0是最新的记录,以此类推。

想要了解更多控制台命令,戳这里:Command Line API

五、使用多个插入符进行选择

当编辑一个文件的时候,你可以按住Ctrlcmd),在你要编辑的地方点击鼠标,可以设置多个插入符,这样可以一次在多个地方编辑。

 

六、保存记录

勾选在Console标签下的保存记录选项,你可以使DevTools的console继续保存记录而不会在每个页面加载之后清除记录。当你想要研究在页面还没加载完之前出现的bug时,这会是一个很方便的方法。

七、优质打印

Chrome’s Developer Tools有内建的美化代码,可以返回一段最小化且格式易读的代码。Pretty Print的按钮在Sources标签的左下角。

 

八、设备模式

对于开发移动友好页面,DevTools包含了一个非常强大的模式,这个谷歌视频介绍了其主要特点,如调整屏幕大小、触摸仿真和模拟糟糕的网络连接。

九、设备传感仿真

设备模式的另一个很酷的功能是模拟移动设备的传感器,例如触摸屏幕和加速计。你甚至可以恶搞你的地理位置。这个功能位于元素标签的底部,点击“show drawer”按钮,就可看见 Emulation标签 –> Sensors.

 

十、颜色选择器

当在样式编辑中选择了一个颜色属性时,你可以点击颜色预览,就会弹出一个颜色选择器。当选择器开启时,如果你停留在页面,鼠标指针会变成一个放大镜,让你去选择像素精度的颜色。

十一、强制改变元素状态

DevTools有一个可以模拟CSS状态的功能,例如元素的hover和focus,可以很容易的改变元素样式。在CSS编辑器中可以利用这个功能

十二、可视化的DOM阴影

Web浏览器在构建如文本框、按钮和输入框一类元素时,其它基本元素的视图是隐藏的。不过,你可以在Settings -> General 中切换成Show user agent shadow DOM,这样就会在元素标签页中显示被隐藏的代码。甚至还能单独设计他们的样式,这给你了很大的控制权。

十三、选择下一个匹配项

当在Sources标签下编辑文件时,按下Ctrl + D (Cmd + D) ,当前选中的单词的下一个匹配也会被选中,有利于你同时对它们进行编辑。

十四、改变颜色格式

在颜色预览功能使用快捷键Shift + 点击,可以在rgba、hsl和hexadecimal来回切换颜色的格式

十五、通过workspaces来编辑本地文件

Workspaces是Chrome DevTools的一个强大功能,这使DevTools变成了一个真正的IDE。Workspaces会将Sources选项卡中的文件和本地项目中的文件进行匹配,所以你可以直接编辑和保存,而不必复制/粘贴外部改变的文件到编辑器。

为了配置Workspaces,只需打开Sources选项,然后右击左边面板的任何一个地方,选择 Add Folder To Worskpace,或者只是把你的整个工程文件夹拖放入Developer Tool。现在,无论在哪一个文件夹,被选中的文件夹,包括其子目录和所有文件都可以被编辑。为了让Workspaces更高效,你可以将页面中用到的文件映射到相应的文件夹,允许在线编辑和简单的保存。

了解更多关于Workspaces的使用,戳这里:Workspaces

在 CentOS 7 中安装并使用自动化工具 Ansible

Ansible是一款为类Unix系统开发的自由开源的配置和自动化工具。它用Python写成,类似于Chef和Puppet,但是有一个不同和优点是我们不需要在节点中安装任何客户端。它使用SSH来和节点进行通信。

在 CentOS 7 中安装并使用自动化工具 Ansible
在 CentOS 7 中安装并使用自动化工具 Ansible

本篇中我们将在CentOS 7上安装并配置Ansible,并且尝试管理两个节点。

  • Ansible 服务端 – ansible.linuxtechi.com ( 192.168.1.15 )

  • 节点 – 192.168.1.9 , 192.168.1.10

第一步: 设置EPEL仓库

Ansible仓库默认不在yum仓库中,因此我们需要使用下面的命令启用epel仓库。

[root@ansible ~]# rpm -iUvh http://dl.fedoraproject.org/pub/epel/7/x86_64/e/epel-release-7-5.noarch.rpm

第二步: 使用yum安装Ansible

[root@ansible ~]# yum install ansible

安装完成后,检查ansible版本:

[root@ansible ~]# ansible --version

ansible-version

第三步: 设置用于节点鉴权的SSH密钥

在Ansible服务端生成密钥,并且复制公钥到节点中。

root@ansible ~]# ssh-keygen
在 CentOS 7 中安装并使用自动化工具 Ansible
在 CentOS 7 中安装并使用自动化工具 Ansible

使用ssh-copy-id命令来复制Ansible公钥到节点中。

在 CentOS 7 中安装并使用自动化工具 Ansible
在 CentOS 7 中安装并使用自动化工具 Ansible

第四步:为Ansible定义节点的清单

文件 /etc/ansible/hosts 维护着Ansible中服务器的清单。

[root@ansible ~]# vi /etc/ansible/hosts
[test-servers]
192.168.1.9
192.168.1.10

保存并退出文件。

主机文件示例如下:

在 CentOS 7 中安装并使用自动化工具 Ansible
在 CentOS 7 中安装并使用自动化工具 Ansible

第五步:尝试在Ansible服务端运行命令

使用ping检查‘test-servers’或者ansible节点的连通性。

[root@ansible ~]# ansible -m ping 'test-servers'
在 CentOS 7 中安装并使用自动化工具 Ansible
在 CentOS 7 中安装并使用自动化工具 Ansible

执行shell命令

例子1:检查Ansible节点的运行时间(uptime)

[root@ansible ~]# ansible -m command -a "uptime" 'test-servers'

ansible-uptime

例子2:检查节点的内核版本

[root@ansible ~]# ansible -m command -a "uname -r" 'test-servers'

kernel-version-ansible

例子3:给节点增加用户

[root@ansible ~]# ansible -m command -a "useradd mark" 'test-servers'
[root@ansible ~]# ansible -m command -a "grep mark /etc/passwd" 'test-servers'
在 CentOS 7 中安装并使用自动化工具 Ansible
在 CentOS 7 中安装并使用自动化工具 Ansible

例子4:重定向输出到文件中

[root@ansible ~]# ansible -m command -a "df -Th" 'test-servers' > /tmp/command-output.txt
在 CentOS 7 中安装并使用自动化工具 Ansible
在 CentOS 7 中安装并使用自动化工具 Ansible

via: http://www.linuxtechi.com/install-and-use-ansible-in-centos-7/

作者:Pradeep Kumar 译者:geekpi 校对:wxy

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

RHCSA 系列(十四): 在 RHEL 7 中设置基于 LDAP 的认证

在这篇文章中,我们将首先罗列一些 LDAP 的基础知识(它是什么,它被用于何处以及为什么会被这样使用),然后向你展示如何使用 RHEL 7 系统来设置一个 LDAP 服务器以及配置一个客户端来使用它达到认证的目的。

RHCSA 系列(十四): 在 RHEL 7 中设置基于 LDAP 的认证
RHCSA 系列(十四): 在 RHEL 7 中设置基于 LDAP 的认证

RHCSA 系列:设置 LDAP 服务器及客户端认证 – Part 14

正如你将看到的那样,关于认证,还存在其他可能的应用场景,但在这篇指南中,我们将只关注基于 LDAP 的认证。另外,请记住,由于这个话题的广泛性,在这里我们将只涵盖它的基础知识,但你可以参考位于总结部分中列出的文档,以此来了解更加深入的细节。

基于相同的原因,你将注意到:为了简洁起见,我已经决定省略了几个位于 man 页中 LDAP 工具的参考,但相应命令的解释是近在咫尺的(例如,输入 man ldapadd)。

那还是让我们开始吧。

我们的测试环境

我们的测试环境包含两台 RHEL 7机器:

Server: 192.168.0.18. FQDN: rhel7.mydomain.com
Client: 192.168.0.20. FQDN: ldapclient.mydomain.com

如若你想,你可以使用在 RHCSA 系列(十二): 使用 Kickstart 完成 RHEL 7 的自动化安装 中使用 Kickstart 安装的机子来作为客户端。

LDAP 是什么?

LDAP 代表轻量级目录访问协议Lightweight Directory Access Protocol,并包含在一系列协议之中,这些协议允许一个客户端通过网络去获取集中存储的信息(例如所登录的 shell 的路径,家目录的绝对路径,或者其他典型的系统用户信息),而这些信息可以从不同的地方访问到或被很多终端用户获取到(另一个例子是含有某个公司所有雇员的家庭地址和电话号码的目录)。

对于那些被赋予了权限可以使用这些信息的人来说,将这些信息进行集中管理意味着可以更容易地维护和获取。

下面的图表提供了一个简化了的关于 LDAP 的示意图,在下面将会进行更多的描述:

RHCSA 系列(十四): 在 RHEL 7 中设置基于 LDAP 的认证
RHCSA 系列(十四): 在 RHEL 7 中设置基于 LDAP 的认证

LDAP 示意图

下面是对上面示意图的一个详细解释。

  • 在一个 LDAP 目录中,一个条目entry代表一个独立单元或信息,被所谓的区别名DN,Distinguished Name 唯一识别。
  • 一个属性attribute是一些与某个条目相关的信息(例如地址,有效的联系电话号码和邮箱地址)。
  • 每个属性被分配有一个或多个value,这些值被包含在一个以空格为分隔符的列表中。每个条目中那个唯一的值被称为一个相对区别名RDN,Relative Distinguished Name

接下来,就让我们进入到有关服务器和客户端安装的内容。

安装和配置一个 LDAP 服务器和客户端

在 RHEL 7 中, LDAP 由 OpenLDAP 实现。为了安装服务器和客户端,分别使用下面的命令:

# yum update && yum install openldap openldap-clients openldap-servers
# yum update && yum install openldap openldap-clients nss-pam-ldapd

一旦安装完成,我们还需要关注一些事情。除非显示地提示,下面的步骤都只在服务器上执行:

1. 在服务器和客户端上,为了确保 SELinux 不会妨碍挡道,长久地开启下列的布尔值:

# setsebool -P allow_ypbind=0 authlogin_nsswitch_use_ldap=0

其中 allow_ypbind 为基于 LDAP 的认证所需要,而 authlogin_nsswitch_use_ldap则可能会被某些应用所需要。

2. 开启并启动服务:

# systemctl enable slapd.service
# systemctl start slapd.service

记住你也可以使用 systemctl 来禁用,重启或停止服务:

# systemctl disable slapd.service
# systemctl restart slapd.service
# systemctl stop slapd.service

3. 由于 slapd 服务是由 ldap 用户来运行的(你可以使用 ps -e -o pid,uname,comm | grep slapd 来验证),为了使得服务器能够更改由管理工具创建的条目,该用户应该有目录 /var/lib/ldap 的所有权,而这些管理工具仅可以由 root 用户来运行(紧接着有更多这方面的内容)。

在递归地更改这个目录的所有权之前,将 slapd 的示例数据库配置文件复制进这个目录:

# cp /usr/share/openldap-servers/DB_CONFIG.example /var/lib/ldap/DB_CONFIG
# chown -R ldap:ldap /var/lib/ldap

4. 设置一个 OpenLDAP 管理用户并设置密码:

# slappasswd

正如下一幅图所展示的那样:

设置 LDAP 管理密码

设置 LDAP 管理密码

然后以下面的内容创建一个 LDIF 文件(ldaprootpasswd.ldif):

dn: olcDatabase={0}config,cn=config
changetype: modify
add: olcRootPW
olcRootPW: {SSHA}PASSWORD

其中:

  • PASSWORD 是先前得到的经过哈希处理的字符串。
  • cn=config 指的是全局配置选项。
  • olcDatabase 指的是一个特定的数据库实例的名称,并且通常可以在 /etc/openldap/slapd.d/cn=config 目录中发现。

根据上面提供的理论背景,ldaprootpasswd.ldif 文件将添加一个条目到 LDAP 目录中。在那个条目中,每一行代表一个属性键值对(其中 dn,changetype,add 和 olcRootPW 为属性,每个冒号右边的字符串为相应的键值)。

随着我们的进一步深入,请记住上面的这些,并注意到在这篇文章的余下部分,我们使用相同的通用名Common Names (cn=),而这些余下的步骤中的每一步都将与其上一步相关。

5. 现在,通过特别指定相对于 ldap 服务的 URI ,添加相应的 LDAP 条目,其中只有 protocol/host/port 这几个域被允许使用。

# ldapadd -H ldapi:/// -f ldaprootpasswd.ldif

上面命令的输出应该与下面的图像相似:

LDAP 配置

LDAP 配置

接着从 /etc/openldap/schema 目录导入一个基本的 LDAP 定义:

# for def in cosine.ldif nis.ldif inetorgperson.ldif; do ldapadd -H ldapi:/// -f /etc/openldap/schema/$def; done
RHCSA 系列(十四): 在 RHEL 7 中设置基于 LDAP 的认证
RHCSA 系列(十四): 在 RHEL 7 中设置基于 LDAP 的认证

LDAP 定义

6. 让 LDAP 在它的数据库中使用你的域名。

以下面的内容创建另一个 LDIF 文件,我们称之为 ldapdomain.ldif, 然后酌情替换这个文件中的域名(在域名部分Domain Component dc=) 和密码:

dn: olcDatabase={1}monitor,cn=config
changetype: modify
replace: olcAccess
olcAccess: {0}to * by dn.base="gidNumber=0+uidNumber=0,cn=peercred,cn=external,cn=auth"
  read by dn.base="cn=Manager,dc=mydomain,dc=com" read by * none
dn: olcDatabase={2}hdb,cn=config
changetype: modify
replace: olcSuffix
olcSuffix: dc=mydomain,dc=com
dn: olcDatabase={2}hdb,cn=config
changetype: modify
replace: olcRootDN
olcRootDN: cn=Manager,dc=mydomain,dc=com
dn: olcDatabase={2}hdb,cn=config
changetype: modify
add: olcRootPW
olcRootPW: {SSHA}PASSWORD
dn: olcDatabase={2}hdb,cn=config
changetype: modify
add: olcAccess
olcAccess: {0}to attrs=userPassword,shadowLastChange by
  dn="cn=Manager,dc=mydomain,dc=com" write by anonymous auth by self write by * none
olcAccess: {1}to dn.base="" by * read
olcAccess: {2}to * by dn="cn=Manager,dc=mydomain,dc=com" write by * read

接着使用下面的命令来加载:

# ldapmodify -H ldapi:/// -f ldapdomain.ldif
RHCSA 系列(十四): 在 RHEL 7 中设置基于 LDAP 的认证
RHCSA 系列(十四): 在 RHEL 7 中设置基于 LDAP 的认证

LDAP 域名配置

7. 现在,该是添加一些条目到我们的 LDAP 目录的时候了。在下面的文件中,属性和键值由一个冒号(:) 所分隔,这个文件我们将命名为 baseldapdomain.ldif:

dn: dc=mydomain,dc=com
objectClass: top
objectClass: dcObject
objectclass: organization
o: mydomain com
dc: mydomain
dn: cn=Manager,dc=mydomain,dc=com
objectClass: organizationalRole
cn: Manager
description: Directory Manager
dn: ou=People,dc=mydomain,dc=com
objectClass: organizationalUnit
ou: People
dn: ou=Group,dc=mydomain,dc=com
objectClass: organizationalUnit
ou: Group

添加条目到 LDAP 目录中:

# ldapadd -x -D cn=Manager,dc=mydomain,dc=com -W -f baseldapdomain.ldif

添加 LDAP 域名,属性和键值

添加 LDAP 域名,属性和键值

8. 创建一个名为 ldapuser 的 LDAP 用户(adduser ldapuser),然后在ldapgroup.ldif 中为一个 LDAP 组创建定义。

# adduser ldapuser
# vi ldapgroup.ldif

添加下面的内容:

dn: cn=Manager,ou=Group,dc=mydomain,dc=com
objectClass: top
objectClass: posixGroup
gidNumber: 1004

其中 gidNumber 是 ldapuser 在 /etc/group 中的 GID,然后加载这个文件:

# ldapadd -x -W -D "cn=Manager,dc=mydomain,dc=com" -f ldapgroup.ldif

9. 为用户 ldapuser 添加一个带有定义的 LDIF 文件(ldapuser.ldif):

dn: uid=ldapuser,ou=People,dc=mydomain,dc=com
objectClass: top
objectClass: account
objectClass: posixAccount
objectClass: shadowAccount
cn: ldapuser
uid: ldapuser
uidNumber: 1004
gidNumber: 1004
homeDirectory: /home/ldapuser
userPassword: {SSHA}fiN0YqzbDuDI0Fpqq9UudWmjZQY28S3M
loginShell: /bin/bash
gecos: ldapuser
shadowLastChange: 0
shadowMax: 0
shadowWarning: 0

并加载它:

# ldapadd -x -D cn=Manager,dc=mydomain,dc=com -W -f ldapuser.ldif

LDAP 用户配置

LDAP 用户配置

相似地,你可以删除你刚刚创建的用户条目:

# ldapdelete -x -W -D cn=Manager,dc=mydomain,dc=com "uid=ldapuser,ou=People,dc=mydomain,dc=com"

10. 允许有关 ldap 的通信通过防火墙:

# firewall-cmd --add-service=ldap

11. 最后,但并非最不重要的是使用 LDAP 开启客户端的认证。

为了在最后一步中对我们有所帮助,我们将使用 authconfig 工具(一个配置系统认证资源的界面)。

使用下面的命令,在通过 LDAP 服务器认证成功后,假如请求的用户的家目录不存在,则将会被创建:

# authconfig --enableldap --enableldapauth --ldapserver=rhel7.mydomain.com --ldapbasedn="dc=mydomain,dc=com" --enablemkhomedir --update
RHCSA 系列(十四): 在 RHEL 7 中设置基于 LDAP 的认证
RHCSA 系列(十四): 在 RHEL 7 中设置基于 LDAP 的认证

LDAP 客户端认证

总结

在这篇文章中,我们已经解释了如何利用一个 LDAP 服务器来设置基本的认证。若想对当前这个指南里描述的设置进行更深入的配置,请参考位于 RHEL 系统管理员指南里的 第 13 章 – LDAP 的配置,并特别注意使用 TLS 来进行安全设定。

请随意使用下面的评论框来留下你的提问。


via: http://www.tecmint.com/setup-ldap-server-and-configure-client-authentication/

作者:Gabriel Cánepa 译者:FSSlc 校对:wxy

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

10 个 Node.js 常见面试题

如果你希望找一份有关Node.js的工作,但又不知道从哪里入手考察自己对Node.js的掌握程度。 本文就提供了这样的一份Node.js面试题列表,通过考察Node.js编程中的一些主要细节, 来帮助你评估你对于Node.js开发的掌握程度。

在进入正文之前,需要提前声明两点:

  1. 这些问题只是Node.js知识体系的一个局部,并不能完全考察被面试者的实际开发能力。
  2. 对现实世界开发中遇到的问题,需要的是随机应变与团队合作,所以你可以尝试结对编程。

Node.js面试题列表

  • 什么是错误优先的回调函数?
  • 你如何避免回调地狱?
  • 你如何用Node来监听80端口?
  • 什么是事件循环?
  • 什么工具可以用来保证一致的风格?
  • 运算错误与程序员错误的区别?
  • 使用NPM有哪些好处?
  • 什么是stub?举个使用场景?
  • 什么是测试金字塔?当我们谈到HTTP API时,我们如何实施它?
  • 你最喜欢的HTTP框架,并说明原因?

现在,我们依次来解答这些问题吧。

什么是错误优先的回调函数?

错误优先的回调函数用于传递错误和数据。第一个参数始终应该是一个错误对象, 用于检查程序是否发生了错误。其余的参数用于传递数据。例如:

fs.readFile(filePath, function(err, data) {
	if (err) {
		//handle the error
	}
	// use the data object
});

解析:这个题目的主要作用在于检查被面试者对于Node中异步操作的一些基本知识的掌握。

如何避免回调地狱

你可以有如下几个方法:

  • 模块化:将回调函数分割为独立的函数
  • 使用Promises
  • 使用yield来计算生成器或Promise

解析:这个问题有很多种答案,取决你使用的场景,例如ES6, ES7,或者一些控制流库。

在Node中你如何监听80端口

这题有陷阱!在类Unix系统中你不应该尝试监听80端口,因为这需要超级用户权限, 因此不建议让你的应用监听这个端口。

目前,如果你想让你的应用一定要监听80端口,可以这么做:让你的Node应用监听大于1024的端口, 然后在它前面在使用一层方向代理(例如nginx)。

解释:这个问题用于检查被面试者是否有实际运行Node应用的经验。

什么是事件循环

Node只运行在一个单一线程上,至少从Node.js开发者的角度是这样的。在底层, Node是通过libuv来实现多线程的。

Libuv库负责Node API的执行。它将不同的任务分配给不同的线程,形成一个事件循环, 以异步的方式将任务的执行结果返回给V8引擎。可以简单用下面这张图来表示。

Event Loop

(图片来源于网络)

每一个I/O都需要一个回调函数——一旦执行完便推到事件循环上用于执行。 如果你需要更多详细的解释,可以参考这个视频。 你也可以参考这篇文章

解释:这用于检查Node.js的底层知识,例如什么是libuv,它的作用是什么。

哪些工具可以用来确保一致性的风格

你可以有如下的工具:

在团队开发中,这些工具对于编写代码非常的有帮助,能够帮助强制执行给定的风格指南, 并且通过静态分析捕获常见的错误。

解析:用于检查被面试者是否有大型项目开发经验。

运算错误与程序员错误的区别

运算错误并不是bug,这是和系统相关的问题,例如请求超时或者硬件故障。而程序员错误就是所谓的bug。

解析:这个题目和Node关系并不大,用于考察面试者的基础知识。

使用NPM有哪些好处?

通过NPM,你可以安装和管理项目的依赖,并且能够指明依赖项的具体版本号。 对于Node应用开发而言,你可以通过package.json文件来管理项目信息,配置脚本, 以及指明项目依赖的具体版本。

关于NPM的更多信息,你可以参考官方文档

解析:它能考察面试者使用npm命令的基础知识和Node.js开发的实际经验。

什么是Stub?举个使用场景

Stub是用于模拟一个组件/模块的一个函数或程序。在测试用例中,Stub可以为函数调用提供封装的答案。 当然,你还可以在断言中指明Stub是如何被调用的。

例如在一个读取文件的场景中,当你不想读取一个真正的文件时:

var fs = require('fs');
var readFileStub = sinon.stub(fs, 'readFile', function (path, cb) {
	return cb(null, 'filecontent');
});
expect(readFileStub).to.be.called;
readFileStub.restore();

解析:用于测试被面试者是否有测试的经验。如果被面试者知道什么是Stub, 那么可以继续问他是如何做单元测试的。

什么是测试金字塔?当我们在谈论HTTP API时如何实施它?

测试金字塔指的是: 当我们在编写测试用例时,底层的单元测试应该远比上层的端到端测试要多。

Test Pyramid

当我们谈到HTTP API时,我们可能会涉及到:

  • 有很多针对模型的底层单元测试
  • 但你需要测试模型间如何交互时,需要减少集成测试

解析:本文主要考察被面试者的在测试方面的经验。

你最喜欢的HTTP框架以及原因

这题没有唯一的答案。本题主要考察被面试者对于他所使用的Node框架的理解程度, 考察他是否能够给出选择该框架的理由,优缺点等。

RHCSA 系列(十五): 虚拟化基础和使用 KVM 进行虚拟机管理

假如你在词典中查一下单词 “虚拟化virtualize”,你将会发现它的意思是 “创造某些事物的一个虚拟物(而非真实的)”。在计算机行业中,术语虚拟化virtualization指的是:在相同的物理(硬件)系统上,同时运行多个操作系统,且这几个系统相互隔离的可能性,而那个硬件在虚拟化架构中被称作宿主机host

RHCSA 系列(十五): 虚拟化基础和使用 KVM 进行虚拟机管理
RHCSA 系列(十五): 虚拟化基础和使用 KVM 进行虚拟机管理

RHCSA 系列: 虚拟化基础和使用 KVM 进行虚拟机管理 – Part 15

通过使用虚拟机监视器(也被称为虚拟机管理程序hypervisor),虚拟机(被称为 guest)由底层的硬件来供给虚拟资源(举几个例子来说,如 CPU,RAM,存储介质,网络接口等)。

考虑到这一点就可以清楚地看出,虚拟化的主要优点是节约成本(在设备和网络基础设施,及维护工作等方面)和显著地减少容纳所有必要硬件所需的物理空间。

由于这个简单的指南不能涵盖所有的虚拟化方法,我鼓励你参考在总结部分中列出的文档,以此对这个话题做更深入的了解。

请记住当前文章只是用于在 RHEL 7 中用命令行工具使用 KVM (Kernel-based Virtual Machine基于内核的虚拟机) 学习虚拟化基础知识的一个起点,而并不是对这个话题的深入探讨。

检查硬件要求并安装软件包

为了设置虚拟化,你的 CPU 必须能够支持它。你可以使用下面的命令来查看你的系统是否满足这个要求:

# grep -E 'svm|vmx' /proc/cpuinfo

在下面的截图中,我们可以看到当前的系统(带有一个 AMD 的微处理器)支持虚拟化,svm 字样的存在暗示了这一点。假如我们有一个 Intel 系列的处理器,我们将会看到上面命令的结果将会出现 vmx 字样。

检查 KVM 支持

检查 KVM 支持

另外,你需要在你宿主机的硬件(BIOS 或 UEFI)中开启虚拟化。

现在,安装必要的软件包:

  • qemu-kvm 是一个开源的虚拟机程序,为 KVM 虚拟机监视器提供硬件仿真,而 qemu-img 则提供了一个操纵磁盘镜像的命令行工具。
  • libvirt 包含与操作系统的虚拟化功能交互的工具。
  • libvirt-python 包含一个模块,它允许用 Python 写的应用来使用由 libvirt 提供的接口。
  • libguestfs-tools 包含各式各样的针对虚拟机的系统管理员命令行工具。
  • virt-install 包含针对虚拟机管理的其他命令行工具。

命令如下:

# yum update && yum install qemu-kvm qemu-img libvirt libvirt-python libguestfs-tools virt-install

一旦安装完成,请确保你启动并开启了 libvirtd 服务:

# systemctl start libvirtd.service
# systemctl enable libvirtd.service

默认情况下,每个虚拟机将只能够与放在相同的物理服务器上的虚拟机以及宿主机自身通信。要使得虚拟机能够访问位于局域网或因特网中的其他机器,我们需要像下面这样在我们的宿主机上设置一个桥接接口(比如说 br0):

1、 添加下面的一行到我们的 NIC 主配置中(类似 /etc/sysconfig/network-scripts/ifcfg-enp0s3 这样的文件):

BRIDGE=br0

2、 使用下面的内容(注意,你可能需要更改 IP 地址,网关地址和 DNS 信息)为 br0 创建一个配置文件(/etc/sysconfig/network-scripts/ifcfg-br0):

DEVICE=br0
TYPE=Bridge
BOOTPROTO=static
IPADDR=192.168.0.18
NETMASK=255.255.255.0
GATEWAY=192.168.0.1
NM_CONTROLLED=no
DEFROUTE=yes
PEERDNS=yes
PEERROUTES=yes
IPV4_FAILURE_FATAL=no
IPV6INIT=yes
IPV6_AUTOCONF=yes
IPV6_DEFROUTE=yes
IPV6_PEERDNS=yes
IPV6_PEERROUTES=yes
IPV6_FAILURE_FATAL=no
NAME=br0
ONBOOT=yes
DNS1=8.8.8.8
DNS2=8.8.4.4

3、 最后在文件/etc/sysctl.conf 中设置:

net.ipv4.ip_forward = 1

来开启包转发并加载更改到当前的内核配置中:

# sysctl -p

注意,你可能还需要告诉 firewalld 让这类的流量应当被允许通过防火墙。假如你需要这样做,记住你可以参考这个系列的 使用 firewalld 和 iptables 来控制网络流量

创建虚拟机镜像

默认情况下,虚拟机镜像将会被创建到 /var/lib/libvirt/images 中,且强烈建议你不要更改这个设定,除非你真的需要那么做且知道你在做什么,并能自己处理有关 SELinux 的设定(这个话题已经超出了本教程的讨论范畴,但你可以参考这个系列的第 13 部分 使用 SELinux 来进行强制访问控制,假如你想更新你的知识的话)。

这意味着你需要确保你在文件系统中分配了必要的空间来容纳你的虚拟机。

下面的命令将使用位于 /home/gacanepa/ISOs目录下的 rhel-server-7.0-x86_64-dvd.iso 镜像文件和 br0 这个网桥来创建一个名为 tecmint-virt01 的虚拟机,它有一个虚拟 CPU,1 GB(=1024 MB)的 RAM,20 GB 的磁盘空间(由/var/lib/libvirt/images/tecmint-virt01.img所代表):

# virt-install
--network bridge=br0
--name tecmint-virt01
--ram=1024
--vcpus=1
--disk path=/var/lib/libvirt/images/tecmint-virt01.img,size=20
--graphics none
--cdrom /home/gacanepa/ISOs/rhel-server-7.0-x86_64-dvd.iso
--extra-args="console=tty0 console=ttyS0,115200"

假如安装文件位于一个 HTTP 服务器上,而不是存储在你磁盘中的镜像中,你必须将上面的 -cdrom 替换为 -location,并明确地指出在线存储仓库的地址。

至于上面的 –graphics none 选项,它告诉安装程序只以文本模式执行安装过程。假如你使用一个 GUI 界面和一个 VNC 窗口来访问主虚拟机控制台,则可以省略这个选项。最后,使用 –extra-args 参数,我们将传递内核启动参数给安装程序,以此来设置一个串行的虚拟机控制台。

现在,所安装的虚拟机应当可以作为一个正常的(真实的)服务来运行了。假如没有,请查看上面列出的步骤。

管理虚拟机

作为一个系统管理员,还有一些典型的管理任务需要你在虚拟机上去完成。注:下面所有的命令都需要在你的宿主机上运行:

1. 列出所有的虚拟机:

# virsh list --all

你必须留意上面命令输出中的虚拟机 ID(尽管上面的命令还会返回虚拟机的名称和当前的状态),因为你需要它来执行有关某个虚拟机的大多数管理任务。

2. 显示某个虚拟机的信息:

# virsh dominfo [VM Id]

3. 开启,重启或停止一个虚拟机操作系统:

# virsh start | reboot | shutdown [VM Id]

4. 假如网络无法连接且在宿主机上没有运行 X 服务器,可以使用下面的命令来访问虚拟机的串行控制台:

# virsh console [VM Id]

:这需要你添加一个串行控制台配置信息到 /etc/grub.conf 文件中(参考刚才创建虚拟机时传递给-extra-args选项的参数)。

5. 修改分配的内存或虚拟 CPU:

首先,关闭虚拟机:

# virsh shutdown [VM Id]

为 RAM 编辑虚拟机的配置:

# virsh edit [VM Id]

然后更改

[内存大小,注意不要加上方括号]

使用新的设定重启虚拟机:

# virsh create /etc/libvirt/qemu/tecmint-virt01.xml

最后,可以使用下面的命令来动态地改变内存的大小:

# virsh setmem [VM Id] [内存大小,这里没有括号]

对于 CPU,使用:

# virsh edit [VM Id]

然后更改

[CPU 数目,这里没有括号]

至于更深入的命令和细节,请参考 RHEL 5 虚拟化指南(这个指南尽管有些陈旧,但包括了用于管理虚拟机的 virsh 命令的详尽清单)的第 26 章里的表 26.1。

总结

在这篇文章中,我们涵盖了在 RHEL 7 中如何使用 KVM 和虚拟化的一些基本概念,这个话题是一个广泛且令人着迷的话题。并且我希望它能成为你在随后阅读官方的 RHEL 虚拟化入门RHEL 虚拟化部署和管理指南 ,探索更高级的主题时的起点教程,并给你带来帮助。

另外,为了分辨或拓展这里解释的某些概念,你还可以参考先前包含在 KVM 系列 中的文章。


via: http://www.tecmint.com/kvm-virtualization-basics-and-guest-administration/

作者:Gabriel Cánepa 译者:FSSlc 校对:wxy

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

[新手技巧] 如何在Ubuntu中添加和删除书签

这是一篇对完全是新手的一篇技巧,我将向你展示如何在Ubuntu文件管理器中添加书签。

现在如果你想知道为什么要这么做,答案很简单。它可以让你可以快速地在左边栏中访问。比如,我在Ubuntu中安装了Copy 云服务。它创建在/Home/Copy。先进入Home目录再进入Copy目录并不是很麻烦,但是我想要更快地访问它。因此我添加了一个书签这样我就可以直接从侧边栏访问了。

在Ubuntu中添加书签

打开Files。进入你想要保存快速访问的目录。你需要在标记书签的目录里面。

现在,你有两种方法:

方法1:

当你在Files(Ubuntu中的文件管理器)中时,查看顶部菜单。你会看到书签按钮。点击它你会看到将当前路径保存为书签的选项。

方法 2:

你可以直接按下Ctrl+D就可以将当前位置保存为书签。

如你所见,这里左边栏就有一个新添加的Copy目录:

管理书签

如果你不想要太多的书签或者你错误地添加了一个书签,你可以很简单地删除它。按下Ctrl+B查看所有的书签。现在选择想要删除的书签并点击删除。

这就是在Ubuntu中管理书签需要做的。我知道这对于大多数用户而言很简单,但是这也许多Ubuntu的新手而言或许还有用。


via: http://itsfoss.com/add-remove-bookmarks-ubuntu/

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

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

5 个在 Linux 中管理文件类型和系统时间的有用命令

对于想学习 Linux 的初学者来说要适应使用命令行或者终端可能非常困难。由于终端比图形用户界面程序更能帮助用户控制 Linux 系统,我们必须习惯在终端中运行命令。因此为了有效记忆 Linux 不同的命令,你应该每天使用终端并明白怎样将命令和不同选项以及参数一同使用。

在 Linux 中管理文件类型和设置时间

在 Linux 中管理文件类型和设置时间

请先查看我们 Linux 小技巧系列之前的文章:

在这篇文章中,我们打算看看终端中 5 个和文件以及时间相关的提示和技巧。

Linux 中的文件类型

在 Linux 中,一切皆文件,你的设备、目录以及普通文件都认为是文件。

Linux 系统中文件有不同的类型:

  • 普通文件:可能包含命令、文档、音频文件、视频、图像,归档文件等。
  • 设备文件:系统用于访问你硬件组件。

这里有两种表示存储设备的设备文件:块文件,例如硬盘,它们以块读取数据;字符文件,以逐个字符读取数据。

  • 硬链接和软链接:用于在 Linux 文件系统的任意地方访问文件。
  • 命名管道和套接字:允许不同的进程之间进行交互。

1. 用 ‘file’ 命令确定文件类型

你可以像下面这样使用 file 命令确定文件的类型。下面的截图显示了用 file 命令确定不同文件类型的例子。

tecmint@tecmint ~/Linux-Tricks $ dir
BACKUP                    master.zip
crossroads-stable.tar.gz          num.txt
EDWARD-MAYA-2011-2012-NEW-REMIX.mp3   reggea.xspf
Linux-Security-Optimization-Book.gif  tmp-link
tecmint@tecmint ~/Linux-Tricks $ file BACKUP/
BACKUP/: directory
tecmint@tecmint ~/Linux-Tricks $ file master.zip
master.zip: Zip archive data, at least v1.0 to extract
tecmint@tecmint ~/Linux-Tricks $ file crossroads-stable.tar.gz
crossroads-stable.tar.gz: gzip compressed data, from Unix, last modified: Tue Apr  5 15:15:20 2011
tecmint@tecmint ~/Linux-Tricks $ file Linux-Security-Optimization-Book.gif
Linux-Security-Optimization-Book.gif: GIF image data, version 89a, 200 x 259
tecmint@tecmint ~/Linux-Tricks $ file EDWARD-MAYA-2011-2012-NEW-REMIX.mp3
EDWARD-MAYA-2011-2012-NEW-REMIX.mp3: Audio file with ID3 version 2.3.0, contains: MPEG ADTS, layer III, v1, 192 kbps, 44.1 kHz, JntStereo
tecmint@tecmint ~/Linux-Tricks $ file /dev/sda1
/dev/sda1: block special
tecmint@tecmint ~/Linux-Tricks $ file /dev/tty1
/dev/tty1: character special

2. 用 ‘ls’ 和 ‘dir’ 命令确定文件类型

确定文件类型的另一种方式是用 ls 和 dir 命令显示一长串结果。

用 ls -l 确定一个文件的类型。

当你查看文件权限时,第一个字符显示了文件类型,其它字符显示文件权限。

tecmint@tecmint ~/Linux-Tricks $ ls -l
total 6908
drwxr-xr-x 2 tecmint tecmint    4096 Sep  9 11:46 BACKUP
-rw-r--r-- 1 tecmint tecmint 1075620 Sep  9 11:47 crossroads-stable.tar.gz
-rwxr----- 1 tecmint tecmint 5916085 Sep  9 11:49 EDWARD-MAYA-2011-2012-NEW-REMIX.mp3
-rw-r--r-- 1 tecmint tecmint   42122 Sep  9 11:49 Linux-Security-Optimization-Book.gif
-rw-r--r-- 1 tecmint tecmint   17627 Sep  9 11:46 master.zip
-rw-r--r-- 1 tecmint tecmint       5 Sep  9 11:48 num.txt
-rw-r--r-- 1 tecmint tecmint       0 Sep  9 11:46 reggea.xspf
-rw-r--r-- 1 tecmint tecmint       5 Sep  9 11:47 tmp-link

使用 ls -l 确定块和字符文件

tecmint@tecmint ~/Linux-Tricks $ ls -l /dev/sda1
brw-rw---- 1 root disk 8, 1 Sep  9 10:53 /dev/sda1
tecmint@tecmint ~/Linux-Tricks $ ls -l /dev/tty1
crw-rw---- 1 root tty 4, 1 Sep  9 10:54 /dev/tty1

使用 dir -l 确定一个文件的类型。

tecmint@tecmint ~/Linux-Tricks $ dir -l
total 6908
drwxr-xr-x 2 tecmint tecmint    4096 Sep  9 11:46 BACKUP
-rw-r--r-- 1 tecmint tecmint 1075620 Sep  9 11:47 crossroads-stable.tar.gz
-rwxr----- 1 tecmint tecmint 5916085 Sep  9 11:49 EDWARD-MAYA-2011-2012-NEW-REMIX.mp3
-rw-r--r-- 1 tecmint tecmint   42122 Sep  9 11:49 Linux-Security-Optimization-Book.gif
-rw-r--r-- 1 tecmint tecmint   17627 Sep  9 11:46 master.zip
-rw-r--r-- 1 tecmint tecmint       5 Sep  9 11:48 num.txt
-rw-r--r-- 1 tecmint tecmint       0 Sep  9 11:46 reggea.xspf
-rw-r--r-- 1 tecmint tecmint       5 Sep  9 11:47 tmp-link

3. 统计指定类型文件的数目

下面我们来看看在一个目录中用 ls,grepwc 命令统计指定类型文件数目的技巧。命令之间的交互通过命名管道完成。

  • grep – 用户根据给定模式或正则表达式进行搜索的命令。
  • wc – 用于统计行、字和字符的命令。

统计普通文件的数目

在 Linux 中,普通文件用符号 表示。

tecmint@tecmint ~/Linux-Tricks $ ls -l | grep ^- | wc -l
7

统计目录的数目

在 Linux 中,目录用符号 d 表示。

tecmint@tecmint ~/Linux-Tricks $ ls -l | grep ^d | wc -l
1

统计符号链接和硬链接的数目

在 Linux 中,符号链接和硬链接用符号 l 表示。

tecmint@tecmint ~/Linux-Tricks $ ls -l | grep ^l | wc -l

统计块文件和字符文件的数目

在 Linux 中,块和字符文件用符号 bc 表示。

tecmint@tecmint ~/Linux-Tricks $ ls -l /dev | grep ^b | wc -l
37
tecmint@tecmint ~/Linux-Tricks $ ls -l /dev | grep ^c | wc -l
159

4. 在 Linux 系统中查找文件

下面我们来看看在 Linux 系统中查找文件一些命令,它们包括 locate、find、whatis 和 which 命令。

用 locate 命令查找文件

在下面的输出中,我想要定位系统中的 Samba 服务器配置文件

tecmint@tecmint ~/Linux-Tricks $ locate samba.conf
/usr/lib/tmpfiles.d/samba.conf
/var/lib/dpkg/info/samba.conffiles

用 find 命令查找文件

想要学习如何在 Linux 中使用 find 命令,你可以阅读我们以下的文章,里面列出了 find 命令的 30 多个例子和使用方法。

用 whatis 命令定位命令

whatis 命令通常用于定位命令,它很特殊,因为它给出关于一个命令的信息,它还能查找配置文件和命令的帮助手册条目。

tecmint@tecmint ~/Linux-Tricks $ whatis bash
bash (1)             - GNU Bourne-Again SHell
tecmint@tecmint ~/Linux-Tricks $ whatis find
find (1)             - search for files in a directory hierarchy
tecmint@tecmint ~/Linux-Tricks $ whatis ls
ls (1)               - list directory contents

用 which 命令定位命令

which 命令用于定位文件系统中的命令。

tecmint@tecmint ~/Linux-Tricks $ which mkdir
/bin/mkdir
tecmint@tecmint ~/Linux-Tricks $ which bash
/bin/bash
tecmint@tecmint ~/Linux-Tricks $ which find
/usr/bin/find
tecmint@tecmint ~/Linux-Tricks $ $ which ls
/bin/ls

5.处理 Linux 系统的时间

在联网环境中,保持你 Linux 系统时间准确是一个好的习惯。Linux 系统中有很多服务要求时间正确才能在联网条件下正常工作。

让我们来看看你可以用来管理你机器时间的命令。在 Linux 中,有两种方式管理时间:系统时间和硬件时间。

系统时间由系统时钟管理,硬件时间由硬件时钟管理。

要查看你的系统时间、日期和时区,像下面这样使用 date 命令。

tecmint@tecmint ~/Linux-Tricks $ date
Wed Sep  9 12:25:40 IST 2015

像下面这样用 date -s 或 date -set=“STRING” 设置系统时间。

tecmint@tecmint ~/Linux-Tricks $ sudo date -s "12:27:00"
Wed Sep  9 12:27:00 IST 2015
tecmint@tecmint ~/Linux-Tricks $ sudo date --set="12:27:00"
Wed Sep  9 12:27:00 IST 2015

你也可以像下面这样设置时间和日期。

tecmint@tecmint ~/Linux-Tricks $ sudo date 090912302015
Wed Sep  9 12:30:00 IST 2015

使用 cal 命令从日历中查看当前日期。

tecmint@tecmint ~/Linux-Tricks $ cal
   September 2015
Su Mo Tu We Th Fr Sa
       1  2  3  4  5
 6  7  8  9 10 11 12
13 14 15 16 17 18 19
20 21 22 23 24 25 26
27 28 29 30

使用 hwclock 命令查看硬件时钟时间。

tecmint@tecmint ~/Linux-Tricks $ sudo hwclock
Wednesday 09 September 2015 06:02:58 PM IST  -0.200081 seconds

要设置硬件时钟时间,像下面这样使用 hwclock –set –date=“STRING” 命令。

tecmint@tecmint ~/Linux-Tricks $ sudo hwclock --set --date="09/09/2015 12:33:00"
tecmint@tecmint ~/Linux-Tricks $ sudo hwclock
Wednesday 09 September 2015 12:33:11 PM IST  -0.891163 seconds

系统时间是由硬件时钟时间在启动时设置的,系统关闭时,硬件时间被重置为系统时间。

因此你查看系统时间和硬件时间时,它们是一样的,除非你更改了系统时间。当你的 CMOS 电量不足时,硬件时间可能不正确。

你也可以像下面这样使用硬件时钟的时间设置系统时间。

$ sudo hwclock --hctosys

也可以像下面这样用系统时钟时间设置硬件时钟时间。

$ sudo hwclock --systohc

要查看你的 Linux 系统已经运行了多长时间,可以使用 uptime 命令。

tecmint@tecmint ~/Linux-Tricks $ uptime
12:36:27 up  1:43,  2 users,  load average: 1.39, 1.34, 1.45
tecmint@tecmint ~/Linux-Tricks $ uptime -p
up 1 hour, 43 minutes
tecmint@tecmint ~/Linux-Tricks $ uptime -s
2015-09-09 10:52:47

总结

对于初学者来说理解 Linux 中的文件类型是一个好的尝试,同时时间管理也非常重要,尤其是在需要可靠有效地管理服务的服务器上。希望这篇指南能对你有所帮助。如果你有任何反馈,别忘了给我们写评论。和我们保持联系。


via: http://www.tecmint.com/manage-file-types-and-set-system-time-in-linux/

作者:Aaron Kili 译者:ictlyh 校对:wxy

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

容器生态圈项目一览:引擎、编排、OS、Registry、监控

Docker 是近一两年来发展最快的技术。很多公司都在使用 Docker(或容器)技术。有的只是用 Docker 来构建本地开发环境,但越来越多的公司是在利用容器技术彻底改造已有的架构和部署流程。本文对当今的容器生态环境做一个简单介绍。

引擎 / 运行环境

容器引擎是容器技术的核心。引擎通常以一些说明性的描述,比如Dockerfile,来创建和运行容器。谈论 Docker 时,一般指的就是 Docker 引擎。

  • Docker Engine 是当前最流行的引擎,也是事实的工业标准。
  • rkt 是CoreOS 团队主导的开源引擎,用于替代 Docker 引擎。

支持 Docker 的云服务商

云服务商已经在他们的平台上提供运行容器的解决方案。一些是内部的解决方案,其它的则是基于开源软件。当然在云主机上安装 Docker来运行容器是没有任何问题的。不过,大多数云服务商的容器服务更进一步地提供了更为简洁友好的用户管理界面。

容器编排工具

容器编排工具现在是最具竞争的领域之一。管理少数几个容器很简单,但是调度、管理以及监控大规模容器很具有挑战性。容器编排工具处理多种多样任务,比如查找最优的位置或者服务器来运行容器,处理失败的任务,分享储存卷或者创建负载均衡与容器间通讯的覆盖网络。

常见的编排项目有:

  • Kubernetes Google 开源的工具,它是基于Google的内部容器设施,并且在功能特性方面是当前最先进的工具。
  • Docker Swarm 允许在Docker集群中调度容器,与 Docker 环境紧密集成。
  • Rancher 在机器集群上以 stack(linked容器)为单位管理容器。它有直观的界面和良好的文档以及本身运行在容器内部。
  • Mesosphere 通用的数据中心管理系统。不是专为 Docker 开发,但是能轻松管理容器,也可以与其它编排系统如 Kubernetes 集成,或者与像Hadoop的传统服务集成。
  • CoreOS fleet CoreOS操作系统的一部分,管理在 CoreOS 集群中任何调度命令(比如运行Docker或者rkt容器)。
  • Nomad 通用的应用调度工具,内置支持 Docker。
  • Centurion Newrelic 的内部部署工具。
  • Flocker 运行在不同主机的容器间数据/Volume迁移工具。
  • Weave Run 提供微服务架构的服务发现、路由、负载均衡和地址管理。

操作系统

你可以在任何操作系统来运行容器,但是企业正越来越多的容器化他们整个基础设施。因此,为Docker或者相关服务运行一个最小化操作系统是非常有意义的。

  • CoreOS 为自动更新设计以及着重在机器集群之间的运行容器。不仅与systemd的fleet调度器交付,而且支持其它编排系统。
  • Project Atomic 运行Docker、Kubernetes、rpm、systemd的轻量级操作系统。
  • Rancher OS 只有 20MB 大小用容器运行整个操作系统。 它区分系统容器 和 用户容器,他们运行在分离的Docker守护进程中。
  • Project Photon VMWare 开源的工具。

容器镜像仓库 Registry

镜像Registry是“容器镜像的GitHub”,你可以与你的团队或者其他人分享容器镜像。

  • Docker Registry 最流行的开源registry。你可以在自己的设施上运行或者使用Docker Hub。
  • Docker Hub 提供了直观的界面、自动化构建、私有仓库以及众多官方镜像。
  • Quay.io CoreOS 开发的容器仓库。
  • CoreOS Enterprise Registry 着重提供细化权限和审计跟踪。

监控

容器输出的日志可以很方便与已有日志收集工具整合。容器监控软件通常关注容器的资源使用情况(CPU、内存)。

  • cAdvisor Google 开源项目。分析容器的资源使用和性能特性,可以用 InfluxDB 作为数据存储,以便后续分析。
  • Datadog Docker 收集容器的运行信息,发送到 Datadog 分析。
  • NewRelic Docker 发送容器统计信息到 NewRelic 的云服务。
  • Sysdig 监控容器资源使用情况。
  • Weave Scope 自动生成容器关系图,有助于理解、监控和控制应用服务。
  • AppFormix 实时基础设施监控,支持 Docker 容器。

PHP 编程中 10 个最常见的错误,你犯过几个?

PHP 编程中 10 个最常见的错误,你犯过几个?
PHP 编程中 10 个最常见的错误,你犯过几个?

错误1:foreach循环后留下悬挂指针

在foreach循环中,如果我们需要更改迭代的元素或是为了提高效率,运用引用是一个好办法:

$arr = array(1,2,3,4);
foreach($arr as&$value){
    $value = $value *2;
}
// $arr is now array(2, 4, 6, 8)

这里有个问题很多人会迷糊。循环结束后,$value并未销毁,$value其实是数组中最后一个元素的引用,这样在后续对$value的使用中,如果不知道这一点,会引发一些莫名奇妙的错误:)看看下面这段代码:

$array =[1,2,3];
echo implode(',', $array),"n";
foreach($array as&$value){}    // by reference
echo implode(',', $array),"n";
foreach($array as $value){}     // by value (i.e., copy)
echo implode(',', $array),"n";

上面代码的运行结果如下:

1,2,3
1,2,3
1,2,2

你猜对了吗?为什么是这个结果呢?

我们来分析下。第一个循环过后,$value是数组中最后一个元素的引用。第二个循环开始:

  • 第一步:复制$arr[0]到$value(注意此时$value是$arr[2]的引用),这时数组变成[1,2,1]
  • 第二步:复制$arr[1]到$value,这时数组变成[1,2,2]
  • 第三步:复制$arr[2]到$value,这时数组变成[1,2,2]

综上,最终结果就是1,2,2

避免这种错误最好的办法就是在循环后立即用unset函数销毁变量:

$arr = array(1,2,3,4);
foreach($arr as&$value){
    $value = $value *2;
}
unset($value);   // $value no longer references $arr[3]

错误2:对isset()函数行为的错误理解

对于isset()函数,变量不存在时会返回false,变量值为null时也会返回false。这种行为很容易把人弄迷糊。。。看下面的代码:

$data = fetchRecordFromStorage($storage, $identifier);
if(!isset($data['keyShouldBeSet']){
    // do something here if 'keyShouldBeSet' is not set
}

写这段代码的人本意可能是如果$data[‘keyShouldBeSet’]未设置,则执行对应逻辑。但问题在于即使$data[‘keyShouldBeSet’]已设置,但设置的值为null,还是会执行对应的逻辑,这就不符合代码的本意了。

下面是另外一个例子:

if($_POST['active']){
    $postData = extractSomething($_POST);
}
// ...
if(!isset($postData)){
    echo 'post not active';
}

上 面的代码假设$_POST[‘active’]为真,那么$postData应该被设置,因此isset($postData)会返回true。反之,上 面代码假设isset($postData)返回false的唯一途径就是$_POST[‘active’]也返回false。

真是这样吗?当然不是!

即使$_POST[‘active’]返回true,$postData也有可能被设置为null,这时isset($postData)就会返回false。这就不符合代码的本意了。

如果上面代码的本意仅是检测$_POST[‘active’]是否为真,下面这样实现会更好:

if($_POST['active']){
    $postData = extractSomething($_POST);
}
// ...
if($_POST['active']){
    echo 'post not active';
}

判断一个变量是否真正被设置(区分未设置和设置值为null),array_key_exists()函数或许更好。重构上面的第一个例子,如下:

$data = fetchRecordFromStorage($storage, $identifier);
if(! array_key_exists('keyShouldBeSet', $data)){
    // do this if 'keyShouldBeSet' isn't set
}

另外,结合get_defined_vars()函数,我们可以更加可靠的检测变量在当前作用域内是否被设置:

if(array_key_exists('varShouldBeSet', get_defined_vars())){
    // variable $varShouldBeSet exists in current scope
}

错误3:混淆返回值和返回引用

考虑下面的代码:

classConfig
{
    private $values =[];
    publicfunction getValues(){
        return $this->values;
    }
}
$config =newConfig();
$config->getValues()['test']='test';
echo $config->getValues()['test'];

运行上面的代码,将会输出下面的内容:

PHP Notice:  Undefined index: test in/path/to/my/script.php on line 21

问题出在哪呢?问题就在于上面的代码混淆了返回值和返回引用。在PHP中,除非你显示的指定返回引用,否则对于数组PHP是值返回,也就是数组的拷贝。因此上面代码对返回数组赋值,实际是对拷贝数组进行赋值,非原数组赋值。

// getValues() returns a COPY of the $values array, so this adds a 'test' element
// to a COPY of the $values array, but not to the $values array itself.
$config->getValues()['test']='test';
// getValues() again returns ANOTHER COPY of the $values array, and THIS copy doesn't
// contain a 'test' element (which is why we get the "undefined index" message).
echo $config->getValues()['test'];

下面是一种可能的解决办法,输出拷贝的数组,而不是原数组:

$vals = $config->getValues();
$vals['test']='test';
echo $vals['test'];

如果你就是想要改变原数组,也就是要反回数组引用,那应该如何处理呢?办法就是显示指定返回引用即可:

classConfig
{
    private $values =[];
    // return a REFERENCE to the actual $values array
    publicfunction&getValues(){
        return $this->values;
    }
}
$config =newConfig();
$config->getValues()['test']='test';
echo $config->getValues()['test'];

经过改造后,上面代码将会像你期望那样会输出test。

我们再来看一个例子会让你更迷糊的例子:

classConfig
{
    private $values;
    // using ArrayObject rather than array
    publicfunction __construct(){
        $this->values =newArrayObject();
    }
    publicfunction getValues(){
        return $this->values;
    }
}
$config =newConfig();
$config->getValues()['test']='test';
echo $config->getValues()['test'];

如果你想的是会和上面一样输出“ Undefined index”错误,那你就错了。代码会正常输出“test”。原因在于PHP对于对象默认就是按引用返回的,而不是按值返回。

综上所述,我们在使用函数返回值时,要弄清楚是值返回还是引用返回。PHP中对于对象,默认是引用返回,数组和内置基本类型默认均按值返回。这个要与其它语言区别开来(很多语言对于数组是引用传递)。

像其它语言,比如java或C#,利用getter或setter来访问或设置类属性是一种更好的方案,当然PHP默认不支持,需要自己实现:

classConfig
{
    private $values =[];
    publicfunction setValue($key, $value){
        $this->values[$key]= $value;
    }
    publicfunction getValue($key){
        return $this->values[$key];
    }
}
$config =newConfig();
$config->setValue('testKey','testValue');
echo $config->getValue('testKey');    // echos 'testValue'

上面的代码给调用者可以访问或设置数组中的任意值而不用给与数组public访问权限。感觉怎么样:) 

错误4:在循环中执行sql查询

在PHP编程中发现类似下面的代码并不少见:

$models =[];
foreach($inputValues as $inputValue){
    $models[]= $valueRepository->findByValue($inputValue);
}

当然上面的代码是没有什么错误的。问题在于我们在迭代过程中$valueRepository->findByValue()可能每次都执行了sql查询:

$result = $connection->query("SELECT `x`,`y` FROM `values` WHERE `value`=". $inputValue);

如果迭代了10000次,那么你就分别执行了10000次sql查询。如果这样的脚本在多线程程序中被调用,那很可能你的系统就挂了。。。

在编写代码过程中,你应该要清楚什么时候应该执行sql查询,尽可能一次sql查询取出所有数据。

有一种业务场景,你很可能会犯上述错误。假设一个表单提交了一系列值(假设为IDs),然后为了取出所有ID对应的数据,代码将遍历IDs,分别对每个ID执行sql查询,代码如下所示:

$data =[];
foreach($ids as $id){
    $result = $connection->query("SELECT `x`, `y` FROM `values` WHERE `id` = ". $id);
    $data[]= $result->fetch_row();
}

但同样的目的可以在一个sql中更加高效的完成,代码如下:

$data =[];
if(count($ids)){
    $result = $connection->query("SELECT `x`, `y` FROM `values` WHERE `id` IN (". implode(',', $ids));
    while($row = $result->fetch_row()){
        $data[]= $row;
    }
}

错误5:内存使用低效和错觉

一次sql查询获取多条记录比每次查询获取一条记录效率肯定要高,但如果你使用的是php中的mysql扩展,那么一次获取多条记录就很可能会导致内存溢出。

我们可以写代码来实验下(测试环境: 512MB RAM、MySQL、php-cli):

// connect to mysql
$connection =new mysqli('localhost','username','password','database');
// create table of 400 columns
$query ='CREATE TABLE `test`(`id` INT NOT NULL PRIMARY KEY AUTO_INCREMENT';
for($col =0; $col <400; $col++){
    $query .=", `col$col` CHAR(10) NOT NULL";
}
$query .=');';
$connection->query($query);
// write 2 million rows
for($row =0; $row <2000000; $row++){
    $query ="INSERT INTO `test` VALUES ($row";
    for($col =0; $col <400; $col++){
        $query .=', '. mt_rand(1000000000,9999999999);
    }
    $query .=')';
    $connection->query($query);
}

现在来看看资源消耗:

// connect to mysql
$connection =new mysqli('localhost','username','password','database');
echo "Before: ". memory_get_peak_usage()."n";
$res = $connection->query('SELECT `x`,`y` FROM `test` LIMIT 1');
echo "Limit 1: ". memory_get_peak_usage()."n";
$res = $connection->query('SELECT `x`,`y` FROM `test` LIMIT 10000');
echo "Limit 10000: ". memory_get_peak_usage()."n";

输出结果如下:

Before:224704
Limit1:224704
Limit10000:224704

根据内存使用量来看,貌似一切正常。为了更加确定,试着一次获取100000条记录,结果程序得到如下输出:

PHP Warning:  mysqli::query():(HY000/2013):
     Lost connection to MySQL server during query in/root/test.php on line 11

这是怎么回事呢?

问 题出在php的mysql模块的工作方式,mysql模块实际上就是libmysqlclient的一个代理。在查询获取多条记录的同时,这些记录会直接 保存在内存中。由于这块内存不属于php的内存模块所管理,所以我们调用memory_get_peak_usage()函数所获得的值并非真实使用内存 值,于是便出现了上面的问题。

我们可以使用mysqlnd来代替mysql,mysqlnd编译为php自身扩展,其内存使用由php内存管理模块所控制。如果我们用mysqlnd来实现上面的代码,则会更加真实的反应内存使用情况:

Before:232048
Limit1:324952
Limit10000:32572912

更加糟糕的是,根据php的官方文档,mysql扩展存储查询数据使用的内存是mysqlnd的两倍,因此原来的代码使用的内存是上面显示的两倍左右。

为了避免此类问题,可以考虑分几次完成查询,减小单次查询数据量:

$totalNumberToFetch =10000;
$portionSize =100;
for($i =0; $i <= ceil($totalNumberToFetch / $portionSize); $i++){
    $limitFrom = $portionSize * $i;
    $res = $connection->query(
                         "SELECT `x`,`y` FROM `test` LIMIT $limitFrom, $portionSize");
}

联系上面提到的错误4可以看出,在实际的编码过程中,要做到一种平衡,才能既满足功能要求,又能保证性能。

为什么 Laravel 会成为最成功的 PHP 框架?

Laravel是一个有着美好前景的年轻框架,它的社区充满着活力,相关的文档和教程完整而清晰,并为快速、安全地开发现代应用程序提供了必要的功能。在近几年对PHP框架流行度的统计中,Laravel始终遥遥领先。那么是什么让Laravel成为最成功的PHP框架?

Laravel

2011年,Taylor Otwell将Laravel作为一种包含全新现代方法的框架介绍给大家。Laravel最初的设计是为了面向MVC架构的,它可以满足如事件处理、用户身份验证等各种需求。另外它还有一个由管理数据库强力支持,用于管理模块化和可扩展性代码的软件包管理器。

Laravel以其简洁、优雅的特性赢得了大家的广泛关注,无论是专家还是新手,在开发PHP项目的时候,都会第一时间的想到Laravel。本文我们将讨论为什么Laravel会成为最成功的PHP框架。

模块化和可扩展性

Laravel注重代码的模块化和可扩展性。你可以在包含超过5500个程序包的Packalyst目录中找到你想要添加的任何文件。Laravel的目标是让你能够找到任何想要的文件。

微服务和程序接口

Lumen是一个由laravel衍生的专注于精简的微框架。它高性能的程序接口可让你更加简单快速的开发微型项目。Lumen使用最小的配置集成了所有laravel的重要特性,你可以通过将代码复制到laravel项目的方式将完整的框架迁移过来。

get('/', function() {
   return view('lumen');
});
$app->post('framework/{id}', function($framework) {
   $this->dispatch(new Energy($framework));
});

HTTP路径

Laravel拥有类似于Ruby on Rails的,快速、高效的路由系统。它可以让用户通过在浏览器上输入路径的方式让应用程序的各部分相关联。

HTTP中间件

Route::get('/', function () {
   return 'Hello World';
});

应用程序可受到中间件的保护——中间件会处理分析和过滤服务器上的HTTP请求。你可以安装中间件,用于验证注册用户,并避免如跨站脚本(XSS)或其它的安全状况的问题。

input('age') <= 200) {
         return redirect('home');
    }
    return $next($request);
  }
}

缓存

你的应用程序可得到一个健壮的缓存系统,通过对其进行调整,可以让应用程序的加载更加快速,这可以给你的用户提供最好的使用体验。

Cache::extend('mongo', function($app) {
   return Cache::repository(new MongoStore);
});

身份验证

安全是至关重要的。Laravel自带对本地用户的身份验证,并可以使用“remember” 选项来记住用户。它还可以让你例如一些额外参数,例如显示是否为活跃的用户。

if (Auth::attempt(['email' => $email, 'password' => $password, 'active' => 1 ], $remember)) {
   // The user is being remembered...
}

各种集成

Laravel Cashier可以满足你要开发支付系统所需要的一切需求。除此之外,它还同步并集成了用户身份验证系统。所以,你不再需要担心如何将计费系统集成到开发当中了。

$user = User::find(1);
$user->subscription('monthly')->create($creditCardToken);

任务自动化

Elixir是一个可让我们使用Gulp定义任务的Laravel程序接口,我们可以使用Elixir定义可精简CSS 和JavaScript的预处理器。 

elixir(function(mix) {
   mix.browserify('main.js');
 });

加密

一个安全的应用程序应该做到可把数据进行加密。使用Laravel,可以启用OpenSSL安全加密算法AES-256-CBC来满足你所有的需求。另外,所有的加密值都是由检测加密信息是否被改变的验证码所签署的。

use Illuminate\Contracts\Encryption\DecryptException;
try {
   $decrypted = Crypt::decrypt($encryptedValue);
} catch (DecryptException $e) {
   //
}

事件处理

应用程序中事件的定义、记录和聆听都非常迅速。EventServiceProvider事件中的listen包含记录在你应用程序上所有事件的列表。

protected $listen = [
  'App\Events\PodcastWasPurchased' => [
     'App\Listeners\EmailPurchaseConfirmation',
  ],
];

分页

在Laravel中分页是非常容易的因为它能够根据用户的浏览器当前页面生成一系列链接。 

paginate(15);
    return view('user.index', ['users' => $users]);
  }
}

对象关系化映射(ORM)

Laravel包含一个处理数据库的层,它的对象关系化映射被称为Eloquent。另外这个也适用于PostgreSQL

$users = User::where('votes', '>', 100)->take(10)->get();
foreach ($users as $user) {
  var_dump($user->name);
}

单元测试

单元测试的开发是一个耗费大量时间的任务,但是它却是保证我们的应用程序保持正常工作的关键。Laravel中可使用PHPUnit执行单元测试。

visit('/')->see('Laravel 5')->dontSee('Rails');
  }
}

待办事项清单

Laravel提供在后台使用待办事项清单(to do list)处理复杂、漫长流程的选择。它可以让我们异步处理某些流程而不需要用户的持续导航。

Queue :: push ( new  SendEmail ( $ message ));

Unicode 和 UTF-8 是什么关系?

绝大多数程序员都听说过 Unicode 和 UTF-8,但是清楚它们之间关系的人就不多了,关于这个问题,与其苍白的陈述它们的概念,不如举例子说明来得自然。

Unicode 和 UTF-8 是什么关系?
Unicode 和 UTF-8 是什么关系?

我前些天碰到一个需求:随机生成几个汉字。原本我便对编码之类的问题发怵,所以完全搞不清楚状况,无奈之下我便上网搜索了一个 PHP 版本的实现:

不过代码里的「19968」和「40869」是什么玩意?这又牵扯到 Unicode code points,为了更好的说明问题,我们需要把如上十进制转换成十六进制:

shell> php -r 'echo dechex(19968);'
4e00
shell> php -r 'echo dechex(40908);'
9fcc

在 Unicode 官方网站,我们能查到 Unihan Grid Index,其中 CJK Unified Ideographs 部分包含了大部分的汉字,其 code points 恰恰是从 U+4E00 到 U+9FCC!

单单上面一个例子还不足以说明问题,下面我们挑选一个「」字深入说明一下:

Unicode 和 UTF-8 是什么关系?
Unicode 和 UTF-8 是什么关系?

Unicode

因为我们编码是 UTF-8,所以就先看看「博」字的 UTF-8 编码是什么:

代码看上去有点冗长,实际上利用 pack/unpack 函数可以很简单的实现类似的逻辑:

shell> php -r 'var_dump(unpack("H6", "博"));'
array(1) {
  ["codes"]=>
  string(6) "e58d9a"
}

于是乎「博」字的 UTF-8 编码是「e58d9a」,再看怎么得到 unicode code point:

shell> php -r 'echo base_convert("e58d9a", 16, 2);'
11100101 10001101 10011010

如上拿到了「博」字的二进制表示,实际上其 unicode code point 就隐藏在这里。通常汉字用 UTF-8 表示时是三个字节,格式为「1110XXXX 10XXXXXX 10XXXXXX」,除掉标志位,把剩余对应位置上的数据抽取出来连接在一起,就得到了 Unicode code point,也就是「0101 001101 011010」,剩下的就简单了,把它从二进制转换成十六进制即可:

shell> php -r 'echo base_convert("0101001101011010", 2, 16);'
535a

需要说明的是,如果你仅仅看「博」字,会发现其 Unicode code point 和 UTF-16 是一样的,很容易据此认为它们是等同的概念,实际上这个结论仅仅在双字节(UCS-2)时才是成立的,一旦大于两个字节,就不成立了,有兴趣的可以参考相应的例子

到底 Unicode 和 UTF-8 是什么关系?一句话:Unicode 是字符集;UTF-8 是编码

看完这些问题后,你还会说自己懂 C 语言么?

这篇文章的目的是让每个程序员(特别是 C 程序员)说:我真的不懂 C。我想要让大家看到 C 语言的那些阴暗角落比我们想象中更近,甚至那些平常的代码中就包含着未定义的行为。

看完这些问题后,你还会说自己懂 C 语言么?
看完这些问题后,你还会说自己懂 C 语言么?

这篇文章设置了一系列的问题和答案。所有的例子都是从源代码中单独分离出来的。

1、这段代码正确吗?

int i;
int i = 10;

Q:这段代码正确吗?是否会因为变量被定义了两次而导致错误的出现?注意这是源于同一个源码文件,而不是函数体或代码段的一部分。

A:是的,这段代码是正确的。第一行是临时的定义直到编译器处理了第二行的定义之后才成为正式的“定义”。

2、这段代码正确吗?

extern void bar(void);
void foo(int *x)
{
  int y = *x;  /* (1) */
  if(!x)       /* (2) */
  {
    return;    /* (3) */
  }
  bar();
  return;
}

Q: 这样写的结果是即使 x 是空指针 bar() 函数都会被调用,并且程序不会崩溃。这是否是优化器的错误,或者全部是正确的?

A: 全部都是正确的。如果 x 是空指针,未定义的行为出现在第 (1) 行, 没有人欠程序员什么,所以程序并不会在第 (1) 行崩溃, 也不会试图在第 (2) 行返回假如已经成功运行第 (1) 行。让我们来探讨编译器遵循的规则,它都按如下的方式进行。在对第 (1) 行的分析之后,编译器认为 x 不会是一个空指针,于是第 (2) 行和 第 (3) 行就被认定为是没用的代码。变量 y 被当做没用的变量去除。从内存中读取的操作也会被去除,因为 *x 并不符合易变类型(volatile)。

这就是无用的变量如何导致空指针检查失效的例子。

3、优化后结果是否不同?

有这样一个函数:

#define ZP_COUNT 10
void func_original(int *xp, int *yp, int *zp)
{
  int i;
  for(i = 0; i < ZP_COUNT; i++)
  {
    *zp++ = *xp + *yp;
  }
}

有人想要按如下方式来优化它:

void func_optimized(int *xp, int *yp, int *zp)
{
  int tmp = *xp + *yp;
  int i;
  for(i = 0; i < ZP_COUNT; i++)
  {
    *zp++ = tmp;
  }
}

Q:调用原始的函数和调用优化后的函数,对于变量 zp 是否有可能获得不同的结果?

A:这是可能的,当 yp == zp 时结果就不同。

4、是否可能返回无穷大?

double f(double x)
{
  assert(x != 0.);
  return 1. / x;
}

Q: 这个函数是否可能返回无穷大(inf) ?假设浮点数运算是按照IEEE 754 标准(大部分机器遵循)执行的, 并且断言语句是可用的(NDEBUG 并没有被定义)。

A:是的,这是可以的。通过传入一个非规范化的 x 的值,比如 1e-309.

5、找出 bug

int my_strlen(const char *x)
{
  int res = 0;
  while(*x)
  {
    res++;
    x++;
  }
  return res;
}

Q: 上面提供的函数应该返回以空终止字符结尾的字符串长度,找出其中存在的一个 bug 。

A: 使用 int 类型来存储对象的大小是错误的,因为无法保证 int 类型能够存下任何对象的大小,应该使用 size_t。

6、为什么是死循环?

#include 
#include 
int main()
{
  const char *str = "hello";
  size_t length = strlen(str);
  size_t i;
  for(i = length - 1; i >= 0; i--)
  {
    putchar(str[i]);
  }
  putchar('n');
  return 0;
}

Q: 这个循环是死循环。这是为什么?

A: size_t 是无符号类型。 如果 i 是无符号类型, 那么 i >= 0 永远都是正确的。

7、结果不同?

#include 
void f(int *i, long *l)
{
  printf("1. v=%ldn", *l); /* (1) */
  *i = 11;                  /* (2) */
  printf("2. v=%ldn", *l); /* (3) */
}
int main()
{
  long a = 10;
  f((int *) &a, &a);
  printf("3. v=%ldn", a);
  return 0;
}

这个程序分别用两个不同的编译器编译并且在一台小端字节序的机器上运行。获得了如下两种不同的结果:

1. v=10    2. v=11    3. v=11
1. v=10    2. v=10    3. v=11

Q:你如何解释第二种结果?

A:所给程序存在未定义的行为。程序违反了编译器的强重叠规则(strict aliasing)。虽然 int 在第 (2) 行被改变了,但是编译器可以假设任何的 long 都没有改变。我们不能间接引用那些和其他不兼容类型指针相重名的指针。这就是编译器之所以可以传递和在第一行的执行过程中被读取的相同的 long (第(3)行)的原因。

8、代码是否正确?

#include 
int main()
{
  int array[] = { 0, 1, 2 };
  printf("%d %d %dn", 10, (5, array[1, 2]), 10);
}

Q: 这个代码是否是正确的?如果不存在未定义行为,那么它会输出什么?

A: 是的, 这里使用了逗号运算符。首先,逗号左边的参数被计算后丢弃,然后,右边的参数经过计算后被当做整个运算符的值使用,所以输出是 10 2 10。

注意在函数调用中的逗号符号(比如 f(a(), b()))并不是逗号运算符,因此也就不会保证运算的顺序,a() 和 b() 会以随机的顺序计算。

9、结果是什么?

unsigned int add(unsigned int a, unsigned int b)
{
  return a + b;
}

Q: 函数 add(UINT_MAX, 1) 的结果是什么?

A:对于无符号数的溢出结果是有定义的,结果是 2^(CHAR_BIT * sizeof(unsigned int)) ,所以函数 add 的结果是 0 。

10、结果是什么?

int add(int a, int b)
{
  return a + b;
}

Q:函数 add(INT_MAX, 1) 的结果是什么?

A:有符号整数的溢出结果是未定义的行为。

11、是否有未定义行为?

int neg(int a)
{
  return -a;
}

Q:这里是否可能出现未定义的行为?如果是的话,是在输入什么参数时发生的?

A:neg(INT_MIN)。如果 ECM 用附加码(补码)表示负整数, 那么 INT_MIN 的绝对值比 INT_MAX 的绝对值大一。在这种情况下,-INT_MIN 造成了有符号整数的溢出,这是一种未定义的行为。

12、是否有未定义行为?

int div(int a, int b)
{
  assert(b != 0);
  return a / b;
}

Q:这里是否可能出现未定义的行为?如果是的话,是在什么参数上发生的?

A:如果 ECM 用附加码表示负数, 那么 div(INT_MIN, -1) 导致了与上一个例子相同的问题。

写好 Git Commit 信息的 7 个建议

写好 Git Commit 信息的 7 个建议
写好 Git Commit 信息的 7 个建议

介绍: 为什么好的提交信息如此重要

当你随意浏览任一 git 仓库的日志,你很可能会发现其中的提交信息或多或少有点乱。举个例子,瞧一瞧我早先提交到 Spring 上的这些宝贝

$ git log --oneline -5 --author cbeams --before "Fri Mar 26 2009"
e5f4b49 Re-adding ConfigurationPostProcessorTests after its brief removal in r814. @Ignore-ing the testCglibClassesAreLoadedJustInTimeForEnhancement() method as it turns out this was one of the culprits in the recent build breakage. The classloader hacking causes subtle downstream effects, breaking unrelated tests. The test method is still useful, but should only be run on a manual basis to ensure CGLIB is not prematurely classloaded, and should not be run as part of the automated build.
2db0f12 fixed two build-breaking issues: + reverted ClassMetadataReadingVisitor to revision 794 + eliminated ConfigurationPostProcessorTests until further investigation determines why it causes downstream tests to fail (such as the seemingly unrelated ClassPathXmlApplicationContextTests)
147709f Tweaks to package-info.java files
22b25e0 Consolidated Util and MutableAnnotationUtils classes into existing AsmUtils
7f96f57 polishing

狂吐吧!和最近提交到同一个仓库的信息比较一下:

$ git log --oneline -5 --author pwebb --before "Sat Aug 30 2014"
5ba3db6 Fix failing CompositePropertySourceTests
84564a0 Rework @PropertySource early parsing logic
e142fd1 Add tests for ImportSelector meta-data
887815f Update docbook dependency and generate epub
ac8326d Polish mockito usage

你更喜欢阅读哪一个?

前者长度和形式截然不同,后者则简洁一致。前者像是随意为之,而后者才是精心构思的。

虽然很多仓库的日志像前者一样,但也有例外。Linux 内核 和 Git 自己都是很好的例子。Tim Pope 维护的仓库,以及 Spring Boot 也都不错。

这些仓库的贡献者知道,和后续开发者(事实上未来就是他们自己)沟通一个改动的上下文context,最好方法就是通过一个好的 git 提交信息。代码差异diff可以告知改动的内容,但只有提交信息才能正确地告诉你为什么。Peter Hutterer 很好地指出了这一点:

重建一段代码的上下文是一种浪费。我们不能完全避免,我们只能努力尽最大可能去减少它。提交的信息就可以做到这一点,以至于一个提交信息可以表明一个开发者是不是一个好的合作者。

如果你对如何写好 git 提交信息没有仔细想过,那你很可能没有怎么使用过 git log 和相关工具。这里有一个恶性循环:因为提交的历史信息组织混乱而且前后矛盾,那后面的人也就不愿意花时间去使用和维护它。 又因为没有人去使用和维护它,提交的信息就会一直组织混乱和前后矛盾。

但一个好的日志是一个优美和有用的东西,一旦日志处理的好,那么 git blamerevertrebaselogshortlog 和其它子命令都将发挥它们的作用。查看别人的提交和 pull 请求是值得的,而且可以随时独立完成。理解几个月或者几年前发生的代码变动不仅变得可能,而且高效。

一个项目的长期成功依赖于(除了其它方面)它的可维护性,一个维护者最有力的工具就是项目的日志。所以非常值得花时间学习如何正确地维护它。刚开始可能很麻烦,但很快会成为习惯,最终会成为人们自豪和生产力的源泉。

我在这篇文章只会讨论如何保持一个健康的提交历史的最基本要素:即如何写好个人的提交信息。其它重要的实践,比如 commit squashing,我在这里不会涉及。我可能会在后续文章里讨论这些。

对于什么是惯用的约定,即命名和格式等,大多数编程语言都已经形成惯例。这些约定千差万别,但是大多数开发者都同意选择一种并坚持下去,这比让每个人自行其事导致混乱要好得多。

一个团队提交日志的方法应该一致 。为了创建一个有用的修改历史,团队应该首先对提交信息的约定形成共识,至少明确以下三件事情:

风格。包含句语、自动换行间距、文法、大小写、拼写。把这些讲清楚,不要让大家猜,并尽可能保持简单。最终的结果就是一份相当一致的日志,不仅让人读起来很爽,而且可以定期阅读。

内容。哪些信息应该包含在提交信息(如果有)的正文中?哪些不用?

元数据。如何引用问题跟踪 ID,pull 请求编号等?

幸运地是,如何生成一个地道的 git 提交信息已经有完善的约定。事实上,大部分都集成在 git 命令中,你不需要重新发明什么。你只需要遵循如下七条规则,就可以像老手一样提交日志。

七条很棒的 git 提交信息规则

记住:这都是之前都说过的。

  1. 用一个空行隔开标题和正文
  2. 限制标题字数在 50 个字符内
  3. 用大写字母写标题行
  4. 不要用句号结束标题行
  5. 在标题行使用祈使语气
  6. 正文在 72 个字符处换行
  7. 使用正文解释是什么和为什么,而不是如何做

例如:

Summarize changes in around 50 characters or less
More detailed explanatory text, if necessary. Wrap it to about 72
characters or so. In some contexts, the first line is treated as the
subject of the commit and the rest of the text as the body. The
blank line separating the summary from the body is critical (unless
you omit the body entirely); various tools like `log`, `shortlog`
and `rebase` can get confused if you run the two together.
Explain the problem that this commit is solving. Focus on why you
are making this change as opposed to how (the code explains that).
Are there side effects or other unintuitive consequenses of this
change? Here's the place to explain them.
Further paragraphs come after blank lines.
 - Bullet points are okay, too
 - Typically a hyphen or asterisk is used for the bullet, preceded
   by a single space, with blank lines in between, but conventions
   vary here
If you use an issue tracker, put references to them at the bottom,
like this:
Resolves: #123
See also: #456, #789

1. 用一个空行隔开标题和正文

来自 git commit 帮助页:

虽然不是必须的,在提交的信息中先用一行简短的文字(少于50个字符)总结改动是好的想法,后面空一行就是更为详细的描述。提交信息中第一个空行前的文字可以作为题目,这个题目会在整个 Git 用到。比如,git-format-patch 把一个提交转化成 email,它就使用这个题目作为邮件的标题,提交中余下的部分作为邮件正文。

首先,不是每一个提交都需要标题和正文。有时简单一行也可以,特别是当这个改动很简单将来也不会变化。比如:

Fix typo in introduction to user guide

不需要再多说什么了。如果读者想知道拼写错误具体是什么,她通过 git showgit diff 或者 git log -p 命令,就可以很容易地看到这个改动。

如果提交的信息像下面命令行那样简单,你可以在 git commit 时使用 -m 开关。

$ git commit -m"Fix typo in introduction to user guide"

但如果一个 commit 需要多解释一点,你就要写一下正文。比如:

Derezz the master control program
MCP turned out to be evil and had become intent on world domination.
This commit throws Tron's disc into MCP (causing its deresolution)
and turns it back into a chess game.

这种情况下使用 -m 开关就不太方便。你真的需要一个合适的编辑器。如果你还没有设定好和 git 命令行一起使用的编辑器,可以参考 Pro Git 的这个章节

无论如何,在浏览日志时,你会发现把标题和正文分开是值得的。下面是完整的日志:

$ git log
commit 42e769bdf4894310333942ffc5a15151222a87be
Author: Kevin Flynn 
Date:   Fri Jan 01 00:00:00 1982 -0200
 Derezz the master control program
 MCP turned out to be evil and had become intent on world domination.
 This commit throws Tron's disc into MCP (causing its deresolution)

现在执行 git log –online,就只印出主题这行:

$ git log --oneline
42e769 Derezz the master control program

或者,git shortlog,按用户来归类提交信息,为了简洁一样也只显示标题。

$ git shortlog
Kevin Flynn (1):
      Derezz the master control program
Alan Bradley (1):
      Introduce security program "Tron"
Ed Dillinger (3):
      Rename chess program to "MCP"
      Modify chess program
      Upgrade chess program
Walter Gibbs (1):
      Introduce protoype chess program

git 中标题和正文之间的差别还体现在许多其他的功能上,但前提是标题和正文之间要有空行,这才能正常工作。

2. 限制标题字数在50个字符内

50个字符不是一个硬性规定,只是一个经验之谈。把标题行限制在这个长度,既保证它们容易阅读,也强迫作者去思考如何用最精简的方式解释发生了什么。

小技巧:如果你发现很难总结,那你可能一次提交太多改动了。 争取作原子的提交(一个题目对应一个单独的提交)

GitHub 的界面充分考虑到这些约定。如果超过50个字符的限制,它会警告你:

写好 Git Commit 信息的 7 个建议
写好 Git Commit 信息的 7 个建议

标题行中超过69字符的部分都被会截断,显示为省略号:

写好 Git Commit 信息的 7 个建议
写好 Git Commit 信息的 7 个建议

所以目标是50个字符,但一定不要超过69个字符。

3. 用大写字母写标题行

这个很简单。标题行的首字母大写。

例如:

Accelerate to 88 miles per hour

而不是:

accelerate to 88 miles per hour

4. 不要用句号结束标题行

标题行不需要考虑标点符号。而且如果希望保持在 50 个字符以内,慎用空格。

例子:

Open the pod bay doors

而不是:

Open the pod bay doors.

5. 在标题行使用祈使语气

祈使语气就是像给一个命令或指示一样叙述或写作。一些例子如下:

  • 打扫你的房间
  • 关门
  • 倒垃圾

你现在读到七个原则中的每一个都是用祈使语气书写的。(“正文在 72 个字符处换行”等)

祈使语气听起来有点粗鲁;所以我们通常不使用它。但对 git 提交信息的标题行而言,它确实很棒。一个原因是当 git 代表你创建一个提交时,它自己就是使用祈使语气。

例如,使用git merge 后的默认输出信息读起来就像:

Merge branch 'myfeature'

当你使用 git revert 时:

Revert "Add the thing with the stuff"
This reverts commit cc87791524aedd593cff5a74532befe7ab69ce9d.

或者当在一个 GitHub  pull请求上点击“Merge”按钮时:

Merge pull request #123 from someuser/somebranch

当你用祈使语气编写你的提交信息时,你遵守了 git 内在的约定。例如:

  • 重构 X 子系统以增加可读性
  • 更新新手入门的文档
  • 删除弃用的方法
  • 发布1.0.0版本

一开始这样写会有点尴尬。同样是用来描述事实,我们更习惯用陈述方式去讲话。这也就是为什么提交信息常常读起来都是这样:

  • 用 Y 修正了问题
  • X的改变行为

有时编写的提交信息像是一个内容的描述:

  • 对损坏事物的更多修正
  • 新酷的 API 方法

有一个简单的规则,每次把它做正确就可以减少误解。

一个格式正确的 git 标题行应该类似下面的句子:

如果被采用,这个 commit 将把你的标题放在这里

例如:

  • 如果被采用,这个 commit 将会重构 X 子系统以增加可读性
  • 如果被采用,这个 commit 将会更新新手入门的文档
  • 如果被采用,这个 commit 将移出弃用的方法
  • 如果被采用,这个 commit 将发布 1.0.0 版本
  • 如果被采用,这个 commit 将从 user/branch 合并 #123 pull 请求

注意下面这些非祈使格式不能工作:

  • 如果被采用,这个 commit 将用 Y 修正了问题
  • 如果被采用,这个 commit 将 X 的改变行为
  • 如果被采用,这个 commit 将对损坏事物的更多修正
  • 如果被采用,这个 commit 将新酷的 API 方法

记住:使用祈使语气只在标题上是重要的。你在编写正文的时候,可以放宽这一限制。

6. 正文在72个字符处换行

Git 从来不自动换行。当你编写一个提交信息的正文时,你必须考虑到正确的间距和手动换行。

推荐在72个字符处换行,这样 git 有足够的空间来缩进文本,还可以保持整体都在80个字符以内。

一个好的编辑器在这里可以有所帮助。例如当你编写一个 git 提交时,可以很容易在 Vim 上设定 72 个字符换行。但是通常,IDE 提供文本换行的智能支持都很糟糕(虽然在最近版本中,IntelliJ IDEA终于有所改善)。

7. 使用正文解释是什么和为什么,而不是如何做

这个提交是一个来自 Bittcoin Core 的好例子,解释了什么被改动了和为什么:

commit eb0b56b19017ab5c16c745e6da39c53126924ed6
Author: Pieter Wuille 
Date:   Fri Aug 1 22:57:55 2014 +0200
   Simplify serialize.h's exception handling
   Remove the 'state' and 'exceptmask' from serialize.h's stream
   implementations, as well as related methods.
   As exceptmask always included 'failbit', and setstate was always
   called with bits = failbit, all it did was immediately raise an
   exception. Get rid of those variables, and replace the setstate
   with direct exception throwing (which also removes some dead
   code).
   As a result, good() is never reached after a failure (there are
   only 2 calls, one of which is in tests), and can just be replaced
   by !eof().
   fail(), clear(n) and exceptions() are just never called. Delete
   them.

看一下完整的 diff,可以想象作者当下花了多少时间提供这些上下文信息,这大大节省了同伴和未来提交者的时间。如果他不这么做,这些信息可能就永远消失了。

大多数情况下,你可以省去改动的具体实现细节。 在这点上,代码通常是不需加以说明的(如果代码太复杂需要文字解释,可以使用代码注释)。首先把你做这个改动的原因交待清楚 —— 改动前是如何工作的(和出了什么问题),现在是如何工作的,以及为什么你要采用这种方法解决问题。

未来的维护者会感谢你,这个人可能就是你自己!

小技巧

学着去热爱命令行,抛弃 IDE

拥抱命令行是明智的,理由就像 git 子命令那样多。Git 是强大的,IDE 也是,但是表现的方式不同。我每天都使用 IDE (IntelliJ IDEA),也用过不少其它的IDE(Eclipse),但我看到集成 git 的IDE ,都无法企及命令行的方便和强大(一旦你了解它)。

某些 git 相关的 IDE 功能是很有用的,比如通过调用git rm 删除一个文件,用 git 重新命名一个文件。但一旦你开始使用 commitmergerebase 这些命令,或者进行复杂的历史分析,IDE 就无能为力了。

要发挥 git 的全部火力,那就是命令行。

记住无论你使用 Bash 还是 Z shell,都可以使用 Tab 补齐脚本来减轻记住子命令和开关的痛苦。

阅读 Pro Git

Pro Git 这本书可以在网上免费得到了 ,太棒了。好好利用吧!

在 SSD 上使用 btrfs 文件系统的相关优化

在 SSD 上使用 btrfs 文件系统的相关优化
在 SSD 上使用 btrfs 文件系统的相关优化

优化挂载参数

在 Linux 中挂载 SSD 上的 btrfs,可以采用各种参数进行优化:

#           
UUID=<略>  /  btrfs defaults,ssd,discard,noatime,compress=lzo,subvol=@ 0   1

这些参数各有优缺点,酌情添加。

ssd

btrfs 文件系统有对 SSD 进行优化,在挂载参数中加入 ssd 即可。该参数不会自动启用 TRIM/discard。

discard

可以通过以下命令确认 SSD 是否支持 TRIM

(more…)

pyinfo():一个像 phpinfo 一样的 Python 脚本

pyinfo():一个像 phpinfo 一样的 Python 脚本
pyinfo():一个像 phpinfo 一样的 Python 脚本

作为一个热衷于 php 的家伙,我已经习惯了使用 phpinfo() 函数来让我轻松访问 php.ini 里的配置和加载的模块等信息。当然我也想要使用一个不存在的 pyinfo() 函数,但没有成功。按下 CTRL-E,google 一下是否有人实现了它?

是的,有人已经实现了。但是,对我来说它非常难看。荒谬!因为我无法忍受丑陋的布局,咳咳,我不得不亲自动手来改改。我用找到的代码,并重新进行布局使之更好看点。Python 官方网站的布局看起来不错,那么何不借用他们的颜色和背景图片呢?是的,这听起来像一个计划。

提醒你下,我仅仅在 Python 2.6.4 上运行过它,所以在别的版本上可能有风险(将它移植到任何其他版本它应该是没有问题的)。要使用它,只需要导入该文件, 并调用pyinfo()函数得到它的返回值打印到屏幕上。好嘞!

如果你在使用 mod_wsgi 时没有得到正确的返回结果,你可以如下运行它(当然得替换路径):

def application(environ, start_response):
    import sys
    path = 'YOUR_WWW_ROOT_DIRECTORY'
    if path not in sys.path:
        sys.path.append(path)
    from pyinfo import pyinfo
    output = pyinfo()
    start_response('200 OK', [('Content-type', 'text/html')])
    return [output]

via:http://bran.name/articles/pyinfo-a-good-looking-phpinfo-like-python-script/

作者:Bran van der Meer 译者:strugglingyouth 校对:wxy

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

树莓派介绍与“食用”方法

树莓派Raspberry Pi是 Raspberry Pi Foundation 推出的迷你电脑,它只有信用卡大小,但可以完成一台普通 PC 能完成的大部分工作,并且价格很便宜,是电脑爱好者的不二选择,如果你是一名 Linuxer 更应该拥有一台这样的迷你电脑。

发展

Raspberry Pi 自 2012 年发布以来,依次发布了 Raspberry Pi 1 A , Raspberry Pi 1 B ,Raspberry Pi 1 B+ ,Raspberry Pi 1 A+ ,Raspberry 2 B 五个版本,这些版本硬件上有不少变化,具体可以查阅 Wikipedia Raspberry Pi ,另外 Raspberry Pi 2 B 将支持 Windows 10 iot ,这对非 Linux 用户来说也是一个福音,因为你可以完全把 Raspberry Pi 2 B 当成你的另一台 Windows PC ,详情可以查看 Raspberry Pi Windows 10 iot。 

Raspberry Pi 的用途

Raspberry Pi 到底能拿来做什么呢?它的玩法多的数不清了,因为这取决于我们的创意,作为一块开发板,它给我们提供了很大的自由。

下面是几张引用文章内的图:

操作系统的选择

由于 Raspberry Pi 几乎是为 Linux 而生的,所以 Raspberry Pi 的操作系统也是多样的,为此以下介绍几个操作系统。

以上操作系统都可以在 Raspberry Pi 主页 找到相关信息。

个人电脑

笔者只是将 Raspberry Pi 当成个人电脑使用而已,因此,未选择 Raspbian 而是选择了 Arch Arm。

下面是我选择的配件

  • Raspberry Pi 2 B
  • 8 G 闪迪内存卡
  • USB Wi-Fi 模块(可选)
  • 亚克力外壳(可选)
  • 散热铝片或铜片
  • 电源线
  • 键盘鼠标(可选)

安装系统

首先参照 Arch Arm Installation 安装系统。

 

为 SD 卡分区(用你 Linux 上的 sd 卡设备代替 sdX ):

fdisk /dev/sdX

第一步请先删除原来的分区并重新创建:

  • 输入 o 清除所有分区。
  • 输入 p 列出所有分区,此时应该没有分区。
  • 输入 n , 然后输入 p 选择主分区,1 是第一个分区 ,输入 ENTER 确定第一个扇区,然后输入 +100M 。
  • 输入 t , 然后输入 c 设置第一个分区类型为 W95 FAT32 (LBA)。
  • 输入 n , 然后输入 p 选择主分区, 2 是第二个分区, 直接输入 ENTER 确定默认的扇区和最后的扇区(剩下的所有容量作为第二个分区)
  • 输入 w 写入分区表并退出。

创建和挂载 vfat 文件系统(用你 Linux 上的 sd 卡设备代替 sdX ):

mkfs.vfat /dev/sdX1
mkdir boot
mount /dev/sdX1 boot

创建个挂载 ext4 文件系统(用你 Linux 上的 sd 卡设备代替 sdX ):

mkfs.ext4 /dev/sdX2
mkdir root
mount /dev/sdX2 root

使用 root 用户下载和解压 根文件系统:

wget http://archlinuxarm.org/os/ArchLinuxARM-rpi-2-latest.tar.gz
bsdtar -xpf ArchLinuxARM-rpi-2-latest.tar.gz -C root
sync

移动启动文件到第一分区:

mv root/boot/* boot

卸载挂载点:

umount boot root

将 SD 卡插入 Raspberry Pi ,连接以太网和 5v 电源。

使用 SSH 登录

默认用户是 alarm 密码 alarm。(ssh 请先用此用户登录,再修改 ssh 配置允许 root 登录)

root 的默认密码是 root。

注意 :以下用到 sudo 命令的,若你未配置 sudo 请直接用 root 用户执行。

首次使用应该按如下格式 ssh 登录:

$ ssh -p 22 alarm@地址

进去后使用 su 切换到 root ,并修改密码:

$ sudo su - # password

为了能使 root 通过 ssh 登录,编辑 /etc/ssh/sshd_config

$ sudo nano /etc/ssh/sshd_config

将 #PermitRootLogin 这行去掉注释,并将值设置为 yes :

PermitRootLogin yes

配置源与更新系统

编辑 /etc/pacman.d/mirrorlist

$ sudo nano /etc/pacman.d/mirrorlist

在顶部增加以下代码,这是中科大的源

## USTC
Server = http://mirrors.ustc.edu.cn/archlinuxarm/armv7h/$repo

编辑好后按 ctrl +x ,然后按 y 保存,然后升级整个系统:

$ sudo pacman -Syu

桌面化 Raspberry Pi

首先安装 xorg

$ sudo pacman -S xorg
$ sudo pacman -S xorg-xinit

然后安装 lxqt 桌面:

$ sudo pacman -S lxqt 

使用 vncviewer 访问 Raspberry Pi

首先配置 vncviewer,本机与 Raspberry Pi 都需要安装 tigervnc

$ sudo pacman -S tigervnc

在 Raspberry Pi 中执行 vncserver

$ vncserver
You will require a password to access your desktops.
Password:
Verify:
Would you like to enter a view-only password (y/n)? n
New 'ArchRaspi:1 (locez)' desktop is ArchRaspi:1
Creating default startup script /home/locez/.vnc/xstartup
Starting applications specified in /home/locez/.vnc/xstartup
Log file is /home/locez/.vnc/ArchRaspi:1.log

然后编辑 ~/.vnc/xstartup ,将原来的内容替换为以下内容,你也可以直接删除原文件,再新建一个同名文件:

#!/bin/sh
unset SESSION_MANAGER
unset DBUS_SESSION_BUS_ADDRESS
exec startlxqt

然后杀掉 vnc 服务,并重启它:

$ vncserver -kill :1
Killing Xvnc process ID 400
$ vncserver
New 'ArchRaspi:1 (locez)' desktop is ArchRaspi:1
Starting applications specified in /home/locez/.vnc/xstartup
Log file is /home/locez/.vnc/ArchRaspi:1.log

记住上面的 :1 可能根据实际情况不同,这个端口是你用 vncviewer 连接时用的端口。

然后本机中执行:

$ vncviewer address:port

然后输入密码就可以了

小屏幕显示

现在我们看见这个不是全屏的,但是可以在启动 vncserver 的时候增加参数,来指定分辨率:

$ vncserver -kill :1
$ vcnserver -geometry 1920x1000

这下就全屏了

全屏

其它用途

然后你可以在不另外配显示屏的情况下正常使用 Raspberry Pi ,将它配置成一个 samba 服务器,或者做成一个下载器,这仅仅取决于,你想将 Raspberry Pi 拿来干什么。

笔者还尝试将 Raspberry Pi 直接连入手机 Wi-Fi ,把手机当成路由器使用,获取手机内网 IP 后,直接在手机上 ssh 内网登录,从而实现 Raspberry Pi 使用手机流量上网,并且不用借助路由器就可以连接 Raspberry Pi。

这里笔者推荐的是 juiceSSH ,手机上简单实用的一款 ssh 工具。由于篇幅原因,此处不再详述如何使用手机直连 Raspberry Pi,动手能力强的同学可以参看上面我给的思路,自行折腾。

后来笔者,买了键盘以后,又利用 tmux 将手机纯粹当屏幕使用。

首先 手机先 ssh 登陆 Raspberry Pi,执行

tmux

然后在看不见屏幕的情况下,使用键盘盲打输入 用户名 、 密码 进行登录,然后执行

tmux attach

此时键盘与手机屏幕的输入已经是同步的,一台个人作品就此完成!

成果展示

下面几张图,是笔者在学校折腾的时候拍的:

参考资料

ReactPHP:PHP版的Node.js

从名字说起 

ReactPHP:PHP版的Node.js
ReactPHP:PHP版的Node.js

虽然ReactPHP项目已经发展了有4年之久,但是对于其称呼显得有点混乱。在开源中国为其建立的项目主页上,其被命名为React,或者node.PHP。国外的一些的博客谈及这个项目时,多数使用的是ReactPHP。到底哪种说法比较标准呢?我们不妨来看看官方的态度。此项目的官方主页是 http://www.reactphp.org 。打开官网,你会发现网站的title是React,其logo上的文字为reactphp。可以看出,官方更倾向于被命名为React或者ReactPHP。我建议使用ReactPHP作为其名称。原因大概有两个。

  1. React单词的意思太泛,并且已经有一些项目的名称与React相关,容易引起误解。
  2. 目前国内使用ReactPHP的人比较少,相关资料文档也比较少。在国外它一般被称为ReactPHP,使用ReactPHP在国外检索资料更容易。

ReactPHP与Node.js有着相同的特点

许多人认为ReactPHP是Node.js的php版本,这是有一定道理的。他们的确有很多相似的特点。

事件驱动,异步执行,非阻塞IO

什么是事件驱动?所谓事件驱动,简单的说就是,你告诉我你关注什么事情,等事情发生的时候我会主动通知你,然后你再作相应的处理。这样可以就可以把你解放出来,你只关注于处理好相应事件即可。采用事件驱动有什么优势呢?相对于常见的多进程编程,能更好的利用CPU资源。多进程编程会使进程数量变多,进程上下文切换频繁会增加系统压力,浪费宝贵的CPU资源。相对于多线程编程而言,可以降低编程复杂度。开发者不必再考虑线程间资源共享导致资源竞争等问题。

ReactPHP和Node.js都采用了事件驱动和非阻塞IO。从官方主页的宣传语上就可以得到印证。在Node.js的官网上有一段话:

Node.js uses an event-driven, non-blocking I/O model that makes it lightweight and efficient。

上面的意思是,Node.js使用事件驱动和非阻塞IO模型,以保证轻量级和高效。

在ReactPHP官网也有一段话:

Event-driven, non-blocking I/O with PHP.

上面的意思是,ReactPHP使用PHP语言实现了事件驱动和非阻塞。

ReactPHP和Node.js在实现事件驱动机制时也有相似之处。在事件的监听上,ReactPHP和Node.js都使用了libev库,但是也都是不只使用libev库。由于libev对windows支持不够好。因此,Node.js中封装了一层libuv。libuv是基于windows的IOCP和*nix的libev进行封装。而ReactPHP除了使用libev库外,还是用了其他的库。如,libevent。

ReactPHP和Node.js都各自有自己的生态圈。在各自生态圈中的一些模块一般都采用了事件驱动,异步编程的风格。如,ReactPHP的Stream模块,提供了以下几个事件:drain、error、close、pipe、end、data。相应的,在Node.js中也有一些类似的事件。Node.js的Net模,其中的net.Socket对象就有以下事件:connect、data、end、timeout、drain、error、close等。这样,开发人员只需要知道自己关注那些事件,并在这些事件上注册回调函数。等事件发生的时候,会主动执行这些注册的回调函数。这些回调函数都是异步执行的,这些函数虽然在注册的时候有先后顺序,但是在执行的时候是无序的,随机的,执行顺序和事件发生顺序相关。事件驱动再加上非阻塞IO,就可以极大的利用系统资源,代码无需阻塞等待资源可用。

单进程单线程

ReactPHP和Node.js一样都是采用了单进程和单线程的运行方式。单进程,单线程方式,没有多线程的资源抢占和上下文切换,高效率的运行,维护着一个事件队列。这种运行方式,通常情况下瓶颈一般在CPU而不是内存。由于单进程,单线程只能在一个CPU上运行,本身不能充分利用多个CPU资源。为了解决这个问题,我们可以启动多个进程,监听不同的端口,前端使用nginx等做代理,把请求分发到不同的进程上。对于多进程的管理上,现在已经有不少开源项目可以实现。如,php-pm

ReactPHP性能压测

相对于传统的nginx+php-fpm方式,ReactPHP的性能表现如何呢?现在我们来做下性能压测。服务器环境如下:

  • 8核CPU
  • PHP版本为5.5.15,使用opcache扩展
  • 操作系统为Centos5
  • Nginx版本为nginx/1.2.9
  • ReactPHP版本为0.4

为了公平起见,我们php-fpm和ReactPHP都只启动一个进程。压测工具我们使用ab,Apache开源的http服务压测工具。我们压测分两种情况来进行:第一种情况是只输出简单的Hello World。第二种情况只进行一次简单的sql语句查询,select 1 as num。

第一种情况:Hello World的压测结果如下,QPS:

ReactPHP:PHP版的Node.js
ReactPHP:PHP版的Node.js

第二种情况:SQL查询的压测结果如下,QPS:

ReactPHP:PHP版的Node.js
ReactPHP:PHP版的Node.js

可见,对于cpu密集型的应用,nginx+php-fpm的方式要比ReactPHP有更好的表现。但是对于数据库查询这样涉及网络IO的场景,ReactPHP的性能要远远好于nginx+php-fpm的方式。 

ReactPHP的应用场景

根据上面的测试,ReactPHP更适合IO密集型的应用。以下是ReactpHP比较适合的应用场景。

  1. 从RESTful API获取数据,并进行拼装输出只是请求api获取数据,然后进行简单的拼装,最后输出到客户端。本身业务逻辑不复杂。在请求的时候,可以同时对多个api进行请求,相对于顺序调用api的方式,会节省很多的时间,大大提高了响应的效率。
  2. 实时推送,在线聊天实时推送和在线聊天都需要维护大量的链接。这个正是ReactpHP擅长的。他可以很轻松的维护上万的链接。
  3. 分布式IO系统如一个数据库中间件层,它需要解析SQL为多条子SQL,然后把子SQL分发到不同的服务器查询数据,然后合并数据返回给客户端。这种情况下可以使用ReactPHP同时对多个数据库服务器进行查询。

如何使用ReactPHP 

ReactPHP可以使用composer安装,这个也是官方推荐的安装方式。首先安装composer。

curl -s https://getcomposer.org/installer| php

安装完成后,会在当前目录下生成一个composer.phar文件。然后我们使用composer.phar安装react。

php ./composer.phar require react/react

安装成功后,会在当前目录下生成一个vendor目录。下载的程序就在这个目录下。现在你就可以使用ReactPHP写程序了。例如,我们想提供一个http服务,我们将把客户端通过data参数提交的数据加上www.后进行返回。代码如下: 

writeHead(200, array('Content-Type' => 'text/plain'));
        $query = $request->getQuery();
        $data = isset($query["data"]) ? $query["data"] : "";
        $response->end("www.{$data}n");
};
$loop = ReactEventLoopFactory::create();
$socket = new ReactSocketServer($loop);
$http = new ReactHttpServer($socket, $loop);
$http->on('request', $app);
$socket->listen($port, '0.0.0.0');
$loop->run();
?> 

把上面的代码保持为文件reactphp.php。然后启动服务:

php ./reactphp.php 5501

最后,我们验证下效果,可以通过下面的方式访问。 

$curl  http://10.101.80.141:5501/?data=bo56.com
www.bo56.com 

ReactPHP也有自己的生态圈。如进行异步mysql查询的react-php。

小结

ReactPHP作为Node.js的PHP版本。在实现思路,使用方法,应用场景上的确有很多相似之处。但是ReactPHP毕竟比Node.js年轻,目前生态圈还是不如Node.js完善。目前文档也不是很完善,在国内应用也比较少。但是相信,它会越来越完善,应用越来越广。

编写更好 Bash 脚本的 8 个建议

在我最开始管理Linux和Unix服务器时,经常遇到其他管理员编写的一大堆临时脚本。时常会因为其中某个脚本突然停止工作而进行故障排查。有时这些脚本编写得规范好理解,其他时候则是杂乱且令人困惑。

虽然排查编写糟糕的脚本很麻烦,但我从中吸取到了教训。即使你认为该脚本只会在今天使用,最好也抱着两年后还将有人去排查的态度编写脚本。因为总会有人查看,甚至很可能是你自己。

在本篇文章中,我想介绍一些优化脚本的建议,不是为了方便你编写脚本,而是方便想要弄清脚本为何不工作的人。

释伴shebang行开头

Shell脚本编写的第一条规则是以释伴shebang行开头。虽然听起来很好笑,但释伴shebang行却很重要,它告诉系统使用哪种二进制作为脚本的解释器。没有释伴shebang行,系统就不知道使用哪种语言解释执行脚本。

一个典型的bash 以释伴shebang行如下所示:

#!/bin/bash

与本文中其他建议不同,这不仅仅是一条建议,而是一条规定。shell脚本必须以解释器行开始;没有这行,你的脚本最终将不能工作。我发现很多脚本没有这一行,有人认为没有这行脚本就不能工作,但事实并非如此。如果没有指定脚本解释器,有些系统会默认使用/bin/sh目录下的解释器。如果是bourne shell脚本,默认/bin/sh路径没有问题,如果是KSH或者使用特定bash脚本而不是bourne,该脚本可能产生无法预料的结果。

添加脚本描述头

当编写脚本或者其他程序时,我总会在脚本开头描述脚本的用途,同时添加我的名字。如果这些脚本是在工作中编写,我还会加上工作邮箱以及脚本编写日期。

下面是一个有脚本头的例子:

#!/bin/bash
### Description: Adds users based on provided CSV file
### CSV file must use : as separator
### uid:username:comment:group:addgroups:/home/dir:/usr/shell:passwdage:password
### Written by: Benjamin Cane - ben@example.com on 03-2012

为什么要添加这些内容?很简单。这里的描述是为了向阅读该脚本的人解释脚本用途并提供他们需要了解的其他信息。添加名字和邮箱,阅读该脚本的人如果有疑问就可以联系上我并提问。添加日期,当他们阅读脚本时,至少知道该脚本是多久之前编写的。日期还能触动你的怀旧之情,当发现自己很久前编写的脚本时,你会问问自己“在编写该脚本时,我是怎么想的?”。

脚本中的描述头可以根据自己的想法随意定制,没有硬性规定哪些是必须的,哪些不需要。通常只要保证信息有效并且放置在脚本开头即可。

缩进代码

代码可读性非常重要,但很多人都会忽略这一点。在深入了解缩进为何很重要前,我们来看一个例子:

NEW_UID=$(echo $x | cut -d: -f1)
NEW_USER=$(echo $x | cut -d: -f2)
NEW_COMMENT=$(echo $x | cut -d: -f3)
NEW_GROUP=$(echo $x | cut -d: -f4)
NEW_ADDGROUP=$(echo $x | cut -d: -f5)
NEW_HOMEDIR=$(echo $x | cut -d: -f6)
NEW_SHELL=$(echo $x | cut -d: -f7)
NEW_CHAGE=$(echo $x | cut -d: -f8)
NEW_PASS=$(echo $x | cut -d: -f9)
PASSCHK=$(grep -c ":$NEW_UID:" /etc/passwd)
if [ $PASSCHK -ge 1 ]
then
echo "UID: $NEW_UID seems to exist check /etc/passwd"
else
useradd -u $NEW_UID -c "$NEW_COMMENT" -md $NEW_HOMEDIR -s $NEW_SHELL -g $NEW_GROUP -G $NEW_ADDGROUP $NEW_USER
if [ ! -z $NEW_PASS ]
then
echo $NEW_PASS | passwd --stdin $NEW_USER
chage -M $NEW_CHAGE $NEW_USER
chage -d 0 $NEW_USER
fi
fi

上述代码能工作吗?是的,但这段代码写的并不好,如果这是一个500行bash脚本,没有任何缩进,那么理解该脚本的用途将非常困难。下面看一下使用缩进后的同一段代码:

NEW_UID=$(echo $x | cut -d: -f1)
NEW_USER=$(echo $x | cut -d: -f2)
NEW_COMMENT=$(echo $x | cut -d: -f3)
NEW_GROUP=$(echo $x | cut -d: -f4)
NEW_ADDGROUP=$(echo $x | cut -d: -f5)
NEW_HOMEDIR=$(echo $x | cut -d: -f6)
NEW_SHELL=$(echo $x | cut -d: -f7)
NEW_CHAGE=$(echo $x | cut -d: -f8)
NEW_PASS=$(echo $x | cut -d: -f9)
PASSCHK=$(grep -c ":$NEW_UID:" /etc/passwd)
if [ $PASSCHK -ge 1 ]
then
  echo "UID: $NEW_UID seems to exist check /etc/passwd"
else
  useradd -u $NEW_UID -c "$NEW_COMMENT" -md $NEW_HOMEDIR -s $NEW_SHELL -g $NEW_GROUP -G $NEW_ADDGROUP $NEW_USER
  if [ ! -z $NEW_PASS ]
  then
      echo $NEW_PASS | passwd --stdin $NEW_USER
      chage -M $NEW_CHAGE $NEW_USER
      chage -d 0 $NEW_USER
  fi
fi

缩进后,很明显第二个if语句内嵌在第一个if语句内,但如果看未缩进的代码,第一眼肯定发现不了。

缩进方式取决于你自己,是使用两个空格、四个空格,还是就使用一个制表符,这都不重要。重要的是代码每次以相同的方式一致缩进。

增加间距

缩进可以增加代码的可理解性,而间距可以增加代码的可读性。通常,我喜欢根据代码的用途来间隔代码,这是个人偏好,其意义在于使代码更加可读并易于理解。

下面是上述代码添加行间距后的例子:

NEW_UID=$(echo $x | cut -d: -f1)
NEW_USER=$(echo $x | cut -d: -f2)
NEW_COMMENT=$(echo $x | cut -d: -f3)
NEW_GROUP=$(echo $x | cut -d: -f4)
NEW_ADDGROUP=$(echo $x | cut -d: -f5)
NEW_HOMEDIR=$(echo $x | cut -d: -f6)
NEW_SHELL=$(echo $x | cut -d: -f7)
NEW_CHAGE=$(echo $x | cut -d: -f8)
NEW_PASS=$(echo $x | cut -d: -f9)
PASSCHK=$(grep -c ":$NEW_UID:" /etc/passwd)
if [ $PASSCHK -ge 1 ]
then
  echo "UID: $NEW_UID seems to exist check /etc/passwd"
else
  useradd -u $NEW_UID -c "$NEW_COMMENT" -md $NEW_HOMEDIR -s $NEW_SHELL -g $NEW_GROUP -G $NEW_ADDGROUP $NEW_USER
  if [ ! -z $NEW_PASS ]
  then
      echo $NEW_PASS | passwd --stdin $NEW_USER
      chage -M $NEW_CHAGE $NEW_USER
      chage -d 0 $NEW_USER
  fi
fi

如你所见,行间距虽不易觉察,但每一处整洁都让以后的代码排错更简单。

注释代码

描述头适合于添加脚本函数描述,而代码注释适合于解释代码本身的用途。下面仍是上述相同的代码片段,但这次我将添加代码注释,解释代码的用途:

### Parse $x (the csv data) and put the individual fields into variables
NEW_UID=$(echo $x | cut -d: -f1)
NEW_USER=$(echo $x | cut -d: -f2)
NEW_COMMENT=$(echo $x | cut -d: -f3)
NEW_GROUP=$(echo $x | cut -d: -f4)
NEW_ADDGROUP=$(echo $x | cut -d: -f5)
NEW_HOMEDIR=$(echo $x | cut -d: -f6)
NEW_SHELL=$(echo $x | cut -d: -f7)
NEW_CHAGE=$(echo $x | cut -d: -f8)
NEW_PASS=$(echo $x | cut -d: -f9)
### Check if the new userid already exists in /etc/passwd
PASSCHK=$(grep -c ":$NEW_UID:" /etc/passwd)
if [ $PASSCHK -ge 1 ]
then
### If it does, skip
  echo "UID: $NEW_UID seems to exist check /etc/passwd"
else
### If not add the user
  useradd -u $NEW_UID -c "$NEW_COMMENT" -md $NEW_HOMEDIR -s $NEW_SHELL -g $NEW_GROUP -G $NEW_ADDGROUP $NEW_USER
### Check if new_pass is empty or not
  if [ ! -z $NEW_PASS ]
  then
### If not empty set the password and pass expiry
      echo $NEW_PASS | passwd --stdin $NEW_USER
      chage -M $NEW_CHAGE $NEW_USER
      chage -d 0 $NEW_USER
  fi
fi

如果你恰好要阅读这段bash代码,却又不知道这段代码的用途,至少可以通过查看注释充分掌握代码的实现目标。在代码中添加注释对其他人非常有帮助,甚至对你自己也有帮助。我曾发现在浏览自己一个月前编写的脚本时不知道脚本的用途。如果注释添加合理,可以在日后节省你和他人的很多时间。

创建描述性的变量名

描述性变量名非常直观,但我发现自己一直都使用通用变量名。通常这些都是临时变量,从不在该代码块之外使用,但即使是临时变量,解释清楚它们的含义也很有用。

下面例子中的变量名大部分是描述性的:

for x in `cat $1`
do
    NEW_UID=$(echo $x | cut -d: -f1)
    NEW_USER=$(echo $x | cut -d: -f2)

可能赋给$NEW_UID和$NEW_USER的值不是很明显,$1的值代表什么以及$x的取值是什么都不够清楚。更具描述性的修改代码如下:

INPUT_FILE=$1
for CSV_LINE in `cat $INPUT_FILE`
do
  NEW_UID=$(echo $CSV_LINE | cut -d: -f1)
  NEW_USER=$(echo $CSV_LINE | cut -d: -f2)

从这段重写的代码块中,很容易看出我们是在读取一个输入文件,该文件名是一个CSV文件。同时很容易看出我们从什么地方获取新的UID和新的USER信息来存储在$NEW_UID和$NEW_USER变量中。

上面的例子看上去有点大材小用,但日后会有人感谢你花费额外时间让变量更具描述性。

使用 $(command) 进行命令替换

如果你想创建一个变量,其值是其他指令的输出,在bash中有两种方式实现。第一种是将命令封装在反引号中,如下所示:

DATE=`date +%F`

第二种是使用一个不同的语法:

DATE=$(date +%F)

虽然两者都正确,但我个人更喜欢第二种方法。这纯粹是个人偏好,但我通常认为$(command)句法比使用反引号更加明显。假如你在挖掘上百行的bash代码;你会发现随着自己不断阅读,那些反引号有时看起来像是单引号。此外,有时单引号看起来像是反引号。最后,所有的建议都与偏好挂钩。所以使用最适合你的,确保与你所选择使用的方法一致。

在出错退出前描述问题

上述示例可以让代码更加易于阅读和理解,最后一条建议对在排错过程前找到错误点非常有用。在脚本中添加描述性错误信息,可以在前期节省很多排错时间。浏览下面的代码,看看如何能使它更具描述性:

if [ -d $FILE_PATH ]
then
  for FILE in $(ls $FILE_PATH/*)
  do
    echo "This is a file: $FILE"
  done
else
  exit 1
fi

该脚本首先检查$FILE_PATH变量的值是否是一个目录,如果不是,脚本将退出,并返回一个错误代码1。虽然使用退出代码能够告诉其他脚本该脚本未成功执行,但却没有给运行该脚本的人做出解释。

我们让代码变得更加友好些:

if [ -d $FILE_PATH ]
then
  for FILE in $(ls $FILE_PATH/*)
  do
    echo "This is a file: $FILE"
  done
else
  echo "exiting... provided file path does not exist or is not a directory"
  exit 1
fi

如果运行第一个代码片段,你将得到大量输出。如果你得不到输出,你将不得不打开脚本文件查看哪些地方可能出错。但如果你运行第二个代码片段,你立刻就能知道是在脚本指定了无效路径。仅添加一行代码就省去了以后大量的排错时间。

上述例子仅仅是我在编程时尝试使用的技巧。我相信编写整洁可读的bash脚本还有其他很多好建议,如果你有任何建议,随时在评论区回复。很高兴能看到其他人提出来的技巧。

如何在 Fedora 上设定和取消 IPv6 地址

如何在 Fedora 上设定和取消 IPv6 地址
如何在 Fedora 上设定和取消 IPv6 地址

1、列出当前的IPv6地址

使用 “ip”

使用方法:

# /sbin/ip -6 addr show dev 

例子:一个静态的主机地址

# /sbin/ip -6 addr show dev eth0
2: eth0:  mtu 1500 qdisc pfifo_ fast qlen 100
inet6 fe80::210:a4ff:fee3:9566/10 scope link
inet6 3ffe:ffff:0:f101::1/64 scope global
inet6 fec0:0:0:f101::1/64 scope site 

自动设定的地址和它的存活时间:

# /sbin/ip -6 addr show dev eth0
3: eth0:  mtu 1500 qdisc pfifo_fast qlen 100
inet6 2002:d950:f5f8:f101:2e0:18ff:fe90:9205/64 scope global dynamic
  valid_lft 16sec preferred_lft 6sec
inet6 3ffe:400:100:f101:2e0:18ff:fe90:9205/64 scope global dynamic
  valid_lft 2591997sec preferred_lft 604797sec inet6 fe80::2e0:18ff:fe90:9205/10 scope link

使用 “ifconfig”

使用方法:

# /sbin/ifconfig 

例子, 它只列出IPv6地址:

# /sbin/ifconfig eth0 |grep "inet6 addr:"
inet6 addr: fe80::210:a4ff:fee3:9566/10 Scope:Link
inet6 addr: 3ffe:ffff:0:f101::1/64 Scope:Global
inet6 addr: fec0:0:0:f101::1/64 Scope:Site

2、增加一个IPv6地址

其原理同IPv4的”IP ALIAS”(IP别名)相同

使用 “ip”

使用方法:

# /sbin/ip -6 addr add / dev  

例子:

# /sbin/ip -6 addr add 3ffe:ffff:0:f101::1/64 dev eth0 

使用 “ifconfig”

使用方法:

# /sbin/ifconfig  inet6 add /

例子:

# /sbin/ifconfig eth0 inet6 add 3ffe:ffff:0:f101::1/64 

3、移除IPv6地址

这个不常用, 不要用它移除不存在的地址,一些早期的核心会因为受不了而挂掉。 

使用 “ip”

使用方法:

# /sbin/ip -6 addr del / dev  

例子:

# /sbin/ip -6 addr del 3ffe:ffff:0:f101::1/64 dev eth0 

使用 “ifconfig”

使用方法:

# /sbin/ifconfig  inet6 del /

例子:

# /sbin/ifconfig eth0 inet6 del 3ffe:ffff:0:f101::1/64

 

来源:http://www.linux.org.tw/CLDP/OLD/Linux-IPv6-HOWTO-6.html

在 Linux 下使用 rfkill 软开关蓝牙及无线功能

很多计算机系统包含无线电传输,其中包括Wi-Fi、蓝牙和3G设备。这些设备消耗电源,在不使用这些设备时是一种能源浪费。 

RFKill 是Linux内核中的一个子系统,它可提供一个接口,在此接口中可查询、激活并取消激活计算机系统中的无线电传输。当取消激活传输时,可使其处于可被软件重新激活的状态( 软锁定 )或软件无法重新激活的位置( 硬锁定 )。

RFKill 为内核子系统提供应用程序编程界面(API)。内核驱动程序被设计为支持RFKill使用这个API注册内核,并包含启用和禁用这个设备的方法。另外,RFKill提供用户程序可解读的通知以及用户程序查询传输状态的方法。

RFKill接口位于 /dev/rfkill,其中包含系统中所有无线电传输的当前状态。每个设备都在 sysfs 中注册当前RFKill状态。另外,在启用了RFKill的设备中每当状态更改时,RFKill会发出 uevents。

rfkill 是一个命令行工具,您可使用它查询和更改系统中启用了RFKill的设备。要获得这个工具,请安装 rfkill 软件包。

如果开机时在可以搜索到无线网络且输入密码正确但仍然无法接入的情况下,就可能是rfkill这个程序阻拦了接入,它是个用来控制无线网络及蓝牙的使用的软开关。

使用命令 rfkill list 获得设备列表,每个都包含与之关联的索引号 ,从 0 开始。

rfkill list
在 Linux 下使用 rfkill 软开关蓝牙及无线功能
在 Linux 下使用 rfkill 软开关蓝牙及无线功能

您可以使用这个索引号让 rfkill 停使或者使用某个设备,例如:

rfkill block 0 

停用系统中第一个启用RFKill的设备。

您还可以使用 rfkill 阻断某一类设备,或者所有启用了RFKill的设备。例如:

rfkill block wifi 

停用系统中的所有Wi-Fi设备。要停用所有启用了RFKill的设备,请运行:

rfkill block all

要重新使用设备,请运行 rfkill unblock。要获得 rfkill 可停用的完整设备类别列表,请运行 rfkill help。

史上最复杂的验证邮件地址的正则表达式

用正则表达式验证邮件地址似乎是一件简单的事情,但是如果要完美的验证一个合规的邮件地址,其实也许很复杂。

史上最复杂的验证邮件地址的正则表达式
史上最复杂的验证邮件地址的正则表达式

邮件地址的规范来自于 RFC 5322 。有一个网站 emailregex.com 专门列出各种编程语言下的验证邮件地址的正则表达式,其中很多正则表达式都是我听说过而从未见过的复杂——我想说,做这个网站的程序员是被邮件验证这件事伤害了多深啊!

其实,在产品环境中,一般来说并不需要这么复杂的正则表达式来做到99.99%正确。一般来说,从执行效率和测试覆盖率来说,只需要一个简单的版本即可:

/^[A-Z0-9._%+-]+@[A-Z0-9.-]+.[A-Z]{2,4}$/i

那么下面我们来看看这些更严谨、更复杂的正则表达式吧:

验证邮件地址的通用正则表达式(符合 RFC 5322 标准)

(?:[a-z0-9!#$%&'*+/=?^_`{|}~-]+(?:.[a-z0-9!#$%&'*+/=?^_`{|}~-]+)*|"(?:[x01-x08x0bx0cx0e-x1fx21x23-x5bx5d-x7f]|\[x01-x09x0bx0cx0e-x7f])*")@(?:(?:[a-z0-9](?:[a-z0-9-]*[a-z0-9])?.)+[a-z0-9](?:[a-z0-9-]*[a-z0-9])?|[(?:(?:25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?).){3}(?:25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?|[a-z0-9-]*[a-z0-9]:(?:[x01-x08x0bx0cx0e-x1fx21-x5ax53-x7f]|\[x01-x09x0bx0cx0e-x7f])+)])

由于各种语言对正则表达式的支持不同、语法差异和覆盖率不同,所以,不同语言里面的正则表达式也不同:

Python

这个是个简单的版本:

r"(^[a-zA-Z0-9_.+-]+@[a-zA-Z0-9-]+.[a-zA-Z0-9-.]+$)"

Javascript

这个有点复杂了:

/^[-a-z0-9~!$%^&*_=+}{'?]+(.[-a-z0-9~!$%^&*_=+}{'?]+)*@([a-z0-9_][-a-z0-9_]*(.[-a-z0-9_]+)*.(aero|arpa|biz|com|coop|edu|gov|info|int|mil|museum|name|net|org|pro|travel|mobi|[a-z][a-z])|([0-9]{1,3}.[0-9]{1,3}.[0-9]{1,3}.[0-9]{1,3}))(:[0-9]{1,5})?$/i

Swift

[A-Z0-9a-z._%+-]+@[A-Za-z0-9.-]+\.[A-Za-z]{2,6}

PHP

PHP 的这个版本就更复杂了,覆盖率就更大一些:

/^(?!(?:(?:x22?x5C[x00-x7E]x22?)|(?:x22?[^x5Cx22]x22?)){255,})(?!(?:(?:x22?x5C[x00-x7E]x22?)|(?:x22?[^x5Cx22]x22?)){65,}@)(?:(?:[x21x23-x27x2Ax2Bx2Dx2F-x39x3Dx3Fx5E-x7E]+)|(?:x22(?:[x01-x08x0Bx0Cx0E-x1Fx21x23-x5Bx5D-x7F]|(?:x5C[x00-x7F]))*x22))(?:.(?:(?:[x21x23-x27x2Ax2Bx2Dx2F-x39x3Dx3Fx5E-x7E]+)|(?:x22(?:[x01-x08x0Bx0Cx0E-x1Fx21x23-x5Bx5D-x7F]|(?:x5C[x00-x7F]))*x22)))*@(?:(?:(?!.*[^.]{64,})(?:(?:(?:xn--)?[a-z0-9]+(?:-[a-z0-9]+)*.){1,126}){1,}(?:(?:[a-z][a-z0-9]*)|(?:(?:xn--)[a-z0-9]+))(?:-[a-z0-9]+)*)|(?:[(?:(?:IPv6:(?:(?:[a-f0-9]{1,4}(?::[a-f0-9]{1,4}){7})|(?:(?!(?:.*[a-f0-9][:]]){7,})(?:[a-f0-9]{1,4}(?::[a-f0-9]{1,4}){0,5})?::(?:[a-f0-9]{1,4}(?::[a-f0-9]{1,4}){0,5})?)))|(?:(?:IPv6:(?:(?:[a-f0-9]{1,4}(?::[a-f0-9]{1,4}){5}:)|(?:(?!(?:.*[a-f0-9]:){5,})(?:[a-f0-9]{1,4}(?::[a-f0-9]{1,4}){0,3})?::(?:[a-f0-9]{1,4}(?::[a-f0-9]{1,4}){0,3}:)?)))?(?:(?:25[0-5])|(?:2[0-4][0-9])|(?:1[0-9]{2})|(?:[1-9]?[0-9]))(?:.(?:(?:25[0-5])|(?:2[0-4][0-9])|(?:1[0-9]{2})|(?:[1-9]?[0-9]))){3}))]))$/iD

Perl / Ruby

对与 PHP 的版本,Perl 和 Ruby 表示不服,可以更严谨:

(?:(?:rn)?[ t])*(?:(?:(?:[^()<>@,;:\".[] 00-31]+(?:(?:(?:rn)?[ t])+|Z|(?=[["()<>@,;:\".[]]))|"(?:[^"r\]|\.|(?:(?:rn)?[ t]))*"(?:(?:rn)?[ t])*)(?:.(?:(?:rn)?[ t])*(?:[^()<>@,;:\".[] 00-31]+(?:(?:(?:rn)?[ t])+|Z|(?=[["()<>@,;:\".[]]))|"(?:[^"r\]|\.|(?:(?:rn)?[t]))*"(?:(?:rn)?[ t])*))*@(?:(?:rn)?[ t])*(?:[^()<>@,;:\".[] 00-31]+(?:(?:(?:rn)?[ t])+|Z|(?=[["()<>@,;:\".[]]))|[([^[]r\]|\.)*](?:(?:rn)?[ t])*)(?:.(?:(?:rn)?[ t])*(?:[^()<>@,;:\".[] 00-31]+(?:(?:(?:rn)?[ t])+|Z|(?=[["()<>@,;:\".[]]))|[([^[]r\]|\.)*](?:(?:rn)?[ t])*))*|(?:[^()<>@,;:\".[] 00-31]+(?:(?:(?:rn)?[ t])+|Z|(?=[["()<>@,;:\".[]]))|"(?:[^"r\]|\.|(?:(?:rn)?[ t]))*"(?:(?:rn)?[ t])*)*<(?:(?:rn)?[ t])*(?:@(?:[^()<>@,;:\".[] 00-31]+(?:(?:(?:rn)?[ t])+|Z|(?=[["()<>@,;:\".[]]))|[([^[]r\]|\.)*](?:(?:rn)?[t])*)(?:.(?:(?:rn)?[ t])*(?:[^()<>@,;:\".[] 00-31]+(?:(?:(?:rn)?[ t])+|Z|(?=[["()<>@,;:\".[]]))|[([^[]r\]|\.)*](?:(?:rn)?[ t])*))*(?:,@(?:(?:rn)?[ t])*(?:[^()<>@,;:\".[] 00-31]+(?:(?:(?:rn)?[t])+|Z|(?=[["()<>@,;:\".[]]))|[([^[]r\]|\.)*](?:(?:rn)?[ t])*)(?:.(?:(?:rn)?[ t])*(?:[^()<>@,;:\".[] 00-31]+(?:(?:(?:rn)?[ t])+|Z|(?=[["()<>@,;:\".[]]))|[([^[]r\]|\.)*](?:(?:rn)?[ t])*))*)*:(?:(?:rn)?[ t])*)?(?:[^()<>@,;:\".[] 00-31]+(?:(?:(?:rn)?[ t])+|Z|(?=[["()<>@,;:\".[]]))|"(?:[^"r\]|\.|(?:(?:rn)?[ t]))*"(?:(?:rn)?[ t])*)(?:.(?:(?:rn)?[ t])*(?:[^()<>@,;:\".[] 00-31]+(?:(?:(?:rn)?[ t])+|Z|(?=[["()<>@,;:\".[]]))|"(?:[^"r\]|\.|(?:(?:rn)?[ t]))*"(?:(?:rn)?[ t])*))*@(?:(?:rn)?[ t])*(?:[^()<>@,;:\".[] 00-31]+(?:(?:(?:rn)?[ t])+|Z|(?=[["()<>@,;:\".[]]))|[([^[]r\]|\.)*](?:(?:rn)?[ t])*)(?:.(?:(?:rn)?[ t])*(?:[^()<>@,;:\".[] 00-31]+(?:(?:(?:rn)?[ t])+|Z|(?=[["()<>@,;:\".[]]))|[([^[]r\]|\.)*](?:(?:rn)?[ t])*))*>(?:(?:rn)?[ t])*)|(?:[^()<>@,;:\".[] 00-31]+(?:(?:(?:rn)?[ t])+|Z|(?=[["()<>@,;:\".[]]))|"(?:[^"r\]|\.|(?:(?:rn)?[ t]))*"(?:(?:rn)?[ t])*)*:(?:(?:rn)?[ t])*(?:(?:(?:[^()<>@,;:\".[]00-31]+(?:(?:(?:rn)?[ t])+|Z|(?=[["()<>@,;:\".[]]))|"(?:[^"r\]|\.|(?:(?:rn)?[ t]))*"(?:(?:rn)?[ t])*)(?:.(?:(?:rn)?[ t])*(?:[^()<>@,;:\".[] 00-31]+(?:(?:(?:rn)?[ t])+|Z|(?=[["()<>@,;:\".[]]))|"(?:[^"r\]|\.|(?:(?:rn)?[ t]))*"(?:(?:rn)?[ t])*))*@(?:(?:rn)?[ t])*(?:[^()<>@,;:\".[] 00-31]+(?:(?:(?:rn)?[ t])+|Z|(?=[["()<>@,;:\".[]]))|[([^[]r\]|\.)*](?:(?:rn)?[ t])*)(?:.(?:(?:rn)?[ t])*(?:[^()<>@,;:\".[] 00-31]+(?:(?:(?:rn)?[ t])+|Z|(?=[["()<>@,;:\".[]]))|[([^[]r\]|\.)*](?:(?:rn)?[ t])*))*|(?:[^()<>@,;:\".[] 00-31]+(?:(?:(?:rn)?[ t])+|Z|(?=[["()<>@,;:\".[]]))|"(?:[^"r\]|\.|(?:(?:rn)?[ t]))*"(?:(?:rn)?[ t])*)*<(?:(?:rn)?[ t])*(?:@(?:[^()<>@,;:\".[] 00-31]+(?:(?:(?:rn)?[ t])+|Z|(?=[["()<>@,;:\".[]]))|[([^[]r\]|\.)*](?:(?:rn)?[ t])*)(?:.(?:(?:rn)?[ t])*(?:[^()<>@,;:\".[] 00-31]+(?:(?:(?:rn)?[ t])+|Z|(?=[["()<>@,;:\".[]]))|[([^[]r\]|\.)*](?:(?:rn)?[ t])*))*(?:,@(?:(?:rn)?[ t])*(?:[^()<>@,;:\".[] 00-31]+(?:(?:(?:rn)?[ t])+|Z|(?=[["()<>@,;:\".[]]))|[([^[]r\]|\.)*](?:(?:rn)?[ t])*)(?:.(?:(?:rn)?[ t])*(?:[^()<>@,;:\".[]00-31]+(?:(?:(?:rn)?[ t])+|Z|(?=[["()<>@,;:\".[]]))|[([^[]r\]|\.)*](?:(?:rn)?[ t])*))*)*:(?:(?:rn)?[ t])*)?(?:[^()<>@,;:\".[] 00-31]+(?:(?:(?:rn)?[ t])+|Z|(?=[["()<>@,;:\".[]]))|"(?:[^"r\]|\.|(?:(?:rn)?[ t]))*"(?:(?:rn)?[ t])*)(?:.(?:(?:rn)?[ t])*(?:[^()<>@,;:\".[] 00-31]+(?:(?:(?:rn)?[ t])+|Z|(?=[["()<>@,;:\".[]]))|"(?:[^"r\]|\.|(?:(?:rn)?[ t]))*"(?:(?:rn)?[ t])*))*@(?:(?:rn)?[ t])*(?:[^()<>@,;:\".[] 00-31]+(?:(?:(?:rn)?[ t])+|Z|(?=[["()<>@,;:\".[]]))|[([^[]r\]|\.)*](?:(?:rn)?[ t])*)(?:.(?:(?:rn)?[ t])*(?:[^()<>@,;:\".[] 00-31]+(?:(?:(?:rn)?[ t])+|Z|(?=[["()<>@,;:\".[]]))|[([^[]r\]|\.)*](?:(?:rn)?[ t])*))*>(?:(?:rn)?[ t])*)(?:,s*(?:(?:[^()<>@,;:\".[] 00-31]+(?:(?:(?:rn)?[ t])+|Z|(?=[["()<>@,;:\".[]]))|"(?:[^"r\]|\.|(?:(?:rn)?[ t]))*"(?:(?:rn)?[ t])*)(?:.(?:(?:rn)?[ t])*(?:[^()<>@,;:\".[] 00-31]+(?:(?:(?:rn)?[ t])+|Z|(?=[["()<>@,;:\".[]]))|"(?:[^"r\]|\.|(?:(?:rn)?[ t]))*"(?:(?:rn)?[ t])*))*@(?:(?:rn)?[ t])*(?:[^()<>@,;:\".[] 00-31]+(?:(?:(?:rn)?[ t])+|Z|(?=[["()<>@,;:\".[]]))|[([^[]r\]|\.)*](?:(?:rn)?[ t])*)(?:.(?:(?:rn)?[ t])*(?:[^()<>@,;:\".[] 00-31]+(?:(?:(?:rn)?[ t])+|Z|(?=[["()<>@,;:\".[]]))|[([^[]r\]|\.)*](?:(?:rn)?[ t])*))*|(?:[^()<>@,;:\".[] 00-31]+(?:(?:(?:rn)?[ t])+|Z|(?=[["()<>@,;:\".[]]))|"(?:[^"r\]|\.|(?:(?:rn)?[ t]))*"(?:(?:rn)?[ t])*)*<(?:(?:rn)?[ t])*(?:@(?:[^()<>@,;:\".[] 00-31]+(?:(?:(?:rn)?[ t])+|Z|(?=[["()<>@,;:\".[]]))|[([^[]r\]|\.)*](?:(?:rn)?[ t])*)(?:.(?:(?:rn)?[ t])*(?:[^()<>@,;:\".[] 00-31]+(?:(?:(?:rn)?[ t])+|Z|(?=[["()<>@,;:\".[]]))|[([^[]r\]|\.)*](?:(?:rn)?[ t])*))*(?:,@(?:(?:rn)?[t])*(?:[^()<>@,;:\".[] 00-31]+(?:(?:(?:rn)?[ t])+|Z|(?=[["()<>@,;:\".[]]))|[([^[]r\]|\.)*](?:(?:rn)?[ t])*)(?:.(?:(?:rn)?[ t])*(?:[^()<>@,;:\".[] 00-31]+(?:(?:(?:rn)?[ t])+|Z|(?=[["()<>@,;:\".[]]))|[([^[]r\]|\.)*](?:(?:rn)?[ t])*))*)*:(?:(?:rn)?[ t])*)?(?:[^()<>@,;:\".[] 00-31]+(?:(?:(?:rn)?[ t])+|Z|(?=[["()<>@,;:\".[]]))|"(?:[^"r\]|\.|(?:(?:rn)?[ t]))*"(?:(?:rn)?[ t])*)(?:.(?:(?:rn)?[ t])*(?:[^()<>@,;:\".[] 00-31]+(?:(?:(?:rn)?[ t])+|Z|(?=[["()<>@,;:\".[]]))|"(?:[^"r\]|\.|(?:(?:rn)?[ t]))*"(?:(?:rn)?[ t])*))*@(?:(?:rn)?[ t])*(?:[^()<>@,;:\".[] 00-31]+(?:(?:(?:rn)?[ t])+|Z|(?=[["()<>@,;:\".[]]))|[([^[]r\]|\.)*](?:(?:rn)?[ t])*)(?:.(?:(?:rn)?[ t])*(?:[^()<>@,;:\".[] 00-31]+(?:(?:(?:rn)?[ t])+|Z|(?=[["()<>@,;:\".[]]))|[([^[]r\]|\.)*](?:(?:rn)?[ t])*))*>(?:(?:rn)?[ t])*))*)?;s

Perl 5.10 及以后版本

上面的版本,嗯,我可以说是天书吗?反正我是没有解读的想法了。当然,新版本的 Perl 语言还有一个更易读的版本(你是说真的么?) 

/(?(DEFINE)
(?
(?&mailbox) | (?&group)) (? (?&name_addr) | (?&addr_spec)) (? (?&display_name)? (?&angle_addr)) (? (?&CFWS)? < (?&addr_spec) > (?&CFWS)?) (? (?&display_name) : (?:(?&mailbox_list) | (?&CFWS))? ; (?&CFWS)?) (? (?&phrase)) (? (?&mailbox) (?: , (?&mailbox))*) (? (?&local_part) @ (?&domain)) (? (?&dot_atom) | (?"ed_string)) (? (?&dot_atom) | (?&domain_literal)) (? (?&CFWS)? [ (?: (?&FWS)? (?&dcontent))* (?&FWS)? ] (?&CFWS)?) (? (?&dtext) | (?"ed_pair)) (? (?&NO_WS_CTL) | [x21-x5ax5e-x7e]) (? (?&ALPHA) | (?&DIGIT) | [!#$%&'*+-/=?^_`{|}~]) (? (?&CFWS)? (?&atext)+ (?&CFWS)?) (? (?&CFWS)? (?&dot_atom_text) (?&CFWS)?) (? (?&atext)+ (?: . (?&atext)+)*) (? [x01-x09x0bx0cx0e-x7f]) (? \ (?&text)) (? (?&NO_WS_CTL) | [x21x23-x5bx5d-x7e]) (? (?&qtext) | (?"ed_pair)) (? (?&CFWS)? (?&DQUOTE) (?:(?&FWS)? (?&qcontent))* (?&FWS)? (?&DQUOTE) (?&CFWS)?) (? (?&atom) | (?"ed_string)) (? (?&word)+) # Folding white space (? (?: (?&WSP)* (?&CRLF))? (?&WSP)+) (? (?&NO_WS_CTL) | [x21-x27x2a-x5bx5d-x7e]) (? (?&ctext) | (?"ed_pair) | (?&comment)) (? ( (?: (?&FWS)? (?&ccontent))* (?&FWS)? ) ) (? (?: (?&FWS)? (?&comment))* (?: (?:(?&FWS)? (?&comment)) | (?&FWS))) # No whitespace control (? [x01-x08x0bx0cx0e-x1fx7f]) (? [A-Za-z]) (? [0-9]) (? x0d x0a) (? ") (? [x20x09]) ) (?&address)/x

Ruby (简单版)

Ruby 表示,其实人家还有个简单版本:

/A([w+-].?)+@[a-zd-]+(.[a-z]+)*.[a-z]+z/i

.NET

这样的版本谁没有啊——.NET 说:

^w+([-+.']w+)*@w+([-.]w+)*.w+([-.]w+)*$

grep 命令

用 grep 命令在文件中查找邮件地址,我想你不会写个若干行的正则表达式吧,意思一下就行了:

$ grep -E -o "b[A-Za-z0-9._%+-]+@[A-Za-z0-9.-]+.[A-Za-z]{2,6}b" filename.txt

SQL Server 

在 SQL Server 中也是可以用正则表达式的,不过这个代码片段应该是来自某个产品环境中的,所以,还体贴的照顾了那些把邮件地址写错的人:

 select email
 from table_name where
 patindex ('%[ &'',":;!+=/()<>]%', email) > 0 -- Invalid characters
 or patindex ('[@.-_]%', email) > 0 -- Valid but cannot be starting character
 or patindex ('%[@.-_]', email) > 0 -- Valid but cannot be ending character
 or email not like '%@%.%' -- Must contain at least one @ and one .
 or email like '%..%' -- Cannot have two periods in a row
 or email like '%@%@%' -- Cannot have two @ anywhere
 or email like '%.@%' or email like '%@.%' -- Cannot have @ and . next to each other
 or email like '%.cm' or email like '%.co' -- Camaroon or Colombia? Typos.
 or email like '%.or' or email like '%.ne' -- Missing last letter

Oracle PL/SQL

这个是不是有点偷懒?尤其是在那些“复杂”的正则表达式之后:

SELECT email
FROM table_name
WHERE REGEXP_LIKE (email, '[A-Z0-9._%-]+@[A-Z0-9._%-]+.[A-Z]{2,4}');

MySQL

好吧,看来最后也一样懒:

SELECT * FROM `users` WHERE `email` NOT REGEXP '^[A-Z0-9._%-]+@[A-Z0-9.-]+.[A-Z]{2,4}$';

那么,你有没有关于验证邮件地址的正则表达式分享给大家?

 

来源:https://linux.cn/article-5963-1.html

无忧之道:Docker中容器的备份、恢复和迁移

今天,我们将学习如何快速地对docker容器进行快捷备份、恢复和迁移。Docker是一个开源平台,用于自动化部署应用,以通过快捷的途径在称之为容器的轻量级软件层下打包、发布和运行这些应用。它使得应用平台独立,因为它扮演了Linux上一个额外的操作系统级虚拟化的自动化抽象层。它通过其组件cgroups和命名空间利用Linux内核的资源分离特性,达到避免虚拟机开销的目的。它使得用于部署和扩展web应用、数据库和后端服务的大规模构建组件无需依赖于特定的堆栈或供应者。

所谓的容器,就是那些创建自Docker镜像的软件层,它包含了独立的Linux文件系统和开箱即用的应用程序。如果我们有一个在机器中运行着的Docker容器,并且想要备份这些容器以便今后使用,或者想要迁移这些容器,那么,本教程将帮助你掌握在Linux操作系统中备份、恢复和迁移Docker容器的方法。

我们怎样才能在Linux中备份、恢复和迁移Docker容器呢?这里为您提供了一些便捷的步骤。

无忧之道:Docker中容器的备份、恢复和迁移
无忧之道:Docker中容器的备份、恢复和迁移

1. 备份容器

首先,为了备份Docker中的容器,我们会想看看我们想要备份的容器列表。要达成该目的,我们需要在我们运行着Docker引擎,并已创建了容器的Linux机器中运行 docker ps 命令。

# docker ps

Docker Containers List

在此之后,我们要选择我们想要备份的容器,然后去创建该容器的快照。我们可以使用 docker commit 命令来创建快照。

# docker commit -p 30b8f18f20b4 container-backup

Docker Commit

该命令会生成一个作为Docker镜像的容器快照,我们可以通过运行 docker images 命令来查看Docker镜像,如下。

# docker images

Docker Images

正如我们所看见的,上面做的快照已经作为Docker镜像保存了。现在,为了备份该快照,我们有两个选择,一个是我们可以登录进Docker注册中心,并推送该镜像;另一个是我们可以将Docker镜像打包成tar包备份,以供今后使用。

如果我们想要在Docker注册中心上传或备份镜像,我们只需要运行 docker login 命令来登录进Docker注册中心,然后推送所需的镜像即可。

# docker login

Docker Login

# docker tag a25ddfec4d2a arunpyasi/container-backup:test
# docker push arunpyasi/container-backup
无忧之道:Docker中容器的备份、恢复和迁移
无忧之道:Docker中容器的备份、恢复和迁移

如果我们不想备份到docker注册中心,而是想要将此镜像保存在本地机器中,以供日后使用,那么我们可以将其作为tar包备份。要完成该操作,我们需要运行以下 docker save 命令。

# docker save -o ~/container-backup.tar container-backup

taking tarball backup

要验证tar包是否已经生成,我们只需要在保存tar包的目录中运行 ls 命令即可。

2. 恢复容器

接下来,在我们成功备份了我们的Docker容器后,我们现在来恢复这些制作了Docker镜像快照的容器。如果我们已经在注册中心推送了这些Docker镜像,那么我们仅仅需要把那个Docker镜像拖回并直接运行即可。

# docker pull arunpyasi/container-backup:test
无忧之道:Docker中容器的备份、恢复和迁移
无忧之道:Docker中容器的备份、恢复和迁移

但是,如果我们将这些Docker镜像作为tar包文件备份到了本地,那么我们只要使用 docker load 命令,后面加上tar包的备份路径,就可以加载该Docker镜像了。

# docker load -i ~/container-backup.tar

现在,为了确保这些Docker镜像已经加载成功,我们来运行 docker images 命令。

# docker images

在镜像被加载后,我们将用加载的镜像去运行Docker容器。

# docker run -d -p 80:80 container-backup

Restoring Docker Tarball

3. 迁移Docker容器

迁移容器同时涉及到了上面两个操作,备份和恢复。我们可以将任何一个Docker容器从一台机器迁移到另一台机器。在迁移过程中,首先我们将把容器备份为Docker镜像快照。然后,该Docker镜像或者是被推送到了Docker注册中心,或者被作为tar包文件保存到了本地。如果我们将镜像推送到了Docker注册中心,我们简单地从任何我们想要的机器上使用 docker run 命令来恢复并运行该容器。但是,如果我们将镜像打包成tar包备份到了本地,我们只需要拷贝或移动该镜像到我们想要的机器上,加载该镜像并运行需要的容器即可。

尾声

最后,我们已经学习了如何快速地备份、恢复和迁移Docker容器,本教程适用于各个可以成功运行Docker的操作系统平台。真的,Docker是一个相当简单易用,然而功能却十分强大的工具。它的命令相当易记,这些命令都非常短,带有许多简单而强大的标记和参数。上面的方法让我们备份容器时很是安逸,使得我们可以在日后很轻松地恢复它们。这会帮助我们恢复我们的容器和镜像,即便主机系统崩溃,甚至意外地被清除。如果你还有很多问题、建议、反馈,请在下面的评论框中写出来吧,可以帮助我们改进或更新我们的内容。谢谢大家!享受吧 🙂


via: http://linoxide.com/linux-how-to/backup-restore-migrate-containers-docker/

作者:Arun Pyasi 译者:GOLinux 校对:wxy

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

来源:https://linux.cn/article-5967-1.html

如何监控 NGINX(第一篇)

如何监控 NGINX(第一篇)
如何监控 NGINX(第一篇)

NGINX 是什么?

NGINX (发音为 “engine X”) 是一种流行的 HTTP 和反向代理服务器。作为一个 HTTP 服务器,NGINX 可以使用较少的内存非常高效可靠地提供静态内容。作为反向代理,它可以用作多个后端服务器或类似缓存和负载平衡这样的其它应用的单一访问控制点。NGINX 是一个自由开源的产品,并有一个具备更全的功能的叫做 NGINX Plus 的商业版。

NGINX 也可以用作邮件代理和通用的 TCP 代理,但本文并不直接讨论 NGINX 的那些用例的监控。

NGINX 主要指标

通过监控 NGINX 可以 捕获到两类问题:NGINX 本身的资源问题,和出现在你的基础网络设施的其它问题。大多数 NGINX 用户会用到以下指标的监控,包括每秒请求数,它提供了一个由所有最终用户活动组成的上层视图;服务器错误率 ,这表明你的服务器已经多长没有处理看似有效的请求;还有请求处理时间,这说明你的服务器处理客户端请求的总共时长(并且可以看出性能降低或当前环境的其他问题)。

更一般地,至少有三个主要的指标类别来监视:

  • 基本活动指标
  • 错误指标
  • 性能指标

下面我们将分析在每个类别中最重要的 NGINX 指标,以及用一个相当普遍但是值得特别提到的案例来说明:使用 NGINX Plus 作反向代理。我们还将介绍如何使用图形工具或可选择的监控工具来监控所有的指标。

本文引用指标术语来自我们的“监控 101 系列”,,它提供了一个指标收集和警告框架。

基本活跃指标

无论你在怎样的情况下使用 NGINX,毫无疑问你要监视服务器接收多少客户端请求和如何处理这些请求。

NGINX Plus 上像开源 NGINX 一样可以报告基本活跃指标,但它也提供了略有不同的辅助模块。我们首先讨论开源的 NGINX,再来说明 NGINX Plus 提供的其他指标的功能。

NGINX

下图显示了一个客户端连接的过程,以及开源版本的 NGINX 如何在连接过程中收集指标。

如何监控 NGINX(第一篇)
如何监控 NGINX(第一篇)

Accepts(接受)、Handled(已处理)、Requests(请求数)是一直在增加的计数器。Active(活跃)、Waiting(等待)、Reading(读)、Writing(写)随着请求量而增减。

名称 描述 指标类型
Accepts(接受) NGINX 所接受的客户端连接数 资源: 功能
Handled(已处理) 成功的客户端连接数 资源: 功能
Active(活跃) 当前活跃的客户端连接数 资源: 功能
Dropped(已丢弃,计算得出) 丢弃的连接数(接受 – 已处理) 工作:错误*
Requests(请求数) 客户端请求数 工作:吞吐量

*严格的来说,丢弃的连接是 一个资源饱和指标,但是因为饱和会导致 NGINX 停止服务(而不是延后该请求),所以,“已丢弃”视作 一个工作指标 比较合适。

NGINX worker 进程接受 OS 的连接请求时 Accepts 计数器增加,而Handled 是当实际的请求得到连接时(通过建立一个新的连接或重新使用一个空闲的)。这两个计数器的值通常都是相同的,如果它们有差别则表明连接被Dropped,往往这是由于资源限制,比如已经达到 NGINX 的worker_connections的限制。

一旦 NGINX 成功处理一个连接时,连接会移动到Active状态,在这里对客户端请求进行处理:

Active状态

  • Waiting: 活跃的连接也可以处于 Waiting 子状态,如果有在此刻没有活跃请求的话。新连接可以绕过这个状态并直接变为到 Reading 状态,最常见的是在使用“accept filter(接受过滤器)” 和 “deferred accept(延迟接受)”时,在这种情况下,NGINX 不会接收 worker 进程的通知,直到它具有足够的数据才开始响应。如果连接设置为 keep-alive ,那么它在发送响应后将处于等待状态。

  • Reading: 当接收到请求时,连接离开 Waiting 状态,并且该请求本身使 Reading 状态计数增加。在这种状态下 NGINX 会读取客户端请求首部。请求首部是比较小的,因此这通常是一个快速的操作。

  • Writing: 请求被读取之后,其使 Writing 状态计数增加,并保持在该状态,直到响应返回给客户端。这意味着,该请求在 Writing 状态时, 一方面 NGINX 等待来自上游系统的结果(系统放在 NGINX “后面”),另外一方面,NGINX 也在同时响应。请求往往会在 Writing 状态花费大量的时间。

通常,一个连接在同一时间只接受一个请求。在这种情况下,Active 连接的数目 == Waiting 的连接 + Reading 请求 + Writing 。然而,较新的 SPDY 和 HTTP/2 协议允许多个并发请求/响应复用一个连接,所以 Active 可小于 Waiting 的连接、 Reading 请求、Writing 请求的总和。 (在撰写本文时,NGINX 不支持 HTTP/2,但预计到2015年期间将会支持。)

NGINX Plus

正如上面提到的,所有开源 NGINX 的指标在 NGINX Plus 中是可用的,但另外也提供其他的指标。本节仅说明了 NGINX Plus 可用的指标。

如何监控 NGINX(第一篇)
如何监控 NGINX(第一篇)

Accepted (已接受)、Dropped,总数是不断增加的计数器。Active、 Idle(空闲)和处于 Current(当前)处理阶段的各种状态下的连接或请​​求的当前数量随着请求量而增减。

名称 描述 指标类型
Accepted(已接受) NGINX 所接受的客户端连接数 资源: 功能
Dropped(已丢弃) 丢弃的连接数(接受 – 已处理) 工作:错误*
Active(活跃) 当前活跃的客户端连接数 资源: 功能
Idle(空闲) 没有当前请求的客户端连接 资源: 功能
Total(全部请求数) 客户端请求数 工作:吞吐量

*严格的来说,丢弃的连接是 一个资源饱和指标,但是因为饱和会导致 NGINX 停止服务(而不是延后该请求),所以,“已丢弃”视作 一个工作指标 比较合适。

当 NGINX Plus worker 进程接受 OS 的连接请求时 Accepted 计数器递增。如果 worker 进程为请求建立连接失败(通过建立一个新的连接或重新使用一个空闲),则该连接被丢弃, Dropped 计数增加。通常连接被丢弃是因为资源限制,如 NGINX Plus 的worker_connections的限制已经达到。

ActiveIdle如上所述的开源 NGINX 的“active” 和 “waiting”状态是相同的,但是有一点关键的不同:在开源 NGINX 上,“waiting”状态包括在“active”中,而在 NGINX Plus 上“idle”的连接被排除在“active” 计数外。Current 和开源 NGINX 是一样的也是由“reading + writing” 状态组成。

Total 为客户端请求的累积计数。请注意,单个客户端连接可涉及多个请求,所以这个数字可能会比连接的累计次数明显大。事实上,(total / accepted)是每个连接的平均请求数量。

开源 和 Plus 之间指标的不同

NGINX (开源) NGINX Plus
accepts accepted
dropped 通过计算得来 dropped 直接得到
reading + writing current
waiting idle
active (包括 “waiting”状态) active (排除 “idle” 状态)
requests total

提醒指标: 丢弃连接

被丢弃的连接数目等于 Accepts 和 Handled 之差(NGINX 中),或是可直接得到的标准指标(NGINX Plus 中)。在正常情况下,丢弃连接数应该是零。如果在每个单位时间内丢弃连接的速度开始上升,那么应该看看是否资源饱和了。

如何监控 NGINX(第一篇)
如何监控 NGINX(第一篇)

提醒指标: 每秒请求数

按固定时间间隔采样你的请求数据(开源 NGINX 的requests或者 NGINX Plus 中total) 会提供给你单位时间内(通常是分钟或秒)所接受的请求数量。监测这个指标可以查看进入的 Web 流量尖峰,无论是合法的还是恶意的,或者突然的下降,这通常都代表着出现了问题。每秒请求数若发生急剧变化可以提醒你的环境出现问题了,即使它不能告诉你确切问题的位置所在。请注意,所有的请求都同样计数,无论 URL 是什么。

如何监控 NGINX(第一篇)
如何监控 NGINX(第一篇)

收集活跃指标

开源的 NGINX 提供了一个简单状态页面来显示基本的服务器指标。该状态信息以标准格式显示,实际上任何图形或监控工具可以被配置去解析这些相关数据,以用于分析、可视化、或提醒。NGINX Plus 提供一个 JSON 接口来供给更多的数据。阅读相关文章“NGINX 指标收集”来启用指标收集的功能。

错误指标

名称 描述 指标类型 可用于
4xx 代码 客户端错误计数 工作:错误 NGINX 日志, NGINX Plus
5xx 代码 服务器端错误计数 工作:错误 NGINX 日志, NGINX Plus

NGINX 错误指标告诉你服务器是否经常返回错误而不是正常工作。客户端错误返回4XX状态码,服务器端错误返回5XX状态码。

提醒指标: 服务器错误率

服务器错误率等于在单位时间(通常为一到五分钟)内5xx错误状态代码的总数除以状态码(1XX,2XX,3XX,4XX,5XX)的总数。如果你的错误率随着时间的推移开始攀升,调查可能的原因。如果突然增加,可能需要采取紧急行动,因为客户端可能收到错误信息。

如何监控 NGINX(第一篇)
如何监控 NGINX(第一篇)

关于客户端错误的注意事项:虽然监控4XX是很有用的,但从该指标中你仅可以捕捉有限的信息,因为它只是衡量客户的行为而不捕捉任何特殊的 URL。换句话说,4xx出现的变化可能是一个信号,例如网络扫描器正在寻找你的网站漏洞时。

收集错误度量

虽然开源 NGINX 不能马上得到用于监测的错误率,但至少有两种方法可以得到:

  • 使用商业支持的 NGINX Plus 提供的扩展状态模块
  • 配置 NGINX 的日志模块将响应码写入访问日志

关于这两种方法,请阅读相关文章“NGINX 指标收集”。

性能指标

名称 描述 指标类型 可用于
request time (请求处理时间) 处理每个请求的时间,单位为秒 工作:性能 NGINX 日志

提醒指标: 请求处理时间

请求处理时间指标记录了 NGINX 处理每个请求的时间,从读到客户端的第一个请求字节到完成请求。较长的响应时间说明问题在上游。

收集处理时间指标

NGINX 和 NGINX Plus 用户可以通过添加 $request_time 变量到访问日志格式中来捕​​捉处理时间数据。关于配置日志监控的更多细节在NGINX指标收集

反向代理指标

名称 描述 指标类型 可用于
上游服务器的活跃链接 当前活跃的客户端连接 资源:功能 NGINX Plus
上游服务器的 5xx 错误代码 服务器错误 工作:错误 NGINX Plus
每个上游组的可用服务器 服务器传递健康检查 资源:可用性 NGINX Plus

反向代理是 NGINX 最常见的使用方法之一。商业支持的 NGINX Plus 显示了大量有关后端(或“上游 upstream”)的服务器指标,这些与反向代理设置相关的。本节重点介绍了几个 NGINX Plus 用户可用的关键上游指标。

NGINX Plus 首先将它的上游指标按组分开,然后是针对单个服务器的。因此,例如,你的反向代理将请求分配到五个上游的 Web 服务器上,你可以一眼看出是否有单个服务器压力过大,也可以看出上游组中服务器的健康状况,以确保良好的响应时间。

活跃指标

每上游服务器的活跃连接的数量可以帮助你确认反向代理是否正确的分配工作到你的整个服务器组上。如果你正在使用 NGINX 作为负载均衡器,任何一台服务器处理的连接数的明显偏差都可能表明服务器正在努力消化请求,或者是你配置使用的负载均衡的方法(例如round-robin 或 IP hashing)不是最适合你流量模式的。

错误指标

错误指标,上面所说的高于5XX(服务器错误)状态码,是监控指标中有价值的一个,尤其是响应码部分。 NGINX Plus 允许你轻松地提取每个上游服务器的 5xx 错误代码的数量,以及响应的总数量,以此来确定某个特定服务器的错误率。

可用性指标

对于 web 服务器的运行状况,还有另一种角度,NGINX 可以通过每个组中当前可用服务器的总量很方便监控你的上游组的健康。在一个大的反向代理上,你可能不会非常关心其中一个服务器的当前状态,就像你只要有可用的服务器组能够处理当前的负载就行了。但监视上游组内的所有工作的服务器总量可为判断 Web 服务器的健康状况提供一个更高层面的视角。

收集上游指标

NGINX Plus 上游指标显示在内部 NGINX Plus 的监控仪表盘上,并且也可通过一个JSON 接口来服务于各种外部监控平台。在我们的相关文章“NGINX指标收集”中有个例子。

结论

在这篇文章中,我们已经谈到了一些有用的指标,你可以使用表格来监控 NGINX 服务器。如果你是刚开始使用 NGINX,监控下面提供的大部分或全部指标,可以让你很好的了解你的网络基础设施的健康和活跃程度:

最终,你会学到更多,更专业的衡量指标,尤其是关于你自己基础设施和使用情况的。当然,监控哪一项指标将取决于你可用的工具。参见相关的文章来逐步指导你的指标收集,不管你使用 NGINX 还是 NGINX Plus。

在 Datadog 中,我们已经集成了 NGINX 和 NGINX Plus,这样你就可以以最少的设置来收集和监控所有 Web 服务器的指标。 在本文中了解如何用 NGINX Datadog来监控,并开始免费试用 Datadog吧。

诚谢

在文章发表之前非常感谢 NGINX 团队审阅这篇,并提供重要的反馈和说明。


via: https://www.datadoghq.com/blog/how-to-monitor-nginx/

作者:K Young 译者:strugglingyouth 校对:wxy

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

来源:https://linux.cn/article-5970-1.html

打造一个全命令行的Android构建系统

“IDE都是给小白程序员的,大牛级别的程序员一定是命令行控,终端控,你看大牛都是使用vim,emacs 就一切搞定”

这话说的虽然有些绝对,但是也不无道理,做开发这行要想效率高,自动化还真是缺少不了命令行工具,因为只有命令行才是最佳的人机交互工具。其实IDE也是底层也是调用命令行工具而已,只不过给普通开发者呈现一个更友好的开发界面。这里可不是宣扬让大家放弃IDE都改命令行,只是每种事物都有他存在的理由,无论是编程语言还是工具都是一个原则 “没有最好的,只有最合适的”。

打造一个全命令行的Android构建系统
打造一个全命令行的Android构建系统

前一段时间做一个人产品 http://xbrowser.me ,发布产品的时候为了统计各渠道流量免不了要构建不通的渠道包,你懂得国内渠道上百个,靠IDE编译打包非吐血不可。这些重复劳动最适合交个程序来做,很多程序员想不明白这个问题,宁愿把大量的精力时间花在业务上,却不知道用工具提高工作效率。在这里写一篇简单的教程,告诉大家怎么脱离IDE环境完成一个android项目的编译构建,有了这基础开发什么自动化构建工具都不是什么难事了, 前一阵子做的一个打包html5应用的在线工具AppBuilder就是基于命令行构建完成的。

说到命令行自然是不需要图形界面,所以Android SDK的安装下载自然都是在终端上进行。下面是本文中使用的一些SDK和基本环境。

  • ubuntu server 14.04 (64位)
  • JDK 1.7
  • android-sdk_r24.0
  • gradle-2.2.1

进入正题,接下来一步一步介绍如何安装配置一个命令行下的编译构建系统.

step 1 安装 JDK 环境

配合android的JDK最好选用JDK官方版本而不是Open JDK,下面是在unbuntu下安装JDK 1.7的方法。

sudo add-apt-repository ppa:webupd8team/java
sudo apt-get update
sudo apt-get install oracle-java7-installer

step 2 安装 Android SDK

android sdk 工具包的一些命令行工具是基于32位系统的,在64为平台运行32程序必须安装 i386 的一些依赖库,方法如下:

sudo dpkg --add-architecture i386
sudo apt-get update
sudo apt-get install libc6:i386 libncurses5:i386 libstdc++6:i386 lib32z1

安装完成32位的依赖库后,我们使用wget 去官方下载最新的linux下android SDK包。

cd ~
wget http://dl.google.com/android/android-sdk_r24.0.1-linux.tgz
tar xvzf android-sdk_r24.0.1-linux.tgz

编辑 .profile 或者 .bash_profile 把下面的目录增加到 path的搜索路径中,确保android SDK的的一些命令工具可以直接在终端使用,比如 adb 命令。

ANDROID_HOME=$HOME/android-sdk-linux
export PATH="$PATH:$ANDROID_HOME/tools:$ANDROID_HOME/platform-tools"
exprot ANDROID_HOME

使环境变量生效

source ~/.profile

环境变量生效后,你可以使用android命令 列出sdk相关的列表,以便我们选择和自己项目匹配的SDK版本。(刚才只是安装了最基础的SDK,要完全满足你的开发环境需要还得从下面的列表中选择你需要的SDK和工具更新下载)

android list sdk --all

输出如下所示:

   1- Android SDK Tools, revision 24.0.1
   2- Android SDK Platform-tools, revision 21
   3- Android SDK Build-tools, revision 21.1.2
   4- Android SDK Build-tools, revision 21.1.1
   5- Android SDK Build-tools, revision 21.1
   6- Android SDK Build-tools, revision 21.0.2
   7- Android SDK Build-tools, revision 21.0.1
   8- Android SDK Build-tools, revision 21
   9- Android SDK Build-tools, revision 20
  10- Android SDK Build-tools, revision 19.1
  11- Android SDK Build-tools, revision 19.0.3
  12- Android SDK Build-tools, revision 19.0.2
  13- Android SDK Build-tools, revision 19.0.1
  14- Android SDK Build-tools, revision 19
  15- Android SDK Build-tools, revision 18.1.1
  16- Android SDK Build-tools, revision 18.1
  17- Android SDK Build-tools, revision 18.0.1
  18- Android SDK Build-tools, revision 17
  19- Documentation for Android SDK, API 21, revision 1
  20- SDK Platform Android 5.0.1, API 21, revision 2
  21- SDK Platform Android 4.4W.2, API 20, revision 2
  22- SDK Platform Android 4.4.2, API 19, revision 4
  23- SDK Platform Android 4.3.1, API 18, revision 3
  24- SDK Platform Android 4.2.2, API 17, revision 3
  ....

这里包括不同的Android API 版本和不同的构建工具,选择你想要安装项目的序号,这里我想安装 build tools 19.1 ,build tools 21 及 android 4.2.2以上的SDK所以选择序号 “1,2,3,20,21,22,23”

android update sdk -u -a -t  1,2,3,10,20,21,22,23

step 3 安装gradle构建环境

使用Ant构建项目已经是过去式了,这里我们选用更加强悍和方便的构建工具gradle 。

下载 grdle 二进制包

cd ~
wget https://services.gradle.org/distributions/gradle-2.2.1-bin.zip

释放到本地Home目录,创建名字为”gradle”的符号链接,符号连接的好处是方便版本更新,有了新的版本直接修改符号链接即可。

 unzip gradle-2.2.1-bin.zip
 ln -s gradle-2.2.1 gradle

配置gradle环境变量并使其生效,编辑 ~/.profje 文件增加下面内容

GRADLE_HOME=$HOME/gradle
export PATH=$PATH:$GRADLE_HOME/bin

保存后使环境变量使其生效

source ~/.profile

环境变量生效后你可以在终端敲入’gradle’命令并运行用以检测gradle是否安装成功。

gradle

如果安装配置的没有问题将会提示类似下面的信息

:help
Welcome to Gradle 2.2.1
To run a build, run gradle  ...
To see a list of available tasks, run gradle tasks
To see a list of command-line options, run gradle --help
BUILD SUCCESSFUL

验证是否可以编译android 应用

完成以上的环境配置,我们的一个Android下的基础构建环境就全部配置好了,接下来做的事情就是尝试使用gradle 编译一个android 应用出来验证我的编译环境是否OK,下载我写的一个gadle demo 例子进行测试。

git clone https://github.com/examplecode/gradle_demo
cd gradle_demo/hello-apk-with-gradle
gradle build

一切顺利的话,编译完成后,你会在 “hello-apk-with-gradle/build/outputs/apk” 目录下找到编译好的apk包。至于如何整合到你自己的项目中去,只需要仿照例子给你的项目提供一个”gradle.build” 脚本即可。

来源:http://www.jianshu.com/p/1694ea9a3f90

如何修复:There is no command installed for 7-zip archive files

问题

我试着在Ubuntu中安装Emerald图标主题,而这个主题被打包成了.7z归档包。和以往一样,我试着通过在GUI中右击并选择“提取到这里”来将它解压缩。但是Ubuntu 15.04却并没有解压文件,取而代之的,却是丢给了我一个下面这样的错误信息:

Could not open this file

无法打开该文件

There is no command installed for 7-zip archive files. Do you want to search for a command to open this file?

没有安装用于7-zip归档文件的命令。你是否想要搜索用于来打开该文件的命令?

错误信息看上去是这样的:

如何修复:There is no command installed for 7-zip archive files
如何修复:There is no command installed for 7-zip archive files

原因

发生该错误的原因从错误信息本身来看就十分明了。7Z,称之为7-zip更好,该程序没有安装,因此7Z压缩文件就无法解压缩。这也暗示着Ubuntu默认不支持7-zip文件。

解决方案:在Ubuntu中安装 7zip

要解决该问题也十分简单,在Ubuntu中安装7-Zip包即可。现在,你也许想知道如何在Ubuntu中安装 7Zip吧?好吧,在前面的错误对话框中,如果你右击“Search Command”搜索命令,它会查找可用的 p7zip 包。只要点击“Install”安装,如下图:

如何修复:There is no command installed for 7-zip archive files
如何修复:There is no command installed for 7-zip archive files

可选方案:在终端中安装 7Zip

如果偏好使用终端,你可以使用以下命令在终端中安装 7zip:

sudo apt-get install p7zip-full

注意:在Ubuntu中,你会发现有3个7zip包:p7zip,p7zip-full 和 p7zip-rar。p7zip和p7zip-full的区别在于,p7zip是一个更轻量化的版本,仅仅提供了对 .7z 和 .7za 文件的支持,而完整版则提供了对更多(用于音频文件等的) 7z 压缩算法的支持。对于 p7zip-rar,它除了对 7z 文件的支持外,也提供了对 .rar 文件的支持。

事实上,相同的错误也会发生在Ubuntu中的RAR文件身上。解决方案也一样,安装正确的程序即可。

希望这篇快文帮助你解决了Ubuntu 14.04中如何打开 7zip的谜团。如有任何问题或建议,我们将无任欢迎。


via: http://itsfoss.com/fix-there-is-no-command-installed-for-7-zip-archive-files/

作者:Abhishek 译者:GOLinux 校对:wxy

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

来源:https://linux.cn/article-5972-1.html

使用 Find 命令来帮你找到那些需要清理的文件

使用 Find 命令来帮你找到那些需要清理的文件
使用 Find 命令来帮你找到那些需要清理的文件

Credit: Sandra H-S

有一个问题几乎困扰着所有的文件系统 — 包括 Unix 和其他的 — 那就是文件的不断积累。几乎没有人愿意花时间清理掉他们不再使用的文件和整理文件系统,结果,文件变得很混乱,很难找到有用的东西,要使它们运行良好、维护备份、易于管理,这将是一种持久的挑战。

我见过的一种解决问题的方法是建议使用者将所有的数据碎屑创建一个文件集合的总结报告或”概况”,来报告诸如所有的文件数量;最老的,最新的,最大的文件;并统计谁拥有这些文件等数据。如果有人看到五年前的一个包含五十万个文件的文件夹,他们可能会去删除哪些文件 — 或者,至少会归档和压缩。主要问题是太大的文件夹会使人担心误删一些重要的东西。如果有一个描述文件夹的方法能帮助显示文件的性质,那么你就可以去清理它了。

当我准备做 Unix 文件系统的总结报告时,几个有用的 Unix 命令能提供一些非常有用的统计信息。要计算目录中的文件数,你可以使用这样一个 find 命令。

$ find . -type f | wc -l
187534

虽然查找最老的和最新的文件是比较复杂,但还是相当方便的。在下面的命令,我们使用 find 命令再次查找文件,以文件时间排序并按年-月-日的格式显示,在列表顶部的显然是最老的。

在第二个命令,我们做同样的,但打印的是最后一行,这是最新的。

$ find -type f -printf '%T+ %pn' | sort | head -n 1
2006-02-03+02:40:33 ./skel/.xemacs/init.el
$ find -type f -printf '%T+ %pn' | sort | tail -n 1
2015-07-19+14:20:16 ./.bash_history

printf 命令输出 %T(文件日期和时间)和 %P(带路径的文件名)参数。

如果我们在查找家目录时,无疑会发现,history 文件(如 .bash_history)是最新的,这并没有什么用。你可以通过 “un-grepping” 来忽略这些文件,也可以忽略以.开头的文件,如下图所示的。

$ find -type f -printf '%T+ %pn' | grep -v "./." | sort | tail -n 1
2015-07-19+13:02:12 ./isPrime

寻找最大的文件使用 %s(大小)参数,包括文件名(%f),因为这就是我们想要在报告中显示的。

$ find -type f -printf '%s %f n' | sort -n | uniq | tail -1
20183040 project.org.tar

统计文件的所有者,使用%u(所有者)

$ find -type f -printf '%u n' | grep -v "./." | sort | uniq -c
   180034 shs
     7500 jdoe

如果文件系统能记录上次的访问日期,也将是非常有用的,可以用来看该文件有没有被访问过,比方说,两年之内没访问过。这将使你能明确分辨这些文件的价值。这个最后访问(%a)参数这样使用:

$ find -type f -printf '%a+ %pn' | sort | head -n 1
Fri Dec 15 03:00:30 2006+ ./statreport

当然,如果大多数最近​​访问的文件也是在很久之前的,这看起来你需要处理更多文件了。

$ find -type f -printf '%a+ %pn' | sort | tail -n 1
Wed Nov 26 03:00:27 2007+ ./my-notes

要想层次分明,可以为一个文件系统或大目录创建一个总结报告,显示这些文件的日期范围、最大的文件、文件所有者们、最老的文件和最新访问时间,可以帮助文件拥有者判断当前有哪些文件夹是重要的哪些该清理了。


via: http://www.itworld.com/article/2949898/linux/profiling-your-file-systems.html

作者:Sandra Henry-Stocker 译者:strugglingyouth 校对:wxy

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

来源:https://linux.cn/article-5973-1.html