2018年6月

Bittorrent 已经存在了很长时间,它可以从互联网上共享和下载数据。市场上有大量的 GUI 和 CLI 的 Bittorrent 客户端。有时,你不能坐下来等待你的下载完成。你可能想要立即观看内容。这就是 BTFS 这个不起眼的文件系统派上用场的地方。使用 BTFS,你可以将种子文件或磁力链接挂载为目录,然后在文件树中作为只读目录。这些文件的内容将在程序读取时按需下载。由于 BTFS 在 FUSE 之上运行,因此不需要干预 Linux 内核。

安装 BTFS

BTFS 存在于大多数 Linux 发行版的默认仓库中。

在 Arch Linux 及其变体上,运行以下命令来安装 BTFS。

$ sudo pacman -S btfs

在Debian、Ubuntu、Linux Mint 上:

$ sudo apt-get install btfs

在 Gentoo 上:

# emerge -av btfs

BTFS 也可以使用 Linuxbrew 包管理器进行安装。

$ brew install btfs

用法

BTFS 的使用非常简单。你所要做的就是找到 .torrent 文件或磁力链接,并将其挂载到一个目录中。种子文件或磁力链接的内容将被挂载到你选择的目录内。当一个程序试图访问该文件进行读取时,实际的数据将按需下载。此外,像 lscatcp 这样的工具能按照预期的方式来操作种子。像 vlcmplayer 这样的程序也可以不加修改地工作。玩家甚至不知道实际内容并非物理存在于本地磁盘中,而是根据需要从 peer 中收集。

创建一个目录来挂载 torrent/magnet 链接:

$ mkdir mnt

挂载 torrent/magnet 链接:

$ btfs video.torrent mnt

cd 到目录:

$ cd mnt

然后,开始观看!

$ vlc <path-to-video.mp4>

给 BTFS 一些时间来找到并获取网站 tracker。一旦加载了真实数据,BTFS 将不再需要 tracker。

要卸载 BTFS 文件系统,只需运行以下命令:

$ fusermount -u mnt

现在,挂载目录中的内容将消失。要再次访问内容,你需要按照上面的描述挂载 torrent。

BTFS 会将你的 VLC 或 Mplayer 变成爆米花时间。挂载你最喜爱的电视节目或电影的种子文件或磁力链接,然后开始观看,无需下载整个种子内容或等待下载完成。种子或磁力链接的内容将在程序访问时按需下载。

就是这些了。希望这些有用。还会有更好的东西。敬请关注!

干杯!


via: https://www.ostechnix.com/btfs-a-bittorrent-filesystem-based-on-fuse/

作者:SK 选题:lujun9972 译者:geekpi 校对:wxy

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

Linux 是一个适合每个人的平台。如果你有一份合适的工作,Linux 已经可以满足或超过它的需求,其中一个工作是教育。如果你是一名教师或一名学生,Linux 已经准备好帮助你在几乎任何级别的教育系统领域中畅游。从辅助学习、写作论文、管理课程,到管理整个机构,Linux 已经全部涵盖了。

如果你不确定,请让我介绍一下 Linux 准备好的一些工具。其中一些工具几乎没有学习曲线,而另一些工具则需要一个全面的系统管理员来安装、设置和管理。我们将从简单开始,然后到复杂。

学习辅助工具

每个人的学习方式都有所不同,每个班级都需要不同的学习类型和水平。幸运的是,Linux 有很多学习辅助工具。让我们来看几个例子:

闪卡 —— KWordQuiz(图 1)是适用于 Linux 平台的许多闪卡应用程序之一。KWordQuiz 使用 kvtml 文件格式,你可以下载大量预制的、人们贡献的文件。 KWordQuiz 是 KDE 桌面环境的一部分,但可以安装在其他桌面上(KDE 依赖文件将与闪卡应用程序一起安装)。

语言工具

由于全球化进程,外语已成为教育的重要组成部分。你会发现很多语言工具,包括 Kiten(图 2)—— KDE 桌面的日语汉字浏览器。

如果日文不是你的母语,你可以试试 Jargon Informatique。这本词典完全是法文的,所以如果你对这门语言还不熟悉,你可能要需要 Google 翻译的帮助才能坚持下去。

Writing Aids / Note Taking

Linux 拥有你需要的所有东西比如记录一个主题,撰写那些学期论文。让我们从记笔记开始。如果你熟悉 Microsoft OneNote,你一定会喜欢 BasKet Note Pads。有了这个应用程序,你可以为主题创建 笔记本 basket ,并添加任何东西——注释、链接、图像、交叉引用(到其他笔记本─图 3)、应用程序启动器、从文件加载等等。

你可以创建任意形式的笔记本,可以移动元素来满足你的需求。如果你更喜欢有序的感觉,那么创建一个表状的 basket 来保留那些封装的笔记。

当然,所有 Linux 写作辅助工具都是由 LibreOffice 发展而来。LibreOffice 是大多数 Linux 发行版默认的办公套件,它能打开文本文档、电子表格、演示文稿、数据库、公式和绘图。

在教育环境中使用 LibreOffice 的一个警告是,你很可能不得不将文档以 MS Office 格式保存。

为教育而生的发行版

