分类 软件开发 下的文章

根据 JavaScript 框架的优点和主要特点对许多 JavaScript 框架进行细分。

 title=

大约十年前,JavaScript 社区开始见证一场 JavaScript 框架的激战。在本文中,我将介绍其中最著名的一些框架。值得注意的是,这些都是开源的 JavaScript 项目,这意味着你可以在 开源许可证 下自由地使用它们,甚至为它们的源代码和社区做出贡献。

不过,在开始之前,了解一些 JavaScript 开发者谈论框架时常用的术语,将对后续的内容大有裨益。

术语释义
文档对象模型(DOM)网站的树形结构表示,每一个节点都是代表网页一部分的对象。万维网联盟(W3C),是万维网的国际标准组织,维护着 DOM 的定义。
虚拟 DOM用户界面(UI)以“虚拟”或“理想”的方式保存在内存中,并通过 ReactDOM 等一些库与“真实” DOM 同步。要进一步探索,请阅读 ReactJS 的虚拟 DOM 和内部文档。
数据绑定一个编程概念,为访问网站上的数据提供一致的接口。Web 元素与 DOM 维护的元素的 属性 property 特性 attribute 相关联(LCTT 译注:根据 MDN 的解释,Javascript 的 属性 property 是对象的特征,通常描述与数据结构相关的特征; 特性 attribute 是指元素所有属性节点的一个实时集合)。例如,当需要在网页表单中填写密码时,数据绑定机制可以用密码验证逻辑检验,确保密码格式有效。

我们已经清楚了常用的术语,下面我们来探索一下开源的 JavaScript 框架有哪些。

框架简介许可证发布日期
ReactJS目前最流行的 JavaScript 框架,由 Facebook 创建MIT 许可证2013-5-24
AngularGoogle 创建的流行的 JavaScript 框架MIT 许可证2010-1-5
VueJS快速增长的 JavaScript 框架MIT 许可证2013-7-28
MeteorJS超乎于 JavaScript 框架的强大框架MIT 许可证2012-1-18
KnockoutJS开源的 MVVM( 模型-视图-视图模型 Model-View-ViewModel ) 框架MIT 许可证2010-7-5
EmberJS另一个开源的 MVVM 框架MIT 许可证2011-12-8
BackboneJS带有 RESTful JSON 和 模型-视图-主持人 Model-View-Presenter 模式的 JavaScript 框架MIT 许可证2010-9-30
Svelte不使用虚拟 DOM 的 JavaScript 开源框架MIT 许可证2016-11-20
AureliaJS现代 JavaScript 模块的集合MIT 许可证2018-2-14

为了说明情况,下面是每个框架的 NPM 包下载量的公开数据,感谢 npm trends

 title=

ReactJS

 title=

ReactJS 是由 Facebook 研发的,它虽然在 Angular 之后发布,但明显是当今 JavaScript 框架的领导者。React 引入了一个虚拟 DOM 的概念,这是一个抽象副本,开发者能在框架内仅使用他们想要的 ReactJS 功能,而无需重写整个项目。此外,React 项目活跃的开源社区无疑成为增长背后的主力军。下面是一些 React 的主要优势:

  • 合理的学习曲线 —— React 开发者可以轻松地创建 React 组件,而不需要重写整个 JavaScript 的代码。在 ReactJS 的 首页 查看它的优点以及它如何使编程更容易。
  • 高度优化的性能 —— React 的虚拟 DOM 的实现和其他功能提升了应用程序的渲染性能。请查看 ReactJS 的关于如何对其性能进行基准测试,并对应用性能进行衡量的相关 描述
  • 优秀的支持工具 —— ReduxThunkReselect 是构建良好、可调式代码的最佳工具。
  • 单向数据绑定 —— 模型使用 Reach 流,只从所有者流向子模块,这使得在代码中追踪因果关系更加简单。请在 ReactJS 的 数据绑定页 阅读更多相关资料。

谁在使用 ReactJS?Facebook 自从发明它,就大量使用 React 构建公司首页,据说 Instagram 完全基于 ReactJS 库。你可能会惊讶地发现,其他知名公司如 纽约时报Netflix可汗学院 也在他们的技术栈中使用了 ReactJS。

更令人惊讶的是 ReactJS 开发者的工作机会,正如在下面 Stackoverflow 所做的研究中看到的,嘿,你可以从事开源项目并获得报酬。这很酷!

 title=

Stackoverflow 的研究显示了对 ReactJS 开发者的巨大需求——来源:2017 年开发者招聘趋势——Stackoverflow 博客

ReactJS 的 GitHub 目前显示超过 13,000 次提交和 1,377 位贡献者。它是一个在 MIT 许可证下的开源项目。

 title=

Angular

 title=

就开发者数量来说,也许 React 是现在最领先的 JavaScript 框架,但是 Angular 紧随其后。事实上,开源开发者和初创公司更乐于选择 React,而较大的公司往往更喜欢 Angular(下面列出了一些例子)。主要原因是,虽然 Angular 可能更复杂,但它的统一性和一致性适用于大型项目。例如,在我整个职业生涯中一直研究 Angular 和 React,我观察到大公司通常认为 Angular 严格的结构是一种优势。下面是 Angular 的另外一些关键优势:

  • 精心设计的命令行工具 —— Angular 有一个优秀的命令行工具(CLI),可以轻松起步和使用 Angular 进行开发。ReactJS 提供命令行工具和其他工具,同时 Angular 有广泛的支持和出色的文档,你可以参见 这个页面
  • 单向数据绑定 —— 和 React 类似,单向数据绑定模型使框架受更少的不必要的副作用的影响。
  • 更好的 TypeScript 支持 —— Angular 与 TypeScript 有很好的一致性,它其实是 JavaScript 强制类型的拓展。它还可以转译为 JavaScript,强制类型是减少错误代码的绝佳选择。

