2019年10月

查找并排除你存储在 AWS 和 GitHub 中的数据里的漏洞。

如果你的日常工作是开发者、系统管理员、全栈工程师或者是网站可靠性工程师(SRE),工作内容包括使用 Git 从 GitHub 上推送、提交和拉取,并部署到亚马逊 Web 服务上(AWS),安全性就是一个需要持续考虑的一个点。幸运的是,开源工具能帮助你的团队避免犯常见错误,这些常见错误会导致你的组织损失数千美元。

本文介绍了四种开源工具,当你在 GitHub 和 AWS 上进行开发时,这些工具能帮助你提升项目的安全性。同样的,本着开源的精神,我会与三位安全专家——Travis McPeak,奈飞高级云安全工程师;Rich Monk,红帽首席高级信息安全分析师;以及 Alison Naylor,红帽首席信息安全分析师——共同为本文做出贡献。

我们已经按场景对每个工具都做了区分,但是它们并不是相互排斥的。

1、使用 gitrob 发现敏感数据

你需要发现任何出现于你们团队的 Git 仓库中的敏感信息,以便你能将其删除。借助专注于攻击应用程序或者操作系统的工具以使用红/蓝队模型,这样可能会更有意义,在这个模型中,一个信息安全团队会划分为两块,一个是攻击团队(又名红队),以及一个防守团队(又名蓝队)。有一个红队来尝试渗透你的系统和应用要远远好于等待一个攻击者来实际攻击你。你的红队可能会尝试使用 Gitrob,该工具可以克隆和爬取你的 Git 仓库,以此来寻找凭证和敏感信息。

即使像 Gitrob 这样的工具可以被用来造成破坏,但这里的目的是让你的信息安全团队使用它来发现无意间泄露的属于你的组织的敏感信息(比如 AWS 的密钥对或者是其他被失误提交上去的凭证)。这样,你可以修整你的仓库并清除敏感数据——希望能赶在攻击者发现它们之前。记住不光要修改受影响的文件,还要删除它们的历史记录

2、使用 git-secrets 来避免合并敏感数据

虽然在你的 Git 仓库里发现并移除敏感信息很重要,但在一开始就避免合并这些敏感信息岂不是更好?即使错误地提交了敏感信息,使用 git-secrets 可以避免你陷入公开的困境。这款工具可以帮助你设置钩子,以此来扫描你的提交、提交信息和合并信息,寻找常见的敏感信息模式。注意你选择的模式要匹配你的团队使用的凭证,比如 AWS 访问密钥和秘密密钥。如果发现了一个匹配项,你的提交就会被拒绝,一个潜在的危机就此得到避免。

为你已有的仓库设置 git-secrets 是很简单的,而且你可以使用一个全局设置来保护所有你以后要创建或克隆的仓库。你同样可以在公开你的仓库之前,使用 git-secrets 来扫描它们(包括之前所有的历史版本)。

3、使用 Key Conjurer 创建临时凭证

有一点额外的保险来防止无意间公开了存储的敏感信息,这是很好的事,但我们还可以做得更好,就完全不存储任何凭证。追踪凭证,谁访问了它,存储到了哪里,上次更新是什么时候——太麻烦了。然而,以编程的方式生成的临时凭证就可以避免大量的此类问题,从而巧妙地避开了在 Git 仓库里存储敏感信息这一问题。使用 Key Conjurer,它就是为解决这一需求而被创建出来的。有关更多 Riot Games 为什么创建 Key Conjurer,以及 Riot Games 如何开发的 Key Conjurer,请阅读 Key Conjurer:我们最低权限的策略

4、使用 Repokid 自动化地提供最小权限

任何一个参加过基本安全课程的人都知道,设置最小权限是基于角色的访问控制的最佳实现。难过的是,离开校门,会发现手动运用最低权限策略会变得如此艰难。一个应用的访问需求会随着时间的流逝而变化,开发人员又太忙了没时间去手动削减他们的权限。Repokid 使用 AWS 提供提供的有关身份和访问管理(IAM)的数据来自动化地调整访问策略。Repokid 甚至可以在 AWS 中为超大型组织提供自动化地最小权限设置。

工具而已,又不是大招

这些工具并不是什么灵丹妙药,它们只是工具!所以,在尝试使用这些工具或其他的控件之前,请和你的组织里一起工作的其他人确保你们已经理解了你的云服务的使用情况和用法模式。

应该严肃对待你的云服务和代码仓库服务,并熟悉最佳实现的做法。下面的文章将帮助你做到这一点。

对于 AWS:

对于 GitHub:

同样重要的一点是,和你的安全团队保持联系;他们应该可以为你团队的成功提供想法、建议和指南。永远记住:安全是每个人的责任,而不仅仅是他们的。


via: https://opensource.com/article/19/9/open-source-cloud-security

作者:Alison Naylor,Anderson Silva 选题:lujun9972 译者:hopefully2333 校对:wxy

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

那些令人怀念的经典游戏可是提高编程能力的好素材。今天就让我们仔细探索一番,怎么用 Bash 编写一个扫雷程序。

我在编程教学方面不是专家,但当我想更好掌握某一样东西时,会试着找出让自己乐在其中的方法。比方说,当我想在 shell 编程方面更进一步时,我决定用 Bash 编写一个扫雷)游戏来加以练习。

如果你是一个有经验的 Bash 程序员,希望在提高技巧的同时乐在其中,那么请跟着我编写一个你的运行在终端中的扫雷游戏。完整代码可以在这个 GitHub 存储库中找到。

做好准备

在我编写任何代码之前,我列出了该游戏所必须的几个部分:

  1. 显示雷区
  2. 创建游戏逻辑
  3. 创建判断单元格是否可选的逻辑
  4. 记录可用和已查明(已排雷)单元格的个数
  5. 创建游戏结束逻辑

显示雷区

在扫雷中,游戏界面是一个由 2D 数组(列和行)组成的不透明小方格。每一格下都有可能藏有地雷。玩家的任务就是找到那些不含雷的方格,并且在这一过程中,不能点到地雷。这个 Bash 版本的扫雷使用 10x10 的矩阵,实际逻辑则由一个简单的 Bash 数组来完成。

