2019年12月

需要知道重要事件发生前有多少天吗?让 Linux bash 和 date 命令可以帮助你!

随着即将来临的重要假期,你可能需要提醒你还要准备多久。

幸运的是,你可以从 date 命令获得很多帮助。在本篇中,我们将研究 date 和 bash 脚本如何告诉你从今天到你预期的事件之间有多少天。

首先,在进行之前有几个提示。date 命令的 %j 选项将以 1 至 366 之间的数字显示当前日期。如你所想的一样,1 月 1 日将显示为 1,12 月 31 日将显示为 365 或 366,这取决于是否是闰年。继续尝试。你应该会看到以下内容:

$ date +%j
339

但是,你可以通过以下方式,在 date 命令中得到一年中任何一天的数字:

$ date -d "Mar 18" +%j
077

要记住的是,即使该日期是过去的日期,上面命令也会向你显示当年的日期。但是,你可以在命令中添加年来修复该问题:

$ date -d "Apr 29" +%j
119
$ date -d "Apr 29 2020" +%j
120

在闰年中,4 月 29 日将是一年的 120 天,而不是 119 天。

如果你想倒数圣诞节之前的日子并且不想在挂历上留下指纹,你可以使用以下脚本:

#!/bin/sh

XMAS=`date -d "Dec 25" +%j`
TODAY=`date +%j`
DAYS=$(($XMAS - $TODAY))

case $DAYS in
  0) echo "It's today! Merry Christmas!";;
  [0-9]*) echo "$DAYS days remaining";;
  -[0-9]*) echo "Oops, you missed it";;
esac

在此脚本中,我们获取 12 月 25 日和今天的日期,然后相减。如果结果是正数,我们将显示剩余天数。如果为零,则发出 “Merry Christmas” 的消息,如果为负,那么仅告诉运行脚本的人他们错过了假期。也许他们沉迷在蛋酒中了。

case 语句由用来打印信息的语句组成,当剩余时间等于 0,或任意数字或以 - 符号开头的数字(也就是过去)分别打印不同的信息。

对于人们想要关注的任何日期,都可以使用相同方法。实际上,我们可以要求运行脚本的人员提供日期,然后让他们知道从现在到那天还有多少天。这个脚本是这样的。

#!/bin/sh

echo -n "Enter event date (e.g., June 6): "
read dt
EVENT=`date -d "$dt" +%j`
TODAY=`date +%j`
DAYS=`expr $EVENT - $TODAY`

case $DAYS in
  0) echo "It's today!";;
  [0-9]*) echo "$DAYS days remaining";;
  -[0-9]*) echo "Oops, you missed it";;
esac

使用此脚本会遇到的一个问题,如果运行该脚本的人希望知道到第二年这个特殊日子还有多少天,他们会感到失望。即使他们输入日期时提供了年,date -d 命令仍将仅提供今年中的天数,而不会提供从现在到那时的天数。

计算从今天到某年的日期之间的天数可能有些棘手。你需要包括所有中间年份,并注意那些闰年。

使用 Unix 纪元时间

计算从现在到某个特殊日期之间的天数的另一种方法是利用 Unix 系统存储日期的方法。如果将自 1970 年 1 月 1 日开始的秒数转换为天数,那么就可以很容易地执行此操作,如下脚本所示:

#!/bin/bash

echo -n "Enter target date (e.g., Mar 18 2021)> "
read target_date
today=`echo $(($(date --utc --date "$1" +%s)/86400))`
target=`echo $(($(date --utc --date "$target_date" +%s)/86400))`
days=`expr $target - $today`
echo "$days days until $target_date"

解释一下,86400 是一天中的秒数。将自 Unix 纪元开始以来的秒数除该数即为天数。

$ ./countdown
Enter target date (e.g., Mar 18 2021)> Mar 18 2020
104 days until Mar 18 2020

via: https://www.networkworld.com/article/3487712/counting-down-the-days-using-bash.html

作者:Sandra Henry-Stocker 选题:lujun9972 译者:geekpi 校对:wxy

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

本文是 24 天 Linux 桌面特别系列的一部分。如果你是一个觉得传统桌面会妨碍你的极简主义者,那么试试 Pekwm Linux 桌面。

假设你想要一个轻量级桌面环境,它只需要能在屏幕上显示图形、四处移动窗口,而别无杂物。你会发现传统桌面的通知、任务栏和系统托盘会妨碍你的工作。你想主要通过终端工作,但也希望运行图形应用。如果听起来像是你的想法,那么 Pekwm 可能是你一直在寻找的东西。

Pekwm 的灵感大概来自于 Window Maker 和 Fluxbox 等。它提供了一个应用菜单、窗口装饰、而不是一大堆其他东西。它非常适合极简主义者,即那些希望节省资源的用户和喜欢在终端工作的用户。

从发行版仓库安装 Pekwm。安装后,请先退出当前桌面会话,以便可以登录到新桌面。默认情况下,会话管理器(KDM、GDM、LightDM 或 XDM,具体取决于你的设置)将继续登录到以前的桌面,因此需要在登录之前修改它。

在 GDM 中覆盖之前的桌面:

 title=

在 KDM 中:

 title=

第一次登录 Pekwm 时,你可能会看到黑屏。可能难以置信,但这是正常的。你看到的是一个空白桌面,没有背景壁纸。你可以使用 feh 命令设置壁纸(你可能需要从仓库中安装它)。此命令有几个用于设置背景的选项,包括 --bg-fill 用壁纸填充屏幕,--bg-scale 缩放到合适大小,等等。

$ feh --bg-fill ~/Pictures/wallpapers/mybackground.jpg

应用菜单

默认情况下,Pekwm 自动生成一个菜单,可在桌面上的任意位置右键单击,从而可让你运行应用。此菜单还提供一些首选项设置,例如选择主题和注销 Pekwm 会话。

 title=

配置

Pekwm 主要通过保存在 $HOME/.pekwm 下的文本配置文件来配置。menu 文件定义你的应用菜单,keys 文件定义键盘快捷键,等等。

start 文件是在 Pekwm 启动后执行的 shell 脚本。它类似于传统 Unix 系统上的 rc.local。它故意放在最后执行的,因此这里的东西将覆盖之前的一切。这是一个重要文件,它可能是你要设置背景的地方,以便你的选择会覆盖正在使用的主题的默认值。

