标签 配置 下的文章

在你使用 Java 编写软件时实现持久化配置。

 title=

当你编写一个应用时,你通常都会希望用户能够定制化他们和应用交互的方式,以及应用与系统进行交互的方式。这种方式通常被称为 “ 偏好 preference ” 或者 “ 设置 setting ”,它们被保存在一个 “偏好文件” 或者 “配置文件” 中,有时也直接简称为 “ 配置 config ”。配置文件可以有很多种格式,包括 INI、JSON、YAML 和 XML。每一种编程语言解析这些格式的方式都不同。本文主要讨论,当你在使用 Java 编程语言 来编写软件时,实现持久化配置的方式。

选择一个格式

编写配置文件是一件相当复杂的事情。我曾经试过把配置项使用逗号分隔保存在一个文本文件里,也试过把配置项保存在非常详细的 YAML 和 XML 中。对于配置文件来说,最重要是要有一致性和规律性,它们使你可以简单快速地编写代码,从配置文件中解析出数据;同时,当用户决定要做出修改时,很方便地保存和更新配置。

目前有 几种流行的配置文件格式。对于大多数常见的配置文件格式,Java 都有对应的 library 。在本文中,我将使用 XML 格式。对于一些项目,你可能会选择使用 XML,因为它的一个突出特点是能够为包含的数据提供大量相关的元数据,而在另外一些项目中,你可能会因为 XML 的冗长而不选择它。在 Java 中使用 XML 是非常容易的,因为它默认包含了许多健壮的 XML 库。

XML 基础

讨论 XML 可是一个大话题。我有一本关于 XML 的书,它有超过 700 页的内容。幸运的是,使用 XML 并不需要非常了解它的诸多特性。就像 HTML 一样,XML 是一个带有开始和结束标记的分层标记语言,每一个标记(标签)内可以包含零个或更多数据。下面是一个 XML 的简单示例片段:

<xml>
  <node>
    <element>Penguin</element>
  </node>
</xml>

在这个 自我描述的 self-descriptive 例子中,XML 解析器使用了以下几个概念:

  • 文档 Document <xml> 标签标志着一个 文档 的开始,</xml> 标签标志着这个文档的结束。
  • 节点 Node <node> 标签代表了一个 节点
  • 元素 Element <element>Penguin</element> 中,从开头的 < 到最后的 > 表示了一个 元素
  • 内容 Content : 在 <element> 元素里,字符串 Penguin 就是 内容

不管你信不信,只要了解了以上几个概念,你就可以开始编写、解析 XML 文件了。

创建一个示例配置文件

要学习如何解析 XML 文件,只需要一个极简的示例文件就够了。假设现在有一个配置文件,里面保存的是关于一个图形界面窗口的属性:

<xml>
  <window>
    <theme>Dark</theme>
    <fullscreen>0</fullscreen>
    <icons>Tango</icons>
</window>
</xml>

创建一个名为 ~/.config/DemoXMLParser 的目录:

$ mkdir ~/.config/DemoXMLParser

在 Linux 中,~/.config 目录是存放配置文件的默认位置,这是在 自由桌面工作组 的规范中定义的。如果你正在使用一个不遵守 自由桌面工作组 Freedesktop 标准的操作系统,你也仍然可以使用这个目录,只不过你需要自己创建这些目录了。

复制 XML 的示例配置文件,粘贴并保存为 ~/.config/DemoXMLParser/myconfig.xml 文件。

使用 Java 解析 XML

如果你是 Java 的初学者,你可以先阅读我写的 面向 Java 入门开发者的 7 个小技巧。一旦你对 Java 比较熟悉了,打开你最喜爱的集成开发工具(IDE),创建一个新工程。我会把我的新工程命名为 myConfigParser

刚开始先不要太关注依赖导入和异常捕获这些,你可以先尝试用 javaxjava.io 包里的标准 Java 扩展来实例化一个解析器。如果你使用了 IDE,它会提示你导入合适的依赖。如果没有,你也可以在文章稍后的部分找到完整的代码,里面就有完整的依赖列表。

Path configPath = Paths.get(System.getProperty("user.home"), ".config", "DemoXMLParser");
File configFile = new File(configPath.toString(), "myconfig.xml");

DocumentBuilderFactory factory = DocumentBuilderFactory.newInstance();

DocumentBuilder builder = null;
builder = factory.newDocumentBuilder();

Document doc = null;
doc = builder.parse(configFile);
doc.getDocumentElement().normalize();

这段示例代码使用了 java.nio.Paths 类来找到用户的主目录,然后在拼接上默认配置文件的路径。接着,它用 java.io.File 类来把配置文件定义为一个 File 对象。

紧接着,它使用了 javax.xml.parsers.DocumentBuilderjavax.xml.parsers.DocumentBuilderFactory 这两个类来创建一个内部的文档构造器,这样 Java 程序就可以导入并解析 XML 数据了。

最后,Java 创建一个叫 doc 的文档对象,并且把 configFile 文件加载到这个对象里。通过使用 org.w3c.dom 包,它读取并规范化了 XML 数据。

基本上就是这样啦。理论上来讲,你已经完成了数据解析的工作。可是,如果你不能够访问数据的话,数据解析也没有多少用处嘛。所以,就让我们再来写一些查询,从你的配置中读取重要的属性值吧。

使用 Java 访问 XML 的值

从你已经读取的 XML 文档中获取数据,其实就是要先找到一个特定的节点,然后遍历它包含的所有元素。通常我们会使用多个循环语句来遍历节点中的元素,但是为了保持代码可读性,我会尽可能少地使用循环语句:

NodeList nodes = doc.getElementsByTagName("window");