首先,我先生成了一些随机数字。这将是地雷在雷区里的位置。控制地雷的数量,在开始编写代码之前,这么做会容易一些。实现这一功能的逻辑可以更好,但我这么做,是为了让游戏实现保持简洁,并有改进空间。(我编写这个游戏纯属娱乐,但如果你能将它修改的更好,我也是很乐意的。)

下面这些变量在整个过程中是不变的,声明它们是为了随机生成数字。就像下面的 a - g 的变量,它们会被用来计算可排除的地雷的值:

# 变量
score=0 # 会用来存放游戏分数
# 下面这些变量,用来随机生成可排除地雷的实际值
a="1 10 -10 -1"
b="-1 0 1"
c="0 1"
d="-1 0 1 -2 -3"
e="1 2 20 21 10 0 -10 -20 -23 -2 -1"
f="1 2 3 35 30 20 22 10 0 -10 -20 -25 -30 -35 -3 -2 -1"
g="1 4 6 9 10 15 20 25 30 -30 -24 -11 -10 -9 -8 -7"
#
# 声明
declare -a room  # 声明一个 room 数组,它用来表示雷区的每一格。

接下来,我会用列(0-9)和行(a-j)显示出游戏界面,并且使用一个 10x10 矩阵作为雷区。(M[10][10] 是一个索引从 0-99,有 100 个值的数组。) 如想了解更多关于 Bash 数组的内容,请阅读这本书那些关于 Bash 你所不了解的事: Bash 数组简介

创建一个叫 plough 的函数,我们先将标题显示出来:两个空行、列头,和一行 -,以示意往下是游戏界面:

printf '\n\n'
printf '%s' "     a   b   c   d   e   f   g   h   i   j"
printf '\n   %s\n' "-----------------------------------------"

然后,我初始化一个计数器变量,叫 r,它会用来记录已显示多少横行。注意,稍后在游戏代码中,我们会用同一个变量 r,作为我们的数组索引。 在 Bash for 循环中,用 seq 命令从 0 增加到 9。我用数字(d%)占位,来显示行号($row,由 seq 定义):

r=0 # 计数器
for row in $(seq 0 9); do
  printf '%d  ' "$row" # 显示 行数 0-9 

在我们接着往下做之前,让我们看看到现在都做了什么。我们先横着显示 [a-j] 然后再将 [0-9] 的行号显示出来,我们会用这两个范围,来确定用户排雷的确切位置。

接着,在每行中,插入列,所以是时候写一个新的 for 循环了。这一循环管理着每一列,也就是说,实际上是生成游戏界面的每一格。我添加了一些辅助函数,你能在源码中看到它的完整实现。 对每一格来说,我们需要一些让它看起来像地雷的东西,所以我们先用一个点(.)来初始化空格。为了实现这一想法,我们用的是一个叫 is_null_field 的自定义函数。 同时,我们需要一个存储每一格具体值的数组,这儿会用到之前已定义的全局数组 room , 并用 变量 r作为索引。随着 r 的增加,遍历所有单元格,并随机部署地雷。

  for col in $(seq 0 9); do
    ((r+=1))  # 循环完一列行数加一
    is_null_field $r  #  假设这里有个函数,它会检查单元格是否为空,为真,则此单元格初始值为点(.)
    printf '%s \e[33m%s\e[0m ' "|" "${room[$r]}" #  最后显示分隔符,注意,${room[$r]} 的第一个值为 '.',等于其初始值。
  #结束 col 循环
  done

最后,为了保持游戏界面整齐好看,我会在每行用一个竖线作为结尾,并在最后结束行循环:

printf '%s\n' "|"   # 显示出行分隔符
printf '   %s\n' "-----------------------------------------"
# 结束行循环
done
printf '\n\n'

完整的 plough 代码如下:

plough()
{
  r=0
  printf '\n\n'
  printf '%s' "     a   b   c   d   e   f   g   h   i   j"
  printf '\n   %s\n' "-----------------------------------------"
  for row in $(seq 0 9); do
    printf '%d  ' "$row"
    for col in $(seq 0 9); do
       ((r+=1))
       is_null_field $r
       printf '%s \e[33m%s\e[0m ' "|" "${room[$r]}"
    done
    printf '%s\n' "|"
    printf '   %s\n' "-----------------------------------------"
  done
  printf '\n\n'
}

我花了点时间来思考,is_null_field 的具体功能是什么。让我们来看看,它到底能做些什么。在最开始,我们需要游戏有一个固定的状态。你可以随便选择个初始值,可以是一个数字或者任意字符。我最后决定,所有单元格的初始值为一个点(.),因为我觉得,这样会让游戏界面更好看。下面就是这一函数的完整代码:

is_null_field()
{
  local e=$1 # 在数组 room 中,我们已经用过循环变量 'r' 了,这次我们用 'e'
    if [[ -z "${room[$e]}" ]];then
      room[$r]="."  #这里用点(.)来初始化每一个单元格
    fi
}

现在,我已经初始化了所有的格子,现在只要用一个很简单的函数就能得出当前游戏中还有多少单元格可以操作:

get_free_fields()
{
  free_fields=0    # 初始化变量 
  for n in $(seq 1 ${#room[@]}); do
    if [[ "${room[$n]}" = "." ]]; then  # 检查当前单元格是否等于初始值(.),结果为真,则记为空余格子。 
      ((free_fields+=1))
    fi
  done
}

这是显示出来的游戏界面,[a-j] 为列,[0-9] 为行。

 title=

创建玩家逻辑

玩家操作背后的逻辑在于,先从 stdin) 中读取数据作为坐标,然后再找出对应位置实际包含的值。这里用到了 Bash 的参数扩展,来设法得到行列数。然后将代表列数的字母传给分支语句,从而得到其对应的列数。为了更好地理解这一过程,可以看看下面这段代码中,变量 o 所对应的值。 举个例子,玩家输入了 c3,这时 Bash 将其分成两个字符:c3。为了简单起见,我跳过了如何处理无效输入的部分。

  colm=${opt:0:1}  # 得到第一个字符,一个字母
  ro=${opt:1:1}    # 得到第二个字符,一个整数
  case $colm in
    a ) o=1;;      # 最后,通过字母得到对应列数。
    b ) o=2;;
    c ) o=3;;
    d ) o=4;;
    e ) o=5;;
    f ) o=6;;
    g ) o=7;;
    h ) o=8;;
    i ) o=9;;
    j ) o=10;;
  esac

下面的代码会计算用户所选单元格实际对应的数字,然后将结果储存在变量中。

