分类 技术 下的文章

通常,我用的是基于 Ubuntu 的发行版进行工作,如 Pop!\_OS、Zorin OS、Linux Mint 或 Ubuntu 本身。

它们不会干扰我的工作,同时又提供了顺滑的软件更新。更不用说,它们与我使用的英特尔-英伟达系统相容性良好。

所有的一切(可能是主观的)都能开箱即用。

然而,最近我决定在实体机上切换到 Arch Linux(我大部分时间都是在虚拟机上使用它)。

结果,我最终选择使用了 Manjaro Linux(一款基于 Arch 的发行版)。

我选择 Manjaro Linux 的原因

令我惊讶的是,即使是使用 Arch Linux 的立付 USB,我甚至无法解决 “nouveau DRM: core notifier timeout” 错误,更别提继续安装了。是的,我知道有向导式的 Arch Linux 安装程序,但是不论怎么样,连接的显示器都会闪烁并突出显示这个错误。

解决这个问题的最好办法是不使用我的最近刚刚升级的英伟达显卡。

很遗憾,我更喜欢英伟达的显卡……

AMD 的 RX 6600 XT 的价格与 RTX 3060 Ti 相当;对于 1440p 来说,购买那款显卡是没有意义的。

因此,在我这个情况下,RTX 3060 Ti 可能是问题所在。

虽然我找到了一些解决问题的方法,但是我太懒了。我只是想看看能否在不用付出太多努力的情况下体验一下 Arch Linux。

所以,我做了以下的事情:

下一个最佳选择就是尝试任何一个专门简化了麻烦的 最佳的基于 Arch 的发行版,对吧?

这就是 Manjaro Linux 登场的地方。

Manjaro Linux 是一款流行的基于 Arch 的发行版,并且我注意到每次更新时都有各种改进(在虚拟机上使用时)。

此外,我喜欢 Manjaro 默认主题的强调色,很适合我的桌面体验。

所以,我决定试试看……

Manjaro Linux: 起步有点困难

使用专有的英伟达驱动程序安装 Manjaro 时我没有遇到任何问题。然而,最近的一个小版本更新,Manjaro Linux 21.2.6, 把我的系统弄乱了。

我无法访问登录界面和 TTY(我只能看到主板制造商的标志)

因此,我只能使用最新的 ISO 重新安装 Manjaro Linux,到目前为止一切都还好(千万别出问题)。

在我使用 Manjaro Linux 的过程中,我注意到了一些事情,有好有坏。

在这里,我分享一些我的经历。如果你还没有尝试过,这些经验应该对你帮助很大,可以让你更多地了解它。

1、简单的安装

基于 Arch 的发行版的主要亮点是使安装过程变得简单。对我来说,完全没有遇到任何问题。

在我的第二块硬盘上安装 Manjaro Linux 是一件轻而易举的事情。引导加载程序正确配置,显示了 Manjaro 主题的启动菜单,让我可以选择 Windows/Manjaro Linux 进行双启动。

2、Manjaro 欢迎页

当尝试新东西时,欢迎体验在用户体验中占据了一大部分重要性。在这方面,Manjaro Linux 没有让人失望。

如果你仔细关注欢迎屏幕上提供的信息,你可以获得所有必要的信息。

GNOME 布局管理器能让你选择一个合适的布局,使你更加舒适。

然而,当我尝试在这里启用“窗口平铺”功能时,它却无法正常工作:

3、包管理器快速且功能丰富

考虑到 GNOME 是我最喜欢的桌面环境,我在软件中心(甚至是像 Pop!\_Shop 这样特定于发行版的商店)上有过糟糕的经历。

虽然它们可以完成工作,但有时它们对我的期望反应不及预期。

在 Manjaro Linux 中,我发现安装了 Pamac 作为包管理器。这似乎是 在 Manjaro Linux 上安装和卸载软件的最佳方式之一

根据我的经验,它在安装和卸载软件时非常快速。在安装某些软件时,你还会收到关于软件包冲突或是否需要替换/删除某些软件包的提示。当你安装某些东西时,包管理器提供了关于可选/必需依赖项的大量信息。

总体来说,体验非常流畅,没有出现任何麻烦。为了提升体验,包管理器还可以通过调整 pamac 的偏好设置,快速启用对 Flatpaks/Snaps/AUR 的支持。

所以,你不必使用终端或不同的软件商店。一切都可在一个统一的平台下获得,这大大节省了时间。

4、尝试 ZSH Shell

我习惯于在基于 Ubuntu 的发行版上使用 Bash shell。然而,Manjaro Linux 默认使用 Zsh shell。我相信 Zsh 比 Bash 更好,不过我很快会在另一篇文章中介绍并深入比较。

换句话说,我可以直接尝试不同的东西。令我兴奋的是,终端提示符和遵循 Manjaro 品牌的Shell(或终端)的主题也采用了 Manjaro 的强调色,非常令人印象深刻!

因此,我没有必要在此处 自定义终端的外观

要了解更多信息,你可以查看一些关于 Zsh 是什么 以及 如何安装它 的信息。

5、缺乏官方软件支持

我希望这个情况能够很快改善。但目前来说,许多软件/工具只提供对 Ubuntu 和 Fedora 的官方支持。

你可以在各种工具中找到官方的 DEB/RPM 包,但它们都不能直接在 Manjaro Linux 上使用。

你得依赖 Arch Linux 仓库或 AUR 中提供的软件包。

幸运的是,很有可能在 AUR 或社区或发行版开发者维护的仓库中找到所需的软件。就像我能够在 Manjaro Linux 上运行 Insync,并使用文件管理器的集成扩展。

然而,由于缺乏对该平台的官方支持,你可能会错过一些功能或快速更新。

当然,如果你依赖于 Flatpak 或 Snap 软件包,这对你来说应该不是个问题。此外,如果你是 Linux 的新手,你可以参考我们的 Flatpak 指南 获取更多信息。

6、缺少分数缩放

我有一个由 1080p 和 1440p 分辨率组成的双显示器。因此,分数缩放很有帮助,但我可以不用它来操作。

要在 Manjaro 上启用分数缩放,你需要安装支持 X11 缩放的 Mutter 和 GNOME 控制中心软件包。这些软件包包括:

  • mutter-x11-scaling
  • gnome-control-center-x11-scaling

这将替换你现有的 Mutter 和 GNOME 控制中心软件包。因此,你将失去桌面的默认主题/强调色设置。

你可能需要使用 GNOME “ 调整 Tweaks ”应用来将一切调整正确。但是,这可能会令人感觉麻烦。

总结

总体而言,我喜欢在 Manjaro Linux 上的桌面体验。如果那个更新没有搞坏我的系统,我认为我会继续使用 Manjaro Linux 作为我的新日常操作系统。

你认为 Manjaro Linux 的优点和缺点是什么?我在我的新体验中有遗漏了什么吗?作为一个有经验的 Arch Linux 用户,你有任何建议吗?

