分类 软件开发 下的文章

Lua 是一个有趣而强大的语言,随着各个版本的推进,功能愈发的强大,开发者群体也在不断的增长。这篇文章我们将探索一下它的各种前景。

Lua 是一个脚本语言,它面向过程、函数式编程,甚至可以是 面向对象的。它使用类 C 语言的语法,但却是动态类型,具有自动内存管理和垃圾回收功能,使用基于寄存器的虚拟机来解释字节码。这些特点使得它对于初学者来说是个很好的语言,同时也是经验丰富的程序员的强大工具。

虽然与 PythonJavaScript 相比,Lua 现在已经有点儿黯然失色了,但是 Lua 拥有的一些优点使得它在许多的重大软件项目中很受欢迎。Lua 很容易嵌入到其他语言当中, 这意味着你可以在(例如)Java 编写的代码中包含 Lua 文件,就像原生的 Java 代码一样运行。这听起来就像魔法一般,现在有许多项目如 luaj 使得其成为可能,之所以可以实现,正是因为 Lua 就是为此而设计的。部分出于这种灵活性,你可以在许多游戏、图形应用的程序中发现 Lua 脚本的身影。

就像其他任何事情一样,做到完美是需要时间的,但 Lua 是很易于学习(并且有趣)的语言。它是一种一致的语言、一种带有有用的错误消息的友好的语言,并且可以在网上轻松找到许多有用的资料。那么就让我们开始吧!

安装 Lua

在 Linux 下,你可以使用发行版自带的包管理来安装 Lua。例如,在 Fedora、CentOS、 Mageia、OpenMandriva 以及类似发行版中:

$ sudo dnf install lua

在 Debian 以及基于 Debian 的系统中:

$ sudo apt install lua

对于 Mac,你可以使用 MacPorts 或者 Homebrew

$ sudo port install lua

对于 Windows,可以使用 Chocolatey 安装 Lua。

完成安装后,可以在终端中输入 lua 来在交互式解释器中使用 Lua。

函数

如许多编程语言一样,Lua 的语法通常是一个内建的函数或关键字,后面跟着参数。例如,print 函数显示你传给它的所有参数。

$ lua
Lua 5.4.2  Copyright (C) 1994-2020 Lua.org,PUC-Rio

> print('hello')
hello

Lua 的 string 库可以操作单词(在编程中称为“字符串”)。例如,要统计字符串中的字母数量,你可以使用 string 库中 len 函数:

> string.len('hello')
5

变量

变量允许你在计算机内存中为临时的数据创建一个指定的空间。Lua 中创建变量的方法是赋予变量一个名字,接着将数据放入其中。

> foo = "hello world"
> print(foo)
hello world
> bar = 1+2
> print(bar)
3

在编程中,数组的使用频率仅次于变量。“数组”这个词的字面意思就是一种排列,而这就是程序中数组的意义了。它是数据的一种排列,因为有排列,所有数组具有结构化的优势。本质上,数组通常用于和变量相同的目的,只不过数组会给对其中的数据进行排序。在 Lua 中,数组被称为“表”。(LCTT 译注:使用过其它编程语言的同学可以发现,Lua 的表相当于其它语言中的关联数组、哈希。)

创建表和创建变量类似,区别仅在于它的初始化内容被设置为 {}:

> mytable = {}

当往表中增加数据时,它就如同创建变量一样,区别在于这里的变量之前总是以表名开头,中间使用 . 来连接:

> mytable.foo = "hello world"
> mytable.bar = 1+2
> print(mytable.foo)
hello world
> print(mytable.bar)
3

使用 Lua 编写脚本

在终端交互环境中运行 Lua 可以得到良好的反馈,但是将 Lua 作为脚本运行会更为有用。Lua 脚本就是包含 Lua 代码的文本文件,Lua 命令可以解析并执行此文件。

在刚刚开始学习一门编程语言时,一个永恒的问题是你怎么知道该写什么。这篇文章将提供一个不错的开端,截至目前,你仅知道了两三个 Lua 函数。懂得查阅文档是很关键的。Lua 并不是一个复杂的语言,可以通过 Lua 文档网站 很方便的获取关键字以及函数的用法。

下面是一个练习题。

假设你想编写一个 Lua 脚本来统计句子中的单词数量。与众多的编程挑战一样,有许多方法可以解决这个问题,假设你在 Lua 文档中找到的第一个相关的函数是 string.gmatch,此函数可以搜索字符串中的特定字符。单词通常通过空格分隔开来,所以你决定计算空格数并加 1 来作为单词的数量。

下面是实现的代码:

function wc(words,delimiter)
  count=1
  for w in string.gmatch(words,delimiter) do
    count = count + 1
  end

  return count
end

下面是这个样例代码的解释:

  • function:这是声明函数开始的关键字。自定义函数的工作方式与内置函数(如 printstring.len)基本相同。
  • wordsdelimiter:这是函数运行所需的参数。正如 print('hello') 当中,hello 是一个参数。
  • counter:一个变量,且被初始化为 1
  • for:在循环中使用 string.gmatch 作为迭代器遍历 words,并且在其中搜索delimiter
  • count = count + 1:当搜索到了 delimiter,则对 count 进行自增 1 的操作。
  • endfor 循环的结束关键字。
  • return count:这个函数输出(或返回)count 变量的内容。
  • end:函数结束的关键字。

现在你已经创建了一个函数,你可以使用它。需要记住,函数不会自动运行,而是等待你在代码中调用它。

