Jim Hall 发布的文章

理解 I/O 有助于提升你的效率。

 title=

如果你打算学习 C 语言的输入、输出,可以从 stdio.h 包含文件开始。正如你从其名字中猜到的,该文件定义了所有的标准(“std”)的输入和输出(“io”)函数。

大多数人学习的第一个 stdio.h 的函数是打印格式化输出的 printf 函数。或者是用来打印一个字符串的 puts 函数。这些函数非常有用,可以将信息打印给用户,但是如果你想做更多的事情,则需要了解其他函数。

你可以通过编写一个常见 Linux 命令的副本来了解其中一些功能和方法。cp 命令主要用于复制文件。如果你查看 cp 的帮助手册,可以看到 cp 命令支持非常多的参数和选项。但最简单的功能,就是复制文件:

cp infile outfile

你只需使用一些读写文件的基本函数,就可以用 C 语言来自己实现 cp 命令。

一次读写一个字符

你可以使用 fgetcfputc 函数轻松地进行输入输出。这些函数一次只读写一个字符。该用法被定义在 stdio.h,并且这也很浅显易懂:fgetc 是从文件中读取一个字符,fputc 是将一个字符保存到文件中。

int fgetc(FILE *stream);
int fputc(int c, FILE *stream);

编写 cp 命令需要访问文件。在 C 语言中,你使用 fopen 函数打开一个文件,该函数需要两个参数:文件名和打开文件的模式。模式通常是从文件读取(r)或向文件写入(w)。打开文件的方式也有其他选项,但是对于本教程而言,仅关注于读写操作。

因此,将一个文件复制到另一个文件就变成了打开源文件和目标文件,接着,不断从第一个文件读取字符,然后将该字符写入第二个文件。fgetc 函数返回从输入文件中读取的单个字符,或者当文件完成后返回文件结束标记(EOF)。一旦读取到 EOF,你就完成了复制操作,就可以关闭两个文件。该代码如下所示:

  do {
    ch = fgetc(infile);
    if (ch != EOF) {
      fputc(ch, outfile);
    }
  } while (ch != EOF);

你可以使用此循环编写自己的 cp 程序,以使用 fgetcfputc 函数一次读写一个字符。cp.c 源代码如下所示:

#include <stdio.h>

int
main(int argc, char **argv)
{
  FILE *infile;
  FILE *outfile;
  int ch;

  /* parse the command line */

  /* usage: cp infile outfile */

  if (argc != 3) {
    fprintf(stderr, "Incorrect usage\n");
    fprintf(stderr, "Usage: cp infile outfile\n");
    return 1;
  }

  /* open the input file */

  infile = fopen(argv[1], "r");
  if (infile == NULL) {
    fprintf(stderr, "Cannot open file for reading: %s\n", argv[1]);
    return 2;
  }

  /* open the output file */

  outfile = fopen(argv[2], "w");
  if (outfile == NULL) {
    fprintf(stderr, "Cannot open file for writing: %s\n", argv[2]);
    fclose(infile);
    return 3;
  }

  /* copy one file to the other */

  /* use fgetc and fputc */

  do {
    ch = fgetc(infile);
    if (ch != EOF) {
      fputc(ch, outfile);
    }
  } while (ch != EOF);

  /* done */

  fclose(infile);
  fclose(outfile);

  return 0;
}

你可以使用 gcc 来将 cp.c 文件编译成一个可执行文件:

$ gcc -Wall -o cp cp.c

-o cp 选项告诉编译器将编译后的程序保存到 cp 文件中。-Wall 选项告诉编译器提示所有可能的警告,如果你没有看到任何警告,则表示一切正常。

读写数据块

通过每次读写一个字符来实现自己的 cp 命令可以完成这项工作,但这并不是很快。在复制“日常”文件(例如文档和文本文件)时,你可能不会注意到,但是在复制大型文件或通过网络复制文件时,你才会注意到差异。每次处理一个字符需要大量的开销。

实现此 cp 命令的一种更好的方法是,读取一块的输入数据到内存中(称为缓存),然后将该数据集合写入到第二个文件。这样做的速度要快得多,因为程序可以一次读取更多的数据,这就就减少了从文件中“读取”的次数。

你可以使用 fread 函数将文件读入一个变量中。这个函数有几个参数:将数据读入的数组或内存缓冲区的指针(ptr),要读取的最小对象的大小(size),要读取对象的个数(nmemb),以及要读取的文件(stream):

size_t fread(void *ptr, size_t size, size_t nmemb, FILE *stream);

不同的选项为更高级的文件输入和输出(例如,读取和写入具有特定数据结构的文件)提供了很大的灵活性。但是,在从一个文件读取数据并将数据写入另一个文件的简单情况下,可以使用一个由字符数组组成的缓冲区。