请在下方评论中告诉我你的想法。

(题图:MJ/2726c4dd-5611-4ace-8c77-0db894ad6a23)


via: https://news.itsfoss.com/manjaro-linux-experience/

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

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

采用 Apache APISIX 的 API 主导架构。

API 网关是一个单一节点,提供对 API 调用入口。网关聚合了所请求的服务,并相应传回合适的响应信息。为了令你的 API 网关有效地工作,设计一个可靠、高效且简洁地 API 至关重要。本文介绍一种设计风格,但只要你理解其中的重点内容,它就能解决你的相关问题。

由 API 主导的方法

API 主导的方法是将 API 置于应用程序和它们需要访问的业务能力之间的通信核心,从而在所有数字通道上一致地交付无缝功能。API 主导的连接是指使用一种可重用、且设计得当的 API 来连接数据和应用程序的方法。

API 主导的架构

API 主导的架构是一种架构方法,它着眼于实现重用 API 的最佳方式。它能解决以下问题:

  • 保护 API,使外界无法在未授权情况下访问 API
  • 确保应用程序能找到正确的 API 端点
  • 限制对 API 的请求次数,从而确保持续的可用性
  • 支持持续集成、测试、生命周期管理、监控、运维等等
  • 防止错误在栈间传播
  • 对 API 的实时监测和分析
  • 实现可伸缩和灵活的业务能力(例如支持 微服务 架构)

API 资源路由

实现一个 API 网关,把它作为与所有服务通信的单一入口点,意味着使用者只需要知道 URL 就能使用 API。将请求路由到相应的服务端点,并执行相应的功能是 API 网关的职责。

Image depicting the API routing traffic.

由于客户端应用程序不需要从多个 HTTP 端点调用功能,这个办法就减少了 API 使用者的操作复杂度。对每个服务来说,也不需实现一个单独的层级去实现认证、授权、节流和速度限制。大多数API 网关,如开源的 Apache APISIX,已经包含了这些核心功能。

API 基于内容的路由

基于内容的路由机制也使用 API 网关根据请求的内容进行路由调用。例如,一个请求可能是基于 HTTP 请求的头部内容或消息体被路由,而不只基于它的目标 URI。

考虑这样一个场景:为了将负载在多个数据库实例间均分,需要对数据库进行分区。当记录总数较大,单个数据库实例难以管理负载时,常常会用这个办法。

还有一个更好的办法,就是把记录在多个数据库实例间分散开来。然后你实现多个服务,每个不同的数据库都有一个服务,把一个 API 网关作为访问所有服务的唯一入口。然后,你可以配置你的 API 网关,根据从 HTTP 头或有效载荷中获得的密钥,将调用路由到相应的服务。

Image of the API gateway exposing a single customer.

在上面的图表中,一个 API 网关向多个客户服务暴露一个单一的 /customers 资源,每个服务都有对应的不同数据库。

API 地理路由

API 地理路由解决方案根据 API 调用的来源将其路由到最近的 API 网关。为了防止地理距离导致的延迟问题(例如一个位于亚洲的客户端调用了位于北美地区的 API),你可以在多个地区部署 API 网关。对于一个 API 网关,你可以在每个区域使用不同的子域名,让应用程序基于业务逻辑选择最近的网关。因此 API 网关就提供了内部负载均衡,确保进入的请求分布于可用的实例之间。

Image of a DNS traffic management system.

通常使用 DNS 流量管理服务和 API 网关,针对该区域的负载均衡器解析子域名,定位到距离最近的网关。

API 聚合器

这项技术对多个服务执行操作(例如查询),并向客户端服务以单个 HTTP 响应的形式返回结果。API 聚合器使用 API 网关在服务器端代表使用者来执行这项工作,而非让客户端程序多次调用 API。

假定你有一款移动端 APP,对不同的 API 发起多次调用。这增加了客户端代码的复杂性,导致网络资源的过度使用,而且由于延迟性,用户体验也不好。网关可以接收所有需要的信息,可以要求认证和验证,并理解来自每个 API 的数据结构。它也可以传递响应的有效载荷,因此它们也会作为一个用户需要的统一负载传回移动端。

Image of an API gateway.

API 集中认证

在这种设计中,API 网关就是一个集中式认证网关。作为一个认证者,API 网关在 HTTP 请求头中查找访问凭据(例如不记名的令牌)。然后它借助于身份验证提供方执行验证凭据的业务逻辑。

Image of a tree showing API gateway's centralized authentication.

使用 API 网关的集中式身份验证能解决很多问题。它完全取代了应用程序中的用户管理模块,通过对来自客户端应用程序的身份验证请求的快速响应来提高性能。Apache APISIX 提供了一系列插件,支持不同的 API 网关认证方法。

Image showing Apache ASPISIS and various plugins.

API 格式转换

API 格式转换是通过同一传输方式将有效载荷从一种格式转换为另一种格式的能力。例如,你可以通过 HTTPS 从 XML/SOAP 格式转换为 JSON 格式,反之亦然。API 网关提供了支持 REST API 的功能,可以有效地进行负载和传输的转换。例如,它可以把消息队列遥测传输(MQTT)转换为 JSON 格式。

Image depicting APISIX transfers.

Apache APISIX 能够接收 HTTP 请求,对其进行代码转换,然后将其转发给 gRPC 服务。它通过 gRPC Transcode 插件获取响应并将其以 HTTP 格式返回给客户端。

API 的可观察性

现在,你知道 API 网关为进入各种目的地的流量提供了一个中心控制点。但它也可以是一个中心观察点,因为就监控客户端和服务器端的流量来说,它有独特的资格。为了收集监测工具所需要的数据(结构化日志、度量和跟踪),你可以对 API 网关作出调整。

Apache APISIX 提供了 预先构建的连接器,因此你可以跟外部监测工具结合使用。你可以利用这些连接器从你的 API 网关收集日志数据,进一步获得有用的指标,并获取完整可见的服务使用情况。

API 缓存

API 缓存通常在网关内部实现。它可以减少对端点的调用次数,同时通过缓存上游的响应,改进了请求延迟的情况。如果网关缓存对请求资源有一个新副本,它会直接使用这个副本来响应这个请求,而不必对端点发出请求。如果缓存数据不存在,就将请求传到目标上游服务。

Image depicting how the API gateway cache functions.

API 错误处理

由于各种原因,API 服务可能会出错。在这种情况下,API 服务需要有一定的弹性,来应对可预见的错误。你也希望确保弹性机制能正常工作。弹性机制包括错误处理代码、断路器、健康检查、回退、冗余等等。新式的 API 网关支持各种常见错误处理功能,包括自动重试和超时设置。

Image depicting some of the many mechanisms that the modern API Gatway can support.