这里也用到了很多的 shuf 命令,shuf 是一个专门用来生成随机序列的 Linux 命令-i 选项后面需要提供需要打乱的数或者范围,-n 选项则规定输出结果最多需要返回几个值。Bash 中,可以在两个圆括号内进行数学计算,这里我们会多次用到。

还是沿用之前的例子,玩家输入了 c3。 接着,它被转化成了 ro=3o=3。 之后,通过上面的分支语句代码, 将 c 转化为对应的整数,带进公式,以得到最终结果 i 的值。

  i=$(((ro*10)+o))   # 遵循运算规则,算出最终值
  is_free_field $i $(shuf -i 0-5 -n 1)   #  调用自定义函数,判断其指向空/可选择单元格。

仔细观察这个计算过程,看看最终结果 i 是如何计算出来的:

i=$(((ro*10)+o))
i=$(((3*10)+3))=$((30+3))=33

最后结果是 33。在我们的游戏界面显示出来,玩家输入坐标指向了第 33 个单元格,也就是在第 3 行(从 0 开始,否则这里变成 4),第 3 列。

创建判断单元格是否可选的逻辑

为了找到地雷,在将坐标转化,并找到实际位置之后,程序会检查这一单元格是否可选。如不可选,程序会显示一条警告信息,并要求玩家重新输入坐标。

在这段代码中,单元格是否可选,是由数组里对应的值是否为点(.)决定的。如果可选,则重置单元格对应的值,并更新分数。反之,因为其对应值不为点,则设置变量 not_allowed。为简单起见,游戏中警告消息这部分源码,我会留给读者们自己去探索。

is_free_field()
{
  local f=$1
  local val=$2
  not_allowed=0
  if [[ "${room[$f]}" = "." ]]; then
    room[$f]=$val
    score=$((score+val))
  else
    not_allowed=1
  fi
}

 title=

如输入坐标有效,且对应位置为地雷,如下图所示。玩家输入 h6,游戏界面会出现一些随机生成的值。在发现地雷后,这些值会被加入用户得分。

 title=

还记得我们开头定义的变量,a - g 吗,我会用它们来确定随机生成地雷的具体值。所以,根据玩家输入坐标,程序会根据(m)中随机生成的数,来生成周围其他单元格的值(如上图所示)。之后将所有值和初始输入坐标相加,最后结果放在 i(计算结果如上)中。

请注意下面代码中的 X,它是我们唯一的游戏结束标志。我们将它添加到随机列表中。在 shuf 命令的魔力下,X 可以在任意情况下出现,但如果你足够幸运的话,也可能一直不会出现。

m=$(shuf -e a b c d e f g X -n 1)   # 将 X 添加到随机列表中,当 m=X,游戏结束
  if [[ "$m" != "X" ]]; then        # X 将会是我们爆炸地雷(游戏结束)的触发标志
    for limit in ${!m}; do          # !m 代表 m 变量的值
      field=$(shuf -i 0-5 -n 1)     # 然后再次获得一个随机数字
      index=$((i+limit))            # 将 m 中的每一个值和 index 加起来,直到列表结尾
      is_free_field $index $field
    done

我想要游戏界面中,所有随机显示出来的单元格,都靠近玩家选择的单元格。

 title=

记录已选择和可用单元格的个数

这个程序需要记录游戏界面中哪些单元格是可选择的。否则,程序会一直让用户输入数据,即使所有单元格都被选中过。为了实现这一功能,我创建了一个叫 free_fields 的变量,初始值为 0。用一个 for 循环,记录下游戏界面中可选择单元格的数量。 如果单元格所对应的值为点(.),则 free_fields 加一。

get_free_fields()
{
  free_fields=0
  for n in $(seq 1 ${#room[@]}); do
    if [[ "${room[$n]}" = "." ]]; then
      ((free_fields+=1))
    fi
  done
}

等下,如果 free_fields=0 呢? 这意味着,玩家已选择过所有单元格。如果想更好理解这一部分,可以看看这里的源代码

if [[ $free_fields -eq 0 ]]; then   # 这意味着你已选择过所有格子
      printf '\n\n\t%s: %s %d\n\n' "You Win" "you scored" "$score"
      exit 0
fi

创建游戏结束逻辑

对于游戏结束这种情况,我们这里使用了一些很巧妙的技巧,将结果在屏幕中央显示出来。我把这部分留给读者朋友们自己去探索。

if [[ "$m" = "X" ]]; then
    g=0                      # 为了在参数扩展中使用它
    room[$i]=X               # 覆盖此位置原有的值,并将其赋值为X
    for j in {42..49}; do    # 在游戏界面中央,
      out="gameover"
      k=${out:$g:1}          # 在每一格中显示一个字母
      room[$j]=${k^^}
      ((g+=1))
    done
fi

最后,我们显示出玩家最关心的两行。

if [[ "$m" = "X" ]]; then
      printf '\n\n\t%s: %s %d\n' "GAMEOVER" "you scored" "$score"
      printf '\n\n\t%s\n\n' "You were just $free_fields mines away."
      exit 0
fi

 title=

文章到这里就结束了,朋友们!如果你想了解更多,具体可以查看我的 GitHub 存储库,那儿有这个扫雷游戏的源代码,并且你还能找到更多用 Bash 编写的游戏。 我希望,这篇文章能激起你学习 Bash 的兴趣,并乐在其中。


via: https://opensource.com/article/19/9/advanced-bash-building-minesweeper

作者:Abhishek Tamrakar 选题:lujun9972 译者:wenwensnow 校对:wxy

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

通常,Linux 管理员们都使用 history 命令来跟踪在先前的会话中执行过哪些命令,但是 history 命令的局限性在于它不存储命令的输出。在某些情况下,我们要检查上一个会话的命令输出,并希望将其与当前会话进行比较。除此之外,在某些情况下,我们正在对 Linux 生产环境中的问题进行故障排除,并希望保存所有终端会话活动以供将来参考,因此在这种情况下,script 命令就变得很方便。

script 是一个命令行工具,用于捕获/记录你的 Linux 服务器终端会话活动,以后可以使用 scriptreplay 命令重放记录的会话。在本文中,我们将演示如何安装 script 命令行工具以及如何记录 Linux 服务器终端会话活动,然后,我们将看到如何使用 scriptreplay 命令来重放记录的会话。

安装 script 工具

在 RHEL 7/ CentOS 7 上安装 script 工具

script 命令由 RPM 包 util-linux 提供,如果你没有在你的 CentOS 7 / RHEL 7 系统上安装它,运行下面的 yum 安装它:

[root@linuxtechi ~]# yum install util-linux -y

在 RHEL 8 / CentOS 8 上安装 script 工具

运行下面的 dnf 命令来在 RHEL 8 / CentOS 8 上安装 script 工具:

[root@linuxtechi ~]# dnf install util-linux -y

在基于 Debian 的系统(Ubuntu / Linux Mint)上安装 script 工具

运行下面的 apt-get 命令来安装 script 工具:

root@linuxtechi ~]# apt-get install util-linux -y

如何使用 script 工具

直接使用 script 命令,在终端上键入 script 命令,然后按回车,它将开始在名为 typescript 的文件中捕获当前的终端会话活动。

[root@linuxtechi ~]# script
Script started, file is typescript
[root@linuxtechi ~]#

要停止记录会话活动,请键入 exit 命令,然后按回车:

[root@linuxtechi ~]# exit
exit
Script done, file is typescript
[root@linuxtechi ~]#

script 命令的语法格式:

~] # script {options}  {file_name}