所有这些都是关于 Linux 面向学生的应用程序,你可以看看专门为教育而开发的一个发行版。最好的是 Edubuntu。这种平易的 Linux 发行版旨在让 Linux 进入学校、家庭和社区。Edubuntu 使用默认的 Ubuntu 桌面(Unity shell)并添加以下软件:

  • KDE 教育套件
  • GCompris
  • Celestia
  • Tux4Kids
  • Epoptes
  • LTSP
  • GBrainy
  • 等等

Edubuntu 并不是唯一的发行版。如果你想测试其他特定于教育的 Linux 发行版,以下是简短列表:

  • Debian-Edu
  • Fedora Education Spin
  • Guadalinux-Edu
  • OpenSuse-Edu
  • Qimo
  • Uberstudent

课堂/机构管理

这是 Linux 平台真正闪耀的地方。有许多专门用于管理的工具。我们先来看看教室特有的工具。

iTalc 是一个强大的课堂教学环境。借助此工具,教师可以查看和控制学生桌面(支持 Linux 和 Windows)。iTalc 系统允许教师查看学生桌面上发生了什么,控制他们的桌面,锁定他们的桌面,对桌面演示,打开或关闭桌面,向学生桌面发送文本消息等等。

aTutor(图 4)是一个开源的学习管理系统(LMS),专注于开发在线课程和电子学习内容。一个老师真正发挥的就是创建和管理在线考试和测验。当然,aTutor 不限于测试的目的,有了这个强大的软件,学生和老师可以享受:

  • 社交网络
  • 配置文件
  • 消息
  • 自适应导航
  • 工作组
  • 文件存储
  • 小组博客
  • 以及更多。

课程资料易于创建和部署(你甚至可以将考试/测验分配给特定的学习小组)。

Moodle 是目前使用最广泛的教育管理软件之一。通过 Moodle,你可以管理、教授、学习甚至参与孩子的教育。这个强大的软件为教师和学生、考试、日历、论坛、文件管理、课程管理(图 5)、通知、进度跟踪、大量注册、批量课程创建、考勤等提供协作工具。

OpenSIS 意即开源学生信息系统,在管理你的教育机构方面做得很好。有一个免费的社区版,但即使使用付费版本,你也可以期待将学区的拥有成本降低高达 75%(与专有解决方案相比)。

OpenSIS 包括以下特点或模块:

  • 出席情况(图 6)
  • 联系信息
  • 学生人口统计
  • 成绩簿
  • 计划
  • 健康记录
  • 报告卡

OpenSIS 有四个版本,在这里查看它们的功能比较。

vufind 是一个优秀的图书馆管理系统,允许学生和教师轻松浏览图书馆资源,例如:

  • 目录记录
  • 本地缓存期刊
  • 数字图书馆项目
  • 机构知识库
  • 机构书目
  • 其他图书馆集合和资源

Vufind 系统允许用户登录,通过认证的用户可以节省资源以便快速回忆,并享受“更像这样”的结果。

这份列表仅仅触及了 Linux 在教育领域可用性的一点皮毛。而且,正如你所期望的那样,每个工具都是高度可定制且开放源代码的 —— 所以如果软件不能精确地满足你的需求,那么你可以自由(在大多数情况下)修改源代码并进行更改。

Linux 与教育齐头并进。无论你是老师,学生还是管理员,你都会找到大量工具来帮助教育机构开放,灵活和强大。


via: https://www.linux.com/news/best-linux-tools-teachers-and-students

作者:Jack Wallen 选题:lujun9972 译者:MjSeven 校对:wxy

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

Go 是一个内置支持并发编程的语言。借助使用 go 关键字去创建 协程 goroutine (轻量级线程)和在 Go 中提供的 使用 信道其它的并发 同步方法,使得并发编程变得很容易、很灵活和很有趣。

另一方面,Go 并不会阻止一些因 Go 程序员粗心大意或者缺乏经验而造成的并发编程错误。在本文的下面部分将展示一些在 Go 编程中常见的并发编程错误,以帮助 Go 程序员们避免再犯类似的错误。

需要同步的时候没有同步

代码行或许 不是按出现的顺序运行的

在下面的程序中有两个错误。

  • 第一,在 main 协程中读取 b 和在新的 协程 中写入 b 可能导致数据争用。
  • 第二,条件 b == true 并不能保证在 main 协程 中的 a != nil。在新的协程中编译器和 CPU 可能会通过 重排序指令 进行优化,因此,在运行时 b 赋值可能发生在 a 赋值之前,在 main 协程 中当 a 被修改后,它将会让部分 a 一直保持为 nil
package main

import (
    "time"
    "runtime"
)

func main() {
    var a []int // nil
    var b bool  // false

    // a new goroutine
    go func () {
        a = make([]int, 3)
        b = true // write b
    }()

    for !b { // read b
        time.Sleep(time.Second)
        runtime.Gosched()
    }
    a[0], a[1], a[2] = 0, 1, 2 // might panic
}

上面的程序或者在一台计算机上运行的很好,但是在另一台上可能会引发异常。或者它可能运行了 N 次都很好,但是可能在第 (N+1) 次引发了异常。

我们将使用 sync 标准包中提供的信道或者同步方法去确保内存中的顺序。例如,

package main

func main() {
    var a []int = nil
    c := make(chan struct{})

    // a new goroutine
    go func () {
        a = make([]int, 3)
        c <- struct{}{}
    }()

    <-c
    a[0], a[1], a[2] = 0, 1, 2
}

使用 time.Sleep 调用去做同步

我们先来看一个简单的例子。

package main

import (
    "fmt"
    "time"
)