for (int i = 0; i < nodes.getLength(); i++) {
 Node mynode = nodes.item(i);
 System.out.println("Property = " + mynode.getNodeName());
       
 if (mynode.getNodeType() == Node.ELEMENT_NODE) {
   Element myelement = (Element) mynode;
             
   System.out.println("Theme = " + myelement.getElementsByTagName("theme").item(0).getTextContent());
   System.out.println("Fullscreen = " + myelement.getElementsByTagName("fullscreen").item(0).getTextContent());
   System.out.println("Icon set = " + myelement.getElementsByTagName("icons").item(0).getTextContent());
 }
}

这段示例代码使用了 org.w3c.dom.NodeList 类,创建了一个名为 nodesNodeList 对象。这个对象包含了所有名字匹配字符串 window 的子节点,实际上这样的节点只有一个,因为本文的示例配置文件中只配置了一个。

紧接着,它使用了一个 for 循环来遍历 nodes 列表。具体过程是:根据节点出现的顺序逐个取出,然后交给一个 if-then 子句处理。这个 if-then 子句创建了一个名为 myelementElement 对象,其中包含了当前节点下的所有元素。你可以使用例如 getChildNodesgetElementById 方法来查询这些元素,项目中还 记录了 其他查询方法。

在这个示例中,每个元素就是配置的键。而配置的值储存在元素的内容中,你可以使用 .getTextContent 方法来提取出配置的值。

在你的 IDE 中运行代码(或者运行编译后的二进制文件):

$ java ./DemoXMLParser.java
Property = window
Theme = Dark
Fullscreen = 0
Icon set = Tango

下面是完整的代码示例:

package myConfigParser;

import java.io.File;
import java.io.IOException;
import java.nio.file.Path;
import java.nio.file.Paths;

import javax.xml.parsers.DocumentBuilder;
import javax.xml.parsers.DocumentBuilderFactory;
import javax.xml.parsers.ParserConfigurationException;

import org.w3c.dom.Document;
import org.w3c.dom.Element;
import org.w3c.dom.NamedNodeMap;
import org.w3c.dom.Node;
import org.w3c.dom.NodeList;
import org.xml.sax.SAXException;

public class ConfigParser {

        public static void main(String[] args) {
                Path configPath = Paths.get(System.getProperty("user.home"), ".config", "DemoXMLParser");
                File configFile = new File(configPath.toString(), "myconfig.xml");
                DocumentBuilderFactory factory =
                DocumentBuilderFactory.newInstance();
                DocumentBuilder builder = null;
               
                try {
                        builder = factory.newDocumentBuilder();
                } catch (ParserConfigurationException e) {
                        e.printStackTrace();
                }
       
                Document doc = null;
       
                try {
                        doc = builder.parse(configFile);
                } catch (SAXException e) {
                        e.printStackTrace();
                } catch (IOException e) {
                        e.printStackTrace();
                }
        doc.getDocumentElement().normalize();
       
        NodeList nodes = doc.getElementsByTagName("window");
        for (int i = 0; i < nodes.getLength(); i++) {
           Node mynode = nodes.item(i);
           System.out.println("Property = " + mynode.getNodeName());
           
           if (mynode.getNodeType() == Node.ELEMENT_NODE) {
               Element myelement = (Element) mynode;

               System.out.println("Theme = " + myelement.getElementsByTagName("theme").item(0).getTextContent());
               System.out.println("Fullscreen = " + myelement.getElementsByTagName("fullscreen").item(0).getTextContent());
               System.out.println("Icon set = " + myelement.getElementsByTagName("icons").item(0).getTextContent());
           } // close if
        } // close for
    } // close method
} //close class

使用 Java 更新 XML

用户时不时地会改变某个偏好项,这时候 org.w3c.dom 库就可以帮助你更新某个 XML 元素的内容。你只需要选择这个 XML 元素,就像你读取它时那样。不过,此时你不再使用 .getTextContent 方法,而是使用 .setTextContent 方法。

updatePref = myelement.getElementsByTagName("fullscreen").item(0);
updatePref.setTextContent("1");

System.out.println("Updated fullscreen to " + myelement.getElementsByTagName("fullscreen").item(0).getTextContent());  

这么做会改变应用程序内存中的 XML 文档,但是还没有把数据写回到磁盘上。配合使用 javaxw3c 库,你就可以把读取到的 XML 内容写回到配置文件中。

TransformerFactory transformerFactory = TransformerFactory.newInstance();

Transformer xtransform;
xtransform = transformerFactory.newTransformer();

DOMSource mydom = new DOMSource(doc);
StreamResult streamResult = new StreamResult(configFile);

xtransform.transform(mydom, streamResult);

这么做会没有警告地写入转换后的数据,并覆盖掉之前的配置。

下面是完整的代码,包括更新 XML 的操作:

package myConfigParser;

import java.io.File;
import java.io.IOException;
import java.nio.file.Path;
import java.nio.file.Paths;

import javax.xml.parsers.DocumentBuilder;
import javax.xml.parsers.DocumentBuilderFactory;
import javax.xml.parsers.ParserConfigurationException;
import javax.xml.transform.Transformer;
import javax.xml.transform.TransformerException;
import javax.xml.transform.TransformerFactory;
import javax.xml.transform.dom.DOMSource;
import javax.xml.transform.stream.StreamResult;

import org.w3c.dom.Document;
import org.w3c.dom.Element;
import org.w3c.dom.Node;
import org.w3c.dom.NodeList;
import org.xml.sax.SAXException;

public class ConfigParser {