将下面的代码写入文件并保存为 words.lua

function wc(words,delimiter)
  count=1
  for w in string.gmatch(words,delimiter) do
    count = count + 1
  end
  return count
end

result = wc('zombie apocalypse',' ')
print(result)

result = wc('ice cream sandwich',' ')
print(result)

result = wc('can you find the bug? ',' ')
print(result)

你现在创建了一个 Lua 脚本。你可以使用 Lua 运行它。随后你会发现统计单词的结果有些问题:

$ lua ./words.lua
2
3
6

你也许已经注意到了最后一个句子的单词统计不正确,因为在句子的最后带有一个额外的空格。Lua正确的检测到了空格,并将其计入 count 中,但是单子的统计是错误的,因为有个空格并没有作为单词的分隔出现。我把这个问题留给你来解决,或者去发现其他方法,即使方法不太理想。编程中有很多需要思考的地方。有时是纯粹学术性的思考,有时也许是应用是否运训正常的思考。

学习 Lua

Lua 是一个有趣且强大的语言,随着版本的推进,功能愈发的强大,开发者群体也在不断的增长。你可以将 Lua 作为实用性的个人脚本使用,或者在工作中使用,或者仅仅是体验一下一个新的语言。尝试一下,看看 Lua 能给你带来什么吧。

(LCTT 译注:顺便问一句,你知道 “Lua” 怎么发音吗? ?)


via: https://opensource.com/article/22/11/lua-worth-learning

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

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

使用 Lua 配置持久化应用设置。

不是所有的应用都需要配置文件;对很多应用来说,在启动时变得焕然一新对它们更有利。例如,简单的工具就极少需要偏好项和设置在使用过程中保持稳定不变。然而,当你编写一个复杂的应用程序时,如果能让用户设置与应用的交互方式,以及应用与系统交互的方式会很不错。这就是配置文件用来做的事情。本文将讨论一些利用 Lua 进行持久化配置的方法。

选择一种格式

关于配置文件很重要的两点是一致性和可预见性。你不会希望为了保存用户偏好项,将信息转储到文件中,然后再花几天去编码实现“逆向工程”,处理最后出现在文件里的随机信息。

这里用一些常用的 配置文件格式。Lua 有一些库可以处理大多数常用的配置格式;在本文中,我会采用 INI 格式。

安装库

Lua 库的核心仓库是 Luarocks.org。你可以在这个网站搜索库,或者你可以安装并使用 luarocks 终端命令。

Linux 环境中,你可以从发行版的软件仓库中下载它,例如:

$ sudo dnf install luarocks

在 macOS 上,请使用 MacPorts 或者 Homebrew。在 Windows 上,请使用 Chocolatey