你可以使用 fwrite 函数将缓冲区中的数据写入到另一个文件。这使用了与 fread 函数有相似的一组选项:要从中读取数据的数组或内存缓冲区的指针,要读取的最小对象的大小,要读取对象的个数以及要写入的文件。

size_t fwrite(const void *ptr, size_t size, size_t nmemb, FILE *stream);

如果程序将文件读入缓冲区,然后将该缓冲区写入另一个文件,则数组(ptr)可以是固定大小的数组。例如,你可以使用长度为 200 个字符的字符数组作为缓冲区。

在该假设下,你需要更改 cp 程序中的循环,以将数据从文件读取到缓冲区中,然后将该缓冲区写入另一个文件中:

  while (!feof(infile)) {
    buffer_length = fread(buffer, sizeof(char), 200, infile);
    fwrite(buffer, sizeof(char), buffer_length, outfile);
  }

这是更新后的 cp 程序的完整源代码,该程序现在使用缓冲区读取和写入数据:

#include <stdio.h>

int
main(int argc, char **argv)
{
  FILE *infile;
  FILE *outfile;
  char buffer[200];
  size_t buffer_length;

  /* parse the command line */

  /* usage: cp infile outfile */

  if (argc != 3) {
    fprintf(stderr, "Incorrect usage\n");
    fprintf(stderr, "Usage: cp infile outfile\n");
    return 1;
  }

  /* open the input file */

  infile = fopen(argv[1], "r");
  if (infile == NULL) {
    fprintf(stderr, "Cannot open file for reading: %s\n", argv[1]);
    return 2;
  }

  /* open the output file */

  outfile = fopen(argv[2], "w");
  if (outfile == NULL) {
    fprintf(stderr, "Cannot open file for writing: %s\n", argv[2]);
    fclose(infile);
    return 3;
  }

  /* copy one file to the other */

  /* use fread and fwrite */

  while (!feof(infile)) {
    buffer_length = fread(buffer, sizeof(char), 200, infile);
    fwrite(buffer, sizeof(char), buffer_length, outfile);
  }

  /* done */

  fclose(infile);
  fclose(outfile);

  return 0;
}

由于你想将此程序与其他程序进行比较,因此请将此源代码另存为 cp2.c。你可以使用 gcc 编译程序:

$ gcc -Wall -o cp2 cp2.c

和之前一样,-o cp2 选项告诉编译器将编译后的程序保存到 cp2 程序文件中。-Wall 选项告诉编译器打开所有警告。如果你没有看到任何警告,则表示一切正常。

是的,这真的更快了

使用缓冲区读取和写入数据是实现此版本 cp 程序更好的方法。由于它可以一次将文件的多个数据读取到内存中,因此该程序不需要频繁读取数据。在小文件中,你可能没有注意到使用这两种方案的区别,但是如果你需要复制大文件,或者在较慢的介质(例如通过网络连接)上复制数据时,会发现明显的差距。

我使用 Linux time 命令进行了比较。此命令可以运行另一个程序,然后告诉你该程序花费了多长时间。对于我的测试,我希望了解所花费时间的差距,因此我复制了系统上的 628 MB CD-ROM 镜像文件。

我首先使用标准的 Linux 的 cp 命令复制了映像文件,以查看所需多长时间。一开始通过运行 Linux 的 cp 命令,同时我还避免使用 Linux 内置的文件缓存系统,使其不会给程序带来误导性能提升的可能性。使用 Linux cp 进行的测试,总计花费不到一秒钟的时间:

$ time cp FD13LIVE.iso tmpfile

real    0m0.040s
user    0m0.001s
sys     0m0.003s

运行我自己实现的 cp 命令版本,复制同一文件要花费更长的时间。每次读写一个字符则花了将近五秒钟来复制文件:

$ time ./cp FD13LIVE.iso tmpfile

real    0m4.823s
user    0m4.100s
sys     0m0.571s

从输入读取数据到缓冲区,然后将该缓冲区写入输出文件则要快得多。使用此方法复制文件花不到一秒钟:

$ time ./cp2 FD13LIVE.iso tmpfile

real    0m0.944s
user    0m0.224s
sys     0m0.608s

我演示的 cp 程序使用了 200 个字符大小的缓冲区。我确信如果一次将更多文件数据读入内存,该程序将运行得更快。但是,通过这种比较,即使只有 200 个字符的缓冲区,你也已经看到了性能上的巨大差异。


via: https://opensource.com/article/21/3/file-io-c

作者:Jim Hall 选题:lujun9972 译者:wyxplus 校对:wxy

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

了解如何使用 ps、kill 和 killall 命令来终止进程并回收系统资源。

 title=

