分类 技术 下的文章

虽然现在不是万圣节,也可以关注一下 Linux 可怕的一面。什么命令可能会显示鬼、巫婆和僵尸的图像?哪个会鼓励“不给糖果就捣蛋”的精神?

crypt

好吧,我们一直看到 crypt。尽管名称不同,crypt 不是一个地窖,也不是垃圾文件的埋葬坑,而是一个加密文件内容的命令。现在,crypt 通常用一个脚本实现,通过调用一个名为 mcrypt 的二进制文件来模拟以前的 crypt 命令来完成它的工作。直接使用 mycrypt 命令是更好的选择。

$ mcrypt x
Enter the passphrase (maximum of 512 characters)
Please use a combination of upper and lower case letters and numbers.
Enter passphrase:
Enter passphrase:

File x was encrypted.

请注意,mcrypt 命令会创建第二个扩展名为 .nc 的文件。它不会覆盖你正在加密的文件。

mcrypt 命令有密钥大小和加密算法的选项。你也可以再选项中指定密钥,但 mcrypt 命令不鼓励这样做。

kill

还有 kill 命令 - 当然并不是指谋杀,而是用来强制和非强制地结束进程,这取决于正确终止它们的要求。当然,Linux 并不止于此。相反,它有各种 kill 命令来终止进程。我们有 killpkillkillallkillpgrfkillskill()读作 es-kill)、tgkilltkillxkill

$ killall runme
[1] Terminated ./runme
[2] Terminated ./runme
[3]- Terminated ./runme
[4]+ Terminated ./runme

shred

Linux 系统也支持一个名为 shred 的命令。shred 命令会覆盖文件以隐藏其以前的内容,并确保使用硬盘恢复工具无法恢复它们。请记住,rm 命令基本上只是删除文件在目录文件中的引用,但不一定会从磁盘上删除内容或覆盖它。shred 命令覆盖文件的内容。