像 YouTube、NetflixIBMWalmart 等知名网站,都已在其应用程序中采用了 Angular。我通过自学使用 Angular 来开始学习前端 JavaScript 开发。我参与了许多涉及 Angular 的个人和专业项目,但那是当时被称为 AngularJS 的 Angular 1.x。当 Google 决定将版本升级到 2.0 时,他们对框架进行了彻底的改造,然后变成了 Angular。新的 Angular 是对之前的 AngularJS 的彻底改造,这一举动带来了一部分新开发者也驱逐了一部分原有的开发者。

截止到撰写本文,Angular 的 GitHub 页面显示 17,781 次提交和 1,133 位贡献者。它也是一个遵循 MIT 许可证的开源项目,因此你可以自由地在你的项目或贡献中使用。

 title=

VueJS

 title=

VueJS 是一个非常有趣的框架。它是 JavaScript 框架领域的新来者,但是在过去几年里它的受欢迎程度显著增加。VueJS 由 尤雨溪 创建,他是曾参与过 Angular 项目的谷歌工程师。该框架现在变得如此受欢迎,以至于许多前端工程师更喜欢 VueJS 而不是其他 JavaScript 框架。下图描述了该框架随着时间的推移获得关注的速度。

 title=

这里有一些 VueJS 的主要优点:

  • 更容易地学习曲线 —— 与 Angular 或 React 相比,许多前端开发者都认为 VueJS 有更平滑的学习曲线。
  • 小体积 —— 与 Angular 或 React 相比,VueJS 相对轻巧。在 官方文档 中,它的大小据说只有约 30 KB;而 Angular 生成的项目超过 65 KB。
  • 简明的文档 —— VueJS 有全面清晰的文档。请自行查看它的 官方文档

VueJS 的 GitHub 显示该项目有 3,099 次提交和 239 位贡献者。

 title=

MeteorJS

 title=

MeteorJS 是一个自由开源的 同构框架,这意味着它和 NodeJS 一样,同时运行客户端和服务器的 JavaScript。Meteor 能够和任何其他流行的前端框架一起使用,如 Angular、React、Vue、Svelte 等。

Meteor 被高通、马自达和宜家等多家公司以及如 Dispatch 和 Rocket.Chat 等多个应用程序使用。您可以其在官方网站上查看更多案例

 title=

Meteor 的一些主要功能包括:

  • 在线数据 —— 服务器发送数据而不是 HTML,并由客户端渲染。在线数据主要是指 Meteor 在页面加载时通过一个 WebSocket 连接服务器,然后通过该链接传输所需要的数据。
  • 用 JavaScript 开发一切 —— 客户端、应用服务、网页和移动界面都可以用 JavaScript 设计。
  • 支持大多数主流框架 —— Angular、React 和 Vue 都可以与 Meteor 结合。因此,你仍然可以使用最喜欢的框架如 React 或 Angular,这并不防碍 Meteor 为你提供一些优秀的功能。

截止到目前,Meteor 的 GitHub 显示 22,804 次提交和 428 位贡献者。这对于开源项目来说相当多了。

 title=

EmberJS

 title=

EmberJS 是一个基于 模型-视图-视图模型(MVVM) 模式的开源 JavaScript 框架。如果你从来没有听说过 EmberJS,你肯定会惊讶于有多少公司在使用它。Apple Music、Square、Discourse、Groupon、LinkedIn、Twitch、Nordstorm 和 Chipotle 都将 EmberJS 作为公司的技术栈之一。你可以通过查询 EmberJS 的官方页面 来发掘所有使用 EmberJS 的应用和客户。

Ember 虽然和我们讨论过的其他框架有类似的好处,但这里有些独特的区别:

  • 约定优于配置 —— Ember 将命名约定标准化并自动生成结果代码。这种方法学习曲线有些陡峭,但可以确保程序员遵循最佳实践。
  • 成熟的模板机制 —— Ember 依赖于直接文本操作,直接构建 HTML 文档,而并不关心 DOM。

正如所期待的那样,作为一个被许多应用程序使用的框架,Ember 的 GitHub 页面显示该项目拥有 19,808 次提交和 785 位贡献者。这是一个巨大的数字!

 title=

KnockoutJS

 title=

KnockoutJS 是一个独立开源的 JavaScript 框架,采用 模板-视图-视图模型(MVVM) 模式与模板。尽管与 Angular、React 或 Vue 相比,听说过这个框架的人可能比较少,这个项目在开发者社区仍然相当活跃,并且有以下功能:

  • 声明式绑定 —— Knockout 的声明式绑定系统提供了一种简洁而强大的方式来将数据链接到 UI。绑定简单的数据属性或使用单向绑定很简单。请在 KnockoutJS 的官方文档页面 阅读更多相关信息。
  • 自动 UI 刷新。
  • 依赖跟踪模板。

Knockout 的 GitHub 页面显示约有 1,766 次提交和 81 位贡献者。与其他框架相比,这些数据并不重要,但是该项目仍然在积极维护中。

 title=

BackboneJS

 title=

BackboneJS 是一个具有 RESTful JSON 接口,基于 模型-视图-主持人 Model-View-Presenter (MVP)设计范式的轻量级 JavaScript 框架。

这个框架据说已经被 Airbnb、Hulu、SoundCloud 和 Trello 使用。你可以在 Backbone 的页面 找到上面所有这些案例来研究。

BackboneJS 的 GitHub 页面显示有 3,386 次提交和 289 位贡献者。

 title=

Svelte

 title=

Svelte 是一个开源的 JavaScript 框架,它生成操作 DOM 的代码,而不是包含框架引用。在构建时而非运行时将应用程序转换为 JavaScript 的过程,在某些情况下可能会带来轻微的性能提升。

Svelte 的 GitHub 页面显示,截止到本文撰写为止,该项目有 5,729 次提交和 296 位贡献者。

 title=

AureliaJS

 title=