        public static void main(String[] args) {
                Path configPath = Paths.get(System.getProperty("user.home"), ".config", "DemoXMLParser");
                File configFile = new File(configPath.toString(), "myconfig.xml");
                DocumentBuilderFactory factory =
                DocumentBuilderFactory.newInstance();
                DocumentBuilder builder = null;
               
                try {
                        builder = factory.newDocumentBuilder();
                } catch (ParserConfigurationException e) {
                        // TODO Auto-generated catch block
                        e.printStackTrace();
                }
       
                Document doc = null;
       
                try {
                        doc = builder.parse(configFile);
                } catch (SAXException e) {
                        // TODO Auto-generated catch block
                        e.printStackTrace();
                } catch (IOException e) {
                        // TODO Auto-generated catch block
                        e.printStackTrace();
                }
        doc.getDocumentElement().normalize();
        Node updatePref = null;
//        NodeList nodes = doc.getChildNodes();
        NodeList nodes = doc.getElementsByTagName("window");
        for (int i = 0; i < nodes.getLength(); i++) {
           Node mynode = nodes.item(i);
           System.out.println("Property = " + mynode.getNodeName());
           
           if (mynode.getNodeType() == Node.ELEMENT_NODE) {
               Element myelement = (Element) mynode;

               System.out.println("Theme = " + myelement.getElementsByTagName("theme").item(0).getTextContent());
               System.out.println("Fullscreen = " + myelement.getElementsByTagName("fullscreen").item(0).getTextContent());
               System.out.println("Icon set = " + myelement.getElementsByTagName("icons").item(0).getTextContent());

               updatePref = myelement.getElementsByTagName("fullscreen").item(0);
               updatePref.setTextContent("2");
               System.out.println("Updated fullscreen to " + myelement.getElementsByTagName("fullscreen").item(0).getTextContent());          
           } // close if
           
        }// close for

        // write DOM back to the file
        TransformerFactory transformerFactory = TransformerFactory.newInstance();
        Transformer xtransform;

        DOMSource mydom = new DOMSource(doc);
        StreamResult streamResult = new StreamResult(configFile);

        try {
                xtransform = transformerFactory.newTransformer();
                xtransform.transform(mydom, streamResult);
        } catch (TransformerException e) {
                e.printStackTrace();
        }
                       
    } // close method
} //close class

如何保证配置不出问题

编写配置文件看上去是一个还挺简单的任务。一开始,你可能会用一个简单的文本格式,因为你的应用程序只要寥寥几个配置项而已。但是,随着你引入了更多的配置项,读取或者写入错误的数据可能会给你的应用程序带来意料之外的错误。一种帮助你保持配置过程安全、不出错的方法,就是使用类似 XML 的规范格式,然后依靠你用的编程语言的内置功能来处理这些复杂的事情。

这也正是我喜欢使用 Java 和 XML 的原因。每当我试图读取错误的配置值时,Java 就会提醒我。通常,这是由于我在代码中试图获取的节点,并不存在于我期望的 XML 路径中。XML 这种高度结构化的格式帮助了代码保持可靠性,这对用户和开发者来说都是有好处的。


via: https://opensource.com/article/21/7/parsing-config-files-java

作者:Seth Kenlon 选题:lujun9972 译者:lkxed 校对:wxy

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

将配置文件与代码分离,使任何人都可以改变他们的配置,而不需要任何特殊的编程技巧。

 title=

将程序配置与代码分离是很重要的。它使非程序员能够改变配置而不需要修改程序的代码。如果是编译好的二进制可执行文件,这对非程序员来说是不可能的,因为它不仅需要访问源文件(我们在开源程序中会这样),而且还需要程序员的技能组合。很少有人有这种能力,而且大多数人都不想学习它。

对于像 Bash 这样的 shell 语言,由于 shell 脚本没有被编译成二进制格式,所以从定义上讲,源码是可以访问的。尽管有这种开放性,但对于非程序员来说,在 shell 脚本中钻研和修改它们并不是一个特别好的主意。即使是经验丰富的开发人员和系统管理员,也会意外地做出一些导致错误或更糟的改变。

因此,将配置项放在容易维护的文本文件中,提供了分离,并允许非程序员编辑配置,而不会有对代码进行意外修改的危险。许多开发者对用编译语言编写的程序都是这样做的,因为他们并不期望用户是开发者。由于许多相同的原因,对解释型 shell 语言这样做也是有意义的。

通常的方式

和其他许多语言一样, 你可以为 Bash 程序编写代码,来读取并解析 ASCII 文本的配置文件、读取变量名称并在程序代码执行时设置值。例如,一个配置文件可能看起来像这样:

var1=LinuxGeek46
var2=Opensource.com

程序将读取文件,解析每一行,并将值设置到每个变量中。

源引

Bash 使用一种更简单的方法来解析和设置变量, 叫做 源引 sourcing 。从一个可执行的 shell 程序中获取一个外部文件是一种简单的方法,可以将该文件的内容完整地引入 shell 程序中。在某种意义上,这很像编译语言的 include 语句,在运行时包括库文件。这样的文件可以包括任何类型的 Bash 代码,包括变量赋值。

(LCTT 译注:对于使用 source. 命令引入另外一个文件的行为,我们首倡翻译为“源引”。)

像往常一样,演示比解释更容易。

首先,创建一个 ~/bin 目录(如果它还不存在的话),并将其作为当前工作目录(PWD)。Linux 文件系统分层标准~/bin 定义为用户存储可执行文件的适当位置。

在这个目录下创建一个新文件。将其命名为 main,并使其可执行:

[dboth@david bin]$ touch main
[dboth@david bin]$ chmod +x main
[dboth@david bin]$

在这个可执行文件中添加以下内容:

#!/bin/bash
Name="LinuxGeek"
echo $Name

并执行这个 Bash 程序:

[dboth@david bin]$ ./main
LinuxGeek
[dboth@david bin]$

创建一个新的文件并命名为 ~/bin/data。这个文件不需要是可执行的。在其中添加以下信息:

# Sourced code and variables
echo "This is the sourced code from the data file."
FirstName="David"
LastName="Both"

main 程序中增加三行,看起来像这样:

#!/bin/bash
Name="LinuxGeek"
echo $Name
source ~/bin/data
echo "First name: $FirstName"
echo "LastName: $LastName"

重新运行该程序:

[dboth@david bin]$ ./main
LinuxGeek
This is the sourced code from the data file.
First name: David
LastName: Both
[dboth@david bin]$