$ shred dupes.txt
$ more dupes.txt
▒oΛ▒▒9▒lm▒▒▒▒▒o▒1־▒▒f▒f▒▒▒i▒▒h^}&▒▒▒{▒▒

僵尸

虽然不是命令,但僵尸在 Linux 系统上是很顽固的存在。僵尸基本上是没有完全清理掉的死亡进程的遗骸。进程不应该这样工作 —— 让死亡进程四处游荡,而不是简单地让它们死亡并进入数字天堂,所以僵尸的存在表明了让他们遗留于此的进程有一些缺陷。

一个简单的方法来检查你的系统是否有僵尸进程遗留,看看 top 命令的标题行。

$ top
top - 18:50:38 up 6 days, 6:36, 2 users, load average: 0.00, 0.00, 0.00
Tasks: 171 total, 1 running, 167 sleeping, 0 stopped, 3 zombie  `< ==`
%Cpu(s): 0.0 us, 0.0 sy, 0.0 ni, 99.9 id, 0.1 wa, 0.0 hi, 0.0 si, 0.0 st
KiB Mem : 2003388 total, 250840 free, 545832 used, 1206716 buff/cache
KiB Swap: 9765884 total, 9765764 free, 120 used. 1156536 avail Mem

可怕!上面显示有三个僵尸进程。

at midnight

有时会在万圣节这么说,死者的灵魂从日落开始游荡直到午夜。Linux 可以通过 at midnight 命令跟踪它们的离开。用于安排在下次到达指定时间时运行的作业,at 的作用类似于一次性的 cron。

$ at midnight
warning: commands will be executed using /bin/sh
at> echo 'the spirits of the dead have left'
at> <EOT>
job 3 at Thu Oct 31 00:00:00 2017

守护进程

Linux 系统也高度依赖守护进程 —— 在后台运行的进程,并提供系统的许多功能。许多守护进程的名称以 “d” 结尾。这个 “d” 代表 守护进程 daemon ,表明这个进程一直运行并支持一些重要功能。有的会用单词 “daemon” 。

$ ps -ef | grep sshd
root 1142 1 0 Oct19 ? 00:00:00 /usr/sbin/sshd -D
root 25342 1142 0 18:34 ? 00:00:00 sshd: shs [priv]
$ ps -ef | grep daemon | grep -v grep
message+ 790 1 0 Oct19 ? 00:00:01 /usr/bin/dbus-daemon --system --address=systemd: --nofork --nopidfile --systemd-activation
root 836 1 0 Oct19 ? 00:00:02 /usr/lib/accountsservice/accounts-daemon

万圣节快乐!

FacebookLinkedIn 上加入 Network World 社区来对主题进行评论。


via: https://www.networkworld.com/article/3235219/linux/scary-linux-commands-for-halloween.html

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

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

在这个 Docker 系列的最后一篇文章中,我们将讲述在 DockerHub 上使用和发布镜像。

在前面的文章中,我们了解到了基本的 Docker 术语,在 Linux 桌面、MacOS 和 Windows上 如何安装 Docker如何创建容器镜像 并且在系统上运行它们。在本系列的最后一篇文章中,我们将讨论如何使用 DockerHub 中的镜像以及将自己的镜像发布到 DockerHub。

首先:什么是 DockerHub 以及为什么它很重要?DockerHub 是一个由 Docker 公司运行和管理的基于云的存储库。它是一个在线存储库,Docker 镜像可以由其他用户发布和使用。有两种库:公共存储库和私有存储库。如果你是一家公司,你可以在你自己的组织内拥有一个私有存储库,而公共镜像可以被任何人使用。

你也可以使用公开发布的官方 Docker 镜像。我使用了很多这样的镜像,包括我的试验 WordPress 环境、KDE plasma 应用程序等等。虽然我们上次学习了如何创建自己的 Docker 镜像,但你不必这样做。DockerHub 上发布了数千镜像供你使用。DockerHub 作为默认存储库硬编码到 Docker 中,所以当你对任何镜像运行 docker pull 命令时,它将从 DockerHub 下载。

从 Docker Hub 下载镜像并在本地运行

开始请查看本系列的前几篇文章,以便继续。然后,一旦 Docker 在你的系统上运行,你就可以打开终端并运行:

$ docker images

该命令将显示当前系统上所有的 docker 镜像。假设你想在本地机器上部署 Ubuntu,你可能会:

$ docker pull ubuntu

如果你的系统上已经存在 Ubuntu 镜像,那么该命令会自动将该系统更新到最新版本。因此,如果你想要更新现有的镜像,只需运行 docker pull 命令,易如反掌。这就像 apt-get update 一样,没有任何的混乱和麻烦。

你已经知道了如何运行镜像:

$ docker run -it <image name>
$ docker run -it ubuntu

命令提示符应该变为如下内容:

root@1b3ec4621737:/#

现在你可以运行任何属于 Ubuntu 的命令和实用程序,这些都被包含在内而且安全。你可以在 Ubuntu 上运行你想要的所有实验和测试。一旦你完成了测试,你就可以销毁镜像并下载一个新的。在虚拟机中不存在系统开销。

你可以通过运行 exit 命令退出该容器:

$ exit

现在假设你想在系统上安装 Nginx,运行 search 命令来找到需要的镜像:

$ docker search nginx

正如你所看到的,DockerHub 上有很多 Nginx 镜像。为什么?因为任何人都可以发布镜像,各种镜像针对不同的项目进行了优化,因此你可以选择合适的镜像。你只需要为你的需求安装合适的镜像。

假设你想要拉取 Bitnami 的 Nginx 镜像:

$ docker pull bitnami/nginx

现在运行:

$ docker run -it bitnami/nginx

如何发布镜像到 Docker Hub?

在此之前,我们学习了如何创建 Docker 镜像,我们可以轻松地将该镜像发布到 DockerHub 中。首先,你需要登录 DockerHub,如果没有账户,请 创建账户。然后,你可以打开终端应用,登录:

$ docker login --username=<USERNAME>

将 “” 替换为你自己的 Docker Hub 用户名。我这里是 arnieswap:

$ docker login --username=arnieswap

输入密码,你就登录了。现在运行 docker images 命令来获取你上次创建的镜像的 ID。

$ docker images

现在,假设你希望将镜像 ng 推送到 DockerHub,首先,我们需要标记该镜像(了解更多关于标记的信息):

$ docker tag e7083fd898c7 arnieswap/my_repo:testing

现在推送镜像:

$ docker push arnieswap/my_repo

推送指向的是 docker.io/arnieswap/my\_repo 仓库:

12628b20827e: Pushed
8600ee70176b: Mounted from library/ubuntu
2bbb3cec611d: Mounted from library/ubuntu
d2bb1fc88136: Mounted from library/ubuntu
a6a01ad8b53f: Mounted from library/ubuntu
833649a3e04c: Mounted from library/ubuntu
testing: digest: sha256:286cb866f34a2aa85c9fd810ac2cedd87699c02731db1b8ca1cfad16ef17c146 size: 1569

哦耶!你的镜像正在上传。一旦完成,打开 DockerHub,登录到你的账户,你就能看到你的第一个 Docker 镜像。现在任何人都可以部署你的镜像。这是开发软件和发布软件最简单,最快速的方式。无论你何时更新镜像,用户都可以简单地运行:

$ docker run arnieswap/my_repo

现在你知道为什么人们喜欢 Docker 容器了。它解决了传统工作负载所面临的许多问题,并允许你在任何时候开发、测试和部署应用程序。通过遵循本系列中的步骤,你自己可以尝试以下。


via: https://www.linux.com/blog/learn/intro-to-linux/2018/1/how-use-dockerhub

作者:Swapnil Bhartiya 译者:MjSeven 校对:wxy

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

借助 GitHub 的 网络钩子 webhook ,开发者可以创建很多有用的服务。从触发一个 Jenkins 实例上的 CI(持续集成) 任务到配置云中的机器,几乎有着无限的可能性。这篇教程将展示如何使用 Python 和 Flask 框架来搭建一个简单的持续部署(CD)服务。

在这个例子中的持续部署服务是一个简单的 Flask 应用,其带有接受 GitHub 的 网络钩子 webhook 请求的 REST 端点 endpoint 。在验证每个请求都来自正确的 GitHub 仓库后,服务器将 拉取 pull 更改到仓库的本地副本。这样每次一个新的 提交 commit 推送到远程 GitHub 仓库,本地仓库就会自动更新。

Flask web 服务

用 Flask 搭建一个小的 web 服务非常简单。这里可以先看看项目的结构。

├── app
│   ├── __init__.py
│   └── webhooks.py
├── requirements.txt
└── wsgi.py

首先,创建应用。应用代码在 app 目录下。

两个文件(__init__.pywebhooks.py)构成了 Flask 应用。前者包含有创建 Flask 应用并为其添加配置的代码。后者有 端点 endpoint 逻辑。这是该应用接收 GitHub 请求数据的地方。

这里是 app/__init__.py 的内容:

import os
from flask import Flask

from .webhooks import webhook

def create_app():
 """ Create, configure and return the Flask application """

  app = Flask(__name__)
  app.config['GITHUB_SECRET'] = os.environ.get('GITHUB_SECRET')
  app.config['REPO_PATH'] = os.environ.get('REPO_PATH')
  app.register_blueprint(webhook)

  return(app)

该函数创建了两个配置变量:

  • GITHUB_SECRET 保存一个密码,用来认证 GitHub 请求。
  • REPO_PATH 保存了自动更新的仓库路径。

这份代码使用 Flask 蓝图 Flask Blueprints 来组织应用的 端点 endpoint 。使用蓝图可以对 API 进行逻辑分组,使应用程序更易于维护。通常认为这是一种好的做法。

这里是 app/webhooks.py 的内容:

import hmac
from flask import request, Blueprint, jsonify, current_app 
from git import Repo

webhook = Blueprint('webhook', __name__, url_prefix='')

@webhook.route('/github', methods=['POST']) 
def handle_github_hook(): 
 """ Entry point for github webhook """

  signature = request.headers.get('X-Hub-Signature') 
  sha, signature = signature.split('=')

  secret = str.encode(current_app.config.get('GITHUB_SECRET'))

  hashhex = hmac.new(secret, request.data, digestmod='sha1').hexdigest()
  if hmac.compare_digest(hashhex, signature): 
    repo = Repo(current_app.config.get('REPO_PATH')) 
    origin = repo.remotes.origin 
    origin.pull('--rebase')

    commit = request.json['after'][0:6]
    print('Repository updated with commit {}'.format(commit))
  return jsonify({}), 200

首先代码创建了一个新的蓝图 webhook。然后它使用 Flask route 为蓝图添加了一个端点。任何请求 /GitHub URL 端点的 POST 请求都将调用这个路由。

验证请求

当服务在该端点上接到请求时,首先它必须验证该请求是否来自 GitHub 以及来自正确的仓库。GitHub 在请求头的 X-Hub-Signature 中提供了一个签名。该签名由一个密码(GITHUB_SECRET),请求体的 HMAC 十六进制摘要,并使用 sha1 哈希生成。

为了验证请求,服务需要在本地计算签名并与请求头中收到的签名做比较。这可以由 hmac.compare_digest 函数完成。

自定义钩子逻辑

在验证请求后,现在就可以处理了。这篇教程使用 GitPython 模块来与 git 仓库进行交互。GitPython 模块中的 Repo 对象用于访问远程仓库 origin。该服务在本地拉取 origin 仓库的最新更改,还用 --rebase 选项来避免合并的问题。

调试打印语句显示了从请求体收到的短提交哈希。这个例子展示了如何使用请求体。更多关于请求体的可用数据的信息,请查询 GitHub 文档

最后该服务返回了一个空的 JSON 字符串和 200 的状态码。这用于告诉 GitHub 的网络钩子服务已经收到了请求。

部署服务

为了运行该服务,这个例子使用 gunicorn web 服务器。首先安装服务依赖。在支持的 Fedora 服务器上,以 sudo 运行这条命令:

sudo dnf install python3-gunicorn python3-flask python3-GitPython

现在编辑 gunicorn 使用的 wsgi.py 文件来运行该服务:

from app import create_app
application = create_app()

为了部署服务,使用以下命令克隆这个 git 仓库或者使用你自己的 git 仓库:

git clone https://github.com/cverna/github_hook_deployment.git /opt/

下一步是配置服务所需的环境变量。运行这些命令:

export GITHUB_SECRET=asecretpassphraseusebygithubwebhook
export REPO_PATH=/opt/github_hook_deployment/

这篇教程使用网络钩子服务的 GitHub 仓库,但你可以使用你想要的不同仓库。最后,使用这些命令开启该 web 服务:

cd /opt/github_hook_deployment/
gunicorn --bind 0.0.0.0 wsgi:application --reload

这些选项中绑定了 web 服务的 IP 地址为 0.0.0.0,意味着它将接收来自任何的主机的请求。选项 --reload 确保了当代码更改时重启 web 服务。这就是持续部署的魔力所在。每次接收到 GitHub 请求时将拉取仓库的最近更新,同时 gunicore 检测这些更改并且自动重启服务。

*注意: *为了能接收到 GitHub 请求,web 服务必须部署到具有公有 IP 地址的服务器上。做到这点的简单方法就是使用你最喜欢的云提供商比如 DigitalOcean,AWS,Linode等。

配置 GitHub

这篇教程的最后一部分是配置 GitHub 来发送网络钩子请求到 web 服务上。这是持续部署的关键。

从你的 GitHub 仓库的设置中,选择 Webhook 菜单,并且点击“Add Webhook”。输入以下信息:

  • “Payload URL”: 服务的 URL,比如 <http://public_ip_address:8000/github>
  • “Content type”: 选择 “application/json”
  • “Secret”: 前面定义的 GITHUB_SECRET 环境变量

然后点击“Add Webhook” 按钮。

现在每当该仓库发生推送事件时,GitHub 将向服务发送请求。

总结

这篇教程向你展示了如何写一个基于 Flask 的用于接收 GitHub 的网络钩子请求,并实现持续集成的 web 服务。现在你应该能以本教程作为起点来搭建对自己有用的服务。


via: https://fedoramagazine.org/continuous-deployment-github-python/

作者:Clément Verna 选题:lujun9972 译者:kimii 校对:wxy

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

我们已经讨论过 Linux 下 gzip 命令的用法。对于初学者来说,gzip 工具主要用于压缩或者展开文件。解压时,在 gzip 命令后添加 -d 选项即可,使用示例如下:

gzip -d [compressed-file-name]

不过,在解压或扩展 gzip 创建的压缩文件时,有另一款完全不同的工具可供使用。谈及的这款工具就是 gunzip。在本文中,我们会使用一些简单、易于理解的例子来解释 gunzip 命令的用法。文中所有示例及指南都在 Ubuntu 16.04 环境下测试。

Linux gunzip 命令

我们现在知道压缩文件可以用 gzip -dgunzip 命令解压。基本的 gunzip 语法为:

gunzip [compressed-file-name]

以下的 Q&A 例子将更清晰地展示 gunzip 工具如何工作:

Q1. 如何使用 gunzip 解压压缩文件?

解压命令非常简单,仅仅需要将压缩文件名称作为参数传递到 gunzip 命令后。

gunzip [archive-name]

比如:

gunzip file1.gz

如何使用 gunzip 解压压缩文件?

Q2. 如何让 gunzip 不删除原始压缩文件?

正如你已注意到的那样,gunzip 命令解压后会删除原始压缩文件。如果你想保留原始压缩文件,可以使用 -c 选项。

gunzip -c [archive-name] > [outputfile-name]

比如:

gunzip -c file1.gz > file1

如何让 gunzip 不删除原始压缩文件?

使用这种方式,原压缩文件不会被删除。

Q3. 如何用 gunzip 解压文件到其他路径?

在 Q&A 中我们已经讨论过 -c 选项的用法。 使用 gunzip 解压文件到工作目录外的其他路径,仅需要在重定向操作符后添加目标目录的绝对路径即可。

gunzip -c [compressed-file] > [/complete/path/to/dest/dir/filename]

示例如下:

gunzip -c file1.gz > /home/himanshu/file1

更多信息

以下从 gzip/gunzip 的 man 页中摘录的细节,对于想了解更多的人会有所助益。

gunzip 在命令行接受一系列的文件,并且将每个文件内容以正确的魔法数开始,且后缀名为 .gz-gz.z-z_z (忽略大小写)的压缩文件,用未压缩的文件替换它,并删除其原扩展名。 gunzip 也可识别一些特殊扩展名的压缩文件,如 .tgz.taz 分别是 .tar.gz.tar.Z 的缩写。在压缩时,gzip 在必要情况下使用 .tgz 作为扩展名,而不是只截取掉 .tar 后缀。

gunzip 目前可以解压 gzipzipcompresscompress -Hpack)产生的文件。gunzip 自动检测输入文件格式。在使用前两种压缩格式时,gunzip 会检验 32 位循环冗余校验码(CRC)。对于 pack 包,gunzip 会检验压缩长度。标准压缩格式在设计上不允许相容性检测。不过 gunzip 有时可以检测出坏的 .Z 文件。如果你解压 .Z 文件时出错,不要因为标准解压没报错就认为 .Z 文件一定是正确的。这通常意味着标准解压过程不检测它的输入,而是直接产生一个错误的输出。SCO 的 compress -H 格式(lzh 压缩方法)不包括 CRC 校验码,但也允许一些相容性检查。