最后我们介绍一下 Aurelia。Aurelia 是一个前端 JavaScript 框架,是一个现代 JavaScript 模块的集合。Aurelia 有以下有趣的功能:

  • 快速渲染 —— Aurelia 宣称比当今其他任何框架的渲染速度都快。
  • 单向数据流 —— Aurelia 使用一个基于观察的绑定系统,将数据从模型推送到视图。
  • 使用原生 JavaScript 架构 —— 可以用原生 JavaScript 构建网站的所有组件。

Aurelia 的 GitHub 页面显示,截止到撰写本文为止该项目有 788 次提交和 96 位贡献者。

 title=

这就是我在查看 JavaScript 框架世界时发现的新内容。我错过了其他有趣的框架吗?欢迎在评论区分享你的想法。


via: https://opensource.com/article/20/5/open-source-javascript-frameworks

作者:Bryant Son 选题:lujun9972 译者:stevending1st 校对:wxy

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

从 Rust 标准库学习一些有用的关键字。

 title=

我使用 Rust 已经有几个月了,写的东西比我预期的要多——尽管随着我的学习,我改进了所写的代码,并完成了一些超出我最初意图的更复杂的任务,相当多的东西已经被扔掉了。

我仍然喜欢它,并认为谈论一些在 Rust 中反复出现的重要关键字可能会有好处。我会提供我个人对它们的作用的总结:为什么你需要考虑如何使用它们,以及任何其他有用的东西,特别是对于刚接触 Rust 的新手或来自另一种语言的人(如 Java;请阅读我的文章 为什么作为一个 Java 程序员的我喜欢学习 Rust)。

事不宜迟,让我们开始吧。获取更多信息的好地方总是 Rust 官方文档 —— 你可能想从 std 标准库开始。

  1. const – 你可以用 const 来声明常量,而且你应该这样做。虽然这不是造火箭,但请一定要用 const ,如果你要在不同的模块中使用常量,那请创建一个 lib.rs 文件(Rust 默认的),你可以把所有的常量放在一个命名良好的模块中。我曾经在不同模块的不同文件中发生过 const 变量名(和值)的冲突,仅仅是因为我太懒了,除了在不同文件中剪切和粘贴之外,我本可以通过创建一个共享模块来节省大量的工作。
  2. let – 你并不 总是 需要用 let 语句声明一个变量,但当你这样做时你的代码会更加清晰。此外,如果可以,请一定要添加变量类型。Rust 会尽最大努力猜测它应该是什么类型的变量,但它不一定总能在运行时做到这一点(在这种情况下,编译器 Cargo 会提示你),它甚至可能做不到你期望的那样。在后一种情况下,对于 Cargo 来说,抱怨你所赋值的函数(例如)与声明不一致,总比 Rust 试图帮助你做错事,而你却不得不在其他地方花费大量时间来进行调试要简单。
  3. matchmatch 对我来说是新鲜事物,我喜欢使用它。它与其他编程语言中的 switch 没有什么不同,但在 Rust 中被广泛使用。它使代码更清晰易读,如果你做了一些愚蠢的事情(例如错过一些可能的情况),Cargo 会很好地提示你。我一般的经验法则是,在管理不同的选项或进行分支时,如果可以使用 match,那就请一定要使用它。
  4. mut – 在声明一个变量时,如果它的值在声明后会发生变化,那么你需要声明它是可变的(LCTT 译注:Rust 中变量默认是不可变的)。常见的错误是在某个变量 没有 变化的情况下声明它是可变的,这时编译器会警告你。如果你收到了 Cargo 的警告,说一个可变的变量没有被改变,而你认为它被 改变 了,那么你可能要检查该变量的范围,并确保你使用的是正确的那个。
  5. return – 实际上我很少使用 return,它用于从函数中返回一个值,但是如果你只是在函数的最后一行提供值(或提供返回值的函数),通常会变得更简单,能更清晰地阅读。警告:在很多情况下,你 忘记省略这一行末尾的分号(;),如果你这样做,编译器会不高兴的。
  6. unsafe – 如其意:如果你想做一些不能保证 Rust 内存安全的事情,那么你就需要使用这个关键字。我绝对无意在现在或将来的任何时候宣布我的任何 Rust 代码不安全;Rust 如此友好的原因之一是它阻止了这种黑客行为。如果你真的需要这样做,再想想,再想想,然后重新设计代码。除非你是一个非常低级的系统程序员,否则要 避免 使用 unsafe
  7. use – 当你想使用另一个 crate 中的东西时,例如结构体、变量、函数等,那么你需要在你要使用它的代码的代码块的开头声明它。另一个常见的错误是,你这样做了,但没有在 Cargo.toml 文件中添加该 crate (最好有一个最小版本号)。

我知道,这不是我写过的最复杂的文章,但这是我在开始学习 Rust 时会欣赏的那种文章。我计划在关键函数和其他 Rust 必知知识方面编写类似的文章:如果你有任何要求,请告诉我!


本文最初发表于 Alice, Eve, and Bob 经作者许可转载。


via: https://opensource.com/article/20/10/keywords-rust

作者:Mike Bursell 选题:lujun9972 译者:mcfd 校对:wxy

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

用 GPG 和 Python 的 getpass 模块给你的密码多一层安全保障。

 title=

密码对程序员来说尤其重要。你不应该在不加密的情况下存储它们,而且你也不应该在用户输入密码的时候显示出输入的内容。当我决定要提高我的笔记本电脑的安全性时,这对我来说变得特别重要。我对我的家目录进行了加密,但当我登录后,任何以纯文本形式存储在配置文件中的密码都有可能暴露在偷窥者面前。