在 Linux 中,每个程序和 守护程序 daemon 都是一个“ 进程 process ”。 大多数进程代表一个正在运行的程序。而另外一些程序可以派生出其他进程,比如说它会侦听某些事件的发生,然后对其做出响应。并且每个进程都需要一定的内存和处理能力。你运行的进程越多,所需的内存和 CPU 使用周期就越多。在老式电脑(例如我使用了 7 年的笔记本电脑)或轻量级计算机(例如树莓派)上,如果你关注过后台运行的进程,就能充分利用你的系统。

你可以使用 ps 命令来查看正在运行的进程。你通常会使用 ps 命令的参数来显示出更多的输出信息。我喜欢使用 -e 参数来查看每个正在运行的进程,以及 -f 参数来获得每个进程的全部细节。以下是一些例子:

$ ps
    PID TTY          TIME CMD
  88000 pts/0    00:00:00 bash
  88052 pts/0    00:00:00 ps
  88053 pts/0    00:00:00 head
$ ps -e | head
    PID TTY          TIME CMD
      1 ?        00:00:50 systemd
      2 ?        00:00:00 kthreadd
      3 ?        00:00:00 rcu_gp
      4 ?        00:00:00 rcu_par_gp
      6 ?        00:00:02 kworker/0:0H-events_highpri
      9 ?        00:00:00 mm_percpu_wq
     10 ?        00:00:01 ksoftirqd/0
     11 ?        00:00:12 rcu_sched
     12 ?        00:00:00 migration/0
$ ps -ef | head
UID          PID    PPID  C STIME TTY          TIME CMD
root           1       0  0 13:51 ?        00:00:50 /usr/lib/systemd/systemd --switched-root --system --deserialize 36
root           2       0  0 13:51 ?        00:00:00 [kthreadd]
root           3       2  0 13:51 ?        00:00:00 [rcu_gp]
root           4       2  0 13:51 ?        00:00:00 [rcu_par_gp]
root           6       2  0 13:51 ?        00:00:02 [kworker/0:0H-kblockd]
root           9       2  0 13:51 ?        00:00:00 [mm_percpu_wq]
root          10       2  0 13:51 ?        00:00:01 [ksoftirqd/0]
root          11       2  0 13:51 ?        00:00:12 [rcu_sched]
root          12       2  0 13:51 ?        00:00:00 [migration/0]

最后的例子显示最多的细节。在每一行,UID(用户 ID)显示了该进程的所有者。PID(进程 ID)代表每个进程的数字 ID,而 PPID(父进程 ID)表示其父进程的数字 ID。在任何 Unix 系统中,进程是从 1 开始编号,是内核启动后运行的第一个进程。在这里,systemd 是第一个进程,它催生了 kthreadd,而 kthreadd 还创建了其他进程,包括 rcu_gprcu_par_gp 等一系列进程。

使用 kill 命令来管理进程

系统会处理大多数后台进程,所以你不需要操心这些进程。你只需要关注那些你所运行的应用创建的进程。虽然许多应用一次只运行一个进程(如音乐播放器、终端模拟器或游戏等),但其他应用则可能创建后台进程。其中一些应用可能当你退出后还在后台运行,以便下次你使用的时候能快速启动。

当我运行 Chromium(作为谷歌 Chrome 浏览器所基于的开源项目)时,进程管理便成了问题。 Chromium 在我的笔记本电脑上运行非常吃力,并产生了许多额外的进程。现在我仅打开五个选项卡,就能看到这些 Chromium 进程:

$ ps -ef | fgrep chromium
jhall      66221   [...]  /usr/lib64/chromium-browser/chromium-browser [...]
jhall      66230   [...]  /usr/lib64/chromium-browser/chromium-browser [...]
[...]
jhall      66861   [...]  /usr/lib64/chromium-browser/chromium-browser [...]
jhall      67329   65132  0 15:45 pts/0    00:00:00 grep -F chromium

我已经省略一些行,其中有 20 个 Chromium 进程和一个正在搜索 “chromium" 字符的 grep 进程。

$ ps -ef | fgrep chromium | wc -l
21

但是在我退出 Chromium 之后,这些进程仍旧运行。如何关闭它们并回收这些进程占用的内存和 CPU 呢?

kill 命令能让你终止一个进程。在最简单的情况下,你告诉 kill 命令终止你想终止的进程的 PID。例如,要终止这些进程,我需要对 20 个 Chromium 进程 ID 都执行 kill 命令。一种方法是使用命令行获取 Chromium 的 PID,而另一种方法针对该列表运行 kill

$ ps -ef | fgrep /usr/lib64/chromium-browser/chromium-browser | awk '{print $2}'
66221
66230
66239
66257
66262
66283
66284
66285
66324
66337
66360
66370
66386
66402
66503
66539
66595
66734
66848
66861
69702

$ ps -ef | fgrep /usr/lib64/chromium-browser/chromium-browser | awk '{print $2}' > /tmp/pids
$ kill $(cat /tmp/pids)