结语

到目前为止提到的 gunzip 基本用法,并不需要过多的学习曲线。我们已包含了一个初学者开始使用它所必须了解的几乎全部知识。想要了解更多的用法,去看 man 页面 吧。


via: https://www.howtoforge.com/linux-gunzip-command/

作者:Himanshu Arora 译者:erialin 校对:wxy

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

概述

Google 的 k8s 在 2017 年已经从容器编排领域的竞争中取得主导地位,从 Docker 之前的一度排挤到最终完全拥抱 k8s,显然 k8s 已经成了目前业界的标准。

但是到目前为止能提供 k8s 完全托管服务的云服务商少之又少,即便是目前在云提供商有统治力的 AWS 也没有完全提供 k8s 托管服务,仅仅提供有限的定制服务,在这一方面并不成熟。

然而 Google 的 k8s 托管服务,即 GKE,却将 k8s 托管服务做到了极致(至少目前看来),不仅提供了全套的 k8s 托管服务,更引人注目的是 Google 已然将 Autoscaler 和 k8s 集成,实现了 k8s 节点的自动伸缩机制,能根据 pod 的需求自动化添加或删除节点,当现有节点无法承载新的服务时会自动添加节点来满足需求,当现有节点足够空闲时会启用调节机制自动化收缩节点,从某种意义上来说这几乎做到了无服务器的理念。

