2017年7月

到目前为止,你已经偶尔听到了关于 dwarves、调试信息、一种无需解析就可以理解源码方式。今天我们会详细介绍源码级的调试信息,作为本指南后面部分使用它的准备。

系列文章索引

随着后面文章的发布,这些链接会逐渐生效。

  1. 准备环境
  2. 断点
  3. 寄存器和内存
  4. Elves 和 dwarves
  5. 源码和信号
  6. 源码级逐步执行
  7. 源码级断点
  8. 调用栈展开
  9. 读取变量
  10. 下一步

ELF 和 DWARF 简介

ELF 和 DWARF 可能是两个你没有听说过,但可能大部分时间都在使用的组件。ELF(Executable and Linkable Format,可执行和可链接格式)是 Linux 系统中使用最广泛的目标文件格式;它指定了一种存储二进制文件的所有不同部分的方式,例如代码、静态数据、调试信息以及字符串。它还告诉加载器如何加载二进制文件并准备执行,其中包括说明二进制文件不同部分在内存中应该放置的地点,哪些位需要根据其它组件的位置固定(重分配)以及其它。在这些博文中我不会用太多篇幅介绍 ELF,但是如果你感兴趣的话,你可以查看这个很好的信息图该标准

DWARF是通常和 ELF 一起使用的调试信息格式。它不一定要绑定到 ELF,但它们两者是一起发展的,一起工作得很好。这种格式允许编译器告诉调试器最初的源代码如何和被执行的二进制文件相关联。这些信息分散到不同的 ELF 部分,每个部分都衔接有一份它自己的信息。下面不同部分的定义,信息取自这个稍有过时但非常重要的 DWARF 调试格式简介

  • .debug_abbrev .debug_info 部分使用的缩略语
  • .debug_aranges 内存地址和编译的映射
  • .debug_frame 调用帧信息
  • .debug_info 包括 DWARF 信息条目 DWARF Information Entries (DIEs)的核心 DWARF 数据
  • .debug_line 行号程序
  • .debug_loc 位置描述
  • .debug_macinfo 宏描述
  • .debug_pubnames 全局对象和函数查找表
  • .debug_pubtypes 全局类型查找表
  • .debug_ranges DIEs 的引用地址范围
  • .debug_str .debug_info 使用的字符串列表
  • .debug_types 类型描述

我们最关心的是 .debug_line.debug_info 部分,让我们来看一个简单程序的 DWARF 信息。

int main() {
    long a = 3;
    long b = 2;
    long c = a + b;
    a = 4;
}

DWARF 行表

如果你用 -g 选项编译这个程序,然后将结果传递给 dwarfdump 执行,在行号部分你应该可以看到类似这样的东西:

.debug_line: line number info for a single cu
Source lines (from CU-DIE at .debug_info offset 0x0000000b):

            NS new statement, BB new basic block, ET end of text sequence
            PE prologue end, EB epilogue begin
            IS=val ISA number, DI=val discriminator value
<pc>        [lno,col] NS BB ET PE EB IS= DI= uri: "filepath"
0x00400670  [   1, 0] NS uri: "/home/simon/play/MiniDbg/examples/variable.cpp"
0x00400676  [   2,10] NS PE
0x0040067e  [   3,10] NS
0x00400686  [   4,14] NS
0x0040068a  [   4,16]
0x0040068e  [   4,10]
0x00400692  [   5, 7] NS
0x0040069a  [   6, 1] NS
0x0040069c  [   6, 1] NS ET

前面几行是一些如何理解 dump 的信息 - 主要的行号数据从以 0x00400670 开头的行开始。实际上这是一个代码内存地址到文件中行列号的映射。NS 表示地址标记一个新语句的开始,这通常用于设置断点或逐步执行。PE 表示函数序言(LCTT 译注:在汇编语言中,function prologue 是程序开始的几行代码,用于准备函数中用到的栈和寄存器)的结束,这对于设置函数断点非常有帮助。ET 表示转换单元的结束。信息实际上并不像这样编码;真正的编码是一种非常节省空间的排序程序,可以通过执行它来建立这些行信息。

那么,假设我们想在 variable.cpp 的第 4 行设置断点,我们该怎么做呢?我们查找和该文件对应的条目,然后查找对应的行条目,查找对应的地址,在那里设置一个断点。在我们的例子中,条目是:

0x00400686  [   4,14] NS

假设我们想在地址 0x00400686 处设置断点。如果你想尝试的话你可以在已经编写好的调试器上手动实现。

反过来也是如此。如果我们已经有了一个内存地址 - 例如说,一个程序计数器值 - 想找到它在源码中的位置,我们只需要从行表信息中查找最接近的映射地址并从中抓取行号。

DWARF 调试信息

.debug_info 部分是 DWARF 的核心。它给我们关于我们程序中存在的类型、函数、变量、希望和梦想的信息。这部分的基本单元是 DWARF 信息条目(DWARF Information Entry),我们亲切地称之为 DIEs。一个 DIE 包括能告诉你正在展现什么样的源码级实体的标签,后面跟着一系列该实体的属性。这是我上面展示的简单事例程序的 .debug_info 部分:

.debug_info

COMPILE_UNIT<header overall offset = 0x00000000>:
< 0><0x0000000b>  DW_TAG_compile_unit
                    DW_AT_producer              clang version 3.9.1 (tags/RELEASE_391/final)
                    DW_AT_language              DW_LANG_C_plus_plus
                    DW_AT_name                  /super/secret/path/MiniDbg/examples/variable.cpp
                    DW_AT_stmt_list             0x00000000
                    DW_AT_comp_dir              /super/secret/path/MiniDbg/build
                    DW_AT_low_pc                0x00400670
                    DW_AT_high_pc               0x0040069c

LOCAL_SYMBOLS:
< 1><0x0000002e>    DW_TAG_subprogram
                      DW_AT_low_pc                0x00400670
                      DW_AT_high_pc               0x0040069c
                      DW_AT_frame_base            DW_OP_reg6
                      DW_AT_name                  main
                      DW_AT_decl_file             0x00000001 /super/secret/path/MiniDbg/examples/variable.cpp
                      DW_AT_decl_line             0x00000001
                      DW_AT_type                  <0x00000077>
                      DW_AT_external              yes(1)
< 2><0x0000004c>      DW_TAG_variable
                        DW_AT_location              DW_OP_fbreg -8
                        DW_AT_name                  a
                        DW_AT_decl_file             0x00000001 /super/secret/path/MiniDbg/examples/variable.cpp
                        DW_AT_decl_line             0x00000002
                        DW_AT_type                  <0x0000007e>