能在 script 命令中使用的不同选项:

options-script-command

让我们开始通过执行 script 命令来记录 Linux 终端会话,然后执行诸如 wroute -ndf -hfree -h,示例如下所示:

script-examples-linux-server

正如我们在上面看到的,终端会话日志保存在文件 typescript 中:

现在使用 cat / vi 命令查看 typescript 文件的内容,

[root@linuxtechi ~]# ls -l typescript
-rw-r--r--. 1 root root 1861 Jun 21 00:50 typescript
[root@linuxtechi ~]#

typescript-file-content-linux

以上内容确认了我们在终端上执行的所有命令都已保存在 typescript 文件中。

在 script 命令中使用定制文件名

假设我们要使用自定义文件名来执行 script 命令,可以在 script 命令后指定文件名。在下面的示例中,我们使用的文件名为 session-log-(当前日期时间).txt

[root@linuxtechi ~]# script sessions-log-$(date +%d-%m-%Y-%T).txt
Script started, file is sessions-log-21-06-2019-01:37:39.txt
[root@linuxtechi ~]#

现在运行该命令并输入 exit

[root@linuxtechi ~]# exit
exit
Script done, file is sessions-log-21-06-2019-01:37:39.txt
[root@linuxtechi ~]#

附加命令输出到 script 记录文件

假设 script 命令已经将命令输出记录到名为 session-log.txt 的文件中,现在我们想将新会话命令的输出附加到该文件中,那么可以在 script 命令中使用 -a 选项。

[root@linuxtechi ~]# script -a sessions-log.txt
Script started, file is sessions-log.txt
[root@linuxtechi ~]# xfs_info /dev/mapper/centos-root
meta-data=/dev/mapper/centos-root isize=512    agcount=4, agsize=2746624 blks
         =                       sectsz=512   attr=2, projid32bit=1
         =                       crc=1        finobt=0 spinodes=0
data     =                       bsize=4096   blocks=10986496, imaxpct=25
         =                       sunit=0      swidth=0 blks
naming   =version 2              bsize=4096   ascii-ci=0 ftype=1
log      =internal               bsize=4096   blocks=5364, version=2
         =                       sectsz=512   sunit=0 blks, lazy-count=1
realtime =none                   extsz=4096   blocks=0, rtextents=0
[root@linuxtechi ~]# exit
exit
Script done, file is sessions-log.txt
[root@linuxtechi ~]#

要查看更新的会话记录,使用 cat session-log.txt 命令。

无需 shell 交互而捕获命令输出到 script 记录文件

假设我们要捕获命令的输出到会话记录文件,那么使用 -c 选项,示例如下所示:

[root@linuxtechi ~]# script -c "uptime && hostname && date" root-session.txt
Script started, file is root-session.txt
 01:57:40 up  2:30,  3 users,  load average: 0.00, 0.01, 0.05
linuxtechi
Fri Jun 21 01:57:40 EDT 2019
Script done, file is root-session.txt
[root@linuxtechi ~]#

以静默模式运行 script 命令

要以静默模式运行 script 命令,请使用 -q 选项,该选项将禁止 script 的启动和完成消息,示例如下所示:

[root@linuxtechi ~]# script -c "uptime && date" -q root-session.txt
 02:01:10 up  2:33,  3 users,  load average: 0.00, 0.01, 0.05
Fri Jun 21 02:01:10 EDT 2019
[root@linuxtechi ~]#

要将时序信息记录到文件中并捕获命令输出到单独的文件中,这可以通过在 script 命令中传递时序文件(-timing)实现,示例如下所示:

语法格式:

~ ]# script -t <timing-file-name>  {file_name}
[root@linuxtechi ~]# script --timing=timing.txt session.log
Script started, file is session.log
[root@linuxtechi ~]# uptime
 02:27:59 up  3:00,  3 users,  load average: 0.00, 0.01, 0.05
[root@linuxtechi ~]# date
Fri Jun 21 02:28:02 EDT 2019
[root@linuxtechi ~]# free -h
              total        used        free      shared  buff/cache   available
Mem:           3.9G        171M        2.0G        8.6M        1.7G        3.3G
Swap:          3.9G          0B        3.9G
[root@linuxtechi ~]# whoami
root
[root@linuxtechi ~]# exit
exit
Script done, file is session.log
[root@linuxtechi ~]#
[root@linuxtechi ~]# ls -l session.log timing.txt
-rw-r--r--. 1 root root 673 Jun 21 02:28 session.log
-rw-r--r--. 1 root root 414 Jun 21 02:28 timing.txt
[root@linuxtechi ~]#

重放记录的 Linux 终端会话活动

现在,使用 scriptreplay 命令重放录制的终端会话活动。

注意:scriptreplay 也由 RPM 包 util-linux 提供。scriptreplay 命令需要时序文件才能工作。

[root@linuxtechi ~]# scriptreplay --timing=timing.txt session.log

上面命令的输出将如下所示,

记录所有用户的 Linux 终端会话活动

在某些关键业务的 Linux 服务器上,我们希望跟踪所有用户的活动,这可以使用 script 命令来完成,将以下内容放在 /etc/profile 文件中,