然而这也许只是冰山一角,更多强大的功能还需要进一步探索,本文只是一个入门指南,主要指导能快速开始上手基于 Google Cloud Platform 的 GKE 服务(k8s 托管服务)。

GKE 入门指南

接下来我们一步步指引如何使用 GKE 来部署服务,前提是对 k8s 有所了解,能简单使用 kubectl 命令。

1. 安装并配置 Google Cloud SDK

Google Cloud SDK 是 访问 GCP(Google Cloud Platform)平台各种资源的命令行工具集,类似 aws 的 aws 命令行工具。

安装和配置就不多说了,点击下面链接选择相应操作系统版本的 tar 包下载,然后解压,在 PATH 环境变量中添加 google-cloud-sdk/bin 即可:

https://cloud.google.com/sdk/?hl=zh-cn

2. 初始化 Google Cloud SDK

初始化 Google Cloud SDK 是将 gcloud 命令和 Google 账号绑定起来并设置一些其他的默认值,比如区域,代理,账号,项目(Google 账号中新建的项目)之类的。

在执行 gcloud init 初始化之前得先给 gcloud 配置 HTTP 代理(GFW 你懂得),具体配置见我之前这篇文章。然后执行 gcloud init 完成初始化,直接根据向导来即可。

3. 到 Google Cloud Platform 控制台建一个 k8s 集群,记住名称