< 2><0x0000005a>      DW_TAG_variable
                        DW_AT_location              DW_OP_fbreg -16
                        DW_AT_name                  b
                        DW_AT_decl_file             0x00000001 /super/secret/path/MiniDbg/examples/variable.cpp
                        DW_AT_decl_line             0x00000003
                        DW_AT_type                  <0x0000007e>
< 2><0x00000068>      DW_TAG_variable
                        DW_AT_location              DW_OP_fbreg -24
                        DW_AT_name                  c
                        DW_AT_decl_file             0x00000001 /super/secret/path/MiniDbg/examples/variable.cpp
                        DW_AT_decl_line             0x00000004
                        DW_AT_type                  <0x0000007e>
< 1><0x00000077>    DW_TAG_base_type
                      DW_AT_name                  int
                      DW_AT_encoding              DW_ATE_signed
                      DW_AT_byte_size             0x00000004
< 1><0x0000007e>    DW_TAG_base_type
                      DW_AT_name                  long int
                      DW_AT_encoding              DW_ATE_signed
                      DW_AT_byte_size             0x00000008

第一个 DIE 表示一个编译单元(CU),实际上是一个包括了所有 #includes 和类似语句的源文件。下面是带含义注释的属性:

DW_AT_producer   clang version 3.9.1 (tags/RELEASE_391/final)    <-- 产生该二进制文件的编译器
DW_AT_language   DW_LANG_C_plus_plus                             <-- 原编程语言
DW_AT_name       /super/secret/path/MiniDbg/examples/variable.cpp  <-- 该 CU 表示的文件名称
DW_AT_stmt_list  0x00000000                                      <-- 跟踪该 CU 的行表偏移
DW_AT_comp_dir   /super/secret/path/MiniDbg/build                  <-- 编译目录
DW_AT_low_pc     0x00400670                                      <-- 该 CU 的代码起始
DW_AT_high_pc    0x0040069c                                      <-- 该 CU 的代码结尾

其它的 DIEs 遵循类似的模式,你也很可能推测出不同属性的含义。

现在我们可以根据新学到的 DWARF 知识尝试和解决一些实际问题。

当前我在哪个函数?

假设我们有一个程序计数器值然后想找到当前我们在哪一个函数。一个解决该问题的简单算法:

for each compile unit:
    if the pc is between DW_AT_low_pc and DW_AT_high_pc:
        for each function in the compile unit:
            if the pc is between DW_AT_low_pc and DW_AT_high_pc:
                return function information

这对于很多目的都有效,但如果有成员函数或者内联(inline),就会变得更加复杂。假如有内联,一旦我们找到其范围包括我们的程序计数器(PC)的函数,我们需要递归遍历该 DIE 的所有孩子检查有没有内联函数能更好地匹配。在我的代码中,我不会为该调试器处理内联,但如果你想要的话你可以添加该功能。

如何在一个函数上设置断点?

再次说明,这取决于你是否想要支持成员函数、命名空间以及类似的东西。对于简单的函数你只需要迭代遍历不同编译单元中的函数直到你找到一个合适的名字。如果你的编译器能够填充 .debug_pubnames 部分,你可以更高效地做到这点。

一旦找到了函数,你可以在 DW_AT_low_pc 给定的内存地址设置一个断点。不过那会在函数序言处中断,但更合适的是在用户代码处中断。由于行表信息可以指定序言的结束的内存地址,你只需要在行表中查找 DW_AT_low_pc 的值,然后一直读取直到被标记为序言结束的条目。一些编译器不会输出这些信息,因此另一种方式是在该函数第二行条目指定的地址处设置断点。

假如我们想在我们示例程序中的 main 函数设置断点。我们查找名为 main 的函数,获取到它的 DIE:

< 1><0x0000002e>    DW_TAG_subprogram
                      DW_AT_low_pc                0x00400670
                      DW_AT_high_pc               0x0040069c
                      DW_AT_frame_base            DW_OP_reg6
                      DW_AT_name                  main
                      DW_AT_decl_file             0x00000001 /super/secret/path/MiniDbg/examples/variable.cpp
                      DW_AT_decl_line             0x00000001
                      DW_AT_type                  <0x00000077>
                      DW_AT_external              yes(1)

这告诉我们函数从 0x00400670 开始。如果我们在行表中查找这个,我们可以获得条目:

0x00400670  [   1, 0] NS uri: "/super/secret/path/MiniDbg/examples/variable.cpp"

我们希望跳过序言,因此我们再读取一个条目:

0x00400676  [   2,10] NS PE

Clang 在这个条目中包括了序言结束标记,因此我们知道在这里停止,然后在地址 0x00400676 处设一个断点。

我如何读取一个变量的内容?

读取变量可能非常复杂。它们是难以捉摸的东西,可能在整个函数中移动、保存在寄存器中、被放置于内存、被优化掉、隐藏在角落里,等等。幸运的是我们的简单示例是真的很简单。如果我们想读取变量 a 的内容,我们需要看它的 DW_AT_location 属性:

DW_AT_location              DW_OP_fbreg -8

这告诉我们内容被保存在以栈帧基(base of the stack frame)偏移为 -8 的地方。为了找到栈帧基,我们查找所在函数的 DW_AT_frame_base 属性。

DW_AT_frame_base            DW_OP_reg6

System V x86\_64 ABI 我们可以知道 reg6 在 x86 中是帧指针寄存器。现在我们读取帧指针的内容,从中减去 8,就找到了我们的变量。如果我们知道它具体是什么,我们还需要看它的类型:

< 2><0x0000004c>      DW_TAG_variable
                        DW_AT_name                  a
                        DW_AT_type                  <0x0000007e>

如果我们在调试信息中查找该类型,我们得到下面的 DIE:

< 1><0x0000007e>    DW_TAG_base_type
                      DW_AT_name                  long int
                      DW_AT_encoding              DW_ATE_signed
                      DW_AT_byte_size             0x00000008

这告诉我们该类型是 8 字节(64 位)有符号整型,因此我们可以继续并把这些字节解析为 int64_t 并向用户显示。

当然,类型可能比那要复杂得多,因为它们要能够表示类似 C++ 的类型,但是这能给你它们如何工作的基本认识。

再次回到帧基(frame base),Clang 可以通过帧指针寄存器跟踪帧基。最近版本的 GCC 倾向于使用 DW_OP_call_frame_cfa,它包括解析 .eh_frame ELF 部分,那是一个我不会去写的另外一篇完全不同的文章。如果你告诉 GCC 使用 DWARF 2 而不是最近的版本,它会倾向于输出位置列表,这更便于阅读:

DW_AT_frame_base            <loclist at offset 0x00000000 with 4 entries follows>
 low-off : 0x00000000 addr  0x00400696 high-off  0x00000001 addr 0x00400697>DW_OP_breg7+8
 low-off : 0x00000001 addr  0x00400697 high-off  0x00000004 addr 0x0040069a>DW_OP_breg7+16
 low-off : 0x00000004 addr  0x0040069a high-off  0x00000031 addr 0x004006c7>DW_OP_breg6+16
 low-off : 0x00000031 addr  0x004006c7 high-off  0x00000032 addr 0x004006c8>DW_OP_breg7+8