[root@linuxtechi ~]# vi /etc/profile
……………………………………………………
if [ "x$SESSION_RECORD" = "x" ]
then
timestamp=$(date +%d-%m-%Y-%T)
session_log=/var/log/session/session.$USER.$$.$timestamp
SESSION_RECORD=started
export SESSION_RECORD
script -t -f -q 2>${session_log}.timing $session_log
exit
fi
……………………………………………………

保存文件并退出。

/var/log 文件夹下创建 session 目录:

[root@linuxtechi ~]# mkdir /var/log/session

给该文件夹指定权限:

[root@linuxtechi ~]# chmod 777 /var/log/session/
[root@linuxtechi ~]#

现在,验证以上代码是否有效。在我正在使用 pkumar 用户的情况下,登录普通用户到 Linux 服务器:

~ ] # ssh root@linuxtechi
root@linuxtechi's password:
[root@linuxtechi ~]$ uptime
 04:34:09 up  5:06,  3 users,  load average: 0.00, 0.01, 0.05
[root@linuxtechi ~]$ date
Fri Jun 21 04:34:11 EDT 2019
[root@linuxtechi ~]$ free -h
              total        used        free      shared  buff/cache   available
Mem:           3.9G        172M        2.0G        8.6M        1.7G        3.3G
Swap:          3.9G          0B        3.9G
[root@linuxtechi ~]$ id
uid=1001(pkumar) gid=1002(pkumar) groups=1002(pkumar) context=unconfined_u:unconfined_r:unconfined_t:s0-s0:c0.c1023
[root@linuxtechi ~]$ whoami
pkumar
[root@linuxtechi ~]$ exit

Login as root and view user’s linux terminal session activity

[root@linuxtechi ~]# cd /var/log/session/
[root@linuxtechi session]# ls -l | grep pkumar
-rw-rw-r--. 1 pkumar pkumar 870 Jun 21 04:34 session.pkumar.19785.21-06-2019-04:34:05
-rw-rw-r--. 1 pkumar pkumar 494 Jun 21 04:34 session.pkumar.19785.21-06-2019-04:34:05.timing
[root@linuxtechi session]#

Session-output-file-linux

我们还可以使用 scriptreplay 命令来重放用户的终端会话活动:

[root@linuxtechi session]# scriptreplay --timing session.pkumar.19785.21-06-2019-04\:34\:05.timing session.pkumar.19785.21-06-2019-04\:34\:05

以上就是本教程的全部内容,请在下面的评论部分中分享你的反馈和评论。


via: https://www.linuxtechi.com/record-replay-linux-terminal-sessions-activity/

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

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

也许你并不需要编译 Linux 内核,但你能通过这篇教程快速上手。

在计算机世界里, 内核 kernel 是处理硬件与一般系统之间通信的 低阶软件 low-level software 。除过一些烧录进计算机主板的初始固件,当你启动计算机时,内核让系统意识到它有一个硬盘驱动器、屏幕、键盘以及网卡。分配给每个部件相等时间(或多或少)使得图像、音频、文件系统和网络可以流畅甚至并行地运行。

然而,对于硬件的需求是源源不断的,随着发布的硬件越多,内核就必须纳入更多代码来保证那些硬件正常工作。得到具体的数字很困难,但是 Linux 内核无疑是硬件兼容性方面的顶级内核之一。Linux 操作着无数的计算机和移动电话、工业用途和爱好者使用的板级嵌入式系统(SoC)、RAID 卡、缝纫机等等。

回到 20 世纪(甚至是 21 世纪初期),对于 Linux 用户来说,在刚买到新的硬件后就需要下载最新的内核代码并编译安装才能使用这是不可理喻的。而现在你也很难见到 Linux 用户为了好玩而编译内核或通过高度专业化定制的硬件的方式赚钱。现在,通常已经不需要再编译 Linux 内核了。

这里列出了一些原因以及快速编译内核的教程。

更新当前的内核

无论你买了配备新显卡或 Wifi 芯片集的新品牌电脑还是给家里配备一个新的打印机,你的操作系统(称为 GNU+Linux 或 Linux,它也是该内核的名字)需要一个驱动程序来打开新部件(显卡、芯片集、打印机和其他任何东西)的信道。有时候当你插入某些新的设备时而你的电脑表示发现了它,这具有一定的欺骗性。别被骗到了,有时候那就够了,但更多的情况是你的操作系统仅仅是使用了通用的协议检测到安装了新的设备。

例如,你的计算机也许能够鉴别出新的网络打印机,但有时候那仅仅是因为打印机的网卡被设计成为了获得 DHCP 地址而在网络上标识自己。它并不意味着你的计算机知道如何发送文档给打印机进行打印。事实上,你可以认为计算机甚至不“知道”那台设备是一个打印机。它也许仅仅是显示网络有个设备在一个特定的地址上,并且该设备以一系列字符 “p-r-i-n-t-e-r” 标识自己而已。人类语言的便利性对于计算机毫无意义。计算机需要的是一个驱动程序。

内核开发者、硬件制造商、技术支持和爱好者都知道新的硬件会不断地发布。它们大多数都会贡献驱动程序,直接提交给内核开发团队以包含在 Linux 中。例如,英伟达显卡驱动程序通常都会写入 Nouveau 内核模块中,并且因为英伟达显卡很常用,它的代码都包含在任一个日常使用的发行版内核中(例如当下载 FedoraUbuntu 得到的内核)。英伟达也有不常用的地方,例如嵌入式系统中 Nouveau 模块通常被移除。对其他设备来说也有类似的模块:打印机得益于 FoomaticCUPS,无线网卡有 b43、ath9k、wl 模块等等。

发行版往往会在它们 Linux 内核的构建中包含尽可能多合理的驱动程序,因为他们想让你在接入新设备时不用安装驱动程序能够立即使用。对于大多数情况来说就是这样的,尤其是现在很多设备厂商都在资助自己售卖硬件的 Linux 驱动程序开发,并且直接将这些驱动程序提交给内核团队以用在通常的发行版上。

有时候,或许你正在运行六个月之前安装的内核,并配备了上周刚刚上市令人兴奋的新设备。在这种情况下,你的内核也许没有那款设备的驱动程序。好消息是经常会出现那款设备的驱动程序已经存在于最近版本的内核中,意味着你只要更新运行的内核就可以了。