func main() {
    var x = 123

    go func() {
        x = 789 // write x
    }()

    time.Sleep(time.Second)
    fmt.Println(x) // read x
}

我们预期程序将打印出 789。如果我们运行它,通常情况下,它确定打印的是 789。但是,这个程序使用的同步方式好吗?No!原因是 Go 运行时并不保证 x 的写入一定会发生在 x 的读取之前。在某些条件下,比如在同一个操作系统上,大部分 CPU 资源被其它运行的程序所占用的情况下,写入 x 可能就会发生在读取 x 之后。这就是为什么我们在正式的项目中,从来不使用 time.Sleep 调用去实现同步的原因。

我们来看一下另外一个示例。

package main

import (
    "fmt"
    "time"
)

var x = 0

func main() {
    var num = 123
    var p = &num

    c := make(chan int)

    go func() {
        c <- *p + x
    }()

    time.Sleep(time.Second)
    num = 789
    fmt.Println(<-c)
}

你认为程序的预期输出是什么?123 还是 789?事实上它的输出与编译器有关。对于标准的 Go 编译器 1.10 来说,这个程序很有可能输出是 123。但是在理论上,它可能输出的是 789,或者其它的随机数。

现在,我们来改变 c <- *p + xc <- *p,然后再次运行这个程序。你将会发现输出变成了 789 (使用标准的 Go 编译器 1.10)。这再次说明它的输出是与编译器相关的。

是的,在上面的程序中存在数据争用。表达式 *p 可能会被先计算、后计算、或者在处理赋值语句 num = 789 时计算。time.Sleep 调用并不能保证 *p 发生在赋值语句处理之前进行。

对于这个特定的示例,我们将在新的协程创建之前,将值保存到一个临时值中,然后在新的协程中使用临时值去消除数据争用。

...
    tmp := *p + x
    go func() {
        c <- tmp
    }()
...

使协程挂起

挂起协程是指让协程一直处于阻塞状态。导致协程被挂起的原因很多。比如,

  • 一个协程尝试从一个 nil 信道中或者从一个没有其它协程给它发送值的信道中检索数据。
  • 一个协程尝试去发送一个值到 nil 信道,或者发送到一个没有其它的协程接收值的信道中。
  • 一个协程被它自己死锁。
  • 一组协程彼此死锁。
  • 当运行一个没有 default 分支的 select 代码块时,一个协程被阻塞,以及在 select 代码块中 case 关键字后的所有信道操作保持阻塞状态。

除了有时我们为了避免程序退出,特意让一个程序中的 main 协程保持挂起之外,大多数其它的协程挂起都是意外情况。Go 运行时很难判断一个协程到底是处于挂起状态还是临时阻塞。因此,Go 运行时并不会去释放一个挂起的协程所占用的资源。

谁先响应谁获胜 的信道使用案例中,如果使用的 future 信道容量不够大,当尝试向 Future 信道发送结果时,一些响应较慢的信道将被挂起。比如,如果调用下面的函数,将有 4 个协程处于永远阻塞状态。

func request() int {
    c := make(chan int)
    for i := 0; i < 5; i++ {
        i := i
        go func() {
            c <- i // 4 goroutines will hang here.
        }()
    }
    return <-c
}

为避免这 4 个协程一直处于挂起状态, c 信道的容量必须至少是 4

实现谁先响应谁获胜的第二种方法 的信道使用案例中,如果将 future 信道用做非缓冲信道,那么有可能这个信息将永远也不会有响应而挂起。例如,如果在一个协程中调用下面的函数,协程可能会挂起。原因是,如果接收操作 <-c 准备就绪之前,五个发送操作全部尝试发送,那么所有的尝试发送的操作将全部失败,因此那个调用者协程将永远也不会接收到值。

func request() int {
    c := make(chan int)
    for i := 0; i < 5; i++ {
        i := i
        go func() {
            select {
            case c <- i:
            default:
            }
        }()
    }
    return <-c
}

将信道 c 变成缓冲信道将保证五个发送操作中的至少一个操作会发送成功,这样,上面函数中的那个调用者协程将不会被挂起。

sync 标准包中拷贝类型值

在实践中,sync 标准包中的类型值不会被拷贝。我们应该只拷贝这个值的指针。

下面是一个错误的并发编程示例。在这个示例中,当调用 Counter.Value 方法时,将拷贝一个 Counter 接收值。作为接收值的一个字段,Counter 接收值的各个 Mutex 字段也会被拷贝。拷贝不是同步发生的,因此,拷贝的 Mutex 值可能会出错。即便是没有错误,拷贝的 Counter 接收值的访问保护也是没有意义的。

import "sync"

type Counter struct {
    sync.Mutex
    n int64
}

// This method is okay.
func (c *Counter) Increase(d int64) (r int64) {
    c.Lock()
    c.n += d
    r = c.n
    c.Unlock()
    return
}

// The method is bad. When it is called, a Counter
// receiver value will be copied.
func (c Counter) Value() (r int64) {
    c.Lock()
    r = c.n
    c.Unlock()
    return
}

我们只需要改变 Value 接收类型方法为指针类型 *Counter,就可以避免拷贝 Mutex 值。

在官方的 Go SDK 中提供的 go vet 命令将会报告潜在的错误值拷贝。

在错误的地方调用 sync.WaitGroup 的方法