位置列表取决于程序计数器所处的位置给出不同的位置。这个例子告诉我们如果程序计数器是在 DW_AT_low_pc 偏移量为 0x0 的位置,那么帧基就在和寄存器 7 中保存的值偏移量为 8 的位置,如果它是在 0x10x4 之间,那么帧基就在和相同位置偏移量为 16 的位置,以此类推。

休息一会

这里有很多的信息需要你的大脑消化,但好消息是在后面的几篇文章中我们会用一个库替我们完成这些艰难的工作。理解概念仍然很有帮助,尤其是当出现错误或者你想支持一些你使用的 DWARF 库所没有实现的 DWARF 概念时。

如果你想了解更多关于 DWARF 的内容,那么你可以从这里获取其标准。在写这篇博客时,刚刚发布了 DWARF 5,但更普遍支持 DWARF 4。


via: https://blog.tartanllama.xyz/c++/2017/04/05/writing-a-linux-debugger-elf-dwarf/

作者:Simon Brand 译者:ictlyh 校对:wxy

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

在项目早期就遇到公开的失败后,一个著名大学的 IT 团队决定将他们的 web 注册系统部署到 Linux上,此举几乎将服务器的最大用户访问量提高了 3 倍

1998年,我在明尼苏达大学为一个新的 web 团队管理他们的服务器管理组。明尼苏达大学是一个非常大的大学,个个院校拥有接近 6000 名在校大学生。当时学校是用一个上了年纪的大型机系统来做学生的档案管理系统,这种系统已经过时了,所以需要做出改变。

这个系统不是 Y2K 类型的(LCTT 译注:保存年份时只用两位数,导致记录 2000 年时计算机会记录为 1900 年,详见 What Does Y2K Compliant Mean?),所以我们准备建立一个由仁科软件公司来交付的新的学生档案管理系统。这个新系统对明尼苏达大学来说有很多作用,不仅能够管理学生的档案,还能提供其他的一些功能。然而它却缺少了一项关键特性:你不能在你的浏览器上通过 web 来给你的班级进行注册。

按照今天的标准来看,这是一个重大的疏忽,但是在上世纪九十年代,互联网还是一个新生概念。亚马逊才建立不久,ebay 刚创业一年,google 呱呱坠地,Wikipedia 还没有影儿。所以 1998 年仁科公司没有支持 web 在线注册课程这个功能也就不足为奇了。但是明尼苏达大学作为 Gopher 网络的发源地,并且给之前的大型机系统开发了一套 web 功能接口,我们觉得 web 在线注册功能对于这个新的学生档案管理系统是至关重要的。

我们在这个 web 团队的任务就是去实现此管理系统的 web 在线注册功能。

幸运的是,我们并不是孤军奋战。我们联系了 IBM ,在第二年一起开始来搭建这个新的 web 在线注册系统。IBM 负责提供硬件和软件环境来运行这个 web 系统:3 个运行最新的 AIX 系统(类 UNIX 操作系统)、IBM Java 和 IBM WebSphere 平台的 SP 电脑节点,并用一个 IBM 的负载均衡器来实现 3 个节点的负载分流。

 title=

在经过一年多的开发和测试后,我们的系统终于上线了!但不幸的是失败却接踵而至。

负载过大

在开发过程中,我们无法准确地模拟测试真实场景下许多学生同时登录的场景。原因不是没有测试环境,明尼苏达大学有定制的 web 负载测试软件包,而且 IBM 有自己的工具做补充,但是这个 web 系统在当时对我们来说实在是太陌生了,我们没有意识到这些测试工具是不能满足要求的。

通过数月的测试,我们将此 web 系统的预期负载量调整到 240 个并发用户。但不幸的是,我们实际的使用量却是预期的两倍左右,在第一天系统上线时,超过 400 名学生马上同时登录进系统,由于负载远远超出预期值,3 台 web 服务器直接宕机了。由于持续的高负载,服务器一直崩溃,只能不断地重启。一台刚重启完,另一台又宕机重启了,这种场景居然持续了一个月。

由于不能有效地通过 web 注册,学生只能通过原来的方法来注册:来到登记员的办公室,拿着笔注册,然后再出门。当地报纸也幸灾乐祸地嘲讽道:"电脑软件的失败强迫学生只能面对面地注册!"

 title=

面对失败这个事实,我们尽自己全力在下一个开发周期中来提高软件性能,在之后 6 个月的时间里,我们疯狂地想去增强这套系统的负载能力。尽管增加了更多的代码,调整了多次配置,还是不能支持更多的用户。尽力了,然而面对的还是失败。

就如所料的,在下一个迭代周期后,迎接我们的还是失败。服务器由于负载问题一次又一次地宕机。这一次新闻标题已经变成了:“web 注册系统就是垃圾”。

在开始下一个为期 6 个月的迭代前,我们已经绝望了。没有人知道服务器不停宕机的原因,我们已经预期这个问题现在是无解的。我们是要采取一些措施来搞定这个问题,但是怎么做呢?以下是我们讨论得出的方法:

是否需要切换新的平台?

IBM 当时引入了 Linux,给它的 Java 和 WebSphere 平台做了二次开发。所有产品都获得了红帽公司的 RHEL 认证,并且有几个产品已经在我们的桌面系统上运行了。我们意识到了现在在 Linux 上已经有了完整的生态系统来运行我们的 web 管理系统,但是它能表现的比 AIX 更好吗?

在搭建好一个测试服务器并进行基本的负载测试后,我们惊奇的发现一台 Linux 服务器能够轻松地支持之前 3 台 AIX 服务器所不能支持的负载量,在相同的 web 代码、IBM Java 和 WebSphere 平台下,单台 Linux 服务器能够支持超过 200 个用户。

我们将这个消息告诉了登记员和 CIO,他们同意将 web 注册系统切换到 Linux 平台上。虽然这是我们第一次在明尼苏达大学跑 Linux,但是失败已成习惯,反而无所畏惧了。AIX 只会失败,Linux 却是我们唯一的希望。

我们马上基于 Linux 来进行开发。另一个组的同事也提供了几台 Intel 服务器来给我们使用,我们给服务器装上红帽系统和相关的 IBM 组件,然后在新系统上进行了持续性的负载测试,令人欣喜的是,Linux 服务器没有出现任何问题。

经过两个月高强度的开发测试,我们的新系统终于上线了,而且是巨大的成功!在巨大的负载下,web 注册系统在 Linux 的表现都堪称完美。同时在线峰值甚至超过了 600 名用户。Linux 拯救了明尼苏达大学的 web 注册系统~