关于源引还有一件非常酷的事情要知道。你可以使用一个单点(.)作为 source 命令的快捷方式。改变 main 文件,用 . 代替 source

#!/bin/bash
Name="LinuxGeek"
echo $Name
. ~/bin/data
echo "First name: $FirstName"
echo "LastName: $LastName"

并再次运行该程序。其结果应该与之前的运行完全相同。

运行 Bash

每一台使用 Bash 的 Linux 主机(几乎所有主机都是,因为 Bash 是所有发行版的默认 shell),都包括一些优秀的、内置的源引示例。

每当 Bash shell 运行时,它的环境必须被配置成可以使用的样子。有五个主要文件和一个目录用于配置 Bash 环境。它们和它们的主要功能如下:

  • /etc/profile: 系统级的环境和启动程序
  • /etc/bashrc: 系统级的函数和别名
  • /etc/profile.d/: 包含系统级的脚本的目录,用于配置各种命令行工具,如 vimmc 以及系统管理员创建的任何自定义配置脚本
  • ~/.bash_profile: 用户特定的环境和启动程序
  • ~/.bashrc: 用户特定的别名和函数
  • ~/.bash_logout: 用户特定的命令,在用户注销时执行

试着通过这些文件追踪执行顺序,确定它在非登录 Bash 初始化和登录 Bash 初始化中使用的顺序。我在我的 Linux 培训系列《使用和管理 Linux:从零到系统管理员》的第一卷第 17 章中这样做过。

给你一个提示。这一切都从 ~/.bashrc 脚本开始。

总结

这篇文章探讨了在 Bash 程序中引用代码和变量的方法。这种从配置文件中解析变量的方法是快速、简单和灵活的。它提供了一种将 Bash 代码与变量赋值分开的方法,以使非程序员能够设置这些变量的值。


via: https://opensource.com/article/21/6/bash-config

作者:David Both 选题:lujun9972 译者:geekpi 校对:wxy

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

这是我怎样设置 Git 来管理我的家目录的方法。

 title=

我有好几台电脑。一台笔记本电脑用于工作,一台工作站放在家里,一台树莓派(或四台),一台 Pocket CHIP,一台 运行各种不同的 Linux 的 Chromebook,等等。我曾经在每台计算机上或多或少地按照相同的步骤设置我的用户环境,也经常告诉自己让每台计算机都略有不同。例如,我在工作中比在家里更经常使用 Bash 别名,并且我在家里使用的辅助脚本可能对工作没有用。

这些年来,我对各种设备的期望开始相融,我会忘记我在家用计算机上建立的功能没有移植到我的工作计算机上,诸如此类。我需要一种标准化我的自定义工具包的方法。使我感到意外的答案是 Git。

Git 是版本跟踪软件。它以既可以用在非常大的开源项目也可以用在极小的开源项目而闻名,甚至最大的专有软件公司也在用它。但是它是为源代码设计的,而不是用在一个装满音乐和视频文件、游戏、照片等的家目录。我听说过有人使用 Git 管理其家目录,但我认为这是程序员们进行的一项附带实验,而不是像我这样的现实生活中的用户。

用 Git 管理我的家目录是一个不断发展的过程。随着时间的推移我一直在学习和适应。如果你决定使用 Git 管理家目录,则可能需要记住以下几点。

1、文本和二进制位置

 title=

当由 Git 管理时,除了配置文件之外,你的家目录对于所有内容而言都是“无人之地”。这意味着当你打开主目录时,除了可预见的目录的列表之外,你什么都看不到。不应有任何杂乱无章的照片或 LibreOffice 文档,也不应有 “我就在这里放一分钟” 的临时文件。

原因很简单:使用 Git 管理家目录时,家目录中所有 提交的内容都会变成噪音。每次执行 git status 时,你都必须翻过去之前 Git 未跟踪的任何文件,因此将这些文件保存在子目录(添加到 .gitignore 文件中)至关重要。

许多 Linux 发行版提供了一组默认目录:

  • Documents
  • Downloads
  • Music
  • Photos
  • Templates
  • Videos

如果需要,你可以创建更多。例如,我把创作的音乐(Music)和购买来聆听的音乐(Albums)区分开来。同样,我的电影(Cinema)目录包含了其他人的电影,而视频(Videos)目录包含我需要编辑的视频文件。换句话说,我的默认目录结构比大多数 Linux 发行版提供的默认设置更详细,但是我认为这样做有好处。如果没有适合你的目录结构,你更会将其存放在家目录中,因为没有更好的存放位置,因此请提前考虑并规划好适合你的工作目录。你以后总是可以添加更多,但是最好先开始擅长的。

2、、设置最优的 .gitignore

清理家目录后,你可以像往常一样将其作为 Git 存储库实例化:

$ cd
$ git init .

你的 Git 仓库中还没有任何内容,你的家目录中的所有内容均未被跟踪。你的第一项工作是筛选未跟踪文件的列表,并确定要保持未跟踪状态的文件。要查看未跟踪的文件:

$ git status
  .AndroidStudio3.2/
  .FBReader/
  .ICEauthority
  .Xauthority
  .Xdefaults
  .android/
  .arduino15/
  .ash_history
[...]

根据你使用家目录的时间长短,此列表可能很长。简单的是你在上一步中确定的目录。通过将它们添加到名为 .gitignore 的隐藏文件中,你告诉 Git 停止将它们列为未跟踪文件,并且永远不对其进行跟踪:

$ \ls -lg | grep ^d | awk '{print $8}' >> ~/.gitignore

完成后,浏览 git status 所示的其余未跟踪文件,并确定是否有其他文件需要排除。这个过程帮助我发现了几个陈旧的配置文件和目录,这些文件和目录最终被我全部丢弃了,而且还发现了一些特定于一台计算机的文件和目录。我在这里非常严格,因为许多配置文件在自动生成时会表现得更好。例如,我从不提交我的 KDE 配置文件,因为许多文件包含了诸如最新文档之类的信息以及其他机器上不存在的其他元素。