每个 sync.WaitGroup 值维护一个内部计数器,这个计数器的初始值为 0。如果一个 WaitGroup 计数器的值是 0,调用 WaitGroup 值的 Wait 方法就不会被阻塞,否则,在计数器值为 0 之前,这个调用会一直被阻塞。

为了让 WaitGroup 值的使用有意义,当一个 WaitGroup 计数器值为 0 时,必须在相应的 WaitGroup 值的 Wait 方法调用之前,去调用 WaitGroup 值的 Add 方法。

例如,下面的程序中,在不正确位置调用了 Add 方法,这将使最后打印出的数字不总是 100。事实上,这个程序最后打印的数字可能是在 [0, 100) 范围内的一个随意数字。原因就是 Add 方法的调用并不保证一定会发生在 Wait 方法调用之前。

package main

import (
    "fmt"
    "sync"
    "sync/atomic"
)

func main() {
    var wg sync.WaitGroup
    var x int32 = 0
    for i := 0; i < 100; i++ {
        go func() {
            wg.Add(1)
            atomic.AddInt32(&x, 1)
            wg.Done()
        }()
    }

    fmt.Println("To wait ...")
    wg.Wait()
    fmt.Println(atomic.LoadInt32(&x))
}

为让程序的表现符合预期,在 for 循环中,我们将把 Add 方法的调用移动到创建的新协程的范围之外,修改后的代码如下。

...
    for i := 0; i < 100; i++ {
        wg.Add(1)
        go func() {
            atomic.AddInt32(&x, 1)
            wg.Done()
        }()
    }
...

不正确使用 futures 信道

信道使用案例 的文章中,我们知道一些函数将返回 futures 信道。假设 fafb 就是这样的两个函数,那么下面的调用就使用了不正确的 future 参数。

doSomethingWithFutureArguments(<-fa(), <-fb())

在上面的代码行中,两个信道接收操作是顺序进行的,而不是并发的。我们做如下修改使它变成并发操作。

ca, cb := fa(), fb()
doSomethingWithFutureArguments(<-c1, <-c2)

没有等协程的最后的活动的发送结束就关闭信道

Go 程序员经常犯的一个错误是,还有一些其它的协程可能会发送值到以前的信道时,这个信道就已经被关闭了。当这样的发送(发送到一个已经关闭的信道)真实发生时,将引发一个异常。

这种错误在一些以往的著名 Go 项目中也有发生,比如在 Kubernetes 项目中的 这个 bug这个 bug

如何安全和优雅地关闭信道,请阅读 这篇文章

在值上做 64 位原子操作时没有保证值地址 64 位对齐

到目前为止(Go 1.10),在标准的 Go 编译器中,在一个 64 位原子操作中涉及到的值的地址要求必须是 64 位对齐的。如果没有对齐则导致当前的协程异常。对于标准的 Go 编译器来说,这种失败仅发生在 32 位的架构上。请阅读 内存布局 去了解如何在一个 32 位操作系统上保证 64 位对齐。

没有注意到大量的资源被 time.After 函数调用占用

time 标准包中的 After 函数返回 一个延迟通知的信道。这个函数在某些情况下用起来很便捷,但是,每次调用它将创建一个 time.Timer 类型的新值。这个新创建的 Timer 值在通过传递参数到 After 函数指定期间保持激活状态,如果在这个期间过多的调用了该函数,可能会有太多的 Timer 值保持激活,这将占用大量的内存和计算资源。

例如,如果调用了下列的 longRunning 函数,将在一分钟内产生大量的消息,然后在某些周期内将有大量的 Timer 值保持激活,即便是大量的这些 Timer 值已经没用了也是如此。

import (
    "fmt"
    "time"
)

// The function will return if a message arrival interval
// is larger than one minute.
func longRunning(messages <-chan string) {
    for {
        select {
        case <-time.After(time.Minute):
            return
        case msg := <-messages:
            fmt.Println(msg)
        }
    }
}

为避免在上述代码中创建过多的 Timer 值,我们将使用一个单一的 Timer 值去完成同样的任务。

func longRunning(messages <-chan string) {
    timer := time.NewTimer(time.Minute)
    defer timer.Stop()

    for {
        select {
        case <-timer.C:
            return
        case msg := <-messages:
            fmt.Println(msg)
            if !timer.Stop() {
                <-timer.C
            }
        }

        // The above "if" block can also be put here.

        timer.Reset(time.Minute)
    }
}

不正确地使用 time.Timer

在最后,我们将展示一个符合语言使用习惯的 time.Timer 值的使用示例。需要注意的一个细节是,那个 Reset 方法总是在停止或者 time.Timer 值释放时被使用。

select 块的第一个 case 分支的结束部分,time.Timer 值被释放,因此,我们不需要去停止它。但是必须在第二个分支中停止定时器。如果在第二个分支中 if 代码块缺失,它可能至少在 Reset 方法调用时,会(通过 Go 运行时)发送到 timer.C 信道,并且那个 longRunning 函数可能会早于预期返回,对于 Reset 方法来说,它可能仅仅是重置内部定时器为 0,它将不会清理(耗尽)那个发送到 timer.C 信道的值。

例如,下面的程序很有可能在一秒内而不是十秒时退出。并且更重要的是,这个程序并不是 DRF 的(LCTT 译注:data race free,多线程程序的一种同步程度)。

package main

import (
    "fmt"
    "time"
)