通常,这些都是通过安装包管理软件完成的。例如在 RHEL、CentOS 和 Fedora 上:

$ sudo dnf update kernel

在 Debian 和 Ubuntu 上,首先获取你当前内核的版本:

$ uname -r
4.4.186

搜索新的版本:

$ sudo apt update
$ sudo apt search linux-image

安装找到的最新版本。在这个例子中,最新的版本是 5.2.4:

$ sudo apt install linux-image-5.2.4

内核更新后,你必须 reboot (除非你使用 kpatch 或 kgraft)。这时,如果你需要的设备驱动程序包含在最新的内核中,你的硬件就会正常工作。

安装内核模块

有时候一个发行版没有预计到用户会使用某个设备(或者该设备的驱动程序至少不足以包含在 Linux 内核中)。Linux 对于驱动程序采用模块化方式,因此尽管驱动程序没有编译进内核,但发行版可以推送单独的驱动程序包让内核去加载。尽管有些复杂但是非常有用,尤其是当驱动程序没有包含进内核中而是在引导过程中加载,或是内核中的驱动程序相比模块化的驱动程序过期时。第一个问题可以用 “initrd” 解决(初始化 RAM 磁盘),这一点超出了本文的讨论范围,第二点通过 “kmod” 系统解决。

kmod 系统保证了当内核更新后,所有与之安装的模块化驱动程序也得到更新。如果你手动安装一个驱动程序,你就体验不到 kmod 提供的自动化,因此只要能用 kmod 安装包,就应该选择它。例如,尽管英伟达驱动程序以 Nouveau 模块构建在内核中,但官方的驱动程序仅由英伟达发布。你可以去网站上手动安装英伟达旗下的驱动程序,下载 “.run” 文件,并运行提供的 shell 脚本,但在安装了新的内核之后你必须重复相同的过程,因为没有任何东西告诉包管理软件你手动安装了一个内核驱动程序。英伟达驱动着你的显卡,手动更新英伟达驱动程序通常意味着你需要通过终端来执行更新,因为没有显卡驱动程序将无法显示。

 title=

然而,如果你通过 kmod 包安装英伟达驱动程序,更新你的内核也会更新你的英伟达驱动程序。在 Fedora 和相关的发行版中:

$ sudo dnf install kmod-nvidia

在 Debian 和相关发行版上:

$ sudo apt update
$ sudo apt install nvidia-kernel-common nvidia-kernel-dkms nvidia-glx nvidia-xconfig nvidia-settings nvidia-vdpau-driver vdpau-va-driver

这仅仅是一个例子,但是如果你真的要安装英伟达驱动程序,你也必须屏蔽掉 Nouveau 驱动程序。参考你使用发行版的文档获取最佳的步骤吧。

下载并安装驱动程序

不是所有的东西都包含在内核中,也不是所有的东西都可以作为内核模块使用。在某些情况下,你需要下载一个由供应商编写并绑定好的特殊驱动程序,还有一些情况,你有驱动程序,但是没有配置驱动程序的前端界面。

有两个常见的例子是 HP 打印机和 Wacom 数位板。如果你有一台 HP 打印机,你可能有能够和打印机通信的通用的驱动程序,甚至能够打印出东西。但是通用的驱动程序却不能为特定型号的打印机提供定制化的选项,例如双面打印、校对、纸盒选择等等。HPLIP(HP Linux 成像和打印系统)提供了选项来进行任务管理、调整打印设置、选择可用的纸盒等等。

HPLIP 通常包含在包管理软件中;只要搜索“hplip”就行了。

 title=

同样的,电子艺术家主要使用的数位板 Wacom 的驱动程序通常也包含在内核中,但是例如调整压感和按键功能等设置只能通过默认包含在 GNOME 的图形控制面板访问。但也可以作为 KDE 上额外的程序包“kde-config-tablet”来访问。

这里也有几个类似的个别例子,例如内核中没有驱动程序,但是以 RPM 或 DEB 文件提供了可供下载并且通过包管理软件安装的 kmod 版本的驱动程序。

打上补丁并编译你的内核

即使在 21 世纪的未来主义乌托邦里,仍有厂商不够了解开源,没有提供可安装的驱动程序。有时候,一些公司为驱动程序提供开源代码,而需要你下载代码、修补内核、编译并手动安装。

这种发布方式和在 kmod 系统之外安装打包的驱动程序拥有同样的缺点:对内核的更新会破坏驱动程序,因为每次更换新的内核时都必须手动将其重新集成到内核中。

令人高兴的是,这种事情变得少见了,因为 Linux 内核团队在呼吁公司们与他们交流方面做得很好,并且公司们最终接受了开源不会很快消失的事实。但仍有新奇的或高度专业的设备仅提供了内核补丁。

官方上,对于你如何编译内核以使包管理器参与到升级系统如此重要的部分中,发行版有特定的习惯。这里有太多的包管理器,所以无法一一涵盖。举一个例子,当你使用 Fedora 上的工具例如 rpmdevbuild-essential,Debian 上的 devscripts

首先,像通常那样,找到你正在运行内核的版本:

$ uname -r

在大多数情况下,如果你还没有升级过内核那么可以试试升级一下内核。搞定之后,也许你的问题就会在最新发布的内核中解决。如果你尝试后发现不起作用,那么你应该下载正在运行内核的源码。大多数发行版提供了特定的命令来完成这件事,但是手动操作的话,可以在 kernel.org 上找到它的源代码。

你必须下载内核所需的任何补丁。有时候,这些补丁对应具体的内核版本,因此请谨慎选择。

通常,或至少在人们习惯于编译内核的那时,都是拿到源代码并对 /usr/src/linux 打上补丁。

解压内核源码并打上需要的补丁:

$ cd /usr/src/linux
$ bzip2 --decompress linux-5.2.4.tar.bz2
$ cd  linux-5.2.4
$ bzip2 -d ../patch*bz2

补丁文件也许包含如何使用的教程,但通常它们都设计成在内核源码树的顶层可用来执行。

$ patch -p1 < patch*example.patch

当内核代码打上补丁后,你可以继续使用旧的配置来对打了补丁的内核进行配置。

$ make oldconfig

make oldconfig 命令有两个作用:它继承了当前的内核配置,并且允许你配置补丁带来的新的选项。