start 文件也是可以启动 dockapp 的地方。dockapp 是一种小程序,它在 Window Maker 和 Fluxbox 引起了人们的关注。它们通常有网络监视器、时钟、音频设置,和其它你可能会在系统托盘或作为一个 KDE plasmoid 或者完整桌面环境中看到的小部件。你可能会在发行版仓库中找到一些 dockapp,或者可以在 dockapps.net 上在线查找它们。

你可以在启动时运行 dockapp,将它们列在 start 文件中,跟上 & 符号:

feh --bg-fill ~/Pictures/wallpapers/mybackground.jpg
wmnd &
bubblemon -d &

start 文件必须设置为可执行,才能在 Pekwm 启动时运行。

$ chmod +x $HOME/.pekwm/start

功能

Pekwm 的功能不多,但这就是它的美。如果你希望在桌面上运行额外的服务,那么由你来启动这些服务。如果你仍在学习 Linux,这是了解那些与完整的桌面环境捆绑在一起时通常不会注意到的微小 GUI 组件的好方法(像是任务栏)。这也习惯一些 Linux 命令(例如 nmcli)的好方法。

Pekwm 是一个有趣的窗口管理器。它分散、简洁、轻巧。请试试看!


via: https://opensource.com/article/19/12/pekwm-linux-desktop

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

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

SCP( 安全复制 Secure Copy )是 Linux 和 Unix 之类的系统中的命令行工具,用于通过网络安全地跨系统传输文件和目录。当我们使用 scp 命令将文件和目录从本地系统复制到远程系统时,则在后端与远程系统建立了 ssh 连接。换句话说,我们可以说 scp 在后端使用了相同的 SSH 安全机制,它需要密码或密钥进行身份验证。

在本教程中,我们将讨论 14 个有用的 Linux scp 命令示例。

scp 命令语法:

# scp <选项> <文件或目录> 用户名@目标主机:/<文件夹>

# scp <选项> 用户名@目标主机:/文件 <本地文件夹>

scp 命令的第一个语法演示了如何将文件或目录从本地系统复制到特定文件夹下的目标主机。

scp 命令的第二种语法演示了如何将目标主机中的文件复制到本地系统中。

下面列出了 scp 命令中使用最广泛的一些选项,

  • -C 启用压缩
  • -i 指定识别文件或私钥
  • -l 复制时限制带宽
  • -P 指定目标主机的 ssh 端口号
  • -p 复制时保留文件的权限、模式和访问时间
  • -q 禁止 SSH 警告消息
  • -r 递归复制文件和目录
  • -v 详细输出

现在让我们跳入示例!

示例:1)使用 scp 将文件从本地系统复制到远程系统

假设我们要使用 scp 命令将 jdk 的 rpm 软件包从本地 Linux 系统复制到远程系统(172.20.10.8),请使用以下命令,

[root@linuxtechi ~]$ scp jdk-linux-x64_bin.rpm root@linuxtechi:/opt
root@linuxtechi's password:
jdk-linux-x64_bin.rpm                          100%   10MB  27.1MB/s   00:00
[root@linuxtechi ~]$

上面的命令会将 jdk 的 rpm 软件包文件复制到 /opt 文件夹下的远程系统。

示例:2)使用 scp 将文件从远程系统复制到本地系统

假设我们想将文件从远程系统复制到本地系统下的 /tmp 文件夹,执行以下 scp 命令,

[root@linuxtechi ~]$ scp root@linuxtechi:/root/Technical-Doc-RHS.odt /tmp
root@linuxtechi's password:
Technical-Doc-RHS.odt                         100% 1109KB  31.8MB/s   00:00
[root@linuxtechi ~]$ ls -l /tmp/Technical-Doc-RHS.odt
-rwx------. 1 pkumar pkumar 1135521 Oct 19 11:12 /tmp/Technical-Doc-RHS.odt
[root@linuxtechi ~]$

示例:3)使用 scp 传输文件时的详细输出(-v)

scp 命令中,我们可以使用 -v 选项启用详细输出。使用详细输出,我们可以轻松地发现后台确切发生了什么。这对于调试连接、认证和配置等问题非常有用。

root@linuxtechi ~]$ scp -v jdk-linux-x64_bin.rpm root@linuxtechi:/opt
Executing: program /usr/bin/ssh host 172.20.10.8, user root, command scp -v -t /opt
OpenSSH_7.8p1, OpenSSL 1.1.1 FIPS  11 Sep 2018
debug1: Reading configuration data /etc/ssh/ssh_config
debug1: Reading configuration data /etc/ssh/ssh_config.d/05-redhat.conf
debug1: Reading configuration data /etc/crypto-policies/back-ends/openssh.config
debug1: /etc/ssh/ssh_config.d/05-redhat.conf line 8: Applying options for *
debug1: Connecting to 172.20.10.8 [172.20.10.8] port 22.
debug1: Connection established.
…………
debug1: Next authentication method: password
root@linuxtechi's password:

示例:4)将多个文件传输到远程系统

可以使用 scp 命令一次性将多个文件复制/传输到远程系统,在 scp 命令中指定多个文件,并用空格隔开,示例如下所示

[root@linuxtechi ~]$ scp install.txt index.html jdk-linux-x64_bin.rpm root@linuxtechi:/mnt
root@linuxtechi's password:
install.txt                                      100%    0     0.0KB/s   00:00
index.html                                       100%   85KB   7.2MB/s   00:00
jdk-linux-x64_bin.rpm                            100%   10MB  25.3MB/s   00:00
[root@linuxtechi ~]$

示例:5)在两个远程主机之间传输文件

使用 scp 命令,我们可以在两个远程主机之间复制文件和目录,假设我们有一个可以连接到两个远程 Linux 系统的本地 Linux 系统,因此从我的本地 Linux 系统中,我可以使用 scp 命令在这两个系统之间复制文件,

命令语法:

# scp 用户名@远程主机1:/<要传输的文件> 用户名@远程主机2:/<文件夹>

示例如下:

# scp root@linuxtechi:~/backup-Oct.zip root@linuxtechi:/tmp
# ssh root@linuxtechi "ls -l /tmp/backup-Oct.zip"
-rwx------. 1 root root 747438080 Oct 19 12:02 /tmp/backup-Oct.zip

示例:6)递归复制文件和目录(-r)

scp 命令中使用 -r 选项将整个目录从一个系统递归地复制到另一个系统,示例如下所示:

[root@linuxtechi ~]$ scp -r Downloads root@linuxtechi:/opt

使用以下命令验证 Downloads 文件夹是否已复制到远程系统,