API 网关作为一个协调器,它会根据各方面情况来决定如何管理流量、将负载均衡发送到一个健康的节点,还能快速失败。当有异常状况,它也会向你发出警示。API 网关也保证路由和其他网络级组件能协同将请求传给 API 进程。它能帮助你在早期检测出问题,并修复问题。网关的错误注入机制(类似于 Apache APISIX 使用的那种)可用于测试应用程序或微服务 API 在各种错误发生时的弹性。

API 版本管理

版本管理是指定义和运行多个并发的 API 版本的功能。这点也很重要,因为 API 是随着时间推移不断改进的。如果能对 API 的并发版本进行管理,那么 API 使用者就可以较快地切换到新的版本。这也意味着较老的版本可以被废弃并最终退役。API 也跟其他应用程序类似,无论是开发新功能还是进行错误修复,都存在演变的过程。

Image of using the API Gateway to implement API versioning.

你可以使用 API 网关来实现 API 版本管理。版本管理可以是请求头,查询参数或路径。

APISIX 的网关

如果你需要令 API 服务可伸缩,就需要使用 API 网关。Apache APISIX 提供了必要的功能,可以实现健壮的入口,它的好处是显而易见的。它遵循 API 主导的架构,并且有可能改变客户端与托管服务交互的方式。

本文经作者许可,从 Apache APISIX 博客改编并转载。

(题图:MJ/f1d05345-48f5-4e3e-9c65-a2ba9105614a)


via: https://opensource.com/article/23/1/api-gateway-apache-apisix

作者:Bobur Umurzokov 选题:lkxed 译者:cool-summer-021 校对:wxy

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

在本文中,我们将逐步向你展示如何在 Kubernetes(k8s)集群上安装 Ansible AWX。

Ansible AWX 是一个强大的开源工具,用于管理和自动化 IT 基础设施。AWX 为 Ansible 提供图形用户界面,使你可以轻松创建、安排和运行 Ansible 剧本 Playbook

另一方面,Kubernetes 是一种流行的容器编排平台,广泛用于部署和管理容器化应用。

先决条件:

  • Kubernetes 集群
  • Kubectl
  • 具有 sudo 权限和集群管理员权限的普通用户
  • 互联网连接

步骤 1:安装 Helm

如果你的系统上安装了 Helm,则在命令下运行以进行安装,

$ curl -fsSL -o get_helm.sh https://raw.githubusercontent.com/helm/helm/main/scripts/get-helm-3
$ chmod +x get_helm.sh
$ ./get_helm.sh
$ helm version

Install-helm-linux-command-line

步骤 2:安装 AWX chart

在 Kubernetes 上安装 AWX 的最简单方法是使用 AWX Helm “ 海图 chart ”。因此,要通过 “海图” 安装 AWX,首先使用以下 helm 命令添加仓库。(LCTT 译注:Kubernetes 生态中大量使用了和航海有关的比喻,因此本文在翻译时也采用了这些比喻)

$ helm repo add awx-operator https://ansible.github.io/awx-operator/
"awx-operator" has been added to your repositories
$

注意:如果你之前已经添加过此仓库,请在命令下运行以获取最新版本的软件包。

$ helm repo update

要通过 Helm 安装 awx-operator,请运行:

$ helm install ansible-awx-operator awx-operator/awx-operator -n awx --create-namespace

helm-install-awx-operator-kubernetes

这将下载 AWX 海图并将其安装在 awx 命名空间中的 Kubernetes 集群上。安装过程可能需要几分钟,请耐心等待。

步骤 3:验证 AWX 操作员安装

安装成功后,你可以通过运行以下命令来验证 AWX 操作员 operator 状态:

$ sudo kubectl get pods -n awx

你应该看到这样的东西:

awx-operator-pod-status-kubectl

步骤 4: 创建 PV、PVC 并部署 AWX yaml 文件

AWX 需要 postgres 容器荚 pod 的持久卷。那么,让我们首先为本地卷创建一个存储类。

注意:在本文中,我使用本地文件系统作为持久卷。

$ vi local-storage-class.yaml
apiVersion: storage.k8s.io/v1
kind: StorageClass
metadata:
  name: local-storage
  namespace: awxprovisioner: kubernetes.io/no-provisioner
volumeBindingMode: WaitForFirstConsumer

保存并关闭文件,然后运行:

$ kubectl create -f local-storage-class.yaml
$ kubectl get sc -n awx
NAME            PROVISIONER                    RECLAIMPOLICY   VOLUMEBINDINGMODE      ALLOWVOLUMEEXPANSION   
local-storage   kubernetes.io/no-provisioner   Delete          WaitForFirstConsumer   false                 
$

接下来使用以下 pv.yaml 文件创建持久卷(PV):

$ vi pv.yaml
apiVersion: v1
kind: PersistentVolume
metadata:
  name: postgres-pv
  namespace: awx
spec:
  capacity:
    storage: 10Gi
  volumeMode: Filesystem
  accessModes:
  - ReadWriteOnce
  persistentVolumeReclaimPolicy: Delete
  storageClassName: local-storage
  local:
    path: /mnt/storage
  nodeAffinity:
    required:
      nodeSelectorTerms:
      - matchExpressions:
        - key: kubernetes.io/hostname
          operator: In
          values:
          - k8s-worker

保存并退出文件。

Postgres-pv-awx-kubernetes

重要说明:确保文件夹 /mnt/storage 存在于工作节点上,如果不存在,则在工作节点上使用 mkdir 命令创建它。在我们的例子中,工作节点是 k8s-worker

执行下面的命令在 awx 命名空间中创建 postgres-pv

$ kubectl create -f pv.yaml

成功创建 PV 后,使用 pvc.yaml 文件创建 PersistentVolumeClaim:

$ vi  pvc.yaml
apiVersion: v1
kind: PersistentVolumeClaim
metadata:
  name: postgres-13-ansible-awx-postgres-13-0
  namespace: awx
spec:
  storageClassName: local-storage
  accessModes:
    - ReadWriteOnce
  resources:
    requests:
      storage: 10Gi

posgres-pvc-awx-kubernetes

要创建 PVC,请运行以下 kubectl 命令:

$ kubectl create -f pvc.yaml

使用下面的命令验证 PV 和 PVC 的状态:

$ kubectl get pv,pvc -n awx

现在,我们都准备好部署 AWX 实例了。创建一个包含以下内容的 ansible-awx.yaml 文件:

$ vi ansible-awx.yaml
---
apiVersion: awx.ansible.com/v1beta1
kind: AWX
metadata:
  name: ansible-awx
  namespace: awx
spec:
  service_type: nodeport
  postgres_storage_class: local-storage

Ansible-awx-yaml-file

保存并关闭文件。

执行以下 kubectl 命令来部署 awx 实例:

$ kubectl create -f ansible-awx.yaml

等待几分钟,然后检查 awx 命名空间中的容器荚状态。

$ kubectl get pods -n awx