具体来说,我使用一个名为 Mutt 的应用作为我的电子邮件客户端。它可以让我在我的 Linux 终端中阅读和撰写电子邮件,但通常它希望在其配置文件中有一个密码。我限制了我的 Mutt 配置文件的权限,以便只有我可以看到它,我是我的笔记本电脑的唯一用户,所以我并不真的担心经过认证的用户会无意中看到我的配置文件。相反,我想保护自己,无论是为了吹嘘还是为了版本控制,不至于心不在焉地把我的配置发布到网上,把我的密码暴露了。此外,虽然我不希望我的系统上有不受欢迎的客人,但我确实想确保入侵者不能通过对我的配置上运行 cat 就获得我的密码。

Python GnuPG

Python 模块 python-gnupggpg 应用的一个 Python 封装。该模块的名字是 python-gnupg,你不要把它和一个叫做 gnupg 的模块混淆。

GnuPG(GPG) 是 Linux 的默认加密系统,我从 2009 年左右开始使用它。我对它很熟悉,对它的安全性有很高的信任。

我决定将我的密码输入 Mutt 的最好方法是将我的密码存储在一个加密的 GPG 文件中,创建一个提示我的 GPG 密码来解锁这个加密文件,然后将密码交给 Mutt(实际上是交给 offlineimap 命令,我用它来同步我的笔记本和电子邮件服务器)。

用 Python 获取用户输入 是非常容易的。对 input 进行调用,无论用户输入什么,都会被存储为一个变量:

print("Enter password: ")
myinput = input()

print("You entered: ", myinput)

我的问题是,当我根据密码提示在终端上输入密码时,我所输入的所有内容对任何从我肩膀上看过去或滚动我的终端历史的人来说都是可见的:

$ ./test.py
Enter password: my-Complex-Passphrase

用 getpass 输入不可见密码

正如通常的情况一样,有一个 Python 模块已经解决了我的问题。这个模块是 getpass4,从用户的角度来看,它的行为和 input 完全一样,只是不显示用户输入的内容。

你可以用 pip 安装这两个模块:

$ python -m pip install --user python-gnupg getpass4

下面是我的 Python 脚本,用于创建密码提示:

#!/usr/bin/env python
# by Seth Kenlon
# GPLv3

# install deps:
# python3 -m pip install --user python-gnupg getpass4

import gnupg
import getpass
from pathlib import Path

def get_api_pass():
  homedir = str(Path.home())
  gpg = gnupg.GPG(gnupghome=os.path.join(homedir,".gnupg"), use_agent=True)
  passwd = getpass.getpass(prompt="Enter your GnuPG password: ", stream=None)

  with open(os.path.join(homedir,'.mutt','pass.gpg'), 'rb') as f:
    apipass = (gpg.decrypt_file(f, passphrase=passwd))

  f.close()

  return str(apipass)
 
if __name__ == "__main__":
  apipass = get_api_pass()
  print(apipass)

如果你想试试,把文件保存为 password_prompt.py。如果你使用 offlineimap 并想在你自己的密码输入中使用这个方案,那么把它保存到某个你可以在 .offlineimaprc 文件中指向 offlineimap 的位置(我使用 ~/.mutt/password_prompt.py)。

测试密码提示

要查看脚本的运行情况,你首先必须创建一个加密文件(我假设你已经设置了 GPG):

$ echo "hello world" > pass
$ gpg --encrypt pass
$ mv pass.gpg ~/.mutt/pass.gpg
$ rm pass

现在运行 Python 脚本:

$ python ~/.mutt/password_prompt.py
Enter your GPG password:
hello world

当你输入时没有任何显示,但只要你正确输入 GPG 口令,你就会看到该测试信息。

将密码提示符与 offlineimap 整合起来

我需要将我的新提示与 offlineimap 命令结合起来。我为这个脚本选择了 Python,因为我知道 offlineimap 可以对 Python 程序进行调用。如果你是一个 offlineimap 用户,你会明白唯一需要的“整合”是在你的 .offlineimaprc 文件中改变两行。

首先,添加一行引用 Python 文件的内容:

pythonfile = ~/.mutt/password_prompt.py

然后将 .offlineimaprc中的 remotepasseval 行改为调用 password_prompt.py中的 get_api_pass() 函数:

remotepasseval = get_api_pass()

配置文件中不再有密码!

安全问题

在你的个人电脑上考虑安全问题有时会让人觉得很偏执。你的 SSH 配置是否真的需要限制为 600?隐藏在名为 .mutt 的无关紧要的电子邮件密码真的重要吗?也许不重要。

然而,知道我没有把敏感数据悄悄地藏在我的配置文件里,使我更容易把文件提交到公共 Git 仓库,把片段复制和粘贴到支持论坛,并以真实好用的配置文件的形式分享我的知识。仅就这一点而言,安全性的提高使我的生活更加轻松。而且有这么多好的 Python 模块可以提供帮助,这很容易实现。


via: https://opensource.com/article/21/7/invisible-passwords-python

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

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

抛开关于是否使用 JSON 作为配置格式的争论,只需学习如何用 Groovy 来解析它。

 title=

应用程序通常包括某种类型的默认或“开箱即用”的状态或配置,以及某种让用户根据自己的需要定制配置的方式。

例如,LibreOffice Writer 通过其菜单栏上的工具 > 选项,可以访问诸如用户数据、字体、语言设置等(以及更多的)设置。一些应用程序(如 LibreOffice)提供了一个点选式的用户界面来管理这些设置。有些,像 Tracker(GNOME 的“任务”,用于索引文件)使用 XML 文件。还有一些,特别是基于 JavaScript 的应用,使用 JSON,尽管它有许多人抗议(例如,这位作者这位其他作者)。

在这篇文章中,我将回避关于是否使用 JSON 作为配置文件格式的争论,并解释如何使用 Groovy 编程语言 来解析这类信息。Groovy 以 Java 为基础,但有一套不同的设计重点,使 Groovy 感觉更像 Python。

安装 Groovy

