分类 技术 下的文章

许多大型代码库中都有 Lisp 代码的身影,因此,熟悉一下这门语言是一个明智之举。

早在 1958 年,Lisp 就被发明出来了,它是世界上第二古老的计算机编程语言(LCTT 译注:最古老的编程语言是 Fortran,诞生于 1957 年)。它有许多现代的衍生品,包括 Common Lisp、Emacs Lisp(Elisp)、Clojure、Racket、Scheme、Fennel 和 GNU Guile 等。

那些喜欢思考编程语言的设计的人,往往都喜欢 Lisp,因为它的语法和数据有着相同的结构:Lisp 代码实际上是 一个列表的列表 a list of lists ,它的名字其实是 “ 列表处理 LISt Processing ” 的简写。而那些喜欢思考编程语言的美学的人,往往都讨厌 Lisp,因为它经常使用括号来定义范围;事实上,编程界也有一个广为流传的笑话:Lisp 代表的其实是 “大量烦人的多余括号” Lots of Irritating Superfluous Parentheses

不管你是喜欢还是讨厌 Lisp 的设计哲学,你都不得不承认,它都是一门有趣的语言,过去如此,现在亦然(这得归功于现代方言 Clojure 和 Guile)。你可能会惊讶于在任何特定行业的大代码库中潜伏着多少 Lisp 代码,因此,现在开始学习 Lisp,至少熟悉一下它,不失为一个好主意。

安装 Lisp

Lisp 有很多不同的实现。比较流行的开源版本有 SBCLGNU LispGNU Common Lisp(GCL)。你可以使用发行版的包管理器安装它们中的任意一个,在本文中,我是用的是 clisp(LCTT 译注:也就是 GNU Lisp,一种 ANSI Common Lisp 的实现)。

以下是在不同的 Linux 发行版中安装 clisp 的步骤。

在 Fedora Linux 上,使用 dnf

$ sudo dnf install clisp

在 Debian 上,使用 apt

$ sudo apt install clisp

在 macOS 上,使用 MacPorts 或者 Homebrew

# 使用 MacPorts
$ sudo port install clisp

# 使用 Homebrew
$ brew install clisp

在 Windows 上,你可以使用 clisp on Cygwin 或者从 gnu.org/software/gcl 上下载 GCL 的二进制文件。

虽然我使用 clisp 命令来运行 Lisp 代码,但是本文中涉及到的大多数语法规则,对任何 Lisp 实现都是适用的。如果你选择使用一个不同的 Lisp 实现,除了用来运行 Lisp 代码的命令会和我不一样外(比如,你可能要用 gclsbcl 而不是 clisp),其它的所有东西都是相同的。

列表处理

Lisp 源代码的基本单元是 “ 表达式 expression ”,它在形式上是一个列表。举个例子,下面就是一个列表,它由一个操作符(+)和两个整数(12)组成:

(+ 1 2)

同时,它也是一个 Lisp 表达式,内容是一个符号(+,会被解析成一个加法函数)和它的两个参数(12)。你可以在 Common Lisp 的交互式环境(即 REPL)中运行该表达式和其它表达式。如果你熟悉 Python 的 IDLE,那么你应该会对 Lisp 的 REPL 感到亲切。(LCTT 译注:REPL 的全称是 “Read-Eval-Print Loop”,意思是 “‘读取-求值-输出’循环”,这个名字很好地描述了它的工作过程。)

要进入到 REPL 中,只需运行 Common Lisp 即可:

$ clisp
[1]>

在 REPL 提示符中,尝试输入一些表达式:

[1]> (+ 1 2)
3
[2]> (- 1 2)
-1
[3]> (- 2 1)
1
[4]> (+ 2 3 4)
9

函数

在了解了 Lisp 表达式的基本结构后,你可以使用函数来做更多有用的事。譬如,print 函数可以接受任意数量的参数,然后把它们都显示在你的终端上,pprint 函数还可以实现格式化打印。还有更多不同的打印函数,不过,pprint 在 REPL 中的效果就挺好的:

[1]> (pprint "hello world")

"hello world"

[2]>

你可以使用 defun 函数来创建一个自定义函数。defun 函数需要你提供自定义函数的名称,以及它接受的参数列表:

[1]> (defun myprinter (s) (pprint s))
MYPRINTER
[2]> (myprinter "hello world")

"hello world"

[3]>

变量

你可以使用 setf 函数来在 Lisp 中创建变量:

[1]> (setf foo "hello world")
"hello world"
[2]> (pprint foo)

"hello world"

[3]>

你可以在表达式里嵌套表达式(就像使用某种管道一样)。举个例子,你可以先使用 string-upcase 函数,把某个字符串的所有字符转换成大写,然后再使用 pprint 函数,将它的内容格式化打印到终端上:

[3]> (pprint (string-upcase foo))

"HELLO WORLD"

[4]>

Lisp 是动态类型语言,这意味着,你在给变量赋值时不需要声明它的类型。Lisp 默认会把整数当作整数来处理:

[1]> (setf foo 2)
[2]> (setf bar 3)
[3]> (+ foo bar)
5

如果你想让整数被当作字符串来处理,你可以给它加上引号:

[4]> (setf foo "2")
"2"
[5]> (setf bar "3")
"3"
[6]> (+ foo bar)

*** - +: "2" is not a number
The following restarts are available:
USE-VALUE      :R1      Input a value to be used instead.
ABORT          :R2      Abort main loop
Break 1 [7]>

在这个示例 REPL 会话中,变量 foobar 都被赋值为加了引号的数字,因此,Lisp 会把它们当作字符串来处理。数学运算符不能够用在字符串上,因此 REPL 进入了调试器模式。想要跳出这个调试器,你需要按下 Ctrl+D 才行(LCTT 译注:就 clisp 而言,使用 quit 关键字也可以退出)。

你可以使用 typep 函数对一些对象进行类型检查,它可以测试对象是否为某个特定数据类型。返回值 TNIL 分别代表 TrueFalse

[4]> (typep foo 'string)
NIL
[5]> (typep foo 'integer)
T

stringinteger 前面加上了一个单引号('),这是为了防止 Lisp(错误地)把这两个单词当作是变量来求值:

[6]> (typep foo string)
*** - SYSTEM::READ-EVAL-PRINT: variable STRING has no value
[...]

这是一种保护某些术语(LCTT 译注:类似于字符串转义)的简便方法,正常情况下它是用 quote 函数来实现的:

[7]> (typep foo (quote string))
NIL
[5]> (typep foo (quote integer))
T

列表

不出人意料,你当然也可以在 Lisp 中创建列表:

[1]> (setf foo (list "hello" "world"))
("hello" "world")

你可以使用 nth 函数来索引列表:

[2]> (nth 0 foo)
"hello"
[3]> (pprint (string-capitalize (nth 1 foo)))

"World"

退出 REPL

要结束一个 REPL 会话,你需要按下键盘上的 Ctrl+D,或者是使用 Lisp 的 quit 关键字:

[99]> (quit)
$

编写脚本

Lisp 可以被编译,也可以作为解释型的脚本语言来使用。在你刚开始学习的时候,后者很可能是最容易的方式,特别是当你已经熟悉 Python 或 Shell 脚本 时。

下面是一个用 Common Lisp 编写的简单的“掷骰子”脚本:

#!/usr/bin/clisp

(defun roller (num)  
  (pprint (random (parse-integer (nth 0 num))))
)

(setf userput *args*)
(setf *random-state* (make-random-state t))
(roller userput)

脚本的第一行注释(LCTT 译注:称之为“ 释伴 shebang ”)告诉了你的 POSIX 终端,该使用什么可执行文件来运行这个脚本。

roller 函数使用 defun 函数创建,它在内部使用 random 函数来打印一个伪随机数,这个伪随机数严格小于 num 列表中下标为 0 的元素。在脚本中,这个 num 列表还没有被创建,不过没关系,因为只有当脚本被调用时,函数才会执行。

接下来的那一行,我们把运行脚本时提供的任意参数,都赋值给一个叫做 userput 的变量。这个 userput 变量是一个列表,当它被传递给 roller 函数后,它就会变成参数 num

脚本的倒数第二行产生了一个“随机种子”。这为 Lisp 提供了足够的随机性来生成一个几乎随机的数字。

最后一行调用了自定义的 roller 函数,并将 userput 列表作为唯一的参数传递给它。

将这个文件保存为 dice.lisp,并赋予它可执行权限:

$ chmod +x dice.lisp

最后,运行它,并给它提供一个数字,以作为它选择随机数的最大值:

$ ./dice.lisp 21

13
$ ./dice.lisp 21

7
$ ./dice.lisp 21

20

看起来还不错!

你或许注意到,你的模拟骰子有可能会是 0,并且永远达不到你提供给它的最大值参数。换句话说,对于一个 20 面的骰子,这个脚本永远投不出 20(除非你把 0 当作 20)。有一个简单的解决办法,它只需要用到在本文中介绍的知识,你能够想到吗?

学习 Lisp

无论你是想将 Lisp 作为个人脚本的实用语言,还是为了助力你的职业生涯,抑或是仅仅作为一个有趣的实验,你都可以去看看一年一度(LCTT 译注:应该是两年一度)的 Lisp 游戏果酱 Game Jam ,从而收获一些特别有创意的用途(其中的大多数提交都是开源的,因此你可以查看代码以从中学习)。

Lisp 是一门有趣而独特的语言,它有着不断增长的开发者用户群、足够悠久的历史和新兴的方言,因此,它有能力让从事各个行业的程序员都满意。


via: https://opensource.com/article/21/5/learn-lisp

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

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

极客们好,将 Ubuntu 22.04(Jammy Jellyfish)启动到 救援 Rescue 紧急 Emergency 模式可以重置忘记的用户密码、修复文件系统错误,以及在启动过程中禁用或启用 systemd 服务。

在这篇文章中,我们将学习如何启动 Ubuntu 22.04 LTS 系统进入救援和应急模式。救援模式类似于单用户模式,所有的故障排除步骤都在这里进行。救援模式加载最小的环境并挂载根文件系统。

而在紧急模式下,我们得到的是单用户 Shell,而不启动任何系统服务。因此,当我们无法启动系统进入救援模式时,就需要紧急模式。

启动 Ubuntu 22.04 进入救援或单用户模式

前往你想启动到救援或单用户模式的目标系统。在启动时按下 SHIFT + ESC 键,进入 GRUB 引导加载器页面。

Default-Grub-Screen-Ubuntu-22-04

选择第一个选项 “Ubuntu”,并按 e 键进入编辑模式。

在以 linux 开头的一行末尾,删除字符串 $vt_handoff 并添加字符串 systemd.unit=rescue.target

rescue-target-ubuntu-22-04

做完修改后,按 Ctrl + XF10 在救援模式下启动。

Troubleshooting-Commands-in-Rescue-Mode

进入救援模式后,运行所有的故障排除命令,并运行 systemctl reboot 命令来重启系统。

另一种启动系统进入救援模式的方法

重新启动系统并按下 ESC + Shift 键,进入 GRUB 启动界面。

选择第二个选项 “ Ubuntu 高级选项 Advanced Options for Ubuntu ”->选择“ 恢复模式 recovery mode ”选项并点击回车->选择 root(进入 root shell 提示符) root (Drop to root shell prompt)

下面是一个例子:

Boot-Ubuntu-22-04-Rescue-Mode

当你有了 root Shell,运行命令来恢复和修复系统问题,最后使用 systemctl reboot 来重启系统。

引导 Ubuntu 22.04 进入紧急模式

要启动系统进入紧急模式,首先进入 GRUB 页面。

Default-Grub-Screen-Ubuntu-22-04

选择第一个选项 “Ubuntu” 并按 e 键进行编辑。寻找以 linux 开头的一行,移到该行的末尾,删除字符串 $vt_handoff 并添加字符串 systemd.unit=emergency.target

Emergency-Mode-Ubuntu-22-04

Ctrl + XF10 将系统启动到紧急模式。

Command-in-Emergency-Mode-Ubuntu-22-04

同样,在紧急模式下,你可以在这个模式下执行所有的故障排除,完成后,就用 systemctl reboot 命令重启系统。

这篇文章的内容就这些。文章内容丰富,不要犹豫,请在你的技术朋友中分享它。请在下面的评论区发表你的疑问和反馈。


via: https://www.linuxtechi.com/boot-ubuntu-22-04-rescue-emergency-mode/

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

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

大家好!我又写了一篇关于 我最喜欢的电脑工具 的文章。这一篇讲的是 Docker Compose!

本文主要就是讲一讲我对 Docker Compose 有多么满意啦(不讨论它的缺点)!咳咳,因为它总能够完成它该做的,并且似乎总能有效,更棒的是,它的使用还非常简单。另外,在本文中,我只讨论我是如何用 Docker Compose 来搭建开发环境的,而不涉及它在生产中的使用。

最近,我考虑了很多关于这种个人开发环境的搭建方式,原因是,我现在把所有的计算工作都搬到了一个私有云上,大概 20 美元/月的样子。这样一来,我就不用在工作的时候花时间去思考应该如何管理几千台 AWS 服务器了。

在此之前,我曾花了两天的时间,尝试使用其他的工具来尝试搭建一个开发环境,搭到后面,我实在是心累了。相比起来,Docker Compose 就简单易用多了,我非常满意。于是,我和妹妹分享了我的 docker-compose 使用经历,她略显惊讶:“是吧!你也觉得 Docker Compose 真棒对吧!” 嗯,我觉得我应该写一篇博文把过程记录下来,于是就有了你们看到的这篇文章。

我们的目标是:搭建一个开发环境

目前,我正在编写一个 Ruby on Rails 服务(它是一个计算机“调试”游戏的后端)。在我的生产服务器上,我安装了:

  • 一个 Nginx 服务器
  • 一个 Rails 服务
  • 一个 Go 服务(使用了 gotty 来代理一些 SSH 连接)
  • 一个 Postgres 数据库

在本地搭建 Rails 服务非常简单,用不着容器(我只需要安装 Postgres 和 Ruby 就行了,小菜一碟)。但是,我还想要把匹配 /proxy/* 的请求的发送到 Go 服务,其他所有请求都发送到 Rails 服务,所以需要借助 Nginx。问题来了,在笔记本电脑上安装 Nginx 对我来说太麻烦了。

是时候使用 docker-compose 了!

docker-compose 允许你运行一组 Docker 容器

基本上,Docker Compose 的作用就是允许你运行一组可以互相通信 Docker 容器。

你可以在一个叫做 docker-compose.yml 的文件中,配置你所有的容器。我在下方将贴上我为这个服务编写的 docker-compose.yml 文件(完整内容),因为我觉得它真的很简洁、直接!

version: "3.3"
services:
  db:
    image: postgres
    volumes:
      - ./tmp/db:/var/lib/postgresql/data
    environment:
      POSTGRES_PASSWORD: password # yes I set the password to 'password'
  go_server:
    # todo: use a smaller image at some point, we don't need all of ubuntu to run a static go binary
    image: ubuntu
    command: /app/go_proxy/server
    volumes:
      - .:/app
  rails_server:
    build: docker/rails
    command: bash -c "rm -f tmp/pids/server.pid && source secrets.sh && bundle exec rails s -p 3000 -b '0.0.0.0'"
    volumes:
      - .:/app
  web:
    build: docker/nginx
    ports:
      - "8777:80" # this exposes port 8777 on my laptop

这个配置包含了两种容器。对于前面两个容器,我直接使用了现有的镜像(image: postgresimage: ubuntu)。对于后面两个容器,我不得不构建一个自定义容器镜像,其中, build: docker/rails 的作用就是告诉 Docker Compose,它应该使用 docker/rails/Dockerfile 来构建一个自定义容器。

我需要允许我的 Rails 服务访问一些 API 密钥和其他东西,因此,我使用了 source secrets.sh,它的作用就是在环境变量中预设一组密钥。

如何启动所有服务:先 “build” 后 “up”

我一直都是先运行 docker-compose build 来构建容器,然后再运行 docker-compose up 把所有服务启动起来。

你可以在 yaml 文件中设置 depends_on,从而进行更多启动容器的控制。不过,对于我的这些服务而言,启动顺序并不重要,所以我没有设置它。

网络互通也非常简单

容器之间的互通也是一件很重要的事情。Docker Compose 让这件事变得超级简单!假设我有一个 Rails 服务正在名为 rails_server 的容器中运行,端口是 3000,那么我就可以通过 http://rails_server:3000 来访问该服务。就是这么简单!

以下代码片段截取自我的 Nginx 配置文件,它是根据我的使用需求配置的(我删除了许多 proxy_set_headers 行,让它看起来更清楚):

location ~ /proxy.* {
  proxy_pass http://go_server:8080;
}
location @app {
  proxy_pass http://rails_server:3000;
}

或者,你可以参考如下代码片段,它截取自我的 Rails 项目的数据库配置,我在其中使用了数据库容器的名称(db):

development:
  <<: *default
  database: myproject_development
  host: db # <-------- 它会被“神奇地”解析为数据库容器的 IP 地址
  username: postgres
  password: password

至于 rails_server 究竟是如何被解析成一个 IP 地址的,我还真有点儿好奇。貌似是 Docker 在我的计算机上运行了一个 DNS 服务来解析这些名字。下面是一些 DNS 查询记录,我们可以看到,每个容器都有它自己的 IP 地址:

$ dig +short @127.0.0.11 rails_server
172.18.0.2
$ dig +short @127.0.0.11 db
172.18.0.3
$ dig +short @127.0.0.11 web
172.18.0.4
$ dig +short @127.0.0.11 go_server
172.18.0.5

是谁在运行这个 DNS 服务?

我(稍微)研究了一下这个 DNS 服务是怎么搭建起来的。

以下所有命令都是在容器外执行的,因为我没有在容器里安装很多网络工具。

第一步::使用 ps aux | grep puma,获取 Rails 服务的进程 ID。

找到了,它是 1837916!简单~

第二步::找到和 1837916 运行在同一个网络命名空间的 UDP 服务。

我使用了 nsenter 来在 puma 进程的网络命令空间内运行 netstat(理论上,我猜想你也可以使用 netstat -tupn 来只显示 UDP 服务,但此时,我的手指头只习惯于打出 netstat -tulpn)。

$ sudo nsenter -n -t 1837916 netstat -tulpn
Active Internet connections (only servers)
Proto Recv-Q Send-Q Local Address           Foreign Address         State       PID/Program name
tcp        0      0 127.0.0.11:32847        0.0.0.0:*               LISTEN      1333/dockerd
tcp        0      0 0.0.0.0:3000            0.0.0.0:*               LISTEN      1837916/puma 4.3.7
udp        0      0 127.0.0.11:59426        0.0.0.0:*                           1333/dockerd

我们可以看到,此时有一个运行在 59426 端口的 UDP 服务,它是由 dockerd 运行的!或许它就是我们要找的 DNS 服务?

第三步:确定它是不是我们要找的 DNS 服务

我们可以使用 dig 工具来向它发送一个 DNS 查询:

$ sudo nsenter -n -t 1837916 dig +short @127.0.0.11 59426 rails_server
172.18.0.2

奇怪,我们之前运行 dig 的时候,DNS 查询怎么没有发送到 59426 端口,而是发送到了 53 端口呢?这到底是怎么回事呀?

第四步:iptables

对于类似“这个服务似乎正运行在 X 端口上,但我却在 Y 端口上访问到了它,这是什么回事呢?”的问题,我的第一念头都是“一定是 iptables 在作怪”。

于是,我在运行了容器的网络命令空间内执行 iptables-save,果不其然,真相大白:

$ sudo nsenter -n -t 1837916 iptables-save
.... redacted a bunch of output ....
-A DOCKER_POSTROUTING -s 127.0.0.11/32 -p udp -m udp --sport 59426 -j SNAT --to-source :53
COMMIT

在输出中有一条 iptables 规则,它将 53 端口的流量发送到了 59426 上。哈哈,真有意思!

数据库文件储存在一个临时目录中

这样做有一个好处:我可以直接挂载 Postgres 容器的数据目录 ./tmp/db,而无需在我的笔记本电脑上管理 Postgres 环境。

我很喜欢这种方式,因为我真的不想在笔记本电脑上独自管理一个 Postgres 环境(我也真的不知道该如何配置 Postgres)。另外,出于习惯,我更喜欢让开发环境的数据库和代码放在同一个目录下。

仅需一行命令,我就可以访问 Rails 控制台

管理 Ruby 的版本总是有点棘手,并且,即使我暂时搞定了它,我也总是有点担心自己会把 Ruby 环境搞坏,然后就要修它个十年(夸张)。

(使用 Docker Compose)搭建好这个开发环境后,如果我需要访问 Rails 控制台 console (一个交互式环境,加载了所有我的 Rails 代码),我只需要运行一行代码即可:

$ docker-compose exec rails_server rails console
Running via Spring preloader in process 597
Loading development environment (Rails 6.0.3.4)
irb(main):001:0>

好耶!

小问题:Rails 控制台的历史记录丢失了

我碰到了一个问题:Rails 控制台的历史记录丢失了,因为我一直在不断地重启它。

不过,我也找到了一个相当简单的解决方案(嘿嘿):我往容器中添加了一个 /root/.irbrc 文件,它能够把 IRB 历史记录文件的保存位置指向一个不受容器重启影响的地方。只需要一行代码就够啦:

IRB.conf[:HISTORY_FILE] = "/app/tmp/irb_history"

我还是不知道它在生产环境的表现如何

到目前为止,这个项目的生产环境搭建进度,还停留在“我制作了一个 DigitalOcean droplet(LCCT 译注:一种 Linux 虚拟机服务),并手工编辑了很多文件”的阶段。

嗯……我相信以后会在生产环境中使用 docker-compose 来运行一下它的。我猜它能够正常工作,因为这个服务很可能最多只有两个用户在使用,并且,如果我愿意,我可以容忍它在部署过程中有 60 秒的不可用时间。不过话又说回来,出错的往往是我想不到的地方。

推特网友提供了一些在生产中使用 docker-compose 的注意事项:

  • docker-compose up 只会重启那些需要重启的容器,这会让重启速度更快。
  • 有一个 Bash 小脚本 wait-for-it,你可以用它来保持等待一个容器,直到另一个容器的服务可用。
  • 你可以准备两份 docker-compose.yaml 文件:用于开发环境的 docker-compose.yaml 和用于生产环境的 docker-compose-prod.yaml。我想我会在分别为 Nginx 指定不同的端口:开发时使用 8999,生产中使用 80
  • 人们似乎一致认为,如果你的项目是一台计算机上运行的小网站,那么 docker-compose 在生产中不会有问题。
  • 有个人建议说,如果愿意在生产环境搭建复杂那么一丢丢,Docker Swarm 就或许会是更好的选择,不过我还没试过(当然,如果要这么说的话,干嘛不用 Kubernetes 呢?Docker Compose 的意义就是它超级简单,而 Kubernetes 肯定不简单 : ))。

Docker 似乎还有一个特性,它能够 把你用 docker-compose 搭建的环境,自动推送到弹性容器服务(ESC)上,听上去好酷的样子,但是我还没有试过。

docker-compose 会有不适用的场景吗

我听说 docker-compose 在以下场景的表现较差:

  • 当你有很多微服务的时候(还是自己搭建比较好)
  • 当你尝试从一个很大的数据库中导入数据时(就像把几百 G 的数据存到每个人的笔记本电脑里一样)
  • 当你在 Mac 电脑上运行 Docker 时。我听说 Docker 在 macOS 上比在 Linux 上要慢很多(我猜想是因为它需要做额外的虚拟化)。我没有 Mac 电脑,所以我还没有碰到这个问题。

以上就是全部内容啦!

在此之前,我曾花了一整天时间,尝试使用 Puppet 来配置 Vagrant 虚拟机,然后在这个虚拟机里配置开发环境。结果,我发现虚拟机启动起来实在是有点慢啊,还有就是,我也不喜欢编写 Puppet 配置(哈哈,没想到吧)。

幸好,我尝试了 Docker Compose,它真好简单,马上就可以开始工作啦!


via: https://jvns.ca/blog/2021/01/04/docker-compose-is-nice/

作者:Julia Evans 选题:lujun9972 译者:lkxed 校对:turbokernel

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

WineZGUI - 一个使用 Zenity 的 Wine GUI 前台

不久前,我们写了关于 Bottles 的文章,这是一个开源的图形应用,可以在 Linux 操作系统上轻松运行 Windows 软件和游戏。今天,我们将讨论一个类似的有趣项目。向 WineZGUI 打个招呼,它是一个 Wine GUI 前台,可以 在 Linux 上用 Wine 运行 Windows 应用和游戏

什么是 WineZGUI?

WineZGUI 是一个 Bash 脚本的集合,它允许你轻松地管理 Wine 前缀,并在 Linux 上使用 Zenity 提供更轻松的 Wine 游戏体验。

(LCTT 译注:Wine 前缀是一个特殊文件夹,Wine 在其中放置所有 Wine 特定的文件,安装 Windows 程序、库和注册表代码,以及用户首选项。)

使用 WineZGUI,我们可以直接从文件管理器中启动 Windows EXE 文件或游戏,而无需安装它们。

WineZGUI 为每个应用或游戏创建快捷方式,以便于访问,同时也为每个 EXE 二进制文件创建单独的前缀。

当你用 WineZGUI 启动一个 Windows EXE 文件时,它会提示你是否使用默认的 Wine 前缀或创建一个新的前缀。默认的前缀是 ~/.local/share/winezgui/default

如果你选择为 Windows 二进制文件(EXE)创建一个新的前缀,WineZGUI 将尝试从 EXE 文件中提取产品名称和图标,并创建一个桌面快捷方式。

当你以后启动相同的二进制文件(EXE)时,它将建议你用先前的相关前缀来运行它。

说得通俗一点,WineZGUI 只是一个用于官方原始 Wine 的简单 GUI。当我们启动一个 EXE 来玩游戏时,Wine 前缀的设置是自动的。

你只需打开一个 EXE,它就会创建一个前缀和一个桌面快捷方式,并从该 EXE 中提取名称和图标。

它使用 exiftoolicotool 工具来分别提取名称和图标。你可以通过现有的前缀打开一个 EXE 来启动该游戏,或者使用桌面快捷方式。

WineZGUI 是一个在 GitHub 上免费托管的 shell 脚本。你可以抓取源代码,改进它,修复错误和增加功能。

Bottles Vs WineZGUI

你可能想知道 WineZGUI 与 Bottles 相比如何。但这些应用之间有一个微妙的区别。

Bottles 是面向前缀的面向运行器的。意思是:Bottles 首先创建一个前缀,然后使用不同的 EXE 文件。Bottles 不会记住 EXE 的前缀。Bottles 使用不同的运行器。

WineZGUI 是面向 EXE 的。它使用 EXE 并只为该 EXE 创建一个前缀。下次我们打开一个 EXE 时,它将询问是否用现有的 EXE 前缀启动。

WineZGUI 不提供像 Bottles 或 lutris 那样的高级功能,如运行程序、在线安装程序等。

如何在 Linux 中安装 WineZGUI

确保你已经安装了 WineZGUI 的必要先决条件。

Debian/Ubuntu:

$ sudo dpkg --add-architecture i386
$ sudo apt install zenity wine winetricks libimage-exiftool-perl icoutils gnome-terminal

Fedora:

$ sudo dnf install zenity wine winetricks perl-Image-ExifTool icoutils gnome-terminal

官方推荐的安装 WineZGUI 的方法是使用 Flatpak

安装完 Flatpak 后,逐一运行以下命令,在 Linux 中安装 WineZGUI。

$ flatpak --user remote-add --if-not-exists flathub https://flathub.org/repo/flathub.flatpakrepo
$ flatpak --user -y install flathub org.winehq.Wine/x86_64/stable-21.08
$ wget https://github.com/fastrizwaan/WineZGUI-Releases/releases/download/WineZGUI-0.4_20220608/io.github.WineZGUI_0_4_20220608.flatpak
$ flatpak --user -y install io.github.WineZGUI_0_4_20220608.flatpak

在 Linux 中用 WineZGUI 运行 Windows 应用和游戏

从 Dash 或菜单中启动 WineZGUI。

Launch WineZGUI

这就是 WineZGUI 的默认界面的样子。

WineZGUI Interface

正如你在上面的截图中看到的,WineZGUI 的界面非常简单易懂。从主窗口中,你可以:

  • 打开一个 EXE 文件。
  • 打开 Winetricks GUI 和 CLI。
  • 启动 Wine 配置。
  • 启动资源管理器。
  • 打开 BASH Shell。
  • 关闭所有的应用/游戏,包括 WineZGUI 界面。
  • 删除 Wine 前缀。
  • 查看已安装的 WineZGUI 版本。

为了演示,我将打开一个 EXE 文件。

在下一个窗口中,选择要运行的 EXE 文件。在我的例子中,它是 WinRAR。

Choose The EXE File To Run

接下来,你是想用默认的前缀运行 EXE 文件,还是创建一个新的前缀。我选择默认的前缀。

Run WinRAR With Default Prefix

几秒钟后,会出现 WinRAR 安装向导。点击安装,继续。

Install WinRAR In Linux

点击 “OK” 来完成 WinRAR 的安装。

Complete WinRAR Installation

点击 “ 运行 WinRAR Run WinRAR ” 来启动它。

Run WinRAR

下面是 WinRAR 在我的 Fedora 36 桌面上的运行情况!

WinRAR Is Running In Fedora Using Wine

总结

WineZGUI 是俱乐部的新人。如果你正在寻找一种在 Linux 桌面上使用 Wine 运行 Windows 应用和游戏的更简单方法,WineZGUI 可能是一个不错的选择。

在 WineZGUI 的帮助下,用户可以选择在与 EXE 相同的文件夹中创建一个 Wine 前缀,并创建一个相对链接的 .desktop 条目来自动执行此操作。

原因是使用 Wine 前缀备份和删除游戏更容易,并且让它生成一个 .desktop 将使其能够适应移动和转移。

一个很酷的场景是使用该应用进行设置,然后将 Wine 前缀分享给你的朋友和其他人,他们只需要一个具有所有依赖性和保存的工作 Wine 前缀。

请试一试它,在下面的评论区告诉我们你对这个项目的看法。

资源:


via: https://ostechnix.com/winezgui-run-windows-apps-and-games-on-linux/

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

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

在这篇文章中学习混沌工程的基础知识。

 title=

混沌工程是由科学、规划以及实验组成的。它是一门在系统上进行实验的学科,用来建立系统在生产中承受混乱条件能力的信心。

首先,我会在文章导论部分解释混沌系统如何工作。

如何开始学习混沌系统呢?

以我的经验,开始学习混沌系统的最好方式是触发一个此前生产中出现的事故来进行实验。使用过去的数据,制定一个计划,以相同的方式破坏你的系统,然后建立修复策略,并确认结果满足你预期。如果计划失败,你就有了一种新的实验方式,并朝着快速处理问题的新方式前进。

最重要的是,你可以随时记录所有内容,这意味着,随着时间的推移,整个系统将被完整记录下来,任何人都可以值守而无需太多加码,每个人都可以在周末好好休息。

你要在混沌工程中做什么?

混沌系统实验运行背后有一些科学依据。我记录了其中一些步骤:

  1. 定义一个稳定状态: 使用监控工具来搜集当系统没有问题或事故时,看起来功能正常的数据。
  2. 提出假设或使用先前的事故: 现在你已经定义了一个稳定状态,请提出一个关于在事故或中断期间会发生(或发生过)的情况的假设。用这个假设来得出一系列将会发生的事故,以及如何解决问题的理论。然后你可以制定一个故意引发该问题的计划。
  3. 引发问题: 用这个计划来破坏系统,并开始在真实环境中测试。收集破坏时的指标状态,按计划修复,并追踪提出解决方案所需时长。确保你把所有的东西都记录下来,以备将来发生故障时使用。
  4. 试图推翻你的假设: 实验中最精彩的部分是尝试推翻你的思考或计划。你要创建一个不同的状态,看看你能走多远,并在系统中生成一个不同的稳定状态。

确保在你在另一个系统中生成的破坏因素前,建立一个处于稳定状态的控制系统。这将使你更容易在实验前、期间和之后发现各种稳定状态的差异。

混沌工程需要什么?

这有一些初学混沌工程很好的工具:

  • 良好的文档编制方法
  • 一个捕捉你系统是否处于稳定状态的监控系统

    • Grafana
    • Prometheus
  • 混沌工程工具:

    • Chaos mesh
    • Litmus
    • 之后的文章我会介绍更多
  • 一个假设
  • 一个计划

去搞破坏吧

现在你已经掌握了基础,是时候去安全的摧毁你的系统了。我计划每年制造四次混乱,然后努力实现每月一次的破坏。

混沌工程是一种很好的实践,也是推进你的内部文档保持最新的好方法。此外,随着时间的推移,新升级或应用程序部署将更加顺畅,你的日常生活管理将通过 Kubernetes 变得更加轻松。


via: https://opensource.com/article/21/5/kubernetes-chaos

作者:Jessica Cherry 选题:lujun9972 译者:Donkey 校对:turbokernel, wxy

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

对于程序员来说,掌握 Java 的内存管理机制并不是必须的,但它能够帮助你更好地理解 JVM 是如何处理程序中的变量和类实例的。

Java 之所以能够如此流行,自动 垃圾回收 Garbage Collection (GC)功不可没,它也是 Java 最重要的几个特性之一。在这篇文章中,我将说明为什么垃圾回收如此重要。本文的主要内容为:自动的分代垃圾回收、JVM 划分内存的依据,以及 JVM 垃圾回收的工作原理。

Java 内存分配

Java 程序的内存空间被划分为以下四个区域:

  1. 堆区 Heap :对象实例就是在这个区域分配的。不过,当我们声明一个对象时,堆中不会发生任何内存分配,只是在栈中创建了一个对象的引用而已。
  2. 栈区 Stack :方法、局部变量和类的实例变量就是在这个区域分配的。
  3. 代码区 Code :这个区域存放了程序的字节码。
  4. 静态区 Static :这个区域存放了程序的静态数据和静态方法。

什么是自动垃圾回收?

自动垃圾回收是这样一个过程:首先,堆中的所有对象会被分类为“被引用的”和“未被引用的”;接着,“未被引用的对象”就会被做上标记,以待之后删除。其中,“被引用的对象”是指程序中的某一部分仍在使用的对象,“未被引用的对象”是指目前没有正在被使用的对象。

许多编程语言,例如 C 和 C++,都需要程序员手动管理内存的分配和释放。在 Java 中,这一过程是通过垃圾回收机制来自动完成的(尽管你也可以在代码中调用 system.gc(); 来手动触发垃圾回收)。

垃圾回收的基本步骤如下:

1、标记已使用和未使用的对象

在这一步骤中,已使用和未使用的对象会被分别做上标记。这是一个及其耗时的过程,因为需要扫描内存中的所有对象,才能够确定它们是否正在被使用。

标记已使用和未使用的对象

2、扫描/删除对象

有两种不同的扫描和删除算法:

简单删除(标记清除):它的过程很简单,我们只需要删除未被引用的对象即可。但是,后续给新对象分配内存就会变得很困难了,因为可用空间被分割成了一块块碎片。

标记清除的过程

删除压缩(标记整理):除了会删除未被引用的对象,我们还会压缩被引用的对象(未被删除的对象)。这样以来,新对象的内存分配就相对容易了,并且内存分配的效率也有了提升。

标记整理的过程

什么是分代垃圾回收,为什么需要它?

正如我们在“扫描删除”模型中所看到的,一旦对象不断增长,我们就很难扫描所有未使用的对象以回收内存。不过,有一项实验性研究指出,在程序执行期间创建的大多数对象,它们的存活时间都很短。

既然大多数对象的存活时间都很短,那么我们就可以利用这个事实,从而提升垃圾回收的效率。该怎么做呢?首先,JVM 将内存划分为不同的“代”。接着,它将所有的对象都分类到这些内存“代”中,然后对这些“代”分别执行垃圾回收。这就是“分代垃圾回收”。

堆内存的“代”和分代垃圾回收过程

为了提升垃圾回收中的“标记清除”的效率,JVM 将对内存划分成以下三个“代”:

  • 新生代 Young Generation
  • 老年代 Old Generation
  • 永久代 Permanent Generation

Hotspot 堆内存结构

下面我将介绍每个“代”及其主要特征。

新生代

所有创建不久的对象都存放在这里。新生代被进一步分为以下两个区域:

  1. 伊甸区 Eden :所有新创建的对象都在此处分配内存。
  2. 幸存者区 Survivor ,分为 S0 和 S1:经历过一次垃圾回收后,仍然存活的对象会被移动到两个幸存者区中的一个。

对象分配

在新生代发生的分代垃圾回收被称为 “ 次要回收 Minor GC ”(LCTT 译注:也称为“ 新生代回收 Young GC ”)。Minor GC 过程中的每个阶段都是“ 停止世界 Stop The World ”(STW)的,这会导致其他应用程序暂停运行,直到垃圾回收结束。这也是次要回收更快的原因。

一句话总结:伊甸区存放了所有新创建的对象,当它的可用空间被耗尽,第一次垃圾回收就会被触发。

填充伊甸区

次要回收:在该垃圾回收过程中,所有存活和死亡的对象都会被做上标记。其中,存活对象会被移动到 S0 幸存者区。当所有存活对象都被移动到了 S0,未被引用的对象就会被删除。

拷贝被引用的对象

S0 中的对象年龄为 1,因为它们挺过了一次次要回收。此时,伊甸区和 S1 都是空的。

每当完成清理后,伊甸区就会再次接受新的存活对象。随着时间的推移,伊甸区和 S0 中的某些对象被宣判死亡(不再被引用),并且伊甸区的可用空间也再次耗尽(填满了),那么次要回收 又将再次被触发。

对象年龄增长

这一次,伊甸区和 S0 中的死亡和存活的对象会被做上标记。其中,伊甸区的存活对象会被移动到 S1,并且年龄增加至 1。S0 中的存活对象也会被移动到 S1,并且年龄增加至 2(因为它们挺过了两次次要回收)。此时,伊甸区和 S0 又是空的了。每次次要回收之后,伊甸区和两个幸存者区中的一个都会是空的。

新对象总是在伊甸区被创建,周而复始。当下一次垃圾回收发生时,伊甸区和 S1 都会被清理,它们中的存活对象会被移动到 S0 区。每次次要回收之后,这两个幸存者区(S0 和 S1)就会交换一次。

额外年龄增长

这个过程会一直进行下去,直到某个存活对象的年龄达到了某个阈值,然后它就会被移动到一个叫做“老年代”的地方,这是通过一个叫做“晋升”的过程来完成的。

使用 -Xmn 选项可以设置新生代的大小。

老年代

这个区域存放着那些挺过了许多次次要回收,并且达到了某个年龄阈值的对象。

晋升

在上面这个示例图表中,晋升的年龄阈值为 8。在老年代发生的垃圾回收被称为 “ 主要回收 Major GC ”。(LCTT 译注:也被称为“ 全回收 Full GC ”)

使用 -Xms-Xmx 选项可以分别设置堆内存大小的初始值和最大值。(LCTT 译注:结合上面的 -Xmn 选项,就可以间接设置老年代的大小了。)

永久代

永久代存放着一些元数据,它们与应用程序、Java 标准环境以及 JVM 自用的库类及其方法相关。JVM 会在运行时,用到了什么类和方法,就会填充相应的数据。当 JVM 发现有未使用的类,就会卸载或是回收它们,从而为正在使用的类腾出空间。

使用 -XX:PermGen-XX:MaxPerGen 选项可以分别设置永久代大小的初始值和最大值。

元空间

Java 8 引入了 元空间 Metaspace ,并用它替换了永久代。这么做的好处是自动调整大小,避免了 内存不足 OutOfMemory (OOM)错误。

总结

本文讨论了各种不同的 JVM 内存“代”,以及它们是如何在分代垃圾回收算法中起作用的。对于程序员来说,掌握 Java 的内存管理机制并不是必须的,但它能够帮助你更好地理解 JVM 处理程序中的变量和类实例的方式。这种理解使你能够规划和排除代码故障,并理解特定平台固有的潜在限制。

正文配图来自:Jayashree Huttanagoudar,CC BY-SA 4.0


via: https://opensource.com/article/22/6/garbage-collection-java-virtual-machine

作者:Jayashree Huttanagoudar 选题:lkxed 译者:lkxed 校对:wxy

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