func main() {
    start := time.Now()
    timer := time.NewTimer(time.Second/2)
    select {
    case <-timer.C:
    default:
        time.Sleep(time.Second) // go here
    }
    timer.Reset(time.Second * 10)
    <-timer.C
    fmt.Println(time.Since(start)) // 1.000188181s
}

time.Timer 的值不再被其它任何一个东西使用时,它的值可能被停留在一种非停止状态,但是,建议在结束时停止它。

在多个协程中如果不按建议使用 time.Timer 值并发,可能会有 bug 隐患。

我们不应该依赖一个 Reset 方法调用的返回值。Reset 方法返回值的存在仅仅是为了兼容性目的。


via: https://go101.org/article/concurrent-common-mistakes.html

作者:<go101.org> 译者:qhwdw 校对:wxy

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

许多组织正在争先恐后地了解隐私法的变化如何影响他们的工作。

2018 年 5 月 25 日, 通用数据保护条例 General Data Protection Regulation, GDPR 开始生效。欧盟出台的该条例将在全球范围内对企业如何保护个人数据产生重大影响。影响也会波及到开源项目以及开源社区。

GDPR 概述

GDPR 于 2016 年 4 月 14 日在欧盟议会通过,从 2018 年 5 月 25 日起开始生效。GDPR 用于替代 95/46/EC 号 数据保护指令 Data Protection Directive ,该指令被设计用于“协调欧洲各国数据隐私法,保护和授权全体欧盟公民的数据隐私,改变欧洲范围内企业处理数据隐私的方式”。

GDPR 目标是在当前日益数据驱动的世界中保护欧盟公民的个人数据。

它对谁生效

GDPR 带来的最大改变之一是影响范围的扩大。不管企业本身是否位于欧盟,只要涉及欧盟公民个人数据的处理,GDPR 就会对其生效。

大部分提及 GDPR 的网上文章关注销售商品或服务的公司,但关注影响范围时,我们也不要忘记开源项目。有几种不同的类型,包括运营开源社区的(营利性)软件公司和非营利性组织(例如,开源软件项目及其社区)。对于面向全球的社区,几乎总是会有欧盟居民加入其中。

如果一个面向全球的社区有对应的在线平台,包括网站、论坛和问题跟踪系统等,那么很可能会涉及欧盟居民的个人数据处理,包括姓名、邮箱地址甚至更多。这些处理行为都需要遵循 GDPR。

GDPR 带来的改变及其影响

相比被替代的指令,GDPR 带来了很多改变,强化了对欧盟居民数据和隐私的保护。正如前文所说,一些改变给社区带来了直接的影响。我们来看看若干改变。

授权

我们假设社区为成员提供论坛,网站中包含若干个用于注册的表单。要遵循 GDPR,你不能再使用冗长、无法辨识的隐私策略和条件条款。无论是每种特殊用途,在论坛注册或使用网站表单注册,你都需要获取明确的授权。授权必须是无条件的、具体的、通知性的以及无歧义的。

以表单为例,你需要提供一个复选框,处于未选中状态并给出个人数据用途的明确说明,一般是当前使用的隐私策略和条件条款附录的超链接。

访问权

GDPR 赋予欧盟公民更多的权利。其中一项权利是向企业查询个人数据包括哪些,保存在哪里;如果 数据相关人 data subject (例如欧盟公民)提出获取相应数据副本的需求,企业还应免费提供数字形式的数据。

遗忘权

欧盟居民还从 GDPR 获得了“遗忘权”,即数据擦除。该权利是指,在一定限制条件下,企业必须删除个人数据,甚至可能停止其自身或第三方机构后续处理申请人的数据。

上述三种改变要求你的平台软件也要遵循 GDPR 的某些方面。需要提供特定的功能,例如获取并保存授权,提取数据并向数据相关人提供数字形式的副本,以及删除数据相关人对应的数据等。

泄露通知

在 GDPR 看来,不经数据相关人授权情况下使用或偷取个人数据都被视为 数据泄露 data breach 。一旦发现,你应该在 72 小时内通知社区成员,除非这些个人数据不太可能给 自然人 natural persons 的权利与自由带来风险。GDPR 强制要求执行泄露通知。

披露记录

企业负责提供一份记录,用于详细披露个人数据处理的过程和目的等。该记录用于证明企业遵从 GDPR 要求,维护了一份个人数据处理行为的记录;同时该记录也用于审计。

罚款

不遵循 GDPR 的企业最高可面临全球年收入总额 4% 或 2000 万欧元 (取两者较大值)的罚款。根据 GDPR,“最高处罚针对严重的侵权行为,包括未经用户充分授权的情况下处理数据,以及违反设计理念中核心隐私部分”。

补充说明

本文不应用于法律建议或 GDPR 合规的指导书。我提到了可能对开源社区有影响的条约部分,希望引起大家对 GDPR 及其影响的关注。当然,条约包含了更多你需要了解和可能需要遵循的条款。

你自己也可能认识到,当运营一个面向全球的社区时,需要行动起来使其遵循 GDPR。如果在社区中,你已经遵循包括 ISO 27001,NIST 和 PCI DSS 在内的健壮安全标准,你已经先人一步。

可以从如下网站/资源中获取更多关于 GDPR 的信息:

关于作者

Robin Muilwijk 是一名互联网和电子政务顾问,在 Red Hat 旗下在线发布平台 Opensource.com 担任社区版主,在 Open Organization 担任大使。此外,Robin 还是 eZ 社区董事会成员,eZ 系统 社区的管理员。Robin 活跃在社交媒体中,促进和支持商业和生活领域的开源项目。可以在 Twitter 上关注 Robin Muilwijk 以获取更多关于他的信息。更多关于我的信息