由于 Groovy 是基于 Java 的,它也需要安装 Java。你可能会在你的 Linux 发行版的软件库中找到最近的、合适的 Java 和 Groovy 版本。或者,你可以按照其网站上的 说明 安装 Groovy。 Linux 用户的一个不错的选择是 SDKMan,你可以使用它来获取 Java、Groovy 和许多其他相关工具的多个版本。 对于本文,我将使用我的发行版的 OpenJDK11 和 SDKMan 的 Groovy 3.0.7。

演示的 JSON 配置文件

在这个演示中,我从 Drupal 中截取了这个 JSON 文件,它是 Drupal CMS 使用的主要配置文件,并将其保存在文件 config.json 中:

{
 "vm": {
  "ip": "192.168.44.44",
  "memory": "1024",
  "synced_folders": [
   {
    "host_path": "data/",
    "guest_path": "/var/www",
    "type": "default"
   }
  ],
  "forwarded_ports": []
 },
 "vdd": {
  "sites": {
   "drupal8": {
    "account_name": "root",
    "account_pass": "root",
    "account_mail": "[email protected]",
    "site_name": "Drupal 8",
    "site_mail": "[email protected]",
    "vhost": {
     "document_root": "drupal8",
     "url": "drupal8.dev",
     "alias": ["www.drupal8.dev"]
    }
   },
   "drupal7": {
    "account_name": "root",
    "account_pass": "root",
    "account_mail": "[email protected]",
    "site_name": "Drupal 7",
    "site_mail": "[email protected]",
    "vhost": {
     "document_root": "drupal7",
     "url": "drupal7.dev",
     "alias": ["www.drupal7.dev"]
    }
   }
  }
 }
}

这是一个漂亮的、复杂的 JSON 文件,有几层结构,如:

<>.vdd.sites.drupal8.account_name

和一些列表,如:

<>.vm.synced_folders

这里,<> 代表未命名的顶层。让我们看看 Groovy 是如何处理的。

用 Groovy 解析 JSON

Groovy 自带的 groovy.json 包,里面有各种很酷的东西。其中最好的部分是 JsonSlurper 类,它包括几个 parse() 方法,可以将 JSON 转换为 Groovy 的 Map,一种根据键值存储的数据结构。

下面是一个简短的 Groovy 程序,名为 config1.groovy,它创建了一个 JsonSlurper 实例,然后调用其中的 parse() 方法来解析文件中的 JSON,并将其转换名为 configMap 实例,最后将该 map 输出:

import groovy.json.JsonSlurper

def jsonSlurper = new JsonSlurper()

def config = jsonSlurper.parse(new File('config.json'))

println "config = $config"

在终端的命令行上运行这个程序:

$ groovy config1.groovy
config = [vm:[ip:192.168.44.44, memory:1024, synced_folders:[[host_path:data/, guest_path:/var/www, type:default]], forwarded_ports:[]], vdd:[sites:[drupal8:[account_name:root, account_pass:root, account_mail:[email protected], site_name:Drupal 8, site_mail:[email protected], vhost:[document_root:drupal8, url:drupal8.dev, alias:[www.drupal8.dev]]], drupal7:[account_name:root, account_pass:root, account_mail:[email protected], site_name:Drupal 7, site_mail:[email protected], vhost:[document_root:drupal7, url:drupal7.dev, alias:[www.drupal7.dev]]]]]]
$

输出显示了一个有两个键的顶层映射:vmvdd。每个键都引用了它自己的值的映射。注意 forwarded_ports 键所引用的空列表。

这很容易,但它所做的只是把东西打印出来。你是如何获得各种组件的呢?下面是另一个程序,显示如何访问存储在 config.vm.ip 的值:

import groovy.json.JsonSlurper

def jsonSlurper = new JsonSlurper()

def config = jsonSlurper.parse(new File('config.json'))

println "config.vm.ip = ${config.vm.ip}"

运行它:

$ groovy config2.groovy
config.vm.ip = 192.168.44.44
$

是的,这也很容易。 这利用了 Groovy 速记,这意味着:

config.vm.ip

在 Groovy 中等同于:

config['vm']['ip']

configconfig.vm 都是 Map 的实例,并且都等同于在 Java 中的:

config.get("vm").get("ip")

仅仅是处理 JSON 就这么多了。如果你想有一个标准的配置并让用户覆盖它呢?在这种情况下,你可能想在程序中硬编码一个 JSON 配置,然后读取用户配置并覆盖任何标准配置的设置。

假设上面的配置是标准的,而用户只想覆盖其中的一点,只想覆盖 vm 结构中的 ipmemory 值,并把它放在 userConfig.json 文件中:

{
 "vm": {
  "ip": "201.201.201.201",
  "memory": "4096",
 }
}

你可以用这个程序来做:

import groovy.json.JsonSlurper

def jsonSlurper = new JsonSlurper()

// 使用 parseText() 来解析一个字符串,而不是从文件中读取。
// 这给了我们一个“标准配置”
def standardConfig = jsonSlurper.parseText("""
{
 "vm": {
  "ip": "192.168.44.44",
  "memory": "1024",
  "synced_folders": [
   {
    "host_path": "data/",
    "guest_path": "/var/www",
    "type": "default"
   }
  ],
  "forwarded_ports": []
 },
 "vdd": {
  "sites": {
   "drupal8": {
    "account_name": "root",
    "account_pass": "root",
    "account_mail": "[email protected]",
    "site_name": "Drupal 8",
    "site_mail": "[email protected]",
    "vhost": {
     "document_root": "drupal8",
     "url": "drupal8.dev",
     "alias": ["www.drupal8.dev"]
    }
   },
   "drupal7": {
    "account_name": "root",
    "account_pass": "root",
    "account_mail": "[email protected]",
    "site_name": "Drupal 7",
    "site_mail": "[email protected]",
    "vhost": {
     "document_root": "drupal7",
     "url": "drupal7.dev",
     "alias": ["www.drupal7.dev"]
    }
   }
  }
 }
}
""")