[root@linuxtechi ~]$ ssh root@linuxtechi "ls -ld /opt/Downloads"
drwxr-xr-x. 2 root root 75 Oct 19 12:10 /opt/Downloads
[root@linuxtechi ~]$

示例:7)通过启用压缩来提高传输速度(-C)

scp 命令中,我们可以通过使用 -C 选项启用压缩来提高传输速度,它将自动在源主机上启用压缩并在目标主机上解压缩。

root@linuxtechi ~]$ scp -r -C Downloads root@linuxtechi:/mnt

在以上示例中,我们正在启用压缩的情况下传输下载目录。

示例:8)复制时限制带宽(-l)

scp 命令中使用 -l 选项设置复制时对带宽使用的限制。带宽以 Kbit/s 为单位指定,示例如下所示:

[root@linuxtechi ~]$ scp -l 500 jdk-linux-x64_bin.rpm  root@linuxtechi:/var

示例:9)在 scp 时指定其他 ssh 端口(-P)

在某些情况下,目标主机上的 ssh 端口会更改,因此在使用 scp 命令时,我们可以使用 -P 选项指定 ssh 端口号。

[root@linuxtechi ~]$ scp -P 2022 jdk-linux-x64_bin.rpm  root@linuxtechi:/var

在上面的示例中,远程主机的 ssh 端口为 “2022”。

示例:10)复制时保留文件的权限、模式和访问时间(-p)

从源复制到目标时,在 scp 命令中使用 -p 选项保留权限、访问时间和模式。

[root@linuxtechi ~]$ scp -p jdk-linux-x64_bin.rpm  root@linuxtechi:/var/tmp
jdk-linux-x64_bin.rpm                            100%   10MB  13.5MB/s   00:00
[root@linuxtechi ~]$

示例:11)在 scp 中以安静模式传输文件(-q)

scp 命令中使用 -q 选项可禁止显示 ssh 的传输进度、警告和诊断消息。示例如下所示:

[root@linuxtechi ~]$ scp -q -r Downloads root@linuxtechi:/var/tmp
[root@linuxtechi ~]$

示例:12)在传输时使用 scp 中的识别文件(-i)

在大多数 Linux 环境中,首选基于密钥的身份验证。在 scp 命令中,我们使用 -i 选项指定识别文件(私钥文件),示例如下所示:

[root@linuxtechi ~]$ scp -i my_key.pem -r Downloads root@linuxtechi:/root

在上面的示例中,my_key.pem 是识别文件或私钥文件。

示例:13)在 scp 中使用其他 ssh\_config 文件(-F)

在某些情况下,你使用不同的网络连接到 Linux 系统,可能某些网络位于代理服务器后面,因此在这种情况下,我们必须具有不同的 ssh_config 文件。

通过 -F 选项在 scp 命令中指定了不同的 ssh_config 文件,示例如下所示:

[root@linuxtechi ~]$ scp -F /home/pkumar/new_ssh_config -r Downloads root@linuxtechi:/root
root@linuxtechi's password:
jdk-linux-x64_bin.rpm                     100%   10MB  16.6MB/s   00:00
backup-Oct.zip                            100%  713MB  41.9MB/s   00:17
index.html                                100%   85KB   6.6MB/s   00:00
[root@linuxtechi ~]$

示例:14)在 scp 命令中使用其他加密方式(-c)

默认情况下,scp 使用 AES-128 加密方式来加密文件。如果你想在 scp 命令中使用其他加密方式,请使用 -c 选项,后接加密方式名称。

假设我们要在用 scp 命令传输文件时使用 3des-cbc 加密方式,请运行以下 scp 命令:

[root@linuxtechi ~]# scp -c 3des-cbc -r Downloads root@linuxtechi:/root

使用以下命令列出 sshscp 支持的加密方式:

[root@linuxtechi ~]# ssh -Q cipher localhost | paste -d , -s -
3des-cbc,aes128-cbc,aes192-cbc,aes256-cbc,root@linuxtechi,aes128-ctr,aes192-ctr,aes256-ctr,root@linuxtechi,root@linuxtechi,root@linuxtechi
[root@linuxtechi ~]#

以上就是本教程的全部内容,要获取有关 scp 命令的更多详细信息,请参考其手册页。请在下面的评论部分中分享你的反馈和评论。


via: https://www.linuxtechi.com/scp-command-examples-in-linux/

作者:Pradeep Kumar 选题:lujun9972 译者:wxy 校对:wxy

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

我的大部分工作都涉及到部署软件系统,这意味着我需要花费很多时间来解决以下问题:

  • 这个软件可以在原开发者的机器上工作,但是为什么不能在我这里运行?
  • 这个软件昨天可以在我的机器上工作,但是为什么今天就不行?

这是一种调试的类型,但是与一般的软件调试有所不同。一般的调试通常只关心代码的逻辑,但是在软件部署中的调试关注的是程序的代码和它所在的运行环境之间的相互影响。即便问题的根源是代码的逻辑错误,但软件显然可以在别的机器上运行的事实意味着这类问题与运行环境密切相关。

所以,在软件部署过程中,我没有使用传统的调试工具(例如 gdb),而是选择了其它工具进行调试。我最喜欢的用来解决“为什么这个软件无法在这台机器上运行?”这类问题的工具就是 strace

什么是 strace?

strace 是一个用来“追踪系统调用”的工具。它主要是一个 Linux 工具,但是你也可以在其它系统上使用类似的工具(例如 DTracektrace)。

它的基本用法非常简单。只需要在 strace 后面跟上你需要运行的命令,它就会显示出该命令触发的所有系统调用(你可能需要先安装好 strace):

$ strace echo Hello
...Snip lots of stuff...
write(1, "Hello\n", 6)                  = 6
close(1)                                = 0
close(2)                                = 0
exit_group(0)                           = ?
+++ exited with 0 +++

这些系统调用都是什么?它们就像是操作系统内核提供的 API。很久以前,软件拥有直接访问硬件的权限。如果软件需要在屏幕上显示一些东西,它将会与视频硬件的端口和内存映射寄存器纠缠不清。当多任务操作系统变得流行以后,这就导致了混乱的局面,因为不同的应用程序将“争夺”硬件,并且一个应用程序的错误可能致使其它应用程序崩溃,甚至导致整个系统崩溃。所以 CPU 开始支持多种不同的特权模式(或者称为“保护环”)。它们让操作系统内核在具有完全硬件访问权限的最高特权模式下运行,于此同时,其它在低特权模式下运行的应用程序必须通过向内核发起系统调用才能够与硬件进行交互。

