分类 技术 下的文章

在这篇文章中,我们将推荐 5 款可以替代微软 Office 的最佳软件,并从功能、操作难易程度等方面,对它们进行比较。看一看哪款更适合你?

可以说,Office 办公软件是微软开发的最优质的软件之一,受到世界各地用户的青睐,广泛应用于各行各业,当属近几十年来软件市场涌现出来的精品。

不过大家都知道,微软 Office 不仅没有开发适用于 Linux 的版本,而且价格高昂。对于企业用户或者个人用户来说,Office 365 的价格就更贵了,远超普通人能接受的价格水平。

那么,有哪些软件可以替代微软 Office 呢?

这篇文章推荐 5 款可以替代微软 Office 的最佳软件。

LibreOffice

LibreOffice

首先推荐的是 LibreOffice。LibreOffice 是一款自由开源的办公套件,由文档基金会开发维护,支持 Linux、macOS 以及 Windows 系统。

LibreOffice 套件包括表格工具 Calc、文字处理工具 Writer、演示工具 Impress、画图工具 Draw 以及数据库工具 Base。

LibreOffice 办公软件的开发十分很活跃,同时不断提升对微软 Office 的兼容性。如果善加利用,LibreOffice 完全可以取代微软 Office。借助丰富的技术文档和社区资源,用户可以迅速掌握 LibreOffice 的使用方法。

企业用户也可以免费使用 LibreOffice,如果需要用它来完成关键工作,用户也可以购买配置服务和支持服务,相关费用十分低廉。

然而,LibreOffice 不提供像 Outlook 一样的邮箱服务。这可能是它的一个小缺点,不过好在现在的邮箱服务都可以在浏览器上运行。

Google Docs

Google Docs

搜索引擎巨头谷歌为用户免费提供了一套网页版的办公套件 —— Google Docs,其中包括 Docs(文档编辑器)、Sheets(表格程序)、Slides(演示程序)。

用户可以在谷歌云盘中免费创建、打开文档。随时随地,自由存取。Google Docs 界面设计优美,内置工具栏、高级选项、拼写检查、语音输入功能(仅支持 Chrome 浏览器)、加密功能以及云存储服务。谷歌也为 iOS 系统和安卓系统提供了移动端,用户可以在移动设备上轻松打开、编辑文档。

Google Docs 最为人称道的功能在于它的模板。有了这些内置模板,用户可以迅速编辑出一份专业的文档。此外,通过邀请其他谷歌用户,还可以使用多人协作在线编辑功能。

如果你需要更多的功能,可以付费使用 Google Workspace。这是一套全面的整合方案,你可以通过 Google Forms 收集信息,并集成到你的文档和表格中、网站编辑工具 Google Sites、Google 日历等服务,保存为文档。

OnlyOffice

OnlyOffice

OnlyOffice(显示名字为 ONLYOFFICE)是一套自由开源的办公软件,包括文本编辑器、表格工具、演示软件,提供共享文件实时协作编辑、修改痕迹记录查看以及制作可供填写的表格等高级功能。

外观上,OnlyOffice 的功能区模仿了微软 Office 365 功能区的设计风格,能让用户快速上手。此外,OnlyOffice 对微软 Office 文件格式(.docx .xlsx 以及 .pptx)的兼容性更好,方便用户与他人共享文件。

值得一提的是,OnlyOffice 还推出了需要付费使用的企业版本 —— ONLYOFFICE Workspace。该版本增加了一些其他的高级功能,提供即时支持服务,非常适合那些预算紧张但对格式兼容性要求又很高的用户。

ONLYOFFICE Workspace 集成了邮箱客户端、客户关系管理产品、项目管理工具以及日历。总体来说,ONLYOFFICE Workspace 是一款不错的软件,但也有一些不足,如拼写检查、打印预览、页面尺寸以及漏洞等问题。不过也不需要过分担心,你可以在 GitHub 上传错误报告,向开发团队寻求帮助。

Softmaker FreeOffice

FreeOffice

FreeOffice 由 SoftMaker 开发,是一套十分优秀的办公软件,包括 TextMaker(可替代 Word)、PlanMaker(可替代 Excel)以及 Presentations(可替代 PowerPoint)。FreeOffice 提供了两种用户界面:带有功能区选项的现代化界面与带有菜单和工具栏的传统界面,两种界面都十分受欢迎。此外,FreeOffice 还为触控设备提供专有的用户界面与功能。