// 打印标准配置
println "standardConfig = $standardConfig"

//读入并解析用户配置信息
def userConfig = jsonSlurper.parse(new File('userConfig.json'))

// 打印出用户配置信息
println "userConfig = $userConfig"

// 一个将用户配置与标准配置合并的函数
def mergeMaps(Map input, Map merge) {
  merge.each { k, v -&gt;
    if (v instanceof Map)
      mergeMaps(input[k], v)
    else
      input[k] = v
  }
}

// 合并配置并打印出修改后的标准配置
mergeMaps(standardConfig, userConfig)

println "modified standardConfig $standardConfig"

以下列方式运行:

$ groovy config3.groovy
standardConfig = [vm:[ip:192.168.44.44, memory:1024, synced_folders:[[host_path:data/, guest_path:/var/www, type:default]], forwarded_ports:[]], vdd:[sites:[drupal8:[account_name:root, account_pass:root, account_mail:[email protected], site_name:Drupal 8, site_mail:[email protected], vhost:[document_root:drupal8, url:drupal8.dev, alias:[www.drupal8.dev]]], drupal7:[account_name:root, account_pass:root, account_mail:[email protected], site_name:Drupal 7, site_mail:[email protected], vhost:[document_root:drupal7, url:drupal7.dev, alias:[www.drupal7.dev]]]]]]
userConfig = [vm:[ip:201.201.201.201, memory:4096]]
modified standardConfig [vm:[ip:201.201.201.201, memory:4096, synced_folders:[[host_path:data/, guest_path:/var/www, type:default]], forwarded_ports:[]], vdd:[sites:[drupal8:[account_name:root, account_pass:root, account_mail:[email protected], site_name:Drupal 8, site_mail:[email protected], vhost:[document_root:drupal8, url:drupal8.dev, alias:[www.drupal8.dev]]], drupal7:[account_name:root, account_pass:root, account_mail:[email protected], site_name:Drupal 7, site_mail:[email protected], vhost:[document_root:drupal7, url:drupal7.dev, alias:[www.drupal7.dev]]]]]]
$

modified standardConfig 开头的一行显示,vm.ip and vm.memory 的值被覆盖了。

眼尖的读者会注意到,我没有检查畸形的 JSON,也没有仔细确保用户的配置是有意义的(不创建新字段,提供合理的值,等等)。所以用这个递归方法来合并两个映射在现实中可能并不那么实用。

好吧,我必须为家庭作业留下 一些 东西,不是吗?

Groovy 资源

Apache Groovy 网站有很多很棒的 文档。另一个很棒的 Groovy 资源是 Mr. Haki。学习 Groovy 的一个非常好的理由是继续学习 Grails,它是一个非常高效的全栈 Web 框架,建立在 Hibernate、Spring Boot 和 Micronaut 等优秀组件之上。


via: https://opensource.com/article/21/6/groovy-parse-json

作者:Chris Hermansen 选题:lujun9972 译者:geekpi 校对:wxy

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

如果你看一下 新的 Datadog Agent,你可能会注意到大部分代码库是用 Go 编写的,尽管我们用来收集指标的检查仍然是用 Python 编写的。这大概是因为 Datadog Agent 是一个 嵌入了 CPython 解释器的普通 Go 二进制文件,可以在任何时候按需执行 Python 代码。这个过程通过抽象层来透明化,使得你可以编写惯用的 Go 代码而底层运行的是 Python。

在 Go 应用程序中嵌入 Python 的原因有很多:

  • 它在过渡期间很有用;可以逐步将现有 Python 项目的部分迁移到新语言,而不会在此过程中丢失任何功能。
  • 你可以复用现有的 Python 软件或库,而无需用新语言重新实现。
  • 你可以通过加载去执行常规 Python 脚本来动态扩展你软件,甚至在运行时也可以。

理由还可以列很多,但对于 Datadog Agent 来说,最后一点至关重要:我们希望做到无需重新编译 Agent,或者说编译任何内容就能够执行自定义检查或更改现有检查。

嵌入 CPython 非常简单,而且文档齐全。解释器本身是用 C 编写的,并且提供了一个 C API 以编程方式来执行底层操作,例如创建对象、导入模块和调用函数。

在本文中,我们将展示一些代码示例,我们将会在与 Python 交互的同时继续保持 Go 代码的惯用语,但在我们继续之前,我们需要解决一个间隙:嵌入 API 是 C 语言,但我们的主要应用程序是 Go,这怎么可能工作?

介绍 cgo

很多好的理由 说服你为什么不要在堆栈中引入 cgo,但嵌入 CPython 是你必须这样做的原因。cgo 不是语言,也不是编译器。它是 外部函数接口 Foreign Function Interface (FFI),一种让我们可以在 Go 中使用来调用不同语言(特别是 C)编写的函数和服务的机制。

当我们提起 “cgo” 时,我们实际上指的是 Go 工具链在底层使用的一组工具、库、函数和类型,因此我们可以通过执行 go build 来获取我们的 Go 二进制文件。下面是使用 cgo 的示例程序:

package main

// #include <float.h>
import "C"
import "fmt"

func main() {
    fmt.Println("Max float value of float is", C.FLT_MAX)
}

在这种包含头文件情况下,import "C" 指令上方的注释块称为“ 序言 preamble ”,可以包含实际的 C 代码。导入后,我们可以通过“C”伪包来“跳转”到外部代码,访问常量 FLT_MAX。你可以通过调用 go build 来构建,它就像普通的 Go 一样。

如果你想查看 cgo 在这背后到底做了什么,可以运行 go build -x。你将看到 “cgo” 工具将被调用以生成一些 C 和 Go 模块,然后将调用 C 和 Go 编译器来构建目标模块,最后链接器将所有内容放在一起。