最后两行是关键。第一个命令行为 Chromium 浏览器生成一个进程 ID 列表。第二个命令行针对该进程 ID 列表运行 kill 命令。

介绍 killall 命令

一次终止多个进程有个更简单方法,使用 killall 命令。你或许可以根据名称猜测出,killall 会终止所有与该名字匹配的进程。这意味着我们可以使用此命令来停止所有流氓 Chromium 进程。这很简单:

$ killall /usr/lib64/chromium-browser/chromium-browser

但是要小心使用 killall。该命令能够终止与你所给出名称相匹配的所有进程。这就是为什么我喜欢先使用 ps -ef 命令来检查我正在运行的进程,然后针对要停止的命令的准确路径运行 killall

你也可以使用 -i--interactive 参数,来让 killkill 在停止每个进程之前提示你。

killall 还支持使用 -o--older-than 参数来查找比特定时间更早的进程。例如,如果你发现了一组已经运行了好几天的恶意进程,这将会很有帮助。又或是,你可以查找比特定时间更晚的进程,例如你最近启动的失控进程。使用 -y--young-than 参数来查找这些进程。

其他管理进程的方式

进程管理是系统维护重要的一部分。在我作为 Unix 和 Linux 系统管理员的早期职业生涯中,杀死非法作业的能力是保持系统正常运行的关键。在如今,你可能不需要亲手在 Linux 上的终止流氓进程,但是知道 killkillall 能够在最终出现问题时为你提供帮助。

你也能寻找其他方式来管理进程。在我这个案例中,我并不需要在我退出浏览器后,使用 killkillall 来终止后台 Chromium 进程。在 Chromium 中有个简单设置就可以进行控制:

 title=

不过,始终关注系统上正在运行哪些进程,并且在需要的时候进行干预是一个明智之举。


via: https://opensource.com/article/20/1/linux-kill-killall

作者:Jim Hall 选题:lujun9972 译者:wyxplus 校对:wxy

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

Bash 是大多数 Linux 系统上的默认命令行 shell。所以你为什么不试着学习如何最大限度地利用它呢?

 title=

Bash 是大多数 Linux 系统上的默认命令行 shell。所以你为什么不试着学习如何最大限度地利用它呢?今年,我们推荐了许多很棒的文章来帮助你充分利用 Bash shell 的强大功能。以下是一些关于 Bash 阅读次数最多的文章:

《通过重定向在 Linux 终端任意读写数据》

输入和输出重定向是任何编程或脚本语言的基础功能。从技术上讲,只要你与电脑互动,它就会自然而然地发生。输入从 stdin(标准输入,通常是你的键盘或鼠标)读取,输出到 stdout(标准输出,一般是文本或数据流),而错误被发送到 stderr(标准错误,一般和标准输出是一个位置)。了解这些数据流的存在,使你能够在使用 Bash 等 shell 时控制信息的去向。Seth Kenlon 分享了这些很棒的技巧,可以让你在不需要大量鼠标移动和按键的情况下从一个地方获取数据。你可能不经常使用重定向,但学习使用它可以为你节省大量不必要的打开文件和复制粘贴数据的时间。

《系统管理员 Bash 脚本入门》

Bash 是自由开源软件,所以任何人都可以安装它,不管他们运行的是 Linux、BSD、OpenIndiana、Windows 还是 macOS。Seth Kenlon 帮助你学习如何使用 Bash 的命令和特性,使其成为最强大的 shell 之一。

《针对大型文件系统可以试试此 Bash 脚本》

你是否曾经想列出一个目录中的所有文件,只显示其中的文件,不包括其他内容?或者只显示目录?如果你有,那么 Nick Clifton 的文章可能正是你正在寻找的。Nick 分享了一个漂亮的 Bash 脚本,它可以列出目录、文件、链接或可执行文件。该脚本使用 find 命令进行搜索,然后运行 ls 显示详细信息。对于管理大型 Linux 系统的人来说,这是一个漂亮的解决方案。

《用 Bash 工具对你的 Linux 系统配置进行快照》

你可能想与他人分享你的 Linux 配置,原因有很多。你可能需要帮助排除系统上的一个问题,或者你对自己创建的环境非常自豪,想向其他开源爱好者展示它。Don Watkins 向我们展示了 screenFetch 和 Neofetch 来捕获和分享你的系统配置。

《6 个方便的 Git 脚本》

Git 已经成为一个无处不在的代码管理系统。了解如何管理 Git 存储库可以简化你的开发体验。Bob Peterson 分享了 6 个 Bash 脚本,它们将使你在使用 Git 存储库时更加轻松。gitlog 打印当前补丁的简略列表,并与主版本相对照。这个脚本的不同版本可以显示补丁的 SHA1 id 或在一组补丁中搜索字符串。