FreeOffice 对 微软 Office 文档格式的兼容性是很好的,可以完成大部分工作。然而,你在处理开放文档格式(ODT)文件时可能会遇到一点麻烦,因为它的支持有限。

FreeOffice 是一款闭源软件。

WPS Office

WPS Office

还记得金山办公软件吗? 它现在的名字叫做 WPS Office。WPS 取 Word, Presentation 与 Spreadsheets 的首字母组合而成。到今天,WPS Office 已有 30 年的发展历史,是老牌办公软件之一。WPS 作为办公软件,功能齐全,支持移动端在内的各类平台。

WPS 最具特色的功能在于支持实时协作编辑。使用 WPS,团队成员可以同时编辑一份共享文档。WPS 还为用户提供了超过 10 万种文档模板,帮助用户编辑出专业美观的文档与演示文件。

WPS 的标准版本可以免费下载使用,不过有一些高级功能需要付费。

如果你需要额外的功能,比如编辑 PDF 文件、云空间扩容、团队协作以及企业支持,可以考虑付费开通会员,使用 WPS 企业版。

注意,这是一款闭源软件,而且可能会推送广告。(LCTT 译注:该公司内部人士表示,免费的 Linux 版没广告。)该软件由中国金山软件公司开发。

对比表

下表基于功能以及其他细节,对上述 5 款办公软进行对比总结。

产品价格是否开源优势劣势
LibreOffice免费开源免费;跨平台;支持多种语言;完全支持 ODF 文件格式;对 微软 Office 兼容性最好;开发活跃不提供邮箱应用;不提供项目管理功能;数据库基于 Java
Google Docs免费闭源免费;跨平台;良好的文档支持;随时随地存取云文档;完美支持移动端需要网络连接;网络连接导致卡顿或延迟;不提供可供安装的版本
OnlyOffice免费(基础功能)开源用户界面酷似微软 Office;对微软 Office 文件拥有更好的兼容性;云集成;支持插件;跨平台基本功能可能出现问题;云集成服务违反欧盟通用数据保护条例;网页端延迟
FreeOffice免费(基础功能)闭源免费;相较于 LibreOffice 更加轻量;支持触屏;良好的微软 Office 兼容性;跨平台免费版本只包括文档、表格与演示功能;其他产品需要付费;对 ODT 文件格式的支持有限;闭源软件
WPS Office免费闭源良好的微软 Office 兼容性;跨平台;标签界面;支持多语言闭源软件;可能弹出广告

我们推荐

抛开所有这些优势和劣势不管,如果你还不确定哪一款才是最适合你的,我推荐你使用 LibreOffice。因为 LibreOffice 与 TDF 格式前景广阔,开发活跃,在全世界都拥有广泛的支持。LibreOffice 有着庞大的在线知识库,为用户提供丰富的使用技巧。通过在 LibreOffice 中使用 Basic 语言或者 Python 宏,你还可以轻松实现办公自动化。

总结

我希望,我们的推荐能帮助你选择适合自己的可替代微软 Office 的办公软件。 说实话,上述软件没有一个能真正比得上微软 Office。但是并不是每个人都能付得起微软 Office 高昂的费用,我相信以上 5 款软件对这部分人来说会是不错的选择。

一些图片来源:上述软件所属公司


via: https://www.debugpoint.com/2022/03/best-alternatives-microsoft-office-2022/

作者:Arindam 选题:lujun9972 译者:aREversez 校对:wxy

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

在你使用 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中国 荣誉推出

简介

Homebrew 是一个 macOS 的包管理器,用于在 macOS 上安装 UNIX 工具。但是,它也可以在 Linux(和 Windows WSL)上使用。它是用 Ruby 编写的,并提供主机系统(macOS 或 Linux)可能不提供的软件包,因此它在操作系统包管理器之外提供了一个辅助的包管理器。此外,它只以非 root 用户身份在前缀 /home/linuxbrew/.linuxbrew~/.linuxbrew 下安装软件包,不会污染系统路径。这个包管理器在 Fedora Linux 上也适用。在这篇文章中,我将尝试告诉你 Homebrew 与 Fedora Linux 包管理器 dnf 有什么不同,为什么你可能想在 Fedora Linux 上安装和使用它,以及如何安装。

免责声明

你应该经常检查你在系统上安装的软件包和二进制文件。Homebrew 包通常以非 sudoer 用户运行,并工作在专门的前缀的路径下,因此它们不太可能造成破坏或错误配置。然而,所有的安装操作都要自己承担风险。作者和 Fedora 社区不对任何可能直接或间接因遵循这篇文章而造成的损失负责。