Ansible-AWX-Pods-Status-Kubernetes

步骤 5:访问 AWX Web 界面

要访问 AWX Web 界面,你需要创建一个公开 awx-web 部署的服务:

$ kubectl expose deployment ansible-awx-web --name ansible-awx-web-svc --type NodePort -n awx

此命令将创建一个 NodePort 服务,该服务将 AWX Web 容器的端口映射到 Kubernetes 节点上的端口。你可以通过运行以下命令找到端口号:

$ kubectl get svc ansible-awx-web-svc  -n awx

这将输出如下内容:

NAME                 TYPE       CLUSTER-IP      EXTERNAL-IP   PORT(S)          AGE
ansible-awx-web-svc   NodePort   10.99.83.248   <none>        8052:32254/TCP   82s

在此示例中,Web 服务在端口 32254 上可用。

Expose-Ansible-AWX-Web-NodePort-Kubernetes

默认情况下,admin 用户是 Web 界面的 admin,密码在 <resourcename>-admin-password 机密信息中。要检索管理员密码,请运行:

$ kubectl get secrets -n awx | grep -i admin-password
ansible-awx-admin-password        Opaque               1      109m
$
$ kubectl get secret ansible-awx-admin-password -o jsonpath="{.data.password}" -n awx | base64 --decode ; echo
l9mWcIOXQhSKnzZQyQQ9LZf3awDV0YMJ
$

你现在可以打开 Web 浏览器并进入 http://<node-ip>:<node-port>/ 来访问 AWX Web 界面。在上面的示例中,URL 是:

http://192.168.1.223:3225

AWX-Login-URL-Kubernetes

输入凭据后单击登录。

Ansible-AWX-Web-Dashboard

恭喜! 你已在 Kubernetes 上成功安装 Ansible AWX。你现在可以使用 AWX 来自动化你的 IT 基础架构,并让你作为系统管理员的生活更轻松。

(题图:MJ/bfd354aa-2ee5-4555-98b8-ac5207cbeabe)


via: https://www.linuxtechi.com/install-ansible-awx-on-kubernetes-cluster/

作者:Pradeep Kumar 选题:lkxed 译者:geekpi 校对:wxy

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

这篇初学者指南解释了在 Ubuntu、Linux Mint 和 Windows 中升级到最新 LibreOffice 所需的步骤。

LibreOffice 7.3.x Community Edition in Ubuntu 22.04 LTS Jammy Jellyfish

LibreOffice 被全球数百万用户使用,是当今最流行的免费办公套件。它由电子表格程序 (Calc)、文档处理器(Writer)、演示文稿(Impress)、绘图(Draw)和数学模块组成,可帮助你完成大部分办公、商务、学术和日常工作。

由于与 DOCX、PPTX 和 XLSX 等专有文件格式兼容,LibreOffice 可以作为付费的微软 Office 套件的出色替代品。它是 Apache OpenOffice 生产力套件的一个分支,由数以千计的全球贡献者积极开发。

LibreOffice Logo

升级到最新的 LibreOffice

LibreOffice 改变了它的版本命名方式,不再采用"Fresh"和"Still"的概念。现在,LibreOffice 有一个社区版和一个推荐的企业版。这是因为许多企业使用带有最新功能的最新 LibreOffice 版本,这对开发工作造成了阻碍。因此,团队建议企业用户从 LibreOffice 官方合作伙伴那里获得付费支持,以获得长期支持。你可以在 此处 阅读博文。

以下是当前的版本:

最新的 LibreOffice 版本

  • 当前的 LibreOffice 社区版本系列是 LibreOffice 7.4.x
  • LibreOffice 稳定推荐的商业版本是 LibreOffice 7.3.x

在 Ubuntu、Linux Mint 和其他基于 Ubuntu 的发行版中升级到最新的 LibreOffice

Ubuntu 22.04 LTS Jammy Jellyfish 具有 LibreOffice 7.3.x。

Ubuntu 20.04 LTS 目前有 LibreOffice 6.4.7,很快就会得到下一次迭代的更新。

Ubuntu 18.04 LTS 支持到 2023 年 4 月,具有 LibreOffice 6.2 版本。Linux Mint 19.x 也提供了相同的版本。你仍然可以在 Ubuntu 18.04 或 Linux Mint 19.x 中下载并安装 LibreOffice 6.3.x 版本。

坚持使用发行版提供的 LibreOffice 版本是比较明智的。此外,除非你需要最新的功能,否则不应升级。如果你喜欢尝试,你可以继续:

通过 PPA

你可以使用官方 LibreOffice PPA 安装和升级到最新的版本。打开终端并在 Ubuntu 或 Linux Mint 中运行以下命令。

sudo add-apt-repository ppa:libreoffice/ppa
sudo apt update && sudo apt install libreoffice

降级 LibreOffice 并删除 PPA,请从终端按顺序运行以下命令。

sudo add-apt-repository --remove ppa:libreoffice/ppa
sudo apt install ppa-purge && sudo ppa-purge ppa:libreoffice/ppa

通过 Snap

你也可以选择通过下面的选项用 Snap 安装最新的LibreOffice。Snap 包 可以作为独立包在受支持的 Linux 发行版中使用。因此,你可以保留现有的 LibreOffice 安装并仍然运行最新的 Snap 版本。

请记住,从技术上讲,你可以使用一个 Snap 版本并行运行两个版本的 LibreOffice。但是,请确保可能存在轻微的文件关联问题和其他问题。这也适用于 Flatpak。

用 Snap 下载 LibreOffice

通过 Flatpak

Flatpak 是另一种方式,你可以通过它获得最新的 LibreOffice 以及发行版提供的版本。你可以安装 Flatpak 并通过以下链接下载 LibreOffice。

用 Flatpak 下载 LibreOffice

Windows 升级

对于 Windows,你不能直接从现有安装升级。你可以从 本页 下载最新的 LibreOffice 并进行安装。在安装过程中,你现有的版本将被卸载。

升级故障排除

如果升级后遇到任何问题或系统不稳定,最好进行全新安装。因此,你可以从 此链接 下载最新版本并进行安装。如果你使用的是 Ubuntu 或 Linux Mint,请不要忘记在安装最新版本之前先删除现有版本。

最后,如果你在 Ubuntu Linux 和其他操作系统中升级到最新的 LibreOffice 时遇到问题,请在下方发表评论。


via: https://www.debugpoint.com/libreoffice-upgrade-update-latest/

作者:Arindam 选题:lkxed 译者:geekpi 校对:wxy

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

像 fzf 和 fzy 这样的现代工具将 Linux 终端中的文件搜索提升到了一个新的水平。

在 Linux 命令行中,如何 搜索文件?你可以使用 find 命令。这是标准答案,没有问题。

通常,你键入带有搜索参数的命令,按回车键,然后它会显示搜索结果。

你可以通过模糊搜索来提升终端中的文件搜索体验。