你可以在 Go 博客 上阅读更多有关 cgo 的信息,该文章包含更多的例子以及一些有用的链接来做进一步了解细节。

现在我们已经了解了 cgo 可以为我们做什么,让我们看看如何使用这种机制运行一些 Python 代码。

嵌入 CPython:一个入门指南

从技术上讲,嵌入 CPython 的 Go 程序并没有你想象的那么复杂。事实上,我们只需在运行 Python 代码之前初始化解释器,并在完成后关闭它。请注意,我们在所有示例中使用 Python 2.x,但我们只需做很少的调整就可以应用于 Python 3.x。让我们看一个例子:

package main

// #cgo pkg-config: python-2.7
// #include <Python.h>
import "C"
import "fmt"

func main() {
    C.Py_Initialize()
    fmt.Println(C.GoString(C.Py_GetVersion()))
    C.Py_Finalize()
}

上面的例子做的正是下面 Python 代码要做的事:

import sys
print(sys.version)

你可以看到我们在序言加入了一个 #cgo 指令;这些指令被会被传递到工具链,让你改变构建工作流程。在这种情况下,我们告诉 cgo 调用 pkg-config 来收集构建和链接名为 python-2.7 的库所需的标志,并将这些标志传递给 C 编译器。如果你的系统中安装了 CPython 开发库和 pkg-config,你只需要运行 go build 来编译上面的示例。

回到代码,我们使用 Py_Initialize()Py_Finalize() 来初始化和关闭解释器,并使用 Py_GetVersion C 函数来获取嵌入式解释器版本信息的字符串。

如果你想知道,所有我们需要放在一起调用 C 语言 Python API的 cgo 代码都是模板代码。这就是为什么 Datadog Agent 依赖 go-python 来完成所有的嵌入操作;该库为 C API 提供了一个 Go 友好的轻量级包,并隐藏了 cgo 细节。这是另一个基本的嵌入式示例,这次使用 go-python:

package main

import (
    python "github.com/sbinet/go-python"
)

func main() {
    python.Initialize()
    python.PyRun_SimpleString("print 'hello, world!'")
    python.Finalize()
}

这看起来更接近普通 Go 代码,不再暴露 cgo,我们可以在访问 Python API 时来回使用 Go 字符串。嵌入式看起来功能强大且对开发人员友好,是时候充分利用解释器了:让我们尝试从磁盘加载 Python 模块。

在 Python 方面我们不需要任何复杂的东西,无处不在的“hello world” 就可以达到目的:

# foo.py
def hello():
    """
    Print hello world for fun and profit.
    """
    print "hello, world!"

Go 代码稍微复杂一些,但仍然可读:

// main.go
package main

import "github.com/sbinet/go-python"

func main() {
    python.Initialize()
    defer python.Finalize()

    fooModule := python.PyImport_ImportModule("foo")
    if fooModule == nil {
        panic("Error importing module")
    }

    helloFunc := fooModule.GetAttrString("hello")
    if helloFunc == nil {
        panic("Error importing function")
    }

    // The Python function takes no params but when using the C api
    // we're required to send (empty) *args and **kwargs anyways.
    helloFunc.Call(python.PyTuple_New(0), python.PyDict_New())
}

构建时,我们需要将 PYTHONPATH 环境变量设置为当前工作目录,以便导入语句能够找到 foo.py 模块。在 shell 中,该命令如下所示:

$ go build main.go && PYTHONPATH=. ./main
hello, world!

可怕的全局解释器锁

为了嵌入 Python 必须引入 cgo ,这是一种权衡:构建速度会变慢,垃圾收集器不会帮助我们管理外部系统使用的内存,交叉编译也很难。对于一个特定的项目来说,这些问题是否是可以争论的,但我认为有一些不容商量的问题:Go 并发模型。如果我们不能从 goroutine 中运行 Python,那么使用 Go 就没有意义了。

在处理并发、Python 和 cgo 之前,我们还需要知道一些事情:它就是 全局解释器锁 Global Interpreter Lock ,即 GIL。GIL 是语言解释器(CPython 就是其中之一)中广泛采用的一种机制,可防止多个线程同时运行。这意味着 CPython 执行的任何 Python 程序都无法在同一进程中并行运行。并发仍然是可能的,锁是速度、安全性和实现简易性之间的一个很好的权衡,那么,当涉及到嵌入时,为什么这会造成问题呢?

当一个常规的、非嵌入式的 Python 程序启动时,不涉及 GIL 以避免锁定操作中的无用开销;在某些 Python 代码首次请求生成线程时 GIL 就启动了。对于每个线程,解释器创建一个数据结构来存储当前的相关状态信息并锁定 GIL。当线程完成时,状态被恢复,GIL 被解锁,准备被其他线程使用。

当我们从 Go 程序运行 Python 时,上述情况都不会自动发生。如果没有 GIL,我们的 Go 程序可以创建多个 Python 线程,这可能会导致竞争条件,从而导致致命的运行时错误,并且很可能出现分段错误导致整个 Go 应用程序崩溃。

解决方案是在我们从 Go 运行多线程代码时显式调用 GIL;代码并不复杂,因为 C API 提供了我们需要的所有工具。为了更好地暴露这个问题,我们需要写一些受 CPU 限制的 Python 代码。让我们将这些函数添加到前面示例中的 foo.py 模块中:

# foo.py
import sys

def print_odds(limit=10):
    """
    Print odds numbers < limit
    """
    for i in range(limit):
        if i%2:
            sys.stderr.write("{}\n".format(i))

def print_even(limit=10):
    """
    Print even numbers < limit
    """
    for i in range(limit):
        if i%2 == 0:
            sys.stderr.write("{}\n".format(i))

我们将尝试从 Go 并发打印奇数和偶数,使用两个不同的 goroutine(因此涉及线程):

package main

import (
    "sync"

    "github.com/sbinet/go-python"
)