Homebrew 如何工作

Homebrew 在底层使用 Ruby 和 Git。它使用特殊的 Ruby 脚本从源代码构建软件,这些脚本被称为 “ 配方 formula ”,看起来像这样(使用 wget 包作为例子):

(LCTT 译注:Homebrew 本身意思是“家酿”,在这个软件中,有各种类似于酿酒的比喻。)

class Wget < Formula
  homepage "https://www.gnu.org/software/wget/"
  url "https://ftp.gnu.org/gnu/wget/wget-1.15.tar.gz"
  sha256 "52126be8cf1bddd7536886e74c053ad7d0ed2aa89b4b630f76785bac21695fcd"

  def install
    system "./configure", "--prefix=#{prefix}"
    system "make", "install"
  end
end

Homebrew 与 dnf 有何不同

Homebrew 是一个包管理器,提供了许多 UNIX 软件工具和包的最新版本,例如 FFmpeg、Composer、Minikube 等。当你想安装一些由于某种原因在 Fedora Linux RPM 仓库中没有的软件包时,它就会证明很有用。所以,它并不能取代 dnf

安装 Homebrew

在开始安装 Homebrew 之前,确保你已经安装了 glibc 和 gcc。这些工具可以在 Fedora 上通过以下方式安装:

sudo dnf groupinstall "Development Tools"

然后,通过在终端运行以下命令来安装 Homebrew:

/bin/bash -c "$(curl -fsSL https://raw.githubusercontent.com/Homebrew/install/HEAD/install.sh)"

在安装过程中,你会被提示输入你的 sudo 密码。另外,你可以选择 Homebrew 的安装前缀,但默认的前缀就可以了。在安装过程中,你将成为 Homebrew 前缀目录的所有者,这样你就不必输入 sudo 密码来安装软件包。安装将需要数分钟。完成后,运行以下命令,将 brew 添加到你的 PATH 中:

echo 'eval "$(/home/linuxbrew/.linuxbrew/bin/brew shellenv)"' >> ~/.bash_profile
eval "$(/home/linuxbrew/.linuxbrew/bin/brew shellenv)"

安装和检查软件包

要在 Homebrew 上使用“配方”安装一个软件包,只需运行:

brew install <formula>

<formula> 替换为你要安装的“配方”的名称。例如,要安装 Minikube,只需运行:

brew install minikube

你也可以用以下方式搜索“配方”:

brew search <formula>

要获得一个“配方”的信息,请运行:

brew info <formula>

另外,你可以用以下命令查看所有已安装的“配方”:

brew list

卸载软件包

要从你的 Homebrew 前缀中卸载一个软件包,请运行:

brew uninstall <formula>

升级软件包

要升级一个用 Homebrew 安装的特定软件包,请运行:

brew upgrade <formula>

要更新 Homebrew 和所有已安装的“配方”到最新版本,请运行:

brew update

总结

Homebrew 是一个简单的包管理器,可以与 dnf 一起成为有用的工具(两者完全没有关系)。尽量坚持使用 Fedora 原生的 dnf 包管理器,以避免软件冲突。然而,如果你在 Fedora Linux 软件库中没有找到某个软件,那么你也许可以用 Homebrew 找到并安装它。请看 “配方”列表 以了解有哪些可用的软件。另外,Fedora Linux 上的 Homebrew 还不支持图形化应用(在 Homebrew 术语中称为“ 酒桶 cask ”)。至少,我在安装 GUI 应用时没有成功过。

参考资料和进一步阅读

要了解更多关于 Homebrew 的信息,请查看以下资源:


via: https://fedoramagazine.org/using-homebrew-package-manager-on-fedora-linux/

作者:Mehdi Haghgoo 选题:lujun9972 译者:geekpi 校对:wxy

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

Java 和 Groovy 中的 映射 map 都是非常通用的,它允许 关键字 key value 为任意类型,只要继承了 Object 类即可。

 title=

我最近在探索 Java 与 Groovy 在 创建并初始化 列表 List 在运行时构建 列表 List 方面的一些差异。我观察到,就实现这些功能而言,Groovy 的简洁和 Java 的繁复形成了鲜明对比。