模糊搜索是一种近似搜索算法或技术。在这种搜索中,通过名称搜索指定位置的文件,并实时显示结果给用户。

模糊搜索在网络搜索引擎中很受欢迎,用户开始输入术语后,它会开始显示与该术语相关的结果。

在本文中,我将讨论两个命令行工具,它们可以让你在 Linux 中执行模糊搜索:

  • fzf:模糊查找工具
  • fzy:模糊选择工具

fzf:Linux 中的模糊查找工具

fzf 是一款可用于 Linux 的模糊搜索工具,你可以通过它进行交互式文件搜索。

在 Ubuntu 中安装 fzf,打开终端并运行以下命令:

sudo apt install fzf

虽然 fzf 本身可以正常工作,但最好与其他工具配合使用,以充分发挥其功能。

使用 fzf

打开终端并运行:

fzf

这将打开一个 fzf 提示符,在当前工作目录中搜索文件。

在终端中运行 fzf 命令

为 fzf 应用边框

你可以使用 --border 选项为 fzf 应用边框,有多种边框可用,如 rounded(圆角)、sharp(尖角)等。

fzf --border=rounded

运行带有边框选项设置为 rounded 和 sharp 的 fzf 命令

应用背景和前景颜色

使用颜色属性,你可以为 fzf 设置 ANSI 颜色,可以作为背景、前景或两者都设置。

为 fzf 应用颜色,用户指定了颜色

fzf --color="bg:black,fg:yellow"

你可以串联这些选项,使 fzf 在视觉上更加美观。

现在,让我展示一些 fzf 模糊搜索的实际用法。

使用 fzf 在 Bash 历史中进行搜索

当然,Bash 历史记录中有 CTRL+R 的反向搜索功能。但如果你想使用 fzf 来获得更好的外观,可以运行以下命令:

history | fzf

使用 fzf 模糊搜索在 bash 历史中进行搜索

使用 fzf 结合 tree 命令

tree 命令 会列出文件和目录,并显示它们的层级关系。

使用 fzf 结合 tree 命令可以帮助你找到特定文件的绝对路径。

tree -afR /home/$USER | fzf

运行 Tree 命令并将输出传递给模糊搜索

? 上述命令会调用 tree 并以递归方式列出包括隐藏文件在内的所有文件(-a)。同时,-f 选项告诉 tree 列出完整路径。

在 fzf 中预览文件

有时,如果你可以获得你搜索的文件的小型预览,那会很有帮助。

幸运的是,fzf 提供了一个预览选项。你可以使用 --preview 来访问它。我在这里使用 find 命令使其更加有用。

find /home/$USER -type f | fzf --preview 'less {}'

在这里,当你滚动浏览结果时,它将使用 less 显示文本文件。

? 如果你使用其他命令如 ls 等,请不要使用 -l 等选项,因为这将显示额外的详细信息(文件权限)。这些额外的详细信息会破坏 fzf 预览所需的格式。在使用预览功能时,输入到 fzf 的应该只是文件名。

如果你已安装了 bat,也可以使用它来预览文件。

find /home/$USER -type f | fzf --preview 'bat --color always {}'

使用 bat 作为 FZF 预览功能的文本查看器

对于 Ubuntu 用户,可以使用 batcat 来调用 bat。因此运行:

find /home/$USER -type f | fzf --preview 'batcat --color always {}'
? 为这些命令创建别名,这样你就不需要反复输入它们。

从任何地方使用 fzf 进入任何目录(高级技巧)

这比以前要复杂一些。在这里,你不能直接将 fzfcd 连接在一起,因为它们是不同的进程。

你可以创建一个别名并使用以下命令:

cd $(find /home/$USER -type d | fzf)

或者,你可以按照下面解释的方法进行操作。

为此,你可能需要在 bashrc 中添加一个函数。让我将这个函数称为 finder。现在请添加以下行到你的 bashrc 中。

finder() {
  local dir
  dir=$(find required/location/to/search/and/enter -type d | fzf)
  if [[ -n "$dir" ]]; then
    cd "$dir" || return
  fi
}

现在,你应该 输入路径,其中包含你要搜索并进入的目录。

例如,我已经使用 /home/$USER 替换了该部分,表示我要从任何位置进入我的主目录中的任何目录。

保存你的 bashrc 文件后,要么重启终端,要么运行以下命令:

source ~/.bashrc

之后,你可以在终端上运行 finder 命令,一旦找到要进入的目录,按回车键即可。

使用 fzf 命令结合 cd 命令进入任意目录

将选择内容复制到剪贴板

到目前为止,你已经了解了如何使用 fzf,它提供了搜索结果或预览。

现在,如果你想要复制某个项目的位置,你不必手动执行此操作。也有相应的解决方案。

首先,确保你已经安装了 xclip

sudo apt install xclip

然后像这样将其传递给 xclip

fzf | xclip -selection clipboard

这将复制你按下回车键的那些行到剪贴板上。

其他用途

正如我之前所说,你可以使用任何涉及大量文本,并希望交互式搜索特定内容的命令。

  • cat ~/.bashrc | fzf - 在 Bashrc 文件中搜索
  • lsblk | fzf - 在锁定设备列表中搜索
  • ps -aux | fzf - 在进程列表中搜索

另一个选择:Fzy,模糊选择器

fzf 不同,fzy 是一个模糊选择器,它会根据输入提供一个菜单供你选择。

例如,如果你将 fzyls 命令一起使用,它将给你提供一个类似菜单的界面。

使用 ls 命令的 fzy 命令

默认情况下,它会显示十个条目。

使用 fzy 进入目录

fzf 类似,你也可以使用 fzy 进入当前工作目录中的目录:

cd $(find -type d | fzy)

使用任何编辑器打开文件

或者使用你喜欢的编辑器打开文件:

nano $(find -type f | fzy)

附加内容:自定义文件和图像预览

下面的命令将在 Ubuntu 中打开一个专门的自定义提示符,用于模糊搜索,你可以通过滚动来预览文本文件。

find /home/$USER -type f | fzf --color="bg:black,fg:yellow" --preview 'batcat --color always {}' --preview-window=bottom

为了方便访问,可以在你的 bashrc 文件中为此创建一个别名。

或者在使用 timg 命令行图像查看器时,在 fzf 中进行图像预览并滚动。使用以下命令进行安装:

sudo apt install timg
? 请注意,图像查看器无法正确显示图像,因为这不是 fzf 预览的主要目的。
fzf --preview 'timg -g 200x100 {}' --preview-window=right:90

对于那些喜欢折腾的人,可以尝试对此部分进行优化。

现代化的替代方案

大多数 Linux 命令都是从 UNIX 时代继承下来的。它们虽然老旧,但功能如预期。但这并不意味着它们不能改进。

我的意思是,你不需要重新发明轮子,但你总是可以努力改进轮子。