成功的经验

当我回首这个项目时,我发现你可以用以下几个点来向你的团队介绍 Linux:

1、 解决问题, 不要自欺欺人

当我们提议在企业中使用 Linux 时,并不是因为我们认为 Linux 很酷才使用它。当然,我们是 Linux 的爱好者并且已经在自己的环境中运行过它,但是我们在公司是为了解决问题的。能用 Linux 是因为我们的登记员和出资人同意 Linux 是解决问题的一个方法,而不仅仅是因为 Linux很酷我们想用它。

2、 尽可能小的去做改变

我们的成功是建立在 IBM 已经基于 Linux 做出了它的 Java 和 WebSphere 产品的基础上的。这能让我们在将 web 系统从 AIX 切换到 Linux 上不用做过多的修改适配。两者比起来只有硬件和操作系统改变了,其他系统相关的组件都保持了一致,这些都是保证平台切换成功的基石。

 title=

 title=

3、 诚实对待风险和回报

我们的问题很明显:web 注册系统在前两个迭代周期中都失败了,而且很可能再次失败。当我们将自己的想法(AIX 切换到 Linux)告诉出资方后,我们对其中的风险和回报是心知肚明的。如果我们什么都不做,就只有失败,如果我们尝试切换到 Linux 平台,我们可能会成功,而且从最初的测试结果分析,成功的概率是高于失败的。

而且就算在 Linux 平台下项目还是失败了,我们也可以迅速地切换回 AIX 服务器。有了这些细致的分析和措施,终于使登记员能够安心让我们试试 Linux。

4、 言简意赅地交流

在项目平台切换的过程中,我们做了一个整体计划。我们在一张白纸上明确地写下了我们计划做什么,为什么要这么做。这种方式的成功关键就在于计划的简短性。领导们不喜欢像看小说一样来看技术性的主意,他们不想纠缠在技术细节中。所以我们有意地在执行层面上进行计划安排,在框架层面上进行描述。

当我们在进行平台切换时,我们会定期的告诉出资人当前进展。当新系统成功完成后,我们每天都会提交更新,报告已经有多少学生成功通过此系统完成注册和遇到的问题。

尽管这个项目已经过去了接近 20 年,但是其中的经验教训在今天仍然适用。尽管 Linux 在其中起了举足轻重的作用,但是最重要的还是我们成功地将所有人的目标引导到解决共同的问题上。我认为这种经验也可以运用到很多你所面对的事情当中。


作者简介:

Jim Hall -我是 FreeDOS 项目的发起者和协调人,我也在 GNOME 理事会中担任董事。工作上我是明尼苏达州拉姆西县的首席信息官,空闲时间里我为开源软件的可用性做出相关的贡献,并通过 Outreachy(为女性提供帮助的一项GNOME外展服务)来指导可用性测试。

via: https://opensource.com/article/17/7/how-introduced-organization-linux

作者:Jim Hall 译者:吴霄/toyijiu 校对:wxy

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

Nylas Mail An Amazing Free Email Client For Linux

有一个经常被提及的问题是 Ubuntu 是否还应该提供默认的电子邮件客户端。就个人而言,我已经很长时间没有使用 Thunderbird 了。我相信这不是一个第一次被问到的问题,但我相信这是一个把它解决掉的很好机会。这是因为日常用户倾向于使用基于网络的客户端,例如 Gmail 或 Outlook 来满足其邮件需求。而对于 Linux 上的经验丰富的用户而言,还有很多可供选择的选项。Geary、Empathy、Evolution 和 Thunderbird 本身已经为很多用户提供了很好的服务,但是我发现了值得一试的东西:它被称为 Nylas Mail。

它以前被称为 Nylas N1,Nylas Mail 于今年初在 1 月份推出,同时还发布了一个免费版本;Nylas Mail Basic 以前是一个付费版本。此外,在 1 月份,客户端仅适用于 Mac,但现在可用于 Linux 和 Windows 用户。

(在我写此文时,它还处于活跃开发状态,但是现在已经停止开发了。)

为什么使用 Nylas?

​很多人因为种种原因选择了 Nylas Mail。让我们来看看一些常见的原因。

简单 - Nylas Mail 客户端管理优雅简单。用 electron 构建,应用非常漂亮,易于使用。其设计还确保了在 Nylas 中设置电子邮件非常简单直接。

Nylas mail an awesome email client for linux

兼容性 - Nylas Mail 与所有电子邮件提供商兼容。它与 Gmail、Yahoo、Exchange 和 IMAP 帐户兼容,因此你可以在任何地方收到邮件。

nylas compatible with gmail facebook imap

功能​强大​ - Nylas 拥有大量功能。它有一个全屏模式、离线支持、多布局格式、多帐户、统一的收件箱、提醒、打盹、签名和稍后发送功能。其中一些功能随 Nylas Mail Basic 一起提供。

nylas email client powerful features

​混合后端​ - 以前,Nylas 将邮件的一个副本同步到使用 Nylas 云的服务器中,这对许多人来说就像 mehn 一样。幸运的是,在最新版本中,Nylas 采用了一个混合后端,可直接连接到 Gmail 或 Outlook 等电子邮件提供商。云同步虽然仍然可用,但仅在使用高级订阅工具(如 打盹 snoozing 和跟踪)时使用。缺点是它是一而二的。如果想要一些专业功能,你需要云同步;你不想用云同步,那你错过了这些功能。

nylas email hybrid backend

​开源和免费版​ - Nylas 作为开源项目。这意味着你可以自己编写代码并自行构建。你甚至可以设置自己的服务器以回避问题。

nylas open source and free email client

跨平台 - Nylas 是在 Linux、Windows 和 Mac OS X 上提供的跨平台应用程序。因此,无论你喜欢哪种桌面操作系统,你都可以放心,因为 Nylas 都已经覆盖了。而且用法是相同的。

还要做些什么?

到目前为止,Nylas 邮件客户端很好,但有一些抱怨。首先是在 2016 年推出的付费选项。引入免费版本有点像是这个解决这个问题的方案,但事实上,一些功能只能以每月 $9 的价格使用让包括我在内的大部分人不快。此外,没有多少人喜欢将邮件的副本保存在某台服务器上。当然,你可以设置自己的服务器,但有些麻烦。最后,对于在后台运行的应用程序来说,它需要相当多的内存。我希望这不是因为它主要是用 electron 写的,我相信随着它的更新和改进,它会变得更好。

总结

​Nylas Mail 在特性和功能方面对我来说非常棒,我相信你一定要用一下。作为一个电子邮件客户端,它是非常有效的,我真的很喜欢 Nylas Mail,并且一定会一直使用它。也许你应该也会。向开发人员的所做的工作致谢。你有其他的程序想让我们看下么?在下面的评论中指出,并分享你的想法和意见。