4. 安装 gcloud kubectl 组件

gcloud components install kubectl 

5. 获取群集的身份验证凭据

创建群集后,您需要获取身份验证凭据以与群集进行交互。要为集群进行身份验证,请运行以下命令:

gcloud container clusters get-credentials <上一步创建的集群名称> 

6. 接下来部署一个简单的 hello-server 服务到 GKE

kubectl run hello-server --image gcr.io/google-samples/hello-app:1.0 --port 8080

相关链接

附录

gloud 常用命令

gcloud auth login --no-launch-browser # gcloud 登录认证
gcloud config set compute/zone [COMPUTE_ZONE] # 设置默认区域
gcloud components list # 列出可安装组件
gcloud components install [组件名称] # 安装组件
gcloud components update  # 更新所有已安装组件
gcloud components remove [组件名称] # 卸载已安装组件

设置 gcloud http 代理

gcloud config set proxy/type http
gcloud config set proxy/address 127.0.0.1
gcloud config set proxy/port 1087

设置集群 docker 私服认证

kubectl create secret docker-registry regcred --docker-server=<your-registry-server> --docker-username=<your-name> --docker-password=<your-pword> --docker-email=<your-email>

注意:设置 docker 私服后,要在 GKE 部署 k8s 服务,必须得在 k8s 资源文件(yaml 格式)中的 container
同一级指定 imagePullSecrets 键,要不然仍然无法拉取配置的私服的镜像,示例资源文件如下:

apiVersion: v1
kind: Pod
metadata:
  name: private-reg
spec:
  containers:
  - name: private-reg-container
    image: <your-private-image>
  imagePullSecrets:
  - name: regcred

查看集群 docker 私服配置