fzffzy 这样的现代化工具将 Linux 终端中的文件搜索提升到了一个新的水平。以下是一些其他有趣的命令行工具。

我尝试给出了这些模糊搜索工具的一些实际示例。希望你对它们感到足够有启发性。如果你打算使用它们,请在评论中告诉我。

(题图:MJ/d25e71fa-f24e-49be-9579-e0520a8f6e18)


via: https://itsfoss.com/fuzzy-file-search-linux/

作者:Sreenath 选题:lkxed 译者:ChatGPT 校对:wxy

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

到目前为止,我们已经讲解了包括 变量、可变性、常量数据类型函数if-else 语句循环 在内的一些关于 Rust 编程的基础知识。

在 Rust 基础系列的最后一章里,让我们现在用 Rust 编写一个程序,使用这些主题,以便更好地理解它们在现实世界中的用途。让我们来编写一个相对简单的程序,用来从水果市场订购水果。

我们程序的基本结构

来让我们首先向用户问好,并告诉他们如何与程序交互。

fn main() {
    println!("欢迎来到水果市场!");
    println!("请选择要购买的水果。\n");

    println!("\n可以购买的水果:苹果、香蕉、橘子、芒果、葡萄");
    println!("购买完成后,请输入“quit”或“q”。\n");
}

获取用户输入

上面的代码非常简单。目前,你不知道接下来该做什么,因为你不知道用户接下来想做什么。

所以让我们添加一些代码,接受用户输入并将其存储在某个地方以便稍后解析,然后根据用户输入采取适当的操作。

use std::io;

fn main() {
    println!("欢迎来到水果市场!");
    println!("请选择要购买的水果。\n");

    println!("\n可以购买的水果:苹果、香蕉、橘子、芒果、葡萄");
    println!("购买完成后,请输入“quit”或“q”。\n");

    // 获取用户输入
    let mut user_input = String::new();
    io::stdin()
        .read_line(&mut user_input)
        .expect("无法读取用户输入。");
}

有三个新元素需要告诉你。所以让我们对这些新元素进行浅层次的探索。

1. 理解 use 关键字

在这个程序的第一行,你可能已经注意到我们“使用”(哈哈!)了一个叫做 use 的新关键字。Rust 中的 use 关键字类似于 C/C++ 中的 #include 指令和 Python 中的 import 关键字。使用 use 关键字,我们从 Rust 标准库 std 中“导入”了 io(输入输出)模块。

LCTT 译注:“使用”在原文中为“use”,与新介绍的关键字一样。

你可能会想知道为什么我们在可以使用 println 宏来将某些内容输出到标准输出时,导入 io 模块是必要的。Rust 的标准库有一个叫做 prelude 的模块,它会自动被包含。该模块包含了 Rust 程序员可能需要使用的所有常用函数,比如 println 宏。(你可以在 这里 阅读更多关于 std::prelude 模块的内容。)

Rust 标准库 std 中的 io 模块是接受用户输入所必需的。因此,我们在程序的第一行添加了一个 use 语句。

2. 理解 Rust 中的 String 类型

在第 11 行,我创建了一个新的可变变量 user_input,正如它的名字所表示的那样,它将被用来存储用户输入。但是在同一行,你可能已经注意到了一些“新的”东西(哈哈,又来了!)。

LCTT 译注:“新的”在原文中为“new”,在第 11 行的代码中,原作者使用了 String::new() 函数,所以此处的梗与“使用”一样,原作者使用了一个在代码中用到的单词。

我没有使用双引号("")声明一个空字符串,而是使用 String::new() 函数来创建一个新的空字符串。

""String::new() 的区别是你将在 Rust 系列的后续文章中学习到的。现在,只需要知道,使用 String::new() 函数,你可以创建一个可变的,位于堆上的字符串。

如果我使用 "" 创建了一个字符串,我将得到一个叫做“字符串切片”的东西。字符串切片的内容也位于堆上,但是字符串本身是不可变的。所以,即使变量本身是可变的,作为字符串存储的实际数据是不可变的,需要被覆盖而不是修改。

3. 接受用户输入

在第 12 行,我调用了 std::iostdin() 函数。如果我在程序的开头没有导入 std::io 模块,那么这一行将是 std::io::stdin() 而不是 io::stdin()

sdtin() 函数返回一个终端的输入句柄。read_line() 函数抓住这个输入句柄,然后,正如它的名字所暗示的那样,读取一行输入。这个函数接受一个可变字符串的引用。所以,我传入了 user_input 变量,通过在它前面加上 &mut,使它成为一个可变引用。

⚠️ read_line() 函数有一个 怪癖。这个函数在用户按下回车键之后 停止 读取输入。因此,这个函数也会记录换行符(\n),并将一个换行符存储在你传入的可变字符串变量的结尾处。

所以,请在处理它时要么考虑到这个换行符,要么将它删除。

Rust 中的错误处理入门

最后,在这个链的末尾有一个 expect() 函数。让我们稍微偏题一下,来理解为什么要调用这个函数。

read_line() 函数返回一个叫做 Result 的枚举。我会在后面的文章中讲解 Rust 中的枚举,但是现在只需要知道,枚举在 Rust 中是非常强大的。这个 Result 枚举返回一个值,告诉程序员在读取用户输入时是否发生了错误。

expect() 函数接受这个 Result 枚举,并检查结果是否正常。如果没有发生错误,什么都不会发生。但是如果发生了错误,我传入的消息(无法读取用户输入。)将会被打印到 STDERR,程序将会退出

? 所有我简要提及的新概念将会在后续的新 Rust 系列文章中讲解。

现在我希望你应该已经理解了这些新概念,让我们添加更多的代码来增加程序的功能。

验证用户输入

我接受了用户的输入,但是我没有对其进行验证。在当前的上下文中,验证意味着用户输入了一些“命令”,我们希望能够处理这些命令。目前,这些命令有两个“类别”。

第一类用户可以输入的命令是用户希望购买的水果的名称。第二个命令表示用户想要退出程序。

我们的任务现在是确保用户输入不会偏离 可接受的命令

use std::io;

fn main() {
    println!("欢迎来到水果市场!");
    println!("请选择要购买的水果。\n");

    println!("\n可以购买的水果:苹果、香蕉、橘子、芒果、葡萄");
    println!("购买完成后,请输入“quit”或“q”。\n");

    // 获取用户输入
    let mut user_input = String::new();
    io::stdin()
        .read_line(&mut user_input)
        .expect("无法读取用户输入。");

    // 验证用户输入
    let valid_inputs = ["苹果", "香蕉", "橘子", "芒果", "葡萄", "quit", "q"];
    user_input = user_input.trim().to_lowercase();
    let mut input_error = true;
    for input in valid_inputs {
        if input == user_input {
            input_error = false;
            break;
        }
    }
}