via: http://www.linuxandubuntu.com/home/nylas-mail-an-amazing-free-email-client-for-linux

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

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

Libral 为系统资源和服务提供了一个统一的管理 API ,其可以作为脚本管理任务和构建配置管理系统的坚实基础。

 title=

作为继承了 Unix 的传统的 Linux 操作系统,其并没有一个综合的系统管理 API 接口,相反,管理操作是通过多种特定用途的工具和 API 来实现的,其每一个都有自己约定和特有的风格。这就使得编写一个哪怕是简单的系统管理任务的脚本也很困难、很脆弱。

举个例子来说,改变 “app” 用户的登录 shell 要运行 usermod -s /sbin/nologin app。这个命令通常没有问题,只是当系统上没有 “app” 用户时就不行了。为了解决这个例外错误,具有创新精神的脚本编写者也许要这样写:

grep -q app /etc/passwd \
  && usermod -s /sbin/nologin app \
  || useradd ... -s /sbin/nologin app

这样,当 “app” 用户存在于系统中时,会执行更改登录 shell 的操作;而当此用户不存在时,就会创建此用户。不幸的是,这种编写系统管理任务脚本的方式是不适合的:对于每一种资源来说都会有一套不同的工具,而且每个都有其不同的使用惯例;不一致性和经常出现的不完备的错误报告将会使错误的处理变得困难;再者也会因为工具自身的特性引发的故障而导致执行失败。

实际上,以上所举的例子也是不正确的:grep 并不是用来查找 “app” 用户的,它只能在文件 /etc/passwd 的一些行中简单的查找是否有字符串 “app”,在大多数情况下它或许可以工作,但是也可能会在最关键的时刻出错。

很显然,那些执行简单任务的脚本管理工具,很难成为大型管理系统的基础。认识到这一点,现有的配置管理系统,比如 Puppet、Chef 及 Ansible,围绕基本的操作系统资源的管理竭尽全力的建立其内部 API 就是明智之举。这些资源抽象是内部 API,其与所需的相应工具密切相关。但这不仅导致大量的重复性工作,也为尝试一个新创新的管理工具设置了强大的障碍。

在创建虚拟机或者容器镜像这一领域,这种障碍就变得非常明显:比如在创建镜像的过程中,就要么需要回答关于它们的简单问题,要么需要对其进行简单的更改才行。但是工具全都需要特别处理,那些所遇到的问题和更改需要用脚本逐个处理。因此,镜像构建要么依靠特定的脚本,要么需要使用(和安装)一个相当强大的配置管理系统。

Libral 将为管理工具和任务提供一个可靠的保证,通过对系统资源提供一个公用的管理 API,并使其可以通过命令行工具 ralsh 使用,它允许用户按照相同的方法查询和修改系统资源,并有可预见的错误报告。对以上的举例来说,可以通过命令 ralsh -aq user app 检查 “app” 用户是否存在;通过 ralsh -aq package foo 检查 “foo” 软件包是否已经安装;一般情况下,可以通过命令 ralsh -aq TYPE NAME 检查 ”NAME“ 是否是 ”TYPE“ 类型的资源。类似的,要创建或更改已存在的用户,可以运行:

ralsh user app home=/srv/app shell=/sbin/nologin

以及,要在文件 /etc/hosts 创建和修改条目,可以运行命令:

ralsh hostmyhost.example.com ip=10.0.0.1 \
  host_aliases=myhost,apphost

以这种方式运行,“ralsh” 的使用者事实上完全隔离在那两个命令内部的不同运行工作机制之外:第一个命令需要适当的调用命令 useradd 或者 usermod,而第二个需要在 /etc/hosts 文件中进行编辑。而对于该用户来说,他们似乎都采取同样的模型:“确保资源处于我所需要的状态。”

怎样获取和使用 Libral 呢?

Libral 可以在这个 git 仓库找到并下载。其核心是由 C++ 编写的,构建它的说明可以在该仓库中找到,不过只是在你想要为 Libral 的 C++ 核心做贡献的时候才需要看它。Libral 的网站上包含了一个 预构建的 tarball,可以用在任何使用 “glibc 2.12” 或者更高版本的 Linux 机器上。可以使用该 “tarball” 的内容进一步探究 ralsh ,以及开发新的提供者(provider),它使得 Libral 具备了管理新类型资源的能力。

在下载解压 tarball 后,在目录 ral/bin 下能找到 ralsh 命令。运行不需要任何参数的 ralsh 命令就会列举出来 Libral 的所有资源类型。利用 --help 选项可以打印输出关于 ralsh 的更多说明。

与配置管理系统的关系

知名的配置管理系统,如 Puppet、Chef 及 Ansible,解决了一些 Libral 所解决的同样的问题。将 Libral 与它们的区分开的主要是它们所做工作和 Libral 不同。配置管理系统被构建来处理跨大量节点管理各种事物的多样性和复杂性。而另一方面 Libral 旨在提供一个定义明确的低级别的系统管理 API ,独立于任何特定的工具,可用于各种各样的编程语言。

通过消除大型配置管理系统中包含的应用程序逻辑,Libral 从前面介绍里提及的简单的脚本任务,到作为构建复杂的管理应用的构建块,它在如何使用方面是非常灵活的。专注与这些基础层面也使其保持很小,目前不到 2.5 MB,这对于资源严重受限的环境,如容器和小型设备来说是一个重要考虑因素。

Libral API

在过去的十年里,Libral API 是在实现配置管理系统的经验下指导设计的,虽然它并没有直接绑定到它们其中任何一个应用上,但它考虑到了这些问题,并规避了它们的缺点。

在 API 设计中四个重要的原则:

  • 期望的状态
  • 双向性
  • 轻量级抽象
  • 易于扩展

基于期望状态的管理 API,举个例子来说,用户表示当操作执行后希望系统看起来是什么状态,而不是怎样进入这个状态,这一点什么争议。双向性使得使用(读、写)相同的 API 成为可能,更重要的是,相同的资源可以抽象成读取现有状态和强制修改成这种状态。轻量级抽象行为确保能容易的学习 API 并能快速的使用;过去在管理 API 上的尝试过度加重了学习建模框架的使用者的负担,其中一个重要的因素是他们的接受力缺乏。

最后,它必须易于扩展 Libral 的管理功能,这样用户可以教给 Libral 如何管理新类型的资源。这很重要,因为人们也许要管理的资源可能很多(而且 Libral 需要在适当时间进行管理),再者,因为即使是完全成熟的 Libral 也总是存在达不到用户自定义的管理需求。