luarocks 安装后,你可以使用 search 子命令来搜索一个恰当的库。如果你不知道库的名字,可以通过关键词来搜索这个库,例如 ini、xml或者json,这取决于你想要用这个库做什么。打个比方,你可以搜索inifile`, 这个库被我用来解析 INI 格式的文本文件。

$ luarocks search inifile
Search results:
inifile
 1.0-2 (rockspec) - https://luarocks.org
 1.0-2 (src) - https://luarocks.org
 1.0-1 (rockspec) - https://luarocks.org
 [...]

一个开发者容易犯的错误是在系统上安装了这个库却忘了把它和应用打包。这会给没有安装这个库的用户带来麻烦。为了防止这个问题发生,可以使用 --tree 选项将它安装在项目的本地文件夹中。如果你没有这个项目文件夹,那就先创建这个文件夹再安装库:

$ mkdir demo
$ cd demo
$ luarocks install --tree=local inifile

--tree 选项指示 luarocks 创建一个新文件夹并在其中安装你的库,例如这个例子中的 local 文件夹。 使用这个简单的技巧,你可以将所有你项目要使用的依赖项直接安装到项目文件夹中。

配置代码

首先,在一个名 myconfig.ini 的文件中创建一些 INI 数据。

[example]
name=Tux
species=penguin
enabled=false

[demo]
name=Beastie
species=demon
enabled=false

将这个文件保存到你的主目录下,命名为 myconfig.ini, 不要 存到项目文件夹下。你通常会希望配置文件独立于你的文件存在,这样当用户卸载你的应用时,使用应用时产生的数据可以保存在系统中。有些用户会删除不重要的配置文件,但大多数不会。最终,如果他们要重装这个应用,还会保留着所有的用户偏好项。

配置文件的位置以技术来说并不重要,但每一个操作系统都有存储它们的特定或者默认的路径。在 Linux 中,这个路径由 Freedesktop 规范 指定。它规定配置文件被保存在一个名为 ~/.config 的隐藏文件夹中。为了操作时更加清晰明确,可以在主目录下存储配置文件,以便于使用和寻找。

创建第二个文件,命名为 main.lua,并在你喜欢的文本编辑器中打开它。

首先,你必须告诉 Lua 你将想要使用的附加库放置在哪里。package.path 变量决定了 Lua 到哪里去寻找这些库。你可以从终端中查看 Lua 默认的包地址:

$ Lua
> print(package.path)
./?.lua;/usr/share/lua/5.3/?.lua;/usr/share/lua/5.3/?/init.lua;/usr/lib64/lua/5.3/?.lua;/usr/lib64/lua/5.3/?/init.lua

在你的 Lua 代码中,将你本地库的路径添加到 package.path 中:

package.path = package.path .. ';local/share/lua/5.3/?.lua

使用 Lua 解析 INI 文件

当包的位置确定以后,下一件事就是引入 inifile 库并处理一些操作系统逻辑。即使这是一个很简单的应用,代码也需要从操作系统获取到用户主目录的路径,并建立在必要时将文件系统路径返回给操作系统的通信方式。

package.path = package.path .. ';local/share/lua/5.3/?.lua
inifile = require('inifile')

-- find home directory
home = os.getenv('HOME')

-- detect path separator
-- returns '/' for Linux and Mac
-- and '\' for Windows
d = package.config:sub(1,1)

现在你可使用 inifile 来从配置文件解析数据到 Lua 表中。一旦这些数据被导入进表中,你可以像查询其他的 Lua 表一样查询它。

-- parse the INI file and
-- put values into a table called conf
conf = inifile.parse(home .. d .. 'myconfig.ini')

-- print the data for review
print(conf['example']['name'])
print(conf['example']['species'])
print(conf['example']['enabled'])

在终端中运行代码可以看见结果:

$ lua ./main.lua
Tux
penguin
false

这看起来是正确的。试试在 demo 块中执行同样的操作。

使用 INI 格式存储数据

不是所有用来解析的库都会读写数据(通常被称为 \_编码 和 解码),但是 inifile 会这样做。这意味着你可以使用它对配置文件进行修改。

为了改变配置文件中的值,你可以对被解析的表中的变量进行设置,然后把表重写回配置文件中。

-- set enabled to true
conf['example']['enabled'] = true
conf['demo']['enabled'] = true

-- save the change
inifile.save(home .. d .. 'myconfig.ini', conf)

现在再来看看配置文件:

$ cat ~/myconfig.ini
[example]
name=Tux
species=penguin
enabled=true

[demo]
name=Beastie
species=demon
enabled=true

配置文件

按照用户的设想来存储数据对程序来说是至关重要的。幸运的是,这对工程师来说是一个很常规的任务,大多数工作可能早已被完成了。只要找到一个好用的库完成开放格式下编码和解码,你就能为用户提供一致且持续的体验。

以下是完整的演示代码,可供参考。

package.path = package.path .. ';local/share/lua/5.3/?.lua'
inifile = require('inifile')

-- find home directory
home = os.getenv('HOME')

-- detect path separator
-- returns '/' for Linux and Mac
-- and '\' for Windows
d = package.config:sub(1,1)

-- parse the INI file and
-- put values into a table called conf
conf = inifile.parse(home .. d .. 'myconfig.ini')

-- print the data for review
print(conf['example']['name'])
print(conf['example']['species'])
print(conf['example']['enabled'])

-- enable Tux
conf['example']['enabled'] = true

-- save the change
inifile.save(home .. d .. 'myconfig.ini', conf)

via: https://opensource.com/article/21/6/parsing-config-files-lua

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

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

awk 和 Groovy 相辅相成,可以创建强大、有用的脚本。

最近我写了一个使用 Groovy 脚本来清理我的音乐文件中的标签的系列。我开发了一个 框架,可以识别我的音乐目录的结构,并使用它来遍历音乐文件。在该系列的最后一篇文章中,我从框架中分离出一个实用类,我的脚本可以用它来处理文件。

这个独立的框架让我想起了很多 awk 的工作方式。对于那些不熟悉 awk 的人来说,你学习下这本电子书:

《awk 实用指南》

我从 1984 年开始大量使用 awk,当时我们的小公司买了第一台“真正的”计算机,它运行的是 System V Unix。对我来说,awk 是非常完美的:它有 关联内存 associative memory ——将数组视为由字符串而不是数字来索引的。它内置了正则表达式,似乎专为处理数据而生,尤其是在处理数据列时,而且结构紧凑,易于学习。最后,它非常适合在 Unix 工作流使用,从标准输入或文件中读取数据并写入到输出,数据不需要经过其他的转换就出现在了输入流中。

说 awk 是我日常计算工具箱中的一个重要部分一点也不为过。然而,在我使用 awk 的过程中,有几件事让我感到不满意。

可能主要的问题是 awk 善于处理以分隔字段呈现的数据,但很奇怪它不善于处理 CSV 文件,因为 CSV 文件的字段被引号包围时可以嵌入逗号分隔符。另外,自 awk 发明以来,正则表达式已经有了很大的发展,我们需要记住两套正则表达式的语法规则,而这并不利于编写无 bug 的代码。一套这样的规则已经很糟糕了

由于 awk 是一门简洁的语言,因此它缺少很多我认为有用的东西,比如更丰富的基础类型、结构体、switch 语句等等。

相比之下,Groovy 拥有这些能力:可以使用 OpenCSV 库,它很擅长处理 CSV 文件、Java 正则表达式和强大的匹配运算符、丰富的基础类型、类、switch 语句等等。

Groovy 所缺乏的是简单的面向管道的概念,即把要处理数据作为一个传入的流,以及把处理过的数据作为一个传出的流。

但我的音乐目录处理框架让我想到,也许我可以创建一个 Groovy 版本的 awk “引擎”。这就是我写这篇文章的目的。

安装 Java 和 Groovy

Groovy 是基于 Java 的,需要先安装 Java。最新的、合适的 Java 和 Groovy 版本可能都在你的 Linux 发行版的软件库中。Groovy 也可以按照 Groovy 主页 上的说明进行安装。对于 Linux 用户来说,一个不错的选择是 SDKMan,它可以用来获得多个版本的 Java、Groovy 和其他许多相关工具。在这篇文章中,我使用的是 SDK 的版本:

  • Java:OpenJDK 11 的 11.0.12 的开源版本
  • Groovy:3.0.8

使用 Groovy 创建 awk

这里的基本想法是将打开一个或多个文件进行处理、将每行分割成字段、以及提供对数据流的访问等复杂情况封装在三个部分:

  • 在处理数据之前
  • 在处理每行数据时
  • 在处理完所有数据之后

我并不打算用 Groovy 来取代 awk。相反,我只是在努力实现我的典型用例,那就是:

  • 使用一个脚本文件而不是在命令行写代码
  • 处理一个或多个输入文件
  • 设置默认的分隔符为 |,并基于这个分隔符分割所有行
  • 使用 OpenCSV 完成分割工作(awk 做不到)

框架类

下面是用 Groovy 类实现的 “awk 引擎”:

@Grab('com.opencsv:opencsv:5.6')
import com.opencsv.CSVReader
public class AwkEngine {
    // With admiration and respect for
    //     Alfred Aho
    //     Peter Weinberger
    //     Brian Kernighan
    // Thank you for the enormous value
    // brought my job by the awk
    // programming language
    Closure onBegin
    Closure onEachLine
    Closure onEnd
    private String fieldSeparator
    private boolean isFirstLineHeader
    private ArrayList<String> fileNameList
    public AwkEngine(args) {
        this.fileNameList = args
        this.fieldSeparator = "|"
        this.isFirstLineHeader = false
    }
    public AwkEngine(args, fieldSeparator) {
        this.fileNameList = args
        this.fieldSeparator = fieldSeparator
        this.isFirstLineHeader = false
    }
    public AwkEngine(args, fieldSeparator, isFirstLineHeader) {
        this.fileNameList = args
        this.fieldSeparator = fieldSeparator
        this.isFirstLineHeader = isFirstLineHeader
    }
    public void go() {
        this.onBegin()
        int recordNumber = 0
        fileNameList.each { fileName ->
            int fileRecordNumber = 0
            new File(fileName).withReader { reader ->
                def csvReader = new CSVReader(reader,
                    this.fieldSeparator.charAt(0))
                if (isFirstLineHeader) {
                    def csvFieldNames = csvReader.readNext() as
                        ArrayList<String>
                    csvReader.each { fieldsByNumber ->
                        def fieldsByName = csvFieldNames.
                            withIndex().
                            collectEntries { name, index ->
                                [name, fieldsByNumber[index]]
                            }
                        this.onEachLine(fieldsByName,
                                recordNumber, fileName,
                                fileRecordNumber)
                        recordNumber++
                        fileRecordNumber++
                    }
                } else {
                    csvReader.each { fieldsByNumber ->
                        this.onEachLine(fieldsByNumber,
                            recordNumber, fileName,
                            fileRecordNumber)
                        recordNumber++
                        fileRecordNumber++
                    }
                }
            }
        }
        this.onEnd()
    }
}

虽然这看起来是相当多的代码,但许多行是因为太长换行了(例如,通常你会合并第 38 行和第 39 行,第 41 行和第 42 行,等等)。让我们逐行看一下。

第 1 行使用 @Grab 注解从 Maven Central 获取 OpenCSV 库的 5.6 本周。不需要 XML。

第 2 行我引入了 OpenCSV 的 CSVReader

第 3 行,像 Java 一样,我声明了一个 public 实用类 AwkEngine

第 11-13 行定义了脚本所使用的 Groovy 闭包实例,作为该类的钩子。像任何 Groovy 类一样,它们“默认是 public”,但 Groovy 将这些字段创建为 private,并对其进行外部引用(使用 Groovy 提供的 getter 和 setter 方法)。我将在下面的示例脚本中进一步解释这个问题。

第 14-16 行声明了 private 字段 —— 字段分隔符,一个指示文件第一行是否为标题的标志,以及一个文件名的列表。

第 17-31 行定义了三个构造函数。第一个接收命令行参数。第二个接收字段的分隔符。第三个接收指示第一行是否为标题的标志。

第 31-67 行定义了引擎本身,即 go() 方法。

第 33 行调用了 onBegin() 闭包(等同于 awk 的 BEGIN {} 语句)。

第 34 行初始化流的 recordNumber(等同于 awk 的 NR 变量)为 0(注意我这里是从 00 而不是 1 开始的)。

第 35-65 行使用 each {} 来循环处理列表中的文件。

第 36 行初始化文件的 fileRecordNumber(等同于 awk 的 FNR 变量)为 0(从 0 而不是 1 开始)。

第 37-64 行获取一个文件对应的 Reader 实例并处理它。

第 38-39 行获取一个 CSVReader 实例。

第 40 行检测第一行是否为标题。

如果第一行是标题,那么在 41-42 行会从第一行获取字段的标题名字列表。

第 43-54 行处理其他的行。

第 44-48 行把字段的值复制到 name:value 的映射中。

第 49-51 行调用 onEachLine() 闭包(等同于 awk 程序 BEGIN {}END {} 之间的部分,不同的是,这里不能输入执行条件),传入的参数是 name:value 映射、处理过的总行数、文件名和该文件处理过的行数。

第 52-53 行是处理过的总行数和该文件处理过的行数的自增。

如果第一行不是标题:

第 56-62 行处理每一行。

第 57-59 调用 onEachLine() 闭包,传入的参数是字段值的数组、处理过的总行数、文件名和该文件处理过的行数。

第 60-61 行是处理过的总行数和该文件处理过的行数的自增。

第 66 行调用 onEnd() 闭包(等同于 awk 的 END {})。

这就是该框架的内容。现在你可以编译它:

$ groovyc AwkEngine.groovy

一点注释:

如果传入的参数不是一个文件,编译就会失败,并出现标准的 Groovy 堆栈跟踪,看起来像这样:

Caught: java.io.FileNotFoundException: not-a-file (No such file or directory)
java.io.FileNotFoundException: not-a-file (No such file or directory)
at AwkEngine$_go_closure1.doCall(AwkEngine.groovy:46)

OpenCSV 可能会返回 String[] 值,不像 Groovy 中的 List 值那样方便(例如,数组没有 each {})。第 41-42 行将标题字段值数组转换为 list,因此第 57 行的 fieldsByNumber 可能也应该转换为 list。

在脚本中使用这个框架

下面是一个使用 AwkEngine 来处理 /etc/group 之类由冒号分隔并没有标题的文件的简单脚本:

def ae = new AwkEngine(args, ':')
int lineCount = 0
ae.onBegin = {
    println “in begin”
}
ae.onEachLine = { fields, recordNumber, fileName, fileRecordNumber ->
    if (lineCount < 10)
        println “fileName $fileName fields $fields”
    lineCount++
}
ae.onEnd = {
    println “in end”
    println “$lineCount line(s) read”
}
ae.go()

第 1 行 调用的有两个参数的构造函数,传入了参数列表,并定义冒号为分隔符。

第 2 行定义一个脚本级的变量 lineCount,用来记录处理过的行数(注意,Groovy 闭包不要求定义在外部的变量为 final)。

第 3-5 行定义 onBegin() 闭包,在标准输出中打印出 “in begin” 字符串。

第 6-10 行定义 onEachLine() 闭包,打印文件名和前 10 行字段,无论是否为前 10 行,处理过的总行数 lineCount 都会自增。

第 11-14 行定义 onEnd() 闭包,打印 “in end” 字符串和处理过的总行数。

第 15 行运行脚本,使用 AwkEngine

像下面一样运行一下脚本:

$ groovy Test1Awk.groovy /etc/group
in begin
fileName /etc/group fields [root, x, 0, ]
fileName /etc/group fields [daemon, x, 1, ]
fileName /etc/group fields [bin, x, 2, ]
fileName /etc/group fields [sys, x, 3, ]
fileName /etc/group fields [adm, x, 4, syslog,clh]
fileName /etc/group fields [tty, x, 5, ]
fileName /etc/group fields [disk, x, 6, ]
fileName /etc/group fields [lp, x, 7, ]
fileName /etc/group fields [mail, x, 8, ]
fileName /etc/group fields [news, x, 9, ]
in end
78 line(s) read
$

当然,编译框架类生成的 .class 文件需要在 classpath 中,这样才能正常运行。通常你可以用 jar 把这些 class 文件打包起来。

我非常喜欢 Groovy 对行为委托的支持,这在其他语言中需要各种诡异的手段。许多年来,Java 需要匿名类和相当多的额外代码。Lambda 已经在很大程度上解决了这个问题,但它们仍然不能引用其范围之外的非 final 变量。

下面是另一个更有趣的脚本,它很容易让人想起我对 awk 的典型使用方式:

def ae = new AwkEngine(args, ';', true)
ae.onBegin = {
    // nothing to do here
}
def regionCount = [:]
    ae.onEachLine = { fields, recordNumber, fileName, fileRecordNumber ->
        regionCount[fields.REGION] =
            (regionCount.containsKey(fields.REGION) ?
                regionCount[fields.REGION] : 0) +
            (fields.PERSONAS as Integer)
}
ae.onEnd = {
    regionCount.each { region, population ->
        println “Region $region population $population”
    }
}
ae.go()

第 1 行调用了三个函数的构造方法,true 表示这是“真正的 CSV” 文件,第一行为标题。由于它是西班牙语的文件,因此它的逗号表示数字的,标准的分隔符是分号。

第 2-4 行定义 onBegin() 闭包,这里什么也不做。

第 5 行定义一个(空的)LinkedHashmap,键是 String 类型,值是 Integer 类型。数据文件来自于智利最近的人口普查,你要在这个脚本中计算出智利每个地区的人口数量。

第 6-11 行处理文件中的行(加上标题一共有 180,500 行)—— 请注意在这个案例中,由于你定义 第 1 行为 CSV 列的标题,因此 fields 参数会成为 LinkedHashMap<String,String> 实例。

第 7-10 行是 regionCount 映射计数增加,键是 REGION 字段的值,值是 PERSONAS 字段的值 —— 请注意,与 awk 不同,在 Groovy 中你不能在赋值操作的右边使用一个不存在的映射而期望得到空值或零值。

第 12-16 行,打印每个地区的人口数量。

第 17 行运行脚本,调用 AwkEngine

像下面一样运行一下脚本:

$ groovy Test2Awk.groovy ~/Downloads/Censo2017/ManzanaEntidad_CSV/Censo*csv
Region 1 population 330558
Region 2 population 607534
Region 3 population 286168
Region 4 population 757586
Region 5 population 1815902
Region 6 population 914555
Region 7 population 1044950
Region 8 population 1556805
Region 16 population 480609
Region 9 population 957224
Region 10 population 828708
Region 11 population 103158
Region 12 population 166533
Region 13 population 7112808
Region 14 population 384837
Region 15 population 226068
$

以上为全部内容。对于那些喜欢 awk 但又希望得到更多的东西的人,我希望你能喜欢这种 Groovy 的方法。


via: https://opensource.com/article/22/9/awk-groovy

作者:Chris Hermansen 选题:lkxed 译者:lxbwolf 校对:wxy

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

git log 命令是 Git 中一个很重要的查看提交记录的工具,它也是人们喜欢使用 Git 的原因之一。

git log 命令能够让你了解到更多关于贡献者 提交 commit 的记录。使用 git log 的一种方式是按日期查看提交记录 。要查看在指定日期或日期范围内创建的 Git 存储库中的提交记录,请使用带有选项 --since--until 或者同时使用以上两个选项的 git log 命令。

首先,进入你要查看的分支(例如,main 分支):

$ git checkout main

接下来,你可以使用以下命令,来显示当前日期(即今天)的提交记录:

$ git log --oneline --since="yesterday"

仅显示某一特定用户(例如,用户 Agil)在今天的提交记录:

$ git log --oneline --since="yesterday" --author="Agil"

还可以显示在某一日期范围内的提交记录。使用以下命令,显示在任意两个日期之间(例如,2022 年 4 月 22 日至 2022 年 4 月 24 日)的提交记录:

$ git log --oneline --since="2022-04-22" --until="2022-04-24"

在上面这个例子中,会输出 2022 年 4 月 22 日至 2022 年 4 月 24 日期间,不包括 2022 年 4 月 22 日的所有提交记录。如果你想要包括 2022 年 4 月 22 日的提交记录,请将命令中的 2022-04-22 替换为 2022-04-21

运行以下命令,能够显示某一特定用户(例如,用户 Agil)在两个指定的日期之间的提交记录:

$ git log --oneline --since="2022-04-22" --until="2022-04-24" --author="Agil"

总结

Git 有很多优点,其中一个优点就是 Git 让你能够收集你项目的相关数据。git log 命令是一个重要的查看提交记录的工具,也是人们喜欢使用 Git 的原因之一!


via: https://opensource.com/article/22/10/git-log-command

作者:Agil Antony 选题:lkxed 译者:chai001125 校对:wxy

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

用这个方便的捕鼠器比喻来理解编译代码。

源代码必须要经过编译才能够运行程序,而对于开源软件,每个人都可以获取源代码。无论你是自己编写了代码,想要编译和运行它,还是下载了某人的项目来尝试它,了解如何通过 编译器 处理源代码,以及编译器如何处理这些代码,这都很有用。

创建一个更好的捕鼠器

一般情况我们不会将一个捕鼠器比作电脑,但不管你信不信,它确实与你正在使用的设备(手机或电脑)的 CPU 有一些相似之处。经典的捕鼠器(我说的不是 ?)有两种状态:打开或者释放。你可以认为 打开 是将捕鼠器设置好准备捕获老鼠,以及 释放 是捕鼠器被老鼠触发。某种意义上来说,捕鼠器就像是一台有鼠标的电脑。你可以想象一下这个代码,用一种虚构的语言来描述这个过程:

if mousetrap == 0 then
  There's a mouse!
else
  There's no mouse yet.
end

换句话说,你可以基于捕鼠器的状态发现是否有老鼠(数据)。当然,捕鼠器不是万无一失的,有可能有一只老鼠在捕鼠器旁边,由于老鼠还没有触发捕鼠器,所以它的状态还是 打开 的。因此该程序可以进行改进,这都是非常典型的。

开关

总的来说,捕鼠器就是一个开关。你会在家里使用开关打开灯。可以从开关中获得许多信息。比如,人们会从你家灯的状态了解到你是否在家。

你可以根据邻居家灯的状态来改变行为。如果邻居家所有的灯都熄灭了,那么请关掉你大声的音乐,因为人们可能已经上床睡觉了。

CPU 也使用这样的逻辑,只不过乘以几个数量级,缩小到了微观级别。当 CPU 在特定寄存器上接收到电信号时,可以触发其他一些寄存器,然后触发另一个,以此类推。如果这些寄存器有特定的意义,那么就可以通信。也许激活同一主板上某处的芯片,或者使 LED 亮起,或者改变屏幕上的像素颜色。

种瓜得瓜,种豆得豆。如果你真的想在多个位置而不是仅限于一处发现老鼠,但是你只有一个捕鼠器,那你应该开发一个应用才行。使用网络摄像头和一些基本的图像识别软件,你可以建立空厨房的模型,然后扫描变化。当老鼠进入厨房,在原先没有老鼠的图像上会有像素的变化。记录下这些数据,如果有无人机可以追踪老鼠并捕获会更好,这样就可以将老鼠赶出厨房了。这时,你通过打开和关闭信号的魔法,创造了一个更好的捕鼠器。

编译器

代码编译器将人们可阅读的代码转换成 CPU 可以理解的机器语言。这是非常复杂的过程,因为 CPU 非常复杂(甚至比捕鼠器更加复杂),同时因为该过程比严格“需要”的更加灵活。并不是所有的编译器都很灵活。有一些编译器只有一个目标,它们只会处理特定格式的代码文件,处理过程也因此而简单明了。

幸运的是,现代的通用编译器并不简单。它们允许你编写不同语言的代码,也允许你用不同的方式链接库文件,并且可以生成运行在不同架构上的文件。GNU 编译器集合(GCC)的 gcc 编译器 --help 会输出超过 50 行的选项,LLVM 的 clang 编译器的 --help 输出超过 1000 行。GCC 指导手册的字数超过 10 万。

当你在编译代码时会有很多选项。

当然,大多数人并不需要知道所有的选项。我从未读过 GCC 的手册页,因为它们是针对 Objective-C、Fortran 以及我从未听说过的芯片架构的。不过我重视它将代码编译为不同的架构 —— 64 位或者 32 位 —— 的能力,以及在其他行业已经落后的计算机上运行开源软件的能力。

编译生命周期

同样重要的是,理解编译代码的不同阶段。这是一个简单的 C 语言程序的生命周期:

  1. 带有宏定义的 C 源代码 .c 文件,用 cpp 预处理为 .i 文件。
  2. 扩展了宏定义的 C 源代码 .i 文件,会被 gcc 转译成 .s 文件。
  3. 以汇编语言写的文本文件 .s 文件被汇编为目标 .o 文件。
  4. 带有 CPU 指令的二进制目标代码,以及其他目标文件和库 *.o 文件,以内存区域无关的偏移量,使用 ld 链接以生成可执行文件。
  5. 最终的二进制文件要么包含所有需要的目标,要么设置以动态链接库 *.so 文件加载。

你可以试试这个简单示例(可能需要对库路径做一些调整):

$ cat << EOF >> hello.c
 #include
 int main(void)
 { printf("hello world\n");
   return 0; }
   EOF
$ cpp hello.c > hello.i
$ gcc -S hello.i
$ as -o hello.o hello.s
$ ld -static -o hello \
  -L/usr/lib64/gcc/x86_64-slackware-linux/5.5.0/ \
  /usr/lib64/crt1.o /usr/lib64/crti.o hello.o \
  /usr/lib64/crtn.o  --start-group -lc -lgcc \
  -lgcc_eh --end-group
$ ./hello
hello world

可获得的知识

计算机已经变得非常强大,并且用户友好。请不要走向这两种可能的极端中的任何一种:计算机不像捕鼠器和电灯开关那么简单,但它们也不是无法理解的。你可以了解编译代码、如何链接以及针对不同架构进行编译。一旦你知道了,你就可以更好地调试代码。你可以理解你下载的代码,甚至可以修复其中的一两个错误。同时从理论上来讲,你可以建造一个更好的捕鼠器,或者用捕鼠器造一个 CPU。由你决定。


via: https://opensource.com/article/22/10/compiling-code

作者:Alan Smithee 选题:lkxed 译者:Donkey-Hao 校对:wxy

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

在 Kubernetes 中运行无服务器函数时,实现更快的启动速度和更小的内存占用。

 title=

由于运行上千个应用程序 容器荚 Pod 所耗费的资源多,令它实现较少工作节点和资源占用所需成本也较高,所以在使用 Kubernetes 时,快速启动和较少的内存占用是至关重要的。在 Kubernetes 平台运行容器化微服务时,内存占用是比吞吐量更重要的考量因素,这是因为:

  • 由于需要持续运行,所以耗费资源更多(不同于 CPU 占用)
  • 微服务令开销成本成倍增加
  • 一个单体应用程序变为若干个微服务的情况(例如 20 个微服务占用的存储空间约有 20GB)

这些情况极大影响了无服务器函数的发展和 Java 部署模型。到目前为止,许多企业开发人员选择 Go、Python 或 Node.js 这些替代方案来解决性能瓶颈,直到出现了 Quarkus 这种基于 kubernetes 的原生 Java 堆栈,才有所改观。本文介绍如何在使用了 Quarkus 的 kubernetes 平台上进行性能优化,以便运行无服务器函数。

容器优先的设计理念

由于 Java 生态系统中传统的框架都要进行框架的初始化,包括配置文件的处理、classpath 的扫描、类加载、注解的处理以及构建元模型,这些过程都是必不可少的,所以它们都比较耗费资源。如果使用了几种不同的框架,所耗费的资源也是成倍增加。

Quarkus 通过“ 左移 shifting left ”,把所有的资源开销大的操作都转移到构建阶段,解决了这些 Java 性能问题。在构建阶段进行代码和框架分析、字节码转换和动态元模型生成,而且只有一次,结果是:运行时可执行文件经过高度优化,启动非常快,不需要经过那些传统的启动过程,全过程只在构建阶段执行一次。

 title=

更重要的是:Quarkus 支持构建原生可执行文件,它具有良好性能,包括快速启动和极小的 驻留集大小 resident set size (RSS)内存占用,跟传统的云原生 Java 栈相比,具备即时扩展的能力和高密度的内存利用。

 title=

这里有个例子,展示如何使用 Quarkus 将一个 Java 无服务器 项目构建为本地可执行文件。

1、使用 Quarkus 创建无服务器 Maven 项目

以下命令生成一个 Quarkus 项目,(例如 quarkus-serverless-native)以此创建一个简单的函数:

$ mvn io.quarkus:quarkus-maven-plugin:1.13.4.Final:create \
       -DprojectGroupId=org.acme \
       -DprojectArtifactId=quarkus-serverless-native \
       -DclassName="org.acme.getting.started.GreetingResource"

2、构建一个本地可执行文件

你需要使用 GraalVM 为 Java 程序构建一个本地可执行文件。你可以选择 GraalVM 的任何发行版,例如 Oracle GraalVM Community Edition (CE)Mandrel(Oracle GraalVM CE 的下游发行版)。Mandrel 是为支持 OpenJDK 11 上的 Quarkus-native 可执行文件的构建而设计的。

打开 pom.xml,你将发现其中的 native 设置。你将使用它来构建本地可执行文件。

<profiles>
    <profile>
        <id>native</id>
        <properties>
            <quarkus.package.type>native</quarkus.package.type>
        </properties>
    </profile>
</profiles>
注意: 你可以在本地安装 GraalVM 或 Mandrel 发行版。你也可以下载 Mandrel 容器映像来构建它(像我那样),因此你还需要在本地运行一个容器引擎(例如 Docker)。

假设你已经打开了容器运行时,此时需要运行一下 Maven 命令:

使用 Docker 作为容器引擎:

$ ./mvnw package -Pnative \
  -Dquarkus.native.container-build=true \
  -Dquarkus.native.container-runtime=docker

使用 Podman 作为容器引擎:

$ ./mvnw package -Pnative \
  -Dquarkus.native.container-build=true \
  -Dquarkus.native.container-runtime=podman

输出信息结尾应当是 BUILD SUCCESS

 title=

不借助 JVM 直接运行本地可执行文件:

$ target/quarkus-serverless-native-1.0.0-SNAPSHOT-runner

输出信息类似于:

__  ____  __  _____   ___  __ ____  ______
 --/ __ \/ / / / _ | / _ \/ //_/ / / / __/
 -/ /_/ / /_/ / __ |/ , _/ ,< / /_/ /\ \  
--\___\_\____/_/ |_/_/|_/_/|_|\____/___/  
INFO  [io.quarkus] (main) quarkus-serverless-native 1.0.0-SNAPSHOT native
(powered by Quarkus xx.xx.xx.) Started in 0.019s. Listening on: http://0.0.0.0:8080
INFO [io.quarkus] (main) Profile prod activated.
INFO [io.quarkus] (main) Installed features: [cdi, kubernetes, resteasy]

简直是超音速!启动只花了 19 毫秒。你的运行时间可能稍有不同。

使用 Linux 的 ps 工具检测一下,结果内存占用还是很低。检测的方法是:在应用程序运行期间,另外打开一个终端,运行如下命令:

$ ps -o pid,rss,command -p $(pgrep -f runner)

输出结果类似于:

  PID    RSS COMMAND
10246  11360 target/quarkus-serverless-native-1.0.0-SNAPSHOT-runner

该进程只占 11MB 内存。非常小!

注意: 各种应用程序(包括 Quarkus)的驻留集大小和内存占用,都因运行环境而异,并随着应用程序载入而上升。

你也可以使用 REST API 访问这个函数。输出结果应该是 Hello RESTEasy:

$ curl localhost:8080/hello
Hello RESTEasy

3、把函数部署到 Knative 服务

如果你还没有创建命名空间,现在就在 OKD(OpenShift Kubernetes 发行版)创建一个命名空间(例如 quarkus-serverless-native),进而把这个本地可执行文件部署为无服务器函数。然后添加 quarkus-openshift 扩展:

$ ./mvnw -q quarkus:add-extension -Dextensions="openshift"

src/main/resources/application.properties 文件中添加以下内容,配置 Knative 和 Kubernetes 的相关资源:

quarkus.container-image.group=quarkus-serverless-native
quarkus.container-image.registry=image-registry.openshift-image-registry.svc:5000
quarkus.native.container-build=true
quarkus.kubernetes-client.trust-certs=true
quarkus.kubernetes.deployment-target=knative
quarkus.kubernetes.deploy=true
quarkus.openshift.build-strategy=docker

构建本地可执行文件,并把它直接部署到 OKD 集群:

$ ./mvnw clean package -Pnative
注意: 提前使用 oc login 命令,确保登录的是正确的项目(例如 quarkus-serverless-native)。

输出信息结尾应当是 BUILD SUCCESS。完成一个本地二进制文件的构建并部署为 Knative 服务需要花费几分钟。成功创建服务后,使用 kubectloc 命令工具,可以查看 Knative 服务和版本信息:

$ kubectl get ksvc
NAME                        URL   [...]
quarkus-serverless-native   http://quarkus-serverless-native-[...].SUBDOMAIN  True

$ kubectl get rev
NAME                              CONFIG NAME                 K8S SERVICE NAME                  GENERATION   READY   REASON
quarkus-serverless-native-00001   quarkus-serverless-native   quarkus-serverless-native-00001   1            True

4、访问本地可执行函数

运行 kubectl 命令,搜索无服务器函数的节点:

$ kubectl get rt/quarkus-serverless-native

输出信息类似于:

NAME                         URL                                                                                                          READY   REASON
quarkus-serverless-native   http://quarkus-serverless-restapi-quarkus-serverless-native.SUBDOMAIN   True

curl 命令访问上述信息中的 URL 字段:

$ curl http://quarkus-serverless-restapi-quarkus-serverless-native.SUBDOMAIN/hello

过了不超过一秒钟,你也会得到跟本地操作一样的结果:

Hello RESTEasy

当你在 OKD 群集中访问 Quarkus 运行中的节点的日志,你会发现本地可执行文件正在以 Knative 服务的形式运行。

 title=

下一步呢?

你可以借助 GraalVM 发行版优化 Java 无服务器函数,从而在 Knative 中使用 Kubernetes 将它们部署为无服务器函数。Quarkus 支持在普通的微服务中使用简易配置进行性能优化。

本系列的下一篇文章将指导你在不更改代码的情况下跨多个无服务器平台实现可移植函数。

(Daniel Oh, CC BY-SA 4.0)


via: https://opensource.com/article/21/6/java-serverless-functions-kubernetes

作者:Daniel Oh 选题:lujun9972 译者:cool-summer-021 校对:wxy

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