在这篇文章中,我将实现在 Java 和 Groovy 中创建并初始化 映射 Map 。映射为开发支持根据 关键字 key 检索的结构提供了可能,如果找到了这样一个关键字,它就会返回对应的 value 。今天,很多编程语言都实现了映射,其中包括 Java 和 Groovy,也包括了 Python(它将映射称为 字典 dict )、Perl、awk 以及许多其他语言。另一个经常被用来描述映射的术语是 关联数组 associative array ,你可以在 这篇维基百科文章 中了解更多。Java 和 Groovy 中的映射都是非常通用的,它允许关键字和值为任意类型,只要继承了 Object 类即可。

安装 Java 和 Groovy

Groovy 基于 Java,因此你需要先安装 Java。你的 Linux 发行版的仓库中可能有最近的比较好的 Java 和 Groovy 版本。或者,你也可以在根据上面链接中的指示来安装 Groovy。对于 Linux 用户来说,SDKMan 是一个不错的代替选项,你可以使用它来获取多个 Java 和 Groovy 版本,以及许多其他的相关工具。在这篇文章中,我使用的 SDK 发行版是:

  • Java: version 11.0.12-open of OpenJDK 11;
  • Groovy: version 3.0.8.

言归正传

Java 提供了非常多的方式来实例化和初始化映射,并且从 Java 9 之后,添加了一些新的方式。其中最明显的方式就是使用 java.util.Map.of() 这个静态方法,下面介绍如何使用它:

var m1 = Map.of(
    "AF", "Afghanistan",
    "AX", "Åland Islands",
    "AL", "Albania",
    "DZ", "Algeria",
    "AS", "American Samoa",
    "AD", "Andorra",
    "AO", "Angola",
    "AI", "Anguilla",
    "AQ", "Antarctica");

System.out.println("m1 = " + m1);
System.out.println("m1 is an instance of " + m1.getClass());

事实证明,在此种情况下,Map.of() 有两个重要的限制。其一,这样创建出来的映射实例是 不可变的 immutable 。其二,你最多只能提供 20 个参数,用来表示 10 个 键值对 key-value pair

你可以尝试着添加第 10 对和第 11 对,比方说 "AG", "Antigua and Barbuda" 和 "AR", "Argentina",然后观察会发生什么。你将发现 Java 编译器尝试寻找一个支持 11 个键值对的 Map.of() 方法而遭遇失败。

快速查看 java.util.Map 类的文档,你就会找到上述第二个限制的原因,以及解决这个难题的一种方式:

var m2 = Map.ofEntries(
    Map.entry("AF", "Afghanistan"),
    Map.entry("AX", "Åland Islands"),
    Map.entry("AL", "Albania"),
    Map.entry("DZ", "Algeria"),
    Map.entry("AS", "American Samoa"),
    Map.entry("AD", "Andorra"),
    Map.entry("AO", "Angola"),
    Map.entry("AI", "Anguilla"),
    Map.entry("AQ", "Antarctica"),
    Map.entry("AG", "Antigua and Barbuda"),
    Map.entry("AR", "Argentina"),
    Map.entry("AM", "Armenia"),
    Map.entry("AW", "Aruba"),
    Map.entry("AU", "Australia"),
    Map.entry("AT", "Austria"),
    Map.entry("AZ", "Azerbaijan"),
    Map.entry("BS", "Bahamas"),
    Map.entry("BH", "Bahrain"),
    Map.entry("BD", "Bangladesh"),
    Map.entry("BB", "Barbados")
);
       
System.out.println("m2 = " + m2);
System.out.println("m2 is an instance of " + m2.getClass());

这就是一个比较好的解决方式,前提是我不在随后的代码里改变使用 Map.ofEntries() 创建并初始化的映射内容。注意,我在上面使用了 Map.ofEntries() 来代替 Map.of()

然而,假设我想要创建并初始化一个非空的映射,随后往这个映射中添加数据,我需要这样做:

var m3 = new HashMap<String,String>(Map.ofEntries(
    Map.entry("AF", "Afghanistan"),
    Map.entry("AX", "Åland Islands"),
    Map.entry("AL", "Albania"),
    Map.entry("DZ", "Algeria"),
    Map.entry("AS", "American Samoa"),
    Map.entry("AD", "Andorra"),
    Map.entry("AO", "Angola"),
    Map.entry("AI", "Anguilla"),
    Map.entry("AQ", "Antarctica"),
    Map.entry("AG", "Antigua and Barbuda"),
    Map.entry("AR", "Argentina"),
    Map.entry("AM", "Armenia"),
    Map.entry("AW", "Aruba"),
    Map.entry("AU", "Australia"),
    Map.entry("AT", "Austria"),
    Map.entry("AZ", "Azerbaijan"),
    Map.entry("BS", "Bahamas"),
    Map.entry("BH", "Bahrain"),
    Map.entry("BD", "Bangladesh"),
    Map.entry("BB", "Barbados")
));