你或许需要运行 make menuconfig 命令,它启动了一个基于 ncurses 的菜单界面,列出了新的内核所有可能的选项。整个菜单可能看不过来,但是它是以旧的内核配置为基础的,你可以遍历菜单并且禁用掉你没有或不需要的硬件模块。另外,如果你知道自己有一些硬件没有包含在当前的配置中,你可以选择构建它,当作模块或者直接嵌入内核中。理论上,这些并不是必要的,因为你可以猜想,当前的内核运行良好只是缺少了补丁,当使用补丁的时候可能已经激活了所有设备所必要的选项。

下一步,编译内核和它的模块:

$ make bzImage
$ make modules

这会产生一个叫作 vmlinuz 的文件,它是你的可引导内核的压缩版本。保存旧的版本并在 /boot 文件夹下替换为新的。

$ sudo mv /boot/vmlinuz /boot/vmlinuz.nopatch
$ sudo cat arch/x86_64/boot/bzImage > /boot/vmlinuz
$ sudo mv /boot/System.map /boot/System.map.stock
$ sudo cp System.map /boot/System.map

到目前为止,你已经打上了补丁并且编译了内核和它的模块,你安装了内核,但你并没有安装任何模块。那就是最后的步骤:

$ sudo make modules_install

新的内核已经就位,并且它的模块也已经安装。

最后一步是更新你的引导程序,为了让你的计算机在加载 Linux 内核之前知道它的位置。GRUB 引导程序使这一过程变得相当简单:

$ sudo grub2-mkconfig

现实生活中的编译

当然,现在没有人手动执行这些命令。相反的,参考你的发行版,寻找发行版维护人员使用的开发者工具集修改内核的说明。这些工具集可能会创建一个集成所有补丁的可安装软件包,告诉你的包管理器来升级并更新你的引导程序。

内核

操作系统和内核都是玄学,但要理解构成它们的组件并不难。下一次你看到某个技术无法应用在 Linux 上时,深呼吸,调查可用的驱动程序,寻找一条捷径。Linux 比以前简单多了——包括内核。


via: https://opensource.com/article/19/8/linux-kernel-21st-century

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

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

通过 Oh My Zsh 安装的主题和插件来扩展 Zsh 的功能。

在我的前文中,我向大家展示了如何安装并使用 Z-Shell (Zsh)。对于某些用户来说,Zsh 最令人激动的是它可以安装主题。Zsh 安装主题非常容易,一方面是因为有非常活跃的社区为 Z-Shell 设计主题,另一方面是因为有 Oh My Zsh 这个项目。这使得安装主题变得轻而易举。

主题的变化可能会立刻吸引你的注意力,因此如果你安装了 Zsh 并且将默认的 Shell 替换为 Zsh 时,你可能不喜欢 Shell 默认主题的样子,那么你可以立即更换 Oh My Zsh 自带的 100 多个主题。Oh My Zsh 不仅拥有大量精美的主题,同时还有数以百计的扩展 Zsh 功能的插件。

安装 Oh My Zsh

Oh My Zsh 的官网建议你使用一个脚本在有网络的情况下来安装这个包。尽管 Oh My Zsh 项目几乎是可以令人信服的,但是盲目地在你的电脑上运行一个脚本这是一个糟糕的建议。如果你想运行这个脚本,你可以把它下载下来,看一下它实现了什么功能,在你确信你已经了解了它的所作所为之后,你就可以运行它了。

如果你下载了脚本并且阅读了它,你就会发现安装过程仅仅只有三步:

1、克隆 oh-my-zsh

第一步,克隆 oh-my-zsh 库到 ~/.oh-my-zsh 目录:

% git clone http://github.com/robbyrussell/oh-my-zsh ~/.oh-my-zsh

2、切换配置文件

下一步,备份你已有的 .zshrc 文件,然后将 oh-my-zsh 自带的配置文件移动到这个地方。这两步操作可以一步完成,只需要你的 mv 命令支持 -b 这个选项。

% mv -b \
~/.oh-my-zsh/templates/zshrc.zsh-template ~/.zshrc

3、编辑配置文件

默认情况下,Oh My Zsh 自带的配置文件是非常简陋的。如果你想将你自己的 ~/.zshrc 文件合并到 .oh-my-zsh 的配置文件中。你可以使用 cat 命令将你的旧的配置文件添加到新文件的末尾。

% cat ~/.zshrc~ >> ~/.zshrc

看一下默认的配置文件以及它提供的一些选项。用你最喜欢的编辑器打开 ~/.zshrc 文件。这个文件有非常良好的注释。这是了解它的一个非常好的方法。

例如,你可以更改 .oh-my-zsh 目录的位置。在安装的时候,它默认是位于你的家目录。但是,根据 Free Desktop 所定义的现代 Linux 规范。这个目录应当放置于 ~/.local/share 。你可以在配置文件中进行修改。如下所示:

# Path to your oh-my-zsh installation.
export ZSH=$HOME/.local/share/oh-my-zsh

然后将 .oh-my-zsh 目录移动到你新配置的目录下:

% mv ~/.oh-my-zsh $HOME/.local/share/oh-my-zsh

如果你使用的是 MacOS,这个目录可能会有点含糊不清,但是最合适的位置可能是在 $HOME/Library/Application\ Support

重新启动 Zsh

编辑配置文件之后,你必须重新启动你的 Shell。在这之前,你必须确定你的任何操作都已正确完成。例如,在你修改了 .oh-my-zsh 目录的路径之后。不要忘记将目录移动到新的位置。如果你不想重新启动你的 Shell。你可以使用 source 命令来使你的配置文件生效。

% source ~/.zshrc
➜  .oh-my-zsh git:(master) ✗

你可以忽略任何丢失更新文件的警告;他们将会在重启的时候再次进行解析。

更换你的主题

安装好 oh-my-zsh 之后。你可以将你的 Zsh 的主题设置为 robbyrussell,这是一个该项目维护者的主题。这个主题的更改是非常小的,仅仅是改变了提示符的颜色。

你可以通过列出 .oh-my-zsh 目录下的所有文件来查看所有安装的主题:

➜  .oh-my-zsh git:(master) ✗ ls ~/.local/share/oh-my-zsh/themes
3den.zsh-theme
adben.zsh-theme
af-magic.zsh-theme
afowler.zsh-theme
agnoster.zsh-theme
[...]