我会跟踪我的个性化配置文件、脚本和实用程序、配置文件和 Bash 配置,以及速查表和我经常引用的其他文本片段。如果有软件主要负责维护的文件,则将其忽略。当对一个文件不确定时,我将其忽略。你以后总是可以取消忽略它(通过从 .gitignore 文件中删除它)。

3、了解你的数据

我使用的是 KDE,因此我使用开源扫描程序 Filelight 来了解我的数据概况。Filelight 为你提供了一个图表,可让你查看每个目录的大小。你可以浏览每个目录以查看占用了空间的内容,然后回溯调查其他地方。这是一个令人着迷的系统视图,它使你可以以全新的方式看待你的文件。

 title=

使用 Filelight 或类似的实用程序查找不需要提交的意外数据缓存。例如,KDE 文件索引器(Baloo)生成了大量特定于其主机的数据,我绝对不希望将其传输到另一台计算机。

4、不要忽略你的 .gitignore 文件

在某些项目中,我告诉 Git 忽略我的 .gitignore 文件,因为有时我要忽略的内容特定于我的工作目录,并且我不认为同一项目中的其他开发人员需要我告诉他们 .gitignore 文件应该是什么样子。因为我的家目录仅供我使用,所以我 会忽略我的家目录的 .gitignore 文件。我将其与其他重要文件一起提交,因此它已在我的所有系统中被继承。当然,从家目录的角度来看,我所有的系统都是相同的:它们具有一组相同的默认文件夹和许多相同的隐藏配置文件。

5、不要担心二进制文件

我对我的系统进行了数周的严格测试,确信将二进制文件提交到 Git 绝对不是明智之举。我试过 GPG 加密的密码文件、试过 LibreOffice 文档、JPEG、PNG 等等。我甚至有一个脚本,可以在将 LibreOffice 文件添加到 Git 之前先解压缩,提取其中的 XML,以便仅提交 XML,然后重新构建 LibreOffice 文件,以便可以在 LibreOffice 中继续工作。我的理论是,提交 XML 会比使用 ZIP 文件(LibreOffice 文档实际上就是一个 ZIP 文件)会让 Git 存储库更小一些。

令我惊讶的是,我发现偶尔提交一些二进制文件并没有大幅增加我的 Git 存储库的大小。我使用 Git 已经很长时间了,我知道如果我要提交几千兆的二进制数据,我的存储库将会受到影响,但是偶尔提交几个二进制文件也不是不惜一切代价要避免的紧急情况。

有了这种信心,我将字体 OTF 和 TTF 文件添加到我的标准主存储库,以及 GDM 的 .face 文件以及其他偶尔小型二进制 Blob 文件。不要想太多,不要浪费时间去避免它。只需提交即可。

6、使用私有存储库

即使托管方提供了私人帐户,也不要将你的主目录提交到公共 Git 存储库。如果你像我一样,拥有 SSH 密钥、GPG 密钥链和 GPG 加密的文件,这些文件不应该出现在任何人的服务器上,而应该出现在我自己的服务器上。

我在树莓派上 运行本地 Git 服务器(这比你想象的要容易),因此我可以在家里时随时更新任何一台计算机。我是一名远程工作者,所以通常情况下就足够了,但是我也可以在旅行时通过 虚拟私人网络 访问我的计算机。

7、要记得推送

Git 的特点是,只有当你告诉它要推送改动时,它才会把改动推送到你的服务器上。如果你是 Git 的老用户,则此过程可能对你很自然。对于可能习惯于 Nextcloud 或 Syncthing 自动同步的新用户,这可能需要一些时间来适应。

Git 家目录

使用 Git 管理我的常用文件,不仅使我在不同设备上的生活更加便利。我知道我拥有所有配置和实用程序脚本的完整历史记录,这会鼓励我尝试新的想法,因为如果结果变得 很糟糕,则很容易回滚我的更改。Git 曾将我从在 .bashrc 文件中一个欠考虑的 umask 设置中解救出来、从深夜对包管理脚本的拙劣添加中解救出来、从当时看似很酷的 rxvt 配色方案的修改中解救出来,也许还有其他一些错误。在家目录中尝试 Git 吧,因为这些提交会让家目录融合在一起。


via: https://opensource.com/article/21/4/git-home

作者:Seth Kenlon 选题:lujun9972 译者:stevenzdg988 校对:wxy

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

Eric Raymond 写了一个工具,用来帮助那些无畏的“代码考古学家们”理解“古代”代码的结构。它叫做 ifdex,它的背后有一段故事,拿起你的 Fedora 和赶牛的鞭子,让我讲给你听……

在开发 NTPsec 早期, 我们就决定替换它的构建系统——它是如此的难于理解和修改——严重的拖慢了我们的开发进度。

古老的 autoconf 构建方式就像一个恐怖的爬行动物,而 NTP 更是一个极端的例子。不完善的宏技术定义了太多的配置符号,为了掌握这些接口,即使你查看了 config.h 也无济于事。尤其是当你要做一些大的修改时,这更是一个问题!

我们的一个伙计 Amar Takhar,是 waf 构建系统的专家,当他初步提出迁移的想法时得到了我的积极响应。几个月之后,他就做出了一些 waf 功能,虽然还未完成,但是已经至少可以生成用于实际测试的二进制了。

这里我说“未完成”的意思是,代码里面还有一些 waf 构建绝不会设置的 #define 。很多人绝对不会碰那些 autoconf 构建的东西,而另外一些人则不。那些从来用不到的配置开关迷失在大堆的系统头文件和编译器设置的 #define 条件中。