《改进你 Bash 脚本的 5 种方法》

系统管理员通常编写各种或长或短的 Bash 脚本,以完成各种任务。Alan Formy-Duval 解释了如何使 Bash 脚本更简单、更健壮、更易于阅读和调试。我们可能会考虑到我们需要使用诸如 Python、C 或 Java 之类的语言来实现更高的功能,但其实也不一定需要。因为 Bash 脚本语言就已经非常强大。要最大限度地发挥它的效用,还有很多东西要学。

《我珍藏的 Bash 秘籍》

Katie McLaughlin 帮助你提高你的工作效率,用别名和其他快捷方式解决你经常忘记的事情。当你整天与计算机打交道时,找到可重复的命令并标记它们以方便以后使用是非常美妙的。Katie 总结了一些有用的 Bash 特性和帮助命令,可以节省你的时间。

这些 Bash 小技巧将一个已经很强大的 shell 提升到一个全新的级别。也欢迎分享你自己的建议。


via: https://opensource.com/article/21/1/bash

作者:Jim Hall 选题:lujun9972 译者:Chao-zhi 校对:wxy

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

测算网站的文本和背景之间的对比度,以确保站点易于阅读。

如果希望人们发现你的网站实用,那么他们需要能够阅读它。为文本选择的颜色可能会影响网站的可读性。不幸的是,网页设计中的一种流行趋势是在打印输出文本时使用低对比度的颜色,就像在白色背景上的灰色文本。对于 Web 设计师来说,这也许看起来很酷,但对于许多阅读它的人来说确实很困难。

W3C 提供了《 Web 内容可访问性指南 Web Content Accessibility Guidelines 》,其中包括帮助 Web 设计人员选择易于区分文本和背景色的指导。z这就是所谓的“ 对比度 contrast ratio ”。 W3C 定义的对比度需要进行一些计算:给定两种颜色,首先计算每种颜色的相对亮度,然后计算对比度。对比度在 1 到 21 的范围内(通常写为 1:1 到 21:1)。对比度越高,文本在背景下的突出程度就越高。例如,白色背景上的黑色文本非常醒目,对比度为 21:1。对比度为 1:1 的白色背景上的白色文本不可读。

W3C 说,正文 的对比度至少应为 4.5:1,标题至少应为 3:1。但这似乎是最低限度的要求。W3C 还建议正文至少 7:1,标题至少 4.5:1。

计算对比度可能比较麻烦,因此最好将其自动化。我已经用这个方便的 Bash 脚本做到了这一点。通常,脚本执行以下操作:

  1. 获取文本颜色和背景颜色
  2. 计算相对亮度
  3. 计算对比度

获取颜色

你可能知道显示器上的每种颜色都可以用红色、绿色和蓝色(R、G 和 B)来表示。要计算颜色的相对亮度,脚本需要知道颜色的红、绿和蓝的各个分量。理想情况下,脚本会将这些信息读取为单独的 R、G 和 B 值。 Web 设计人员可能知道他们喜欢的颜色的特定 RGB 代码,但是大多数人不知道不同颜色的 RGB 值。作为一种替代的方法是,大多数人通过 “red” 或 “gold” 或 “maroon” 之类的名称来引用颜色。

幸运的是,GNOME 的 Zenity 工具有一个颜色选择器应用程序,可让你使用不同的方法选择颜色,然后用可预测的格式 rgb(R,G,B) 返回 RGB 值。使用 Zenity 可以轻松获得颜色值:

color=$( zenity --title 'Set text color' --color-selection --color='black' )

如果用户(意外地)单击 “Cancel(取消)” 按钮,脚本将假定一种颜色:

if [ $? -ne 0 ] ; then
        echo '** color canceled .. assume black'
        color='rgb(0,0,0)'
fi

脚本对背景颜色值也执行了类似的操作,将其设置为 $background

计算相对亮度

一旦你在 $color 中设置了前景色,并在 $background 中设置了背景色,下一步就是计算每种颜色的相对亮度。 W3C 提供了一个算法 用以计算颜色的相对亮度。

对于 sRGB 色彩空间,一种颜色的相对亮度定义为:

L = 0.2126 * R + 0.7152 * G + 0.0722 * B

R、G 和 B 定义为:

if R sRGB​ <= 0.03928 then R = R sRGB​/12.92

else R = ((R sRGB​+0.055)/1.055) 2.4

if G sRGB​ <= 0.03928 then G = G sRGB​/12.92

else G = ((G sRGB​+0.055)/1.055) 2.4

if B sRGB​ <= 0.03928 then B = B sRGB​/12.92

else B = ((B sRGB​+0.055)/1.055) 2.4

R sRGB​、G sRGB​ 和 B sRGB​ 定义为:

R sRGB​ = R 8bit​/255