在二进制级别上,发起系统调用相比简单的函数调用有一些区别,但是大部分程序都使用标准库提供的封装函数。例如,POSIX C 标准库包含一个 write() 函数,该函数包含用于进行 write 系统调用的所有与硬件体系结构相关的代码。

简单来说,一个应用程序与其环境(计算机系统)的交互都是通过系统调用来完成的。所以当软件在一台机器上可以工作但是在另一台机器无法工作的时候,追踪系统调用是一个很好的查错方法。具体地说,你可以通过追踪系统调用分析以下典型操作:

  • 控制台输入与输出 (IO)
  • 网络 IO
  • 文件系统访问以及文件 IO
  • 进程/线程生命周期管理
  • 原始内存管理
  • 访问特定的设备驱动

什么时候可以使用 strace?

理论上,strace 适用于任何用户空间程序,因为所有的用户空间程序都需要进行系统调用。strace 对于已编译的低级程序最有效果,但如果你可以避免运行时环境和解释器带来的大量额外输出,则仍然可以与 Python 等高级语言程序一起使用。

当软件在一台机器上正常工作,但在另一台机器上却不能正常工作,同时抛出了有关文件、权限或者不能运行某某命令等模糊的错误信息时,strace 往往能大显身手。不幸的是,它不能诊断高等级的问题,例如数字证书验证错误等。这些问题通常需要组合使用 strace(有时候是 ltrace)和其它高级工具(例如使用 openssl 命令行工具调试数字证书错误)。

本文中的示例基于独立的服务器,但是对系统调用的追踪通常也可以在更复杂的部署平台上完成,仅需要找到合适的工具。

一个简单的例子

假设你正在尝试运行一个叫做 foo 的服务器应用程序,但是发生了以下情况:

$ foo
Error opening configuration file: No such file or directory

显然,它没有找到你已经写好的配置文件。之所以会发生这种情况,是因为包管理工具有时候在编译应用程序时指定了自定义的路径,所以你应当遵循特定发行版提供的安装指南。如果错误信息告诉你正确的配置文件应该在什么地方,你就可以在几秒钟内解决这个问题,但如果没有告诉你呢?你该如何找到正确的路径?

如果你有权访问源代码,则可以通过阅读源代码来解决问题。这是一个好的备用计划,但不是最快的解决方案。你还可以使用类似 gdb 的单步调试器来观察程序的行为,但使用专门用于展示程序与系统环境交互作用的工具 strace 更加有效。

一开始, strace 产生的大量输出可能会让你不知所措,幸好你可以忽略其中大部分的无用信息。我经常使用 -o 参数把输出的追踪结果保存到单独的文件里:

$ strace -o /tmp/trace foo
Error opening configuration file: No such file or directory
$ cat /tmp/trace
execve("foo", ["foo"], 0x7ffce98dc010 /* 16 vars */) = 0
brk(NULL)                               = 0x56363b3fb000
access("/etc/ld.so.preload", R_OK)      = -1 ENOENT (No such file or directory)
openat(AT_FDCWD, "/etc/ld.so.cache", O_RDONLY|O_CLOEXEC) = 3
fstat(3, {st_mode=S_IFREG|0644, st_size=25186, ...}) = 0
mmap(NULL, 25186, PROT_READ, MAP_PRIVATE, 3, 0) = 0x7f2f12cf1000
close(3)                                = 0
openat(AT_FDCWD, "/lib/x86_64-linux-gnu/libc.so.6", O_RDONLY|O_CLOEXEC) = 3
read(3, "\177ELF\2\1\1\3\0\0\0\0\0\0\0\0\3\0>\0\1\0\0\0\260A\2\0\0\0\0\0"..., 832) = 832
fstat(3, {st_mode=S_IFREG|0755, st_size=1824496, ...}) = 0
mmap(NULL, 8192, PROT_READ|PROT_WRITE, MAP_PRIVATE|MAP_ANONYMOUS, -1, 0) = 0x7f2f12cef000
mmap(NULL, 1837056, PROT_READ, MAP_PRIVATE|MAP_DENYWRITE, 3, 0) = 0x7f2f12b2e000
mprotect(0x7f2f12b50000, 1658880, PROT_NONE) = 0
mmap(0x7f2f12b50000, 1343488, PROT_READ|PROT_EXEC, MAP_PRIVATE|MAP_FIXED|MAP_DENYWRITE, 3, 0x22000) = 0x7f2f12b50000
mmap(0x7f2f12c98000, 311296, PROT_READ, MAP_PRIVATE|MAP_FIXED|MAP_DENYWRITE, 3, 0x16a000) = 0x7f2f12c98000
mmap(0x7f2f12ce5000, 24576, PROT_READ|PROT_WRITE, MAP_PRIVATE|MAP_FIXED|MAP_DENYWRITE, 3, 0x1b6000) = 0x7f2f12ce5000
mmap(0x7f2f12ceb000, 14336, PROT_READ|PROT_WRITE, MAP_PRIVATE|MAP_FIXED|MAP_ANONYMOUS, -1, 0) = 0x7f2f12ceb000
close(3)                                = 0
arch_prctl(ARCH_SET_FS, 0x7f2f12cf0500) = 0
mprotect(0x7f2f12ce5000, 16384, PROT_READ) = 0
mprotect(0x56363b08b000, 4096, PROT_READ) = 0
mprotect(0x7f2f12d1f000, 4096, PROT_READ) = 0
munmap(0x7f2f12cf1000, 25186)           = 0
openat(AT_FDCWD, "/etc/foo/config.json", O_RDONLY) = -1 ENOENT (No such file or directory)
dup(2)                                  = 3
fcntl(3, F_GETFL)                       = 0x2 (flags O_RDWR)
brk(NULL)                               = 0x56363b3fb000
brk(0x56363b41c000)                     = 0x56363b41c000
fstat(3, {st_mode=S_IFCHR|0620, st_rdev=makedev(0x88, 0x8), ...}) = 0
write(3, "Error opening configuration file"..., 60) = 60
close(3)                                = 0
exit_group(1)                           = ?
+++ exited with 1 +++

strace 输出的第一页通常是低级的进程启动过程。(你可以看到很多 mmapmprotectbrk 调用,这是用来分配原始内存和映射动态链接库的。)实际上,在查找错误时,最好从下往上阅读 strace 的输出。你可以看到 write 调用在最后返回了错误信息。如果你向上找,你将会看到第一个失败的系统调用是 openat,它在尝试打开 /etc/foo/config.json 时抛出了 ENOENT (“No such file or directory”)的错误。现在我们已经知道了配置文件应该放在哪里。