kubectl get secret regcred --output=yaml      #base64 格式 显示
kubectl get secret regcred --output="jsonpath={.data.\.dockerconfigjson}" | base64 -d # base64

Jim 给他的终端冒险游戏添加了颜色,演示了如何用 curses 操纵颜色。

在我的使用 ncurses 库进行编程的系列文章的第一篇第二篇中,我已经介绍了一些 curses 函数来在屏幕上作画、从屏幕上查询和从键盘读取字符。为了搞清楚这些函数,我使用 curses 来利用简单字符绘制游戏地图和玩家角色,创建了一个简单的冒险游戏。在这篇紧接着的文章里,我展示了如何为你的 curses 程序添加颜色。

在屏幕上绘图一切都挺好的,但是如果只有黑底白字的文本,你的程序可能看起来很无趣。颜色可以帮助传递更多的信息。举个例子,如果你的程序需要报告执行成功或者执行失败时。在这样的情况下你可以使用绿色或者红色来帮助强调输出。或者,你只是简单地想要“潮艺”一下给你的程序来让它看起来更美观。

在这篇文章中,我用一个简单的例子来展示通过 curses 函数进行颜色操作。在我先前的文章中,我写了一个可以让你在一个粗糙绘制的地图上移动玩家角色的初级冒险类游戏。但是那里面的地图完全是白色和黑色的文本,通过形状来表明是水()或者山(^)。所以,让我们将游戏更新到使用颜色的版本吧。

颜色要素

在你可以使用颜色之前,你的程序需要知道它是否可以依靠终端正确地显示颜色。在现代操作系统上,此处应该永远为true。但是在经典的计算机上,一些终端是单色的,例如古老的 VT52 和 VT100 终端,一般它们提供黑底白色或者黑底绿色的文本。

可以使用 has_colors() 函数查询终端的颜色功能。这个函数将会在终端可以显示颜色的时候返回 true,否则将会返回 false。这个函数一般用于 if 块的开头,就像这样:

if (has_colors() == FALSE) {
    endwin();
    printf("Your terminal does not support color\n");
    exit(1);
}

在知道终端可以显示颜色之后,你可以使用 start_color() 函数来设置 curses 使用颜色。现在是时候定义程序将要使用的颜色了。

在 curses 中,你应该按对定义颜色:一个前景色放在一个背景色上。这样允许 curses 一次性设置两个颜色属性,这也是一般你想要使用的方式。通过 init_pair() 函数可以定义一个前景色和背景色并关联到索引数字来设置颜色对。大致语法如下:

init_pair(index, foreground, background);

控制台支持八种基础的颜色:黑色、红色、绿色、黄色、蓝色、品红色、青色和白色。这些颜色通过下面的名称为你定义好了:

  • COLOR_BLACK
  • COLOR_RED
  • COLOR_GREEN
  • COLOR_YELLOW
  • COLOR_BLUE
  • COLOR_MAGENTA
  • COLOR_CYAN
  • COLOR_WHITE

应用颜色

在我的冒险游戏中,我想要让草地呈现绿色而玩家的足迹变成不易察觉的绿底黄色点迹。水应该是蓝色,那些表示波浪的 ~ 符号应该是近似青色的。我想让山(^)是灰色的,但是我可以用白底黑色文本做一个可用的折中方案。(LCTT 译注:意为终端预设的颜色没有灰色,使用白底黑色文本做一个折中方案)为了让玩家的角色更易见,我想要使用一个刺目的品红底红色设计。我可以像这样定义这些颜色对:

start_color();
init_pair(1, COLOR_YELLOW, COLOR_GREEN);
init_pair(2, COLOR_CYAN, COLOR_BLUE);
init_pair(3, COLOR_BLACK, COLOR_WHITE);
init_pair(4, COLOR_RED, COLOR_MAGENTA);

为了让颜色对更容易记忆,我的程序中定义了一些符号常量:

#define GRASS_PAIR     1
#define EMPTY_PAIR     1
#define WATER_PAIR     2
#define MOUNTAIN_PAIR  3
#define PLAYER_PAIR    4

有了这些常量,我的颜色定义就变成了:

start_color();
init_pair(GRASS_PAIR, COLOR_YELLOW, COLOR_GREEN);
init_pair(WATER_PAIR, COLOR_CYAN, COLOR_BLUE);
init_pair(MOUNTAIN_PAIR, COLOR_BLACK, COLOR_WHITE);
init_pair(PLAYER_PAIR, COLOR_RED, COLOR_MAGENTA);

在任何时候你想要使用颜色显示文本,你只需要告诉 curses 设置哪种颜色属性。为了更好的编程实践,你同样应该在你完成了颜色使用的时候告诉 curses 取消颜色组合。为了设置颜色,应该在调用像 mvaddch() 这样的函数之前使用attron(),然后通过 attroff() 关闭颜色属性。例如,在我绘制玩家角色的时候,我应该这样做:

attron(COLOR_PAIR(PLAYER_PAIR));
mvaddch(y, x, PLAYER);
attroff(COLOR_PAIR(PLAYER_PAIR));

记住将颜色应用到你的程序对你如何查询屏幕有一些微妙的影响。一般来讲,由 mvinch() 函数返回的值是没有带颜色属性的类型 chtype,这个值基本上是一个整型值,也可以当作整型值来用。但是,由于使用颜色添加了额外的属性到屏幕上的字符上,所以 chtype 按照扩展的位模式携带了额外的颜色信息。一旦你使用 mvinch(),返回值将会包含这些额外的颜色值。为了只提取文本值,例如在 is_move_okay() 函数中,你需要和 A_CHARTEXT& 位运算:

int is_move_okay(int y, int x)
{
    int testch;

    /* return true if the space is okay to move into */

    testch = mvinch(y, x);
    return (((testch & A_CHARTEXT) == GRASS)
            || ((testch & A_CHARTEXT) == EMPTY));
}

通过这些修改,我可以用颜色更新这个冒险游戏:

/* quest.c */

#include <curses.h>
#include <stdlib.h>

#define GRASS     ' '
#define EMPTY     '.'
#define WATER     '~'
#define MOUNTAIN  '^'
#define PLAYER    '*'

#define GRASS_PAIR     1
#define EMPTY_PAIR     1
#define WATER_PAIR     2
#define MOUNTAIN_PAIR  3
#define PLAYER_PAIR    4

int is_move_okay(int y, int x);
void draw_map(void);

int main(void)
{
    int y, x;
    int ch;

    /* 初始化curses */

    initscr();
    keypad(stdscr, TRUE);
    cbreak();
    noecho();

    /* 初始化颜色 */

    if (has_colors() == FALSE) {
        endwin();
        printf("Your terminal does not support color\n");
        exit(1);
    }

    start_color();
    init_pair(GRASS_PAIR, COLOR_YELLOW, COLOR_GREEN);
    init_pair(WATER_PAIR, COLOR_CYAN, COLOR_BLUE);
    init_pair(MOUNTAIN_PAIR, COLOR_BLACK, COLOR_WHITE);
    init_pair(PLAYER_PAIR, COLOR_RED, COLOR_MAGENTA);

    clear();

    /* 初始化探索地图 */

    draw_map();

    /* 在左下角创建新角色 */

    y = LINES - 1;
    x = 0;

    do {

        /* 默认情况下,你获得了一个闪烁的光标--用来指明玩家 * */

        attron(COLOR_PAIR(PLAYER_PAIR));
        mvaddch(y, x, PLAYER);
        attroff(COLOR_PAIR(PLAYER_PAIR));
        move(y, x);
        refresh();

        ch = getch();

        /* 测试输入键值并获取方向 */

        switch (ch) {
        case KEY_UP:
        case 'w':
        case 'W':
            if ((y > 0) && is_move_okay(y - 1, x)) {
                attron(COLOR_PAIR(EMPTY_PAIR));
                mvaddch(y, x, EMPTY);
                attroff(COLOR_PAIR(EMPTY_PAIR));
                y = y - 1;
            }
            break;
        case KEY_DOWN:
        case 's':
        case 'S':
            if ((y < LINES - 1) && is_move_okay(y + 1, x)) {
                attron(COLOR_PAIR(EMPTY_PAIR));
                mvaddch(y, x, EMPTY);
                attroff(COLOR_PAIR(EMPTY_PAIR));
                y = y + 1;
            }
            break;
        case KEY_LEFT:
        case 'a':
        case 'A':
            if ((x > 0) && is_move_okay(y, x - 1)) {
                attron(COLOR_PAIR(EMPTY_PAIR));
                mvaddch(y, x, EMPTY);
                attroff(COLOR_PAIR(EMPTY_PAIR));
                x = x - 1;
            }
            break;
        case KEY_RIGHT:
        case 'd':
        case 'D':
            if ((x < COLS - 1) && is_move_okay(y, x + 1)) {
                attron(COLOR_PAIR(EMPTY_PAIR));
                mvaddch(y, x, EMPTY);
                attroff(COLOR_PAIR(EMPTY_PAIR));
                x = x + 1;
            }
            break;
        }
    }
    while ((ch != 'q') && (ch != 'Q'));

    endwin();

    exit(0);
}

int is_move_okay(int y, int x)
{
    int testch;

    /* 当空白处可以进入的时候返回true */

    testch = mvinch(y, x);
    return (((testch & A_CHARTEXT) == GRASS)
            || ((testch & A_CHARTEXT) == EMPTY));
}