我这里说的不是几个或几十个,我最终统计出了有超过 670 个不同的 #define 用在 #if/#ifdef/#ifndef/#elif 条件中,而这些条件,如 John D. Bell 指出的那样,有 2430 个之多。我需要一些办法来检查它们并分成不同的类型:有的来自系统头文件、有的是配置开关,以及其它的……

所以我写了一个分析器,它可以对代码树解析每个编译时条件的符号,然后将它们分成单纯的列表或 GCC 类的文件/行错误信息,你可以用 Emacs 的编译模式逐个分析。

为了降低干扰,它掌握着一个条件符号的长列表(大概200个),这些可以忽略掉。比如像 \_\_GNUC\_\_ 符号是 GCC 预定义,而 O\_NONBLOCK 宏用于几种系统调用等等。

这些符号分成几组,你可以使用命令行选项分别忽略它们。所以,如果你希望忽略列表中所有标准的 POSIX 宏而想看到操作系统相关的任何东西,那就可以做到了。

另外一个重要的功能是你可以构造你自己的排除列表及注释。这样当我探索 NTP 编译条件的黑暗森林时,所构造的越来越大的排除列表就代表了我已经了解了的条件符号。最终(我希望)未知符号的报告将缩减为空,那么我就已经了解了所有配置开关的确切意义了。

目前为止,我已经搞定了它们之中的 300 个,还有 373 个。这是我用我的漂亮工具在一周内完成的主要工作。噢,从来没有人说过代码考古是如此的容易。

最后, ifdef 的主页是: http://www.catb.org/esr/ifdex/,代码库在这里:https://gitlab.com/esr/ifdex 。希望你用的上。

在我的上一篇文章中讲过如何做一个高可用系统:两个树莓派布署上 GlusterFS 集群文件系统,就变成一个容错文件服务器了。在这篇文章中我们会基于这个高可用系统构建另一个容错服务:建立一个简单的 Web 服务器集群。

可能你没有看过我的上一篇文章,那我就在这里简单回顾一下上次的内容。我有两个树莓派:Pi1 和 Pi2。Pi1 的 IP 地址为 192.168.0.121,Pi2 的 IP 地址为 192.168.0.122。我把它们组合成为 GlusterFS 集群系统,共享一个存储卷,卷名为 gv0。并且这个 gv0 被挂载在两个树莓派上,挂载点都是 /mnt/gluster1,这样一来,两个树莓派能同时访问到这个共享卷。最后我测试了下系统的容错性,将共享卷挂载到第三个物理机上,并执行共享卷上的一个简单的脚本:打印系统时间并输出到 gv0 上的一个文件内,然后轮着关掉两台树莓派,检测这个冗余系统是否还能用。

现在我手上有这个经过检验的存储系统,我要做的是把这个系统做成有容错功能的 Web 服务器集群。树莓派的 CPU 处理速度和内存资源都不是很高,好在对付一个静态 Web 服务器已经是绰绰有余了。我故意把这个例子做得非常简单,因为我觉得如果你能摆平这个简单的例子,你也能处理复杂一点的问题了。

安装 Nginx

虽然我也比较喜欢 Apache,但在资源有限的情况下,像 Nginx 这样功能全、性能高、资源占用少的 Web 服务器是比较理想的选择。在 Raspbian 的默认源中存在 Nginx,所以我只要登入一台树莓派并且输入以下命令:

$ sudo apt-get update
$ sudo apt-get install nginx

Nginx 安装好后,创建配置文件 /mnt/gluster1/cluster ,内容如下:

server {
  root /mnt/gluster1/www;
  index index.html index.htm;
  server_name twopir twopir.example.com;

  location / {
        try_files $uri $uri/ /index.html;
  }
}

注意:我这里把服务器命名为“twopir”,你可以根据自己爱好命名你的网站。另外我把 Web 服务器的根目录设置为 /mng/gluster1/www。这样,我就能把我的静态文件放在这个共享存储系统内,从而让两个树莓派主机都访问到它们。

现在删除默认的 Nginx 配置文件,将上面配好的配置文件作为 Nginx 的配置文件。在 Debian 系统中,Nginx 将它的配置文件像 Apache 一样放在 sites-available 和 site-enabled 两个目录下面。虚拟主机配置文档放在 sites-available 中,而 sites-enabled 目录包含了你想生效的配置文件的软链接。

$ cd /etc/nginx/sites-available
$ sudo ln -s /mnt/gluster1/cluster .
$ cd /etc/nginx/sites-enabled
$ sudo rm default
$ sudo ln -s /etc/nginx/sites-available/cluster .

现在我已经放好了配置文件,但系统还不存在上面所说的 Web 服务器根目录。下一步就是创建目录 /mnt/gluster1/www,并复制 index.html 文件进去。当然你也可以建立自己定义的 index.html 文件,但先复制一个已有的文件是个不错的选择:

$ sudo mkdir /mnt/gluster1/www
$ cp /usr/share/nginx/www/index.html /mnt/gluster1/www

重启 Nginx 服务:

$ sudo /etc/init.d/nginx restart

现在去 DNS 服务器为192.168.0.121的树莓派配置记录。你可以根据自己的情况配置你自己的域名和 IP 地址。现在在浏览器上输入 http://twopir/ 然后出现默认的 Nginx 首页。如果你查看下 /var/log/nginx/access.log 文件,你可以看到点击网页留下的记录。

Nginx 能正常工作后,配置第二个树莓派。因为我们已经将配置文件都放在 GlusterFS 共享目录下,我们现在要做的只是安装 Nginx,创建相关的软链接,重启 Nginx:

$ sudo apt-get update
$ sudo apt-get install nginx
$ cd /etc/nginx/sites-available
$ sudo ln -s /mnt/gluster1/cluster .
$ cd /etc/nginx/sites-enabled
$ sudo rm default
$ sudo ln -s /etc/nginx/sites-available/cluster .
$ sudo /etc/init.d/nginx restart

DNS 服务器上配置两条 A 记录

