Merge remote-tracking branch 'LCTT/master'

This commit is contained in:
Xingyu Wang 2020-01-13 21:17:14 +08:00
commit f2a6ec341b
8 changed files with 600 additions and 615 deletions

View File

@ -0,0 +1,320 @@
[#]: collector: (lujun9972)
[#]: translator: (lxbwolf)
[#]: reviewer: (wxy)
[#]: publisher: (wxy)
[#]: url: (https://linux.cn/article-11778-1.html)
[#]: subject: (Lessons learned from programming in Go)
[#]: via: (https://opensource.com/article/19/12/go-common-pitfalls)
[#]: author: (Eduardo Ferreira https://opensource.com/users/edufgf)
Go 并发编程中的经验教训
======
> 通过学习如何定位并发处理的陷阱来避免未来处理这些问题时的困境。
![](https://img.linux.net.cn/data/attachment/album/202001/13/150539n217ak1vcf717uzx.jpg)
在复杂的分布式系统进行任务处理时,你通常会需要进行并发的操作。在 [Mode.net][2] 公司,我们每天都要和实时、快速和灵活的软件打交道。而没有一个高度并发的系统,就不可能构建一个毫秒级的动态地路由数据包的全球专用网络。这个动态路由是基于网络状态的,尽管这个过程需要考虑众多因素,但我们的重点是链路指标。在我们的环境中,链路指标可以是任何跟网络链接的状态和当前属性(如链接延迟)有关的任何内容。
### 并发探测链接监控
我们的动态路由算法 [H.A.L.O.][4]<ruby>逐跳自适应链路状态最佳路由<rt>Hop-by-Hop Adaptive Link-State Optimal Routing</rt></ruby>)部分依赖于链路指标来计算路由表。这些指标由位于每个 PoP<ruby>存活节点<rt>Point of Presence</rt></ruby>上的独立组件收集。PoP 是表示我们的网络中单个路由实体的机器,通过链路连接并分布在我们的网络拓扑中的各个位置。某个组件使用网络数据包探测周围的机器,周围的机器回复数据包给前者。从接收到的探测包中可以获得链路延迟。由于每个 PoP 都有不止一个临近节点,所以这种探测任务实质上是并发的:我们需要实时测量每个临近连接点的延迟。我们不能串行地处理;为了计算这个指标,必须尽快处理每个探测。
![latency computation graph][6]
### 序列号和重置:一个重新排列场景
我们的探测组件互相发送和接收数据包,并依靠序列号进行数据包处理。这旨在避免处理重复的包或顺序被打乱的包。我们的第一个实现依靠特殊的序列号 0 来重置序列号。这个数字仅在组件初始化时使用。主要的问题是我们考虑了递增的序列号总是从 0 开始。在该组件重启后,包的顺序可能会重新排列,某个包的序列号可能会轻易地被替换成重置之前使用过的值。这意味着,后继的包都会被忽略掉,直到排到重置之前用到的序列值。
### UDP 握手和有限状态机
这里的问题是该组件重启前后的序列号是否一致。有几种方法可以解决这个问题,经过讨论,我们选择了实现一个带有清晰状态定义的三步握手协议。这个握手过程在初始化时通过链接建立会话。这样可以确保节点通过同一个会话进行通信且使用了适当的序列号。
为了正确实现这个过程,我们必须定义一个有清晰状态和过渡的有限状态机。这样我们就可以正确管理握手过程中的所有极端情况。
![finite state machine diagram][7]
会话 ID 由握手的初始化程序生成。一个完整的交换顺序如下:
1. 发送者发送一个 `SYN(ID)` 数据包。
2. 接收者存储接收到的 `ID` 并发送一个 `SYN-ACK(ID)`
3. 发送者接收到 `SYN-ACK(ID)` 并发送一个 `ACK(ID)`。它还发送一个从序列号 0 开始的数据包。
4. 接收者检查最后接收到的 `ID`,如果 ID 匹配,则接受 `ACK(ID)`。它还开始接受序列号为 0 的数据包。
### 处理状态超时
基本上,每种状态下你都需要处理最多三种类型的事件:链接事件、数据包事件和超时事件。这些事件会并发地出现,因此你必须正确处理并发。
* 链接事件包括网络连接或网络断开的变化,相应的初始化一个链接会话或断开一个已建立的会话。
* 数据包事件是控制数据包(`SYN`/`SYN-ACK`/`ACK`)或只是探测响应。
* 超时事件在当前会话状态的预定超时时间到期后触发。
这里面临的最主要的问题是如何处理并发的超时到期和其他事件。这里很容易陷入死锁和资源竞争的陷阱。
### 第一种方法
本项目使用的语言是 [Golang][8]。它确实提供了原生的同步机制,如自带的通道和锁,并且能够使用轻量级线程来进行并发处理。
![gophers hacking together][9]
*gopher 们聚众狂欢*
首先,你可以设计两个分别表示我们的会话和超时处理程序的结构体。
```go
type Session struct {  
  State SessionState  
  Id SessionId  
  RemoteIp string  
}
type TimeoutHandler struct {  
  callback func(Session)  
  session Session  
  duration int  
  timer *timer.Timer  
}
```
`Session` 标识连接会话,内有表示会话 ID、临近的连接点的 IP 和当前会话状态的字段。
`TimeoutHandler` 包含回调函数、对应的会话、持续时间和指向调度计时器的指针。
每一个临近连接点的会话都包含一个保存调度 `TimeoutHandler` 的全局映射。
```
SessionTimeout map[Session]*TimeoutHandler
```
下面方法注册和取消超时:
```go
// schedules the timeout callback function.  
func (timeout* TimeoutHandler) Register() {  
  timeout.timer = time.AfterFunc(time.Duration(timeout.duration) * time.Second, func() {  
    timeout.callback(timeout.session)  
  })  
}
func (timeout* TimeoutHandler) Cancel() {  
  if timeout.timer == nil {  
    return  
  }  
  timeout.timer.Stop()  
}
```
你可以使用类似下面的方法来创建和存储超时:
```go
func CreateTimeoutHandler(callback func(Session), session Session, duration int) *TimeoutHandler {  
  if sessionTimeout[session] == nil {  
    sessionTimeout[session] := new(TimeoutHandler)  
  }  
   
  timeout = sessionTimeout[session]  
  timeout.session = session  
  timeout.callback = callback  
  timeout.duration = duration  
  return timeout  
}
```
超时处理程序创建后,会在经过了设置的 `duration` 时间(秒)后执行回调函数。然而,有些事件会使你重新调度一个超时处理程序(与 `SYN` 状态时的处理一样,每 3 秒一次)。
为此,你可以让回调函数重新调度一次超时:
```go
func synCallback(session Session) {  
  sendSynPacket(session)
  // reschedules the same callback.  
  newTimeout := NewTimeoutHandler(synCallback, session, SYN_TIMEOUT_DURATION)  
  newTimeout.Register()
  sessionTimeout[state] = newTimeout  
}
```
这次回调在新的超时处理程序中重新调度自己,并更新全局映射 `sessionTimeout`
### 数据竞争和引用
你的解决方案已经有了。可以通过检查计时器到期后超时回调是否执行来进行一个简单的测试。为此,注册一个超时,休眠 `duration` 秒,然后检查是否执行了回调的处理。执行这个测试后,最好取消预定的超时时间(因为它会重新调度),这样才不会在下次测试时产生副作用。
令人惊讶的是,这个简单的测试发现了这个解决方案中的一个问题。使用 `cancel` 方法来取消超时并没有正确处理。以下顺序的事件会导致数据资源竞争:
1. 你有一个已调度的超时处理程序。
2. 线程 1
1. 你接收到一个控制数据包,现在你要取消已注册的超时并切换到下一个会话状态(如发送 `SYN` 后接收到一个 `SYN-ACK`
2. 你调用了 `timeout.Cancel()`,这个函数调用了 `timer.Stop()`请注意Golang 计时器的停止不会终止一个已过期的计时器。)
3. 线程 2
1. 在取消调用之前,计时器已过期,回调即将执行。
2. 执行回调,它调度一次新的超时并更新全局映射。
4. 线程 1
1. 切换到新的会话状态并注册新的超时,更新全局映射。
两个线程并发地更新超时映射。最终结果是你无法取消注册的超时,然后你也会丢失对线程 2 重新调度的超时的引用。这导致处理程序在一段时间内持续执行和重新调度,出现非预期行为。
### 锁也解决不了问题
使用锁也不能完全解决问题。如果你在处理所有事件和执行回调之前加锁,它仍然不能阻止一个过期的回调运行:
```go
func (timeout* TimeoutHandler) Register() {  
  timeout.timer = time.AfterFunc(time.Duration(timeout.duration) * time._Second_, func() {  
    stateLock.Lock()  
    defer stateLock.Unlock()
    timeout.callback(timeout.session)  
  })  
}
```
现在的区别就是全局映射的更新是同步的,但是这还是不能阻止在你调用 `timeout.Cancel()` 后回调的执行 —— 这种情况出现在调度计时器过期了但是还没有拿到锁的时候。你还是会丢失一个已注册的超时的引用。
### 使用取消通道
你可以使用取消通道,而不必依赖不能阻止到期的计时器执行的 golang 函数 `timer.Stop()`
这是一个略有不同的方法。现在你可以不用再通过回调进行递归地重新调度;而是注册一个死循环,这个循环接收到取消信号或超时事件时终止。
新的 `Register()` 产生一个新的 go 线程,这个线程在超时后执行你的回调,并在前一个超时执行后调度新的超时。返回给调用方一个取消通道,用来控制循环的终止。
```go
func (timeout *TimeoutHandler) Register() chan struct{} {  
  cancelChan := make(chan struct{})  
   
  go func () {  
    select {  
    case _ = <- cancelChan:  
      return  
    case _ = <- time.AfterFunc(time.Duration(timeout.duration) * time.Second):  
      func () {  
        stateLock.Lock()  
        defer stateLock.Unlock()
        timeout.callback(timeout.session)  
      } ()  
    }  
  } ()
  return cancelChan  
}
func (timeout* TimeoutHandler) Cancel() {  
  if timeout.cancelChan == nil {  
    return  
  }  
  timeout.cancelChan <- struct{}{}  
}
```
这个方法给你注册的所有超时提供了取消通道。一个取消调用向通道发送一个空结构体并触发取消操作。然而,这并不能解决前面的问题;可能在你通过通道取消之前以及超时线程拿到锁之前,超时时间就已经到了。
这里的解决方案是,在拿到锁**之后**,检查一下超时范围内的取消通道。
```go
  case _ = <- time.AfterFunc(time.Duration(timeout.duration) * time.Second):  
    func () {  
      stateLock.Lock()  
      defer stateLock.Unlock()  
     
      select {  
      case _ = <- handler.cancelChan:  
        return  
      default:  
        timeout.callback(timeout.session)  
      }  
    } ()  
  }
```
最终,这可以确保在拿到锁之后执行回调,不会触发取消操作。
### 小心死锁
这个解决方案看起来有效;但是还是有个隐患:[死锁][10]。
请阅读上面的代码,试着自己找到它。考虑下描述的所有函数的并发调用。
这里的问题在取消通道本身。我们创建的是无缓冲通道,即发送的是阻塞调用。当你在一个超时处理程序中调用取消函数时,只有在该处理程序被取消后才能继续处理。问题出现在,当你有多个调用请求到同一个取消通道时,这时一个取消请求只被处理一次。当多个事件同时取消同一个超时处理程序时,如连接断开或控制包事件,很容易出现这种情况。这会导致死锁,可能会使应用程序停机。
![gophers on a wire, talking][11]
*有人在听吗?*
(已获得 Trevor Forrey 授权。)
这里的解决方案是创建通道时指定缓存大小至少为 1这样向通道发送数据就不会阻塞也显式地使发送变成非阻塞的避免了并发调用。这样可以确保取消操作只发送一次并且不会阻塞后续的取消调用。
```go
func (timeout* TimeoutHandler) Cancel() {  
  if timeout.cancelChan == nil {  
    return  
  }  
   
  select {  
  case timeout.cancelChan <- struct{}{}:  
  default:  
    // cant send on the channel, someone has already requested the cancellation.  
  }  
}
```
### 总结
在实践中你学到了并发操作时出现的常见错误。由于其不确定性,即使进行大量的测试,也不容易发现这些问题。下面是我们在最初的实现中遇到的三个主要问题:
#### 在非同步的情况下更新共享数据
这似乎是个很明显的问题,但如果并发更新发生在不同的位置,就很难发现。结果就是数据竞争,由于一个更新会覆盖另一个,因此对同一数据的多次更新中会有某些更新丢失。在我们的案例中,我们是在同时更新同一个共享映射里的调度超时引用。(有趣的是,如果 Go 检测到在同一个映射对象上的并发读写,会抛出致命错误 — 你可以尝试下运行 Go 的[数据竞争检测器](https://golang.org/doc/articles/race_detector.html))。这最终会导致丢失超时引用,且无法取消给定的超时。当有必要时,永远不要忘记使用锁。
![gopher assembly line][13]
*不要忘记同步 gopher 们的工作*
#### 缺少条件检查
在不能仅依赖锁的独占性的情况下,就需要进行条件检查。我们遇到的场景稍微有点不一样,但是核心思想跟[条件变量][14]是一样的。假设有个一个生产者和多个消费者使用一个共享队列的经典场景,生产者可以将一个元素添加到队列并唤醒所有消费者。这个唤醒调用意味着队列中的数据是可访问的,并且由于队列是共享的,消费者必须通过锁来进行同步访问。每个消费者都可能拿到锁;然而,你仍然需要检查队列中是否有元素。因为在你拿到锁的瞬间并不知道队列的状态,所以还是需要进行条件检查。
在我们的例子中,超时处理程序收到了计时器到期时发出的“唤醒”调用,但是它仍需要检查是否已向其发送了取消信号,然后才能继续执行回调。
![gopher boot camp][15]
*如果你要唤醒多个 gopher可能就需要进行条件检查*
#### 死锁
当一个线程被卡住,无限期地等待一个唤醒信号,但是这个信号永远不会到达时,就会发生这种情况。死锁可以通过让你的整个程序停机来彻底杀死你的应用。
在我们的案例中,这种情况的发生是由于多次发送请求到一个非缓冲且阻塞的通道。这意味着向通道发送数据只有在从这个通道接收完数据后才能返回。我们的超时线程循环迅速从取消通道接收信号;然而,在接收到第一个信号后,它将跳出循环,并且再也不会从这个通道读取数据。其他的调用会一直被卡住。为避免这种情况,你需要仔细检查代码,谨慎处理阻塞调用,并确保不会发生线程饥饿。我们例子中的解决方法是使取消调用成为非阻塞调用 — 我们不需要阻塞调用。
--------------------------------------------------------------------------------
via: https://opensource.com/article/19/12/go-common-pitfalls
作者:[Eduardo Ferreira][a]
选题:[lujun9972][b]
译者:[lxbwolf](https://github.com/lxbwolf)
校对:[wxy](https://github.com/wxy)
本文由 [LCTT](https://github.com/LCTT/TranslateProject) 原创编译,[Linux中国](https://linux.cn/) 荣誉推出
[a]: https://opensource.com/users/edufgf
[b]: https://github.com/lujun9972
[1]: https://opensource.com/sites/default/files/styles/image-full-size/public/lead-images/go-golang.png?itok=OAW9BXny (Goland gopher illustration)
[2]: http://mode.net
[3]: https://en.wikipedia.org/wiki/Metrics_%28networking%29
[4]: https://people.ece.cornell.edu/atang/pub/15/HALO_ToN.pdf
[5]: https://en.wikipedia.org/wiki/Point_of_presence
[6]: https://opensource.com/sites/default/files/uploads/image2_0_3.png (latency computation graph)
[7]: https://opensource.com/sites/default/files/uploads/image3_0.png (finite state machine diagram)
[8]: https://golang.org/
[9]: https://opensource.com/sites/default/files/uploads/image4.png (gophers hacking together)
[10]: https://en.wikipedia.org/wiki/Deadlock
[11]: https://opensource.com/sites/default/files/uploads/image5_0_0.jpg (gophers on a wire, talking)
[12]: https://golang.org/doc/articles/race_detector.html
[13]: https://opensource.com/sites/default/files/uploads/image6.jpeg (gopher assembly line)
[14]: https://en.wikipedia.org/wiki/Monitor_%28synchronization%29#Condition_variables
[15]: https://opensource.com/sites/default/files/uploads/image7.png (gopher boot camp)

View File

@ -1,8 +1,8 @@
[#]: collector: (lujun9972)
[#]: translator: (geekpi)
[#]: reviewer: ( )
[#]: publisher: ( )
[#]: url: ( )
[#]: reviewer: (wxy)
[#]: publisher: (wxy)
[#]: url: (https://linux.cn/article-11776-1.html)
[#]: subject: (Make VLC More Awesome With These Simple Tips)
[#]: via: (https://itsfoss.com/simple-vlc-tips/)
[#]: author: (Ankush Das https://itsfoss.com/author/ankush/)
@ -10,20 +10,21 @@
这些简单的技巧使 VLC 更加出色
======
如果 [VLC][1] 不是最好的播放器,那它是[最好的开源视频播放器][2]之一。大多数人不知道的是,它不仅仅是视频播放器。
![](https://img.linux.net.cn/data/attachment/album/202001/13/090635eu9va9999rr4ue92.jpeg)
如果 [VLC][1] 不是最好的播放器,那它也是[最好的开源视频播放器][2]之一。大多数人不知道的是,它不仅仅是视频播放器。
你可以进行许多复杂的任务,如直播视频、捕捉设备等。只需打开菜单,你就可以看到它有多少选项。
它的 FOSS 页面有一个详细的教程,讨论一些[专业的 VLC 技巧][3],但这些对于普通用户太复杂。
我们有一个详细的教程,讨论一些[专业的 VLC 技巧][3],但这些对于普通用户太复杂。
这就是为什么我写另一篇文章的原因,来向你展示一些可以在 VLC 中使用的简单技巧。
这就是为什么我写另一篇文章的原因,来向你展示一些可以在 VLC 中使用的简单技巧。
### 使用这些简单技巧让 VLC 做更多事
Lets see what can you do with VLC other than just playing a video file.
让我们看看除了播放视频文件之外,你还可以使用 VLC 做什么。
#### 1\. 使用 VLC 观看 YouTube 视频
#### 1使用 VLC 观看 YouTube 视频
![][4]
@ -31,65 +32,60 @@ Lets see what can you do with VLC other than just playing a video file.
是的,在 VLC 上流式传输 YouTube 视频是非常容易的。
只需启动 VLC 播放器,前往媒体设置,然后单击 ”**Open Network Stream**“ 或使用快捷方式 **CTRL + N**
只需启动 VLC 播放器,前往媒体设置,然后单击 ”Open Network Stream“ 或使用快捷方式 `CTRL + N`
![][6]
接下来,你只需要粘贴要观看的视频的 URL。有一些选项可以调整但通常你无需担心这些。如果你好奇你可以点击 ”**Advanced options**“ 来探索。
接下来,你只需要粘贴要观看的视频的 URL。有一些选项可以调整但通常你无需担心这些。如果你好奇你可以点击 ”Advanced options“ 来探索。
你还可以通过这种方式向 YouTube 视频添加字幕。然而,[一个更简单的带字幕观看 Youtube 视频的办法是使用 Penguin 字幕播放器][7]。
#### 2\. 将视频转换为不同格式
#### 2将视频转换为不同格式
![][8]
你可以[在 Linux 命令行使用 ffmpeg 转换视频][9]。你还可以使用图形工具,如 [HandBrake 转换视频格式][10]。
但是,如果你不想用一个单独的应用转码视频,你可以使用 VLC 播放器来完成该工作。
但是,如果你不想用一个单独的应用转码视频,你可以使用 VLC 播放器来完成该工作。
为此,只需点击 VLC 上的媒体选项,然后单击 **Convert/Save**,或者在 VLC 播放器处于活动状态时按下快捷键 CTRL + R。
为此,只需点击 VLC 上的媒体选项,然后单击 “Convert/Save”,或者在 VLC 播放器处于活动状态时按下快捷键 `CTRL + R`。接下来,你需要从计算机/硬盘或者 URL 导入你想保存/转换的的视频
接下来,你需要从计算机/硬盘或者 URL 导入你想保存/转换的的视频。
不管什么来源,只需选择文件后点击 ”**Convert/Save**“ 按钮
你现在会看到另外一个窗口给你更改 ”**Profile**“ 设置。点击并选择你想转换的格式(并保存)。
不管是什么来源,只需选择文件后点击 “Convert/Save” 按钮。你现在会看到另外一个窗口可以更改 “Profile” 设置。点击并选择你想转换的格式(并保存)。
你还可以在转换之前通过在屏幕底部设置目标文件夹来更改转换文件的存储路径。
#### 3\. 从源录制音频/视频
#### 3、从源录制音频/视频
![Vlc Advanced Controls][11]
你是否想在 VLC 播放器中录制正在播放的音频/视频?
如果是的话,有一个简单的解决方案。只需**通过 View然后点击 ”Advanced Controls“**
如果是的话,有一个简单的解决方案。只需通过 “View”然后点击 “Advanced Controls”
完成后,你会看到一个新按钮(包括 VLC 播放器中的红色录制按钮)。
#### 4\. 自动下载字幕
#### 4自动下载字幕
![][12]
是的,你可以[使用 VLC 自动下载字幕][13]。你甚至不必在单独的网站上查找字幕。你只需点击 **View-&gt;VLSub**
是的,你可以[使用 VLC 自动下载字幕][13]。你甚至不必在单独的网站上查找字幕。你只需点击
“View”->“VLSub”。
默认情况下,它是禁用的,因此当你单击该选项时,它会被激活,并允许你搜索/下载想要的字幕。
[VLC 还能让你使用简单的键盘快捷键同步字幕][14]
#### 5\. 截图
#### 5截图
![][15]
你可以在观看视频时使用 VLC 获取一些视频的截图/图像。
你只需在视频播放/暂停时右击播放器,你会看到一组选项,点击 **Video-&gt;Take Snapshot**
你可以在观看视频时使用 VLC 获取一些视频的截图/图像。你只需在视频播放/暂停时右击播放器,你会看到一组选项,点击 “Video”->“Take Snapshot”。
如果安装了旧版本,你可能在右键时看到截图选项。
#### 额外技巧:给视频添加音频/视频效果
在菜单中,进入 ”**Tools**“ 选项。单击 ”**Effects and Filters**“,或者在 VLC 播放器窗口中按 **CTRL + E** 打开选项。
在菜单中,进入 “Tools” 选项。单击 “Effects and Filters”或者在 VLC 播放器窗口中按 `CTRL + E` 打开选项。
好了,你可以观察你给视频添加的音频和视频效果了。你也许无法实时看到效果,因此你需要调整并保存来看发生了什么。
@ -108,7 +104,7 @@ via: https://itsfoss.com/simple-vlc-tips/
作者:[Ankush Das][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/) 荣誉推出

View File

@ -1,165 +0,0 @@
[#]: collector: (lujun9972)
[#]: translator: (geekpi)
[#]: reviewer: ( )
[#]: publisher: ( )
[#]: url: ( )
[#]: subject: (Generating numeric sequences with the Linux seq command)
[#]: via: (https://www.networkworld.com/article/3511954/generating-numeric-sequences-with-the-linux-seq-command.html)
[#]: author: (Sandra Henry-Stocker https://www.networkworld.com/author/Sandra-Henry_Stocker/)
Generating numeric sequences with the Linux seq command
======
The Linux seq command can generate lists of numbers and at lightning speed. It's easy to use and flexible, too.
[Jamie][1] [(CC BY 2.0)][2]
One of the easiest ways to generate a list of numbers in Linux is to use the **seq** (sequence) command. In its simplest form, **seq** will take a single number and then list all the numbers from 1 to that number. For example:
```
$ seq 5
1
2
3
4
5
```
Unless directed otherwise, **seq** always starts with 1. You can start a sequence with a different number by inserting it before the final number.
```
$ seq 3 5
3
4
5
```
### Specifying an increment
You can also specify an increment. Say you want to list multiples of 3. Specify your starting point (first 3 in this example), increment (second 3) and end point (18).
[][3]
BrandPost Sponsored by HPE
[Take the Intelligent Route with Consumption-Based Storage][3]
Combine the agility and economics of HPE storage with HPE GreenLake and run your IT department with efficiency.
```
$ seq 3 3 18
3
6
9
12
15
18
```
You can elect to go from larger to smaller numbers by using a negative increment (i.e., a decrement).
```
$ seq 18 -3 3
18
15
12
9
6
3
```
The **seq** command is also very fast. You can probably generate a list of a million numbers in under 10 seconds.
Advertisement
```
$ time seq 1000000
1
2
3
999998
999999
1000000
real 0m9.290s <== 9+ seconds
user 0m0.020s
sys 0m0.899s
```
## Using a separator
Another very useful option is to use a separator. Instead of listing a single number on each line, you can insert commas, colons or some other characters. The -s option followed by the character you wish to use.
```
$ seq -s: 3 3 18
3:6:9:12:15:18
```
In fact, if you simply want your numbers to be listed on a single line, you can use a blank as your separator in place of the default linefeed.
**[ Also see [Invaluable tips and tricks for troubleshooting Linux][4]. ]**
```
$ seq -s' ' 3 3 18
3 6 9 12 15 18
```
### Getting to the math
It may seem like a big leap to go from generating a sequence of numbers to doing math, but given the right separators, **seq** can easily prepare calculations that you can pass to **bc**. For example:
```
$ seq -s* 5 | bc
120
```
What is going on in this command? Lets take a look. First, **seq** is generating a list of numbers and using * as the separator.
```
$ seq -s* 5
1*2*3*4*5
```
Its then passing the string to the calculator (**bc**) which promptly multiplies the numbers. And you can do a fairly extensive calculation in a fraction of a second.
```
$ time seq -s* 117 | bc
39699371608087208954019596294986306477904063601683223011297484643104\
22041758630649341780708631240196854767624444057168110272995649603642\
560353748940315749184568295424000000000000000000000000000
real 0m0.003s
user 0m0.004s
sys 0m0.000s
```
### Limitations
You only get to choose one separator, so your calculations will be very limited. Use **bc** by itself for more complicated math. In addition, **seq** only works with numbers. To generate a sequence of single letters, use a command like this instead:
```
$ echo {a..g}
a b c d e f g
```
Join the Network World communities on [Facebook][5] and [LinkedIn][6] to comment on topics that are top of mind.
--------------------------------------------------------------------------------
via: https://www.networkworld.com/article/3511954/generating-numeric-sequences-with-the-linux-seq-command.html
作者:[Sandra Henry-Stocker][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://www.networkworld.com/author/Sandra-Henry_Stocker/
[b]: https://github.com/lujun9972
[1]: https://creativecommons.org/licenses/by/2.0/
[2]: https://creativecommons.org/licenses/by/2.0/legalcode
[3]: https://www.networkworld.com/article/3440100/take-the-intelligent-route-with-consumption-based-storage.html?utm_source=IDG&utm_medium=promotions&utm_campaign=HPE21620&utm_content=sidebar ( Take the Intelligent Route with Consumption-Based Storage)
[4]: https://www.networkworld.com/article/3242170/linux/invaluable-tips-and-tricks-for-troubleshooting-linux.html
[5]: https://www.facebook.com/NetworkWorld/
[6]: https://www.linkedin.com/company/network-world

View File

@ -1,5 +1,5 @@
[#]: collector: (lujun9972)
[#]: translator: ( )
[#]: translator: (geekpi)
[#]: reviewer: ( )
[#]: publisher: ( )
[#]: url: ( )

View File

@ -7,28 +7,27 @@
[#]: via: (https://opensource.com/article/19/12/jumping-python-platformer-game)
[#]: author: (Seth Kenlon https://opensource.com/users/seth)
Add jumping to your Python platformer game
为你的 Python 平台类游戏添加跳跃功能
======
Learn how to fight gravity with jumping in this installment on
programming video games with Python's Pygame module.
![Arcade games][1]
在本期使用 Python Pygame 模块编写视频游戏中,学会如何使用跳跃来对抗重力。
![游戏厅中的游戏][1]
In the [previous article][2] in this series, you simulated gravity, but now you need to give your player a way to fight against gravity by jumping.
在本系列的 [前一篇文章][2] 中,你已经模拟了重力。但现在,你需要赋予你的角色跳跃的能力来对抗重力。
A jump is a temporary reprieve from gravity. For a few moments, you jump _up_ instead of falling down, the way gravity is pulling you. But once you hit the peak of your jump, gravity kicks in again and pulls you back down to earth.
跳跃是对重力作用的暂时延缓。在这一小段时间里你是向_上_跳而不是被重力拉着向下落。但你一旦到达了跳跃的最高点重力就会重新发挥作用将你拉回地面。
In code, this translates to variables. First, you must establish variables for the player sprite so that Python can track whether or not the sprite is jumping. Once the player sprite is jumping, then gravity is applied to the player sprite again, pulling it back down to the nearest object.
在代码中,跳跃被表示为变量。首先,你需要为玩家对象建立一个变量,使得 Python 能够跟踪对象是否正在跳跃中。一旦玩家对象开始跳跃,他就会再次受到重力的作用,并被拉回最近的物体。
### Setting jump state variables
### 设置跳跃状态变量
You must add two new variables to your Player class:
你需要为你的 Player 类添加两个新变量:
* One to track whether your player is jumping or not, determined by whether or not your player sprite is standing on solid ground
* One to bring the player back down to the ground
* 一个是为了跟踪你的角色是否正在跳跃中,可通过你的玩家对象是否站在坚实的地面来确定
* 一个是为了将玩家带回地面
Add these variables to your **Player** class. In the following code, the lines above the comment are for context, so just add the final two lines:
将如下两个变量添加到你的 **Player** 类中。在下方的代码中,注释前的部分用于提示上下文,因此只需要添加最后两行:
```
@ -36,20 +35,20 @@ Add these variables to your **Player** class. In the following code, the lines a
                self.movey = 0
                self.frame = 0
                self.health = 10
                # gravity variables here
                # 此处是重力相关变量
                self.collide_delta = 0
                self.jump_delta = 6
```
The first variable (**collide_delta**) is set to 0 because, in its natural state, the player sprite is not in a mid-jump. The other variable (**jump_delta**) is set to 6 to prevent the sprite from bouncing (actually, jumping) when it first lands in the game world. When you've finished this article's examples, try setting it to 0 to see what happens.
第一个变量 **collide_delta** 被设为 0 是因为在正常状态下,玩家对象没有处在跳跃中的状态。另一个变量 **jump_delta** 被设为 6是为了防止对象在第一次进入游戏世界时就发生反弹实际上就是跳跃。当你完成了本篇文章的示例尝试把该变量设为 0 看看会发生什么。
### Colliding mid-jump
### 跳跃中的碰撞
If you jump on a trampoline, your jumps are pretty impressive. But what would happen if you jumped into a wall? (Don't try it to find out!) Your jump, no matter how impressively it started, would end very quickly when you collide with something much larger and much more solid than you.
如果你是跳到一个蹦床上,那你的跳跃一定非常优美。但是如果你是跳向一面墙会发生什么呢?(千万不要去尝试!)不管你的起跳多么令人印象深刻,当你撞到比你更大更硬的物体时,你都会立马停下。(译注:原理参考动量守恒定律)
To mimic this in your video game, you must set the **self.collide_delta** variable to 0 whenever your player sprite collides with something, like the ground. If **self.collide_delta** is anything other than 0, then your player is jumping, and your player can't jump when it hits a wall or the ground.
为了在你的视频游戏中模拟这一点,你需要在你的玩家对象与地面等东西发生碰撞时,将 **self.collide_delta** 变量设为 0。如果你的 **self.collide_delta** 不是 0 而是其它的什么值,那么你的玩家就会发生跳跃,并且当你的玩家与墙或者地面发生碰撞时无法跳跃。
In the **update** function of your **Player** class, modify the ground collision block to look like this:
在你的 **Player** 类的 **update** 方法中,将地面碰撞相关代码块修改为如下所示:
```
@ -57,54 +56,54 @@ In the **update** function of your **Player** class, modify the ground collision
        for g in ground_hit_list:
            self.movey = 0
            self.rect.y = worldy-ty-ty
            self.collide_delta = 0 # stop jumping
            self.collide_delta = 0 # 停止跳跃
            if self.rect.y &gt; g.rect.y:
                self.health -=1
                print(self.health)
```
This code block checks for collisions happening between the ground sprites and the player sprite. In the event of a collision, it sets the Y-position of the player to a value equal to the height of the game window (**worldy**) minus the height of a tile minus the height of another tile (so that the player sprite appears to be standing on top of the ground and not in the middle of it). It also sets **self.collide_delta** to 0 so that the program is aware that the player is not in mid-jump. Additionally, it sets **self.movey** to 0 so that the program is aware that the player is not being pulled by gravity (it's a quirk of game physics that you don't need to continue to pull your player toward earth once the player has been grounded).
这段代码块检查了地面对象和玩家对象之间发生的碰撞。当发生碰撞时,它会将玩家 Y 方向的坐标值设置为游戏窗口的高度减去一个瓷贴的高度再减去另一个瓷贴的高度。以此保证了玩家对象是站在地面**上**,而不是嵌在地面里。同时它也将 **self.collide_delta** 设为 0使得程序能够知道玩家未处在跳跃中。除此之外它将 **self.movey** 设为 0使得程序能够知道玩家当前未受到重力的牵引作用这是游戏物理引擎的奇怪之处一旦玩家落地也就没有必要继续将玩家拉向地面
The **if** statement detects whether the player has descended _below_ the level of the ground; if so, it deducts health points as a penalty. This assumes that you want your player to lose health points for falling off the world. That's not strictly necessary; it's just a common convention in platformers. More than likely, you want this event to trigger something, though, or else your real-world player will be stuck playing a game with a sprite that's fallen off the screen. An easy recovery is to set **self.rect.y** to 0 again so that when the player sprite falls off the world, it respawns at the top of the world and falls back onto solid ground.
此处 **if** 语句用来检测玩家是否已经落到地面之_下_如果是那就扣除一点生命值作为惩罚。此处假定了你希望当你的玩家落到地图之外时失去生命值。这个设定不是必需的它只是平台类游戏的一种惯例。更有可能的是你希望这个事件能够触发另一些事件或者说是一种能够让你的现实世界玩家沉迷于让对象掉到屏幕之外的东西。一种简单的恢复方式是在玩家对象掉落到地图之外时**self.rect.y** 重新设置为 0这样它就会在地图上方重新生成并落到坚实的地面上。
### Hitting the ground
### 撞向地面
Your simulated gravity wants your player's Y-axis movement to be 0 or more. To create a jump, write code that sends your player sprite off solid ground and into the air.
模拟的重力使你玩家的 Y 坐标不断增大(译注:此处原文中为 0但在 Pygame 中越靠下方 Y 坐标应越大)。要实现跳跃,完成如下代码使你的玩家对象离开地面,飞向空中。
In the **update** function of your **Player** class, add a temporary reprieve from gravity:
在你的 **Player** 类的 **update** 方法中,添加如下代码来暂时延缓重力的作用:
```
        if self.collide_delta &lt; 6 and self.jump_delta &lt; 6:
            self.jump_delta = 6*2
            self.movey -= 33  # how high to jump
            self.movey -= 33  # 跳跃的高度
            self.collide_delta += 6
            self.jump_delta    += 6
```
According to this code, a jump sends the player sprite 33 pixels into the air. It's _negative_ 33 because a lower number in Pygame means it's closer to the top of the screen.
根据此代码所示,跳跃使玩家对象向空中移动了 33 个像素。此处是_负_ 33 是因为在 Pygame 中,越小的数代表距离屏幕顶端越近。
This event is conditional, though. It only happens if **self.collide_delta** is less than 6 (its default value established in the **init** function of your **Player** sprite) and if **self.jump_delta** is less than 6. This condition prevents the player from triggering another jump until it collides with a platform. In other words, it prevents mid-air jumps.
不过此事件视条件而定,只有当 **self.collide_delta** 小于 6缺省值定义在你 **Player** 类的 **init** 方法中)并且 **self.jump_delta** 也于 6 的时候才会发生。此条件能够保证直到玩家碰到一个平台,才能触发另一次跳跃。换言之,它能够阻止空中二段跳。
You don't have to prevent mid-air jumps, or you can allow for mid-air jumps under special conditions; for instance, if a player obtains a special loot item, then you could grant it the ability to perform mid-air jumps until the next time it is hit by an enemy.
在某些特殊条件下,你可能不想阻止空中二段跳,或者说你允许玩家进行空中二段跳。举个栗子,如果玩家获得了某个战利品,那么在他被敌人攻击到之前,都能够拥有空中二段跳的能力。
When you've finished this article's examples, try setting **self.collide_delta** and **self.jump_delta** to 0 for a 100% chance to jump in mid-air.
当你完成本篇文章中的示例,尝试将 **self.collide_delta****self.jump_delta** 设置为 0从而获得百分之百的几率触发空中二段跳。
### Landing on a platform
### 在平台上着陆
So far, you've defined an anti-gravity condition for when the player sprite hits the ground, but the game code keeps platforms and the ground in separate lists. (As with so many choices made in this article, that's not strictly necessary, and you can experiment with treating the ground as just another platform.) To enable a player sprite to stand on top of a platform, you must detect a collision between the player sprite and a platform sprite and then perform the same actions as you did for a ground collision. Place this code into your **update** function:
目前你已经定义了再玩家对象摔落地面时的抵抗重力条件,但此时你的游戏代码仍保持平台与地面置于不同的列表中(就像本文中做的很多其他选择一样,这个设定并不是必需的,你可以尝试将地面作为另一种平台)。为了允许玩家对象站在平台之上,你必须像检测地面碰撞一样,检测玩家对象与平台对象之间的碰撞。将如下代码放于你的 **update** 方法中:
```
        plat_hit_list = pygame.sprite.spritecollide(self, plat_list, False)
        for p in plat_hit_list:
            self.collide_delta = 0 # stop jumping
            self.collide_delta = 0 # 跳跃结束
            self.movey = 0
```
There's one additional concern, though: platforms hang in the air, meaning the player can interact with them by approaching them from either the top or the bottom.
但此处还有一点需要考虑:平台悬在空中,也就意味着玩家可以通过从上面或者从下面接触平台来与之互动。
It's up to you how you want the platforms to react to your player. It's not uncommon to block a player from accessing a platform from below. Add this code to the above code block to treat platforms as a sort of ceiling or pergola, such that the player sprite can jump onto a platform as long as it jumps higher than the platform's topside, but obstructs the player when it tries to jump from beneath:
确定平台如何与玩家互动取决于你,阻止玩家从下方到达平台也并不稀奇。将如下代码加到上方的代码块中,使得平台表现得像天花板或者说是藤架。只有在玩家对象跳得比平台上沿更高时才能跳到平台上,但会阻止玩家从平台下方跳上来:
```
@ -114,17 +113,17 @@ It's up to you how you want the platforms to react to your player. It's not unco
                self.rect.y = p.rect.y-ty
```
The first clause of this **if** statement blocks the player from accessing a platform when the player sprite is directly under the platform. It does this by detecting that the player sprite's position is greater (in Pygame, that means lower on the screen, from top to bottom), and then sets the player sprite's new Y position to its current Y position plus the height of a tile, effectively keeping the player one tile's height away from ever passing through a platform from beneath it.
此处 **if** 语句代码块的第一个子句阻止玩家对象从平台正下方跳到平台上。如果它检测到玩家对象的坐标比平台更大(在 Pygame 中,坐标更大意味着在屏幕的更下方),那么将玩家对象新的 Y 坐标设置为当前平台的 Y 坐标加上一个瓷贴的高度。实际效果就是保证玩家对象距离平台一个瓷贴的高度,防止其从下方穿过平台。
The **else** clause does the opposite. If the program is running this code, then the player sprite's Y position is _not_ greater than the platforms, meaning that the player sprite is falling from the sky (either because it has been freshly spawned there or because the player has jumped). In this event, the player sprite's position is set to the position of the platform minus one tile's height (because, remember, in Pygame, lower numbers mean something is higher up on the screen). This keeps the player on top of the platform unless it jumps or walks off of it.
**else** 子句做了相反的事情。当程序运行到此处时,如果玩家对象的 Y 坐标_不_比平台的更大意味着玩家对象是从空中落下不论是由于玩家刚刚从此处生成或者是玩家执行了跳跃。在这种情况下玩家对象的 Y 坐标被设为平台的 Y 坐标减去一个瓷贴的高度(切记,在 Pygame 中更小的 Y 坐标代表在屏幕上的更高处。这样就能保证玩家在平台_上_除非他从平台上跳下来或者走下来。
You can experiment with other ways of dealing with sprite and platform interaction. For instance, maybe the player is assumed to be "in front" of the platforms and can seamlessly jump through them to stand on top of one. Or a platform could slow a player's leap but not block it entirely. You can even mix and match by grouping platforms into different lists.
你也可以尝试其他的方式来处理玩家与平台之间的互动。举个栗子,也许玩家对象被设定为处在平台的“前面”,他能够无障碍地跳跃穿过平台并站在上面。或者你可以设计一种平台会减缓而又不完全阻止玩家的跳跃过程。甚至你可以通过将不同平台分到不同列表中来混合搭配使用。
### Triggering a jump
### 触发一次跳跃
Your code now simulates all the necessary jump conditions, but it still lacks a jump trigger. Your player sprite's **self.jump_delta** is set to 6 initially, and the jump update code is triggered only when it's less than 6.
目前为此,你的代码已经模拟了所有必需的跳跃条件,但仍缺少一个跳跃触发器。你的玩家对象的 **self.jump_delta** 初始值被设置为 6只有当它比 6 小时才会触发更新跳跃的代码。
To trigger a new setting for the jumping variable, create a **jump** function in your **Player** class that sets the **self.jump_delta** to less than 6, causing gravity to be temporarily reprieved by sending your player sprite 33 pixels into the air:
为跳跃变量设置一个新的设置方法,在你的 **Player** 类中创建一个 **jump** 方法,并将 **self.jump_delta** 设为小于 6 的值。通过使玩家对象向空中移动 33 个像素,来暂时减缓重力的作用。
```
@ -132,17 +131,17 @@ To trigger a new setting for the jumping variable, create a **jump** function in
        self.jump_delta = 0
```
That's all the **jump** function requires, believe it or not. The rest happens in the **update** function, and you've already written that code.
不管你相信与否,这就是 **jump** 方法的全部。剩余的部分在 **update** 方法中,你已经在前面实现了相关代码。
There's one last thing to do before jumping is functional in your game. If you can't think of what it is, try playing your game to see how jumping works for you.
要使你游戏中的跳跃功能生效,还有最后一件事情要做。如果你想不起来是什么,运行游戏并观察跳跃是如何生效的。
The problem is that nothing in your main loop is calling the **jump** function. You made a placeholder keypress for it early on, but right now, all the jump key does is print **jump** to the terminal.
问题就在于你的主循环中没有调用 **jump** 方法。先前你已经为该方法创建了一个按键占位符,现在,跳跃键所做的就是将 **jump** 打印到终端。
### Calling the jump function
### 调用 jump 方法
In your main loop, change the result of the Up arrow from printing a debug statement to calling the **jump** function.
在你的主循环中将_上_方向键的效果从打印一条调试语句改为调用 **jump** 方法。
Notice that the **jump** function, like the **update** function, needs to know about collisions, so you have to tell it which **plat_list** to use.
注意此处,与 **update** 方法类似,**jump** 方法也需要检测碰撞,因此你需要告诉它使用哪个 **plat_list**
```
@ -150,13 +149,13 @@ Notice that the **jump** function, like the **update** function, needs to know a
                player.jump(plat_list)
```
If you would rather use the Spacebar for jumping, set the key to **pygame.K_SPACE** instead of **pygame.K_UP**. Alternately, you can use both (as separate **if** statements) so that the player has a choice.
如果你倾向于使用空格键作为跳跃键,使用 **pygame.K_SPACE** 替代 **pygame.K_UP** 作为按键。另一种选择,你可以同时使用两种方式(使用单独的 **if** 语句),给玩家多一种选择。
Try your game now. In the next article, you'll make your world scroll.
现在来尝试你的游戏吧!在下一篇文章中,你将让你的游戏卷动起来。
![Pygame platformer][3]
![Pygame 平台类游戏][3]
Here's all the code so far:
以下是目前为止的所有代码:
```
@ -184,7 +183,7 @@ Objects
'''
class Platform(pygame.sprite.Sprite):
    # x location, y location, img width, img height, img file    
# x 坐标y 坐标,图像宽度,图像高度,图像文件
    def __init__(self,xloc,yloc,imgw,imgh,img):
        pygame.sprite.Sprite.__init__(self)
        self.image = pygame.image.load(os.path.join('images',img)).convert()
@ -195,7 +194,7 @@ class Platform(pygame.sprite.Sprite):
class Player(pygame.sprite.Sprite):
    '''
    Spawn a player
    生成一个玩家
    '''
    def __init__(self):
        pygame.sprite.Sprite.__init__(self)
@ -227,34 +226,34 @@ class Player(pygame.sprite.Sprite):
       
    def control(self,x,y):
        '''
        control player movement
        控制玩家移动
        '''
        self.movex += x
        self.movey += y
       
    def update(self):
        '''
        Update sprite position
        更新对象位置
        '''
       
        self.rect.x = self.rect.x + self.movex
        self.rect.y = self.rect.y + self.movey
        # moving left
        # 向左移动
        if self.movex &lt; 0:
            self.frame += 1
            if self.frame &gt; ani*3:
                self.frame = 0
            self.image = self.images[self.frame//ani]
        # moving right
        # 向右移动
        if self.movex &gt; 0:
            self.frame += 1
            if self.frame &gt; ani*3:
                self.frame = 0
            self.image = self.images[(self.frame//ani)+4]
        # collisions
        # 碰撞
        enemy_hit_list = pygame.sprite.spritecollide(self, enemy_list, False)
        for enemy in enemy_hit_list:
            self.health -= 1
@ -286,7 +285,7 @@ class Player(pygame.sprite.Sprite):
           
class Enemy(pygame.sprite.Sprite):
    '''
    Spawn an enemy
    生成一个敌人
    '''
    def __init__(self,x,y,img):
        pygame.sprite.Sprite.__init__(self)
@ -302,7 +301,7 @@ class Enemy(pygame.sprite.Sprite):
               
    def move(self):
        '''
        enemy movement
        敌人移动
        '''
        distance = 80
        speed = 8
@ -337,9 +336,9 @@ class Enemy(pygame.sprite.Sprite):
class Level():
    def bad(lvl,eloc):
        if lvl == 1:
            enemy = Enemy(eloc[0],eloc[1],'yeti.png') # spawn enemy
            enemy_list = pygame.sprite.Group() # create enemy group
            enemy_list.add(enemy)              # add enemy to group
            enemy = Enemy(eloc[0],eloc[1],'yeti.png') # 生成敌人
            enemy_list = pygame.sprite.Group() # 创建敌人组
            enemy_list.add(enemy)              # 将敌人添加到敌人组
           
        if lvl == 2:
            print("Level " + str(lvl) )
@ -392,8 +391,8 @@ Setup
worldx = 960
worldy = 720
fps = 40 # frame rate
ani = 4  # animation cycles
fps = 40 # 帧率
ani = 4  # 动画循环
clock = pygame.time.Clock()
pygame.init()
main = True
@ -406,7 +405,7 @@ ALPHA = (0,255,0)
world = pygame.display.set_mode([worldx,worldy])
backdrop = pygame.image.load(os.path.join('images','stage.png')).convert()
backdropbox = world.get_rect()
player = Player() # spawn player
player = Player() # 生成玩家
player.rect.x = 0
player.rect.y = 0
player_list = pygame.sprite.Group()
@ -418,8 +417,8 @@ eloc = []
eloc = [200,20]
gloc = []
#gloc = [0,630,64,630,128,630,192,630,256,630,320,630,384,630]
tx = 64 #tile size
ty = 64 #tile size
tx = 64 # 瓷贴尺寸
ty = 64 # 瓷贴尺寸
i=0
while i &lt;= (worldx/tx)+tx:
@ -431,7 +430,7 @@ ground_list = Level.ground( 1,gloc,tx,ty )
plat_list = Level.platform( 1,tx,ty )
'''
Main loop
主循环
'''
while main == True:
    for event in pygame.event.get():
@ -464,26 +463,26 @@ while main == True:
#    world.fill(BLACK)
    world.blit(backdrop, backdropbox)
    player.gravity() # check gravity
    player.gravity() # 检查重力
    player.update()
    player_list.draw(world) #refresh player position
    enemy_list.draw(world)  # refresh enemies
    ground_list.draw(world)  # refresh enemies
    plat_list.draw(world)   # refresh platforms
    player_list.draw(world) # 刷新玩家位置
    enemy_list.draw(world)  # 刷新敌人
    ground_list.draw(world)  # 刷新地面
    plat_list.draw(world)   # 刷新平台
    for e in enemy_list:
        e.move()
    pygame.display.flip()
    clock.tick(fps)
```
This is the 7th installment in an ongoing series about creating video games in [Python 3][4] using the [Pygame][5] module. Previous articles are:
本期是使用 [Pygame][5] 模块在 [Python 3][4] 中创建视频游戏连载系列的第 7 期。往期文章为:
* [Learn how to program in Python by building a simple dice game][6]
* [Build a game framework with Python using the Pygame module][7]
* [How to add a player to your Python game][8]
* [Using Pygame to move your game character around][9]
* [What's a hero without a villain? How to add one to your Python game][10]
* [Simulate gravity in your Python game][2]
* [通过构建一个简单的掷骰子游戏去学习怎么用 Python 编程][6]
* [使用 Python 和 Pygame 模块构建一个游戏框架][7]
* [如何在你的 Python 游戏中添加一个玩家][8]
* [用 Pygame 使你的游戏角色移动起来][9]
* [如何向你的 Python 游戏中添加一个敌人][10]
* [在你的 Python 游戏中模拟重力][2]
@ -493,7 +492,7 @@ via: https://opensource.com/article/19/12/jumping-python-platformer-game
作者:[Seth Kenlon][a]
选题:[lujun9972][b]
译者:[译者ID](https://github.com/译者ID)
译者:[cycoe](https://github.com/cycoe)
校对:[校对者ID](https://github.com/校对者ID)
本文由 [LCTT](https://github.com/LCTT/TranslateProject) 原创编译,[Linux中国](https://linux.cn/) 荣誉推出

View File

@ -1,318 +0,0 @@
[#]: collector: (lujun9972)
[#]: translator: (lxbwolf)
[#]: reviewer: ( )
[#]: publisher: ( )
[#]: url: ( )
[#]: subject: (Lessons learned from programming in Go)
[#]: via: (https://opensource.com/article/19/12/go-common-pitfalls)
[#]: author: (Eduardo Ferreira https://opensource.com/users/edufgf)
Go 编程中的经验教训
======
通过学习如何定位并发处理的陷阱来避免未来处理这些问题时的困境。
![Goland gopher illustration][1]
在复杂的分布式系统进行任务处理时,你通常会需要进行并发的操作。[Mode.net][2] 公司系统每天要处理实时、快速和灵活的以毫秒为单位动态路由数据包的全球专用网络和数据,需要高度并发的系统。这个动态路由是基于网络状态的,而这个过程需要考虑众多因素,我们只考虑关系链的监控。在我们的环境中,调用关系链监控可以是任何跟网络调用关系链有关的状态和当前属性(如链接延迟)。
### 并发探测链接监控
[H.A.L.O.][4] Hop-by-Hop Adaptive Link-State Optimal Routing译注逐跳自适应链路状态最佳路由我们的动态路由算法部分依赖于链路度量来计算路由表。 这些指标由位于每个PoP译注存活节点上的独立组件收集。PoP是表示我们的网络中单个路由实体的机器通过链路连接并分布在我们的网络拓扑中的各个位置。某个组件使用网络数据包探测周围的机器周围的机器回复数据包给前者。从接收到的探测包中可以获得链路延迟。由于每个 PoP 都有不止一个临近节点,所以这种探测任务实质上是并发的:我们需要实时测量每个临近连接点的延迟。我们不能串行地处理;为了计算这个指标,必须尽快处理每个探针。
![latency computation graph][6]
### 序列号和重置:一个记录场景
我们的探测组件互相发送和接收数据包并依靠序列号进行数据包处理。旨在避免处理重复的包或顺序被打乱的包。我们的第一个实现依靠特殊的序列号 0 来重置序列号。这个数字仅在组件初始化时使用。主要的问题是我们只考虑了始终从 0 开始递增的序列号。组件重启后,包的顺序可能会重新排列,某个包的序列号可能会轻易地被替换成重置之前使用过的值。这意味着,直到排到重置之前用到的值之前,它后面的包都会被忽略掉。
### UDP 握手和有限状态机
这里的问题是重启前后的序列号是否一致。有几种方法可以解决这个问题,经过讨论,我们选择了实现一个带有清晰状态定义的三向交握协议。这个握手过程在初始化时通过链接建立 session。这样可以确保节点通过同一个 session 进行通信且使用了适当的序列号。
为了正确实现这个过程,我们必须定义一个有清晰状态和过渡的有限状态机。这样我们就可以正确管理握手过程中的所有极端情况。
![finite state machine diagram][7]
session ID 由握手的初始化程序生成。一个完整的交换顺序如下:
1. sender 发送一个 **SYN (ID)** 数据包。
2. receiver 存储接收到的 **ID** 并发送一个 **SYN-ACK (ID)**.
3. sender 接收到 **SYN-ACK (ID)** _并发送一个 **ACK (ID)**_。它还发送一个从序列号 0 开始的数据包。
4. receiver 检查最后接收到的 **ID**,如果 ID 匹配_则接受 **ACK (ID)**_。它还开始接受序列号为 0 的数据包。
### 处理状态超时
基本上,每种状态下你都需要处理最多三种类型的事件:链接事件、数据包事件和超时事件。这些事件会并发地出现,因此你必须正确处理并发。
* 链接事件包括连接和断开,连接时会初始化一个链接 session断开时会断开一个已建立的 seesion。
* 数据包事件是控制数据包 **(SYN/SYN-ACK/ACK)** 或只是探测响应。
* 超时事件在当前 session 状态的预定超时时间到期后触发。
这里面临的最主要的问题是如何处理并发超时到期和其他事件。这里很容易陷入死锁和资源竞争的陷阱。
### 第一种方法
本项目使用的语言是 [Golang][8]. 它确实提供了原生的同步机制,如自带的 channel 和锁,并且能够使用轻量级线程来进行并发处理。
![gophers hacking together][9]
gopher 们聚众狂欢
首先,你可以设计两个分别表示我们的 **Session****Timeout Handlers** 的结构体。
```go
type Session struct {  
  State SessionState  
  Id SessionId  
  RemoteIp string  
}
type TimeoutHandler struct {  
  callback func(Session)  
  session Session  
  duration int  
  timer *timer.Timer  
}
```
**Session** 标识连接 session内有表示 session ID、临近的连接点的 IP 和当前 session 状态的字段。
**TimeoutHandler** 包含回调函数、对应的 session、持续时间和指向调度计时器的 timer 指针。
每一个临近连接点的 session 都包含一个保存调度 `TimeoutHandler` 的全局 map。
```
`SessionTimeout map[Session]*TimeoutHandler`
```
下面方法注册和取消超时:
```go
// schedules the timeout callback function.  
func (timeout* TimeoutHandler) Register() {  
  timeout.timer = time.AfterFunc(time.Duration(timeout.duration) * time.Second, func() {  
    timeout.callback(timeout.session)  
  })  
}
func (timeout* TimeoutHandler) Cancel() {  
  if timeout.timer == nil {  
    return  
  }  
  timeout.timer.Stop()  
}
```
你可以使用类似下面的方法来创建和存储超时:
```go
func CreateTimeoutHandler(callback func(Session), session Session, duration int) *TimeoutHandler {  
  if sessionTimeout[session] == nil {  
    sessionTimeout[session] := new(TimeoutHandler)  
  }  
   
  timeout = sessionTimeout[session]  
  timeout.session = session  
  timeout.callback = callback  
  timeout.duration = duration  
  return timeout  
}
```
超时 handler 创建后,会在经过了设置的 _duration_ 时间(秒)后执行回调函数。然而,有些事件会使你重新调度一个超时 handler**SYN** 状态时的处理一样 — 每 3 秒一次)。
为此,你可以让回调函数重新调度一次超时:
```go
func synCallback(session Session) {  
  sendSynPacket(session)
  // reschedules the same callback.  
  newTimeout := NewTimeoutHandler(synCallback, session, SYN_TIMEOUT_DURATION)  
  newTimeout.Register()
  sessionTimeout[state] = newTimeout  
}
```
这次回调在新的超时 handler 中重新调度自己,并更新全局 map **sessionTimeout**
### 数据竞争和引用
你的解决方案已经有了。可以通过检查计时器到期后超时回调是否执行来进行一个简单的测试。为此,注册一个超时,在 *duration* 时间内 sleep然后检查是否执行了回调的处理。执行这个测试后最好取消预定的超时时间因为它会重新调度这样才不会在下次测试时产生副作用。
令人惊讶的是,这个简单的测试发现了这个解决方案中的一个 bug。使用 cancel 方法来取消超时并没有正确处理。以下顺序的事件会导致数据资源竞争:
1. 你有一个已调度的超时 handler。
2. 线程 1:
a你接收到一个控制数据包现在你要取消已注册的超时并切换到下一个 session 状态(如 发送 **SYN** 后接收到一个 **SYN-ACK**
b你调用了 **timeout.Cancel()**,这个函数调用了 **timer.Stop()**请注意Golang 计时器的 stop 不会终止一个已过期的计时器。)
3. 线程 2:
a在调用 cancel 之前,计时器已过期,回调即将执行。
b执行回调它调度一次新的超时并更新全局 map。
4. 线程 1:
a切换到新的 session 状态并注册新的超时,更新全局 map。
两个线程同时更新超时 map。最终结果是你无法取消注册的超时然后你也会丢失对线程 2 重新调度的超时的引用。这导致 handler 在一段时间内持续执行和重新调度,出现非预期行为。
### 锁也解决不了问题
使用锁也不能完全解决问题。如果你在处理所有事件和执行回调之前加锁,它仍然不能阻止一个过期的回调运行:
```go
func (timeout* TimeoutHandler) Register() {  
  timeout.timer = time.AfterFunc(time.Duration(timeout.duration) * time._Second_, func() {  
    stateLock.Lock()  
    defer stateLock.Unlock()
    timeout.callback(timeout.session)  
  })  
}
```
现在的区别就是全局 map 的更新是同步的,但是这还是不能阻止在你调用 **timeout.Cancel()** 后回调的执行 — 这种情况出现在调度计时器过期了但是还没有拿到锁的时候。你还是会丢失一个已注册的超时的引用。
### 使用取消 channel
你可以使用取消 channel而不必依赖不能阻止到期的计时器执行的 golang 函数 **timer.Stop()**
这是一个略有不同的方法。现在你可以不用再通过回调进行递归地重新调度;而是注册一个死循环,这个循环接收到取消信号或超时事件时终止。
新的 **Register()** 产生一个新的 go 协程,这个协程在在超时后执行你的回调,并在前一个超时执行后调度新的超时。返回给调用方一个取消 channel用来控制循环的终止。
```go
func (timeout *TimeoutHandler) Register() chan struct{} {  
  cancelChan := make(chan struct{})  
   
  go func () {  
    select {  
    case _ = <- cancelChan:  
      return  
    case _ = <- time.AfterFunc(time.Duration(timeout.duration) * time.Second):  
      func () {  
        stateLock.Lock()  
        defer stateLock.Unlock()
        timeout.callback(timeout.session)  
      } ()  
    }  
  } ()
  return cancelChan  
}
func (timeout* TimeoutHandler) Cancel() {  
  if timeout.cancelChan == nil {  
    return  
  }  
  timeout.cancelChan <- struct{}{}  
}
```
这个方法提供了你注册的所有超时的取消 channel。对 cancel 的一次调用向 channel 发送一个空结构体并触发取消操作。然而,这并不能解决前面的问题;可能在你通过 channel 调用 cancel 超时线程还没有拿到锁之前,超时时间就已经到了。
这里的解决方案是,在拿到锁之后,检查一下超时范围内的取消 channel。
```go
  case _ = <- time.AfterFunc(time.Duration(timeout.duration) * time.Second):  
    func () {  
      stateLock.Lock()  
      defer stateLock.Unlock()  
     
      select {  
      case _ = <- handler.cancelChan:  
        return  
      default:  
        timeout.callback(timeout.session)  
      }  
    } ()  
  }
```
最终,这可以确保在拿到锁之后执行回调,不会触发取消操作。
### 小心死锁
这个解决方案看起来有效;但是还是有个隐患:[死锁][10]。
请阅读上面的代码,试着自己找到它。考虑下描述的所有函数的并发调用。
这里的问题在取消 channel 本身。我们创建的是无缓冲 channel即发送是阻塞调用。当你在一个超时 handler 中调用取消函数时,只有在该 handler 被取消后才能继续处理。问题出现在,当你有多个调用请求到同一个取消 channel 时,这时一个取消请求只被处理一次。当多个事件同时取消同一个超时 handler 时,如链接断开或控制包事件,很容易出现这种情况。这会导致死锁,可能会使应用程序 halt。
![gophers on a wire, talking][11]
有人在听吗?
已获得 Trevor Forrey 授权。
这里的解决方案是创建 channel 时指定大小至少为 1这样向 channel 发送数据就不会阻塞,也显式地使发送变成非阻塞的,避免了并发调用。这样可以确保取消操作只发送一次,并且不会阻塞后续的取消调用。
```go
func (timeout* TimeoutHandler) Cancel() {  
  if timeout.cancelChan == nil {  
    return  
  }  
   
  select {  
  case timeout.cancelChan <- struct{}{}:  
  default:  
    // cant send on the channel, someone has already requested the cancellation.  
  }  
}
```
### 总结
在实践中你学到了并发操作时出现的常见错误。由于其不确定性,即使进行大量的测试,也不容易发现这些问题。下面是我们在最初的实现中遇到的三个主要问题:
#### 在非同步的情况下更新共享数据
这似乎是个很明显的问题,但如果并发更新发生在不同的位置,就很难发现。结果就是数据竞争,由于一个更新会覆盖另一个,因此对同一数据的多次更新中会有某些更新丢失。在我们的案例中,我们是在同时更新同一个共享 map 里的调度超时引用。有趣的是,(如果 Go 检测到在同一个 map 对象上的并发读写,会抛出 fatal 错误 — 你可以尝试下运行 Go 的[数据竞争检测器](https://golang.org/doc/articles/race_detector.html))。这最终会导致丢失超时引用,且无法取消给定的超时。当有必要时,永远不要忘记使用锁。
![gopher assembly line][13]
不要忘记同步 gopher 们的工作
#### 缺少条件检查
在不能仅依赖锁的独占性的情况下,就需要进行条件检查。我们遇到的场景稍微有点不一样,但是核心思想跟[条件变量][14]是一样的。假设有个经典的一个生产者和多个消费者使用一个共享队列的场景,生产者可以将一个元素添加到队列并唤醒所有消费者。这个唤醒调用意味着队列中的数据是可访问的,并且由于队列是共享的,消费者必须通过锁来进行同步访问。每个消费者都可能拿到锁;然而,你仍然需要检查队列中是否有元素。因为在你拿到锁的瞬间并不知道队列的状态,所以还是需要进行条件检查。
在我们的例子中,超时 handler 收到了计时器到期时发出的「唤醒」调用,但是它仍需要检查是否已向其发送了取消信号,然后才能继续执行回调。
![gopher boot camp][15]
如果你要唤醒多个 gopher可能就需要进行条件检查
#### 死锁
当一个线程被卡住,无限期地等待一个唤醒信号,但是这个信号永远不会到达时,就会发生这种情况。死锁可以通过让你的整个程序 halt 来彻底杀死你的应用。
在我们的案例中,这种情况的发生是由于多次发送请求到一个非缓冲且阻塞的 channel。这意味着向 channel 发送数据只有在从这个 channel 接收完数据后才能 return。我们的超时线程循环迅速从取消 channel 接收信号;然而,在接收到第一个信号后,它将跳出循环,并且再也不会从这个 channel 读取数据。其他的调用会一直被卡住。为避免这种情况,你需要仔细检查代码,谨慎处理阻塞调用,并确保不会发生线程饥饿。我们例子中的解决方法是使取消调用成为非阻塞调用 — 我们不需要阻塞调用。
--------------------------------------------------------------------------------
via: https://opensource.com/article/19/12/go-common-pitfalls
作者:[Eduardo Ferreira][a]
选题:[lujun9972][b]
译者:[lxbwolf](https://github.com/lxbwolf)
校对:[校对者ID](https://github.com/校对者ID)
本文由 [LCTT](https://github.com/LCTT/TranslateProject) 原创编译,[Linux中国](https://linux.cn/) 荣誉推出
[a]: https://opensource.com/users/edufgf
[b]: https://github.com/lujun9972
[1]: https://opensource.com/sites/default/files/styles/image-full-size/public/lead-images/go-golang.png?itok=OAW9BXny (Goland gopher illustration)
[2]: http://mode.net
[3]: https://en.wikipedia.org/wiki/Metrics_%28networking%29
[4]: https://people.ece.cornell.edu/atang/pub/15/HALO_ToN.pdf
[5]: https://en.wikipedia.org/wiki/Point_of_presence
[6]: https://opensource.com/sites/default/files/uploads/image2_0_3.png (latency computation graph)
[7]: https://opensource.com/sites/default/files/uploads/image3_0.png (finite state machine diagram)
[8]: https://golang.org/
[9]: https://opensource.com/sites/default/files/uploads/image4.png (gophers hacking together)
[10]: https://en.wikipedia.org/wiki/Deadlock
[11]: https://opensource.com/sites/default/files/uploads/image5_0_0.jpg (gophers on a wire, talking)
[12]: https://golang.org/doc/articles/race_detector.html
[13]: https://opensource.com/sites/default/files/uploads/image6.jpeg (gopher assembly line)
[14]: https://en.wikipedia.org/wiki/Monitor_%28synchronization%29#Condition_variables
[15]: https://opensource.com/sites/default/files/uploads/image7.png (gopher boot camp)

View File

@ -0,0 +1,153 @@
[#]: collector: (lujun9972)
[#]: translator: (geekpi)
[#]: reviewer: ( )
[#]: publisher: ( )
[#]: url: ( )
[#]: subject: (Generating numeric sequences with the Linux seq command)
[#]: via: (https://www.networkworld.com/article/3511954/generating-numeric-sequences-with-the-linux-seq-command.html)
[#]: author: (Sandra Henry-Stocker https://www.networkworld.com/author/Sandra-Henry_Stocker/)
使用 Linux seq 命令生成数字序列
======
Linux seq 命令可以以闪电般的速度生成数字列表。它易于使用而且灵活。
[Jamie][1] [(CC BY 2.0)][2]
在 Linux 中生成数字列表的最简单方法之一是使用 **seq**sequence命令。最简单的形式是**seq** 接收一个数字,并输出从 1 到该数字的列表。例如:
```
$ seq 5
1
2
3
4
5
```
除非另有指定,否则 **seq** 始终以 1 开头。你可以在最终数字前面插上不同数字开始。
```
$ seq 3 5
3
4
5
```
### 指定增量
你还可以指定增量。假设你要列出 3 的倍数。指定起点(在此示例中为第一个 3 ),增量(第二个 3和终点18
```
$ seq 3 3 18
3
6
9
12
15
18
```
你可以选择使用负增量(即减量)将数字从大变小。
```
$ seq 18 -3 3
18
15
12
9
6
3
```
**seq** 命令也非常快。你或许可以在 10 秒内生成一百万个数字的列表。
Advertisement
```
$ time seq 1000000
1
2
3
999998
999999
1000000
real 0m9.290s <== 9+ seconds
user 0m0.020s
sys 0m0.899s
```
## 使用分隔符
另一个非常有用的选项是使用分隔符。你可以插入逗号,冒号或其他一些字符,而不是在每行上列出单个数字。-s 选项后跟要使用的字符。
```
$ seq -s: 3 3 18
3:6:9:12:15:18
```
实际上,如果只是希望将数字列在一行上,那么可以使用空格代替默认的换行符。
```
$ seq -s' ' 3 3 18
3 6 9 12 15 18
```
### 开始数学运算
从生成数字序列到进行数学运算似乎是一个巨大的飞跃,但是有了正确的分隔符,**seq** 可以轻松地传递给 **bc** 进行计算。例如:
```
$ seq -s* 5 | bc
120
```
该命令中发生了什么?让我们来看看。首先,**seq** 生成一个数字列表,并使用 \* 作为分隔符。
```
$ seq -s* 5
1*2*3*4*5
```
然后,它将字符串传递给计算器 **bc**),计算器立即将数字相乘。你可以在不到一秒的时间内进行相当广泛的计算。
```
$ time seq -s* 117 | bc
39699371608087208954019596294986306477904063601683223011297484643104\
22041758630649341780708631240196854767624444057168110272995649603642\
560353748940315749184568295424000000000000000000000000000
real 0m0.003s
user 0m0.004s
sys 0m0.000s
```
### 局限性
你只能选择一个分隔符,因此计算将非常有限。单独使用 **bc** 可进行更复杂的数学运算。此外,**seq** 仅适用于数字。要生成单个字母序列,请改用如下命令:
```
$ echo {a..g}
a b c d e f g
```
加入 [Facebook][5] 和 [LinkedIn][6] 上的 Network World 社区,评论热门主题。
--------------------------------------------------------------------------------
via: https://www.networkworld.com/article/3511954/generating-numeric-sequences-with-the-linux-seq-command.html
作者:[Sandra Henry-Stocker][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://www.networkworld.com/author/Sandra-Henry_Stocker/
[b]: https://github.com/lujun9972
[1]: https://creativecommons.org/licenses/by/2.0/
[2]: https://creativecommons.org/licenses/by/2.0/legalcode
[5]: https://www.facebook.com/NetworkWorld/
[6]: https://www.linkedin.com/company/network-world

View File

@ -1,5 +1,5 @@
[#]: collector: (lujun9972)
[#]: translator: ( )
[#]: translator: (qianmingtian)
[#]: reviewer: ( )
[#]: publisher: ( )
[#]: url: ( )
@ -7,30 +7,30 @@
[#]: via: (https://www.2daygeek.com/bash-script-to-check-user-account-password-expiry-linux/)
[#]: author: (Magesh Maruthamuthu https://www.2daygeek.com/author/magesh/)
Bash Script to Send eMail With a List of User Accounts Expiring in “X” Days
使用Bash 脚本发送包含 “X” 天内到期的用户账号列表的电子邮件
======
The password enforcement policy is common to all operating systems and applications.
密码强制策略对所有操作系统和应用程序都是通用的。
If you want to **[implement a password enforcement policy on Linux][1]**, go to the following article.
如果要 **[在Linux上实现密码强制策略][1]** ,请参阅以下文章。
The password enforcement policy will be enforced by most companies by default, but the time period will be different depending on the companys requirements.
默认情况下,大多数公司都会强制执行密码强制策略,但根据公司的要求,密码的时间周期会有所不同。
Usually everyone uses a 90-days password cycle.
通常每个人都使用 90 天的密码周期。
The user will only **[change the password][2]** on some of the servers they use, and they wont change the password on the servers they dont use often.
用户只会在他们使用的一些服务器上 **[更改密码][2]**,而不会在他们不经常使用的服务器上更改密码。
In particular, most team forget to change the service account password, which can lead to breaking regular jobs even if they are configured to work with **[SSH key-based authentication][3]**.
特别地,大多数团队忘记更改服务帐户密码,这可能导致日常工作的中断,即使他们配置有 **[SSH基于密钥的身份验证][3]** 。
SSH key-based authentication and **[cronjobs][4]** will not work if the user account password expires.
如果用户帐户密码过期基于SSH密钥的身份验证和 **[cronjobs][4]** 将不起作用。
To avoid this situation, we have created a **[shell script][5]** that sends you a list of user accounts that expire within 10 days.
为了避免这种情况,我们创建了一个 **[shell脚本][5]** 来向您发送 10 天内到期的用户帐户列表。
There are two **[bash scripts][6]** included in this tutorial that will help you collect information about user expiration days on your system.
本教程中包含两个 **[bash脚本][6]** 可以帮助您收集系统中用户到期日的信息。
### 1) Bash Script to Check List of User Accounts Expiring in 10 Days
### 1) 检查 10 天后到期的用户帐户列表
This script will help you to check the list of user accounts that expire in 10 days on your terminal.
此脚本将帮助您检查终端上 10 天内到期的用户帐户列表。
```
# vi /opt/script/user-password-expiry.sh
@ -54,13 +54,13 @@ cat /tmp/user-expiry.txt | awk '$2 <= 10' > /tmp/user-expiry-1.txt
cat /tmp/user-expiry-1.txt | column -t
```
Set an executable Linux file permission to **“user-password-expiry.sh”** file.
将文件 **“user password expiry.sh”** 设置为 Linux 可执行文件权限。
```
# chmod +x /opt/script/user-password-expiry.sh
```
You will get an output like the one below. But the username and days may be different
你将得到如下输出,但用户与天数可能不同。
```
# sh /opt/script/user-password-expiry.sh
@ -73,10 +73,9 @@ u2 9
u3 3
u4 5
```
### 2) 发送包含 10 天内到期的用户帐户列表的电子邮件
### 2) Bash Script to Send eMail With a List of User Accounts Expiring in 10 Days
This script will send a mail with a list of user accounts expiring in 10 days.
此脚本将发送一封包含 10 天内到期的用户帐户列表的邮件。
```
# vi /opt/script/user-password-expiry-mail.sh
@ -104,20 +103,20 @@ rm $MESSAGE
rm $MESSAGE1
```
Set an executable Linux file permission to **“user-password-expiry-mail.sh”** file.
将文件 **“user-password-expiry-mail.sh”** 设置为 Linux 可执行文件权限。
```
# chmod +x /opt/script/user-password-expiry-mail.sh
```
Finally add a **[cronjob][4]** to automate this. It runs once in a day at 8AM.
最后,添加一个 **[cronjob][4]** 去自动执行脚本。每天早上 8 点运行一次。
```
# crontab -e
0 8 * * * /bin/bash /opt/script/user-password-expiry-mail.sh
```
You will receive a mail similar to the first shell script output.
你将收到一封与第一个脚本输出类似的电子邮件。
--------------------------------------------------------------------------------
@ -125,13 +124,14 @@ via: https://www.2daygeek.com/bash-script-to-check-user-account-password-expiry-
作者:[Magesh Maruthamuthu][a]
选题:[lujun9972][b]
译者:[译者ID](https://github.com/译者ID)
译者:[qianmingtian][c]
校对:[校对者ID](https://github.com/校对者ID)
本文由 [LCTT](https://github.com/LCTT/TranslateProject) 原创编译,[Linux中国](https://linux.cn/) 荣誉推出
[a]: https://www.2daygeek.com/author/magesh/
[b]: https://github.com/lujun9972
[c]: https://github.com/qianmingtian
[1]: https://www.2daygeek.com/how-to-set-password-complexity-policy-on-linux/
[2]: https://www.2daygeek.com/linux-passwd-chpasswd-command-set-update-change-users-password-in-linux-using-shell-script/
[3]: https://www.2daygeek.com/configure-setup-passwordless-ssh-key-based-authentication-linux/