via: https://opensource.com/article/18/4/gdpr-impact

作者: Robin Muilwijk 选题者: lujun9972 译者: pinewall 校对: wxy

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

在这个 Git 入门系列的第三篇中,我们来学习一下如何添加和删除 Git 分支。

在本系列的前两篇文章中,我们开始使用 Git,学会如何克隆项目,修改、增加和删除内容。在这第三篇文章中,我将介绍 Git 分支,为何以及如何使用分支。

 title=

不妨用树来描绘 Git 仓库。图中的树有很多分支,或长或短,或从树干延伸或从其它分支延伸。在这里,我们用树干比作仓库的 master 分支,其中 master 代指 ”master 分支”,是 Git 仓库的中心分支或第一个分支。为简单起见,我们假设 master 是树干,其它分支都是从该分支分出的。

为何在 Git 仓库中使用分支

使用分支的主要理由为:

  • 如果你希望为项目增加新特性,但很可能会影响当前可正常工作的代码。对于该项目的活跃用户而言,这是很糟糕的事情。与其将特性加入到其它人正在使用的 master 分支,更好的方法是在仓库的其它分支中变更代码,下面会给出具体的工作方式。
  • 更重要的是,Git 其设计用于协作。如果所有人都在你代码仓库的 master 分支上操作,会引发很多混乱。对编程语言或项目的知识和阅历因人而异;有些人可能会编写有错误或缺陷的代码,也可能会编写你觉得不适合该项目的代码。使用分支可以让你核验他人的贡献并选择适合的加入到项目中。(这里假设你是代码库唯一的所有者,希望对增加到项目中的代码有完全的控制。在真实的项目中,代码库有多个具有合并代码权限的所有者)

创建分支

让我们回顾本系列上一篇文章,看一下在我们的 Demo 目录中分支是怎样的。如果你没有完成上述操作,请按照文章中的指示从 GitHub 克隆代码并进入 Demo 目录。运行如下命令:

pwd
git branch
ls -la

pwd 命令(是当前工作目录的英文缩写)返回当前你所处的目录(以便确认你在 Demo 目录中),git branch 列出该项目在你主机上的全部分支,ls -la 列出当前目录下的所有文件。你的终端输出类似于:

 title=

master 分支中,只有一个文件 README.md。(Git 会友好地忽略掉其它目录和文件。)

接下来,运行如下命令:

git status
git checkout -b myBranch
git status

第一条命令 git status 告知你当前位于 branch master,(就像在终端中看到的那样)它与 origin/master 处于同步状态,这意味着 master 分支的本地副本中的全部文件也出现在 GitHub 中。两份副本没有差异,所有的提交也是一致的。

下一条命令 git checkout -b myBranch 中的 -b 告知 Git 创建一个名为 myBranch 的新分支,然后 checkout 命令将我们切换到新创建的分支。运行第三条命令 git status 确保你已经位于刚创建的分支下。

如你所见,git status 告知你当前处于 myBranch 分支,没有变更需要提交。这是因为我们既没有增加新文件,也没有修改已有文件。

 title=

如果希望以可视化的方式查看分支,可以运行 gitk 命令。如果遇到报错 bash: gitk: command not found...,请先安装 gitk 软件包(找到你操作系统对应的安装文档,以获得安装方式)。

(LCTT 译注:需要在有 X 服务器的终端运行 gitk,否则会报错)

下图展示了我们在 Demo 项目中的所作所为:你最后一次提交(的对应信息)是 Delete file.txt,在此之前有三次提交。当前的提交用黄点标注,之前的提交用蓝点标注,黄点和 Delete file.txt 之间的三个方块展示每个分支所在的位置(或者说每个分支中的最后一次提交的位置)。由于 myBranch 刚创建,提交状态与 master 分支及其对应的记为 remotes/origin/master 的远程 master 分支保持一致。(非常感谢来自 Red Hat 的 Peter Savage 让我知道 gitk 这个工具)

 title=

下面让我们在 myBranch 分支下创建一个新文件并观察终端输出。运行如下命令:

echo "Creating a newFile on myBranch" > newFile
cat newFile
git status

第一条命令中的 echo 创建了名为 newFile 的文件,接着 cat newFile 打印出文件内容,最后 git status 告知你我们 myBranch 分支的当前状态。在下面的终端输出中,Git 告知 myBranch 分支下有一个名为 newFile 的文件当前处于 untracked 状态。这表明我们没有让 Git 追踪发生在文件 newFile 上的变更。

 title=

下一步是增加文件,提交变更并将 newFile 文件推送至 myBranch 分支(请回顾本系列上一篇文章获得更多细节)。

git add newFile
git commit -m "Adding newFile to myBranch"
git push origin myBranch

在上述命令中,push 命令使用的分支参数为 myBranch 而不是 master。Git 添加 newFile 并将变更推送到你 GitHub 账号下的 Demo 仓库中,告知你在 GitHub 上创建了一个与你本地副本分支 myBranch 一样的新分支。终端输出截图给出了运行命令的细节及命令输出。

 title=

当你访问 GitHub 时,在分支选择的下拉列表中可以发现两个可供选择的分支。

 title=