现在两个主机共享同一份文件,下一步是设置冗余系统的关键步骤。虽然你完全可以设置一个类似心跳的服务用于轮询检测哪个树莓派的 Web 服务是开启的,但这里有个更好的方法:给同一个主机名指定两个 DNS 的 A 记录,分别指向你的两个树莓派(译注:A 记录就是将域名转化为 IP 地址的记录,DNS 包含很多记录方式,如 A 记录、PTR 记录、MX 记录等等),这就是传说中的 DNS 负载均衡,DNS 访问主机时,如果主机名对应多个 IP 地址,DNS 会随机返回这些 IP 地址的顺序:

$ dig twopir.example.com A +short
192.168.0.121
192.168.0.122
$ dig twopir.example.com A +short
192.168.0.122
192.168.0.121

因为返回随机的顺序,用户可以均匀地往两台服务器发送请求,这个负载均衡的机制就是由 DNS 服务器的多条 A 记录来提供。相对于 DNS 的负载均衡技术,我更感兴趣的是 Web 浏览器如何处理请求失败的情况。当浏览器通过网页主机获得两条 A 记录,并且第一条记录所在的主机当机了,浏览器几乎立刻就切到另一条记录上。切换效率快到用户根本察觉不出来,这可比传统的心跳线轮循请求主机快多了。

所以在你正在使用的 DNS 服务器上添加你的树莓派集群的两个 IP 地址,然后试试 dig 命令,就像我在上面使用过的一样,你也应该能得到两个 IP 地址。

当你为同一个域名设置好了两个 A 记录,这套集群就可以提供容错服务了。打开两个终端并分别登录到两个树莓派,运行 tail -f /var/log/ngnix/access.log 命令,你可以监视 Web 服务器的访问情况。当你通过浏览器访问网页时,你可以看到在一台树莓派上产生了访问日志,而在另外一台的日志里什么也没有出现。现在你可以刷新几次页面,当你觉得你对能成功访问到 Web 服务器感到满意了,你可以重启响应你请求的那台树莓派,然后再刷新几次页面。也许浏览器上会出现一个短暂的不可访问信号,但会马上重定向到第二台树莓派上,你会看到一样的页面,并且你能通过终端访问日志了解具体情况。当第一台树莓派启动后,你在浏览器上根本不会察觉到。

(译注:如何处理DNS 轮询的多个IP地址,不同浏览器有不同的做法,目前译者收集到的资料显示,只有mozilla的netlib库支持自动重连下一个IP地址的做法。如果读者知道更多的浏览器对DNS轮询的处理策略,请评论给我们。其实,以译者的态度看,DNS轮询根本就不能算作是高可用性的方案,勉强可以算作是负载均衡方案,建议考虑更可靠的其它方案。)

随机重启一台树莓派,只要有一台在线,Web 服务器就能提供服务。这是一个非常简单的案例,你可以把你其它的静态文件放到 /mnt/gluster1/www 上,为你提供真正有价值的服务,现在好好享受你的低成本容错集群 Web 服务器吧。


via: http://www.linuxjournal.com/content/two-pi-r-2-web-servers

译者:bazz2 校对:wxy

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

你好! 准备好读另一篇很酷的Linux内核文章了么?

接下来,在这个任务中,我们可以启用/禁用"Fusion MPT logging facility"。MPT代表"Message Passing Technology"(消息传递技术)。Fusion驱动是由LSI Logic公司开发。MPT一种进程间使用的特定消息策略。这个技术是同步的意味着进程将会等待所需的消息。

在这之后,如果计算机处理拥有火线端口就应该启用"FireWire driver stack"。如果没有,那么就没有必要去启动一个不会使用到的火线驱动。火线很像USB。不过在协议、速度、物理形状和端口布局上不同。通常上,苹果设备使用火线和USB。一些PC有火线端口,但是不像USB口那样普及。

一些火线控制器使用OHCI-1394规范(OHCI-1394 controllers)。如果是这样,启用这个驱动。

为了使用火线存储设备,启用下一个驱动(Storage devices (SBP-2 protocol))。这个驱动提供了火线存储单元与火线总线通信的协议(the card with the attached FireWire ports)。一些火线扫描仪同样需要这个驱动。

IPv4可以用在火线端口(IP networking over 1394)。IEEE 1394或者简单的"1394"就是火线。使用IPv4在火线多播有局限。

"Nosy"是"FireWire PCILynx"卡上的流量监控(Nosy - a FireWire traffic sniffer for PCILynx cards)。

下一步,可以支持I2O设备(I2O device support)。"Intelligent Input/Output (I2O)"(智能输入/输出)总线使用硬件和操作系统层的驱动。硬件驱动(hardware drivers (HDM))并不特定与任何操作系统而OS驱动(OS drivers (OSM))必须在目标操作系统上使用。OSM可以与任何HDM通信。I2O卡/总线有一个IOP- 输入/输出处理器(Input/Output Processor)。由于主CPU处理更少的数据,所以加速了系统。

只在缺乏SUN I2O控制器的系统上启用"Enable LCT notification"。I2C SUN固件不支持LCT通知。 如果目标是RAID,Adaptec I2O控制器需要下一个驱动(Enable Adaptec extensions)。

64位的直接内存访问可以在Adaptec I2O控制器上启用(Enable 64-bit DMA)。

如果允许,可以配置I2O设备(I2O Configuration support)。这个特性主要用在RAID设定中。

可以为I2O启用支持老的输入/输出控制(Enable ioctls (OBSOLETE))。

可以启用I2O总线适配器的OSM软件(I2O Bus Adapter OSM)。这组OSM被用来寻找新的在其他适配器末端的I2O设备。

下面,可以启用I2O块设备上的OSM(I2O Block OSM)。I2O硬件上的RAID控制器需要这个OSM。

下面的OSM用于I2O控制器上的SCSI或者光纤通道设备。