想在切换主题之前查看一下它的样子,你可以查看 Oh My Zsh 的 wiki 页面。要查看更多主题,可以查看 外部主题 wiki 页面。

大部分的主题是非常易于安装和使用的,仅仅需要改变 .zshrc 文件中的配置选项然后重新载入配置文件。

➜ ~ sed -i 's/_THEME=\"robbyrussel\"/_THEME=\"linuxonly\"/g' ~/.zshrc
➜ ~ source ~/.zshrc
seth@darkstar:pts/0-&gt;/home/skenlon (0) ➜

其他的主题可能需要一些额外的配置。例如,为了使用 agnoster 主题,你必须先安装 Powerline 字体。这是一个开源字体,如果你使用 Linux 操作系统的话,这个字体很可能在你的软件库中存在。使用下面的命令安装这个字体:

➜ ~ sudo dnf install powerline-fonts

在配置文件中更改你的主题:

➜ ~ sed -i 's/_THEME=\"linuxonly\"/_THEME=\"agnoster\"/g' ~/.zshrc

重新启动你的 Sehll(一个简单的 source 命令并不会起作用)。一旦重启,你就可以看到新的主题:

 title=

安装插件

Oh My Zsh 有超过 200 的插件,你可以在 .oh-my-zsh/plugins 中看到它们。每一个扩展目录下都有一个 README 文件解释了这个插件的作用。

一些插件相当简单。例如,dnfubuntubrewmacports 插件仅仅是为了简化与 DNF、Apt、Homebres 和 MacPorts 的交互操作而定义的一些别名。

而其他的一些插件则较为复杂,git 插件默认是被激活使用的。当你的目录是一个 git 仓库的时候,这个扩展就会更新你的 Shell 提示符,以显示当前的分支和是否有未合并的更改。

为了激活这个扩展,你可以将这个扩展添加到你的配置文件 ~/.zshrc 中。例如,你可以添加 dnfpass 插件,按照如下的方式更改:

plugins=(git dnf pass)

保存修改,重新启动你的 Shell。

% source ~/.zshrc

这个扩展现在就可以使用了。你可以通过使用 dnf 提供的别名来测试一下:

% dnfs fop
====== Name Exactly Matched: fop ======
fop.noarch : XSL-driven print formatter

不同的插件做不同的事,因此你可以一次安装一两个插件来帮你学习新的特性和功能。

兼容性

一些 Oh My Zsh 插件具有通用性。如果你看到一个插件声称它可以与 Bash 兼容,那么它就可以在你自己的 Bash 中使用。另一些插件需要 Zsh 提供的特定功能。因此,它们并不是所有都能工作。但是你可以添加一些其他的插件,例如 dnfubuntufirewalld,以及其他的一些插件。你可以使用 source 使你的选择生效。例如:

if [ -d $HOME/.local/share/oh-my-zsh/plugins ]; then
        source $HOME/.local/share/oh-my-zsh/plugins/dnf/dnf.plugin.zsh
fi

选择或者不选择 Zsh

Z-shell 的内置功能和它由社区贡献的扩展功能都非常强大。你可以把它当成你的主 Shell 使用,你也可以在你休闲娱乐的时候尝试一下。这取决于你的爱好。

什么是你最喜爱的主题和扩展可以在下方的评论告诉我们!


via: https://opensource.com/article/19/9/adding-plugins-zsh

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

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

Fedora 31 中丢弃了 32 位 i686 内核及其可启动镜像。虽然可能有一些用户仍然拥有无法与 64 位 x86\_64 内核一起使用的硬件,但数量很少。本文为你提供了这次更改背后的整个事情,以及在 Fedora 31 中仍然可以找到的 32 位元素。

发生了什么?

i686 架构实质上从 Fedora 27 版本就进入了社区支持阶段(LCTT 译注:不再由官方支持)。不幸的是,社区中没有足够的成员愿意做维护该体系结构的工作。不过请放心,Fedora 不会删除所有 32 位软件包,仍在构建许多 i686 软件包,以确保诸如 multilib、wine 和 Steam 之类的东西可以继续工作。

尽管该存储库不再构建和镜像输出,但存在一个 koji i686 存储库,该库可与 mock 一起使用以构建 32 位程序包,并且可以在紧要关头安装不属于 x86\_64 multilib 存储库的 32 位版本。当然,维护人员希望这样做解决有限的使用场景。只是需要运行一个 32 位应用程序的用户应该可以在 64 位系统上使用 multilib 来运行。

如果你要运行 32 位应用需要做什么?

如果你仍在运行 32 位 i686 系统,则会在 Fedora 30 生命周期中继续收到受支持的 Fedora 更新。直到大约 2020 年 5 月或 6 月。到那时,如果硬件支持,你可以将其重新安装为 64 位 x86\_64,或者如果可能的话,将其替换为支持 64 位的硬件。

社区中有一个用户已经成功地从 32 位 Fedora “升级” 到了 64 位 x86 Fedora。虽然这不是预期或受支持的升级路径,但应该也可行。该项目希望可以为具有 64 位功能的硬件的用户提供一些文档,以在 Fedora 30 使用寿命终止之前说明该升级过程。

如果有 64 位的 CPU,但由于内存不足而运行 32 位 Fedora,请尝试备用桌面流派之一。LXDE 和其他产品在内存受限的环境中往往表现良好。对于仅在旧的可以扔掉的 32 位硬件上运行简单服务器的用户,请考虑使用较新的 ARM 板之一。在许多情况下,仅节能一项就可以支付新硬件的费用。如果以上皆不可行,CentOS 7 提供了一个 32 位镜像,并对该平台提供长期支持。

安全与你

尽管有些用户可能会在生命周期结束后继续运行旧版本的 Fedora,但强烈建议不要这样做。人们不断研究软件的安全问题。通常,他们发现这些问题已经存在多年了。

一旦 Fedora 维护人员知道了此类问题,他们通常会为它们打补丁,并为支持的发行版提供更新,而不会给使用寿命已终止的发行版提供。当然,一旦这些漏洞公开,就会有人尝试利用它们。如果你在生命周期结束时运行了较旧的发行版,则安全风险会随着时间的推移而增加,从而使你的系统面临不断增长的风险。


via: https://fedoramagazine.org/in-fedora-31-32-bit-i686-is-86ed/

作者:Justin Forbes 选题:lujun9972 译者:wxy 校对:wxy

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