点击 myBranch 切换到 myBranch 分支,你可以看到在此分支上新增的文件。

 title=

截至目前,我们有两个分支:一个是 master 分支,只有一个 README.md 文件;另一个是 myBranch 分支,有两个文件。

你已经知道如何创建分支了,下面我们再创建一个分支。输入如下命令:

git checkout master
git checkout -b myBranch2
touch newFile2
git add newFile2
git commit -m "Adding newFile2 to myBranch2"
git push origin myBranch2

我不再给出终端输出,需要你自己尝试,但你可以在 GitHub 代码库 中验证你的结果。

删除分支

由于我们增加了两个分支,下面删除其中的一个(myBranch),包括两步:

  1. 删除本地分支 你不能删除正在操作的分支,故切换到 master 分支 (或其它你希望保留的分支),命令及终端输出如下:

git branch 可以列出可用的分支,使用 checkout 切换到 master 分支,然后使用 git branch -D myBranch 删除该分支。再次运行 git branch 检查是否只剩下两个分支(而不是三个)。

  1. 删除 GitHub 上的分支 使用如下命令删除 myBranch 的远程分支:
git push origin :myBranch

 title=

上面 push 命令中分支名称前面的冒号(:)告知 GitHub 删除分支。另一种写法为:

git push -d origin myBranch

其中 -d (也可以用 --delete) 也用于告知 GitHub 删除你的分支。

我们学习了 Git 分支的使用,在本系列的下一篇文章中,我们将介绍如何执行 fetchrebase 操作,对于多人同时的贡献的项目而言,这是很必须学会的。


via: https://opensource.com/article/18/5/git-branching

作者:Kedar Vijay Kulkarni 选题:lujun9972 译者:pinewall 校对:wxy

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

将您的计算机变成一个多任务的动力室。

你是否有过这种感觉,你的主机运行速度没有预期的那么快?我也曾经有过这种感觉,直到我发现了 GNU Parallel。

GNU Parallel 是一个 shell 工具,可以并行执行任务。它可以解析多种输入,让你可以同时在多份数据上运行脚本或命令。你终于可以使用全部的 CPU 了!

如果你用过 xargs,上手 Parallel 几乎没有难度。如果没有用过,这篇教程会告诉你如何使用,同时给出一些其它的用例。

安装 GNU Parallel

GNU Parallel 很可能没有预装在你的 Linux 或 BSD 主机上,你可以从软件源中安装。以 Fedora 为例:

$ sudo dnf install parallel

对于 NetBSD:

# pkg_add parallel

如果各种方式都不成功,请参考项目主页

从串行到并行

正如其名称所示,Parallel 的强大之处是以并行方式执行任务;而我们中不少人平时仍然以串行方式运行任务。

当你对多个对象执行某个命令时,你实际上创建了一个任务队列。一部分对象可以被命令处理,剩余的对象需要等待,直到命令处理它们。这种方式是低效的。只要数据够多,总会形成任务队列;但与其只使用一个任务队列,为何不使用多个更小规模的任务队列呢?

假设你有一个图片目录,你希望将目录中的图片从 JEEG 格式转换为 PNG 格式。有多种方法可以完成这个任务。可以手动用 GIMP 打开每个图片,输出成新格式,但这基本是最差的选择,费时费力。

上述方法有一个漂亮且简洁的变种,即基于 shell 的方案:

$ convert 001.jpeg 001.png
$ convert 002.jpeg 002.png
$ convert 003.jpeg 003.png
... 略 ...

对于初学者而言,这是一个不小的转变,而且看起来是个不小的改进。不再需要图像界面和不断的鼠标点击,但仍然是费力的。

进一步改进:

$ for i in *jpeg; do convert $i $i.png ; done

至少,这一步设置好任务执行,让你节省时间去做更有价值的事情。但问题来了,这仍然是串行操作;一张图片转换完成后,队列中的下一张进行转换,依此类推直到全部完成。

使用 Parallel:

$ find . -name "*jpeg" | parallel -I% --max-args 1 convert % %.png

这是两条命令的组合:find 命令,用于收集需要操作的对象;parallel 命令,用于对象排序并确保每个对象按需处理。

  • find . -name "*jpeg" 查找当前目录下以 jpeg 结尾的所有文件。
  • parallel 调用 GNU Parallel。
  • -I% 创建了一个占位符 %,代表 find 传递给 Parallel 的内容。如果不使用占位符,你需要对 find 命令的每一个结果手动编写一个命令,而这恰恰是你想要避免的。
  • --max-args 1 给出 Parallel 从队列获取新对象的速率限制。考虑到 Parallel 运行的命令只需要一个文件输入,这里将速率限制设置为 1。假如你需要执行更复杂的命令,需要两个文件输入(例如 cat 001.txt 002.txt > new.txt),你需要将速率限制设置为 2。
  • convert % %.png 是你希望 Parallel 执行的命令。

组合命令的执行效果如下:find 命令收集所有相关的文件信息并传递给 parallel,后者(使用当前参数)启动一个任务,(无需等待任务完成)立即获取参数行中的下一个参数(LCTT 译注:管道输出的每一行对应 parallel 的一个参数,所有参数构成参数行);只要你的主机没有瘫痪,Parallel 会不断做这样的操作。旧任务完成后,Parallel 会为分配新任务,直到所有数据都处理完成。不使用 Parallel 完成任务大约需要 10 分钟,使用后仅需 3 至 5 分钟。