G sRGB​ = G 8bit​/255

B sRGB​ = B 8bit​/255

由于 Zenity 以 rgb(R,G,B) 的格式返回颜色值,因此脚本可以轻松拉取分隔开的 R、B 和 G 的值以计算相对亮度。AWK 可以使用逗号作为字段分隔符(-F,),并使用 substr() 字符串函数从 rgb(R,G,B) 中提取所要的颜色值:

R=$( echo $color | awk -F, '{print substr($1,5)}' )
G=$( echo $color | awk -F, '{print $2}' )
B=$( echo $color | awk -F, '{n=length($3); print substr($3,1,n-1)}' )

有关使用 AWK 提取和显示数据的更多信息,查看 AWK 备忘表

最好使用 BC 计算器来计算最终的相对亮度。BC 支持计算中所需的简单 if-then-else,这使得这一过程变得简单。但是由于 BC 无法使用非整数指数直接计算乘幂,因此需要使用自然对数替代它做一些额外的数学运算:

echo "scale=4
rsrgb=$R/255
gsrgb=$G/255
bsrgb=$B/255
if ( rsrgb <= 0.03928 ) r = rsrgb/12.92 else r = e( 2.4 * l((rsrgb+0.055)/1.055) )
if ( gsrgb <= 0.03928 ) g = gsrgb/12.92 else g = e( 2.4 * l((gsrgb+0.055)/1.055) )
if ( bsrgb <= 0.03928 ) b = bsrgb/12.92 else b = e( 2.4 * l((bsrgb+0.055)/1.055) )
0.2126 * r + 0.7152 * g + 0.0722 * b" | bc -l

这会将一些指令传递给 BC,包括作为相对亮度公式一部分的 if-then-else 语句。接下来 BC 打印出最终值。

计算对比度

利用文本颜色和背景颜色的相对亮度,脚本就可以计算对比度了。 W3C 确定对比度 是使用以下公式:

(L1 + 0.05) / (L2 + 0.05),这里的 L1 是颜色较浅的相对亮度, L2 是颜色较深的相对亮度。

给定两个相对亮度值 $r1$r2,使用 BC 计算器很容易计算对比度:

echo "scale=2
if ( $r1 > $r2 ) { l1=$r1; l2=$r2 } else { l1=$r2; l2=$r1 }
(l1 + 0.05) / (l2 + 0.05)" | bc

使用 if-then-else 语句确定哪个值($r1$r2)是较浅还是较深的颜色。BC 执行结果计算并打印结果,脚本可以将其存储在变量中。

最终脚本

通过以上内容,我们可以将所有内容整合到一个最终脚本。 我使用 Zenity 在文本框中显示最终结果:

#!/bin/sh
# script to calculate contrast ratio of colors

# read color and background color:
# zenity returns values like 'rgb(255,140,0)' and 'rgb(255,255,255)'

color=$( zenity --title 'Set text color' --color-selection --color='black' )
if [ $? -ne 0 ] ; then
        echo '** color canceled .. assume black'
        color='rgb(0,0,0)'
fi

background=$( zenity --title 'Set background color' --color-selection --color='white' )
if [ $? -ne 0 ] ; then
        echo '** background canceled .. assume white'
        background='rgb(255,255,255)'
fi

# compute relative luminance:

function luminance()
{
        R=$( echo $1 | awk -F, '{print substr($1,5)}' )
        G=$( echo $1 | awk -F, '{print $2}' )
        B=$( echo $1 | awk -F, '{n=length($3); print substr($3,1,n-1)}' )

        echo "scale=4
rsrgb=$R/255
gsrgb=$G/255
bsrgb=$B/255
if ( rsrgb <= 0.03928 ) r = rsrgb/12.92 else r = e( 2.4 * l((rsrgb+0.055)/1.055) )
if ( gsrgb <= 0.03928 ) g = gsrgb/12.92 else g = e( 2.4 * l((gsrgb+0.055)/1.055) )
if ( bsrgb <= 0.03928 ) b = bsrgb/12.92 else b = e( 2.4 * l((bsrgb+0.055)/1.055) )
0.2126 * r + 0.7152 * g + 0.0722 * b" | bc -l
}

lum1=$( luminance $color )
lum2=$( luminance $background )

# compute contrast

function contrast()
{
        echo "scale=2
if ( $1 > $2 ) { l1=$1; l2=$2 } else { l1=$2; l2=$1 }
(l1 + 0.05) / (l2 + 0.05)" | bc
}

rel=$( contrast $lum1 $lum2 )

# print results