这是一个简单的例子,但我敢说在 90% 的情况下,使用 strace 进行调试不需要更多复杂的工作。以下是完整的调试步骤:

  1. 从程序中获得含糊不清的错误信息
  2. 使用 strace 运行程序
  3. 在输出中找到错误信息
  4. 往前追溯并找到第一个失败的系统调用

第四步中的系统调用很可能向你显示出问题所在。

小技巧

在开始更加复杂的调试之前,这里有一些有用的调试技巧帮助你高效使用 strace

man 是你的朋友

在很多 *nix 操作系统中,你可以通过 man syscalls 查看系统调用的列表。你将会看到类似于 brk(2) 之类的东西,这意味着你可以通过运行 man 2 brk 得到与此相关的更多信息。

一个小问题:man 2 fork 会显示出在 GNU libc 里封装的 fork() 手册页,而 fork() 现在实际上是由 clone 系统调用实现的。fork 的语义与 clone 相同,但是如果我写了一个含有 fork() 的程序并使用 strace 去调试它,我将找不到任何关于 fork 调用的信息,只能看到 clone 调用。如果将源代码与 strace 的输出进行比较的时候,像这种问题会让人感到困惑。

使用 -o 将输出保存到文件

strace 可以生成很多输出,所以将输出保存到单独的文件是很有帮助的(就像上面的例子一样)。它还能够在控制台中避免程序自身的输出与 strace 的输出发生混淆。

使用 -s 查看更多的参数

你可能已经注意到,错误信息的第二部分没有出现在上面的例子中。这是因为 strace 默认仅显示字符串参数的前 32 个字节。如果你需要捕获更多参数,请向 strace 追加类似于 -s 128 之类的参数。

-y 使得追踪文件或套接字更加容易

“一切皆文件”意味着 *nix 系统通过文件描述符进行所有 IO 操作,不管是真实的文件还是通过网络或者进程间管道。这对于编程而言是很方便的,但是在追踪系统调用时,你将很难分辨出 readwrite 的真实行为。

-y 参数使 strace 在注释中注明每个文件描述符的具体指向。

使用 -p 附加到正在运行的进程中

正如我们将在后面的例子中看到的,有时候你想追踪一个正在运行的程序。如果你知道这个程序的进程号为 1337 (可以通过 ps 查询),则可以这样操作:

$ strace -p 1337
...system call trace output...

你可能需要 root 权限才能运行。

使用 -f 追踪子进程

strace 默认只追踪一个进程。如果这个进程产生了一个子进程,你将会看到创建子进程的系统调用(一般是 clone),但是你看不到子进程内触发的任何调用。

如果你认为在子进程中存在错误,则需要使用 -f 参数启用子进程追踪功能。这样做的缺点是输出的内容会让人更加困惑。当追踪一个进程时,strace 显示的是单个调用事件流。当追踪多个进程的时候,你将会看到以 <unfinished ...> 开始的初始调用,接着是一系列针对其它线程的调用,最后才出现以 <... foocall resumed> 结束的初始调用。此外,你可以使用 -ff 参数将所有的调用分离到不同的文件中(查看 strace 手册 获取更多信息)。

使用 -e 进行过滤

正如你所看到的,默认的追踪输出是所有的系统调用。你可以使用 -e 参数过滤你需要追踪的调用(查看 strace 手册)。这样做的好处是运行过滤后的 strace 比起使用 grep 进行二次过滤要更快。老实说,我大部分时间都不会被打扰。

并非所有的错误都是不好的

一个简单而常用的例子是一个程序在多个位置搜索文件,例如 shell 搜索哪个 bin/ 目录包含可执行文件:

$ strace sh -c uname
...
stat("/home/user/bin/uname", 0x7ffceb817820) = -1 ENOENT (No such file or directory)
stat("/usr/local/bin/uname", 0x7ffceb817820) = -1 ENOENT (No such file or directory)
stat("/usr/bin/uname", {st_mode=S_IFREG|0755, st_size=39584, ...}) = 0
...

“错误信息之前的最后一次失败调用”这种启发式方法非常适合于查找错误。无论如何,自下而上地查找是有道理的。

C 编程指南非常有助于理解系统调用

标准 C 库函数调用不属于系统调用,但它们仅是系统调用之上的唯一一个薄层。所以如果你了解(甚至只是略知一二)如何使用 C 语言,那么阅读系统调用追踪信息就非常容易。例如,如果你在调试网络系统调用,你可以尝试略读 Beej 经典的《网络编程指南》

一个更复杂的调试例子

就像我说的那样,简单的调试例子表现了我在大部分情况下如何使用 strace。然而,有时候需要一些更加细致的工作,所以这里有一个稍微复杂(且真实)的例子。

bcron 是一个任务调度器,它是经典 *nix cron 守护程序的另一种实现。它已经被安装到一台服务器上,但是当有人尝试编辑作业时间表时,发生了以下情况:

# crontab -e -u logs
bcrontab: Fatal: Could not create temporary file

好的,现在 bcron 尝试写入一些文件,但是它失败了,也没有告诉我们原因。以下是 strace 的输出:

# strace -o /tmp/trace crontab -e -u logs
bcrontab: Fatal: Could not create temporary file
# cat /tmp/trace
...
openat(AT_FDCWD, "bcrontab.14779.1573691864.847933", O_RDONLY) = 3
mmap(NULL, 8192, PROT_READ|PROT_WRITE, MAP_PRIVATE|MAP_ANONYMOUS, -1, 0) = 0x7f82049b4000
read(3, "#Ansible: logsagg\n20 14 * * * lo"..., 8192) = 150
read(3, "", 8192)                       = 0
munmap(0x7f82049b4000, 8192)            = 0
close(3)                                = 0
socket(AF_UNIX, SOCK_STREAM, 0)         = 3
connect(3, {sa_family=AF_UNIX, sun_path="/var/run/bcron-spool"}, 110) = 0
mmap(NULL, 8192, PROT_READ|PROT_WRITE, MAP_PRIVATE|MAP_ANONYMOUS, -1, 0) = 0x7f82049b4000
write(3, "156:Slogs\0#Ansible: logsagg\n20 1"..., 161) = 161
read(3, "32:ZCould not create temporary f"..., 8192) = 36
munmap(0x7f82049b4000, 8192)            = 0
close(3)                                = 0
write(2, "bcrontab: Fatal: Could not creat"..., 49) = 49
unlink("bcrontab.14779.1573691864.847933") = 0
exit_group(111)                         = ?
+++ exited with 111 +++