目前与 Libral API 进行交互的主要方式是通过 ralsh 命令行工具。它也提供了底层的 C++ API ,不过其仍处在不断的演变当中,主要的还是为简单的脚本任务做准备。该项目也提供了为 CRuby 提供语言绑定,其它语言也在陆续跟进。

未来 Libral 还将提供一个提供远程 API 的守护进程,它可以做为管理系统的基础服务,而不需要在管理节点上安装额外的代理。这一点,加上对 Libral 管理功能的定制能力,可以严格控制系统的哪些方面可以管理,哪些方面要避免干扰。

举个例子来说,一个仅限于管理用户和服务的 Libral 配置会避免干扰到在节点上安装的包。当前任何现有的配置管理系统都不可能控制以这种方式管理的内容;尤其是,需要对受控节点进行任意的 SSH 访问也会将该系统暴露不必要的意外和恶意干扰。

Libral API 的基础是由两个非常简单的操作构成的:“get” 用来检索当前资源的状态,“set” 用来设置当前资源的状态。理想化地实现是这样的,通过以下步骤:

provider.get(names) -> List[resource]
provider.set(List[update]) -> List[change]

“provider” 是要知道怎样管理的一种资源的对象,就像用户、服务、软件包等等,Libral API 提供了一种查找特定资源的 管理器 provider 的方法。

“get” 操作能够接收资源名称列表(如用户名),然后产生一个资源列表,其本质来说是利用散列的方式列出每种资源的属性。这个列表必须包含所提供名称的资源,但是可以包含更多内容,因此一个简单的 “get” 的实现可以忽略名称并列出所有它知道的资源。

“set” 操作被用来设置所要求的状态,并接受一个更新列表。每个更新可以包含 “update.is”,其表示当前状态的资源,“update.should” 表示被资源所期望的状态。调用 “set” 方法将会让更新列表中所提到的资源成为 “update.should” 中指示的状态,并列出对每个资源所做的更改。

ralsh 下,利用 ralsh user root 能够重新获得 “root” 用户的当前状态;默认情况下,这个命令会产生一个用户可读的输出,就像 Puppet 中一样,但是 ralsh 支持 --json 选项,可以生成脚本可以使用的 JSON 输出。用户可读的输出是:

# ralsh user root
user::useradd { 'root':
  ensure  => 'present',
  comment => 'root',
  gid     => '0',
  groups  => ['root'],
  home    => '/root',
  shell   => '/bin/bash',
  uid     => '0',
}

类似的,用户也可以用下面的形式修改:

# ralsh user root comment='The superuser'
user::useradd { 'root':
  ensure  => 'present',
  comment => 'The superuser',
  gid     => '0',
  groups  => ['root'],
  home    => '/root',
  shell   => '/bin/bash',
  uid     => '0',
}
comment(root->The superuser)

ralsh 的输出列出了 “root” 用户的新状态和被改变的 comment 属性,以及修改了什么内容(在这种情形下单指 comment 属性)。下一秒运行相同的命令将产生同样的输出,但是不会提示修改,因为没有需要修改的内容。

编写 管理器 provider