System.out.println("m3 = " + m3);
System.out.println("m3 is an instance of " + m3.getClass());

m3.put("BY", "Belarus");
System.out.println("BY: " + m3.get("BY"));

这里,我把使用 Map.ofEntries() 创建出来的不可变映射作为 HashMap 的一个构造参数,以此创建了该映射的一个 可变副本 mutable copy ,之后我就可以修改它 —— 比如使用 put() 方法。

让我们来看看上述过程如何用 Groovy 来实现:

def m1 = [
    "AF": "Afghanistan",
    "AX": "Åland Islands",
    "AL": "Albania",
    "DZ": "Algeria",
    "AS": "American Samoa",
    "AD": "Andorra",
    "AO": "Angola",
    "AI": "Anguilla",
    "AQ": "Antarctica",
    "AG": "Antigua and Barbuda",
    "AR": "Argentina",
    "AM": "Armenia",
    "AW": "Aruba",
    "AU": "Australia",
    "AT": "Austria",
    "AZ": "Azerbaijan",
    "BS": "Bahamas",
    "BH": "Bahrain",
    "BD": "Bangladesh",
    "BB": "Barbados"]

println "m1 = $m1"
println "m1 is an instance of ${m1.getClass()}"

m1["BY"] = "Belarus"
println "m1 = $m1"

只看一眼,你就会发现 Groovy 使用了 def 关键字而不是 var —— 尽管在 最近模型 late-model 的 Groovy(version 3+)中,使用 var 关键字也是可行的。

你还会发现,你是通过在括号里添加了一个键值对列表来创建一个映射的。不仅如此,这样创建的列表对象还非常有用,这里有几个原因。其一,它是可变的;其二,它是一个 LinkedHashMap 的实例,内部维持了数据的插入顺序。所以,当你运行 Java 版本的代码并打印出变量 m3,你会看到:

m3 = {BB=Barbados, BD=Bangladesh, AD=Andorra, AF=Afghanistan, AG=Antigua and Barbuda, BH=Bahrain, AI=Anguilla, AL=Albania, AM=Armenia, AO=Angola, AQ=Antarctica, BS=Bahamas, AR=Argentina, AS=American Samoa, AT=Austria, AU=Australia, DZ=Algeria, AW=Aruba, AX=Åland Islands, AZ=Azerbaijan}

而当你运行 Groovy 版本的代码,你会看到:

m1 = [AF:Afghanistan, AX:Åland Islands, AL:Albania, DZ:Algeria, AS:American Samoa, AD:Andorra, AO:Angola, AI:Anguilla, AQ:Antarctica, AG:Antigua and Barbuda, AR:Argentina, AM:Armenia, AW:Aruba, AU:Australia, AT:Austria, AZ:Azerbaijan, BS:Bahamas, BH:Bahrain, BD:Bangladesh, BB:Barbados]

再一次,你将看到 Groovy 是如何简化事情的。这样的语法非常直观,有点像 Python 里的字典,并且,即使你有一个超过 10 个键值对的初始列表,你也不需要去记住各种必要的别扭方式。注意我们使用的表达式:

m1[“BY”] = “Belarus”

而在 Java 中,你需要这样做:

m1.put(“BY”, “Belarus”)

还有,这个映射默认是可变的,这么做的利弊很难评判,还是得取决于你的需求是什么。我个人觉得,Java 在这种情况下的 “默认不可变” 机制,最让我困扰的地方是,它没有一个类似于 Map.mutableOfMutableEntries() 的方法。这迫使一些刚学会如何声明和初始化一个映射的程序员,不得不转念去思考该如何把他们手中不可变的映射,转换为可变的。同时我也想问,创建一个不可变的对象然后再舍弃它,这样真的好吗?

另一个值得考虑的事情是,Groovy 使用方括号代替 Java 中的 put()get() 方法来进行关键字查找。因此你可以这样写:

m1[“ZZ”] = m1[“BY”]

而不需要这样写:

m1.put(“ZZ”,m1.get(“BY”))

有时候,就像使用某个类的实例变量一样来使用映射中的关键字和值是一个好办法。设想你现在有一堆想要设置的属性,在 Groovy 中,它们看起来就像下面这样:

def properties = [
      verbose: true,
      debug: false,
      logging: false]

然后,你可以改变其中的某个属性,就像下面这样:

properties.verbose = false

之所以这样能工作,是因为,只要关键字符合特定的规则,你就可以省略引号,然后直接用点操作符来代替方括号。尽管这个功能非常有用,也非常好用,它也同时也意味着,如果你要把一个变量作为一个映射的关键字来使用,你就必须把这个变量包裹在圆括号里,就像下面这样:

def myMap = [(k1): v1, (k2): v2]

是时候告诉勤奋的读者 Groovy 是一门为编写脚本而量身定制的语言了。映射通常是脚本中的关键元素,它为脚本提供了 查找表 lookup table ,并且通常起到了作为内存数据库的作用。我在这里使用的例子是 ISO 3166 规定的两个字母的国家代码和国家名称。对在世界上各个国家的互联网使用者来说,这些代码是很熟悉的。此外,假设我们要编写一个从日志文件中查找互联网主机名,并借此来了解用户的地理位置分布的脚本工具,那么这些代码会是十分有用的部分。

Groovy 相关资源

Apache Groovy 网站 上有非常多的文档。另一个很棒的 Groovy 资源是 Mr. HakiBaeldung 网站 提供了大量 Java 和 Groovy 的有用教程。学习 Groovy 还有一个很棒的原因,那就是可以接着学习 Grails,后者是一个优秀的、高效率的全栈 Web 框架。它基于许多优秀组件构建而成,比如有 Hibernate、Spring Boot 和 Micronaut 等。


via: https://opensource.com/article/22/3/maps-groovy-vs-java

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

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

编写一个 awk 脚本来找到一组单词中出现次数最多(和最少)的单词。

 title=

近一段时间,我开始编写一个小游戏,在这个小游戏里,玩家使用一个个字母块来组成单词。编写这个游戏之前,我需要先知道常见英文单词中每个字母的使用频率,这样一来,我就可以找到一组更有用的字母块。字母频次统计在很多地方都有相关讨论,包括在 维基百科 上,但我还是想要自己来实现。

Linux 系统在 /usr/share/dict/words 文件中提供了一个单词列表,所以我已经有了一个现成的单词列表。然而,尽管这个 words 文件包含了很多我想要的单词,却也包含了一些我不想要的。我想要的单词首先不能是复合词(即不包含连接符和空格的单词),也不能是专有名词(即不包含大写字母单词)。为了得到这个结果,我可以运行 grep 命令来取出只由小写字母组成的行:

$ grep  '^[a-z]*$' /usr/share/dict/words

这个正则表达式的作用是让 grep 去匹配仅包含小写字母的行。表达式中的字符 ^$ 分别代表了这一行的开始和结束。[a-z] 分组仅匹配从 “a” 到 “z” 的小写字母。

下面是一个输出示例:

$ grep  '^[a-z]*$' /usr/share/dict/words | head
a
aa
aaa
aah
aahed
aahing
aahs
aal
aalii
aaliis

没错,这些都是合法的单词。比如,“aahed” 是 “aah” 的过去式,表示在放松时的感叹,而 “aalii” 是一种浓密的热带灌木。

现在我只需要编写一个 gawk 脚本来统计出单词中各个字母出现的次数,然后打印出每个字母的相对频率。

字母计数

一种使用 gawk 来统计字母个数的方式是,遍历每行输入中的每一个字符,然后对 “a” 到 “z” 之间的每个字母进行计数。substr 函数会返回一个给定长度的子串,它可以只包含一个字符,也可以是更长的字符串。比如,下面的示例代码能够取到输入中的每一个字符 c

{
    len = length($0); for (i = 1; i <= len; i++) {
        c = substr($0, i, 1);
    }
}

如果使用一个全局字符串变量 LETTERS 来存储字母表,我就可以借助 index 函数来找到某个字符在字母表中的位置。我将扩展 gawk 代码示例,让它在输入数据中只取范围在 “a” 到 “z” 的字母:

BEGIN { LETTERS = "abcdefghijklmnopqrstuvwxyz" }
 
{
    len = length($0); for (i = 1; i <= len; i++) {
        c = substr($0, i, 1);
        ltr = index(LETTERS, c);
    }
}

需要注意的是,index 函数将返回字母在 LETTERS 字符串中首次出现的位置,第一个位置返回 1,如果没有找到则返回 0。如果我有一个大小为 26 的数组,我就可以利用这个数组来统计每个字母出现的次数。我将在下面的示例代码中添加这个功能,每当一个字母出现在输入中,我就让它对应的数组元素值增加 1(使用 ++):