如果启用了(I2O /proc support),可以通过/proc读取I2O设备的信息。

在启用/禁用了I2O特性,我们可以继续其他的内核特性。下面,我们看到"Macintosh device drivers"。这只对苹果设备有用。PC的Linux内核不应该有任何这些驱动启用。然而,正如许多说法都有例外一样。一些PC用户可能会使用苹果鼠标、键盘和/或者一些其他的苹果设备。再说一次,最好彻底地理解需求和正在开发的内核。

下一步,我们有一个用于网络的驱动(Network device support)。X11和其他的Linux软件不依赖于这个驱动,所以如果内核不会连接到另一台计算机、因特网、内联网或者网络,那么这个特性可以安全地禁用。

下面的驱动就像上面,但是特定于核心驱动(Network core driver support)。

这个驱动支持Etherchannel(Bonding driver support)。"bonding"是两条或者更多的以太网通道的融合。这也成为中继。

使用这个驱动(Dummy net driver support),可以在Linux中设置一个虚拟网络。虚拟网络(dummy network)就像网络中的/dev/null。任何发送给虚拟网络的数据都会永久消失,因为它会发往/dev/null。IP地址没有设置。用户可以定义他们的网络相当于/dev/null。

下一步,可以支持和EQL(EQL (serial line load balancing) support)。这允许两台计算机使用SLIP或者PPP协议在两条串行连接上通信。

光纤通道是一种用于连接存储设备到计算机的快速串行协议(Fibre Channel driver support)。

TMII收发器需要这个驱动(Generic Media Independent Interface device support)。MII是一种用于最高速度为100Mbit/s以太网的接口。以太网线缆用于连接到PHYceiver,这是一种以太网收发器。

为了通过虚拟接口组织许多以太网设备,需要"Ethernet team driver support"。

"MAC-VLAN support"允许用户在特定的MAC地址和某个接口上映射数据包。

TAP字符设备可以由MAC-VLAN接口生成(MAC-VLAN based tap driver)。TAP设备从内核中获取数据包,这样它们就可以被送往其他地方。

下一个特性允许虚拟vxvlan接口在3层网络上创建2层网络(Virtual eXtensible Local Area Network (VXLAN))。这通常用于隧道虚拟网络。

内核发送给网络的消息可任意通过这个特性记录下来(Network console logging support)。除非记录网络信息对你很重要时才启用它。禁用这个特性会增强性能。

这个特性允许不同参数被改变(Dynamic reconfiguration of logging targets)。这些参数包括端口号、MAC地址、IP地址和其他一些设定。

如果用户空间程序希望使用TAP设备,那么启用这个特性可以允许这样的活动(Universal TUN/TAP device driver support)。

这个驱动用于本地以太网隧道(Virtual ethernet pair device)。

"Virtio network driver"用于QEMU、Xen、KVM和其他虚拟机。

下一步,可以启用"ARCnet support"。ARCnet是一种类似令牌环本地局域网络(Local-Area-Network (LAN)协议。ARCnet代表"Attached Resource Computer Network"(附加资源计算器网络)。

现在,我们进入到"ATM drivers"。ATM代表"Asynchronous Transfer Mode"(异步传输模式)。ATM用于电信。

Marevell以太网交换机芯片需要这个驱动(Marvell 88E6060 ethernet switch chip support)。同样,这类交换机的芯片同样需要依赖模型(Marvell 88E6085/6095/6095F/6131 ethernet switch chip support)和(Marvell 88E6123/6161/6165 ethernet switch chip support)。

现在,我们可以学习关于"Ethernet driver support"。

首先我们可以启用/禁用"3Com devices"。接下来允许内核开发者选择支持哪些3Com设备。

下一组选项是对于不同的"Adaptec devices"和接下来的"Alteon devices"。

这些只是特定设备/供应商驱动。通常地,这些驱动被作为模块加入。

在设置了这两组选项后,接下来还有"AMD devices"和"Atheros devices"。

注意:请记住内核会运行在哪类硬件上。对于大量不同的设备,或许最好把它们作为模块加入

这里有不同特定供货商的设备驱动-"Cadence devices"、"Broadcom devices"、"Brocade devices"、"Chelsio devices"、"Cisco devices"、"Digital Equipment devices"。一些其他的特定设备/供应商驱动遵循它们。

接下来的驱动并不是特定设备/供应商的 "SLIP (serial line) support"。这个驱动支持SLIP和CSLIP。SLIP(Serial Line Internet Protocol)是一种用于调制解调器和串口的因特网驱动。PPP现在用来代替SLIP。CSLIP是压缩的SLIP。

下面,"CSLIP compressed headers"可以启用用来压缩TCP/IP头。CSLIP快于SLIP,但是想要启用CSLIP,传输和接收的计算机都必须理解CSLIP。

当在恶劣的模拟线路上使用SLIP时,最好启用"Keepalive and linefill",这会帮助保持连接。

对于质量差的网络或者7bit网络中运行IP而言,最好启用"Six bit SLIP encapsulation"。

现在我们可以进入流行的USB系统,但是这些是用于网络的USB驱动。

第一个启用/禁用的USB网络设备是"USB CATC NetMate-based Ethernet device support"。这是用于10Mbps的USB以太网EL1210A芯片设备。USB设备将会扮演和成为一个以太网设备即使硬件是USB。

接下来,除了设备是KLSI KL5KUSB101B芯片组(USB KLSI KL5USB101-based ethernet device support),其他与上面的驱动一样。

Pegasus USB是USB转以太网的适配器/转换器(USB Pegasus/Pegasus-II based ethernet device support)。

接下来是另外一个USB转以太网驱动(USB RTL8150 based ethernet device support)。

下一篇文章中,我们将继续配置USB网络系统。


via: http://www.linux.org/threads/the-linux-kernel-configuring-the-kernel-part-14.4765/

译者:geekpi 校对:wxy

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