要使验证更容易,我创建了一个叫做 valid_inputs 的字符串切片数组(第 17 行)。这个数组包含了所有可以购买的水果的名称,以及字符串切片 qquit,让用户可以传达他们是否希望退出。

用户可能不知道我们希望输入是什么样的。用户可能会输入“Apple”、“apple”或 “APPLE” 来表示他们想要购买苹果。我们的工作是正确处理这些输入。

在第 18 行,我通过调用 trim() 函数从 user_input 字符串中删除了尾部的换行符。为了处理上面提到的问题,我使用 to_lowercase() 函数将所有字符转换为小写,这样 “Apple”、“apple” 和 “APPLE” 都会变成 “apple”。

现在,来看第 19 行,我创建了一个名为 input_error 的可变布尔变量,初始值为 true。稍后在第 20 行,我创建了一个 for 循环,它遍历了 valid_inputs 数组的所有元素(字符串切片),并将迭代的模式存储在 input 变量中。

在循环内部,我检查用户输入是否等于其中一个有效字符串,如果是,我将 input_error 布尔值的值设置为 false,并跳出 for 循环。

处理无效输入

现在是时候处理无效输入了。这可以通过将一些代码移动到无限循环中来完成,如果用户给出无效输入,则 继续 该无限循环。

use std::io;

fn main() {
    println!("欢迎来到水果市场!");
    println!("请选择要购买的水果。\n");

    let valid_inputs = ["苹果", "香蕉", "橘子", "芒果", "葡萄", "quit", "q"];

    'mart: loop {
        let mut user_input = String::new();

        println!("\n可以购买的水果:苹果、香蕉、橘子、芒果、葡萄");
        println!("购买完成后,请输入“quit”或“q”。\n");

        // 读取用户输入
        io::stdin()
            .read_line(&mut user_input)
            .expect("无法读取用户输入。");
        user_input = user_input.trim().to_lowercase();

        // 验证用户输入
        let mut input_error = true;
        for input in valid_inputs {
            if input == user_input {
                input_error = false;
                break;
            }
        }

        // 处理无效输入
        if input_error {
            println!("错误: 请输入有效的输入");
            continue 'mart;
        }
    }
}

这里,我将一些代码移动到了循环内部,并重新组织了一下代码,以便更好地处理循环的引入。在循环内部,第 31 行,如果用户输入了一个无效的字符串,我将 continue mart 循环。

对用户输入做出反应

现在,所有其他的状况都已经处理好了,是时候写一些代码来让用户从水果市场购买水果了,当用户希望退出时,程序也会退出。

因为你也知道用户选择了哪种水果,所以让我们问一下他们打算购买多少,并告诉他们输入数量的格式。

use std::io;

fn main() {
    println!("欢迎来到水果市场!");
    println!("请选择要购买的水果。\n");

    let valid_inputs = ["苹果", "香蕉", "橘子", "芒果", "葡萄", "quit", "q"];

    'mart: loop {
        let mut user_input = String::new();
        let mut quantity = String::new();

        println!("\n可以购买的水果:苹果、香蕉、橘子、芒果、葡萄");
        println!("购买完成后,请输入“quit”或“q”。\n");

        // 读取用户输入
        io::stdin()
            .read_line(&mut user_input)
            .expect("无法读取用户输入。");
        user_input = user_input.trim().to_lowercase();

        // 验证用户输入
        let mut input_error = true;
        for input in valid_inputs {
            if input == user_input {
                input_error = false;
                break;
            }
        }

        // 处理无效输入
        if input_error {
            println!("错误: 请输入有效的输入");
            continue 'mart;
        }

        // 如果用户想要退出,就退出
        if user_input == "q" || user_input == "quit" {
            break 'mart;
        }

        // 获取数量
        println!(
            "\n你选择购买的水果是 \"{}\"。请输入以千克为单位的数量。
(1 千克 500 克的数量应该输入为 '1.5'。)",
            user_input
        );
        io::stdin()
            .read_line(&mut quantity)
            .expect("无法读取用户输入。");
    }
}

在第 11 行,我声明了另一个可变变量,它的值是一个空字符串,在第 48 行,我接受了用户的输入,但是这次是用户打算购买的水果的数量。

解析数量

我刚刚增加了一些代码,以已知的格式接受数量,但是这些数据被存储为字符串。我需要从中提取出浮点数。幸运的是,这可以通过 parse() 方法来完成。

就像 read_line() 方法一样,parse() 方法返回一个 Result 枚举。parse() 方法返回 Result 枚举的原因可以通过我们试图实现的内容来轻松理解。

我正在接受用户的字符串,并尝试将其转换为浮点数。浮点数有两个可能的值。一个是浮点数本身,另一个是小数。

字符串可以包含字母,但是浮点数不行。所以,如果用户输入的不是浮点数和小数,parse() 函数将会返回一个错误。

因此,这个错误也需要处理。我们将使用 expect() 函数来处理这个错误。

use std::io;

fn main() {
    println!("欢迎来到水果市场!");
    println!("请选择要购买的水果。\n");

    let valid_inputs = ["苹果", "香蕉", "橘子", "芒果", "葡萄", "quit", "q"];

    'mart: loop {
        let mut user_input = String::new();
        let mut quantity = String::new();

        println!("\n可以购买的水果:苹果、香蕉、橘子、芒果、葡萄");
        println!("购买完成后,请输入“quit”或“q”。\n");

        // 读取用户输入
        io::stdin()
            .read_line(&mut user_input)
            .expect("无法读取用户输入。");
        user_input = user_input.trim().to_lowercase();

        // 验证用户输入
        let mut input_error = true;
        for input in valid_inputs {
            if input == user_input {
                input_error = false;
                break;
            }
        }

        // 处理无效输入
        if input_error {
            println!("错误: 请输入有效的输入");
            continue 'mart;
        }

        // 如果用户想要退出,就退出
        if user_input == "q" || user_input == "quit" {
            break 'mart;
        }

        // 获取数量
        println!(
            "\n你选择购买的水果是 \"{}\"。请输入以千克为单位的数量。
(1 千克 500 克的数量应该输入为 '1.5'。)",
            user_input
        );
        io::stdin()
            .read_line(&mut quantity)
            .expect("无法读取用户输入。");

        let quantity: f64 = quantity
            .trim()
            .parse()
            .expect("请输入有效的数量。");

    }
}

如你所见,我通过变量遮蔽将解析后的浮点数存储在变量 quantity 中。为了告诉 parse() 函数,我的意图是将字符串解析为 f64,我手动将变量 quantity 的类型注释为 f64

现在,parse() 函数将会解析字符串并返回一个 f64 或者一个错误,expect() 函数将会处理这个错误。

计算价格 + 最后的修饰

现在我们知道了用户想要购买的水果及其数量,现在是时候进行计算了,并让用户知道结果/总价了。

为了真实起见,我将为每种水果设置两个价格。第一个价格是零售价,我们在购买少量水果时向水果供应商支付的价格。水果的第二个价格是当有人批量购买水果时支付的批发价。