func main() {
    // The following will also create the GIL explicitly
    // by calling PyEval_InitThreads(), without waiting
    // for the interpreter to do that
    python.Initialize()

    var wg sync.WaitGroup
    wg.Add(2)

    fooModule := python.PyImport_ImportModule("foo")
    odds := fooModule.GetAttrString("print_odds")
    even := fooModule.GetAttrString("print_even")

    // Initialize() has locked the the GIL but at this point we don't need it
    // anymore. We save the current state and release the lock
    // so that goroutines can acquire it
    state := python.PyEval_SaveThread()

    go func() {
        _gstate := python.PyGILState_Ensure()
        odds.Call(python.PyTuple_New(0), python.PyDict_New())
        python.PyGILState_Release(_gstate)

        wg.Done()
    }()

    go func() {
        _gstate := python.PyGILState_Ensure()
        even.Call(python.PyTuple_New(0), python.PyDict_New())
        python.PyGILState_Release(_gstate)

        wg.Done()
    }()

    wg.Wait()

    // At this point we know we won't need Python anymore in this
    // program, we can restore the state and lock the GIL to perform
    // the final operations before exiting.
    python.PyEval_RestoreThread(state)
    python.Finalize()
}

在阅读示例时,你可能会注意到一个模式,该模式将成为我们运行嵌入式 Python 代码的习惯写法:

  1. 保存状态并锁定 GIL。
  2. 执行 Python。
  3. 恢复状态并解锁 GIL。

代码应该很简单,但我们想指出一个微妙的细节:请注意,尽管借用了 GIL 执行,有时我们通过调用 PyEval_SaveThread()PyEval_RestoreThread() 来操作 GIL,有时(查看 goroutines 里面)我们对 PyGILState_Ensure()PyGILState_Release() 来做同样的事情。

我们说过当从 Python 操作多线程时,解释器负责创建存储当前状态所需的数据结构,但是当同样的事情发生在 C API 时,我们来负责处理。

当我们用 go-python 初始化解释器时,我们是在 Python 上下文中操作的。因此,当调用 PyEval_InitThreads() 时,它会初始化数据结构并锁定 GIL。我们可以使用 PyEval_SaveThread()PyEval_RestoreThread() 对已经存在的状态进行操作。

在 goroutines 中,我们从 Go 上下文操作,我们需要显式创建状态并在完成后将其删除,这就是 PyGILState_Ensure()PyGILState_Release() 为我们所做的。

释放 Gopher

在这一点上,我们知道如何处理在嵌入式解释器中执行 Python 的多线程 Go 代码,但在 GIL 之后,另一个挑战即将来临:Go 调度程序。

当一个 goroutine 启动时,它被安排在可用的 GOMAXPROCS 线程之一上执行,参见此处 可了解有关该主题的更多详细信息。如果一个 goroutine 碰巧执行了系统调用或调用 C 代码,当前线程会将线程队列中等待运行的其他 goroutine 移交给另一个线程,以便它们有更好的机会运行; 当前 goroutine 被暂停,等待系统调用或 C 函数返回。当这种情况发生时,线程会尝试恢复暂停的 goroutine,但如果这不可能,它会要求 Go 运行时找到另一个线程来完成 goroutine 并进入睡眠状态。 goroutine 最后被安排给另一个线程,它就完成了。

考虑到这一点,让我们看看当一个 goroutine 被移动到一个新线程时,运行一些 Python 代码的 goroutine 会发生什么:

  1. 我们的 goroutine 启动,执行 C 调用并暂停。GIL 被锁定。
  2. 当 C 调用返回时,当前线程尝试恢复 goroutine,但失败了。
  3. 当前线程告诉 Go 运行时寻找另一个线程来恢复我们的 goroutine。
  4. Go 调度器找到一个可用线程并恢复 goroutine。
  5. goroutine 快完成了,并在返回之前尝试解锁 GIL。
  6. 当前状态中存储的线程 ID 来自原线程,与当前线程的 ID 不同。
  7. 崩溃!

所幸,我们可以通过从 goroutine 中调用运行时包中的 LockOSThread 函数来强制 Go runtime 始终保持我们的 goroutine 在同一线程上运行:

go func() {
    runtime.LockOSThread()

    _gstate := python.PyGILState_Ensure()
    odds.Call(python.PyTuple_New(0), python.PyDict_New())
    python.PyGILState_Release(_gstate)
    wg.Done()
}()

这会干扰调度器并可能引入一些开销,但这是我们愿意付出的代价。

结论

为了嵌入 Python,Datadog Agent 必须接受一些权衡:

  • cgo 引入的开销。
  • 手动处理 GIL 的任务。
  • 在执行期间将 goroutine 绑定到同一线程的限制。

为了能方便在 Go 中运行 Python 检查,我们很乐意接受其中的每一项。但通过意识到这些权衡,我们能够最大限度地减少它们的影响,除了为支持 Python 而引入的其他限制,我们没有对策来控制潜在问题:

  • 构建是自动化和可配置的,因此开发人员仍然需要拥有与 go build 非常相似的东西。
  • Agent 的轻量级版本,可以使用 Go 构建标签,完全剥离 Python 支持。
  • 这样的版本仅依赖于在 Agent 本身硬编码的核心检查(主要是系统和网络检查),但没有 cgo 并且可以交叉编译。

我们将在未来重新评估我们的选择,并决定是否仍然值得保留 cgo;我们甚至可以重新考虑整个 Python 是否仍然值得,等待 Go 插件包 成熟到足以支持我们的用例。但就目前而言,嵌入式 Python 运行良好,从旧代理过渡到新代理再简单不过了。

你是一个喜欢混合不同编程语言的多语言者吗?你喜欢了解语言的内部工作原理以提高你的代码性能吗?


via: https://www.datadoghq.com/blog/engineering/cgo-and-python/

作者:Massimiliano Pippi 译者:Zioyi 校对: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中国 荣誉推出