多个输入

只要你熟悉 findxargs (整体被称为 GNU 查找工具,或 findutils),find 命令是一个完美的 Parallel 数据提供者。它提供了灵活的接口,大多数 Linux 用户已经很习惯使用,即使对于初学者也很容易学习。

find 命令十分直截了当:你向 find 提供搜索路径和待查找文件的一部分信息。可以使用通配符完成模糊搜索;在下面的例子中,星号匹配任何字符,故 find 定位(文件名)以字符 searchterm 结尾的全部文件:

$ find /path/to/directory -name "*searchterm"

默认情况下,find 逐行返回搜索结果,每个结果对应 1 行:

$ find ~/graphics -name "*jpg"
/home/seth/graphics/001.jpg
/home/seth/graphics/cat.jpg
/home/seth/graphics/penguin.jpg
/home/seth/graphics/IMG_0135.jpg

当使用管道将 find 的结果传递给 parallel 时,每一行中的文件路径被视为 parallel 命令的一个参数。另一方面,如果你需要使用命令处理多个参数,你可以改变队列数据传递给 parallel 的方式。

下面先给出一个不那么实际的例子,后续会做一些修改使其更加有意义。如果你安装了 GNU Parallel,你可以跟着这个例子操作。

假设你有 4 个文件,按照每行一个文件的方式列出,具体如下:

$ echo ada > ada ; echo lovelace > lovelace
$ echo richard > richard ; echo stallman > stallman
$ ls -1
ada
lovelace
richard
stallman

你需要将两个文件合并成第三个文件,后者同时包含前两个文件的内容。这种情况下,Parallel 需要访问两个文件,使用 -I% 变量的方式不符合本例的预期。

Parallel 默认情况下读取 1 个队列对象:

$ ls -1 | parallel echo
ada
lovelace
richard
stallman

现在让 Parallel 每个任务使用 2 个队列对象:

$ ls -1 | parallel --max-args=2 echo
ada lovelace
richard stallman

现在,我们看到行已经并合并;具体而言,ls -1 的两个查询结果会被同时传送给 Parallel。传送给 Parallel 的参数涉及了任务所需的 2 个文件,但目前还只是 1 个有效参数:(对于两个任务分别为)“ada lovelace” 和 “richard stallman”。你真正需要的是每个任务对应 2 个独立的参数。

值得庆幸的是,Parallel 本身提供了上述所需的解析功能。如果你将 --max-args 设置为 2,那么 {1}{2} 这两个变量分别代表传入参数的第一和第二部分:

$ ls -1 | parallel --max-args=2 cat {1} {2} ">" {1}_{2}.person

在上面的命令中,变量 {1} 值为 adarichard (取决于你选取的任务),变量 {2} 值为 lovelacestallman。通过使用重定向符号(放到引号中,防止被 Bash 识别,以便 Parallel 使用),(两个)文件的内容被分别重定向至新文件 ada_lovelace.personrichard_stallman.person

$ ls -1
ada
ada_lovelace.person
lovelace
richard
richard_stallman.person
stallman

$ cat ada_*person
ada lovelace
$ cat ri*person
richard stallman

如果你整天处理大量几百 MB 大小的日志文件,那么(上述)并行处理文本的方法对你帮忙很大;否则,上述例子只是个用于上手的示例。

然而,这种处理方法对于很多文本处理之外的操作也有很大帮助。下面是来自电影产业的真实案例,其中需要将一个目录中的视频文件和(对应的)音频文件进行合并。

$ ls -1
12_LS_establishing-manor.avi
12_wildsound.flac
14_butler-dialogue-mixed.flac
14_MS_butler.avi
...略...

使用同样的方法,使用下面这个简单命令即可并行地合并文件:

$ ls -1 | parallel --max-args=2 ffmpeg -i {1} -i {2} -vcodec copy -acodec copy {1}.mkv

简单粗暴的方式

上述花哨的输入输出处理不一定对所有人的口味。如果你希望更直接一些,可以将一堆命令甩给 Parallel,然后去干些其它事情。

首先,需要创建一个文本文件,每行包含一个命令:

$ cat jobs2run
bzip2 oldstuff.tar
oggenc music.flac
opusenc ambiance.wav
convert bigfile.tiff small.jpeg
ffmepg -i foo.avi -v:b 12000k foo.mp4
xsltproc --output build/tmp.fo style/dm.xsl src/tmp.xml
bzip2 archive.tar

接着,将文件传递给 Parallel:

$ parallel --jobs 6 < jobs2run

现在文件中对应的全部任务都在被 Parallel 执行。如果任务数量超过允许的数目(LCTT 译注:应该是 --jobs 指定的数目或默认值),Parallel 会创建并维护一个队列,直到任务全部完成。

更多内容

GNU Parallel 是个强大而灵活的工具,还有很多很多用例无法在本文中讲述。工具的 man 页面提供很多非常酷的例子可供你参考,包括通过 SSH 远程执行和在 Parallel 命令中使用 Bash 函数等。YouTube 上甚至有一个系列,包含大量操作演示,让你可以直接从 GNU Parallel 团队学习。GNU Paralle 的主要维护者还发布了官方使用指导手册,可以从 Lulu.com 获取。

GNU Parallel 有可能改变你完成计算的方式;即使没有,也会至少改变你主机花在计算上的时间。马上上手试试吧!


via: https://opensource.com/article/18/5/gnu-parallel

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

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