批发价将会在订单数量大于被认为是批发购买的最低订单数量时确定。这个最低订单数量对于每种水果都是不同的。每种水果的价格都是每千克多少卢比。

想好了逻辑,下面是最终的程序。

use std::io;

const APPLE_RETAIL_PER_KG: f64 = 60.0;
const APPLE_WHOLESALE_PER_KG: f64 = 45.0;

const BANANA_RETAIL_PER_KG: f64 = 20.0;
const BANANA_WHOLESALE_PER_KG: f64 = 15.0;

const ORANGE_RETAIL_PER_KG: f64 = 100.0;
const ORANGE_WHOLESALE_PER_KG: f64 = 80.0;

const MANGO_RETAIL_PER_KG: f64 = 60.0;
const MANGO_WHOLESALE_PER_KG: f64 = 55.0;

const GRAPES_RETAIL_PER_KG: f64 = 120.0;
const GRAPES_WHOLESALE_PER_KG: f64 = 100.0;

fn main() {
    println!("欢迎来到水果市场!");
    println!("请选择要购买的水果。\n");

    let valid_inputs = ["苹果", "香蕉", "橘子", "芒果", "葡萄", "quit", "q"];

    'mart: loop {
        let mut user_input = String::new();
        let mut quantity = String::new();

        println!("\n可以购买的水果:苹果、香蕉、橘子、芒果、葡萄");
        println!("购买完成后,请输入“quit”或“q”。\n");

        // 读取用户输入
        io::stdin()
            .read_line(&mut user_input)
            .expect("无法读取用户输入。");
        user_input = user_input.trim().to_lowercase();

        // 验证用户输入
        let mut input_error = true;
        for input in valid_inputs {
            if input == user_input {
                input_error = false;
                break;
            }
        }

        // 处理无效输入
        if input_error {
            println!("错误: 请输入有效的输入");
            continue 'mart;
        }

        // 如果用户想要退出,就退出
        if user_input == "q" || user_input == "quit" {
            break 'mart;
        }

        // 获取数量
        println!(
            "\n你选择购买的水果是 \"{}\"。请输入以千克为单位的数量。
(1 千克 500 克的数量应该输入为 '1.5'。)",
            user_input
        );
        io::stdin()
            .read_line(&mut quantity)
            .expect("无法读取用户输入。");

        let quantity: f64 = quantity
            .trim()
            .parse()
            .expect("请输入有效的数量。");

        total += calc_price(quantity, user_input);
    }

    println!("\n\n总价是 {} 卢比。", total);
}

fn calc_price(quantity: f64, fruit: String) -> f64 {
    if fruit == "apple" {
        price_apple(quantity)
    } else if fruit == "banana" {
        price_banana(quantity)
    } else if fruit == "orange" {
        price_orange(quantity)
    } else if fruit == "mango" {
        price_mango(quantity)
    } else {
        price_grapes(quantity)
    }
}

fn price_apple(quantity: f64) -> f64 {
    if quantity > 7.0 {
        quantity * APPLE_WHOLESALE_PER_KG
    } else {
        quantity * APPLE_RETAIL_PER_KG
    }
}

fn price_banana(quantity: f64) -> f64 {
    if quantity > 4.0 {
        quantity * BANANA_WHOLESALE_PER_KG
    } else {
        quantity * BANANA_RETAIL_PER_KG
    }
}

fn price_orange(quantity: f64) -> f64 {
    if quantity > 3.5 {
        quantity * ORANGE_WHOLESALE_PER_KG
    } else {
        quantity * ORANGE_RETAIL_PER_KG
    }
}

fn price_mango(quantity: f64) -> f64 {
    if quantity > 5.0 {
        quantity * MANGO_WHOLESALE_PER_KG
    } else {
        quantity * MANGO_RETAIL_PER_KG
    }
}

fn price_grapes(quantity: f64) -> f64 {
    if quantity > 2.0 {
        quantity * GRAPES_WHOLESALE_PER_KG
    } else {
        quantity * GRAPES_RETAIL_PER_KG
    }
}

对比之前的版本,我做了一些改动……

水果的价格可能会波动,但是在我们程序的生命周期内,这些价格不会波动。所以我将每种水果的零售价和批发价存储在常量中。我将这些常量定义在 main() 函数之外(即全局常量),因为我不会在 main() 函数内计算每种水果的价格。这些常量被声明为 f64,因为它们将与 quantity 相乘,而 quantityf64。记住,Rust 没有隐式类型转换 ?

当水果名称和用户想要购买的数量被存下来之后,calc_price() 函数被调用来计算用户指定数量的水果的价格。这个函数接受水果名称和数量作为参数,并将价格作为 f64 返回。

当你看到 calc_price() 函数的内部时,你会发现它是许多人所说的包装函数。它被称为包装函数,因为它调用其他函数来完成它的脏活。

因为每种水果都有不同的最低订单数量,才能被认为是批发购买,为了确保代码在未来可以轻松维护,每种水果都有单独的函数负责计算价格。

所以,calc_price() 函数所做的就是确定用户选择了哪种水果,并调用相应的函数来计算所选水果的价格。这些水果特定的函数只接受一个参数:数量。这些水果特定的函数将价格作为 f64 返回。

现在,price_*() 函数只做一件事。它们检查订单数量是否大于被认为是批发购买的最低订单数量。如果是这样,quantity 将会乘以水果的每千克批发价格。否则,quantity 将会乘以水果的每千克零售价格。

由于乘法行末尾没有分号,所以函数返回乘积。

如果你仔细看看 calc_price() 函数中水果特定函数的函数调用,这些函数调用在末尾没有分号。这意味着,price_*() 函数返回的值将会被 calc_price() 函数返回给它的调用者。

而且 calc_price() 函数只有一个调用者。这个调用者在 mart 循环的末尾,这个调用者使用这个函数返回的值来增加 total 的值。

最终,当 mart 循环结束(当用户输入 qquit 时),存储在变量 total 中的值将会被打印到屏幕上,并且用户将会被告知他/她需要支付的价格。

总结

这篇文章中,我使用了之前讲解的 Rust 编程语言的所有主题来创建一个简单的程序,这个程序仍然在某种程度上展示了一个现实世界的问题。

现在,我写的代码肯定可以用一种更符合编程习惯的方式来写,这种方式最好地使用了 Rust 的喜爱特性,但是我还没有讲到它们!

所以,敬请关注后续的 将 Rust 带入下一个层次 系列,并学习更多 Rust 编程语言的内容!

Rust 基础系列到此结束。欢迎你的反馈。

(题图:MJ/6d486f23-e6fe-4bef-a28d-df067ef2ec06)


via: https://itsfoss.com/milestone-rust-program/

作者:Pratham Patel 选题:lkxed 译者:Cubik65536 校对:wxy

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