在程序结束之前有一个 write 的错误信息,但是这次有些不同。首先,在此之前没有任何相关的失败系统调用。其次,我们看到这个错误信息是由 read 从别的地方读取而来的。这看起来像是真正的错误发生在别的地方,而 bcrontab 只是在转播这些信息。

如果你查阅了 man 2 read,你将会看到 read 的第一个参数 (3) 是一个文件描述符,这是 *nix 操作系统用于所有 IO 操作的句柄。你该如何知道文件描述符 3 代表什么?在这种情况下,你可以使用 -y 参数运行 strace(如上文所述),它将会在注释里告诉你文件描述符的具体指向,但是了解如何从上面这种输出中分析追踪结果是很有用的。

一个文件描述符可以来自于许多系统调用之一(这取决于它是用于控制台、网络套接字还是真实文件等的描述符),但不论如何,我们都可以搜索返回值为 3 的系统调用(例如,在 strace 的输出中查找 =3)。在这次 strace 中可以看到有两个这样的调用:最上面的 openat 以及中间的 socketopenat 打开一个文件,但是紧接着的 close(3) 表明其已经被关闭。(注意:文件描述符可以在打开并关闭后重复使用。)所以 socket 调用才是与此相关的(它是在 read 之前的最后一个),这告诉我们 brcontab 正在与一个网络套接字通信。在下一行,connect 表明文件描述符 3 是一个连接到 /var/run/bcron-spool 的 Unix 域套接字。

因此,我们需要弄清楚 Unix 套接字的另一侧是哪个进程在监听。有两个巧妙的技巧适用于在服务器部署中调试。一个是使用 netstat 或者较新的 ss。这两个命令都描述了当前系统中活跃的网络套接字,使用 -l 参数可以显示出处于监听状态的套接字,而使用 -p 参数可以得到正在使用该套接字的程序信息。(它们还有更多有用的选项,但是这两个已经足够完成工作了。)

# ss -pl | grep /var/run/bcron-spool
u_str LISTEN 0   128   /var/run/bcron-spool 1466637   * 0   users:(("unixserver",pid=20629,fd=3))

这告诉我们 /var/run/bcron-spool 套接字的监听程序是 unixserver 这个命令,它的进程 ID 为 20629。(巧合的是,这个程序也使用文件描述符 3 去连接这个套接字。)

第二个常用的工具就是使用 lsof 查找相同的信息。它可以列出当前系统中打开的所有文件(或文件描述符)。或者,我们可以得到一个具体文件的信息:

# lsof /var/run/bcron-spool
COMMAND   PID   USER  FD  TYPE  DEVICE              SIZE/OFF  NODE    NAME
unixserve 20629 cron  3u  unix  0x000000005ac4bd83  0t0       1466637 /var/run/bcron-spool type=STREAM

进程 20629 是一个常驻进程,所以我们可以使用 strace -o /tmp/trace -p 20629 去查看该进程的系统调用。如果我们在另一个终端尝试编辑 cron 的计划任务表,就可以在错误发生时捕获到以下信息:

accept(3, NULL, NULL)                   = 4
clone(child_stack=NULL, flags=CLONE_CHILD_CLEARTID|CLONE_CHILD_SETTID|SIGCHLD, child_tidptr=0x7faa47c44810) = 21181
close(4)                                = 0
accept(3, NULL, NULL)                   = ? ERESTARTSYS (To be restarted if SA_RESTART is set)
--- SIGCHLD {si_signo=SIGCHLD, si_code=CLD_EXITED, si_pid=21181, si_uid=998, si_status=0, si_utime=0, si_stime=0} ---
wait4(0, [{WIFEXITED(s) && WEXITSTATUS(s) == 0}], WNOHANG|WSTOPPED, NULL) = 21181
wait4(0, 0x7ffe6bc36764, WNOHANG|WSTOPPED, NULL) = -1 ECHILD (No child processes)
rt_sigaction(SIGCHLD, {sa_handler=0x55d244bdb690, sa_mask=[CHLD], sa_flags=SA_RESTORER|SA_RESTART, sa_restorer=0x7faa47ab9840}, {sa_handler=0x55d244bdb690, sa_mask=[CHLD], sa_flags=SA_RESTORER|SA_RESTART, sa_restorer=0x7faa47ab9840}, 8) = 0
rt_sigreturn({mask=[]})                 = 43
accept(3, NULL, NULL)                   = 4
clone(child_stack=NULL, flags=CLONE_CHILD_CLEARTID|CLONE_CHILD_SETTID|SIGCHLD, child_tidptr=0x7faa47c44810) = 21200
close(4)                                = 0
accept(3, NULL, NULL)                   = ? ERESTARTSYS (To be restarted if SA_RESTART is set)
--- SIGCHLD {si_signo=SIGCHLD, si_code=CLD_EXITED, si_pid=21200, si_uid=998, si_status=111, si_utime=0, si_stime=0} ---
wait4(0, [{WIFEXITED(s) && WEXITSTATUS(s) == 111}], WNOHANG|WSTOPPED, NULL) = 21200
wait4(0, 0x7ffe6bc36764, WNOHANG|WSTOPPED, NULL) = -1 ECHILD (No child processes)
rt_sigaction(SIGCHLD, {sa_handler=0x55d244bdb690, sa_mask=[CHLD], sa_flags=SA_RESTORER|SA_RESTART, sa_restorer=0x7faa47ab9840}, {sa_handler=0x55d244bdb690, sa_mask=[CHLD], sa_flags=SA_RESTORER|SA_RESTART, sa_restorer=0x7faa47ab9840}, 8) = 0
rt_sigreturn({mask=[]})                 = 43
accept(3, NULL, NULL

(最后一个 accept 调用没有在追踪期间完成。)不幸的是,这次追踪没有包含我们想要的错误信息。我们没有观察到 bcrontan 往套接字发送或接受的任何信息。然而,我们看到了很多进程管理操作(clonewait4SIGCHLD,等等)。这个进程产生了子进程,我们猜测真实的工作是由子进程完成的。如果我们想捕获子进程的追踪信息,就必须往 strace 追加 -f 参数。以下是我们最终使用 strace -f -o /tmp/trace -p 20629 找到的错误信息:

21470 openat(AT_FDCWD, "tmp/spool.21470.1573692319.854640", O_RDWR|O_CREAT|O_EXCL, 0600) = -1 EACCES (Permission denied)
21470 write(1, "32:ZCould not create temporary f"..., 36) = 36
21470 write(2, "bcron-spool[21470]: Fatal: logs:"..., 84) = 84
21470 unlink("tmp/spool.21470.1573692319.854640") = -1 ENOENT (No such file or directory)
21470 exit_group(111)                   = ?
21470 +++ exited with 111 +++

现在我们知道了进程 ID 21470 在尝试创建文件 tmp/spool.21470.1573692319.854640 (相对于当前的工作目录)时得到了一个没有权限的错误。如果我们知道当前的工作目录,就可以得到完整路径并能指出为什么该进程无法在此处创建临时文件。不幸的是,这个进程已经退出了,所以我们不能使用 lsof -p 21470 去找出当前的工作目录,但是我们可以往前追溯,查找进程 ID 21470 使用哪个系统调用改变了它的工作目录。这个系统调用是 chdir(可以在搜索引擎很轻松地找到)。以下是一直往前追溯到服务器进程 ID 20629 的结果:

20629 clone(child_stack=NULL, flags=CLONE_CHILD_CLEARTID|CLONE_CHILD_SETTID|SIGCHLD, child_tidptr=0x7faa47c44810) = 21470
...
21470 execve("/usr/sbin/bcron-spool", ["bcron-spool"], 0x55d2460807e0 /* 27 vars */) = 0
...
21470 chdir("/var/spool/cron")          = 0
...
21470 openat(AT_FDCWD, "tmp/spool.21470.1573692319.854640", O_RDWR|O_CREAT|O_EXCL, 0600) = -1 EACCES (Permission denied)
21470 write(1, "32:ZCould not create temporary f"..., 36) = 36
21470 write(2, "bcron-spool[21470]: Fatal: logs:"..., 84) = 84
21470 unlink("tmp/spool.21470.1573692319.854640") = -1 ENOENT (No such file or directory)
21470 exit_group(111)                   = ?
21470 +++ exited with 111 +++

(如果你在这里迷糊了,你可能需要阅读 我之前有关 *nix 进程管理和 shell 的文章

现在 PID 为 20629 的服务器进程没有权限在 /var/spool/cron/tmp/spool.21470.1573692319.854640 创建文件。最可能的原因就是典型的 *nix 文件系统权限设置。让我们检查一下:

# ls -ld /var/spool/cron/tmp/
drwxr-xr-x 2 root root 4096 Nov  6 05:33 /var/spool/cron/tmp/
# ps u -p 20629
USER       PID %CPU %MEM    VSZ   RSS TTY      STAT START   TIME COMMAND
cron     20629  0.0  0.0   2276   752 ?        Ss   Nov14   0:00 unixserver -U /var/run/bcron-spool -- bcron-spool

这就是问题所在!这个服务进程以 cron 用户运行,但是只有 root 用户才有向 /var/spool/cron/tmp/ 目录写入的权限。一个简单 chown cron /var/spool/cron/tmp/ 命令就能让 bcron 正常工作。(如果不是这个问题,那么下一个最有可能的怀疑对象是诸如 SELinux 或者 AppArmor 之类的内核安全模块,因此我将会使用 dmesg 检查内核日志。)

总结

最初,系统调用追踪可能会让人不知所措,但是我希望我已经证明它们是调试一整套常见部署问题的快速方法。你可以设想一下尝试用单步调试器去调试多进程的 bcron 问题。

通过一连串的系统调用解决问题是需要练习的,但正如我说的那样,在大多数情况下,我只需要使用 strace 从下往上追踪并查找错误。不管怎样,strace 节省了我很多的调试时间。我希望这也对你有所帮助。


via: https://theartofmachinery.com/2019/11/14/deployment_debugging_strace.html

作者:Simon Arneaud 选题:lujun9972 译者:hanwckf 校对:wxy

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

学习怎样使用 awk 的 !visited[$0]++ 在不重新排序或改变原排列顺序的前提下删掉重复的行。

假设你有一个文本文件,你需要删掉所有重复的行。

TL;DR

要保持原来的排列顺序删掉重复行,使用:

awk '!visited[$0]++' your_file > deduplicated_file

工作原理

这个脚本维护一个关联数组,索引(键)为文件中去重后的行,每个索引对应的值为该行出现的次数。对于文件的每一行,如果这行(之前)出现的次数为 0,则值加 1,并打印这行,否则值加 1,不打印这行。

我之前不熟悉 awk,我想弄清楚这么短小的一个脚本是怎么实现的。我调研了下,下面是调研心得:

  • 这个 awk “脚本” !visited[$0]++ 对输入文件的每一行都执行。
  • visited[] 是一个关联数组(又名映射)类型的变量。awk 会在第一次执行时初始化它,因此我们不需要初始化。
  • $0 变量的值是当前正在被处理的行的内容。
  • visited[$0] 通过与 $0(正在被处理的行)相等的键来访问该映射中的值,即出现次数(我们在下面设置的)。
  • ! 对表示出现次数的值取反:

    • awk 中,任意非零的数或任意非空的字符串的值是 true
    • 变量默认的初始值为空字符串,如果被转换为数字,则为 0。
    • 也就是说:

      • 如果 visited[$0] 的值是一个比 0 大的数,取反后被解析成 false
      • 如果 visited[$0] 的值为等于 0 的数字或空字符串,取反后被解析成 true
    • ++ 表示变量 visited[$0] 的值加 1。

      • 如果该值为空,awk 自动把它转换为 0(数字) 后加 1。
      • 注意:加 1 操作是在我们取到了变量的值之后执行的。

总的来说,整个表达式的意思是:

  • true:如果表示出现次数为 0 或空字符串
  • false:如果出现的次数大于 0

awk模式或表达式和一个与之关联的动作 组成:

<模式/表达式> { <动作> }

如果匹配到了模式,就会执行后面的动作。如果省略动作,awk 默认会打印(print)输入。

省略动作等价于 {print $0}

我们的脚本由一个 awk 表达式语句组成,省略了动作。因此这样写:

awk '!visited[$0]++' your_file > deduplicated_file

等于这样写:

awk '!visited[$0]++ { print $0 }' your_file > deduplicated_file

对于文件的每一行,如果表达式匹配到了,这行内容被打印到输出。否则,不执行动作,不打印任何东西。

为什么不用 uniq 命令?

uniq 命令仅能对相邻的行去重。这是一个示例:

$ cat test.txt
A
A
A
B
B
B
A
A
C
C
C
B
B
A
$ uniq < test.txt
A
B
A
C
B
A

其他方法

使用 sort 命令

我们也可以用下面的 sort 命令来去除重复的行,但是原来的行顺序没有被保留

sort -u your_file > sorted_deduplicated_file

使用 cat + sort + cut

上面的方法会产出一个去重的文件,各行是基于内容进行排序的。通过管道连接命令可以解决这个问题。

cat -n your_file | sort -uk2 | sort -nk1 | cut -f2-

工作原理

假设我们有下面一个文件:

abc
ghi
abc
def
xyz
def
ghi
klm

cat -n test.txt 在每行前面显示序号:

1       abc
2       ghi
3       abc
4       def
5       xyz
6       def
7       ghi
8       klm

sort -uk2 基于第二列(k2 选项)进行排序,对于第二列相同的值只保留一次(u 选项):

1       abc
4       def
2       ghi
8       klm
5       xyz

sort -nk1 基于第一列排序(k1 选项),把列的值作为数字来处理(-n 选项):

1       abc
2       ghi
4       def
5       xyz
8       klm

最后,cut -f2- 从第二列开始打印每一行,直到最后的内容(-f2- 选项:留意 - 后缀,它表示这行后面的内容都包含在内)。

abc
ghi
def
xyz
klm

参考

以上为全文。


via: https://opensource.com/article/19/10/remove-duplicate-lines-files-awk

作者:Lazarus Lazaridis 选题:lujun9972 译者:lxbwolf 校对:wxy

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

曾经,我们必须进行大量研究、阅读大量评论,才能找到一种在所选的 Linux 桌面发行版上可以以最少的麻烦工作的机器。而如今,这种日子已经一去不复返了,几乎每台机器都可以运行 Linux。Linux 内核社区在设备驱动程序支持方面做得非常出色,可以使一切都开箱即用。

不过,有的是可以运行 Linux 的机器,有的是运行 Linux 的机器。戴尔计算机属于后一类。五年前,Barton George 在戴尔内部启动了一项计划,将桌面版 Linux 引入到消费级的高端戴尔系统。从一台机器开始,到现在整套从产品线的高端笔记本电脑和台式机都可以运行 Linux。

在这些机器中,XPS 13 是我的最爱。尽管我需要一个功能强大的台式机来处理 4K UHD、多机位视频制作,但我还需要一台超便携的笔记本电脑,可以随身携带,而不必担心笨重的背包和充电器。XPS 13 也是我的第一台笔记本电脑,陪了我 7 年多。因此,是的,这还有一个怀旧因素。

戴尔几乎每年都会更新其 XPS 产品线,并且最新的产品展示宣布于 10 月XPS 13(7390) 是该系列的增量更新,而且戴尔非常乐意向我寄来一台测评设备。

它由 6 核 Core i7-10710U CPU 所支持。它配备 16GB 内存和 1TB SSD。在 1.10 GHz 的基本频率(可以超频到 4.1 GHz)的情况下,这是一台用于常规工作负载的出色机器。它没有使用任何专用的 GPU,因此它并不适合进行游戏或从源代码进行编译的 Gentoo Linux 或 Arch Linux。但是,我确实设法在上面运行了一些 Steam 游戏。

如果你想运行 Kubernetes 集群、AI 框架或虚拟现实,那么 Precision 系列中还有更强大的机器,这些机器可以运行 Red Hat Enterprise Linux 和 Ubuntu。

该机器的底盘与上一代相同。边框保持与上一代一样的薄,依旧比 MacBook 和微软的 Surface Pro 薄。

它具有三个端口,其中两个是 USB-C Thunderbolt 3,可用于连接 4K 显示器、USB 附件以及用于对等网络的计算机之间的高速数据传输。

它还具有一个 microSD 插槽。作为视频记者,SD 卡插槽会更有用。大量使用树莓派的用户也会喜欢这种卡。

它具有 4 个麦克风和一个改进的摄像头,该摄像头现在位于顶部(再见,鼻孔摄像头!)。

XPS 13(7390)光滑纤薄。它的重量仅为 2.7 磅(1.2kg),可以与苹果的 MacBook Air 相提并论。 这台机器可以成为你的旅行伴侣,并且可以执行日常任务,例如检查电子邮件、浏览网络和写作。

其 4K UHD 屏幕支持 HDR,这意味着你将可以尽享《The Mandalorian》的全部美妙之处。另外,车载扬声器并没有那么好,听起来有些沉闷。它们适合进行视频聊天或休闲的 YouTube 观看,但是如果你想在今年晚些时候观看《The Witcher》剧集,或者想欣赏 Amazon、Apple Music 或 YouTube Music 的音乐,则需要耳机或外接扬声器。

但是,在插入充电线之前,你可以能使用这台机器多少时间?在正常工作量的情况下,它为我提供了大约 7-8 个小时的电池续航时间:我打开了几个选项卡浏览网络,只是看看电影或听音乐。多任务处理,尤其是各种 Web 活动,都会加速消耗电池电量。在 Linux 上进行一些微调可能会给你带来更多的续航时间,而在 Windows 10 上,我可以使用 10 多个小时呢!

作为仍在从事大量写作工作的视频记者,我非常喜欢键盘。但是,我们这么多年来在 Linux 台式机上听到的触控板故事一直没变:它与 MacBook 或 Windows 上的品质相差甚远。这或许有一天能改变。值得称道的是,他们确实发布了可增强体验的触控板驱动程序,但我没有运行此系统随附的提供的 Ubuntu 18.04 LTS。我全新安装了 Ubuntu 19.10,因为 Gnome 在 18.04 中的运行速度非常慢。我尝试过 openSUSE Tumbleweed、Zorin OS、elementary OS、Fedora、KDE neon 和 Arch Linux。一切正常,尽管有些需要额外的努力才能运行。

那么,该系统适用于谁?显然,这是给那些想要设计精良的、他们信赖的品牌的高端机器的专业人士打造的。适用于喜欢 MacBook Air,但更喜欢 Linux 台式机生态系统的用户。适用于那些希望使用 Linux 来工作,而不是使 Linux 可以工作的人。

我使用这台机器一周的时间,进一步说明了为什么我如此喜欢戴尔的 XPS 系列。它们是目前最好的 Linux 笔记本电脑。这款 XPS 13(7390),你值得拥有!


via: https://www.linux.com/articles/dell-xps-13-7390-review-the-best-laptop-for-desktop-linux-user/

作者:Swapnil Bhartiya 选题:lujun9972 译者:wxy 校对:wxy

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