void draw_map(void)
{
    int y, x;

    /* 绘制探索地图 */

    /* 背景 */

    attron(COLOR_PAIR(GRASS_PAIR));
    for (y = 0; y < LINES; y++) {
        mvhline(y, 0, GRASS, COLS);
    }
    attroff(COLOR_PAIR(GRASS_PAIR));

    /* 山峰和山路 */

    attron(COLOR_PAIR(MOUNTAIN_PAIR));
    for (x = COLS / 2; x < COLS * 3 / 4; x++) {
        mvvline(0, x, MOUNTAIN, LINES);
    }
    attroff(COLOR_PAIR(MOUNTAIN_PAIR));

    attron(COLOR_PAIR(GRASS_PAIR));
    mvhline(LINES / 4, 0, GRASS, COLS);
    attroff(COLOR_PAIR(GRASS_PAIR));

    /* 湖 */

    attron(COLOR_PAIR(WATER_PAIR));
    for (y = 1; y < LINES / 2; y++) {
        mvhline(y, 1, WATER, COLS / 3);
    }
    attroff(COLOR_PAIR(WATER_PAIR));
}

你可能不能认出所有为了在冒险游戏里面支持颜色需要的修改,除非你目光敏锐。diff 工具展示了所有为了支持颜色而添加的函数或者修改的代码:

$ diff quest-color/quest.c quest/quest.c
12,17d11
< #define GRASS_PAIR     1
< #define EMPTY_PAIR     1
< #define WATER_PAIR     2
< #define MOUNTAIN_PAIR  3
< #define PLAYER_PAIR    4
<
33,46d26
<     /* initialize colors */
<
<     if (has_colors() == FALSE) {
<    endwin();
<    printf("Your terminal does not support color\n");
<    exit(1);
<     }
<
<     start_color();
<     init_pair(GRASS_PAIR, COLOR_YELLOW, COLOR_GREEN);
<     init_pair(WATER_PAIR, COLOR_CYAN, COLOR_BLUE);
<     init_pair(MOUNTAIN_PAIR, COLOR_BLACK, COLOR_WHITE);
<     init_pair(PLAYER_PAIR, COLOR_RED, COLOR_MAGENTA);
<
61d40
<    attron(COLOR_PAIR(PLAYER_PAIR));
63d41
<    attroff(COLOR_PAIR(PLAYER_PAIR));
76d53
<            attron(COLOR_PAIR(EMPTY_PAIR));
78d54
<            attroff(COLOR_PAIR(EMPTY_PAIR));
86d61
<            attron(COLOR_PAIR(EMPTY_PAIR));
88d62
<            attroff(COLOR_PAIR(EMPTY_PAIR));
96d69
<            attron(COLOR_PAIR(EMPTY_PAIR));
98d70
<            attroff(COLOR_PAIR(EMPTY_PAIR));
106d77
<            attron(COLOR_PAIR(EMPTY_PAIR));
108d78
<            attroff(COLOR_PAIR(EMPTY_PAIR));
128,129c98
<     return (((testch & A_CHARTEXT) == GRASS)
<        || ((testch & A_CHARTEXT) == EMPTY));
---
>     return ((testch == GRASS) || (testch == EMPTY));
140d108
<     attron(COLOR_PAIR(GRASS_PAIR));
144d111
<     attroff(COLOR_PAIR(GRASS_PAIR));
148d114
<     attron(COLOR_PAIR(MOUNTAIN_PAIR));
152d117
<     attroff(COLOR_PAIR(MOUNTAIN_PAIR));
154d118
<     attron(COLOR_PAIR(GRASS_PAIR));
156d119
<     attroff(COLOR_PAIR(GRASS_PAIR));
160d122
<     attron(COLOR_PAIR(WATER_PAIR));
164d125
<     attroff(COLOR_PAIR(WATER_PAIR));

开始玩吧--现在有颜色了

程序现在有了更舒服的颜色设计了,更匹配原来的桌游地图,有绿色的地、蓝色的湖和壮观的灰色山峰。英雄穿着红色的制服十分夺目。

图 1. 一个简单的带湖和山的桌游地图

图 2. 玩家站在左下角

图 3. 玩家可以在游戏区域移动,比如围绕湖,通过山的通道到达未知的区域。

通过颜色,你可以更清楚地展示信息。这个例子使用颜色指出可游戏的区域(绿色)相对着不可通过的区域(蓝色或者灰色)。我希望你可以使用这个示例游戏作为你自己的程序的一个起点或者参照。这取决于你需要你的程序做什么,你可以通过 curses 做得更多。

在下一篇文章,我计划展示 ncurses 库的其它特性,比如怎样创建窗口和边框。同时,如果你对于学习 curses 有兴趣,我建议你去读位于 Linux 文档计划 的 Pradeep Padala 写的 NCURSES Programming HOWTO


via: http://www.linuxjournal.com/content/programming-color-ncurses

作者:Jim Hall 译者:leemeans 校对:wxy

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