( cat<<EOF
Color is $color on $background

Contrast ratio is $rel
Contrast ratios can range from 1 to 21 (commonly written 1:1 to 21:1).

EOF

if [ ${rel%.*} -ge 4 ] ; then
        echo "Ok for body text"
else
        echo "Not good for body text"
fi
if [ ${rel%.*} -ge 3 ] ; then
        echo "Ok for title text"
else
        echo "Not good for title text"
fi

cat<<EOF

W3C 说明:

1.4.3 对比度(最小值):文本和文本图像的视觉呈现方式的对比度至少为 4.5:1,但以下情况除外:(AA 级)

    大文本:大文本和大文本图像的对比度至少为 3:1;

    附带说明:作为非活动用户界面组件一部分,纯装饰的,任何人都不可见或图片的一部分包含特定的其他可视内容的文本或文本图像没有对比度要求。

    小示意图:徽标或商标名称中的文本没有最低对比度要求。

1.4.6 对比度(增强):文本和文本图像的视觉表示具有至少 7:1 的对比度,但以下情况除外:(AAA 级)

    大文本:大文本和大文本图像的对比度至少为 4.5:1;

    附带说明:作为非活动用户界面组件一部分,纯装饰的,任何人都不可见或图片的一部分包含特定的其他可视内容的文本或文本图像没有对比度要求。
 
    小示意图:徽标或商标名称中的文本没有最低对比度要求。
EOF
) | zenity --text-info --title='Relative Luminance' --width=800 --height=600

最后,我希望提供有关 W3C 建议的参考信息,以提醒自己。

Zenity 颜色选择器完成了所有解释颜色的艰苦工作,用户可以通过单击色轮或输入值来选择颜色。 Zenity 接受网站上使用的标准十六进制颜色值,例如 #000000#000rgb(0,0,0)(所有这些均为黑色)。这是白色背景上的黑色文本的示例计算:

Zenity 还识别标准的颜色名称,如“cadetblue”、“orange”或“gold”。在Zenity 中输入颜色名称,然后点击 Tab 键,Zenity 会将颜色名称转换为十六进制颜色值,如以下示例中对金色背景上的黑色文本的计算:


via: https://opensource.com/article/19/2/make-websites-more-readable-shell-script

作者:Jim Hall 选题:lujun9972 译者:stevenzdg988 校对:wxy

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

FED 编辑器让我可以轻松有效地对我的 FreeDOS 项目进行编码。学习如何充分利用这个灵活的 Linux、Windows 和 DOS 编辑器。

 title=

当我不在我的 Linux 桌面上工作的时候,我通常是在为一个旧版的 16 位系统写代码。FreeDOS 是一个开源的 DOS 兼容操作系统,你可以用它来玩经典的 DOS 游戏,运行旧版的商业软件,或者开发嵌入式系统。任何在 MS-DOS 上运行的程序在 FreeDOS 上也应该可以运行。

我是和 DOS 一起长大的。我家的第一台个人电脑是一台 Apple II 兼容机,但我们最终升级到了一台运行 DOS 的 IBM PC。从 1980 年代早期到 1993 年我发现 Linux 这段时间,我做了十多年的 DOS 用户。

Linux 和开源软件所提供的自由度给我留下了深刻的印象。所以当微软在 1994 年宣布 DOS 的终结,以及即将推出的 Windows 95 时,我决定编写自己的开源 DOS。这就是 FreeDOS 的开始

这么多年过去了,我还在继续研究 FreeDOS。它是一个很好的业余兴趣系统,在这里我可以运行我最喜欢的 DOS 应用和游戏。是的,我仍然在为 FreeDOS 写代码。

我最喜欢的 DOS 编程编辑器是 FED 编辑器。FED 是一个极简的文本编辑器,没有太多的视觉效果。这种精简的方法帮助我充分利用了 DOS 中标准的 80x25 屏幕。当编辑一个文件时,FED 会在屏幕底部显示一行状态行,让你在剩下的 24 行来编写你的代码。FED 还支持彩色语法高亮显示,以不同的颜色显示代码的不同部分,使你更容易发现错别字,以免它们变成错误。

Writing a Solitaire game with FED

用 FED 写一个纸牌游戏

当你需要菜单时,按下键盘上的 Alt 键,FED 就会在最上面一行显示一个菜单。FED 也支持键盘快捷键,但要注意默认值。例如,Ctrl-C 会关闭文件,Ctrl-V 会改变视图。如果你不喜欢这些默认键,你可以在 Config 菜单中更改键位映射。

Tap the Alt key to bring up the menu

按下 Alt 键弹出菜单

如果你不喜欢默认的白底黑字显示,你可以在 Config 菜单下更改颜色。我更喜欢蓝底白字的正文,关键词用亮白色,注释用亮蓝色,特殊字符用青色,数字用绿色。FED 可以很容易地设置你想要的颜色。

My preferred colors when programming on DOS

我在 DOS 上编程时喜欢的颜色