BEGIN { LETTERS = "abcdefghijklmnopqrstuvwxyz" }
 
{
    len = length($0); for (i = 1; i <= len; i++) {
        c = substr($0, i, 1);
        ltr = index(LETTERS, c);
 
        if (ltr &gt; 0) {
            ++count[ltr];
        }
    }
}

打印相对频率

gawk 脚本统计完所有的字母后,我希望它能输出每个字母的频率。毕竟,我对输入中各个字母的个数没有兴趣,我更关心它们的 相对频率

我将先统计字母 “a” 的个数,然后把它和剩余 “b” 到 “z” 字母的个数比较:

END {
    min = count[1]; for (ltr = 2; ltr <= 26; ltr++) {
        if (count[ltr] < min) {
            min = count[ltr];
        }
    }
}

在循环的最后,变量 min 会等于最少的出现次数,我可以把它为基准,为字母的个数设定一个参照值,然后计算打印出每个字母的相对频率。比如,如果出现次数最少的字母是 “q”,那么 min 就会等于 “q” 的出现次数。

接下来,我会遍历每个字母,打印出它和它的相对频率。我通过把每个字母的个数都除以 min 的方式来计算出它的相对频率,这意味着出现次数最少的字母的相对频率是 1。如果另一个字母出现的次数恰好是最少次数的两倍,那么这个字母的相对频率就是 2。我只关心整数,所以 2.1 和 2.9 对我来说是一样的(都是 2)。

END {
    min = count[1]; for (ltr = 2; ltr <= 26; ltr++) {
        if (count[ltr] < min) {
            min = count[ltr];
        }
    }
 
    for (ltr = 1; ltr <= 26; ltr++) {
        print substr(LETTERS, ltr, 1), int(count[ltr] / min);
    }
}

最后的完整程序

现在,我已经有了一个能够统计输入中各个字母的相对频率的 gawk 脚本:

#!/usr/bin/gawk -f
 
# 只统计 a-z 的字符,忽略 A-Z 和其他的字符
 
BEGIN { LETTERS = "abcdefghijklmnopqrstuvwxyz" }
 
{
    len = length($0); for (i = 1; i <= len; i++) {
        c = substr($0, i, 1);
        ltr = index(LETTERS, c);
 
        if (ltr < 0) {
            ++count[ltr];
        }
    }
}
 
# 打印每个字符的相对频率
   
END {
    min = count[1]; for (ltr = 2; ltr <= 26; ltr++) {
        if (count[ltr] < min) {
            min = count[ltr];
        }
    }
 
    for (ltr = 1; ltr <= 26; ltr++) {
        print substr(LETTERS, ltr, 1), int(count[ltr] / min);
    }
}

我将把这段程序保存到名为 letter-freq.awk 的文件中,这样一来,我就可以在命令行中更方便地使用它。

如果你愿意的话,你也可以使用 chmod +x 命令把这个文件设为可独立执行。第一行中的 #!/usr/bin/gawk -f 表示 Linux 会使用 /usr/bin/gawk 把这个文件当作一个脚本来运行。由于 gawk 命令行使用 -f 来指定它要运行的脚本文件名,你需要在末尾加上 -f。如此一来,当你在 shell 中执行 letter-freq.awk,它会被解释为 /usr/bin/gawk -f letter-freq.awk

接下来我将用几个简单的输入来测试这个脚本。比如,如果我给我的 gawk 脚本输入整个字母表,每个字母的相对频率都应该是 1:

$ echo abcdefghijklmnopqrstuvwxyz | gawk -f letter-freq.awk
a 1
b 1
c 1
d 1
e 1
f 1
g 1
h 1
i 1
j 1
k 1
l 1
m 1
n 1
o 1
p 1
q 1
r 1
s 1
t 1
u 1
v 1
w 1
x 1
y 1
z 1

还是使用上述例子,只不过这次我在输入中添加了一个字母 “e”,此时的输出结果中,“e” 的相对频率会是 2,而其他字母的相对频率仍然会是 1:

$ echo abcdeefghijklmnopqrstuvwxyz | gawk -f letter-freq.awk
a 1
b 1
c 1
d 1
e 2
f 1
g 1
h 1
i 1
j 1
k 1
l 1
m 1
n 1
o 1
p 1
q 1
r 1
s 1
t 1
u 1
v 1
w 1
x 1
y 1
z 1