ralsh 编写新的管理器(provider) 是很容易的,也花费不了多少工夫,但是这一步骤是至关重要的。正因为如此,ralsh 提供了大量的调用约定,使得可以根据管理器所能提供的能力而调整其实现复杂性成为可能。管理器可以使用遵循特定调用约定的外部脚本,也可以以 C++ 实现并内置到 Libral 里面。到目前为止,有三种调用约定:

  • simple 调用约定是编写 shell 脚本用为管理器。
  • JSON 调用约定意味着可以利用 Ruby 或者 Python 脚本语言编写管理器。
  • 内部 C++ API[8 可以被用来实现原生的管理器。

强烈建议使用 “simple” 或者 “JSON” 调用约定开始开发管理器。GitHub 上的 simple.prov 文件包含了一个简单的 shell 管理器框架,应该很容易的替换为你自己的管理器。python.prov 文件包含了利用 Python编写的 JSON 管理器框架。

利用高级脚本语言编写的管理器存在一个问题是,对于这些语言,在当前运行 Libral 的系统上需要包含所有的支持库在内运行环境。在某些情况下,这不是一个障碍;举例子来说,基于 “yum” 的包管理的管理器需要 Python 被安装在当前的系统上,因为 “yum” 就是用 Python 开发的。

然而在很多时候,无法预期一种 Bourne shell(Bash)之外的设计语言能够安装到所有的管理系统上。通常,管理器的编写者需要一个更加强大的脚本编译环境是实际需要。然而事与愿违,绑定一个完整的 Ruby 或 Python 作为解释器来运行将会增加 Libral 的大小超出了实际使用环境对资源的限制。另一方面,通常选择 Lua 或者 JavaScript 作为可嵌入的脚本编辑语言是不适应这种环境的,因为大多数的管理器的编写者不熟悉他们,通常情况下需要做大量的工作才能满足系统管理的实际需求。

Libral 绑定了一个 mruby 版本,一个小的、嵌入在 Ruby 的版本,提供给管理器的编写者一个稳定的基础以及功能强大的可实现的程序设计语言。mruby 是 Ruby 语言的一个完整实现,尽管其减少了大量的标准库支持。与 Libral 绑定的 mruby 包含了 Ruby 用于脚本编辑管理任务的大多数的重要标准库,其将基于管理器编写者的需求随着时间的推移得到加强。Libral 的 mruby 绑定了 API 适配器使编写管理器更适合 JSON 约定,比如它包含了简单的工具(如编译修改结构体文件的 Augeas)来解决解析和输出 JSON 的约定。mruby.prov 文件包含了利用 mruby 编写的 JSON 管理器框架实例。

下一步工作

Libral 最关键的是下一步要使其增加广泛的可用性——从 预编译的 tarball 对于开发管理器的起步和深入是一个不错的方法,但是 Libral 也需要打包到主流的发行版上,并在上面可以找到。同样的,Libral 强大的功能取决于它附带的管理器的集合,及需要被扩展覆盖一组核心管理功能。Libral 的网站上包含了一个 todo 列表列出了管理器的最迫切需求。

现在有多种方法能够提高不同用途的 Libral 的可用性:从编写更多程序语言的绑定,举例来说,Python 或者 Go;到使 ralsh 更容易在 shell 脚本中使用,除了现有的可读输出和 JSON 输出之外,可以很轻松的在 shell 脚本中格式化输出。在大规模的管理中使用 Libral 也能够在增加上面讨论过的远程 API 而得到改良,Libral 利用像 SSH 这样的传输工具实现更好的支持批量安装要求为各种架构提供预编译的 tarball,并且脚本基于所发现的目标系统架构选择正确的包。

Libral 和它的 API、它的性能能够不断地进化发展;一个有趣的可能性是为 API 增加提醒能力,这样做可以向系统报告资源在超出它的范围发生的变化。Libral 面临的挑战是保持小型化、轻量级和良好定义的工具,来对抗不断增加的用例和管理性能——我希望每一个读者都能成为这个挑战之旅的一部分。

如果这让你很好奇,我很想听听你的想法,可以用拉取请求的方式,也可以是增强请求方式,亦或者报告你对 ralsh 体验经验。

(题图:Internet Archive Book Images,修改:Opensource.com. CC BY-SA 4.0)


作者简介:

David Lutterkort - 戴维是一个 Puppet 的软件工程师,他曾经参与的项目有 Direct Puppet 和最好的供给工具 Razor。他是 Puppet 最早的编著者之一,也是配置编辑工具 Augeas 的主要作者。


via: https://opensource.com/article/17/5/intro-libral-systems-management-library-linux

作者:David Lutterkort 译者:stevenzdg988 校对:wxy

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

安卓 6.0 棉花糖

2015 年 10 月,谷歌给世界带来了安卓 6.0 棉花糖。配合这个版本的发布,谷歌委托生产了两部新的 Nexus 设备:华为 Nexus 6P 和 LG Nexus 5X。除了常规的性能升级,新手机还带有一套关键硬件:为棉花糖的新指纹 API 准备的指纹识别器。棉花糖还引入了一个疯狂的全新搜索特性,被称作“Google Now on Tap”,用户控制的应用权限,一个全新的数据备份系统,以及许多其它的改良。

新谷歌应用

棉花糖是谷歌大标志重新设计后的第一个安卓版本。系统也随之升级,主要是一个新的谷歌应用,给搜索小部件,搜索页面以及应用图标添加了一个多彩的标志。

谷歌将应用抽屉从页面导航的横向布局还原回了单页竖直滚动表的形式。早期版本的安卓用的都是竖直滚动表的形式,直到谷歌在蜂巢中改成了横向页面系统。滚动单页面让人更容易从很多应用中找到目标。一项“快速滚动”的特性同样好用,它可以让你拖动滚动条来激活字母索引。新的应用抽屉布局也用到了小部件抽屉上。考虑到旧系统中小部件轻松就超过了 15 页,这是个大改进。

棉花糖应用抽屉顶部的“建议应用”栏也让查找应用变得更快。该栏显示的内容一直在变化,试图在你需要的时候为你提供你需要的应用。它使用了算法来统计应用使用,经常一起打开的应用以及每天的打开次数。

Google Now on Tap——一个没有完美实现的特性

棉花糖的头等新特性之一就是“Google Now on Tap”。有了 Now on Tap,你可以在安卓的任意界面长按 home 键,安卓会将整个屏幕发送给谷歌进行处理。谷歌会试着分析页面上的内容,并从屏幕底部弹出显示一个特殊的搜索结果列表。

Now on Tap 产生的结果不是通常的 10 个蓝色链接——即便那必定有一个通往谷歌搜索的链接。Now on Tap 还可以深度连接到其它使用了谷歌的应用索引功能的应用。他们的想法是你可以在 Youtube 音乐视频那里唤出 Now on tap,然后获得一个到谷歌 Play 或亚马逊“购买”页面的链接。在演员新闻文章处唤出 Now on Tap 可以链接到 IMDb 应用中该演员的页面上。

谷歌没有让这成为私有特性,而是给安卓创建了一个全新的“Assistant API(助理 API)”。用户可以挑选一个“助理应用”,它可以在长按 home 键的时候获取很多信息。助理应用会获取所有由当前应用加载的数据——不仅是直接从屏幕获取到的——连同所有这些图片还有任何开发者想要包含的元数据。这个 API 驱动了谷歌 Now on Tap,如果愿意的话,它还允许第三方打造 Now on Tap 的竞争对手。

谷歌在棉花糖的发布会上炒作了 Now on Tap,但实际上,这项特性不是很实用。谷歌搜索的价值在于你可以问它准确的问题——你输入你想要的内容,它搜索整个互联网寻找答案或网页。Now on Tap 让事情变得无限困难,因为它甚至不知道你要问的是什么。你带着特定意图打开了 Now on Tap,但你发送给谷歌的查询是很不准确的“屏幕上的所有内容”。谷歌需要猜测你查询的内容然后试着基于它给出有用的结果或操作。

在这背后,谷歌可能在疯狂处理整个页面文字和图片来强行获得你想要的结果。但往往 Now on Tap 给出的结果像是页面每个合适的名词的搜索结果列表。从结果列表中筛选多个查询就像是陷入必应的“搜索结果过载”广告里那样的情形。查询目标的缺失让 Now on Tap 感觉像是让谷歌给你读心,而它永远做不到。谷歌最终给文本选中菜单打了补丁,添加了一个“助理”按钮,给 Now on Tap 提供一些它极度需要的搜索目标。

不得不说 Now on Tap 是个失败的产物。Now on Tap 的快捷方式——长按 home 键——基本上让它成为了一个隐藏,难以发现的特性,很容易就被遗忘了。我们推测这个特性的使用率非常低。即便用户发现了 Now on Tap,它经常没法读取你的想法,在几次尝试之后,大多数用户可能会选择放弃。

随着 2016 年 Google Pixels 的发布,谷歌似乎承认了失败。它把 Now on Tap 改名成了“屏幕搜索”并把它降级成了谷歌助手的支援。谷歌助理——谷歌的新语音命令系统——接管了 Now on Tap 的 home 键手势并将它关联到了语音系统激活后的二次手势。谷歌似乎还从 Now on Tap 差劲的可发现性中学到了教训。谷歌给助理给 home 键添加了一组带动画的彩点,帮助用户发现并记住这个特性。

权限

安卓 6.0 终于引入了应用权限系统,让用户可以细粒度地控制应用可以访问的数据。

应用在安装的时候不再给你一份长长的权限列表。在棉花糖中,应用安装根本不询问任何权限。当应用需要一个权限的时候——比如访问你的位置、摄像头、麦克风,或联系人列表的时候——它们会在需要用到的时候询问。在你使用应用的时候,如果需要新权限时会弹出一个“允许或拒绝”的对话框。一些应用的设置流程这么处理:在启动的时候询问获取一些关键权限,其它的等到需要用到的时候再弹出提示。这样更好地与用户沟通了需要权限是为了做什么——应用需要摄像头权限,因为你刚刚点击了摄像头按钮。

除了及时的“允许或拒绝”对话框,棉花糖还添加了一个权限设置界面。这个复选框大列表让数据敏感用户可以浏览应用拥有的权限。他们不仅可以通过应用来查询,也可以通过权限来查询。举个例子,你可以查看所有拥有访问麦克风权限的应用。

谷歌试验应用权限已经有一段时间了,这些设置界面基本就是隐藏的“App Ops”系统的重生,它是在安卓 4.3 中不小心引入并很快被移除的权限管理系统。

尽管谷歌在之前版本就试验过了,棉花糖的权限系统最大的不同是它代表了一个向权限系统的有序过渡。安卓 4.3 的 App Ops 从没有计划暴露给用户,所以开发者不了解它。在 4.3 中拒绝一个应用需要的一个权限经常导致奇怪的错误信息或一个彻底的崩溃。棉花糖的系统对开发者是缺省的——新的权限系统只适用于针对棉花糖 SDK 开发的应用,谷歌将它作为开发者已经为权限处理做好准备的信号。权限系统还允许在一项功能由于权限被拒绝无法正常工作时与用户进行沟通。应用会被告知它们的权限请求被拒绝,它们可以指导用户在需要该功能的时候去打开该权限访问。

指纹 API

在棉花糖出现之前,少数厂商推出了他们自己的指纹解决方案以作为对苹果的 Touch ID 的回应。但在棉花糖中,谷歌终于带来了生态级别的指纹识别 API。新系统包含了指纹注册界面,指纹验证锁屏以及允许应用将内容保护在一个指纹扫描或锁屏验证之后的 API。

Play 商店是最先支持该 API 的应用之一。你可以使用你的指纹来购买应用,而不用输入你的密码。Nexus 5X 和 6P 是最先支持指纹 API 的手机,手机背面带有指纹读取硬件。

指纹 API 推出不久后时间,是罕见的安卓生态合作例子之一。所有带有指纹识别的手机都使用谷歌的 API,并且大多数银行和购物应用都很好地支持了它。


Ron Amadeo / Ron 是 Ars Technica 的评论编缉,专注于安卓系统和谷歌产品。他总是在追寻新鲜事物,还喜欢拆解事物看看它们到底是怎么运作的。@RonAmadeo


via: http://arstechnica.com/gadgets/2016/10/building-android-a-40000-word-history-of-googles-mobile-os/31/

作者:RON AMADEO 译者:alim0x 校对:wxy

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

一些能让你自定义 Bash 提示符的黑科技

 title=

当你在 Linux 环境下打开一个 Shell 终端时,会看到命令行中出现了类似下面的一个 Bash 提示符:

[user@$host ~]$

你知道命令行提示符其实是可以自己设置添加许多非常有用的信息的吗?在这篇文章中我就会教你如何自定义自己的 Bash 命令行提示符,想看的话就接着看吧~

如何设置 Bash 提示符

Bash 提示符是通过环境变量 PS1 提示符字符串 1 Prompt String 1 ) 来设置的,它用于交互式 shell 提示符。当然如果你需要更多的输入才能完成一个 Bash 命令时,PS2 环境变量就是用来设置多行提示符的:

[dneary@dhcp-41-137 ~]$ export PS1="[Linux Rulez]$ "
[Linux Rulez] export PS2="... "
[Linux Rulez] if true; then
... echo "Success!"
... fi
Success!

在哪里设置 PS1 的值?

PS1 就是一个普通的环境变量,系统默认值设置在 /etc/bashrc 中,在我的系统中,默认提示符通过以下命令来设置的:

[ "$PS1" = "\\s-\\v\\\$ " ] && PS1="[\u@\h \W]\\$ "

它判断 PS1 是否是系统的默认值 \s-\v$ ,如果是的话则将值设置为 [\u@\h \W]\$。(LCTT 译注:注意命令中用 \ 做了转义。)

但如果你想要自定义提示符,不应该修改 /etc/bashrc ,而是应该在你的主目录下将自定义命令加到 .bashrc 文件中。

上面提到的 \u\h\W\s\v 是什么意思?

man bash 中的 PROMPTING 章节中,你能够找到所有 PS1PS2 相关的特殊字符的描述,以下是一些比较常用的:

  • \u:用户名
  • \h:短主机名
  • \W:当前你所在的目录的名称(basename),~ 表示你的主目录
  • \s:Shell 名字(bash 或者 sh,取决于你的 Shell 的名字是什么)
  • \v:Shell 的版本号

还有哪些特殊的字符串可以用在提示符当中

除了上面这些,还有很多有用的字符串可以用在提示符当中:

  • \d:将日期扩展成 “Tue Jun 27” 这种格式
  • \D{fmt}:允许自定义日期格式——可通过 man strftime 来获得更多信息
  • \D{%c}:获得本地化的日期和时间
  • \n:换行(参考下面的多行提示符)
  • \w:显示当前工作目录的完整路径
  • \H:当前工作机器的完整主机名

除了以上这些,你还可以在 Bash 的 man 页面的 PROMPTING 部分找到更多的特殊字符和它的用处。

多行提示符

如果你的提示符过长(比如说你想包括 \H\w 或完整的日期时间时 ),想将提示符切成两行,可以使用 \n 将提示符切断成两行显示,比如下面的多行的例子会在第一行显示日期、时间和当前工作目录,第二行显示用户名和主机名:

PS1="\D{%c} \w\n[\u@\H]$ "

还能再好玩点吗?

人们偶尔也想将提示符变成彩色的。虽然我觉得彩色提示符让人分心、易怒,但是也许你很喜欢。如果我们想将日期变成红色的,目录变成青蓝色,用户名搞一个黄色背景,你可以这样做:

PS1="\[\e[31m\]\D{%c}\[\e[0m\]
     \[\e[36m\]\w\[\e[0m\]\n[\[\e[1;43m\]\u\[\e[0m\]@\H]$ "
  • \[..\] :表示一些非打印字符
  • \e[.. :转义字符,后面的跟着的特定的转义字符串在终端中表示颜色或者其他意思
  • 31m :表示红色字体(41m 表示是红色背景)
  • 36m :表示是青蓝色字体
  • 1;43m :表示黄色字体(1;33m 表示黄色字体)
  • \[\e[0m]\] :它在最后将颜色恢复成系统终端默认颜色

你可以在 Bash prompt HOWTO 这里找到更多的颜色代码,甚至可以让字符反相和闪烁!我不知道为什么地球人会有这种想法,但是你可以这么干!

所以你最喜欢的自定义提示符是什么样子的呢?有没有让你抓狂的自定义提示符呢?请在评论里告诉我吧~

(照片来源:ajmexico. 修改自 Jason Baker. CC BY-SA 2.0.)


作者简介:

Dave Neary - Dave Neary 是红帽开源和标准化团队成员,帮助开源项目对红帽的成功至关重要。自从在 1999 年为 GIMP 提交了第一个补丁以来,他一直带着各种不同的帽子,在开源的世界徜徉。


via: https://opensource.com/article/17/7/bash-prompt-tips-and-tricks

作者:Dave Neary 译者:吴霄/toyijiu 校对:wxy

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