FED 也是一个折叠式文本编辑器,这意味着它可以折叠或展开我的部分代码,以便我可以看到更多的文件。在函数名上按下 Ctrl-F,FED 会折叠整个函数。折叠在其他代码上也可以使用。我也使用折叠来隐藏 forwhile 循环或其他流程控制,如 ifswitch 块。

Folding a function lets you see more of the file

折叠函数可以让你看到更多的文件

Shawn Hargreaves 从 1994 年至 2004 年编写并维护 FED。Robert Riebisch 从那时起就开始维护 FED。FED 在 GNU GPL 许可下发布,支持 DOS、Linux 和 Windows。

你可以在 https://www.bttr-software.de/products/fed/ 下载 FED。


via: https://opensource.com/article/21/1/fed-editor

作者:Jim Hall 选题:lujun9972 译者:geekpi 校对:wxy

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

当你学习一门新的编程语言时,这个“猜数字”游戏是一个很好的入门程序。下面是如何用 C 语言来编写它。

我在小学时就开始 自学编程。我的第一个程序是在 Apple II 上编写的,但最终,我还是通过看书和编程练习学会了 C 语言。练习编程的最佳方法是编写示例程序,它能帮助你练习新知识。

在我学习一种新的编程语言时,我喜欢编写一个简单的“猜数字”游戏来练习。电脑从 1 到 100 中随机挑选一个数字,你必须通过猜测来算出来。在另一篇文章中,我展示了如何用 Bash 语言编写这个“猜数字”游戏,我的同事也写了一些文章,介绍如何用 JavaJulia 和其他计算机语言编写它。

“猜数字”游戏的伟大之处在于它践行了几个编程概念:如何使用变量、如何比较值、如何打印输出以及如何读取输入。

整个夏天,我录制了一个系列视频,教人们如何用 C 语言编写程序。从那以后,我听到了很多人都在跟着它学习 C 语言编程的消息。所以,我想接着用 C 语言写一个“猜数字”的游戏。

去取一个随机数

从写一个函数来选择一个随机数字来开始“猜数字”游戏。在编写函数时,优秀的程序员会尽量使它们具有灵活性,这样他们就可以重用它们来解决略有不同的问题。因此,与其硬编码函数来选择 1 到 100 之间的一个随机数,不如编写函数来选择 1 到某个整数 maxval 之间的一个随机数:

#include <stdio.h>
#include <sys/random.h>

int
randnum(int maxval)
{
  /* pick a random number from 1 to maxval */

  int randval;

  getrandom(&randval, sizeof(int), GRND_NONBLOCK);

  /* could be negative, so ensure it's positive */

  if (randval < 0) {
    return (-1 * randval % maxval + 1);
  }
  else {
    return (randval % maxval + 1);
  }
}

该函数使用 Linux 的系统调用 getrandom 来生成一系列随机数。你可以在手册页中了解关于这个系统调用的更多信息,但请注意,getrandom 将用随机的 0 和 1 填充变量。这意味着最终值可以是正的,也可以是负的,因此你需要在之后进行测试,以确保 randnum 函数的结果是正值。

编写程序

你可以用这个函数来写你的“猜数字”程序:

#include <stdio.h>
#include <sys/random.h>
 
int
randnum(int maxval)
{
  ...
}

int
main(void)
{
  int number;
  int guess;

  number = randnum(100);

  puts("Guess a number between 1 and 100");

  do {
    scanf("%d", &guess);

    if (guess < number) {
      puts("Too low");
    }
    else if (guess > number) {
      puts("Too high");
    }
  } while (guess != number);

  puts("That's right!");

  return 0;
}

程序首先使用 randnum 函数从 1 到 100 之间选择一个随机数。在向用户输出一个提示后,程序进入一个 do-while 循环,以便用户可以猜测数字。

在循环的每次迭代中,程序测试用户的猜测的数值。如果用户的猜测小于随机数,程序将输出“Too low”,如果猜测大于随机数,程序将输出“Too high”。循环继续,直到用户的猜测与随机数相同。

当循环退出时,程序输出 “That's right!”,然后立即结束:

$ gcc -o guess -Wall guess.c

$ ./guess
Guess a number between 1 and 100
50
Too high
30
Too low
40
Too low
45
Too high
42
Too low
43
Too low
44
That's right!

尝试动手

在学习一门新的编程语言时,这个“猜数字”游戏是一个很好的入门程序,因为它以一种非常直接的方式练习了几个常见的编程概念。通过用不同的编程语言实现这个简单的游戏,你可以演示一些核心概念,并比较每种语言的细节。

你有最喜欢的编程语言吗?你会如何用它来编写“猜数字”游戏呢?关注本系列文章,查看你可能感兴趣的其他编程语言的示例。


via: https://opensource.com/article/21/1/learn-c

作者:Jim Hall 选题:lujun9972 译者:qfzy1233 校对:wxy

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