现在我可以跨出最大的一步了!我将使用 grep 命令和 /usr/share/dict/words 文件,统计所有仅由小写字母组成的单词中,各个字母的相对使用频率:

$ grep  '^[a-z]*$' /usr/share/dict/words | gawk -f letter-freq.awk
a 53
b 12
c 28
d 21
e 72
f 7
g 15
h 17
i 58
j 1
k 5
l 36
m 19
n 47
o 47
p 21
q 1
r 46
s 48
t 44
u 25
v 6
w 4
x 1
y 13
z 2

/usr/share/dict/words 文件的所有小写单词中,字母 “j”、“q” 和 “x” 出现的相对频率最低,字母 “z” 也使用得很少。不出意料,字母 “e” 是使用频率最高的。


via: https://opensource.com/article/21/4/gawk-letter-game

作者:Jim Hall 选题:lujun9972 译者:lkxed 校对:wxy

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

现在可以在 Steam Deck 上运行 Epic 游戏商店了,几乎无懈可击! 但是,它是非官方的。

Steam Deck 在加强对 Linux 平台的游戏支持方面做出了有力推动。

它运行在 Steam OS 3.0(基于 Arch)上,并具有 KDE Plasma 桌面环境。感谢 Valve,它没有锁定平台并让用户在上面进行试验。

尽管不是每个人都可以拿到它,但这是一款令人兴奋的硬件,可以挑战任天堂 Switch 掌机。

它可能还不支持所有的流行游戏(比如《命运 2》、《堡垒之夜》),但它在几个 3A 级大作和独立游戏上取得了不错的进展。你可以到官方的 Deck 认证 页面查看有关支持游戏的最新信息。

现在,更令人激动的是,事实证明 Steam Deck 也可以使用 Epic 游戏商店非官方的)来运行游戏。但是,怎样运行呢,让我们来一探究竟。

通过 Heroic 游戏启动器使用 Epic 游戏商店

是的,这就是 去年 制作的 Heroic 游戏启动器, 并且已知它可以运行在 Linux 桌面上。

另外,(据 GamingOnLinux)感谢 Liam Dawe,他和各位开发者协调,成功地在 Steam Deck 上测试运行了 Heroic 游戏启动器(及 Heroic Bash 启动器)。

补充一句,Heroic Bash 启动器 是一个为所有已安装的 Heroic 游戏创建启动脚本(.sh 文件)的工具,它允许你直接从终端或者游戏前端/启动器启动游戏,而不必打开 Heroic。

故事的发生是这样的(根据我与 Heroic Bash 启动器开发者的简短交谈):

最初,在 Steam Deck 上实验运行 Epic 游戏商店时,Steam 控制器无法工作,因为 Epic 游戏商店是使用 Steam 客户端以一个“非 Steam 游戏”运行的。

所以,Heroic Bash 启动器的开发者,Rishabh Moharir(也是这里的一位作者)建议使用他的工具,按照他 GitHub 上的维基指南 来试试。

Heroic Bash 启动器可以在 Linux 桌面上与 Epic 游戏商店配合使用。所以,这值得一试!

然后,幸运地,它工作了。

在 Steam Deck 上安装 Epic 游戏商店

首先,你需要在 Steam Deck 上使用可用的 AppImage 文件在桌面模式下安装 Heroic 游戏启动器

完成后,你需要登录并下载你选择的游戏。

接下来,你需要下载最新的 legendary 二进制文件,并在启动器设置中将其设置为替代的 legendary 二进制文件。

你需要在启动器的游戏设置中配置并设置兼容层为 Proton 7.0。

这时,你需要下载最新的 Heroic Bash 启动器二进制文件,然后运行它。

最后,你必须根据这个 GitHub 上的官方维基指南,把游戏添加到 Steam 中(以便在 Steam Deck 的界面中找到它)。

总之,“手工爱好者”们肯定需要花好大一会儿才能使其工作。另外,如果你仍然困惑,你可以在 维基 上找到包含详细信息的同样的一套步骤和细节,这是 Heroic 游戏启动器团队整理的(或者参考上面的视频)。

对我来说,这听起来可行,应该不会超越大多数 Steam Deck 用户的能力。不幸的是,我无法在印度买到 Steam Deck(目前)。

至于 Steam Deck 上的 Epic 游戏商店的未来,我们只能抱以最好的期望。

你试过 Steam Deck 吗?在下面的评论区让我知道你的看法。


via: https://news.itsfoss.com/epic-games-steam-deck/

作者:Ankush Das 选题:lujun9972 译者:zd200572 校对:wxy

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