mirror of
https://github.com/LCTT/TranslateProject.git
synced 2025-01-25 23:11:02 +08:00
commit
20b4095521
@ -0,0 +1,286 @@
|
||||
[#]: collector: "lujun9972"
|
||||
[#]: translator: "amwps290"
|
||||
[#]: reviewer: "wxy"
|
||||
[#]: publisher: "wxy"
|
||||
[#]: url: "https://linux.cn/article-13087-1.html"
|
||||
[#]: subject: "Learn JavaScript by writing a guessing game"
|
||||
[#]: via: "https://opensource.com/article/21/1/learn-javascript"
|
||||
[#]: author: "Mandy Kendall https://opensource.com/users/mkendall"
|
||||
|
||||
通过编写一个简单的游戏学习 JavaScript
|
||||
======
|
||||
|
||||
> 通过使用一个简单的游戏来练习一些基本的 JavaScript 概念,迈出创建交互性动态 Web 内容的第一步。
|
||||
|
||||
![](https://img.linux.net.cn/data/attachment/album/202102/05/151531z6fzqx8vk8tdan81.jpg)
|
||||
|
||||
可以肯定地说,没有 [JavaScript][2],大多数现代 Web 都将不存在。它是三种标准 Web 技术(以及 HTML 和 CSS )之一,它使任何人都可以创建我们在万维网体验中所期待的交互式、动态内容。从 [React][3] 这样的框架到 [D3][4] 这样的数据可视化库,很难想象没有它的 Web。
|
||||
|
||||
现在有很多东西要学习,开始学习这种流行语言的好方法是编写一个简单的应用程序以熟悉某些概念。 最近,一些人写了关于如何通过编写简单的猜谜游戏来学习自己喜欢的语言的文章,因此这是一个很好的起点!
|
||||
|
||||
### 现在开始吧
|
||||
|
||||
JavaScript 有许多种风格,但我将从最基本的开始,通常称为 “普通 JavaScript”。 JavaScript 主要是一种客户端脚本语言,因此它可以在任何标准浏览器中运行,而无需安装任何程序。你只需要一个代码编辑器([Brackets][5] 就是一个不错的选择)和一个 Web 浏览器。
|
||||
|
||||
### HTML 用户界面
|
||||
|
||||
JavaScript 在 Web 浏览器中运行,并与其他标准 Web 技术 HTML 和 CSS 交互。要创建此游戏,你首先需要使用 HTML(超文本标记语言)来创建供玩家使用的简单界面。如果你不清楚,HTML 是一种标记语言,用于为 Web 内容提供结构。
|
||||
|
||||
首先,先创建一个 HTML 文件。该文件应具有 `.html` 扩展名,以使浏览器知道它是 HTML 文档。你可以将文件命名为 `guessingGame.html`。
|
||||
|
||||
在此文件中使用一些基本的 HTML 标签来显示游戏的标题、玩法说明,供玩家用来输入和提交其猜测的交互式元素以及用于向玩家提供反馈的占位符:
|
||||
|
||||
```
|
||||
<!DOCTYPE>
|
||||
<html>
|
||||
<head>
|
||||
<meta charset="UTF-8" />
|
||||
<title> JavaScript Guessing Game </title>
|
||||
</head>
|
||||
<body>
|
||||
<h1>Guess the Number!</h1>
|
||||
<p>I am thinking of a number between 1 and 100. Can you guess what it is?</p>
|
||||
|
||||
<label for="guess">My Guess</label>
|
||||
<input type="number" id="guess">
|
||||
<input type="submit" id="submitGuess" value="Check My Guess">
|
||||
|
||||
<p id="feedback"></p>
|
||||
</body>
|
||||
</html>
|
||||
```
|
||||
|
||||
`<h1>` 和 `<p>` 元素使浏览器知道在页面上显示什么类型的文本。标签对 `<h1></h1>` 表示标签之间的文本(`Guess the Number!`)是标题。后面的一组 `<p>` 标签表示带有说明的短文本是一个段落。此代码块末尾的空 `<p>` 标签用作占位符,用于根据用户的输入提供一些反馈。
|
||||
|
||||
### `<script>` 标签
|
||||
|
||||
在网页中包含 JavaScript 的方法有很多种,但是对于像这样的简短脚本,可以使用一组 `<script>` 标签并将 JavaScript 直接写在 HTML 文件中。 这些 `<script>` 标签应位于 HTML 文件末尾附近的 `</body>` 标签之前。
|
||||
|
||||
现在,你可以开始在这两个脚本标签之间编写 JavaScript。 最终文件如下所示:
|
||||
|
||||
```
|
||||
<!DOCTYPE>
|
||||
<html>
|
||||
|
||||
<head>
|
||||
<meta charset="UTF-8" />
|
||||
<title> JavaScript Guessing Game </title>
|
||||
</head>
|
||||
|
||||
<body>
|
||||
<h1>Guess the Number!</h1>
|
||||
<p>I am thinking of a number between 1 and 100. Can you guess what it is?</p>
|
||||
|
||||
<form>
|
||||
<label for="guess">My Guess</label>
|
||||
<input type="number" id="guess">
|
||||
<input type="submit" id="submitGuess" value="Check My Guess">
|
||||
</form>
|
||||
|
||||
<p id="feedback"></p>
|
||||
|
||||
<script>
|
||||
const randomNumber = Math.floor(Math.random() * 100) + 1
|
||||
console.log('Random Number', randomNumber)
|
||||
|
||||
function checkGuess() {
|
||||
let myGuess = guess.value
|
||||
if (myGuess === randomNumber) {
|
||||
feedback.textContent = "You got it right!"
|
||||
} else if (myGuess > randomNumber) {
|
||||
feedback.textContent = "Your guess was " + myGuess + ". That's too high. Try Again!"
|
||||
} else if (myGuess < randomNumber) {
|
||||
feedback.textContent = "Your guess was " + myGuess + ". That's too low. Try Again!"
|
||||
}
|
||||
}
|
||||
submitGuess.addEventListener('click', checkGuess)
|
||||
</script>
|
||||
|
||||
</body>
|
||||
|
||||
</html>
|
||||
```
|
||||
|
||||
要在浏览器中运行此文件,请双击文件或打开你喜欢的浏览器,点击菜单,然后选择**文件->打开文件**。(如果使用 Brackets 软件,也可以使用角落处的闪电图标在浏览器中打开文件)。
|
||||
|
||||
### 生成伪随机数
|
||||
|
||||
猜谜游戏的第一步是为玩家生成一个数字供玩家猜测。JavaScript 包含几个内置的全局对象,可帮助你编写代码。要生成随机数,请使用 `Math` 对象。
|
||||
|
||||
JavaScript中的 [Math][17] 具有处理和数学相关的属性和功能。你将使用两个数学函数来生成随机数,供你的玩家猜测。
|
||||
|
||||
[Math.random()][18],会将生成一个介于 0 和 1 之间的伪随机数。(`Math.random` 包含 0 但不包含 1。这意味着该函数可以生成 0 ,永远不会产生 1)
|
||||
|
||||
对于此游戏,请将随机数设置在 1 到 100 之间以缩小玩家的选择范围。取刚刚生成的小数,然后乘以 100,以产生一个介于 0 到……甚至不是 100 之间的小数。至此,你将需要其他步骤来解决这个问题。
|
||||
|
||||
现在,你的数字仍然是小数,但你希望它是一个整数。为此,你可以使用属于 `Math` 对象的另一个函数 [Math.floor()][19]。`Math.floor()` 的目的是返回小于或等于你作为参数指定的数字的最大整数,这意味着它会四舍五入为最接近的整数:
|
||||
|
||||
```
|
||||
Math.floor(Math.random() * 100)
|
||||
```
|
||||
|
||||
这样你将得到 0 到 99 之间的整数,这不是你想要的范围。你可以在最后一步修复该问题,即在结果中加 1。瞧!现在,你有一个(有点)随机生成的数字,介于 1 到 100 之间:
|
||||
|
||||
```
|
||||
Math.floor(Math.random() * 100) + 1
|
||||
```
|
||||
|
||||
### 变量
|
||||
|
||||
现在,你需要存储随机生成的数字,以便可以将其与玩家的猜测进行比较。为此,你可以将其存储到一个 **变量**。
|
||||
|
||||
JavaScript 具有不同类型的变量,你可以选择这些类型,具体取决于你要如何使用该变量。对于此游戏,请使用 `const` 和 `let`。
|
||||
|
||||
* `let` 用于指示变量在整个程序中可以改变。
|
||||
* `const` 用于指示变量不应该被修改。
|
||||
|
||||
`const` 和 `let` 还有很多要说的,但现在知道这些就足够了。
|
||||
|
||||
随机数在游戏中仅生成一次,因此你将使用 `const` 变量来保存该值。你想给变量起一个清楚地表明要存储什么值的名称,因此将其命名为 `randomNumber`:
|
||||
|
||||
```
|
||||
const randomNumber
|
||||
```
|
||||
|
||||
有关命名的注意事项:JavaScript 中的变量和函数名称以驼峰形式编写。如果只有一个单词,则全部以小写形式书写。如果有多个单词,则第一个单词均为小写,其他任何单词均以大写字母开头,且单词之间没有空格。
|
||||
|
||||
### 打印到控制台
|
||||
|
||||
通常,你不想向任何人显示随机数,但是开发人员可能想知道生成的数字以使用它来帮助调试代码。 使用 JavaScript,你可以使用另一个内置函数 [console.log()][20] 将数字输出到浏览器的控制台。
|
||||
|
||||
大多数浏览器都包含开发人员工具,你可以通过按键盘上的 `F12` 键来打开它们。从那里,你应该看到一个 **控制台** 标签。打印到控制台的所有信息都将显示在此处。由于到目前为止编写的代码将在浏览器加载后立即运行,因此,如果你查看控制台,你应该会看到刚刚生成的随机数!
|
||||
|
||||
![Javascript game with console][21]
|
||||
|
||||
### 函数
|
||||
|
||||
接下来,你需要一种方法来从数字输入字段中获得玩家的猜测,将其与你刚刚生成的随机数进行比较,并向玩家提供反馈,让他们知道他们是否正确猜到了。为此,编写一个函数。 **函数** 是执行一定任务的代码块。函数是可以重用的,这意味着如果你需要多次运行相同的代码,则可以调用函数,而不必重写执行任务所需的所有步骤。
|
||||
|
||||
根据你使用的 JavaScript 版本,有许多不同的方法来编写或声明函数。由于这是该语言的基础入门,因此请使用基本函数语法声明函数。
|
||||
|
||||
以关键字 `function` 开头,然后起一个函数名。好的做法是使用一个描述该函数的功能的名称。在这个例子中,你正在检查玩家的猜测的数,因此此函数的名字可以是 `checkGuess`。在函数名称之后,写上一组小括号,然后写上一组花括号。 你将在以下花括号之间编写函数的主体:
|
||||
|
||||
```
|
||||
function checkGuess() {}
|
||||
```
|
||||
|
||||
### 使用 DOM
|
||||
|
||||
JavaScript 的目的之一是与网页上的 HTML 交互。它通过文档对象模型(DOM)进行此操作,DOM 是 JavaScript 用于访问和更改网页信息的对象。现在,你需要从 HTML 中获取数字输入字段中玩家的猜测。你可以使用分配给 HTML 元素的 `id` 属性(在这种情况下为 `guess`)来做到这一点:
|
||||
|
||||
```
|
||||
<input type="number" id="guess">
|
||||
```
|
||||
|
||||
JavaScript 可以通过访问玩家输入到数字输入字段中的数来获取其值。你可以通过引用元素的 ID 并在末尾添加 `.value` 来实现。这次,使用 `let` 定义的变量来保存用户的猜测值:
|
||||
|
||||
```
|
||||
let myGuess = guess.value
|
||||
```
|
||||
|
||||
玩家在数字输入字段中输入的任何数字都将被分配给 `checkGuess` 函数中的 `myGuess` 变量。
|
||||
|
||||
### 条件语句
|
||||
|
||||
下一步是将玩家的猜测与游戏产生的随机数进行比较。你还想给玩家反馈,让他们知道他们的猜测是太高,太低还是正确。
|
||||
|
||||
你可以使用一系列条件语句来决定玩家将收到的反馈。**条件语句** 在运行代码块之前检查是否满足条件。如果不满足条件,则代码停止,继续检查下一个条件,或者继续执行其余代码,而无需执行条件块中的代码:
|
||||
|
||||
```
|
||||
if (myGuess === randomNumber){
|
||||
feedback.textContent = "You got it right!"
|
||||
}
|
||||
else if(myGuess > randomNumber) {
|
||||
feedback.textContent = "Your guess was " + myGuess + ". That's too high. Try Again!"
|
||||
}
|
||||
else if(myGuess < randomNumber) {
|
||||
feedback.textContent = "Your guess was " + myGuess + ". That's too low. Try Again!"
|
||||
}
|
||||
```
|
||||
|
||||
第一个条件块使用比较运算符 `===` 将玩家的猜测与游戏生成的随机数进行比较。比较运算符检查右侧的值,将其与左侧的值进行比较,如果匹配则返回布尔值 `true`,否则返回布尔值 `false`。
|
||||
|
||||
如果数字匹配(猜对了!),为了让玩家知道。通过将文本添加到具有 `id` 属性 `feedback` 的 `<p>` 标记中来操作 DOM。就像上面的 `guess.value` 一样,除了不是从 DOM 获取信息,而是更改其中的信息。`<p>` 元素没有像 `<input>` 元素那样的值,而是具有文本,因此请使用 `.textContent` 访问元素并设置要显示的文本:
|
||||
|
||||
```
|
||||
feedback.textContent = "You got it right!"
|
||||
```
|
||||
|
||||
当然,玩家很有可能在第一次尝试时就猜错了,因此,如果 `myGuess` 和 `randomNumber` 不匹配,请给玩家一个线索,以帮助他们缩小猜测范围。如果第一个条件失败,则代码将跳过该 `if` 语句中的代码块,并检查下一个条件是否为 `true`。 这使你进入 `else if` 块:
|
||||
|
||||
```
|
||||
else if(myGuess > randomNumber) {
|
||||
feedback.textContent = "Your guess was " + myGuess + ". That's too high. Try Again!"
|
||||
}
|
||||
```
|
||||
|
||||
如果你将其作为句子阅读,则可能是这样的:“如果玩家的猜测等于随机数,请让他们知道他们猜对了。否则,请检查玩家的猜测是否大于 `randomNumber`,如果是,则显示玩家的猜测并告诉他们太高了。”
|
||||
|
||||
最后一种可能性是玩家的猜测低于随机数。 要检查这一点,再添加一个 `else if` 块:
|
||||
|
||||
```
|
||||
else if(myGuess < randomNumber) {
|
||||
feedback.textContent = "Your guess was " + myGuess + ". That's too low. Try Again!"
|
||||
}
|
||||
```
|
||||
|
||||
### 用户事件和事件监听器
|
||||
|
||||
如果你看上面的代码,则会看到某些代码在页面加载时自动运行,但有些则不会。你想在玩游戏之前生成随机数,但是你不想在玩家将数字输入到数字输入字段并准备检查它之前检查其猜测。
|
||||
|
||||
生成随机数并将其打印到控制台的代码不在函数的范围内,因此它将在浏览器加载脚本时自动运行。 但是,要使函数内部的代码运行,你必须对其进行调用。
|
||||
|
||||
调用函数有几种方法。在此,你希望该函数在用户单击 “Check My Guess” 按钮时运行。单击按钮将创建一个用户事件,然后 JavaScript 可以 “监听” 这个事件,以便知道何时需要运行函数。
|
||||
|
||||
代码的最后一行将事件侦听器添加到按钮上,以在单击按钮时调用函数。当它“听到”该事件时,它将运行分配给事件侦听器的函数:
|
||||
|
||||
```
|
||||
submitGuess.addEventListener('click', checkGuess)
|
||||
```
|
||||
|
||||
就像访问 DOM 元素的其他实例一样,你可以使用按钮的 ID 告诉 JavaScript 与哪个元素进行交互。 然后,你可以使用内置的 `addEventListener` 函数来告诉 JavaScript 要监听的事件。
|
||||
|
||||
你已经看到了带有参数的函数,但花点时间看一下它是如何工作的。参数是函数执行其任务所需的信息。并非所有函数都需要参数,但是 `addEventListener` 函数需要两个参数。它采用的第一个参数是将为其监听的用户事件的名称。用户可以通过多种方式与 DOM 交互,例如键入、移动鼠标,键盘上的 `TAB` 键和粘贴文本。在这种情况下,你正在监听的用户事件是单击按钮,因此第一个参数将是 `click`。
|
||||
|
||||
`addEventListener`的第二个所需的信息是用户单击按钮时要运行的函数的名称。 这里我们需要 `checkGuess` 函数。
|
||||
|
||||
现在,当玩家按下 “Check My Guess” 按钮时,`checkGuess` 函数将获得他们在数字输入字段中输入的值,将其与随机数进行比较,并在浏览器中显示反馈,以使玩家知道他们猜的怎么样。 太棒了!你的游戏已准备就绪。
|
||||
|
||||
### 学习 JavaScript 以获取乐趣和收益
|
||||
|
||||
这一点点的平凡无奇的 JavaScript 只是这个庞大的生态系统所提供功能的一小部分。这是一种值得花时间投入学习的语言,我鼓励你继续挖掘并学习更多。
|
||||
|
||||
--------------------------------------------------------------------------------
|
||||
|
||||
via: https://opensource.com/article/21/1/learn-javascript
|
||||
|
||||
作者:[Mandy Kendall][a]
|
||||
选题:[lujun9972][b]
|
||||
译者:[amwps290](https://github.com/amwps290)
|
||||
校对:[wxy](https://github.com/wxy)
|
||||
|
||||
本文由 [LCTT](https://github.com/LCTT/TranslateProject) 原创编译,[Linux中国](https://linux.cn/) 荣誉推出
|
||||
|
||||
[a]: https://opensource.com/users/mkendall
|
||||
[b]: https://github.com/lujun9972
|
||||
[1]: https://opensource.com/sites/default/files/styles/image-full-size/public/lead-images/code_javascript.jpg?itok=60evKmGl "Javascript code close-up with neon graphic overlay"
|
||||
[2]: https://opensource.com/tags/javascript
|
||||
[3]: https://opensource.com/article/20/11/reactjs-tutorial
|
||||
[4]: https://opensource.com/article/18/9/open-source-javascript-chart-libraries
|
||||
[5]: https://opensource.com/article/20/12/brackets
|
||||
[6]: http://december.com/html/4/element/html.html
|
||||
[7]: http://december.com/html/4/element/head.html
|
||||
[8]: http://december.com/html/4/element/meta.html
|
||||
[9]: http://december.com/html/4/element/title.html
|
||||
[10]: http://december.com/html/4/element/body.html
|
||||
[11]: http://december.com/html/4/element/h1.html
|
||||
[12]: http://december.com/html/4/element/p.html
|
||||
[13]: http://december.com/html/4/element/label.html
|
||||
[14]: http://december.com/html/4/element/input.html
|
||||
[15]: http://december.com/html/4/element/form.html
|
||||
[16]: http://december.com/html/4/element/script.html
|
||||
[17]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Math
|
||||
[18]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Math/random
|
||||
[19]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Math/floor
|
||||
[20]: https://developer.mozilla.org/en-US/docs/Web/API/Console/log
|
||||
[21]: https://opensource.com/sites/default/files/javascript-game-with-console.png "Javascript game with console"
|
@ -1,16 +1,18 @@
|
||||
[#]: collector: (lujun9972)
|
||||
[#]: translator: (geekpi)
|
||||
[#]: reviewer: ( )
|
||||
[#]: publisher: ( )
|
||||
[#]: url: ( )
|
||||
[#]: reviewer: (wxy)
|
||||
[#]: publisher: (wxy)
|
||||
[#]: url: (https://linux.cn/article-13084-1.html)
|
||||
[#]: subject: (Use your Raspberry Pi as a productivity powerhouse)
|
||||
[#]: via: (https://opensource.com/article/21/1/raspberry-pi-productivity)
|
||||
[#]: author: (Kevin Sonney https://opensource.com/users/ksonney)
|
||||
|
||||
将你的树莓派用作生产力源泉
|
||||
======
|
||||
树莓派已经从主要为 hack 和业余爱好者服务,成为了小型生产力工作站的可靠选择。
|
||||
![Team checklist and to dos][1]
|
||||
|
||||
> 树莓派已经从主要为黑客和业余爱好者服务,成为了小型生产力工作站的可靠选择。
|
||||
|
||||
![](https://img.linux.net.cn/data/attachment/album/202102/04/103826pjbxb7j1m8ok6ezf.jpg)
|
||||
|
||||
在前几年,这个年度系列涵盖了单个的应用。今年,我们除了关注 2021 年的策略外,还将关注一体化解决方案。欢迎来到 2021 年 21 天生产力的第十六天。
|
||||
|
||||
@ -20,17 +22,17 @@
|
||||
|
||||
![Geary and Calendar apps on the Raspberry Pi][3]
|
||||
|
||||
Geary 和 Calendar 应用 (Kevin Sonney, [CC BY-SA 4.0][4])
|
||||
*Geary 和 Calendar 应用 (Kevin Sonney, [CC BY-SA 4.0][4])*
|
||||
|
||||
基本的 [Raspbian][5] 安装包括 [Claw Mail][6],这是一个轻量级的邮件客户端。它的用户界面有点过时了,而且非常的简陋。如果你是一个 [Mutt 用户][7],它可能会满足你的需求。
|
||||
|
||||
我更喜欢安装 [Geary][8],因为它也是轻量级的,而且有一个现代化的界面。另外,与 Claws 不同的是,Geary 默认支持富文本 (HTML) 邮件。我不喜欢富文本电子邮件,但它已经成为必要的,所以对它有良好的支持是至关重要的。
|
||||
|
||||
默认的 Raspbian 安装不包含日历,所以我添加了 [GNOME Calendar][9] ,因为它可以与远程服务通信(因为我的几乎所有日历都在云提供商那里)。
|
||||
默认的 Raspbian 安装不包含日历,所以我添加了 [GNOME 日历][9] ,因为它可以与远程服务通信(因为我的几乎所有日历都在云提供商那里)。
|
||||
|
||||
![GTG and GNote open on Raspberry Pi][10]
|
||||
|
||||
GTG 和 GNote(Kevin Sonney, [CC BY-SA 4.0][4])
|
||||
*GTG 和 GNote(Kevin Sonney, [CC BY-SA 4.0][4])*
|
||||
|
||||
那笔记和待办事项清单呢?有很多选择,但我喜欢用 [GNote][11] 来做笔记,用 [Getting-Things-GNOME!][12] 来做待办事项。两者都相当轻量级,并且可以相互同步,也可以同步到其他服务。
|
||||
|
||||
@ -40,9 +42,9 @@ GNOME 中包含了 [Evolution][13],它将邮件、日历、笔记、待办事
|
||||
|
||||
![Evolution on Raspbian][14]
|
||||
|
||||
Raspbian 上的 Evolution (Kevin Sonney, [CC BY-SA 4.0][4])
|
||||
*Raspbian 上的 Evolution (Kevin Sonney, [CC BY-SA 4.0][4])*
|
||||
|
||||
树莓派在过去的几年里走过了很长的路,已经从主要为 hack 和业余爱好者服务,成为了小型生产力工作站的可靠选择。
|
||||
树莓派在过去的几年里进步很快,已经从主要为黑客和业余爱好者服务,成为了小型生产力工作站的可靠选择。
|
||||
|
||||
--------------------------------------------------------------------------------
|
||||
|
||||
@ -51,7 +53,7 @@ via: https://opensource.com/article/21/1/raspberry-pi-productivity
|
||||
作者:[Kevin Sonney][a]
|
||||
选题:[lujun9972][b]
|
||||
译者:[geekpi](https://github.com/geekpi)
|
||||
校对:[校对者ID](https://github.com/校对者ID)
|
||||
校对:[wxy](https://github.com/wxy)
|
||||
|
||||
本文由 [LCTT](https://github.com/LCTT/TranslateProject) 原创编译,[Linux中国](https://linux.cn/) 荣誉推出
|
||||
|
@ -1,30 +1,30 @@
|
||||
[#]: collector: (lujun9972)
|
||||
[#]: translator: (geekpi)
|
||||
[#]: reviewer: ( )
|
||||
[#]: publisher: ( )
|
||||
[#]: url: ( )
|
||||
[#]: reviewer: (wxy)
|
||||
[#]: publisher: (wxy)
|
||||
[#]: url: (https://linux.cn/article-13086-1.html)
|
||||
[#]: subject: (3 email mistakes and how to avoid them)
|
||||
[#]: via: (https://opensource.com/article/21/1/email-mistakes)
|
||||
[#]: author: (Kevin Sonney https://opensource.com/users/ksonney)
|
||||
|
||||
3 个电子邮件错误以及如何避免它们
|
||||
======
|
||||
自动化是美好的,除非不是那样。确保你的电子邮件自动回复和抄送配置正确,这样你就不会浪费大家的时间。
|
||||
![Computer screen with files or windows open][1]
|
||||
|
||||
> 自动化是美好的,但也不总是那样。确保你的电子邮件自动回复和抄送配置正确,这样你就不会浪费大家的时间。
|
||||
|
||||
![](https://img.linux.net.cn/data/attachment/album/202102/05/090335a888nqn7pcolblzn.jpg)
|
||||
|
||||
在前几年,这个年度系列涵盖了单个的应用。今年,我们除了关注 2021 年的策略外,还将关注一体化解决方案。欢迎来到 2021 年 21 天生产力的第十七天。
|
||||
|
||||
好了,我们已经谈到了一些我们应该对电子邮件做的事情:[不要再把它当作即时通讯工具][2]、[优先处理事情][3]、[努力达到收件箱 0 新邮件][4],以及[有效过滤][5]。但哪些事情是我们不应该做的呢?
|
||||
|
||||
|
||||
|
||||
![Automated email reply][6]
|
||||
|
||||
你真幸运 (Kevin Sonney, [CC BY-SA 4.0][7])
|
||||
*你真幸运 (Kevin Sonney, [CC BY-SA 4.0][7])*
|
||||
|
||||
### 1\. 请不要对_所有事情_自动回复
|
||||
### 1、请不要对所有事情自动回复
|
||||
|
||||
任何邮件列表中总有些人,他们去度假了,并设置了一个“我在度假”的自动回复信息。然而,他们没有正确地设置,所以它对列表上的每一封邮件都会回复“我在度假”,直到管理员将其屏蔽或取消订阅。
|
||||
邮件列表中总有些人,他们去度假了,并设置了一个“我在度假”的自动回复信息。然而,他们没有正确地设置,所以它对列表上的每一封邮件都会回复“我在度假”,直到管理员将其屏蔽或取消订阅。
|
||||
|
||||
我们都感受到了这种痛苦,我承认过去至少有一次,我就是那个人。
|
||||
|
||||
@ -32,25 +32,25 @@
|
||||
|
||||
![An actual email with lots of CC'd recipients][8]
|
||||
|
||||
这是一封真实的电子邮件 (Kevin Sonney, [CC BY-SA 4.0][7])
|
||||
*这是一封真实的电子邮件 (Kevin Sonney, [CC BY-SA 4.0][7])*
|
||||
|
||||
### 2\. 请不要抄送给所有人
|
||||
### 2、请不要抄送给所有人
|
||||
|
||||
我们都至少做过一次。我们需要发送邮件的人员众多,因此我们只需抄送他们_所有人_。有时这是有必要的,但大多数时候,它并不是。当然,你邀请每个人在庭院吃生日蛋糕,或者你的表姐要结婚了,或者公司刚拿到一个大客户,这都是好事。如果你有邮件列表的话,请用邮件列表,如果没有的话,请给每个人密送。说真的,密送是你的朋友。
|
||||
我们都至少做过一次。我们需要发送邮件的人员众多,因此我们只需抄送他们*所有人*。有时这是有必要的,但大多数时候,它并不是。当然,你邀请每个人在庭院吃生日蛋糕,或者你的表姐要结婚了,或者公司刚拿到一个大客户,这都是好事。如果你有邮件列表的话,请用邮件列表,如果没有的话,请给每个人密送。说真的,密送是你的朋友。
|
||||
|
||||
### 3\. 回复所有人不是你的朋友
|
||||
### 3、回复所有人不是你的朋友
|
||||
|
||||
![Reply options in Kmail][9]
|
||||
|
||||
这一条与上一条是相辅相成的。我不知道有多少次看到有人向一个名单(或者是一个大群的人)发送信息,而这个信息本来是要发给一个人的。我见过这样发送的相对好的邮件,以及随之而来的纪律处分邮件。
|
||||
|
||||
认真地说,除非必须,不要使用“回复全部”按钮。即使是这样,也要确保你_真的_需要这样做。
|
||||
认真地说,除非必须,不要使用“回复全部”按钮。即使是这样,也要确保你*真的*需要这样做。
|
||||
|
||||
有些电子邮件应用比其他应用管理得更好。Kmail,[KDE Kontact][10] 的电子邮件组件,在**回复**工具栏按钮的子菜单中,有几个不同的回复选项。你可以选择只回复给**发件人**字段中的任何实体(通常是一个人,但有时是一个邮件列表),或者回复给作者(在抄送或密送中去除任何一个人),只回复给一个邮件列表,或者回复_所有人_(不要这样做)。看到明确列出的选项,的确可以帮助你了解谁会收到你要发送的邮件副本,这有时比你想象的更发人深省。我曾经发现,当我意识到一个评论并不一定会对一个复杂的讨论的最终目标有所帮助时,我就会把邮件的收件人改为只收作者,而不是整个列表。
|
||||
有些电子邮件应用比其他应用管理得更好。Kmail,[KDE Kontact][10] 的电子邮件组件,在**回复**工具栏按钮的子菜单中,有几个不同的回复选项。你可以选择只回复给**发件人**字段中的任何实体(通常是一个人,但有时是一个邮件列表),或者回复给作者(在抄送或密送中去除每一个人),或者只回复给一个邮件列表,或者回复*所有人*(不要这样做)。看到明确列出的选项,的确可以帮助你了解谁会收到你要发送的邮件副本,这有时比你想象的更发人深省。我曾经发现,当我意识到一个评论并不一定会对一个复杂的讨论的最终目标有所帮助时,我就会把邮件的收件人改为只是作者,而不是整个列表。
|
||||
|
||||
(另外,如果你写的邮件可能会给你的 HR 或公司带来麻烦,请在点击发送之前多考虑下。)
|
||||
(另外,如果你写的邮件可能会给你的 HR 或公司带来麻烦,请在点击发送之前多考虑下。——
|
||||
|
||||
希望你已经_不再_电子邮件中这么做了。如果你认识的人是这样的呢?欢迎与他们分享。
|
||||
希望你已经*不再*在电子邮件中这么做了。如果你认识的人是这样的呢?欢迎与他们分享。
|
||||
|
||||
--------------------------------------------------------------------------------
|
||||
|
||||
@ -59,7 +59,7 @@ via: https://opensource.com/article/21/1/email-mistakes
|
||||
作者:[Kevin Sonney][a]
|
||||
选题:[lujun9972][b]
|
||||
译者:[geekpi](https://github.com/geekpi)
|
||||
校对:[校对者ID](https://github.com/校对者ID)
|
||||
校对:[wxy](https://github.com/wxy)
|
||||
|
||||
本文由 [LCTT](https://github.com/LCTT/TranslateProject) 原创编译,[Linux中国](https://linux.cn/) 荣誉推出
|
||||
|
@ -1,5 +1,5 @@
|
||||
[#]: collector: (lujun9972)
|
||||
[#]: translator: ( )
|
||||
[#]: translator: (MjSeven)
|
||||
[#]: reviewer: ( )
|
||||
[#]: publisher: ( )
|
||||
[#]: url: ( )
|
||||
|
@ -1,174 +0,0 @@
|
||||
[#]: collector: (lujun9972)
|
||||
[#]: translator: (robsean)
|
||||
[#]: reviewer: ( )
|
||||
[#]: publisher: ( )
|
||||
[#]: url: ( )
|
||||
[#]: subject: (How to Run a Shell Script in Linux [Essentials Explained for Beginners])
|
||||
[#]: via: (https://itsfoss.com/run-shell-script-linux/)
|
||||
[#]: author: (Abhishek Prakash https://itsfoss.com/author/abhishek/)
|
||||
|
||||
How to Run a Shell Script in Linux [Essentials Explained for Beginners]
|
||||
======
|
||||
|
||||
There are two ways to run a shell script in Linux. You can use:
|
||||
|
||||
```
|
||||
bash script.sh
|
||||
```
|
||||
|
||||
Or you can execute the shell script like this:
|
||||
|
||||
```
|
||||
./script.sh
|
||||
```
|
||||
|
||||
That maybe simple, but it doesn’t explain a lot. Don’t worry, I’ll do the necessary explaining with examples so that you understand why a particular syntax is used in the given format while running a shell script.
|
||||
|
||||
I am going to use this one line shell script to make things as uncomplicated as possible:
|
||||
|
||||
```
|
||||
[email protected]:~/Scripts$ cat hello.sh
|
||||
|
||||
echo "Hello World!"
|
||||
```
|
||||
|
||||
### Method 1: Running a shell script by passing the file as argument to shell
|
||||
|
||||
The first method involves passing the script file name as an argument to the shell.
|
||||
|
||||
Considering that bash is the default shell, you can run a script like this:
|
||||
|
||||
```
|
||||
bash hello.sh
|
||||
```
|
||||
|
||||
Do you know the advantage of this approach? **Your script doesn’t need to have the execute permission**. Pretty handy for quick and simple tasks.
|
||||
|
||||
![Running a Shell Script Linux][1]
|
||||
|
||||
If you are not familiar already, I advise you to [read my detailed guide on file permission in Linux][2].
|
||||
|
||||
Keep in mind that it needs to be a shell script that you pass as argument. A shell script is composed of commands. If you use a normal text file, it will complain about incorrect commands.
|
||||
|
||||
![Running a Text File As Script][3]
|
||||
|
||||
In this approach, **you explicitly specified that you want to use bash as the interpreter** for the script.
|
||||
|
||||
Shell is just a program and bash is an implementation of that. There are other such shells program like ksh, [zsh][4], etc. If you have other shells installed, you can use that as well instead of bash.
|
||||
|
||||
For example, I installed zsh and used it to run the same script:
|
||||
|
||||
![Execute Shell Script With Zsh][5]
|
||||
|
||||
**Recommended Read:**
|
||||
|
||||
![][6]
|
||||
|
||||
#### [How to Run Multiple Linux Commands at Once in Linux Terminal [Essential Beginners Tip]][7]
|
||||
|
||||
### Method 2: Execute shell script by specifying its path
|
||||
|
||||
The other method to run a shell script is by providing its path. But for that to be possible, your file must be executable. Otherwise, you’ll have “permission denied” error when you try to execute the script.
|
||||
|
||||
So first you need to make sure that your script has the execute permission. You can [use the chmod command][8] to give yourself this permission like this:
|
||||
|
||||
```
|
||||
chmod u+x script.sh
|
||||
```
|
||||
|
||||
Once your script is executable, all you need to do is to type the file name along with its absolute or relative path. Most often you are in the same directory so you just use it like this:
|
||||
|
||||
```
|
||||
./script.sh
|
||||
```
|
||||
|
||||
If you are not in the same directory as your script, you can specify it the absolute or relative path to the script:
|
||||
|
||||
![Running Shell Script In Other Directory][9]
|
||||
|
||||
#### That ./ before the script is important (when you are in the same directory as the script)
|
||||
|
||||
![][10]
|
||||
|
||||
Why can you not use the script name when you are in the same directory? That is because your Linux systems looks for the executables to run in a few selected directories that are specified in the PATH variable.
|
||||
|
||||
Here’s the value of PATH variable for my system:
|
||||
|
||||
```
|
||||
[email protected]:~$ echo $PATH
|
||||
/home/abhishek/.local/bin:/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin:/usr/games:/usr/local/games:/snap/bin
|
||||
```
|
||||
|
||||
This means that any file with execute permissions in one of the following directories can be executed from anywhere in the system:
|
||||
|
||||
* /home/abhishek/.local/bin
|
||||
* /usr/local/sbin
|
||||
* /usr/local/bin
|
||||
* /usr/sbin
|
||||
* /usr/bin
|
||||
* /sbin
|
||||
* /bin
|
||||
* /usr/games
|
||||
* /usr/local/games
|
||||
* /snap/bin
|
||||
|
||||
|
||||
|
||||
The binaries or executable files for Linux commands like ls, cat etc are located in one of those directories. This is why you are able to run these commands from anywhere on your system just by using their names. See, the ls command is located in /usr/bin directory.
|
||||
|
||||
![][11]
|
||||
|
||||
When you specify the script WITHOUT the absolute or relative path, it cannot find it in the directories mentioned in the PATH variable.
|
||||
|
||||
#### Why most shell scripts contain #! /bin/bash at the beginning of the shell scripts?
|
||||
|
||||
Remember how I mentioned that shell is just a program and there are different implementations of shells.
|
||||
|
||||
When you use the #! /bin/bash, you are specifying that the script is to run with bash as interpreter. If you don’t do that and run a script in ./script.sh manner, it is usually run with whatever shell you are running.
|
||||
|
||||
Does it matter? It could. See, most of the shell syntax is common in all kind of shell but some might differ.
|
||||
|
||||
For example, the array behavior is different in bash and zsh shells. In zsh, the array index starts at 1 instead of 0.
|
||||
|
||||
![Bash Vs Zsh][12]
|
||||
|
||||
Using #! /bin/bash indicates that the script is bash shell script and should be run with bash as interpreter irrespective of the shell which is being used on the system. If you are using zsh specific syntax, you can indicate that it is zsh script by adding #! /bin/zsh as the first line of the script.
|
||||
|
||||
The space between #! /bin/bash doesn’t matter. You can also use #!/bin/bash.
|
||||
|
||||
### Was it helpful?
|
||||
|
||||
I hope this article added to your Linux knowledge. If you still have questions or suggestions, please leave a comment.
|
||||
|
||||
Expert users can still nitpick this article about things I missed out. But the problem with such beginner topics is that it is not easy to find the right balance of information and avoid having too much or too few details.
|
||||
|
||||
If you are interested in learning bash script, we have an [entire Bash Beginner Series][13] on our sysadmin focused website [Linux Handbook][14]. If you want, you may also [purchase the ebook with additional exercises to support Linux Handbook][15].
|
||||
|
||||
--------------------------------------------------------------------------------
|
||||
|
||||
via: https://itsfoss.com/run-shell-script-linux/
|
||||
|
||||
作者:[Abhishek Prakash][a]
|
||||
选题:[lujun9972][b]
|
||||
译者:[译者ID](https://github.com/译者ID)
|
||||
校对:[校对者ID](https://github.com/校对者ID)
|
||||
|
||||
本文由 [LCTT](https://github.com/LCTT/TranslateProject) 原创编译,[Linux中国](https://linux.cn/) 荣誉推出
|
||||
|
||||
[a]: https://itsfoss.com/author/abhishek/
|
||||
[b]: https://github.com/lujun9972
|
||||
[1]: https://i0.wp.com/itsfoss.com/wp-content/uploads/2021/01/run-a-shell-script-linux.png?resize=741%2C329&ssl=1
|
||||
[2]: https://linuxhandbook.com/linux-file-permissions/
|
||||
[3]: https://i0.wp.com/itsfoss.com/wp-content/uploads/2021/01/running-text-file-as-script.png?resize=741%2C329&ssl=1
|
||||
[4]: https://www.zsh.org
|
||||
[5]: https://i2.wp.com/itsfoss.com/wp-content/uploads/2021/01/execute-shell-script-with-zsh.png?resize=741%2C253&ssl=1
|
||||
[6]: https://i2.wp.com/itsfoss.com/wp-content/uploads/2020/09/run-multiple-commands-in-linux.png?fit=800%2C450&ssl=1
|
||||
[7]: https://itsfoss.com/run-multiple-commands-linux/
|
||||
[8]: https://linuxhandbook.com/chmod-command/
|
||||
[9]: https://i2.wp.com/itsfoss.com/wp-content/uploads/2021/01/running-shell-script-in-other-directory.png?resize=795%2C272&ssl=1
|
||||
[10]: https://i0.wp.com/itsfoss.com/wp-content/uploads/2021/01/executing-shell-scripts-linux.png?resize=800%2C450&ssl=1
|
||||
[11]: https://i0.wp.com/itsfoss.com/wp-content/uploads/2021/01/locating-command-linux.png?resize=795%2C272&ssl=1
|
||||
[12]: https://i1.wp.com/itsfoss.com/wp-content/uploads/2021/01/bash-vs-zsh.png?resize=795%2C386&ssl=1
|
||||
[13]: https://linuxhandbook.com/tag/bash-beginner/
|
||||
[14]: https://linuxhandbook.com
|
||||
[15]: https://www.buymeacoffee.com/linuxhandbook
|
@ -1,85 +0,0 @@
|
||||
[#]: collector: (lujun9972)
|
||||
[#]: translator: (geekpi)
|
||||
[#]: reviewer: ( )
|
||||
[#]: publisher: ( )
|
||||
[#]: url: ( )
|
||||
[#]: subject: (Generate QR codes with this open source tool)
|
||||
[#]: via: (https://opensource.com/article/21/2/zint-barcode-generator)
|
||||
[#]: author: (Don Watkins https://opensource.com/users/don-watkins)
|
||||
|
||||
Generate QR codes with this open source tool
|
||||
======
|
||||
Zint makes it easy to generate more than 50 types of custom barcodes.
|
||||
![Working from home at a laptop][1]
|
||||
|
||||
QR codes are an excellent way to provide information to people without the trouble and expense of printing it. Most people have smartphones that support QR code scanning, regardless of the operating system.
|
||||
|
||||
There are many reasons you might want to use QR codes. Maybe you're a teacher looking to challenge your students with supplemental material to enhance learning or a restaurant that needs to provide menus while complying with social-distancing guidelines. I often walk on nature trails where trees and other flora are labeled. Supplementing those small labels with QR codes is a great way to provide additional information about the park's exhibits without the expense and maintenance of signage. In these cases and others, QR codes are very useful.
|
||||
|
||||
In searching the internet for an easy, open source way to create QR codes, I discovered [Zint][2]. Zint is an excellent open source (GPLv3.0) solution for generating barcodes. According to the project's [GitHub repository][3], "Zint is a suite of programs to allow easy encoding of data in any of a wide range of public domain barcode standards and to allow integration of this capability into your own programs."
|
||||
|
||||
Zint supports more than 50 types of barcodes, including QR codes (ISO 18004), that you can easily create, then copy and paste into word processing documents, blogs, wikis, and other digital media. People can scan these QR codes with their smartphones to quickly link to information.
|
||||
|
||||
### Install Zint
|
||||
|
||||
Zint is available for Linux, macOS, and Windows.
|
||||
|
||||
You can install the Zint command with Apt on Ubuntu-based Linux distributions:
|
||||
|
||||
|
||||
```
|
||||
`$ sudo apt install zint`
|
||||
```
|
||||
|
||||
I also wanted a graphical user interface (GUI), so I installed Zint-QT:
|
||||
|
||||
|
||||
```
|
||||
`$ sudo apt install zint-qt`
|
||||
```
|
||||
|
||||
Consult the manual's [installation section][4] for macOS and Windows instructions.
|
||||
|
||||
### Generate QR codes with Zint
|
||||
|
||||
Once I installed the application, I launched it and created my first QR code, which is a link to Opensource.com.
|
||||
|
||||
![Generating QR code with Zint][5]
|
||||
|
||||
(Don Watkins, [CC BY-SA 4.0][6])
|
||||
|
||||
Zint's 50-plus other barcode options include postal codes for many countries, DotCode, EAN, EAN-14, and Universal Product Code (UPC). The [project documentation][2] contains a complete list of all the codes it can render.
|
||||
|
||||
You can copy any barcode as a BMP or SVG or save the output as an image file in any size you need for your application. This is my QR code at 77x77 pixels.
|
||||
|
||||
![QR code][7]
|
||||
|
||||
(Don Watkins, [CC BY-SA 4.0][6])
|
||||
|
||||
The project maintains an excellent user manual with instructions for using Zint on the [command line][8] and in the [GUI][9]. You can even try Zint [online][10]. For feature requests or bug reports, [visit the site][11] or [send an email][12].
|
||||
|
||||
--------------------------------------------------------------------------------
|
||||
|
||||
via: https://opensource.com/article/21/2/zint-barcode-generator
|
||||
|
||||
作者:[Don Watkins][a]
|
||||
选题:[lujun9972][b]
|
||||
译者:[译者ID](https://github.com/译者ID)
|
||||
校对:[校对者ID](https://github.com/校对者ID)
|
||||
|
||||
本文由 [LCTT](https://github.com/LCTT/TranslateProject) 原创编译,[Linux中国](https://linux.cn/) 荣誉推出
|
||||
|
||||
[a]: https://opensource.com/users/don-watkins
|
||||
[b]: https://github.com/lujun9972
|
||||
[1]: https://opensource.com/sites/default/files/styles/image-full-size/public/lead-images/wfh_work_home_laptop_work.png?itok=VFwToeMy (Working from home at a laptop)
|
||||
[2]: http://www.zint.org.uk/
|
||||
[3]: https://github.com/zint/zint
|
||||
[4]: http://www.zint.org.uk/Manual.aspx?type=p&page=2
|
||||
[5]: https://opensource.com/sites/default/files/uploads/zintqrcode_generation.png (Generating QR code with Zint)
|
||||
[6]: https://creativecommons.org/licenses/by-sa/4.0/
|
||||
[7]: https://opensource.com/sites/default/files/uploads/zintqrcode_77px.png (QR code)
|
||||
[8]: http://zint.org.uk/Manual.aspx?type=p&page=4
|
||||
[9]: http://zint.org.uk/Manual.aspx?type=p&page=3
|
||||
[10]: http://www.barcode-generator.org/
|
||||
[11]: https://lists.sourceforge.net/lists/listinfo/zint-barcode
|
||||
[12]: mailto:zint-barcode@lists.sourceforge.net
|
@ -1,5 +1,5 @@
|
||||
[#]: collector: (lujun9972)
|
||||
[#]: translator: ( )
|
||||
[#]: translator: (geekpi)
|
||||
[#]: reviewer: ( )
|
||||
[#]: publisher: ( )
|
||||
[#]: url: ( )
|
||||
|
@ -0,0 +1,507 @@
|
||||
[#]: collector: (lujun9972)
|
||||
[#]: translator: ( )
|
||||
[#]: reviewer: ( )
|
||||
[#]: publisher: ( )
|
||||
[#]: url: ( )
|
||||
[#]: subject: (A guide to understanding Linux software libraries in C)
|
||||
[#]: via: (https://opensource.com/article/21/2/linux-software-libraries)
|
||||
[#]: author: (Marty Kalin https://opensource.com/users/mkalindepauledu)
|
||||
|
||||
A guide to understanding Linux software libraries in C
|
||||
======
|
||||
Software libraries are an easy and sensible way to reuse code.
|
||||
![5 pengiuns floating on iceburg][1]
|
||||
|
||||
Software libraries are a longstanding, easy, and sensible way to reuse code. This article explains how to build libraries from scratch and make them available to clients. Although the two sample libraries target Linux, the steps for creating, publishing, and using these libraries apply to other Unix-like systems.
|
||||
|
||||
The sample libraries are written in C, which is well suited to the task. The Linux kernel is written mostly in C with the rest in assembly language. (The same goes for Windows and Linux cousins such as macOS.) The standard system libraries for input/output, networking, string processing, mathematics, security, data encoding, and so on are likewise written mainly in C. To write a library in C is therefore to write in Linux's native language. Moreover, C sets the mark for performance among high-level languages.
|
||||
|
||||
There are also two sample clients (one in C, the other in Python) to access the libraries. It's no surprise that a C client can access a library written in C, but the Python client illustrates that a library written in C can serve clients from other languages.
|
||||
|
||||
### Static versus dynamic libraries
|
||||
|
||||
Linux systems have two types of libraries:
|
||||
|
||||
* A **static library (aka library archive)** is baked into a statically compiled client (e.g., one in C or Rust) during the compilation process' link phase. In effect, each client gets its own copy of the library. A significant downside of a static library comes to the fore if the library needs to be revised (for example, to fix a bug), as each library client must be relinked to the static library. A dynamic library, described next, avoids this shortcoming.
|
||||
* A **dynamic (aka shared) library** is flagged during a statically compiled client program's link phase, but the client program and the library code remain otherwise unconnected until runtime—the library code is not baked into the client. At runtime, the system's dynamic loader connects a shared library with an executing client, regardless of whether the client comes from a statically compiled language, such as C, or a dynamically compiled language, such as Python. As a result, a dynamic library can be updated without inconveniencing clients. Finally, multiple clients can share a single copy of a dynamic library.
|
||||
|
||||
|
||||
|
||||
In general, dynamic libraries are preferred over static ones, although there is a cost in complexity and performance. Here's how each library type is created and published:
|
||||
|
||||
1. The source code for the library is compiled into one or more object modules, which are binary files that can be included in a library and linked to executable clients.
|
||||
2. The object modules are packaged into a single file. For a static library, the standard extension is `.a` for "archive." For a dynamic library, the extension is `.so` for "shared object." The two sample libraries, which have the same functionality, are published as the files `libprimes.a` (static) and `libshprimes.so` (dynamic). The prefix `lib` is used for both types of library.
|
||||
3. The library file is copied to a standard directory so that client programs, without fuss, can access the library. A typical location for the library, whether static or dynamic, is `/usr/lib` or `/usr/local/lib`; other locations are possible.
|
||||
|
||||
|
||||
|
||||
Detailed steps for building and publishing each type of library are coming shortly. First, however, I will introduce the C functions in the two libraries.
|
||||
|
||||
### The sample library functions
|
||||
|
||||
The two sample libraries are built from the same five C functions, four of which are accessible to client programs. The fifth function, which is a utility for one of the other four, shows how C supports hiding information. The source code for each function is short enough that the functions can be housed in a single source file, although multiple source files (e.g., one per each of the four published functions) is an option.
|
||||
|
||||
The library functions are elementary and deal, in various ways, with prime numbers. All of the functions expect unsigned (i.e., non-negative) integer values as arguments:
|
||||
|
||||
* The `is_prime` function tests whether its single argument is a prime.
|
||||
* The `are_coprimes` function checks whether its two arguments have a greatest common divisor (gcd) of 1, which defines co-primes.
|
||||
* The `prime_factors` function lists the prime factors of its argument.
|
||||
* The `goldbach` function expects an even integer value of 4 or more, listing whichever two primes sum to this argument; there may be multiple summing pairs. The function is named after the 18th-century mathematician [Christian Goldbach][2], whose conjecture that every even integer greater than two is the sum of two primes remains one of the oldest unsolved problems in number theory.
|
||||
|
||||
|
||||
|
||||
The utility function `gcd` resides in the deployed library files, but this function is not accessible outside of its containing file; hence, a library client cannot directly invoke the `gcd` function. A closer look at C functions clarifies the point.
|
||||
|
||||
### More on C functions
|
||||
|
||||
Every function in C has a storage class, which determines the function's scope. For functions there are two options:
|
||||
|
||||
* The default storage class for functions is `extern`, which gives a function global scope. A client program can call any `extern` function in the sample libraries. Here's the definition for the function `are_coprimes` with an explicit `extern`: [code] extern unsigned are_coprimes(unsigned n1, unsigned n2) {
|
||||
...
|
||||
}
|
||||
```
|
||||
* The storage class `static` limits a function's scope to the file in which the function is defined. In the sample libraries, the utility function `gcd` is `static`: [code] static unsigned gcd(unsigned n1, unsigned n2) {
|
||||
...
|
||||
}
|
||||
```
|
||||
|
||||
|
||||
|
||||
Only functions within the `primes.c` file can invoke `gcd`, and only the function `are_coprimes` does so. When the static and dynamic libraries are built and published, other programs can call an `extern` function such as `are_coprimes` but not the `static` function `gcd`. The `static` storage class thus hides the `gcd` function from library clients by limiting the function's scope to the other library functions.
|
||||
|
||||
The functions other than `gcd` in the `primes.c` file need not specify a storage class, which would default to `extern`. However, it is common in libraries to make the `extern` explicit.
|
||||
|
||||
C distinguishes between function definitions and declarations, which is important for libraries. Let's start with definitions. C has named functions only, and every function is defined with:
|
||||
|
||||
* A unique name. No two functions in a program can have the same name.
|
||||
* An argument list, which may be empty. The arguments are typed.
|
||||
* Either a return value type (e.g., `int` for a 32-bit signed integer) or `void` if there is no value returned.
|
||||
* A body enclosed in curly braces. In a contrived example, the body could be empty.
|
||||
|
||||
|
||||
|
||||
Every function in a program must be defined exactly once.
|
||||
|
||||
Here's the full definition for the library function `are_coprimes`:
|
||||
|
||||
|
||||
```
|
||||
extern unsigned are_coprimes(unsigned n1, unsigned n2) { /* definition */
|
||||
return 1 == gcd(n1, n2); /* greatest common divisor of 1? */
|
||||
}
|
||||
```
|
||||
|
||||
The function returns a boolean value (either 0 for false or 1 for true), depending on whether the two integer arguments have a greatest common divisor of 1. The utility function `gcd` computes the greatest common divisor of integer arguments `n1` and `n2`.
|
||||
|
||||
A function declaration, unlike a definition, does not have a body:
|
||||
|
||||
|
||||
```
|
||||
`extern unsigned are_coprimes(unsigned n1, unsigned n2); /* declaration */`
|
||||
```
|
||||
|
||||
The declaration ends with a semicolon after the argument list; there are no curly braces enclosing a body. A function may be declared multiple times within a program.
|
||||
|
||||
Why are declarations needed at all? In C, a called function must be visible to its caller. There are various ways to provide such visibility, depending on how fussy the compiler is. One sure way is to define the called function above its caller when both reside in the same file:
|
||||
|
||||
|
||||
```
|
||||
void f() {...} /* f is defined before being called */
|
||||
void g() { f(); } /* ok */
|
||||
```
|
||||
|
||||
The definition of function `f` could be moved below the call from function `g` if `f` were declared above the call:
|
||||
|
||||
|
||||
```
|
||||
void f(); /* declaration makes f visible to caller */
|
||||
void g() { f(); } /* ok */
|
||||
void f() {...} /* easier to put this above the call from g */
|
||||
```
|
||||
|
||||
But what if the called function resides in a different file than its caller? How are functions defined in one file made visible in another file, given that each function must be defined exactly once in a program?
|
||||
|
||||
This issue impacts libraries, whether static or dynamic. For example, the functions in the two primes libraries are defined in the source file `primes.c`, binary copies of which are in each library; but these defined functions must be visible to a library client in C, which is a separate program with its own source file(s).
|
||||
|
||||
Providing visibility across files is what function declarations can do. For the "primes" examples, there is a header file named `primes.h` that declares the four functions to be made visible to library clients in C:
|
||||
|
||||
|
||||
```
|
||||
/** header file primes.h: function declarations **/
|
||||
extern unsigned is_prime(unsigned);
|
||||
extern void prime_factors(unsigned);
|
||||
extern unsigned are_coprimes(unsigned, unsigned);
|
||||
extern void goldbach(unsigned);
|
||||
```
|
||||
|
||||
These declarations serve as an interface by specifying the invocation syntax for each function.
|
||||
|
||||
For client convenience, the text file `primes.h` could be stored in a directory on the C compiler's search path. Typical locations are `/usr/include` and `/usr/local/include`. A C client would `#include` this header file near the top of the client's source code. (A header file is thus imported into the "head" of another source file.) C header files also serve as input to utilities (e.g., Rust's `bindgen`) that enable clients in other languages to access a C library.
|
||||
|
||||
In summary, a library function is defined exactly once but declared wherever needed; any library client in C needs the declaration. A header file should contain function declarations—but not function definitions. If a header file did contain definitions, the file might be included multiple times in a C program, thereby breaking the rule that a function must be defined exactly once in a C program.
|
||||
|
||||
### The library source code
|
||||
|
||||
Below is the source code for two libraries. This code, the header file, and the two sample clients are available on my [website][3].
|
||||
|
||||
**The library functions**
|
||||
|
||||
|
||||
```
|
||||
#include <stdio.h>
|
||||
#include <math.h>
|
||||
|
||||
extern unsigned is_prime(unsigned n) {
|
||||
if (n <= 3) return n > 1; /* 2 and 3 are prime */
|
||||
if (0 == (n % 2) || 0 == (n % 3)) return 0; /* multiples of 2 or 3 aren't */
|
||||
|
||||
/* check that n is not a multiple of other values < n */
|
||||
unsigned i;
|
||||
for (i = 5; (i * i) <= n; i += 6)
|
||||
if (0 == (n % i) || 0 == (n % (i + 2))) return 0; /* not prime */
|
||||
|
||||
return 1; /* a prime other than 2 or 3 */
|
||||
}
|
||||
|
||||
extern void prime_factors(unsigned n) {
|
||||
/* list 2s in n's prime factorization */
|
||||
while (0 == (n % 2)) {
|
||||
[printf][4]("%i ", 2);
|
||||
n /= 2;
|
||||
}
|
||||
|
||||
/* 2s are done, the divisor is now odd */
|
||||
unsigned i;
|
||||
for (i = 3; i <= [sqrt][5](n); i += 2) {
|
||||
while (0 == (n % i)) {
|
||||
[printf][4]("%i ", i);
|
||||
n /= i;
|
||||
}
|
||||
}
|
||||
|
||||
/* one more prime factor? */
|
||||
if (n > 2) [printf][4]("%i", n);
|
||||
}
|
||||
|
||||
/* utility function: greatest common divisor */
|
||||
static unsigned gcd(unsigned n1, unsigned n2) {
|
||||
while (n1 != 0) {
|
||||
unsigned n3 = n1;
|
||||
n1 = n2 % n1;
|
||||
n2 = n3;
|
||||
}
|
||||
return n2;
|
||||
}
|
||||
|
||||
extern unsigned are_coprimes(unsigned n1, unsigned n2) {
|
||||
return 1 == gcd(n1, n2);
|
||||
}
|
||||
|
||||
extern void goldbach(unsigned n) {
|
||||
/* input errors */
|
||||
if ((n <= 2) || ((n & 0x01) > 0)) {
|
||||
[printf][4]("Number must be > 2 and even: %i is not.\n", n);
|
||||
return;
|
||||
}
|
||||
|
||||
/* two simple cases: 4 and 6 */
|
||||
if ((4 == n) || (6 == n)) {
|
||||
[printf][4]("%i = %i + %i\n", n, n / 2, n / 2);
|
||||
return;
|
||||
}
|
||||
|
||||
/* for n >= 8: multiple possibilities for many */
|
||||
unsigned i;
|
||||
for (i = 3; i < (n / 2); i++) {
|
||||
if (is_prime(i) && is_prime(n - i)) {
|
||||
[printf][4]("%i = %i + %i\n", n, i, n - i);
|
||||
/* if one pair is enough, replace this with break */
|
||||
}
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
These functions serve as grist for the library mill. The two libraries derive from exactly the same source code, and the header file `primes.h` is the C interface for both libraries.
|
||||
|
||||
### Building the libraries
|
||||
|
||||
The steps for building and publishing static and dynamic libraries differ in a few details. Only three steps are required for the static library and just two more for the dynamic library. The additional steps in building the dynamic library reflect the added flexibility of the dynamic approach. Let's start with the static library.
|
||||
|
||||
The library source file `primes.c` is compiled into an object module. Here's the command, with the percent sign as the system prompt (double sharp signs introduce my comments):
|
||||
|
||||
|
||||
```
|
||||
`% gcc -c primes.c ## step 1 static`
|
||||
```
|
||||
|
||||
This produces the binary file `primes.o`, the object module. The flag `-c` means compile only.
|
||||
|
||||
The next step is to archive the object module(s) by using the Linux `ar` utility:
|
||||
|
||||
|
||||
```
|
||||
`% ar -cvq libprimes.a primes.o ## step 2 static`
|
||||
```
|
||||
|
||||
The three flags `-cvq` are short for "create," "verbose," and "quick append" (in case new files must be added to an archive). Recall that the prefix `lib` is standard, but the library name is arbitrary. Of course, the file name for a library must be unique to avoid conflicts.
|
||||
|
||||
The archive is ready to be published:
|
||||
|
||||
|
||||
```
|
||||
`% sudo cp libprimes.a /usr/local/lib ## step 3 static`
|
||||
```
|
||||
|
||||
The static library is now accessible to clients, examples of which are forthcoming. (The `sudo` is included to ensure the correct access rights for copying a file into `/usr/local/lib`.)
|
||||
|
||||
The dynamic library also requires one or more object modules for packaging:
|
||||
|
||||
|
||||
```
|
||||
`% gcc primes.c -c -fpic ## step 1 dynamic`
|
||||
```
|
||||
|
||||
The added flag `-fpic` directs the compiler to generate position-independent code, which is a binary module that need not be loaded into a fixed memory location. Such flexibility is critical in a system of multiple dynamic libraries. The resulting object module is slightly larger than the one generated for the static library.
|
||||
|
||||
Here's the command to create the single library file from the object module(s):
|
||||
|
||||
|
||||
```
|
||||
`% gcc -shared -Wl,-soname,libshprimes.so -o libshprimes.so.1 primes.o ## step 2 dynamic`
|
||||
```
|
||||
|
||||
The flag `-shared` indicates that the library is shared (dynamic) rather than static. The `-Wl` flag introduces a list of compiler options, the first of which sets the dynamic library's `soname`, which is required. The `soname` first specifies the library's logical name (`libshprimes.so`) and then, following the `-o` flag, the library's physical file name (`libshprimes.so.1`). The goal to is keep the logical name constant while allowing the physical file name to change with new versions. In this example, the 1 at the end of the physical file name `libshprimes.so.1` represents the first version of the library. The logical and physical file names could be the same, but best practice is to have separate names. A client accesses the library through its logical name (in this case, `libshprimes.so`), as I will clarify shortly.
|
||||
|
||||
The next step is to make the shared library easily accessible to clients by copying it to the appropriate directory; for example, `/usr/local/lib again:`
|
||||
|
||||
|
||||
```
|
||||
`% sudo cp libshprimes.so.1 /usr/local/lib ## step 3 dynamic`
|
||||
```
|
||||
|
||||
A symbolic link is now set up between the shared library's logical name (`libshprimes.so`) and its full physical file name (`/usr/local/lib/libshprimes.so.1`). It's easiest to give the command with `/usr/local/lib` as the working directory:
|
||||
|
||||
|
||||
```
|
||||
`% sudo ln --symbolic libshprimes.so.1 libshprimes.so ## step 4 dynamic`
|
||||
```
|
||||
|
||||
The logical name `libshprimes.so` should not change, but the target of the symbolic link (`libshrimes.so.1`) can be updated as needed for new library implementations that fix bugs, boost performance, and so on.
|
||||
|
||||
The final step (a precautionary one) is to invoke the `ldconfig` utility, which configures the system's dynamic loader. This configuration ensures that the loader will find the newly published library:
|
||||
|
||||
|
||||
```
|
||||
`% sudo ldconfig ## step 5 dynamic`
|
||||
```
|
||||
|
||||
The dynamic library is now ready for clients, including the two sample ones that follow.
|
||||
|
||||
### A C library client
|
||||
|
||||
The sample C client is the program tester, whose source code begins with two `#include` directives:
|
||||
|
||||
|
||||
```
|
||||
#include <stdio.h> /* standard input/output functions */
|
||||
#include <primes.h> /* my library functions */
|
||||
```
|
||||
|
||||
The angle brackets around the file names indicate that these header files are to be found on the compiler's search path (in the case of `primes.h`, the directory `/usr/local/include`). Without this `#include`, the compiler would complain about missing declarations for functions such as `is_prime` and `prime_factors`, which are published in both libraries. By the way, the source code for the tester program need not change at all to test each of the two libraries.
|
||||
|
||||
By contrast, the source file for the library (`primes.c`) opens with these `#include` directives:
|
||||
|
||||
|
||||
```
|
||||
#include <stdio.h>
|
||||
#include <math.h>
|
||||
```
|
||||
|
||||
The header file `math.h` is required because the library function `prime_factors` calls the mathematics function `sqrt` in the standard library `libm.so`.
|
||||
|
||||
For reference, here is the source code for the tester program:
|
||||
|
||||
**The tester program**
|
||||
|
||||
|
||||
```
|
||||
#include <stdio.h>
|
||||
#include <primes.h>
|
||||
|
||||
int main() {
|
||||
/* is_prime */
|
||||
[printf][4]("\nis_prime\n");
|
||||
unsigned i, count = 0, n = 1000;
|
||||
for (i = 1; i <= n; i++) {
|
||||
if (is_prime(i)) {
|
||||
count++;
|
||||
if (1 == (i % 100)) [printf][4]("Sample prime ending in 1: %i\n", i);
|
||||
}
|
||||
}
|
||||
[printf][4]("%i primes in range of 1 to a thousand.\n", count);
|
||||
|
||||
/* prime_factors */
|
||||
[printf][4]("\nprime_factors\n");
|
||||
[printf][4]("prime factors of 12: ");
|
||||
prime_factors(12);
|
||||
[printf][4]("\n");
|
||||
|
||||
[printf][4]("prime factors of 13: ");
|
||||
prime_factors(13);
|
||||
[printf][4]("\n");
|
||||
|
||||
[printf][4]("prime factors of 876,512,779: ");
|
||||
prime_factors(876512779);
|
||||
[printf][4]("\n");
|
||||
|
||||
/* are_coprimes */
|
||||
[printf][4]("\nare_coprime\n");
|
||||
[printf][4]("Are %i and %i coprime? %s\n",
|
||||
21, 22, are_coprimes(21, 22) ? "yes" : "no");
|
||||
[printf][4]("Are %i and %i coprime? %s\n",
|
||||
21, 24, are_coprimes(21, 24) ? "yes" : "no");
|
||||
|
||||
/* goldbach */
|
||||
[printf][4]("\ngoldbach\n");
|
||||
goldbach(11); /* error */
|
||||
goldbach(4); /* small one */
|
||||
goldbach(6); /* another */
|
||||
for (i = 100; i <= 150; i += 2) goldbach(i);
|
||||
|
||||
return 0;
|
||||
}
|
||||
```
|
||||
|
||||
In compiling `tester.c` into an executable, the tricky part is the order of the link flags. Recall that the two sample libraries begin with the prefix `lib`, and each has the usual extension: `.a` for the static library `libprimes.a` and `.so` for the dynamic library `libshprimes.so`. In a links specification, the prefix `lib` and the extension fall away. A link flag begins with `-l` (lowercase L), and a compilation command may contain many link flags. Here is the full compilation command for the tester program, using the dynamic library as the example:
|
||||
|
||||
|
||||
```
|
||||
`% gcc -o tester tester.c -lshprimes -lm`
|
||||
```
|
||||
|
||||
The first link flag identifies the library `libshprimes.so` and the second link flag identifies the standard mathematics library `libm.so`.
|
||||
|
||||
The linker is lazy, which means that the order of the link flags matters. For example, reversing the order of the link specifications generates a compile-time error:
|
||||
|
||||
|
||||
```
|
||||
`% gcc -o tester tester.c -lm -lshprimes ## danger!`
|
||||
```
|
||||
|
||||
The flag that links to `libm.so` comes first, but no function from this library is invoked explicitly in the tester program; hence, the linker does not link to the `math.so` library. The call to the `sqrt` library function occurs only in the `prime_factors` function that is now contained in the `libshprimes.so` library. The resulting error in compiling the tester program is:
|
||||
|
||||
|
||||
```
|
||||
`primes.c: undefined reference to 'sqrt'`
|
||||
```
|
||||
|
||||
Accordingly, the order of the link flags should notify the linker that the `sqrt` function is needed:
|
||||
|
||||
|
||||
```
|
||||
`% gcc -o tester tester.c -lshprimes -lm ## -lshprimes 1st`
|
||||
```
|
||||
|
||||
The linker picks up the call to the library function `sqrt` in the `libshprimes.so` library and, therefore, does the appropriate link to the mathematics library `libm.so`. There is a more complicated option for linking that supports either link-flag order; in this case, however, the easy way is to arrange the link flags appropriately.
|
||||
|
||||
Here is some output from a run of the tester client:
|
||||
|
||||
|
||||
```
|
||||
is_prime
|
||||
Sample prime ending in 1: 101
|
||||
Sample prime ending in 1: 401
|
||||
...
|
||||
168 primes in range of 1 to a thousand.
|
||||
|
||||
prime_factors
|
||||
prime factors of 12: 2 2 3
|
||||
prime factors of 13: 13
|
||||
prime factors of 876,512,779: 211 4154089
|
||||
|
||||
are_coprime
|
||||
Are 21 and 22 coprime? yes
|
||||
Are 21 and 24 coprime? no
|
||||
|
||||
goldbach
|
||||
Number must be > 2 and even: 11 is not.
|
||||
4 = 2 + 2
|
||||
6 = 3 + 3
|
||||
...
|
||||
32 = 3 + 29
|
||||
32 = 13 + 19
|
||||
...
|
||||
100 = 3 + 97
|
||||
100 = 11 + 89
|
||||
...
|
||||
```
|
||||
|
||||
For the `goldbach` function, even a relatively small even value (e.g., 18) may have multiple pairs of primes that sum to it (in this case, 5+13 and 7+11). Such multiple prime pairs are among the factors that complicate an attempted proof of Goldbach's conjecture.
|
||||
|
||||
### Wrapping up with a Python client
|
||||
|
||||
Python, unlike C, is not a statically compiled language, which means that the sample Python client must access the dynamic rather than the static version of the primes library. To do so, Python has various modules (standard and third-party) that support a foreign function interface (FFI), which allows a program written in one language to invoke functions written in another. Python `ctypes` is a standard and relatively simple FFI that enables Python code to call C functions.
|
||||
|
||||
Any FFI has challenges because the interfacing languages are unlikely to have exactly the same data types. For example, the primes library uses the C type `unsigned int`, which Python does not have; the `ctypes` FFI maps a C `unsigned int` to a Python `int`. Of the four `extern` C functions published in the primes library, two behave better in Python with explicit `ctypes` configuration.
|
||||
|
||||
The C functions `prime_factors` and `goldbach` have `void` instead of a return type, but `ctypes` by default replaces the C `void` with the Python `int`. When called from Python code, the two C functions then return a random (hence, meaningless) integer value from the stack. However, `ctypes` can be configured to have the functions return `None` (Python's null type) instead. Here's the configuration for the `prime_factors` function:
|
||||
|
||||
|
||||
```
|
||||
`primes.prime_factors.restype = None`
|
||||
```
|
||||
|
||||
A similar statement handles the `goldbach` function.
|
||||
|
||||
The interactive session below (in Python 3) shows that the interface between a Python client and the primes library is straightforward:
|
||||
|
||||
|
||||
```
|
||||
>>> from ctypes import cdll
|
||||
|
||||
>>> primes = cdll.LoadLibrary("libshprimes.so") ## logical name
|
||||
|
||||
>>> primes.is_prime(13)
|
||||
1
|
||||
>>> primes.is_prime(12)
|
||||
0
|
||||
|
||||
>>> primes.are_coprimes(8, 24)
|
||||
0
|
||||
>>> primes.are_coprimes(8, 25)
|
||||
1
|
||||
|
||||
>>> primes.prime_factors.restype = None
|
||||
>>> primes.goldbach.restype = None
|
||||
|
||||
>>> primes.prime_factors(72)
|
||||
2 2 2 3 3
|
||||
|
||||
>>> primes.goldbach(32)
|
||||
32 = 3 + 29
|
||||
32 = 13 + 19
|
||||
```
|
||||
|
||||
The functions in the primes library use only a simple data type, `unsigned int`. If this C library used complicated types such as structures, and if pointers to structures were passed to and returned from library functions, then an FFI more powerful than `ctypes` might be better for a smooth interface between Python and C. Nonetheless, the `ctypes` example shows that a Python client can use a library written in C. Indeed, the popular NumPy library for scientific computing is written in C and then exposed in a high-level Python API.
|
||||
|
||||
The simple primes library and the advanced NumPy library underscore that C remains the lingua franca among programming languages. Almost every language can talk to C—and, through C, to any other language that talks to C. Python talks easily to C and, as another example, Java may do the same when [Project Panama][6] becomes an alternative to Java Native Interface (JNI).
|
||||
|
||||
--------------------------------------------------------------------------------
|
||||
|
||||
via: https://opensource.com/article/21/2/linux-software-libraries
|
||||
|
||||
作者:[Marty Kalin][a]
|
||||
选题:[lujun9972][b]
|
||||
译者:[译者ID](https://github.com/译者ID)
|
||||
校对:[校对者ID](https://github.com/校对者ID)
|
||||
|
||||
本文由 [LCTT](https://github.com/LCTT/TranslateProject) 原创编译,[Linux中国](https://linux.cn/) 荣誉推出
|
||||
|
||||
[a]: https://opensource.com/users/mkalindepauledu
|
||||
[b]: https://github.com/lujun9972
|
||||
[1]: https://opensource.com/sites/default/files/styles/image-full-size/public/lead-images/rh_003499_01_linux31x_cc.png?itok=Pvim4U-B (5 pengiuns floating on iceburg)
|
||||
[2]: https://en.wikipedia.org/wiki/Christian_Goldbach
|
||||
[3]: https://condor.depaul.edu/mkalin
|
||||
[4]: http://www.opengroup.org/onlinepubs/009695399/functions/printf.html
|
||||
[5]: http://www.opengroup.org/onlinepubs/009695399/functions/sqrt.html
|
||||
[6]: https://openjdk.java.net/projects/panama
|
272
sources/tech/20210204 A hands-on tutorial of SQLite3.md
Normal file
272
sources/tech/20210204 A hands-on tutorial of SQLite3.md
Normal file
@ -0,0 +1,272 @@
|
||||
[#]: collector: (lujun9972)
|
||||
[#]: translator: ( )
|
||||
[#]: reviewer: ( )
|
||||
[#]: publisher: ( )
|
||||
[#]: url: ( )
|
||||
[#]: subject: (A hands-on tutorial of SQLite3)
|
||||
[#]: via: (https://opensource.com/article/21/2/sqlite3-cheat-sheet)
|
||||
[#]: author: (Klaatu https://opensource.com/users/klaatu)
|
||||
|
||||
A hands-on tutorial of SQLite3
|
||||
======
|
||||
Get started with this incredibly powerful and common database. Download
|
||||
the SQLite cheat sheet.
|
||||
![Cheat Sheet cover image][1]
|
||||
|
||||
Applications very often save data. Whether your users create simple text documents, complex graphic layouts, game progress, or an intricate list of customers and order numbers, software usually implies that data is being generated. There are many ways to store data for repeated use. You can dump text to configuration formats such as INI, [YAML][2], XML, or JSON, you can write out raw binary data, or you can store data in a structured database. SQLite is a self-contained, lightweight database that makes it easy to create, parse, query, modify, and transport data.
|
||||
|
||||
**[Download our [SQLite3 cheat sheet][3]]**
|
||||
|
||||
SQLite has been dedicated to the [public domain][4], which [technically means it is not copyrighted and therefore requires no license][5]. Should you require a license, you can [purchase a Warranty of Title][6]. SQLite is immensely common, with an estimated 1 _trillion_ SQLite databases in active use. That's counting multiple databases on every Android and iOS device, every macOS and Windows 10 computer, most Linux systems, within every Webkit-based web browser, modern TV sets, automotive multimedia systems, and countless other software applications.
|
||||
|
||||
In summary, it's a reliable and simple system to use for storing and organizing data.
|
||||
|
||||
### Installing
|
||||
|
||||
You probably already have SQLite libraries on your system, but you need its command-line tools installed to use it directly. On Linux, you probably already have these tools installed. The command provided by the tools is **sqlite3** (not just **sqlite**).
|
||||
|
||||
If you don't have SQLite installed on Linux or BSD, you can install it from your software repository or ports tree, or [download and install it][7] from source code or as a compiled binary.
|
||||
|
||||
On macOS or Windows, you can download and install SQLite tools from [sqlite.org][7].
|
||||
|
||||
### Using SQLite
|
||||
|
||||
It's common to interact with a database through a programming language. For this reason, there are SQLite interfaces (or "bindings") for Java, Python, Lua, PHP, Ruby, C++, and many many others. However, before using these libraries, it helps to understand what's actually happening with the database engine and why your choice of a database is significant. This article introduces you to SQLite and the **sqlite3** command so you can get familiar with the basics of how this database handles data.
|
||||
|
||||
### Interacting with SQLite
|
||||
|
||||
You can interact with SQLite using the **sqlite3** command. This command provides an interactive shell so you can view and update your databases.
|
||||
|
||||
|
||||
```
|
||||
$ sqlite3
|
||||
SQLite version 3.34.0 2020-12-01 16:14:00
|
||||
Enter ".help" for usage hints.
|
||||
Connected to a transient in-memory database.
|
||||
Use ".open FILENAME" to reopen on a persistent database.
|
||||
sqlite>
|
||||
```
|
||||
|
||||
The command places you in an SQLite subshell, and so your prompt is now an SQLite prompt. Your usual Bash commands don't work here. You must use SQLite commands. To see a list of SQLite commands, type **.help**:
|
||||
|
||||
|
||||
```
|
||||
sqlite> .help
|
||||
.archive ... Manage SQL archives
|
||||
.auth ON|OFF SHOW authorizer callbacks
|
||||
.backup ?DB? FILE Backup DB (DEFAULT "main") TO FILE
|
||||
.bail ON|off Stop after hitting an error. DEFAULT OFF
|
||||
.binary ON|off Turn BINARY output ON OR off. DEFAULT OFF
|
||||
.cd DIRECTORY CHANGE the working directory TO DIRECTORY
|
||||
[...]
|
||||
```
|
||||
|
||||
Some of these commands are binary, while others require unique arguments (like filenames, paths, etc.). These are administrative commands for your SQLite shell and are not database queries. Databases take queries in Structured Query Language (SQL), and many SQLite queries are the same as what you may already know from the [MySQL][8] and [MariaDB][9] databases. However, data types and functions differ, so pay close attention to minor differences if you're familiar with another database.
|
||||
|
||||
### Creating a database
|
||||
|
||||
When launching SQLite, you can either open a prompt in memory, or you can select a database to open:
|
||||
|
||||
|
||||
```
|
||||
`$ sqlite3 mydatabase.db`
|
||||
```
|
||||
|
||||
If you have no database yet, you can create one at the SQLite prompt:
|
||||
|
||||
|
||||
```
|
||||
`sqlite> .open mydatabase.db`
|
||||
```
|
||||
|
||||
You now have an empty file on your hard drive, ready to be used as an SQLite database. The file extension **.db** is arbitrary. You can also use **.sqlite**, or whatever you want.
|
||||
|
||||
### Creating a table
|
||||
|
||||
Databases contain _tables_, which can be visualized as a spreadsheet. There's a series of rows (called _records_ in a database) and columns. The intersection of a row and a column is called a _field_.
|
||||
|
||||
The Structured Query Language (SQL) is named after what it provides: A method to inquire about the contents of a database in a predictable and consistent syntax to receive useful results. SQL reads a lot like an ordinary English sentence, if a little robotic. Currently, your database is empty, devoid of any tables.
|
||||
|
||||
You can create a table with the **CREATE** query. It's useful to combine this with the **IF NOT EXISTS** statement, which prevents SQLite from clobbering an existing table.
|
||||
|
||||
You can't create an empty table in SQLite, so before trying a **CREATE** statement, you must think about what kind of data you anticipate the table will store. In this example, I'll create a table called _member_ with these columns:
|
||||
|
||||
* A unique identifier
|
||||
* A person's name
|
||||
* The date and time of data entry
|
||||
|
||||
|
||||
|
||||
#### Unique ID
|
||||
|
||||
It's always good to refer to a record by a unique number, and luckily SQLite recognizes this and does it automatically for you in a column called **rowid**.
|
||||
|
||||
No SQL statement is required to create this field.
|
||||
|
||||
#### Data types
|
||||
|
||||
For my example table, I'm creating a _name_ column to hold **TEXT** data. To prevent a record from being created without data in a specified field, you can add the **NOT NULL** directive.
|
||||
|
||||
The SQL to create this field is: **name TEXT NOT NULL**
|
||||
|
||||
There are five data types (actually _storage classes_) in SQLite:
|
||||
|
||||
* TEXT: a text string
|
||||
* INTEGER: a whole number
|
||||
* REAL: a floating point (unlimited decimal places) number
|
||||
* BLOB: binary data (for instance, a .jpeg or .webp image)
|
||||
* NULL: a null value
|
||||
|
||||
|
||||
|
||||
#### Date and time stamp
|
||||
|
||||
SQLite includes a convenient date and timestamp function. It is not a data type itself but a function in SQLite that generates either a string or integer, depending on your desired format. In this example, I left it as the default.
|
||||
|
||||
The SQL to create this field is: **datestamp DATETIME DEFAULT CURRENT_TIMESTAMP**
|
||||
|
||||
### Table creation SQL
|
||||
|
||||
The full SQL for creating this example table in SQLite:
|
||||
|
||||
|
||||
```
|
||||
sqlite> CREATE TABLE
|
||||
...> IF NOT EXISTS
|
||||
...> member (name TEXT NOT NULL,
|
||||
...> datestamp DATETIME DEFAULT CURRENT_TIMESTAMP
|
||||
```
|
||||
|
||||
In this code sample, I pressed **Return** after the logical clauses of the statement to make it easier to read. SQLite won't run your command unless it terminates with a semi-colon (**;**).
|
||||
|
||||
You can verify that the table has been created with the SQLite command **.tables**:
|
||||
|
||||
|
||||
```
|
||||
sqlite> .tables
|
||||
member
|
||||
```
|
||||
|
||||
### View all columns in a table
|
||||
|
||||
You can verify what columns and rows a table contains with the **PRAGMA** statement:
|
||||
|
||||
|
||||
```
|
||||
sqlite> PRAGMA table_info(member);
|
||||
0|name|TEXT|1||0
|
||||
1|datestamp|CURRENT_TIMESTAMP|0||0
|
||||
```
|
||||
|
||||
### Data entry
|
||||
|
||||
You can populate your new table with some sample data by using the **INSERT** SQL keyword:
|
||||
|
||||
|
||||
```
|
||||
> INSERT INTO member (name) VALUES ('Alice');
|
||||
> INSERT INTO member (name) VALUES ('Bob');
|
||||
> INSERT INTO member (name) VALUES ('Carol');
|
||||
> INSERT INTO member (name) VALUES ('David');
|
||||
```
|
||||
|
||||
Verify the data in the table:
|
||||
|
||||
|
||||
```
|
||||
> SELECT * FROM member;
|
||||
Alice|2020-12-15 22:39:00
|
||||
Bob|2020-12-15 22:39:02
|
||||
Carol|2020-12-15 22:39:05
|
||||
David|2020-12-15 22:39:07
|
||||
```
|
||||
|
||||
#### Adding multiple rows at once
|
||||
|
||||
Now create a second table:
|
||||
|
||||
|
||||
```
|
||||
> CREATE TABLE IF NOT EXISTS linux (
|
||||
...> distro TEXT NOT NULL)
|
||||
```
|
||||
|
||||
Populate it with some sample data, this time using a little **VALUES** shortcut so you can add multiple rows in just one command. The **VALUES** keyword expects a list in parentheses but can take multiple lists separated by commas:
|
||||
|
||||
|
||||
```
|
||||
> INSERT INTO linux (distro)
|
||||
...> VALUES ('Slackware'), ('RHEL'),
|
||||
...> ('Fedora'),('Debian');
|
||||
```
|
||||
|
||||
### Altering a table
|
||||
|
||||
You now have two tables, but as yet, there's no relationship between the two. They each contain independent data, but possibly you might need to associate a member of the first table to a specific item listed in the second.
|
||||
|
||||
To do that, you can create a new column for the first table that corresponds to something in the second. Because both tables were designed with unique identifiers (automatically, thanks to SQLite), the easiest way to connect them is to use the **rowid** field of one as a selector for the other.
|
||||
|
||||
Create a new column in the first table to represent a value in the second table:
|
||||
|
||||
|
||||
```
|
||||
`> ALTER TABLE member ADD os INT;`
|
||||
```
|
||||
|
||||
Using the unique IDs of the **linux** table, assign a distribution to each member. Because the records already exist, you use the **UPDATE** SQL keyword rather than **INSERT**. Specifically, you want to select one row and then update the value of one column. Syntactically, this is expressed a little in reverse, with the update happening first and the selection matching last:
|
||||
|
||||
|
||||
```
|
||||
`> UPDATE member SET os=1 WHERE name='Alice';`
|
||||
```
|
||||
|
||||
Repeat this process for the other names in the **member** table, just to populate it with data. For variety, assign three different distributions across the four rows (doubling up on one).
|
||||
|
||||
### Joining tables
|
||||
|
||||
Now that these two tables relate to one another, you can use SQL to display the associated data. There are many kinds of _joins_ in databases, but you can try them all once you know the basics. Here's a basic join to correlate the values found in the **os** field of the **member** table to the **id** field of the **linux** table:
|
||||
|
||||
|
||||
```
|
||||
> SELECT * FROM member INNER JOIN linux ON member.os=linux.rowid;
|
||||
Alice|2020-12-15 22:39:00|1|Slackware
|
||||
Bob|2020-12-15 22:39:02|3|Fedora
|
||||
Carol|2020-12-15 22:39:05|3|Fedora
|
||||
David|2020-12-15 22:39:07|4|Debian
|
||||
```
|
||||
|
||||
The **os** and **id** fields form the join.
|
||||
|
||||
In a graphical application, you can imagine that the **os** field might be set by a drop-down menu, the values for which are drawn from the contents of the **distro** field of the **linux** table. By using separate tables for unique but related sets of data, you ensure the consistency and validity of data, and thanks to SQL, you can associate them dynamically later.
|
||||
|
||||
### Learning more
|
||||
|
||||
SQLite is an infinitely useful self-contained, portable, open source database. Learning to use it interactively is a great first step toward managing it for web applications or using it through programming language libraries.
|
||||
|
||||
If you enjoy SQLite, you might also try [Fossil][10] by the same author, Dr. Richard Hipp.
|
||||
|
||||
As you learn and use SQLite, it may help to have a list of common commands nearby, so download our **[SQLite3 cheat sheet][3]** today!
|
||||
|
||||
--------------------------------------------------------------------------------
|
||||
|
||||
via: https://opensource.com/article/21/2/sqlite3-cheat-sheet
|
||||
|
||||
作者:[Klaatu][a]
|
||||
选题:[lujun9972][b]
|
||||
译者:[译者ID](https://github.com/译者ID)
|
||||
校对:[校对者ID](https://github.com/校对者ID)
|
||||
|
||||
本文由 [LCTT](https://github.com/LCTT/TranslateProject) 原创编译,[Linux中国](https://linux.cn/) 荣誉推出
|
||||
|
||||
[a]: https://opensource.com/users/klaatu
|
||||
[b]: https://github.com/lujun9972
|
||||
[1]: https://opensource.com/sites/default/files/styles/image-full-size/public/lead-images/coverimage_cheat_sheet.png?itok=lYkNKieP (Cheat Sheet cover image)
|
||||
[2]: https://www.redhat.com/sysadmin/yaml-beginners
|
||||
[3]: https://opensource.com/downloads/sqlite-cheat-sheet
|
||||
[4]: https://sqlite.org/copyright.html
|
||||
[5]: https://directory.fsf.org/wiki/License:PublicDomain
|
||||
[6]: https://www.sqlite.org/purchase/license?
|
||||
[7]: https://www.sqlite.org/download.html
|
||||
[8]: https://www.mysql.com/
|
||||
[9]: https://mariadb.org/
|
||||
[10]: https://opensource.com/article/20/11/fossil
|
@ -0,0 +1,106 @@
|
||||
[#]: collector: (lujun9972)
|
||||
[#]: translator: ( )
|
||||
[#]: reviewer: ( )
|
||||
[#]: publisher: ( )
|
||||
[#]: url: ( )
|
||||
[#]: subject: (Get started with distributed tracing using Grafana Tempo)
|
||||
[#]: via: (https://opensource.com/article/21/2/tempo-distributed-tracing)
|
||||
[#]: author: (Annanay Agarwal https://opensource.com/users/annanayagarwal)
|
||||
|
||||
Get started with distributed tracing using Grafana Tempo
|
||||
======
|
||||
Grafana Tempo is a new open source, high-volume distributed tracing
|
||||
backend.
|
||||
![Computer laptop in space][1]
|
||||
|
||||
Grafana's [Tempo][2] is an easy-to-use, high-scale, distributed tracing backend from Grafana Labs. Tempo has integrations with [Grafana][3], [Prometheus][4], and [Loki][5] and requires only object storage to operate, making it cost-efficient and easy to operate.
|
||||
|
||||
I've been involved with this open source project since its inception, so I'll go over some of the basics about Tempo and show why the cloud-native community has taken notice of it.
|
||||
|
||||
### Distributed tracing
|
||||
|
||||
It's common to want to gather telemetry on requests made to an application. But in the modern server world, a single application is regularly split across many microservices, potentially running on several different nodes.
|
||||
|
||||
Distributed tracing is a way to get fine-grained information about the performance of an application that may consist of discreet services. It provides a consolidated view of the request's lifecycle as it passes through an application. Tempo's distributed tracing can be used with monolithic or microservice applications, and it gives you [request-scoped information][6], making it the third pillar of observability (alongside metrics and logs).
|
||||
|
||||
The following is an example of a Gantt chart that distributed tracing systems can produce about applications. It uses the Jaeger [HotROD][7] demo application to generate traces and stores them in Grafana Cloud's hosted Tempo. This chart shows the processing time for the request, broken down by service and function.
|
||||
|
||||
![Gantt chart from Grafana Tempo][8]
|
||||
|
||||
(Annanay Agarwal, [CC BY-SA 4.0][9])
|
||||
|
||||
### Reducing index size
|
||||
|
||||
Traces have a ton of information in a rich and well-defined data model. Usually, there are two interactions with a tracing backend: filtering for traces using metadata selectors like the service name or duration, and visualizing a trace once it's been filtered.
|
||||
|
||||
To enhance search, most open source distributed tracing frameworks index a number of fields from the trace, including the service name, operation name, tags, and duration. This results in a large index and pushes you to use a database like Elasticsearch or [Cassandra][10]. However, these can be tough to manage and costly to operate at scale, so my team at Grafana Labs set out to come up with a better solution.
|
||||
|
||||
At Grafana, our on-call debugging workflows start with drilling down for the problem using a metrics dashboard (we use [Cortex][11], a Cloud Native Computing Foundation incubating project for scaling Prometheus, to store metrics from our application), sifting through the logs for the problematic service (we store our logs in Loki, which is like Prometheus, but for logs), and then viewing traces for a given request. We realized that all the indexing information we need for the filtering step is available in Cortex and Loki. However, we needed a strong integration for trace discoverability through these tools and a complimentary store for key-value lookup by trace ID.
|
||||
|
||||
This was the start of the [Grafana Tempo][12] project. By focusing on retrieving traces given a trace ID, we designed Tempo to be a minimal-dependency, high-volume, cost-effective distributed tracing backend.
|
||||
|
||||
### Easy to operate and cost-effective
|
||||
|
||||
Tempo uses an object storage backend, which is its only dependency. It can be used in either single binary or microservices mode (check out the [examples][13] in the repo on how to get started easily). Using object storage also means you can store a high volume of traces from applications without any sampling. This ensures that you never throw away traces for those one-in-a-million requests that errored out or had higher latencies.
|
||||
|
||||
### Strong integration with open source tools
|
||||
|
||||
[Grafana 7.3 includes a Tempo data source][14], which means you can visualize traces from Tempo in the Grafana UI. Also, [Loki 2.0's new query features][15] make trace discovery in Tempo easy. And to integrate with Prometheus, the team is working on adding support for exemplars, which are high-cardinality metadata information you can add to time-series data. The metric storage backends do not index these, but you can retrieve and display them alongside the metric value in the Grafana UI. While exemplars can store various metadata, trace-IDs are stored to integrate strongly with Tempo in this use case.
|
||||
|
||||
This example shows using exemplars with a request latency histogram where each exemplar data point links to a trace in Tempo.
|
||||
|
||||
![Using exemplars in Tempo][16]
|
||||
|
||||
(Annanay Agarwal, [CC BY-SA 4.0][9])
|
||||
|
||||
### Consistent metadata
|
||||
|
||||
Telemetry data emitted from applications running as containerized applications generally has some metadata associated with it. This can include information like the cluster ID, namespace, pod IP, etc. This is great for providing on-demand information, but it's even better if you can use the information contained in metadata for something productive.
|
||||
|
||||
For instance, you can use the [Grafana Cloud Agent to ingest traces into Tempo][17], and the agent leverages the Prometheus Service Discovery mechanism to poll the Kubernetes API for metadata information and adds these as tags to spans emitted by the application. Since this metadata is also indexed in Loki, it makes it easy for you to jump from traces to view logs for a given service by translating metadata into Loki label selectors.
|
||||
|
||||
The following is an example of consistent metadata that can be used to view the logs for a given span in a trace in Tempo.
|
||||
|
||||
### ![][18]
|
||||
|
||||
### Cloud-native
|
||||
|
||||
Grafana Tempo is available as a containerized application, and you can run it on any orchestration engine like Kubernetes, Mesos, etc. The various services can be horizontally scaled depending on the workload on the ingest/query path. You can also use cloud-native object storage, such as Google Cloud Storage, Amazon S3, or Azure Blog Storage with Tempo. For further information, read the [architecture section][19] in Tempo's documentation.
|
||||
|
||||
### Try Tempo
|
||||
|
||||
If this sounds like it might be as useful for you as it has been for us, [clone the Tempo repo][20] and give it a try.
|
||||
|
||||
--------------------------------------------------------------------------------
|
||||
|
||||
via: https://opensource.com/article/21/2/tempo-distributed-tracing
|
||||
|
||||
作者:[Annanay Agarwal][a]
|
||||
选题:[lujun9972][b]
|
||||
译者:[译者ID](https://github.com/译者ID)
|
||||
校对:[校对者ID](https://github.com/校对者ID)
|
||||
|
||||
本文由 [LCTT](https://github.com/LCTT/TranslateProject) 原创编译,[Linux中国](https://linux.cn/) 荣誉推出
|
||||
|
||||
[a]: https://opensource.com/users/annanayagarwal
|
||||
[b]: https://github.com/lujun9972
|
||||
[1]: https://opensource.com/sites/default/files/styles/image-full-size/public/lead-images/computer_space_graphic_cosmic.png?itok=wu493YbB (Computer laptop in space)
|
||||
[2]: https://grafana.com/oss/tempo/
|
||||
[3]: http://grafana.com/oss/grafana
|
||||
[4]: https://prometheus.io/
|
||||
[5]: https://grafana.com/oss/loki/
|
||||
[6]: https://peter.bourgon.org/blog/2017/02/21/metrics-tracing-and-logging.html
|
||||
[7]: https://github.com/jaegertracing/jaeger/tree/master/examples/hotrod
|
||||
[8]: https://opensource.com/sites/default/files/uploads/tempo_gantt.png (Gantt chart from Grafana Tempo)
|
||||
[9]: https://creativecommons.org/licenses/by-sa/4.0/
|
||||
[10]: https://opensource.com/article/19/8/how-set-apache-cassandra-cluster
|
||||
[11]: https://cortexmetrics.io/
|
||||
[12]: http://github.com/grafana/tempo
|
||||
[13]: https://grafana.com/docs/tempo/latest/getting-started/example-demo-app/
|
||||
[14]: https://grafana.com/blog/2020/10/29/grafana-7.3-released-support-for-the-grafana-tempo-tracing-system-new-color-palettes-live-updates-for-dashboard-viewers-and-more/
|
||||
[15]: https://grafana.com/blog/2020/11/09/trace-discovery-in-grafana-tempo-using-prometheus-exemplars-loki-2.0-queries-and-more/
|
||||
[16]: https://opensource.com/sites/default/files/uploads/tempo_exemplar.png (Using exemplars in Tempo)
|
||||
[17]: https://grafana.com/blog/2020/11/17/tracing-with-the-grafana-cloud-agent-and-grafana-tempo/
|
||||
[18]: https://lh5.googleusercontent.com/vNqk-ygBOLjKJnCbTbf2P5iyU5Wjv2joR7W-oD7myaP73Mx0KArBI2CTrEDVi04GQHXAXecTUXdkMqKRq8icnXFJ7yWUEpaswB1AOU4wfUuADpRV8pttVtXvTpVVv8_OfnDINgfN
|
||||
[19]: https://grafana.com/docs/tempo/latest/architecture/architecture/
|
||||
[20]: https://github.com/grafana/tempo
|
@ -0,0 +1,128 @@
|
||||
[#]: collector: (lujun9972)
|
||||
[#]: translator: ( )
|
||||
[#]: reviewer: ( )
|
||||
[#]: publisher: ( )
|
||||
[#]: url: ( )
|
||||
[#]: subject: (How to implement business requirements in software development)
|
||||
[#]: via: (https://opensource.com/article/21/2/exceptional-behavior)
|
||||
[#]: author: (Alex Bunardzic https://opensource.com/users/alex-bunardzic)
|
||||
|
||||
How to implement business requirements in software development
|
||||
======
|
||||
Increment your e-commerce app to ensure it implements required business
|
||||
process rules correctly.
|
||||
![Working on a team, busy worklife][1]
|
||||
|
||||
In my previous articles in this series, I explained why tackling coding problems all at once, as if they were hordes of zombies, is a mistake. I'm using a helpful acronym to explain why it's better to approach problems incrementally. **ZOMBIES** stands for:
|
||||
|
||||
**Z** – Zero
|
||||
**O** – One
|
||||
**M** – Many (or more complex)
|
||||
**B** – Boundary behaviors
|
||||
**I** – Interface definition
|
||||
**E** – Exercise exceptional behavior
|
||||
**S** – Simple scenarios, simple solutions
|
||||
|
||||
In the first three articles in this series, I demonstrated the first five **ZOMBIES** principles. The first article [implemented **Z**ero][2], which provides the simplest possible path through your code. The second article performed [tests with **O**ne and **M**any][3] samples, and the third article looked at [**B**oundaries and **I**nterfaces][4]. In this article, I'll take a look at the penultimate letter in our acronym: **E**, which stands for "exercise exceptional behavior."
|
||||
|
||||
### Exceptional behavior in action
|
||||
|
||||
When you write an app like the e-commerce tool in this example, you need to contact product owners or business sponsors to learn if there are any specific business policy rules that need to be implemented.
|
||||
|
||||
Sure enough, as with any e-commerce operation, you want to put business policy rules in place to entice customers to keep buying. Suppose a business policy rule has been communicated that any order with a grand total greater than $500 gets a percentage discount.
|
||||
|
||||
OK, time to roll up your sleeves and craft the executable expectation for this business policy rule:
|
||||
|
||||
|
||||
```
|
||||
[Fact]
|
||||
public void Add2ItemsTotal600GrandTotal540() {
|
||||
var expectedGrandTotal = 540.00;
|
||||
var actualGrandTotal = 0.00;
|
||||
Assert.Equal(expectedGrandTotal, actualGrandTotal);
|
||||
}
|
||||
```
|
||||
|
||||
The confirmation example that encodes the business policy rule states that if the order total is $600.00, the `shoppingAPI` will calculate the grand total to discount it to $540.00. The script above fakes the expectation just to see it fail. Now, make it pass:
|
||||
|
||||
|
||||
```
|
||||
[Fact]
|
||||
public void Add2ItemsTotal600GrandTotal540() {
|
||||
var expectedGrandTotal = 540.00;
|
||||
Hashtable item = [new][5] Hashtable();
|
||||
item.Add("00000001", 200.00);
|
||||
shoppingAPI.AddItem(item);
|
||||
Hashtable item2 = [new][5] Hashtable();
|
||||
item2.Add("00000002", 400.00);
|
||||
shoppingAPI.AddItem(item2);
|
||||
var actualGrandTotal = shoppingAPI.CalculateGrandTotal();
|
||||
Assert.Equal(expectedGrandTotal, actualGrandTotal);
|
||||
}
|
||||
```
|
||||
|
||||
In the confirmation example, you are adding one item priced at $200 and another item priced at $400 for a total of $600 for the order. When you call the `CalculateGrandTotal()` method, you expect to get a total of $540.
|
||||
|
||||
Will this microtest pass?
|
||||
|
||||
|
||||
```
|
||||
[xUnit.net 00:00:00.57] tests.UnitTest1.Add2ItemsTotal600GrandTotal540 [FAIL]
|
||||
X tests.UnitTest1.Add2ItemsTotal600GrandTotal540 [2ms]
|
||||
Error Message:
|
||||
Assert.Equal() Failure
|
||||
Expected: 540
|
||||
Actual: 600
|
||||
[...]
|
||||
```
|
||||
|
||||
Well, it fails miserably. You were expecting $540, but the system calculates $600. Why the error? It's because you haven't taught the system how to calculate the discount on order totals larger than $500 and then subtract that discount from the grand total.
|
||||
|
||||
Implement that processing logic. Judging from the confirmation example above, when the order total is $600.00 (which is greater than the business rule threshold of an order totaling $500), the expected grand total is $540. This means the system needs to subtract $60 from the grand total. And $60 is precisely 10% of $600. So the business policy rule that deals with discounts expects a 10% discount on all order totals greater than $500.
|
||||
|
||||
Implement this processing logic in the `ShippingAPI` class:
|
||||
|
||||
|
||||
```
|
||||
private double Calculate10PercentDiscount(double total) {
|
||||
double discount = 0.00;
|
||||
if(total > 500.00) {
|
||||
discount = (total/100) * 10;
|
||||
}
|
||||
return discount;
|
||||
}
|
||||
```
|
||||
|
||||
First, check to see if the order total is greater than $500. If it is, then calculate 10% of the order total.
|
||||
|
||||
You also need to teach the system how to subtract the calculated 10% from the order grand total. That's a very straightforward change:
|
||||
|
||||
|
||||
```
|
||||
`return grandTotal - Calculate10PercentDiscount(grandTotal);`
|
||||
```
|
||||
|
||||
Now all tests pass, and you're again enjoying steady success. Your script **Exercises exceptional behavior** to implement the required business policy rules.
|
||||
|
||||
### One more to go
|
||||
|
||||
I've taken us to **ZOMBIE** now, so there's just **S** remaining. I'll cover that in the exciting series finale.
|
||||
|
||||
--------------------------------------------------------------------------------
|
||||
|
||||
via: https://opensource.com/article/21/2/exceptional-behavior
|
||||
|
||||
作者:[Alex Bunardzic][a]
|
||||
选题:[lujun9972][b]
|
||||
译者:[译者ID](https://github.com/译者ID)
|
||||
校对:[校对者ID](https://github.com/校对者ID)
|
||||
|
||||
本文由 [LCTT](https://github.com/LCTT/TranslateProject) 原创编译,[Linux中国](https://linux.cn/) 荣誉推出
|
||||
|
||||
[a]: https://opensource.com/users/alex-bunardzic
|
||||
[b]: https://github.com/lujun9972
|
||||
[1]: https://opensource.com/sites/default/files/styles/image-full-size/public/lead-images/team_dev_email_chat_video_work_wfm_desk_520.png?itok=6YtME4Hj (Working on a team, busy worklife)
|
||||
[2]: https://opensource.com/article/21/1/zombies-zero
|
||||
[3]: https://opensource.com/article/21/1/zombies-2-one-many
|
||||
[4]: https://opensource.com/article/21/1/zombies-3-boundaries-interface
|
||||
[5]: http://www.google.com/search?q=new+msdn.microsoft.com
|
@ -1,291 +0,0 @@
|
||||
[#]: collector: "lujun9972"
|
||||
[#]: translator: "amwps290"
|
||||
[#]: reviewer: " "
|
||||
[#]: publisher: " "
|
||||
[#]: url: " "
|
||||
[#]: subject: "Learn JavaScript by writing a guessing game"
|
||||
[#]: via: "https://opensource.com/article/21/1/learn-javascript"
|
||||
[#]: author: "Mandy Kendall https://opensource.com/users/mkendall"
|
||||
|
||||
通过编写猜数游戏学习 JavaScript
|
||||
======
|
||||
|
||||
通过使用一个简单的游戏来练习一些基本的 JavaScript 概念,迈出创建交互,动态 Web 内容的第一步。
|
||||
|
||||
![Javascript code close-up with neon graphic overlay][1]
|
||||
|
||||
可以肯定地说,没有 [JavaScript][2],大多数现代 web 都将不存在。 它是三种标准 Web 技术(以及 HTML 和 CSS )之一,它使任何人都可以创建许多交互式的动态内容,这是我们在使用万维网时所期望的。 从 [React][3] 这样的框架到 [D3][4] 这样的数据可视化库,很难想象没有它的网络。
|
||||
|
||||
现在有很多东西要学习,开始学习这种流行语言的好方法是编写一个简单的应用程序以熟悉某些概念。 最近,Opensource.com 通讯员写了一篇关于如何通过编写简单的猜谜游戏来学习自己喜欢的语言的文章,因此这是一个很好的起点!
|
||||
|
||||
### 现在开始吧
|
||||
|
||||
JavaScript 有许多种风格,但我将从最基本的开始,通常称为 “ Vanilla JavaScript”。 JavaScript 主要是一种客户端脚本语言,因此它可以在任何标准浏览器中运行,而无需安装任何程序。 您只需要一个代码编辑器([Brackets ][5] 就是一个不错的选择)和一个 Web 浏览器。
|
||||
|
||||
### HTML 用户界面
|
||||
|
||||
JavaScript 在 Web 浏览器中运行,并与其他标准 Web 技术 HTML 和 CSS 交互。 要创建此游戏,您首先需要使用 HTML(超文本标记语言)来创建供玩家使用的简单界面。 如果您不熟悉,HTML 是一种标记语言,用于为 Web 内容提供结构。
|
||||
|
||||
首先,先创建一个 HTML 文件。 该文件应具有`.html `扩展名,以使浏览器知道它是 HTML 文档。 您可以将文件命名为 `guessingGame.html`。
|
||||
|
||||
在此文件中使用一些基本的 HTML 标签来显示游戏的标题,玩法说明,供玩家用来输入和提交其猜测的交互式元素以及用于向玩家提供反馈的占位符:
|
||||
|
||||
|
||||
```
|
||||
<!DOCTYPE>
|
||||
<[html][6]>
|
||||
<[head][7]>
|
||||
<[meta][8] charset="UTF-8" />
|
||||
<[title][9]> JavaScript Guessing Game </[title][9]>
|
||||
</[head][7]>
|
||||
<[body][10]>
|
||||
<[h1][11]>Guess the Number!</[h1][11]>
|
||||
<[p][12]>I am thinking of a number between 1 and 100. Can you guess what it is?</[p][12]>
|
||||
|
||||
<[label][13] for="guess">My Guess</[label][13]>
|
||||
<[input][14] type="number" id="guess">
|
||||
<[input][14] type="submit" id="submitGuess" value="Check My Guess">
|
||||
|
||||
<[p][12] id="feedback"></[p][12]>
|
||||
</[body][10]>
|
||||
</[html][6]>
|
||||
```
|
||||
|
||||
<h1> 和 <p> 元素使浏览器知道在页面上显示什么类型的文本。 标签对 `<h1></h1>` 表示标签(`Guess the Number!`)之间的文本是标题。 后面的一组`<p>` 标签表示带有说明的短文本是一个段落。 此代码块末尾的空 `<p>` 标签用作占位符,用于根据用户的输入提供一些反馈。
|
||||
|
||||
### <script> 标签
|
||||
|
||||
在网页中包含 JavaScript 的方法有很多种,但是对于像这样的简短脚本,可以使用一组 `<script>` 标签并将 JavaScript 直接写在 HTML 文件中。 这些 `<script>` 标签应位于 HTML 文件末尾附近的 `</body>` 标签之前。
|
||||
|
||||
现在,您可以开始在这两个脚本标签之间编写 JavaScript。 最终文件如下所示:
|
||||
|
||||
```javascript
|
||||
<!DOCTYPE>
|
||||
<[html][6]>
|
||||
|
||||
<[head][7]>
|
||||
<[meta][8] charset="UTF-8" />
|
||||
<[title][9]> JavaScript Guessing Game </[title][9]>
|
||||
</[head][7]>
|
||||
|
||||
<[body][10]>
|
||||
<[h1][11]>Guess the Number!</[h1][11]>
|
||||
<[p][12]>I am thinking of a number between 1 and 100. Can you guess what it is?</[p][12]>
|
||||
|
||||
<[form][15]>
|
||||
<[label][13] for="guess">My Guess</[label][13]>
|
||||
<[input][14] type="number" id="guess">
|
||||
<[input][14] type="submit" id="submitGuess" value="Check My Guess">
|
||||
</[form][15]>
|
||||
|
||||
<[p][12] id="feedback"></[p][12]>
|
||||
|
||||
<[script][16]>
|
||||
const randomNumber = Math.floor(Math.random() * 100) + 1
|
||||
console.log('Random Number', randomNumber)
|
||||
|
||||
function checkGuess() {
|
||||
let myGuess = guess.value
|
||||
if (myGuess === randomNumber) {
|
||||
feedback.textContent = "You got it right!"
|
||||
} else if (myGuess > randomNumber) {
|
||||
feedback.textContent = "Your guess was " + myGuess + ". That's too high. Try Again!"
|
||||
} else if (myGuess < randomNumber) {
|
||||
feedback.textContent = "Your guess was " + myGuess + ". That's too low. Try Again!"
|
||||
}
|
||||
}
|
||||
submitGuess.addEventListener('click', checkGuess)
|
||||
</[script][16]>
|
||||
|
||||
</[body][10]>
|
||||
|
||||
</[html][6]>
|
||||
```
|
||||
|
||||
要在浏览器中运行此文件,请双击文件或打开您喜欢的浏览器,点击菜单,然后选择**文件->打开文件**。 (如果使用 Brackets 软件,也可以使用角落处的闪电图标在浏览器中打开文件)。
|
||||
### 生成伪随机数
|
||||
|
||||
猜谜游戏的第一步是为玩家生成一个数字供玩家猜测。 JavaScript 包含几个内置的全局对象,可帮助您编写代码。 要生成随机数,请使用 Math 对象。
|
||||
|
||||
JavaScript中的 [Math][17] 具有处理和数学相关的属性和功能。 您将使用两个数学函数来生成随机数,供您的玩家猜测。
|
||||
|
||||
[Math.random()][18],会将生成一个介于 0 和 1 之间的伪随机数。( Math.random 包含 0 但不包含 1。这意味着该函数可以生成 0 ,永远不会产生 1.)
|
||||
|
||||
对于此游戏,请将随机数设置在 1 到 100 之间以缩小玩家的选择范围。 取刚刚生成的小数,然后乘以 100,以产生一个介于 0 到......甚至不是 100 之间的小数。至此,您将需要其他步骤来解决这个问题。
|
||||
|
||||
现在,您的数字仍然是小数,但您希望它是一个整数。 为此,您可以使用属于 Math 对象的另一个函数 [Math.floor()][19]。 Math.floor() 的目的是返回小于或等于您作为参数指定的数字的最大整数,这意味着它会四舍五入为最接近的整数:
|
||||
|
||||
|
||||
```javascript
|
||||
`Math.floor(Math.random() * 100)`
|
||||
```
|
||||
|
||||
这样您将得到 0 到 99 之间的整数,这不是您想要的范围。 您可以在最后一步修复该问题,即在结果中加 1。 瞧! 现在,您有一个(有点)随机生成的数字,介于 1 到 100 之间:
|
||||
|
||||
|
||||
```javascript
|
||||
`Math.floor(Math.random() * 100) + 1`
|
||||
```
|
||||
|
||||
### 变量
|
||||
|
||||
现在,您需要存储随机生成的数字,以便可以将其与玩家的猜测进行比较。 为此,您可以将其存储到一个 **变量**。
|
||||
|
||||
JavaScript 具有不同类型的变量,您可以选择这些类型,具体取决于您要如何使用该变量。 对于此游戏,请使用 const 和 let 。
|
||||
|
||||
* **let** 用于指示变量在整个程序中可以改变
|
||||
* **const** 用于指示变量不应该被修改.
|
||||
|
||||
const 和 let 还有很多要说的,但现在知道这些就足够了。
|
||||
|
||||
随机数在游戏中仅生成一次,因此您将使用 const 变量来保存该值。 您想给变量起一个清楚地表明要存储什么值的名称,因此将其命名为 `randomNumber`:
|
||||
|
||||
|
||||
```javascript
|
||||
`const randomNumber`
|
||||
```
|
||||
|
||||
有关命名的注意事项:JavaScript 中的变量和函数名称以驼峰形式编写。 如果只有一个单词,则全部以小写形式书写。 如果有多个单词,则第一个单词均为小写,其他任何单词均以大写字母开头,且单词之间没有空格。
|
||||
|
||||
### 打印到控制台
|
||||
|
||||
通常,您不想向任何人显示随机数,但是开发人员可能想知道生成的数字以使用它来帮助调试代码。 使用 JavaScript,您可以使用另一个内置函数 [console.log()][20] 将数字输出到浏览器的控制台。
|
||||
|
||||
大多数浏览器都包含开发人员工具,您可以通过按键盘上的 **F12** 键来打开它们。 从那里,您应该看到一个标签为 **控制台** 的标签。 打印到控制台的所有信息都将显示在此处。 由于到目前为止编写的代码将在浏览器加载后立即运行,因此,如果您查看控制台,您应该会看到刚刚生成的随机数! 万岁!
|
||||
|
||||
![Javascript game with console][21]
|
||||
|
||||
### 函数
|
||||
|
||||
接下来,您需要一种方法来从数字输入字段中获得玩家的猜测,将其与您刚刚生成的随机数进行比较,并向玩家提供反馈,让他们知道他们是否正确猜到了。 为此,编写一个函数。 **函数** 是执行一定任务的代码块。 函数是可以重用的,这意味着如果您需要多次运行相同的代码,则可以调用函数,而不必重写执行任务所需的所有步骤。
|
||||
|
||||
根据您使用的 JavaScript 版本,有许多不同的方法来编写或声明函数。 由于这是该语言的基础入门,因此请使用基本函数语法声明函数。
|
||||
|
||||
以关键字 `function` 开头,然后起一个函数名。 好的做法是使用一个描述该函数的功能的名称。 在这个例子中,您正在检查玩家的猜测的数,因此此函数的名字可以是 `checkGuess`。 在函数名称之后,写上一组括号,然后写上一组花括号。 您将在以下花括号之间编写函数的主体:
|
||||
|
||||
|
||||
```
|
||||
`function checkGuess() {}`
|
||||
```
|
||||
|
||||
### 使用 DOM
|
||||
|
||||
JavaScript 的目的之一是与网页上的 HTML 交互。 它通过文档对象模型(DOM)进行此操作,DOM 是 JavaScript 用于访问和更改网页信息的对象。 现在,您需要从 HTML 中获取数字输入字段中玩家的猜测。 您可以使用分配给 HTML 元素的 `id` 属性(在这种情况下为 `guess`)来做到这一点:
|
||||
|
||||
|
||||
```
|
||||
`<input type="number" id="guess">`
|
||||
```
|
||||
|
||||
JavaScript 可以通过访问玩家输入到数字输入字段中的数来获取其值。 您可以通过引用元素的 ID 并在末尾添加 `.value` 来实现。 这次,使用 `let` 定义的变量来保存用户的猜测值:
|
||||
|
||||
```
|
||||
`let myGuess = guess.value`
|
||||
```
|
||||
|
||||
玩家在数字输入字段中输入的任何数字都将被分配给 `checkGuess` 函数中的 `myGuess` 变量。
|
||||
|
||||
### 条件语句
|
||||
下一步是将玩家的猜测与游戏产生的随机数进行比较。 您还想给玩家反馈,让他们知道他们的猜测是太高,太低还是正确。
|
||||
|
||||
您可以使用一系列条件语句来决定玩家将收到的反馈。 **条件语句** 在运行代码块之前检查是否满足条件。 如果不满足条件,则代码停止,继续检查下一个条件,或者继续执行其余代码,而无需执行条件块中的代码:
|
||||
|
||||
```
|
||||
if (myGuess === randomNumber){
|
||||
feedback.textContent = "You got it right!"
|
||||
}
|
||||
else if(myGuess > randomNumber) {
|
||||
feedback.textContent = "Your guess was " + myGuess + ". That's too high. Try Again!"
|
||||
}
|
||||
else if(myGuess < randomNumber) {
|
||||
feedback.textContent = "Your guess was " + myGuess + ". That's too low. Try Again!"
|
||||
}
|
||||
```
|
||||
|
||||
第一个条件块使用比较运算符 `===` 将玩家的猜测与游戏生成的随机数进行比较。 比较运算符检查右侧的值,将其与左侧的值进行比较,如果匹配则返回布尔值 `true` ,否则返回布尔值 `false`。
|
||||
|
||||
如果数字匹配(猜对了!),为了让玩家知道。 通过将文本添加到具有 id 属性 "feedback" 的 `<p>` 标记中来操作 DOM。 就像上面的 `guess.value` 一样工作,除了不是从 DOM 获取信息,而是更改其中的信息。 `<p>` 元素没有像 `<input>` 元素那样的值,而是具有文本,因此请使用 `.textContent` 访问元素并设置要显示的文本:
|
||||
|
||||
|
||||
```
|
||||
`feedback.textContent = "You got it right!"`
|
||||
```
|
||||
|
||||
当然,玩家很有可能在第一次尝试时就猜错了,因此,如果 `myGuess` 和 `randomNumber` 不匹配,请给玩家一个线索,以帮助他们缩小猜测范围。 如果第一个条件失败,则代码将跳过该 if 语句中的代码块,并检查下一个条件是否为 true。 这使您进入 `else if` 块:
|
||||
|
||||
```
|
||||
else if(myGuess > randomNumber) {
|
||||
feedback.textContent = "Your guess was " + myGuess + ". That's too high. Try Again!"
|
||||
}
|
||||
```
|
||||
|
||||
如果您将其作为句子阅读,则可能是这样的:“如果玩家的猜测等于随机数,请让他们知道他们猜对了。否则,请检查玩家的猜测是否大于 `randomNumber`,如果是,则显示玩家的猜测并告诉他们太高了。”
|
||||
|
||||
最后一种可能性是玩家的猜测低于随机数。 要检查这一点,再添加一个 `else if` 块:
|
||||
|
||||
```
|
||||
else if(myGuess < randomNumber) {
|
||||
feedback.textContent = "Your guess was " + myGuess + ". That's too low. Try Again!"
|
||||
}
|
||||
```
|
||||
|
||||
### 用户事件和事件监听器
|
||||
|
||||
如果您看上面的代码,则会看到某些代码在页面加载时自动运行,但有些则不会。 您想在玩游戏之前生成随机数,但是您不想在玩家将数字输入到数字输入字段并准备检查它之前检查其猜测。
|
||||
|
||||
生成随机数并将其打印到控制台的代码不在函数的范围内,因此它将在浏览器加载脚本时自动运行。 但是,要使函数内部的代码运行,您必须对其进行调用。
|
||||
|
||||
调用函数有几种方法。 在此,您希望该函数在用户单击“检查我的猜测”按钮时运行。 单击按钮将创建一个用户事件,然后 JavaScript 可以 “监听” 这个事件,以便知道何时需要运行函数。
|
||||
|
||||
代码的最后一行将事件侦听器添加到按钮上,以在单击按钮时调用函数。 当它“听到”该事件时,它将运行分配给事件侦听器的函数:
|
||||
|
||||
|
||||
```
|
||||
`submitGuess.addEventListener('click', checkGuess)`
|
||||
```
|
||||
|
||||
就像访问 DOM 元素的其他实例一样,您可以使用按钮的 ID 告诉 JavaScript 与哪个元素进行交互。 然后,您可以使用内置的 `addEventListener` 函数来告诉 JavaScript 要监听的事件。
|
||||
|
||||
您已经看到了带有参数的函数,但花点时间看一下它是如何工作的。 参数是函数执行其任务所需的信息。 并非所有函数都需要参数,但是 `addEventListener` 函数需要两个参数。 它采用的第一个参数是将为其侦听的用户事件的名称。 用户可以通过多种方式与 DOM 交互,例如键入,移动鼠标,键盘上的 TAB 键和粘贴文本。 在这种情况下,您正在监听的用户事件是单击按钮,因此第一个参数将是 ` click`。
|
||||
|
||||
`addEventListener`的第二个所需的信息是用户单击按钮时要运行的函数的名称。 这里我们需要 `checkGuess` 函数。
|
||||
|
||||
现在,当玩家按下“检查我的猜测”按钮时,`checkGuess` 函数将获得他们在数字输入字段中输入的值,将其与随机数进行比较,并在浏览器中显示反馈,以使玩家知道他们猜的怎么样。 太棒了! 您的游戏已准备就绪。
|
||||
|
||||
### 学习 JavaScript 以获取乐趣和收益
|
||||
一点点的 Vanilla JavaScript 只是这个庞大的生态系统所提供功能的一小部分。 这是一种值得花时间投入学习的语言,我鼓励您继续挖掘并学习更多。
|
||||
|
||||
--------------------------------------------------------------------------------
|
||||
|
||||
via: https://opensource.com/article/21/1/learn-javascript
|
||||
|
||||
作者:[Mandy Kendall][a]
|
||||
选题:[lujun9972][b]
|
||||
译者:[amwps290](https://github.com/amwps290)
|
||||
校对:[校对者ID](https://github.com/校对者ID)
|
||||
|
||||
本文由 [LCTT](https://github.com/LCTT/TranslateProject) 原创编译,[Linux中国](https://linux.cn/) 荣誉推出
|
||||
|
||||
[a]: https://opensource.com/users/mkendall
|
||||
[b]: https://github.com/lujun9972
|
||||
[1]: https://opensource.com/sites/default/files/styles/image-full-size/public/lead-images/code_javascript.jpg?itok=60evKmGl "Javascript code close-up with neon graphic overlay"
|
||||
[2]: https://opensource.com/tags/javascript
|
||||
[3]: https://opensource.com/article/20/11/reactjs-tutorial
|
||||
[4]: https://opensource.com/article/18/9/open-source-javascript-chart-libraries
|
||||
[5]: https://opensource.com/article/20/12/brackets
|
||||
[6]: http://december.com/html/4/element/html.html
|
||||
[7]: http://december.com/html/4/element/head.html
|
||||
[8]: http://december.com/html/4/element/meta.html
|
||||
[9]: http://december.com/html/4/element/title.html
|
||||
[10]: http://december.com/html/4/element/body.html
|
||||
[11]: http://december.com/html/4/element/h1.html
|
||||
[12]: http://december.com/html/4/element/p.html
|
||||
[13]: http://december.com/html/4/element/label.html
|
||||
[14]: http://december.com/html/4/element/input.html
|
||||
[15]: http://december.com/html/4/element/form.html
|
||||
[16]: http://december.com/html/4/element/script.html
|
||||
[17]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Math
|
||||
[18]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Math/random
|
||||
[19]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Math/floor
|
||||
[20]: https://developer.mozilla.org/en-US/docs/Web/API/Console/log
|
||||
[21]: https://opensource.com/sites/default/files/javascript-game-with-console.png "Javascript game with console"
|
@ -7,31 +7,32 @@
|
||||
[#]: via: "https://opensource.com/article/21/1/ifconfig-ip-linux"
|
||||
[#]: author: "Rajan Bhardwaj https://opensource.com/users/rajabhar"
|
||||
|
||||
Why you need to drop ifconfig for ip
|
||||
|
||||
放弃 ifconfig,拥抱 ip
|
||||
======
|
||||
Start using the modern method for configuring a Linux network interface.
|
||||
开始使用现代方法配置 Linux 网络接口。
|
||||
![Tips and gears turning][1]
|
||||
|
||||
For a long time, the `ifconfig` command was the default method for configuring a network interface. It served Linux users well, but networking is complex, and the commands to configure it must be robust. The `ip` command is the new default networking command for modern systems, and in this article, I'll show you how to use it.
|
||||
在很长一段时间内,`ifconfig` 命令是配置网络接口的默认方法。它为 Linux 用户提供了很好的服务,但是网络很复杂,所以配置网络的命令必须健壮。`ip` 命令是现代系统中新的默认网络命令,在本文中,我将向你展示如何使用它。
|
||||
|
||||
The `ip` command is functionally organized on two layers of the [OSI networking stack][2]: Layer 2 (data link layer) and Layer 3 (network or IP layer). It does all the work in the old `net-tools` package.
|
||||
`ip` 命令工作在 [OSI 网络栈][2] 上:数据链路层和网络(IP)层。它做了之前 `net-tools` 包的所有工作。
|
||||
|
||||
### Installing ip
|
||||
### 安装 ip
|
||||
|
||||
The `ip` command is included in the `iproute2util` package. It's probably already included in your Linux distribution. If it's not, you can install it from your distro's software repository.
|
||||
`ip` 命令包含在 `iproute2util` 包中,它可能已经在你的 Linux 发行版中安装了。如果没有,你可以从发行版的仓库中进行安装。
|
||||
|
||||
### Comparing ipconfig and ip usage
|
||||
### ifconfig 和 ip 使用对比
|
||||
|
||||
The `ip` and `ipconfic` commands can be used to configure a network interface, but they do things differently. I'll compare how to do common tasks with the old (`ipconfig`) and new (`ip`) commands.
|
||||
`ip` 和 `ifconfig` 命令可以用来配置网络接口,但它们做事方法不同。接下来,作为对比,我将用它们来执行一些常见的任务。
|
||||
|
||||
#### View network interface and IP address
|
||||
#### 查看网口和 IP 地址
|
||||
|
||||
If you want to see the IP address of a host or view network interface information, the old `ifconfig` command, with no arguments, provides a good summary:
|
||||
如果你想查看主机的 IP 地址或网络接口信息,`ifconfig` (不带任何参数)命令提供了一个很好的总结。
|
||||
|
||||
|
||||
```
|
||||
$ ifconfig
|
||||
|
||||
|
||||
eth0: flags=4099<UP,BROADCAST,MULTICAST> mtu 1500
|
||||
ether bc:ee:7b:5e:7d:d8 txqueuelen 1000 (Ethernet)
|
||||
RX packets 0 bytes 0 (0.0 B)
|
||||
@ -59,7 +60,7 @@ wlan0: flags=4163<UP,BROADCAST,RUNNING,MULTICAST> mtu 1500
|
||||
TX errors 0 dropped 0 overruns 0 carrier 0 collisions 0
|
||||
```
|
||||
|
||||
The new `ip` command provides similar results, but the command is `ip address show`, or just `ip a` for short:
|
||||
新的 `ip` 命令提供了类似的结果,但命令是 `ip address show`,或者简写为 `ip a`:
|
||||
|
||||
|
||||
```
|
||||
@ -83,118 +84,118 @@ $ ip a
|
||||
valid_lft forever preferred_lft forever
|
||||
```
|
||||
|
||||
#### Add IP address
|
||||
#### 添加 IP 地址
|
||||
|
||||
To add an IP address to an interface with `ifconfig`, the command is:
|
||||
使用 `ifconfig` 命令添加 IP 地址命令为:
|
||||
|
||||
|
||||
```
|
||||
`$ ifconfig eth0 add 192.9.203.21`
|
||||
```bash
|
||||
$ ifconfig eth0 add 192.9.203.21
|
||||
```
|
||||
|
||||
The command is similar for `ip`:
|
||||
`ip` 类似:
|
||||
|
||||
|
||||
```
|
||||
`$ ip address add 192.9.203.21 dev eth0`
|
||||
```bash
|
||||
$ ip address add 192.9.203.21 dev eth0
|
||||
```
|
||||
|
||||
Subcommands in `ip` can be shortened, so this command is equally valid:
|
||||
`ip` 中的子命令可以缩短,所以下面这个命令同样有效:
|
||||
|
||||
|
||||
```
|
||||
`$ ip addr add 192.9.203.21 dev eth0`
|
||||
```bash
|
||||
$ ip addr add 192.9.203.21 dev eth0
|
||||
```
|
||||
|
||||
You can make it even shorter:
|
||||
你甚至可以更短些:
|
||||
|
||||
|
||||
```
|
||||
`$ ip a add 192.9.203.21 dev eth0`
|
||||
```bash
|
||||
$ ip a add 192.9.203.21 dev eth0
|
||||
```
|
||||
|
||||
#### Remove an IP address
|
||||
#### 移除一个 IP 地址
|
||||
|
||||
The inverse of adding an IP address is to remove one.
|
||||
添加 IP 地址与删除 IP 地址正好相反。
|
||||
|
||||
With `ifconfig`, the syntax is:
|
||||
使用 `ifconfig`,命令是:
|
||||
|
||||
|
||||
```
|
||||
`$ ifconfig eth0 del 192.9.203.21`
|
||||
```bash
|
||||
$ ifconfig eth0 del 192.9.203.21
|
||||
```
|
||||
|
||||
The `ip` command syntax is:
|
||||
`ip` 命令的语法是:
|
||||
|
||||
|
||||
```
|
||||
`$ ip a del 192.9.203.21 dev eth0`
|
||||
```bash
|
||||
$ ip a del 192.9.203.21 dev eth0
|
||||
```
|
||||
|
||||
#### Enable or disable multicast
|
||||
#### 启用或禁用组播
|
||||
|
||||
Enabling (or disabling) [multicast][3] on an interface with `ifconfig` happens with the `multicast` argument:
|
||||
使用 `ifconfig` 接口来启用或禁用[组播][3]:
|
||||
|
||||
|
||||
```
|
||||
`# ifconfig eth0 multicast`
|
||||
```bash
|
||||
# ifconfig eth0 multicast
|
||||
```
|
||||
|
||||
With `ip`, use the `set` subcommand with the device (`dev`) and a Boolean or toggle `multicast` option:
|
||||
对于 `ip`,使用 `set` 子命令与设备(`dev`)以及一个布尔值和 `multicast` 选项:
|
||||
|
||||
|
||||
```
|
||||
`# ip link set dev eth0 multicast on`
|
||||
```bash
|
||||
# ip link set dev eth0 multicast on
|
||||
```
|
||||
|
||||
#### Enable or disable a network
|
||||
#### 启用或禁用网络
|
||||
|
||||
Every sysadmin is familiar with the old "turn it off and then on again" trick to fix a problem. In terms of networking interfaces, that translates to bringing a network up or down.
|
||||
每个系统管理员都熟悉“先关闭,然后打开”这个技巧来解决问题。在网络接口上,即打开或关闭网络。
|
||||
|
||||
The `ifconfig` command does this with the `up` or `down` keywords:
|
||||
`ifconfig` 命令使用 `up` 或 `down` 关键字来实现:
|
||||
|
||||
|
||||
```
|
||||
`# ifconfig eth0 up`
|
||||
```bash
|
||||
# ifconfig eth0 up
|
||||
```
|
||||
|
||||
Or you could use a dedicated command:
|
||||
或者你可以使用一个专用命令:
|
||||
|
||||
|
||||
```
|
||||
`# ifup eth0`
|
||||
```bash
|
||||
# ifup eth0
|
||||
```
|
||||
|
||||
The `ip` command uses the `set` subcommand to set the interface to an `up` or `down` state:
|
||||
`ip` 命令使用 `set` 子命令将网络设置为 `up` 或 `down` 状态:
|
||||
|
||||
|
||||
```
|
||||
`# ip link set eth0 up`
|
||||
```bash
|
||||
# ip link set eth0 up
|
||||
```
|
||||
|
||||
#### Enable or disable the Address Resolution Protocol (ARP)
|
||||
#### 开启或关闭地址解析功能(ARP)
|
||||
|
||||
With `ifconfig`, you enable ARP by declaring it:
|
||||
使用 `ifconfig`,你可以通过声明来启用:
|
||||
|
||||
|
||||
```
|
||||
`# ifconfig eth0 arp`
|
||||
```bash
|
||||
# ifconfig eth0 arp
|
||||
```
|
||||
|
||||
With `ip`, you _set_ the `arp` property as `on` or `off`:
|
||||
使用 `ip`,你可以将 `arp` 属性设置为 `on` 或 `off`:
|
||||
|
||||
|
||||
```
|
||||
`# ip link set dev eth0 arp on`
|
||||
```bash
|
||||
# ip link set dev eth0 arp on
|
||||
```
|
||||
|
||||
### Pros and cons of ip and ipconfig
|
||||
### ip 和 ipconfig 的优缺点
|
||||
|
||||
The `ip` command is more versatile and technically more efficient than `ifconfig` because it uses `Netlink` sockets rather than `ioctl` system calls.
|
||||
`ip` 命令比 `ifconfig` 更通用,技术上也更有效,因为它使用的是 `Netlink` 套接字,而不是 `ioctl` 系统调用。
|
||||
|
||||
The `ip` command may appear more verbose and more complex than `ifconfig`, but that's one reason it's more versatile. Once you start using it, you'll get a feel for its internal logic (for instance, using `set` instead of a seemingly arbitrary mix of declarations or settings).
|
||||
`ip` 命令可能看起来比 `ifconfig` 更详细、更复杂,但这是它拥有更多功能的一个原因。一旦你开始使用它,你会了解它的内部逻辑(例如,使用 `set` 而不是看起来随意叠加的声明或设置)。
|
||||
|
||||
Ultimately, `ifconfig` is outdated (for instance, it lacks full support for network namespaces), and `ip` is designed for the modern network. Try it out, learn it, use it. You'll be glad you did!
|
||||
最后,`ifconfig` 已经过时了(例如,它缺乏对网络名称空间的支持),而 `ip` 是为现代网络而生的。尝试并学习它,使用它,你会很感激你使用它的!
|
||||
|
||||
--------------------------------------------------------------------------------
|
||||
|
||||
@ -202,7 +203,7 @@ via: https://opensource.com/article/21/1/ifconfig-ip-linux
|
||||
|
||||
作者:[Rajan Bhardwaj][a]
|
||||
选题:[lujun9972][b]
|
||||
译者:[译者ID](https://github.com/译者ID)
|
||||
译者:[MjSeven](https://github.com/MjSeven)
|
||||
校对:[校对者ID](https://github.com/校对者ID)
|
||||
|
||||
本文由 [LCTT](https://github.com/LCTT/TranslateProject) 原创编译,[Linux中国](https://linux.cn/) 荣誉推出
|
@ -0,0 +1,174 @@
|
||||
[#]: collector: (lujun9972)
|
||||
[#]: translator: (robsean)
|
||||
[#]: reviewer: ( )
|
||||
[#]: publisher: ( )
|
||||
[#]: url: ( )
|
||||
[#]: subject: (How to Run a Shell Script in Linux [Essentials Explained for Beginners])
|
||||
[#]: via: (https://itsfoss.com/run-shell-script-linux/)
|
||||
[#]: author: (Abhishek Prakash https://itsfoss.com/author/abhishek/)
|
||||
|
||||
如何在 Linux 中运行一个 Shell 脚本 [初学者必知]
|
||||
======
|
||||
|
||||
在 linux 中有两种运行 shell 脚本的方法。你可以使用:
|
||||
|
||||
```
|
||||
bash script.sh
|
||||
```
|
||||
|
||||
或者,你可以像这样执行 shell 脚本:
|
||||
|
||||
```
|
||||
./script.sh
|
||||
```
|
||||
|
||||
这可能很简单,它解释不了多少。不要担心,我将使用示例来进行必要的解释,以便你能理解为什么在运行一个 shell 脚本时要使用给定的特定语法格式。
|
||||
|
||||
我将使用这一行 shell 脚本来使需要解释的事情变地尽可能简单:
|
||||
|
||||
```
|
||||
abhishek@itsfoss:~/Scripts$ cat hello.sh
|
||||
|
||||
echo "Hello World!"
|
||||
```
|
||||
|
||||
### 方法 1: 通过将文件作为参数传递给 shell 以运行 shell 脚本
|
||||
|
||||
第一种方法涉及将脚本文件的名称作为参数传递给 shell 。
|
||||
|
||||
考虑到 bash 是默认脚本,你可以像这样运行一个脚本:
|
||||
|
||||
```
|
||||
bash hello.sh
|
||||
```
|
||||
|
||||
你知道这种方法的优点码?**你的脚本没有所需要执行权限**。非常方便快速和简单的任务。
|
||||
|
||||
![在 Linux 中运行一个 Shell 脚本][1]
|
||||
|
||||
如果你还不熟悉,我建议你 [阅读我的 Linux 文件权限详细指南][2] 。
|
||||
|
||||
记住,它需要成为一个 shell 脚本,以便你能将其作为参数传递。一个 shell 脚本是由命令组成的。如果你使用一个普通的文本文件,它将会抱怨错误的命令。
|
||||
|
||||
![运行一个文本文件为脚本][3]
|
||||
|
||||
在这种方法中,**你要明确地具体指定你想使用 bash 作为脚本的解释器** 。
|
||||
|
||||
Shell 只是一个程序,并且 bash 只是 Shell 的一个实施。这里有其它的 shell 程序,像 ksh ,[zsh][4] ,等等。如果你安装有其它的 shell ,你也可以使用它们来代替 bash 。
|
||||
|
||||
例如,我已安装了 zsh ,并使用它来运行相同的脚本:
|
||||
|
||||
![使用 Zsh 来执行 Shell 脚本][5]
|
||||
|
||||
**建议阅读:**
|
||||
|
||||
![][6]
|
||||
|
||||
#### [如何在 Linux 终端中一次运行多个 Linux 命令 [初学者必知提示]][7]
|
||||
|
||||
### 方法 2: 通过具体指定 shell 脚本的路径来执行脚本
|
||||
|
||||
另外一种运行一个 shell 脚本的方法是通过提供它的路径。但是为使这变地可能,你的文件必须是可执行的。否则,当你尝试执行脚本时,你将会得到 “拒绝访问” 错误。
|
||||
|
||||
因此,你首先需要确保你的脚本有可执行权限。你可以 [使用 chmod 命令][8] 来给予你自己脚本的这种权限,像这样:
|
||||
|
||||
```
|
||||
chmod u+x script.sh
|
||||
```
|
||||
|
||||
在你的脚本是可执行的后,你需要的全部工作是输入文件的名称及其绝对路径或相对路径。大多数情况下,你都在同一个目录中,因此你可以像这样使用它:
|
||||
|
||||
```
|
||||
./script.sh
|
||||
```
|
||||
|
||||
如果你不与你的脚本在同一个目录中,你可以具体指定脚本的绝对路径或相对路径:
|
||||
|
||||
![在其它的目录中运行 Shell 脚本][9]
|
||||
|
||||
#### 在脚本前的这个 ./ 是非常重要的。(当你与脚本在同一个目录中)
|
||||
|
||||
![][10]
|
||||
|
||||
为什么当你在同一个目录下,却不能使用脚本名称?这是因为你的 Linux 系统会在 PATH 变量中查找具体指定的几个选定目录中的可执行的文件来运行。
|
||||
|
||||
这里是我的系统的 PATH 变量的值:
|
||||
|
||||
```
|
||||
abhishek@itsfoss:~$ echo $PATH
|
||||
/home/abhishek/.local/bin:/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin:/usr/games:/usr/local/games:/snap/bin
|
||||
```
|
||||
|
||||
这意味着在下面目录中具有可执行权限的任意文件都可以在系统的任何位置运行:
|
||||
|
||||
* /home/abhishek/.local/bin
|
||||
* /usr/local/sbin
|
||||
* /usr/local/bin
|
||||
* /usr/sbin
|
||||
* /usr/bin
|
||||
* /sbin
|
||||
* /bin
|
||||
* /usr/games
|
||||
* /usr/local/games
|
||||
* /snap/bin
|
||||
|
||||
|
||||
|
||||
Linux 命令(像 ls ,cat 等)的二进制文件或可执行文件都位于这些目录中的其中一个目录。这就是为什么你可以在你系统的任何位置通过使用命令的名称来运作这些命令的原因。看看,ls 命令就是位于 /usr/bin 目录中。
|
||||
|
||||
![][11]
|
||||
|
||||
当你使用脚本而不具体指定其绝对路径或相对路径时,系统将不能在 PATH 变量中找到涉及的脚本。
|
||||
|
||||
#### 为什么大多数 shell 脚本在其头部包含 #! /bin/bash ?
|
||||
|
||||
记得我提过 shell 只是一个程序,并且有不同实现的 shell 程序。
|
||||
|
||||
当你使用 #! /bin/bash 时,你是具体指定 bash 作为解释器来运行脚本。如果你不这样做,并且以 ./script.sh 的方式运行一个脚本,它通常会在你正在运行的 shell 中运行。
|
||||
|
||||
有问题吗?可能会有。看看,大多数的 shell 语法是大多数种类的 shell 中通用的,但是有一些语法可能会有所不同。
|
||||
|
||||
例如,在 bash和 zsh 中数组的行为是不同的。在 zsh 中,数组索引是从 1 开始的,而不是从 0 开始。
|
||||
|
||||
![Bash Vs Zsh][12]
|
||||
|
||||
使用 #! /bin/bash 标示该级别是 bash 脚本,并且应该使用bash 作为脚本的解释器来运行,而不受在系统上正在使用的 shell 的影响。如果你使用 zsh 的特殊语法,你可以通过在脚本的第一行添加 #! /bin/zsh 的方式来标示其是 zsh 脚本
|
||||
|
||||
在 #! 和 /bin/bash 之间的空格是没有影响的。你也可以使用 #!/bin/bash 。
|
||||
|
||||
### 它有帮助吗?
|
||||
|
||||
我希望这篇文章能够增加你的 Linux 知识。如果你还有问题或建议,请留下评论。
|
||||
|
||||
专家用户可能依然会挑出我丢失的东西。但是这类初学者话题的问题不容易找到信息和避免过多或过少的细节之间的平衡。
|
||||
|
||||
如果你对学习 bash 脚本感兴趣,在我们的以系统管理为中心的网站 [Linux Handbook][14] 上,我们有一个 [完整的 Bash 初学者系列][13] 。如果你想要,你也可以 [购买附加练习题的电子书][15] ,以支持 Linux Handbook。
|
||||
|
||||
--------------------------------------------------------------------------------
|
||||
|
||||
via: https://itsfoss.com/run-shell-script-linux/
|
||||
|
||||
作者:[Abhishek Prakash][a]
|
||||
选题:[lujun9972][b]
|
||||
译者:[robsean](https://github.com/robsean)
|
||||
校对:[校对者ID](https://github.com/校对者ID)
|
||||
|
||||
本文由 [LCTT](https://github.com/LCTT/TranslateProject) 原创编译,[Linux中国](https://linux.cn/) 荣誉推出
|
||||
|
||||
[a]: https://itsfoss.com/author/abhishek/
|
||||
[b]: https://github.com/lujun9972
|
||||
[1]: https://i0.wp.com/itsfoss.com/wp-content/uploads/2021/01/run-a-shell-script-linux.png?resize=741%2C329&ssl=1
|
||||
[2]: https://linuxhandbook.com/linux-file-permissions/
|
||||
[3]: https://i0.wp.com/itsfoss.com/wp-content/uploads/2021/01/running-text-file-as-script.png?resize=741%2C329&ssl=1
|
||||
[4]: https://www.zsh.org
|
||||
[5]: https://i2.wp.com/itsfoss.com/wp-content/uploads/2021/01/execute-shell-script-with-zsh.png?resize=741%2C253&ssl=1
|
||||
[6]: https://i2.wp.com/itsfoss.com/wp-content/uploads/2020/09/run-multiple-commands-in-linux.png?fit=800%2C450&ssl=1
|
||||
[7]: https://itsfoss.com/run-multiple-commands-linux/
|
||||
[8]: https://linuxhandbook.com/chmod-command/
|
||||
[9]: https://i2.wp.com/itsfoss.com/wp-content/uploads/2021/01/running-shell-script-in-other-directory.png?resize=795%2C272&ssl=1
|
||||
[10]: https://i0.wp.com/itsfoss.com/wp-content/uploads/2021/01/executing-shell-scripts-linux.png?resize=800%2C450&ssl=1
|
||||
[11]: https://i0.wp.com/itsfoss.com/wp-content/uploads/2021/01/locating-command-linux.png?resize=795%2C272&ssl=1
|
||||
[12]: https://i1.wp.com/itsfoss.com/wp-content/uploads/2021/01/bash-vs-zsh.png?resize=795%2C386&ssl=1
|
||||
[13]: https://linuxhandbook.com/tag/bash-beginner/
|
||||
[14]: https://linuxhandbook.com
|
||||
[15]: https://www.buymeacoffee.com/linuxhandbook
|
@ -0,0 +1,85 @@
|
||||
[#]: collector: (lujun9972)
|
||||
[#]: translator: (geekpi)
|
||||
[#]: reviewer: ( )
|
||||
[#]: publisher: ( )
|
||||
[#]: url: ( )
|
||||
[#]: subject: (Generate QR codes with this open source tool)
|
||||
[#]: via: (https://opensource.com/article/21/2/zint-barcode-generator)
|
||||
[#]: author: (Don Watkins https://opensource.com/users/don-watkins)
|
||||
|
||||
用这个开源工具生成二维码
|
||||
======
|
||||
Zint 可以轻松生成 50 多种类型的自定义条码。
|
||||
![Working from home at a laptop][1]
|
||||
|
||||
二维码是一种很好的可以向人们提供信息的方式,且没有打印的麻烦和费用。大多数人的智能手机都支持二维码扫描,无论其操作系统是什么。
|
||||
|
||||
你可能想使用二维码的原因有很多。也许你是一名教师,希望通过补充材料来测试你的学生,以增强学习效果,或者是一家餐厅,需要在遵守社交距离准则的同时提供菜单。我经常行走于自然小径,那里贴有树木和其他植物的标签。用二维码来补充这些小标签是一种很好的方式,它可以提供关于公园展品的额外信息,而无需花费和维护标识牌。在这些和其他情况下,二维码是非常有用的。
|
||||
|
||||
在互联网上搜索一个简单的、开源的方法来创建二维码时,我发现了 [Zint][2]。Zint 是一个优秀的开源 (GPLv3.0) 生成条码的解决方案。根据该项目的 [GitHub 仓库][3]:“Zint 是一套可以方便地对任何一种公共领域条形码标准的数据进行编码的程序,并允许你将这种功能集成到你自己的程序中。”
|
||||
|
||||
Zint 支持 50 多种类型的条形码,包括二维码码 (ISO 18004),你可以轻松地创建这些条形码,然后复制和粘贴到 word 文档、博客、维基和其他数字媒体中。人们可以用智能手机扫描这些二维码,快速链接到信息。
|
||||
|
||||
### 安装 Zint
|
||||
|
||||
Zint 适用于 Linux、macOS 和 Windows。
|
||||
|
||||
你可以在基于 Ubuntu 的 Linux 发行版上使用 apt 安装 Zint 命令:
|
||||
|
||||
|
||||
```
|
||||
`$ sudo apt install zint`
|
||||
```
|
||||
|
||||
我还想要一个图形用户界面 (GUI),所以我安装了 Zint-QT:
|
||||
|
||||
|
||||
```
|
||||
`$ sudo apt install zint-qt`
|
||||
```
|
||||
|
||||
请参考手册的[安装部分][4],了解 macOS 和 Windows 的说明。
|
||||
|
||||
### 用 Zint 生成二维码
|
||||
|
||||
安装好后,我启动了它,并创建了我的第一个二维码,这是一个指向 Opensource.com 的链接。
|
||||
|
||||
![Generating QR code with Zint][5]
|
||||
|
||||
(Don Watkins, [CC BY-SA 4.0][6])
|
||||
|
||||
Zint 的 50 多个其他条码选项包括许多国家的邮政编码、DotCode、EAN、EAN-14 和通用产品代码 (UPC)。[项目文档][2]中包含了它可以渲染的所有代码的完整列表。
|
||||
|
||||
你可以将任何条形码复制为 BMP 或 SVG,或者将输出保存为你应用中所需要的任何尺寸的图像文件。这是我的 77x77 像素的二维码。
|
||||
|
||||
![QR code][7]
|
||||
|
||||
(Don Watkins, [CC BY-SA 4.0][6])
|
||||
|
||||
该项目维护了一份出色的用户手册,其中包含了在[命令行][8]和 [GUI][9] 中使用 Zint 的说明。你甚至可以[在线][10]试用 Zint。对于功能请求或错误报告,请[访问网站][11]或[发送电子邮件][12]。
|
||||
|
||||
--------------------------------------------------------------------------------
|
||||
|
||||
via: https://opensource.com/article/21/2/zint-barcode-generator
|
||||
|
||||
作者:[Don Watkins][a]
|
||||
选题:[lujun9972][b]
|
||||
译者:[geekpi](https://github.com/geekpi)
|
||||
校对:[校对者ID](https://github.com/校对者ID)
|
||||
|
||||
本文由 [LCTT](https://github.com/LCTT/TranslateProject) 原创编译,[Linux中国](https://linux.cn/) 荣誉推出
|
||||
|
||||
[a]: https://opensource.com/users/don-watkins
|
||||
[b]: https://github.com/lujun9972
|
||||
[1]: https://opensource.com/sites/default/files/styles/image-full-size/public/lead-images/wfh_work_home_laptop_work.png?itok=VFwToeMy (Working from home at a laptop)
|
||||
[2]: http://www.zint.org.uk/
|
||||
[3]: https://github.com/zint/zint
|
||||
[4]: http://www.zint.org.uk/Manual.aspx?type=p&page=2
|
||||
[5]: https://opensource.com/sites/default/files/uploads/zintqrcode_generation.png (Generating QR code with Zint)
|
||||
[6]: https://creativecommons.org/licenses/by-sa/4.0/
|
||||
[7]: https://opensource.com/sites/default/files/uploads/zintqrcode_77px.png (QR code)
|
||||
[8]: http://zint.org.uk/Manual.aspx?type=p&page=4
|
||||
[9]: http://zint.org.uk/Manual.aspx?type=p&page=3
|
||||
[10]: http://www.barcode-generator.org/
|
||||
[11]: https://lists.sourceforge.net/lists/listinfo/zint-barcode
|
||||
[12]: mailto:zint-barcode@lists.sourceforge.net
|
Loading…
Reference in New Issue
Block a user