无密码验证：服务器

![](/data/attachment/album/201806/15/000057ms1oszswwhooiiis.jpg)

无密码验证可以让你只输入一个 email 而无需输入密码即可登入系统。这是一种比传统的电子邮件/密码验证方式登入更安全的方法。

下面我将为你展示，如何在 [Go](https://golang.org/) 中实现一个 HTTP API 去提供这种服务。

### 流程

* 用户输入他的电子邮件地址。
* 服务器创建一个临时的一次性使用的代码（就像一个临时密码一样）关联到用户，然后给用户邮箱中发送一个“魔法链接”。
* 用户点击魔法链接。
* 服务器提取魔法链接中的代码，获取关联的用户，并且使用一个新的 JWT 重定向到客户端。
* 在每次有新请求时，客户端使用 JWT 去验证用户。

### 必需条件

* 数据库：我们为这个服务使用了一个叫 [CockroachDB](https://www.cockroachlabs.com/) 的 SQL 数据库。它非常像 postgres，但它是用 Go 写的。
* SMTP 服务器：我们将使用一个第三方的邮件服务器去发送邮件。开发的时我们使用 [mailtrap](https://mailtrap.io/)。Mailtrap 发送所有的邮件到它的收件箱，因此，你在测试时不需要创建多个假邮件帐户。

从 [Go 的主页](https://golang.org/dl/) 上安装它，然后使用 `go version`（1.10.1 atm）命令去检查它能否正常工作。

从 [CockroachDB 的主页](https://www.cockroachlabs.com/docs/stable/install-cockroachdb.html) 上下载它，展开它并添加到你的 `PATH` 变量中。使用 `cockroach version`（2.0 atm）命令检查它能否正常工作。

### 数据库模式

现在，我们在 `GOPATH` 目录下为这个项目创建一个目录，然后使用 `cockroach start` 启动一个新的 CockroachDB 节点：

```
cockroach start --insecure --host 127.0.0.1

```

它会输出一些内容，找到 SQL 地址行，它将显示像 `postgresql://root@127.0.0.1:26257?sslmode=disable` 这样的内容。稍后我们将使用它去连接到数据库。

使用如下的内容去创建一个 `schema.sql` 文件。

```
DROP DATABASE IF EXISTS passwordless_demo CASCADE;
CREATE DATABASE IF NOT EXISTS passwordless_demo;
SET DATABASE = passwordless_demo;

CREATE TABLE IF NOT EXISTS users (
    id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
    email STRING UNIQUE,
    username STRING UNIQUE
);

CREATE TABLE IF NOT EXISTS verification_codes (
    id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
    user_id UUID NOT NULL REFERENCES users ON DELETE CASCADE,
    created_at TIMESTAMPTZ NOT NULL DEFAULT now()
);

INSERT INTO users (email, username) VALUES
    ('john@passwordless.local', 'john_doe');

```

这个脚本创建了一个名为 `passwordless_demo` 的数据库、两个名为 `users` 和 `verification_codes` 的表，以及为了稍后测试而插入的一些假用户。每个验证代码都与用户关联并保存创建时间，以用于去检查验证代码是否过期。

在另外的终端中使用 `cockroach sql` 命令去运行这个脚本：

```
cat schema.sql | cockroach sql --insecure

```

### 环境配置

需要配置两个环境变量：`SMTP_USERNAME` 和 `SMTP_PASSWORD`，你可以从你的 mailtrap 帐户中获得它们。将在我们的程序中用到它们。

### Go 依赖

我们需要下列的 Go 包：

* [github.com/lib/pq](https://github.com/lib/pq)：它是 CockroachDB 使用的 postgres 驱动
* [github.com/matryer/way](https://github.com/matryer/way): 路由器
* [github.com/dgrijalva/jwt-go](https://github.com/dgrijalva/jwt-go): JWT 实现

```
go get -u github.com/lib/pq
go get -u github.com/matryer/way
go get -u github.com/dgrijalva/jwt-go

```

### 代码

#### 初始化函数

创建 `main.go` 并且通过 `init` 函数里的环境变量中取得一些配置来启动。

```
var config struct {
    port        int
    appURL      *url.URL
    databaseURL string
    jwtKey      []byte
    smtpAddr    string
    smtpAuth    smtp.Auth
}

func init() {
    config.port, _ = strconv.Atoi(env("PORT", "80"))
    config.appURL, _ = url.Parse(env("APP_URL", "http://localhost:"+strconv.Itoa(config.port)+"/"))
    config.databaseURL = env("DATABASE_URL", "postgresql://root@127.0.0.1:26257/passwordless_demo?sslmode=disable")
    config.jwtKey = []byte(env("JWT_KEY", "super-duper-secret-key"))
    smtpHost := env("SMTP_HOST", "smtp.mailtrap.io")
    config.smtpAddr = net.JoinHostPort(smtpHost, env("SMTP_PORT", "25"))
    smtpUsername, ok := os.LookupEnv("SMTP_USERNAME")
    if !ok {
        log.Fatalln("could not find SMTP_USERNAME on environment variables")
    }
    smtpPassword, ok := os.LookupEnv("SMTP_PASSWORD")
    if !ok {
        log.Fatalln("could not find SMTP_PASSWORD on environment variables")
    }
    config.smtpAuth = smtp.PlainAuth("", smtpUsername, smtpPassword, smtpHost)
}

func env(key, fallbackValue string) string {
    v, ok := os.LookupEnv(key)
    if !ok {
        return fallbackValue
    }
    return v
}

```

* `appURL` 将去构建我们的 “魔法链接”。
* `port` 将要启动的 HTTP 服务器。
* `databaseURL` 是 CockroachDB 地址，我添加 `/passwordless_demo` 前面的数据库地址去表示数据库名字。
* `jwtKey` 用于签名 JWT。
* `smtpAddr` 是 `SMTP_HOST` + `SMTP_PORT` 的联合；我们将使用它去发送邮件。
* `smtpUsername` 和 `smtpPassword` 是两个必需的变量。
* `smtpAuth` 也是用于发送邮件。

`env` 函数允许我们去获得环境变量，不存在时返回一个回退值。

#### 主函数

```
var db *sql.DB

func main() {
    var err error
    if db, err = sql.Open("postgres", config.databaseURL); err != nil {
        log.Fatalf("could not open database connection: %v\n", err)
    }
    defer db.Close()
    if err = db.Ping(); err != nil {
        log.Fatalf("could not ping to database: %v\n", err)
    }

    router := way.NewRouter()
    router.HandleFunc("POST", "/api/users", jsonRequired(createUser))
    router.HandleFunc("POST", "/api/passwordless/start", jsonRequired(passwordlessStart))
    router.HandleFunc("GET", "/api/passwordless/verify_redirect", passwordlessVerifyRedirect)
    router.Handle("GET", "/api/auth_user", authRequired(getAuthUser))

    addr := fmt.Sprintf(":%d", config.port)
    log.Printf("starting server at %s \n", config.appURL)
    log.Fatalf("could not start server: %v\n", http.ListenAndServe(addr, router))
}

```

首先，打开数据库连接。记得要加载驱动。

```
import (
    _ "github.com/lib/pq"
)

```

然后，我们创建路由器并定义一些端点。对于无密码流程来说，我们使用两个端点：`/api/passwordless/start` 发送魔法链接，和 `/api/passwordless/verify_redirect` 用 JWT 响应。

最后，我们启动服务器。

你可以创建空处理程序和中间件去测试服务器启动。

```
func createUser(w http.ResponseWriter, r *http.Request) {
    http.Error(w, http.StatusText(http.StatusNotImplemented), http.StatusNotImplemented)
}

func passwordlessStart(w http.ResponseWriter, r *http.Request) {
    http.Error(w, http.StatusText(http.StatusNotImplemented), http.StatusNotImplemented)
}

func passwordlessVerifyRedirect(w http.ResponseWriter, r *http.Request) {
    http.Error(w, http.StatusText(http.StatusNotImplemented), http.StatusNotImplemented)
}

func getAuthUser(w http.ResponseWriter, r *http.Request) {
    http.Error(w, http.StatusText(http.StatusNotImplemented), http.StatusNotImplemented)
}

func jsonRequired(next http.HandlerFunc) http.HandlerFunc {
    return func(w http.ResponseWriter, r *http.Request) {
        next(w, r)
    }
}

func authRequired(next http.HandlerFunc) http.HandlerFunc {
    return func(w http.ResponseWriter, r *http.Request) {
        next(w, r)
    }
}

```

接下来：

```
go build
./passwordless-demo

```

我们在目录中有了一个 “passwordless-demo”，但是你的目录中可能与示例不一样，`go build` 将创建一个同名的可执行文件。如果你没有关闭前面的 cockroach 节点，并且你正确配置了 `SMTP_USERNAME` 和 `SMTP_PASSWORD` 变量，你将看到命令 `starting server at http://localhost/` 没有错误输出。

#### 请求 JSON 的中间件

端点需要从请求体中解码 JSON，因此要确保请求是 `application/json` 类型。因为它是一个通用的东西，我将它解耦到中间件。

```
func jsonRequired(next http.HandlerFunc) http.HandlerFunc {
    return func(w http.ResponseWriter, r *http.Request) {
        ct := r.Header.Get("Content-Type")
        isJSON := strings.HasPrefix(ct, "application/json")
        if !isJSON {
            respondJSON(w, "JSON body required", http.StatusUnsupportedMediaType)
            return
        }
        next(w, r)
    }
}

```

实现很容易。首先它从请求头中获得内容的类型，然后检查它是否是以 “application/json” 开始，如果不是则以 `415 Unsupported Media Type` 提前返回。

#### 响应 JSON 的函数

以 JSON 响应是非常通用的做法，因此我把它提取到函数中。

```
func respondJSON(w http.ResponseWriter, payload interface{}, code int) {
    switch value := payload.(type) {
    case string:
        payload = map[string]string{"message": value}
    case int:
        payload = map[string]int{"value": value}
    case bool:
        payload = map[string]bool{"result": value}
    }
    b, err := json.Marshal(payload)
    if err != nil {
        respondInternalError(w, fmt.Errorf("could not marshal response payload: %v", err))
        return
    }
    w.Header().Set("Content-Type", "application/json; charset=utf-8")
    w.WriteHeader(code)
    w.Write(b)
}

```

首先，对原始类型做一个类型判断，并将它们封装到一个 `map`。然后将它们编组到 JSON，设置响应内容类型和状态码，并写 JSON。如果 JSON 编组失败，则响应一个内部错误。

#### 响应内部错误的函数

`respondInternalError` 是一个响应 `500 Internal Server Error` 的函数，但是也同时将错误输出到控制台。

```
func respondInternalError(w http.ResponseWriter, err error) {
    log.Println(err)
    respondJSON(w,
        http.StatusText(http.StatusInternalServerError),
        http.StatusInternalServerError)
}

```

#### 创建用户的处理程序

下面开始编写 `createUser` 处理程序，因为它非常容易并且是 REST 式的。

```
type User struct {
    ID       string `json:"id"`
    Email    string `json:"email"`
    Username string `json:"username"`
}

```

`User` 类型和 `users` 表相似。

```
var (
    rxEmail = regexp.MustCompile("^[^\\s@]+@[^\\s@]+\\.[^\\s@]+$")
    rxUsername = regexp.MustCompile("^[a-zA-Z][\\w|-]{1,17}$")
)

```

这些正则表达式是分别用于去验证电子邮件和用户名的。这些都很简单，可以根据你的需要随意去适配。

现在，在 `createUser` 函数内部，我们将开始解码请求体。

```
var user User
if err := json.NewDecoder(r.Body).Decode(&user); err != nil {
    respondJSON(w, err.Error(), http.StatusBadRequest)
    return
}
defer r.Body.Close()

```

我们将使用请求体去创建一个 JSON 解码器来解码出一个用户指针。如果发生错误则返回一个 `400 Bad Request`。不要忘记关闭请求体读取器。

```
errs := make(map[string]string)
if user.Email == "" {
    errs["email"] = "Email required"
} else if !rxEmail.MatchString(user.Email) {
    errs["email"] = "Invalid email"
}
if user.Username == "" {
    errs["username"] = "Username required"
} else if !rxUsername.MatchString(user.Username) {
    errs["username"] = "Invalid username"
}
if len(errs) != 0 {
    respondJSON(w, errs, http.StatusUnprocessableEntity)
    return
}

```

这是我如何做验证；一个简单的 `map` 并检查如果 `len(errs) != 0`，则使用 `422 Unprocessable Entity` 去返回。

```
err := db.QueryRowContext(r.Context(), `
    INSERT INTO users (email, username) VALUES ($1, $2)
    RETURNING id
`, user.Email, user.Username).Scan(&user.ID)

if errPq, ok := err.(*pq.Error); ok && errPq.Code.Name() == "unique_violation" {
    if strings.Contains(errPq.Error(), "email") {
        errs["email"] = "Email taken"
    } else {
        errs["username"] = "Username taken"
    }
    respondJSON(w, errs, http.StatusForbidden)
    return
} else if err != nil {
    respondInternalError(w, fmt.Errorf("could not insert user: %v", err))
    return
}

```

这个 SQL 查询使用一个给定的 email 和用户名去插入一个新用户，并返回自动生成的 id，每个 `$` 将被接下来传递给 `QueryRowContext` 的参数替换掉。

因为 `users` 表在 `email` 和 `username` 字段上有唯一性约束，因此我将检查 “unique\_violation” 错误并返回 `403 Forbidden` 或者返回一个内部错误。

```
respondJSON(w, user, http.StatusCreated)

```

最后使用创建的用户去响应。

#### 无密码验证开始部分的处理程序

```
type PasswordlessStartRequest struct {
    Email       string `json:"email"`
    RedirectURI string `json:"redirectUri"`
}

```

这个结构体含有 `passwordlessStart` 的请求体：希望去登入的用户 email、来自客户端的重定向 URI（这个应用中将使用我们的 API）如：`https://frontend.app/callback`。

```
var magicLinkTmpl = template.Must(template.ParseFiles("templates/magic-link.html"))

```

我们将使用 golang 模板引擎去构建邮件，因此需要你在 `templates` 目录中，用如下的内容创建一个 `magic-link.html` 文件：

```
<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="utf-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>Magic Link</title>
</head>
<body>
    Click <a href="{{ .MagicLink }}" target="_blank">here</a> to login.
    <br>
    <em>This link expires in 15 minutes and can only be used once.</em>
</body>
</html>

```

这个模板是给用户发送魔法链接邮件用的。你可以根据你的需要去随意调整它。

现在， 进入 `passwordlessStart` 函数内部：

```
var input PasswordlessStartRequest
if err := json.NewDecoder(r.Body).Decode(&input); err != nil {
    respondJSON(w, err.Error(), http.StatusBadRequest)
    return
}
defer r.Body.Close()

```

首先，我们像前面一样解码请求体。

```
errs := make(map[string]string)
if input.Email == "" {
    errs["email"] = "Email required"
} else if !rxEmail.MatchString(input.Email) {
    errs["email"] = "Invalid email"
}
if input.RedirectURI == "" {
    errs["redirectUri"] = "Redirect URI required"
} else if u, err := url.Parse(input.RedirectURI); err != nil || !u.IsAbs() {
    errs["redirectUri"] = "Invalid redirect URI"
}
if len(errs) != 0 {
    respondJSON(w, errs, http.StatusUnprocessableEntity)
    return
}

```

我们使用 golang 的 URL 解析器去验证重定向 URI，检查那个 URI 是否为绝对地址。

```
var verificationCode string
err := db.QueryRowContext(r.Context(), `
    INSERT INTO verification_codes (user_id) VALUES
        ((SELECT id FROM users WHERE email = $1))
    RETURNING id
`, input.Email).Scan(&verificationCode)
if errPq, ok := err.(*pq.Error); ok && errPq.Code.Name() == "not_null_violation" {
    respondJSON(w, "No user found with that email", http.StatusNotFound)
    return
} else if err != nil {
    respondInternalError(w, fmt.Errorf("could not insert verification code: %v", err))
    return
}

```

这个 SQL 查询将插入一个验证代码，这个代码通过给定的 email 关联到用户，并且返回一个自动生成的 id。因为有可能会出现用户不存在的情况，那样的话子查询可能解析为 `NULL`，这将导致在 `user_id` 字段上因违反 `NOT NULL` 约束而导致失败，因此需要对这种情况进行检查，如果用户不存在，则返回 `404 Not Found` 或者一个内部错误。

```
q := make(url.Values)
q.Set("verification_code", verificationCode)
q.Set("redirect_uri", input.RedirectURI)
magicLink := *config.appURL
magicLink.Path = "/api/passwordless/verify_redirect"
magicLink.RawQuery = q.Encode()

```

现在，构建魔法链接并设置查询字符串中的 `verification_code` 和 `redirect_uri` 的值。如：`http://localhost/api/passwordless/verify_redirect?verification_code=some_code&redirect_uri=https://frontend.app/callback`。

```
var body bytes.Buffer
data := map[string]string{"MagicLink": magicLink.String()}
if err := magicLinkTmpl.Execute(&body, data); err != nil {
    respondInternalError(w, fmt.Errorf("could not execute magic link template: %v", err))
    return
}

```

我们将得到的魔法链接模板的内容保存到缓冲区中。如果发生错误则返回一个内部错误。

```
to := mail.Address{Address: input.Email}
if err := sendMail(to, "Magic Link", body.String()); err != nil {
    respondInternalError(w, fmt.Errorf("could not mail magic link: %v", err))
    return
}

```

现在来写给用户发邮件的 `sendMail` 函数。如果发生错误则返回一个内部错误。

```
w.WriteHeader(http.StatusNoContent)

```

最后，设置响应状态码为 `204 No Content`。对于成功的状态码，客户端不需要很多数据。

#### 发送邮件函数

```
func sendMail(to mail.Address, subject, body string) error {
    from := mail.Address{
        Name:    "Passwordless Demo",
        Address: "noreply@" + config.appURL.Host,
    }
    headers := map[string]string{
        "From":         from.String(),
        "To":           to.String(),
        "Subject":      subject,
        "Content-Type": `text/html; charset="utf-8"`,
    }
    msg := ""
    for k, v := range headers {
        msg += fmt.Sprintf("%s: %s\r\n", k, v)
    }
    msg += "\r\n"
    msg += body

    return smtp.SendMail(
        config.smtpAddr,
        config.smtpAuth,
        from.Address,
        []string{to.Address},
        []byte(msg))
}

```

这个函数创建一个基本的 HTML 邮件结构体并使用 SMTP 服务器去发送它。邮件的内容你可以随意定制，我喜欢使用比较简单的内容。

#### 无密码验证重定向的处理程序

```
var rxUUID = regexp.MustCompile("^[0-9a-f]{8}-[0-9a-f]{4}-4[0-9a-f]{3}-[89ab][0-9a-f]{3}-[0-9a-f]{12}$")

```

首先，这个正则表达式去验证一个 UUID（即验证代码）。

现在进入 `passwordlessVerifyRedirect` 函数内部：

```
q := r.URL.Query()
verificationCode := q.Get("verification_code")
redirectURI := q.Get("redirect_uri")

```

`/api/passwordless/verify_redirect` 是一个 `GET` 端点，以便于我们从查询字符串中读取数据。

```
errs := make(map[string]string)
if verificationCode == "" {
    errs["verification_code"] = "Verification code required"
} else if !rxUUID.MatchString(verificationCode) {
    errs["verification_code"] = "Invalid verification code"
}
var callback *url.URL
var err error
if redirectURI == "" {
    errs["redirect_uri"] = "Redirect URI required"
} else if callback, err = url.Parse(redirectURI); err != nil || !callback.IsAbs() {
    errs["redirect_uri"] = "Invalid redirect URI"
}
if len(errs) != 0 {
    respondJSON(w, errs, http.StatusUnprocessableEntity)
    return
}

```

类似的验证，我们保存解析后的重定向 URI 到一个 `callback` 变量中。

```
var userID string
if err := db.QueryRowContext(r.Context(), `
    DELETE FROM verification_codes
    WHERE id = $1
        AND created_at >= now() - INTERVAL '15m'
    RETURNING user_id
`, verificationCode).Scan(&userID); err == sql.ErrNoRows {
    respondJSON(w, "Link expired or already used", http.StatusBadRequest)
    return
} else if err != nil {
    respondInternalError(w, fmt.Errorf("could not delete verification code: %v", err))
    return
}

```

这个 SQL 查询通过给定的 id 去删除相应的验证代码，并且确保它创建之后时间不超过 15 分钟，它也返回关联的 `user_id`。如果没有检索到内容，意味着代码不存在或者已过期，我们返回一个响应信息，否则就返回一个内部错误。

```
expiresAt := time.Now().Add(time.Hour * 24 * 60)
tokenString, err := jwt.NewWithClaims(jwt.SigningMethodHS256, jwt.StandardClaims{
    Subject:   userID,
    ExpiresAt: expiresAt.Unix(),
}).SignedString(config.jwtKey)
if err != nil {
    respondInternalError(w, fmt.Errorf("could not create JWT: %v", err))
    return
}

```

这些是如何去创建 JWT。我们为 JWT 设置一个 60 天的过期值，你也可以设置更短的时间（大约 2 周），并添加一个新端点去刷新令牌，但是不要搞的过于复杂。

```
expiresAtB, err := expiresAt.MarshalText()
if err != nil {
    respondInternalError(w, fmt.Errorf("could not marshal expiration date: %v", err))
    return
}
f := make(url.Values)
f.Set("jwt", tokenString)
f.Set("expires_at", string(expiresAtB))
callback.Fragment = f.Encode()

```

我们去规划重定向；你可使用查询字符串去添加 JWT，但是更常见的是使用一个哈希片段。如：`https://frontend.app/callback#jwt=token_here&expires_at=some_date`.

过期日期可以从 JWT 中提取出来，但是这样做的话，就需要在客户端上实现一个 JWT 库来解码它，因此为了简化，我将它加到这里。

```
http.Redirect(w, r, callback.String(), http.StatusFound)

```

最后我们使用一个 `302 Found` 重定向。

---

无密码的流程已经完成。现在需要去写 `getAuthUser` 端点的代码了，它用于获取当前验证用户的信息。你应该还记得，这个端点使用了 `guard` 中间件。

#### 使用 Auth 中间件

在编写 `guard` 中间件之前，我将编写一个不需要验证的分支。目的是，如果没有传递 JWT，它将不去验证用户。

```
type ContextKey struct {
    Name string
}

var keyAuthUserID = ContextKey{"auth_user_id"}

func withAuth(next http.HandlerFunc) http.HandlerFunc {
    return func(w http.ResponseWriter, r *http.Request) {
        a := r.Header.Get("Authorization")
        hasToken := strings.HasPrefix(a, "Bearer ")
        if !hasToken {
            next(w, r)
            return
        }
        tokenString := a[7:]

        p := jwt.Parser{ValidMethods: []string{jwt.SigningMethodHS256.Name}}
        token, err := p.ParseWithClaims(
            tokenString,
            &jwt.StandardClaims{},
            func (*jwt.Token) (interface{}, error) { return config.jwtKey, nil },
        )
        if err != nil {
            respondJSON(w, http.StatusText(http.StatusUnauthorized), http.StatusUnauthorized)
            return
        }

        claims, ok := token.Claims.(*jwt.StandardClaims)
        if !ok || !token.Valid {
            respondJSON(w, http.StatusText(http.StatusUnauthorized), http.StatusUnauthorized)
            return
        }

        ctx := r.Context()
        ctx = context.WithValue(ctx, keyAuthUserID, claims.Subject)

        next(w, r.WithContext(ctx))
    }
}

```

JWT 将在每次请求时以 `Bearer <token_here>` 格式包含在 `Authorization` 头中。因此，如果没有提供令牌，我们将直接通过，进入接下来的中间件。

我们创建一个解析器来解析令牌。如果解析失败则返回 `401 Unauthorized`。

然后我们从 JWT 中提取出要求的内容，并添加 `Subject`（就是用户 ID）到需要的地方。

#### Guard 中间件

```
func guard(next http.HandlerFunc) http.HandlerFunc {
    return withAuth(func(w http.ResponseWriter, r *http.Request) {
        _, ok := r.Context().Value(keyAuthUserID).(string)
        if !ok {
            respondJSON(w, http.StatusText(http.StatusUnauthorized), http.StatusUnauthorized)
            return
        }
        next(w, r)
    })
}

```

现在，`guard` 将使用 `withAuth` 并从请求内容中提取出验证用户的 ID。如果提取失败，它将返回 `401 Unauthorized`，提取成功则继续下一步。

#### 获取 Auth 用户

在 `getAuthUser` 处理程序内部：

```
ctx := r.Context()
authUserID := ctx.Value(keyAuthUserID).(string)

user, err := fetchUser(ctx, authUserID)
if err == sql.ErrNoRows {
    respondJSON(w, http.StatusText(http.StatusTeapot), http.StatusTeapot)
    return
} else if err != nil {
    respondInternalError(w, fmt.Errorf("could not query auth user: %v", err))
    return
}

respondJSON(w, user, http.StatusOK)

```

首先，我们从请求内容中提取验证用户的 ID，我们使用这个 ID 去获取用户。如果没有获取到内容，则发送一个 `418 I'm a teapot`，或者一个内部错误。最后，我们将用这个用户去响应。

#### 获取 User 函数

下面你看到的是 `fetchUser` 函数。

```
func fetchUser(ctx context.Context, id string) (User, error) {
    user := User{ID: id}
    err := db.QueryRowContext(ctx, `
        SELECT email, username FROM users WHERE id = $1
    `, id).Scan(&user.Email, &user.Username)
    return user, err
}

```

我将它解耦是因为通过 ID 来获取用户是个常做的事。

---

以上就是全部的代码。你可以自己去构建它和测试它。[这里](https://go-passwordless-demo.herokuapp.com/) 还有一个 demo 你可以试用一下。

如果你在 mailtrap 上点击之后出现有关 `脚本运行被拦截，因为文档的框架是沙箱化的，并且没有设置 'allow-scripts' 权限` 的问题，你可以尝试右键点击 “在新标签中打开链接“。这样做是安全的，因为邮件内容是 [沙箱化的](https://developer.mozilla.org/en-US/docs/Web/HTML/Element/iframe#attr-sandbox)。我在 `localhost` 上有时也会出现这个问题，但是我认为你一旦以 `https://` 方式部署到服务器上应该不会出现这个问题了。

如果有任何问题，请在我的 [GitHub repo](https://github.com/nicolasparada/go-passwordless-demo) 留言或者提交 PRs

以后，我为这个 API 写了一个客户端作为这篇文章的[第二部分](https://nicolasparada.netlify.com/posts/passwordless-auth-client/)。

---

via: <https://nicolasparada.netlify.com/posts/passwordless-auth-server/>

作者：[Nicolás Parada](https://nicolasparada.netlify.com/) 译者：[qhwdw](https://github.com/qhwdw) 校对：[wxy](https://github.com/wxy)

本文由 [LCTT](https://github.com/LCTT/TranslateProject) 原创编译，[Linux中国](https://linux.cn/) 荣誉推出


如何装载/卸载 Linux 内核模块

Summary: 找到并装载内核模块以解决外设问题。

找到并装载内核模块以解决外设问题。

>
> 找到并装载内核模块以解决外设问题。
>
>
>

![](/data/attachment/album/201806/16/112027iau92bydbyxuf7mn.png)

本文来自 Manning 出版的 [Linux in Action](https://www.manning.com/books/linux-in-action?a_aid=bootstrap-it&a_bid=4ca15fc9&chan=opensource) 的第 15 章。

Linux 使用内核模块管理硬件外设。 我们来看看它是如何工作的。

运行中的 Linux 内核是您不希望被破坏的东西之一。毕竟，内核是驱动计算机所做的一切工作的软件。考虑到在一个运行的系统上必须同时管理诸多细节，最好能让内核尽可能的减少分心，专心的完成它的工作。但是，如果对计算环境进行任何微小的更改都需要重启整个系统，那么插入一个新的网络摄像头或打印机就可能会严重影响您的工作流程。每次添加设备时都必须重新启动，以使系统识别它，这效率很低。

为了在稳定性和可用性之间达成有效的平衡，Linux 将内核隔离，但是允许您通过可加载内核模块 (LKM) 实时添加特定的功能。如下图所示，您可以将模块视为软件的一部分，它告诉内核在哪里找到一个设备以及如何使用它。反过来，内核使设备对用户和进程可用，并监视其操作。

![Kernel modules](/data/attachment/album/201806/16/112041o02hso1bs2h2177z.png "Kernel modules")

*内核模块充当设备和 Linux 内核之间的转换器。*

虽然你可以自己编写模块来完全按照你喜欢的方式来支持一个设备，但是为什么要这样做呢？Linux 模块库已经非常强大，通常不需要自己去实现一个模块。 而绝大多数时候，Linux 会自动加载新设备的模块，而您甚至不知道它。

不过，有时候，出于某种原因，它本身并不会自动进行。（你肯定不想让那个招聘经理不耐烦地一直等待你的笑脸加入视频面试。）为了帮助你解决问题，你需要更多地了解内核模块，特别是，如何找到运行你的外设的实际模块，然后如何手动激活它。

### 查找内核模块

按照公认的约定，内核模块是位于 `/lib/modules/` 目录下的具有 .ko（内核对象）扩展名的文件。 然而，在你找到这些文件之前，你还需要选择一下。因为在引导时你需要从版本列表中选择其一加载，所以支持您选择的特定软件（包括内核模块）必须存在某处。 那么，`/lib/modules/` 就是其中之一。 你会发现目录里充满了每个可用的 Linux 内核版本的模块; 例如：

```
$ ls /lib/modules
4.4.0-101-generic
4.4.0-103-generic
4.4.0-104-generic

```

在我的电脑上，运行的内核是版本号最高的版本（4.4.0-104-generic），但不能保证这对你来说是一样的（内核经常更新）。 如果您将要在一个运行的系统上使用模块完成一些工作的话，你需要确保您找到正确的目录树。

好消息：有一个可靠的窍门。相对于通过名称来识别目录，并希望能够找到正确的目录，你可以使用始终指向使用的内核名称的系统变量。 您可以使用 `uname -r`（ `-r` 从系统信息中指定通常显示的内核版本号）来调用该变量：

```
$ uname -r
4.4.0-104-generic

```

通过这些信息，您可以使用称为命令替换的过程将 `uname` 并入您的文件系统引用中。 例如，要导航到正确的目录，您需要将其添加到 `/lib/modules` 。 要告诉 Linux “uname” 不是一个文件系统中的位置，请将 `uname` 部分用反引号括起来，如下所示：

```
$ ls /lib/modules/`uname -r`
build   modules.alias        modules.dep      modules.softdep
initrd  modules.alias.bin    modules.dep.bin  modules.symbols
kernel  modules.builtin      modules.devname  modules.symbols.bin
misc    modules.builtin.bin  modules.order    vdso

```

你可以在 `kernel/` 目录下的子目录中找到大部分模块。 花几分钟时间浏览这些目录，了解事物的排列方式和可用内容。 这些文件名通常会让你知道它们是什么。

```
$ ls /lib/modules/`uname -r`/kernel
arch  crypto  drivers  fs  kernel  lib  mm
net  sound  ubuntu  virt  zfs

```

这是查找内核模块的一种方法；实际上，这是一种快速的方式。 但这不是唯一的方法。 如果你想获得完整的集合，你可以使用 `lsmod` 列出所有当前加载的模块以及一些基本信息。 这个截断输出的第一列（在这里列出的太多了）是模块名称，后面是文件大小和数量，然后是每个模块的名称：

```
$ lsmod
[...]
vboxdrv          454656  3 vboxnetadp,vboxnetflt,vboxpci
rt2x00usb        24576  1 rt2800usb
rt2800lib        94208  1 rt2800usb
[...]

```

到底有多少？好吧，我们再运行一次 `lsmod` ，但是这一次将输出管道输送到 `wc -l` 看一下一共多少行:

```
$ lsmod | wc -l
113

```

这是已加载的模块。 总共有多少个？ 运行 `modprobe -c` 并计算这些行将给我们这个数字：

```
$ modprobe -c | wc -l
33350

```

有 33,350 个可用模块！？ 看起来好像有人多年来一直在努力为我们提供软件来驱动我们的物理设备。

注意：在某些系统中，您可能会遇到自定义的模块，这些模块要么在 `/etc/modules` 文件中使用独特的条目进行引用，要么在 `/etc/modules-load.d/` 下的配置文件中。这些模块很可能是本地开发项目的产物，可能涉及前沿实验。不管怎样，知道你看到的是什么总是好的。

这就是如何找到模块的方法。 如果出于某种原因，它不会自行加载，您的下一个工作就是弄清楚如何手动加载未激活的模块。

### 手动加载内核模块

在加载内核模块之前，逻辑上您必须确认它存在。在这之前，你需要知道它叫什么。要做到这一点，有时需要兼有魔法和运气以及在线文档作者的辛勤工作的帮助。

我将通过描述一段时间前遇到的问题来说明这个过程。在一个晴朗的日子里，出于某种原因，笔记本电脑上的 WiFi 接口停止工作了。就这样。也许是软件升级把它搞砸了。谁知道呢？我运行了 `lshw -c network` ，得到了这个非常奇怪的信息:

```
network UNCLAIMED
    AR9485 Wireless Network Adapter

```

Linux 识别到了接口（Atheros AR9485），但将其列为未声明。 那么，正如他们所说的那样，“当情况变得严峻时，就会在互联网上进行艰难的搜索。” 我搜索了一下 atheros ar9 linux 模块，在浏览了一页又一页五年前甚至是十年前的页面后，它们建议我自己写个模块或者放弃吧，然后我终于发现（最起码 Ubuntu 16.04）有一个可以工作的模块。 它的名字是 ath9k 。

是的! 这场战斗胜券在握！向内核添加模块比听起来容易得多。 要仔细检查它是否可用，可以针对模块的目录树运行 `find`，指定 `-type f` 来告诉 Linux 您正在查找文件，然后将字符串 `ath9k` 和星号一起添加以包含所有以你的字符串打头的文件：

```
$ find /lib/modules/$(uname -r) -type f -name ath9k*
/lib/modules/4.4.0-97-generic/kernel/drivers/net/wireless/ath/ath9k/ath9k_common.ko
/lib/modules/4.4.0-97-generic/kernel/drivers/net/wireless/ath/ath9k/ath9k.ko
/lib/modules/4.4.0-97-generic/kernel/drivers/net/wireless/ath/ath9k/ath9k_htc.ko
/lib/modules/4.4.0-97-generic/kernel/drivers/net/wireless/ath/ath9k/ath9k_hw.ko

```

再一步，加载模块：

```
# modprobe ath9k

```

就是这样。无启动，没烦恼。

这里还有一个示例，向您展示如何使用已经崩溃的运行模块。曾经有一段时间，我使用罗技网络摄像头和一个特定的软件会使摄像头在下次系统启动前无法被任何其他程序访问。有时我需要在不同的应用程序中打开相机，但没有时间关机重新启动。（我运行了很多应用程序，在引导之后将它们全部准备好需要一些时间。）

由于这个模块可能是运行的，所以使用 `lsmod` 来搜索 video 这个词应该给我一个关于相关模块名称的提示。 实际上，它比提示更好：用 video 这个词描述的唯一模块是 uvcvideo（如下所示）：

```
$ lsmod | grep video
uvcvideo               90112  0
videobuf2_vmalloc      16384  1 uvcvideo
videobuf2_v4l2         28672  1 uvcvideo
videobuf2_core         36864  2 uvcvideo,videobuf2_v4l2
videodev              176128  4 uvcvideo,v4l2_common,videobuf2_core,videobuf2_v4l2
media                  24576  2 uvcvideo,videodev

```

有可能是我自己的操作导致了崩溃，我想我可以挖掘更深一点，看看我能否以正确的方式解决问题。但结果你知道的；有时你不关心理论，只想让设备工作。 所以我用 `rmmod` 杀死了 `uvcvideo` 模块，然后用 `modprobe` 重新启动它，一切都好：

```
# rmmod uvcvideo
# modprobe uvcvideo

```

再一次：不重新启动。没有其他的后续影响。

---

via: <https://opensource.com/article/18/5/how-load-or-unload-linux-kernel-module>

作者：[David Clinton](https://opensource.com/users/dbclinton) 选题：[lujun9972](https://github.com/lujun9972) 译者：[amwps290](https://github.com/amwps290) 校对：[wxy](https://github.com/wxy)

本文由 [LCTT](https://github.com/LCTT/TranslateProject) 原创编译，[Linux中国](https://linux.cn/) 荣誉推出


Vim-plug：极简 Vim 插件管理器

没法用，你用过么，瞎翻译

来自120.244.214.22的 Internet Explorer 11.0|Windows 10 用户

挺好用的，说实话

:PlugXXX应该是:PluginXXX，少了`in`

![](/data/attachment/album/201806/16/114435wd3f3jgwjeewfdxs.png)

当没有插件管理器时，Vim 用户必须手动下载 tarball 包形式的插件，并将它们解压到 `~/.vim` 目录中。在少量插件的时候可以。但当他们安装更多的插件时，就会变得一团糟。所有插件文件分散在单个目录中，用户无法找到哪个文件属于哪个插件。此外，他们无法找到他们应该删除哪个文件来卸载插件。这时 Vim 插件管理器就可以派上用场。插件管理器将安装插件的文件保存在单独的目录中，因此管理所有插件变得非常容易。我们几个月前已经写了关于 [Vundle](/article-9416-1.html) 的文章。今天，我们将看到又一个名为 “Vim-plug” 的 Vim 插件管理器。

Vim-plug 是一个自由、开源、速度非常快的、极简的 vim 插件管理器。它可以并行地安装或更新插件。你还可以回滚更新。它创建<ruby> 浅层克隆 <rt>  shallow clone </rt></ruby>最小化磁盘空间使用和下载时间。它支持按需加载插件以加快启动时间。其他值得注意的特性是支持分支/标签/提交、post-update 钩子、支持外部管理的插件等。

### 安装

安装和使用起来非常容易。你只需打开终端并运行以下命令：

```
$ curl -fLo ~/.vim/autoload/plug.vim --create-dirs https://raw.githubusercontent.com/junegunn/vim-plug/master/plug.vim

```

Neovim 用户可以使用以下命令安装 Vim-plug：

```
$ curl -fLo ~/.config/nvim/autoload/plug.vim --create-dirs https://raw.githubusercontent.com/junegunn/vim-plug/master/plug.vim

```

### 用法

#### 安装插件

要安装插件，你必须如下所示首先在 Vim 配置文件中声明它们。一般 Vim 的配置文件是 `~/.vimrc`，Neovim 的配置文件是 `~/.config/nvim/init.vim`。请记住，当你在配置文件中声明插件时，列表应该以 `call plug#begin(PLUGIN_DIRECTORY)` 开始，并以 `plug#end()` 结束。

例如，我们安装 “lightline.vim” 插件。为此，请在 `~/.vimrc` 的顶部添加以下行。

```
call plug#begin('~/.vim/plugged')
Plug 'itchyny/lightline.vim'
call plug#end()

```

在 vim 配置文件中添加上面的行后，通过输入以下命令重新加载：

```
:source ~/.vimrc

```

或者，只需重新加载 Vim 编辑器。

现在，打开 vim 编辑器：

```
$ vim

```

使用以下命令检查状态：

```
:PlugStatus

```

然后输入下面的命令，然后按回车键安装之前在配置文件中声明的插件。

```
:PlugInstall

```

#### 更新插件

要更新插件，请运行：

```
:PlugUpdate

```

更新插件后，按下 `d` 查看更改。或者，你可以之后输入 `:PlugDiff`。

#### 审查插件

有时，更新的插件可能有新的 bug 或无法正常工作。要解决这个问题，你可以简单地回滚有问题的插件。输入 `:PlugDiff` 命令，然后按回车键查看上次 `:PlugUpdate`的更改，并在每个段落上按 `X` 将每个插件回滚到更新前的前一个状态。

#### 删除插件

删除一个插件删除或注释掉你以前在你的 vim 配置文件中添加的 `plug` 命令。然后，运行 `:source ~/.vimrc` 或重启 Vim 编辑器。最后，运行以下命令卸载插件：

```
:PlugClean

```

该命令将删除 vim 配置文件中所有未声明的插件。

#### 升级 Vim-plug

要升级vim-plug本身，请输入：

```
:PlugUpgrade

```

如你所见，使用 Vim-plug 管理插件并不难。它简化了插件管理。现在去找出你最喜欢的插件并使用 Vim-plug 来安装它们。

就是这些了。我将很快在这里发布另一个有趣的话题。在此之前，请继续关注我们。

干杯!

---

via: <https://www.ostechnix.com/vim-plug-a-minimalist-vim-plugin-manager/>

作者：[SK](https://www.ostechnix.com/author/sk/) 选题：[lujun9972](https://github.com/lujun9972) 译者：[geekpi](https://github.com/geekpi) 校对：[wxy](https://github.com/wxy)

本文由 [LCTT](https://github.com/LCTT/TranslateProject) 原创编译，[Linux中国](https://linux.cn/) 荣誉推出


机器人学影响 CIO 角色的 3 种方式

Summary: 机器人流程自动化如何影响 CIO ？考虑这些可能。

机器人流程自动化如何影响 CIO ？考虑这些可能。

>
> 机器人流程自动化如何影响 CIO ？考虑这些可能。
>
>
>

![配图](/data/attachment/album/201806/16/120845tip64et67xp4qpqt.png)

随着 2017 的结束，许多 CIO 们的 2018 年目标也将确定。或许你们将参与到机器人流程自动化（RPA）中。多年以来，RPA 对许多公司来说只是一个可望不可及的概念。但是随着组织被迫变得越来越敏捷高效，RPA 所具有的潜在优势开始受到重视。

根据 Redwood Sofeware 和 Sapio Research 的最新 [研究报告](https://www.redwood.com/press-releases/it-decision-makers-speak-59-of-business-processes-could-be-automated-by-2022/)，IT 决策者们相信，未来 5 年有 59% 的业务可以被自动化处理，从而产生新的速度和效率，并且消减相应的重复性的人工工作量的岗位。但是，目前在相应岗位上没有实施 RPA 的公司中，有 20% 的公司员工超过 1000 人。

对于 CIO 们来说，RPA 会潜在影响你在业务中的角色和你的团队。随着 RPA 的地位在 2018 年中越来越重要，CIO 们和其它 IT 决策者的角色也能会受到如下三种方式的改变：

### 成为战略性变革代理人的机会增加

随着压力的增长，用最少的时间做更多的事，内部流程越来越重要。在每个企业中，员工们每天做着既重要又枯燥的一些任务。这些任务可能是枯燥且重复性的任务，但是要求他们必须快速完成且不能出错。

**[有关你的自动化策略的 ROI 建议，查看我们相关的文章，[如何在自动化方面提升 ROI：4 个小提示](https://enterprisersproject.com/article/2017/11/how-improve-roi-automation-4-tips?sc_cid=70160000000h0aXAAQ)。]**

在财务中从后台的一些操作到采购、供应链、账务、客户服务、以及人力资源，在一个组织中几乎所有的岗位都有一些枯燥的任务。这就为 CIO 们提供了一个机会，将 IT 与业务联合起来，成为使用 RPA 进行战略变革的先锋。

除了过去的屏幕抓取技术，机器人现在已经实现可定制化，即插即用的解决方案可以根据企业的需要进行设计。使用这种以流程为中心的方法，企业不仅可以将以前由人来完成的任务进行自动化，也可以将应用程序和系统特定任务自动化，比如 ERP 和企业其它应用程序。

为端到端的流程实施更高级别的自动化将是 CIO 们的价值所在。CIO 们将会站在这种机遇的最前沿。

### 重新关注人和培训

技术的变动将让员工更加恐慌，尤其是当这些自动化的变动涉及到他们每日职责的相当大的部分。CIO 们应该清楚地告诉他们，RPA 将如何让他们的角色和责任变得更好，以及用数据说明的、最终将影响到他们底线的战略决策。

当实施 RPA 时，清晰明确地表达出“人对组织的成功来说是很重要的”信息是很关键的，并且，这种成功需要使技术和人的技能实现适当的平衡。

CIO 们也应该分析工作流并实施更好的流程，这种流程能够超越以前由终端用户完成的特定任务。通过端到端的流程自动化，CIO 们可以让员工表现更加出色。

因为在整个自动化的过程中，提升和培训员工技术是很重要的，CIO 们必须与企业高管们一起去决定，帮助员工自信地应对变化的培训项目。

### 需要长远的规划

为确保机器人流程自动化的成功，甲方必须有一个长期的方法。这将要求一个弹性的解决方案，它将对整个业务模型有好处，包括消费者。比如，当年亚马逊为他的 Prime 消费者推广快速投递服务时，他们不仅在他们的仓库中重新调整了订单交付流程，他们也自动化了他们的在线客户体验，让它们变得更简化、更快速，并且让消费者比以前更容易下订单。

在即将到来的一年里，CIO 们可以用同样的方法来应用技术，构建整体的解决方案来改变组织的动作方式。裁员只会让最终效益产生很大影响，但是流程自动化将使 CIO 们能够通过优化和授权来考虑更大的问题。这种方法让 CIO 们去为他们自己和 RPA 构建长期的信誉。这反过来将强化 CIO 们作为探路者的角色，并为整个组织的成功作出贡献。

对于 CIO 们来说，采用一个长期的、策略性的方法让 RPA 成功，需要时间和艰苦的努力。尽管如此，那些致力于创建一种人力和技术平衡战略的 CIO 们，将会在现在和将来实现更大的价值。

---

via: <https://enterprisersproject.com/article/2017/11/3-ways-robotics-affects-cio-role>

作者：[Dennis Walsh](https://enterprisersproject.com/user/dennis-walsh) 译者：[qhwdw](https://github.com/qhwdw) 校对：[wxy](https://github.com/wxy)

本文由 [LCTT](https://github.com/LCTT/TranslateProject) 原创编译，[Linux中国](https://linux.cn/) 荣誉推出


如何在 Linux 中不安装软件测试一个软件包

![](/data/attachment/album/201806/16/225749u8hbmbr1r88nhm1k.png)

出于某种原因，你可能需要在将软件包安装到你的 Linux 系统之前对其进行测试。如果是这样，你很幸运！今天，我将向你展示如何在 Linux 中使用 **Nix** 包管理器来实现。Nix 包管理器的一个显著特性是它允许用户测试软件包而无需先安装它们。当你想要临时使用特定的程序时，这会很有帮助。

### 测试一个软件包而不在 Linux 中安装它

确保你先安装了 Nix 包管理器。如果尚未安装，请参阅以下指南。

例如，假设你想测试你的 C++ 代码。你不必安装 GCC。只需运行以下命令：

```
$ nix-shell -p gcc

```

该命令会构建或下载 gcc 软件包及其依赖项，然后将其放入一个存在 `gcc` 命令的 Bash shell 中，所有这些都不会影响正常环境。

```
LANGUAGE = (unset),
LC_ALL = (unset),
LANG = "en_US.UTF-8"
are supported and installed on your system.
perl: warning: Falling back to the standard locale ("C").
download-using-manifests.pl: perl: warning: Setting locale failed.
download-using-manifests.pl: perl: warning: Please check that your locale settings:
download-using-manifests.pl: LANGUAGE = (unset),
download-using-manifests.pl: LC_ALL = (unset),
download-using-manifests.pl: LANG = "en_US.UTF-8"
download-using-manifests.pl: are supported and installed on your system.
download-using-manifests.pl: perl: warning: Falling back to the standard locale ("C").
download-from-binary-cache.pl: perl: warning: Setting locale failed.
download-from-binary-cache.pl: perl: warning: Please check that your locale settings:
download-from-binary-cache.pl: LANGUAGE = (unset),
download-from-binary-cache.pl: LC_ALL = (unset),
download-from-binary-cache.pl: LANG = "en_US.UTF-8"

[...]

fetching path ‘/nix/store/6mk1s81va81dl4jfbhww86cwkl4gyf4j-stdenv’...
perl: warning: Setting locale failed.
perl: warning: Please check that your locale settings:
LANGUAGE = (unset),
LC_ALL = (unset),
LANG = "en_US.UTF-8"
are supported and installed on your system.
perl: warning: Falling back to the standard locale ("C").

*** Downloading ‘https://cache.nixos.org/nar/0aznfg1g17a8jdzvnp3pqszs9rq2wiwf2rcgczyg5b3k6d0iricl.nar.xz’ to ‘/nix/store/6mk1s81va81dl4jfbhww86cwkl4gyf4j-stdenv’...
% Total % Received % Xferd Average Speed Time Time Time Current
Dload Upload Total Spent Left Speed
100 8324 100 8324 0 0 6353 0 0:00:01 0:00:01 --:--:-- 6373

[nix-shell:~]$

```

检查GCC版本：

```
[nix-shell:~]$ gcc -v
Using built-in specs.
COLLECT_GCC=/nix/store/dyj2k6ch35r1ips4vr97md2i0yvl4r5c-gcc-5.4.0/bin/gcc
COLLECT_LTO_WRAPPER=/nix/store/dyj2k6ch35r1ips4vr97md2i0yvl4r5c-gcc-5.4.0/libexec/gcc/x86_64-unknown-linux-gnu/5.4.0/lto-wrapper
Target: x86_64-unknown-linux-gnu
Configured with:
Thread model: posix
gcc version 5.4.0 (GCC)

```

现在，继续并测试代码。完成后，输入 `exit` 返回到控制台。

```
[nix-shell:~]$ exit
exit

```

一旦你从 nix-shell 中退出，你就不能使用 GCC。

这是另一个例子。

```
$ nix-shell -p hello

```

这会构建或下载 GNU Hello 和它的依赖关系，然后将其放入 `hello` 命令所在的 Bash shell 中，所有这些都不会影响你的正常环境：

```
[nix-shell:~]$ hello
Hello, world!

```

输入 `exit` 返回到控制台。

```
[nix-shell:~]$ exit

```

现在测试你的 `hello` 程序是否可用。

```
$ hello
hello: command not found

```

有关 Nix 包管理器的更多详细信息，请参阅以下指南。

希望本篇对你有帮助！还会有更好的东西。敬请关注！！

干杯!

---

via: <https://www.ostechnix.com/how-to-test-a-package-without-installing-it-in-linux/>

作者：[SK](https://www.ostechnix.com/author/sk/) 选题：[lujun9972](https://github.com/lujun9972) 译者：[geekpi](https://github.com/geekpi) 校对：[wxy](https://github.com/wxy)

本文由 [LCTT](https://github.com/LCTT/TranslateProject) 原创编译，[Linux中国](https://linux.cn/) 荣誉推出


使用 OpenCV 进行高动态范围（HDR）成像

![](/data/attachment/album/201806/16/235137u634bg9q363lfhwx.jpg)

在本教程中，我们将学习如何使用由不同曝光设置拍摄的多张图像创建<ruby> 高动态范围 <rt>  High Dynamic Range </rt></ruby>（HDR）图像。 我们将以 C++ 和 Python 两种形式分享代码。

### 什么是高动态范围成像？

大多数数码相机和显示器都是按照 24 位矩阵捕获或者显示彩色图像。 每个颜色通道有 8 位，因此每个通道的像素值在 0-255 范围内。 换句话说，普通的相机或者显示器的动态范围是有限的。

但是，我们周围世界动态范围极大。 在车库内关灯就会变黑，直接看着太阳就会变得非常亮。 即使不考虑这些极端，在日常情况下，8 位的通道勉强可以捕捉到现场场景。 因此，相机会尝试去评估光照并且自动设置曝光，这样图像的最关注区域就会有良好的动态范围，并且太暗和太亮的部分会被相应截取为 0 和 255。

在下图中，左侧的图像是正常曝光的图像。 请注意，由于相机决定使用拍摄主体（我的儿子）的设置，所以背景中的天空已经完全流失了，但是明亮的天空也因此被刷掉了。 右侧的图像是由 iPhone 生成的HDR图像。

[![High Dynamic Range (HDR)](/data/attachment/album/201806/16/235245awc1vv88gvpv5gvl.jpg)](http://www.learnopencv.com/wp-content/uploads/2017/09/high-dynamic-range-hdr.jpg)

iPhone 是如何拍摄 HDR 图像的呢？ 它实际上采用三种不同的曝光度拍摄了 3 张图像，3 张图像拍摄非常迅速，在 3 张图像之间几乎没有产生位移。然后组合三幅图像来产生 HDR 图像。 我们将在下一节看到一些细节。

>
> 将在不同曝光设置下获取的相同场景的不同图像组合的过程称为高动态范围（HDR）成像。
>
>
>

### 高动态范围（HDR）成像是如何工作的？

在本节中，我们来看下使用 OpenCV 创建 HDR 图像的步骤。

>
> 要想轻松学习本教程，请点击[此处](http://www.learnopencv.com/wp-content/uploads/2017/10/hdr.zip)[下载](http://www.learnopencv.com/wp-content/uploads/2017/10/hdr.zip) C++ 和 Python 代码还有图像。 如果您有兴趣了解更多关于人工智能，计算机视觉和机器学习的信息，请[订阅](https://bigvisionllc.leadpages.net/leadbox/143948b73f72a2%3A173c9390c346dc/5649050225344512/)我们的电子杂志。
>
>
>

### 第 1 步：捕获不同曝光度的多张图像

当我们使用相机拍照时，每个通道只有 8 位来表示场景的动态范围（亮度范围）。 但是，通过改变快门速度，我们可以在不同的曝光条件下拍摄多个场景图像。 大多数单反相机（SLR）有一个功能称为<ruby> 自动包围式曝光 <rt>  Auto Exposure Bracketing </rt></ruby>（AEB），只需按一下按钮，我们就可以在不同的曝光下拍摄多张照片。 如果你正在使用 iPhone，你可以使用这个[自动包围式 HDR 应用程序](https://itunes.apple.com/us/app/autobracket-hdr/id923626339?mt=8&amp;ign-mpt=uo%3D8)，如果你是一个 Android 用户，你可以尝试一个[更好的相机应用程序](https://play.google.com/store/apps/details?id=com.almalence.opencam&amp;hl=en)。

场景没有变化时，在相机上使用自动包围式曝光或在手机上使用自动包围式应用程序，我们可以一张接一张地快速拍摄多张照片。 当我们在 iPhone 中使用 HDR 模式时，会拍摄三张照片。

1. 曝光不足的图像：该图像比正确曝光的图像更暗。 目标是捕捉非常明亮的图像部分。
2. 正确曝光的图像：这是相机将根据其估计的照明拍摄的常规图像。
3. 曝光过度的图像：该图像比正确曝光的图像更亮。 目标是拍摄非常黑暗的图像部分。

但是，如果场景的动态范围很大，我们可以拍摄三张以上的图片来合成 HDR 图像。 在本教程中，我们将使用曝光时间为1/30 秒，0.25 秒，2.5 秒和 15 秒的 4 张图像。 缩略图如下所示。

[![Auto Exposure Bracketed  HDR image sequence](/data/attachment/album/201806/16/235246nellr49aa3s94ar9.jpg)](http://www.learnopencv.com/wp-content/uploads/2017/10/hdr-image-sequence.jpg)

单反相机或手机的曝光时间和其他设置的信息通常存储在 JPEG 文件的 EXIF 元数据中。 查看此[链接](https://www.howtogeek.com/289712/how-to-see-an-images-exif-data-in-windows-and-macos)可在 Windows 和 Mac 中查看存储在 JPEG 文件中的 EXIF 元数据。 或者，您可以使用我最喜欢的名为 [EXIFTOOL](https://www.sno.phy.queensu.ca/%7Ephil/exiftool) 的查看 EXIF 的命令行工具。

我们先从读取分配到不同曝光时间的图像开始。

**C++**

```
void readImagesAndTimes(vector<Mat> &images, vector<float> &times)
{

  int numImages = 4;

  // 曝光时间列表
  static const float timesArray[] = {1/30.0f,0.25,2.5,15.0};
  times.assign(timesArray, timesArray + numImages);

  // 图像文件名称列表
  static const char* filenames[] = {"img_0.033.jpg", "img_0.25.jpg", "img_2.5.jpg", "img_15.jpg"};
  for(int i=0; i < numImages; i++)
  {
    Mat im = imread(filenames[i]);
    images.push_back(im);
  }

}

```

**Python**

```
def readImagesAndTimes():
  # 曝光时间列表
  times = np.array([ 1/30.0, 0.25, 2.5, 15.0 ], dtype=np.float32)

  # 图像文件名称列表
  filenames = ["img_0.033.jpg", "img_0.25.jpg", "img_2.5.jpg", "img_15.jpg"]
  images = []
  for filename in filenames:
    im = cv2.imread(filename)
    images.append(im)

  return images, times

```

### 第 2 步：对齐图像

合成 HDR 图像时使用的图像如果未对齐可能会导致严重的伪影。 在下图中，左侧的图像是使用未对齐的图像组成的 HDR 图像，右侧的图像是使用对齐的图像的图像。 通过放大图像的一部分（使用红色圆圈显示的）我们会在左侧图像中看到严重的鬼影。

[![Misalignment problem in HDR](/data/attachment/album/201806/16/235247n5idpda35xm4dsh4.jpg)](http://www.learnopencv.com/wp-content/uploads/2017/10/aligned-unaligned-hdr-comparison.jpg)

在拍摄照片制作 HDR 图像时，专业摄影师自然是将相机安装在三脚架上。 他们还使用称为[镜像锁定](https://www.slrlounge.com/workshop/using-mirror-up-mode-mirror-lockup)功能来减少额外的振动。 即使如此，图像可能仍然没有完美对齐，因为没有办法保证无振动的环境。 使用手持相机或手机拍摄图像时，对齐问题会变得更糟。

幸运的是，OpenCV 提供了一种简单的方法，使用 `AlignMTB` 对齐这些图像。 该算法将所有图像转换为<ruby> 中值阈值位图 <rt>  median threshold bitmaps </rt></ruby>（MTB）。 图像的 MTB 生成方式为将比中值亮度的更亮的分配为 1，其余为 0。 MTB 不随曝光时间的改变而改变。 因此不需要我们指定曝光时间就可以对齐 MTB。

基于 MTB 的对齐方式的代码如下。

**C++**

```
// 对齐输入图像
Ptr<AlignMTB> alignMTB = createAlignMTB();
alignMTB->process(images, images);

```

**Python**

```
# 对齐输入图像
alignMTB = cv2.createAlignMTB()
alignMTB.process(images, images)

```

### 第 3 步：提取相机响应函数

典型相机的响应与场景亮度不成线性关系。 那是什么意思呢？ 假设有两个物体由同一个相机拍摄，在现实世界中其中一个物体是另一个物体亮度的两倍。 当您测量照片中两个物体的像素亮度时，较亮物体的像素值将不会是较暗物体的两倍。 在不估计<ruby> 相机响应函数 <rt>  Camera Response Function </rt></ruby>（CRF）的情况下，我们将无法将图像合并到一个HDR图像中。

将多个曝光图像合并为 HDR 图像意味着什么？

只考虑图像的某个位置 `(x,y)` 一个像素。 如果 CRF 是线性的，则像素值将直接与曝光时间成比例，除非像素在特定图像中太暗（即接近 0）或太亮（即接近 255）。 我们可以过滤出这些不好的像素（太暗或太亮），并且将像素值除以曝光时间来估计像素的亮度，然后在像素不差的（太暗或太亮）所有图像上对亮度值取平均。我们可以对所有像素进行这样的处理，并通过对“好”像素进行平均来获得所有像素的单张图像。

但是 CRF 不是线性的， 我们需要评估 CRF 把图像强度变成线性，然后才能合并或者平均它们。

好消息是，如果我们知道每个图像的曝光时间，则可以从图像估计 CRF。 与计算机视觉中的许多问题一样，找到 CRF 的问题本质是一个最优解问题，其目标是使由数据项和平滑项组成的目标函数最小化。 这些问题通常会降维到线性最小二乘问题，这些问题可以使用<ruby> 奇异值分解 <rt>  Singular Value Decomposition </rt></ruby>（SVD）来解决，奇异值分解是所有线性代数包的一部分。 CRF 提取算法的细节在[从照片提取高动态范围辐射图](http://www.pauldebevec.com/Research/HDR/debevec-siggraph97.pdf)这篇论文中可以找到。

使用 OpenCV 的 `CalibrateDebevec` 或者 `CalibrateRobertson` 就可以用 2 行代码找到 CRF。本篇教程中我们使用 `CalibrateDebevec`

**C++**

```
// 获取图像响应函数 (CRF)
Mat responseDebevec;
Ptr<CalibrateDebevec> calibrateDebevec = createCalibrateDebevec();
calibrateDebevec->process(images, responseDebevec, times);

```

**Python**

```
# 获取图像响应函数 (CRF)
calibrateDebevec = cv2.createCalibrateDebevec()
responseDebevec = calibrateDebevec.process(images, times)

```

下图显示了使用红绿蓝通道的图像提取的 CRF。

[![Camera Response Function](/data/attachment/album/201806/16/235248rjzpw7wyrexejeep.jpg)](http://www.learnopencv.com/wp-content/uploads/2017/10/camera-response-function.jpg)

### 第 4 步：合并图像

一旦 CRF 评估结束，我们可以使用 `MergeDebevec` 将曝光图像合并成一个HDR图像。 C++ 和 Python 代码如下所示。

**C++**

```
// 将图像合并为HDR线性图像
Mat hdrDebevec;
Ptr<MergeDebevec> mergeDebevec = createMergeDebevec();
mergeDebevec->process(images, hdrDebevec, times, responseDebevec);
// 保存图像
imwrite("hdrDebevec.hdr", hdrDebevec);

```

**Python**

```
# 将图像合并为HDR线性图像
mergeDebevec = cv2.createMergeDebevec()
hdrDebevec = mergeDebevec.process(images, times, responseDebevec)
# 保存图像
cv2.imwrite("hdrDebevec.hdr", hdrDebevec)

```

上面保存的 HDR 图像可以在 Photoshop 中加载并进行色调映射。示例图像如下所示。

[![HDR Photoshop tone mapping](/data/attachment/album/201806/16/235248gfot888jkzfjkoj2.jpg)](http://www.learnopencv.com/wp-content/uploads/2017/10/hdr-Photoshop-Tonemapping.jpg)

*HDR Photoshop 色调映射*

### 第 5 步：色调映射

现在我们已经将我们的曝光图像合并到一个 HDR 图像中。 你能猜出这个图像的最小和最大像素值吗？ 对于黑色条件，最小值显然为 0。 理论最大值是什么？ 无限大！ 在实践中，不同情况下的最大值是不同的。 如果场景包含非常明亮的光源，那么最大值就会非常大。

尽管我们已经使用多个图像恢复了相对亮度信息，但是我们现在又面临了新的挑战：将这些信息保存为 24 位图像用于显示。

>
> 将高动态范围（HDR）图像转换为 8 位单通道图像的过程称为色调映射。这个过程的同时还需要保留尽可能多的细节。
>
>
>

有几种色调映射算法。 OpenCV 实现了其中的四个。 要记住的是没有一个绝对正确的方法来做色调映射。 通常，我们希望在色调映射图像中看到比任何一个曝光图像更多的细节。 有时色调映射的目标是产生逼真的图像，而且往往是产生超现实图像的目标。 在 OpenCV 中实现的算法倾向于产生现实的并不那么生动的结果。

我们来看看各种选项。 以下列出了不同色调映射算法的一些常见参数。

1. <ruby> 伽马 <rt>  gamma </rt></ruby>：该参数通过应用伽马校正来压缩动态范围。 当伽马等于 1 时，不应用修正。 小于 1 的伽玛会使图像变暗，而大于 1 的伽马会使图像变亮。
2. <ruby> 饱和度 <rt>  saturation </rt></ruby>：该参数用于增加或减少饱和度。 饱和度高时，色彩更丰富，更浓。 饱和度值接近零，使颜色逐渐消失为灰度。
3. <ruby> 对比度 <rt>  contrast </rt></ruby>：控制输出图像的对比度（即 `log(maxPixelValue/minPixelValue)`）。

让我们来探索 OpenCV 中可用的四种色调映射算法。

#### Drago 色调映射

Drago 色调映射的参数如下所示：

```
createTonemapDrago
(
float   gamma = 1.0f,
float   saturation = 1.0f,
float   bias = 0.85f
)

```

这里，`bias` 是 `[0, 1]` 范围内偏差函数的值。 从 0.7 到 0.9 的值通常效果较好。 默认值是 0.85。 有关更多技术细节，请参阅这篇[论文](http://resources.mpi-inf.mpg.de/tmo/logmap/logmap.pdf)。

C++ 和 Python 代码如下所示。 参数是通过反复试验获得的。 最后的结果乘以 3 只是因为它给出了最令人满意的结果。

**C++**

```
// 使用Drago色调映射算法获得24位彩色图像
Mat ldrDrago;
Ptr<TonemapDrago> tonemapDrago = createTonemapDrago(1.0, 0.7);
tonemapDrago->process(hdrDebevec, ldrDrago);
ldrDrago = 3 * ldrDrago;
imwrite("ldr-Drago.jpg", ldrDrago * 255);

```

**Python**

```
# 使用Drago色调映射算法获得24位彩色图像
tonemapDrago = cv2.createTonemapDrago(1.0, 0.7)
ldrDrago = tonemapDrago.process(hdrDebevec)
ldrDrago = 3 * ldrDrago
cv2.imwrite("ldr-Drago.jpg", ldrDrago * 255)

```

结果如下：

[![HDR tone mapping using Drago's algorithm](/data/attachment/album/201806/16/235249x2dnqizzid5599iw.jpg)](http://www.learnopencv.com/wp-content/uploads/2017/10/hdr-Drago.jpg)

*使用Drago算法的HDR色调映射*

#### Durand 色调映射

Durand 色调映射的参数如下所示：

```
createTonemapDurand
(
  float     gamma = 1.0f,
  float     contrast = 4.0f,
  float     saturation = 1.0f,
  float     sigma_space = 2.0f,
  float     sigma_color = 2.0f
);

```

该算法基于将图像分解为基础层和细节层。 使用称为双边滤波器的边缘保留滤波器来获得基本层。 `sigma_space` 和`sigma_color` 是双边滤波器的参数，分别控制空间域和彩色域中的平滑量。

有关更多详细信息，请查看这篇[论文](https://people.csail.mit.edu/fredo/PUBLI/Siggraph2002/DurandBilateral.pdf)。

**C++**

```
// 使用Durand色调映射算法获得24位彩色图像
Mat ldrDurand;
Ptr<TonemapDurand> tonemapDurand = createTonemapDurand(1.5,4,1.0,1,1);
tonemapDurand->process(hdrDebevec, ldrDurand);
ldrDurand = 3 * ldrDurand;
imwrite("ldr-Durand.jpg", ldrDurand * 255);

```

**Python**

```
# 使用Durand色调映射算法获得24位彩色图像
 tonemapDurand = cv2.createTonemapDurand(1.5,4,1.0,1,1)
 ldrDurand = tonemapDurand.process(hdrDebevec)
 ldrDurand = 3 * ldrDurand
 cv2.imwrite("ldr-Durand.jpg", ldrDurand * 255)

```

结果如下：

[![HDR tone mapping using Durand's algorithm](/data/attachment/album/201806/16/235250mp1ot02r2h11rob0.jpg)](http://www.learnopencv.com/wp-content/uploads/2017/10/hdr-Durand.jpg)

*使用Durand算法的HDR色调映射*

#### Reinhard 色调映射

```

createTonemapReinhard
(
float   gamma = 1.0f,
float   intensity = 0.0f,
float   light_adapt = 1.0f,
float   color_adapt = 0.0f
)

```

`intensity` 参数应在 `[-8, 8]` 范围内。 更高的亮度值会产生更明亮的结果。 `light_adapt` 控制灯光，范围为 `[0, 1]`。 值 1 表示仅基于像素值的自适应，而值 0 表示全局自适应。 中间值可以用于两者的加权组合。 参数 `color_adapt` 控制色彩，范围为 `[0, 1]`。 如果值被设置为 1，则通道被独立处理，如果该值被设置为 0，则每个通道的适应级别相同。中间值可以用于两者的加权组合。

有关更多详细信息，请查看这篇[论文](http://citeseerx.ist.psu.edu/viewdoc/download?doi=10.1.1.106.8100&amp;rep=rep1&amp;type=pdf)。

**C++**

```
// 使用Reinhard色调映射算法获得24位彩色图像
Mat ldrReinhard;
Ptr<TonemapReinhard> tonemapReinhard = createTonemapReinhard(1.5, 0,0,0);
tonemapReinhard->process(hdrDebevec, ldrReinhard);
imwrite("ldr-Reinhard.jpg", ldrReinhard * 255);

```

**Python**

```
# 使用Reinhard色调映射算法获得24位彩色图像
tonemapReinhard = cv2.createTonemapReinhard(1.5, 0,0,0)
ldrReinhard = tonemapReinhard.process(hdrDebevec)
cv2.imwrite("ldr-Reinhard.jpg", ldrReinhard * 255)

```

结果如下：

[![HDR tone mapping using Reinhard's algorithm](/data/attachment/album/201806/16/235251f37zl038b0703cdx.jpg)](http://www.learnopencv.com/wp-content/uploads/2017/10/hdr-Reinhard.jpg)

*使用Reinhard算法的HDR色调映射*

#### Mantiuk 色调映射

```
createTonemapMantiuk
(
float   gamma = 1.0f,
float   scale = 0.7f,
float   saturation = 1.0f
)

```

参数 `scale` 是对比度比例因子。 从 0.7 到 0.9 的值通常效果较好

有关更多详细信息，请查看这篇[论文](http://citeseerx.ist.psu.edu/viewdoc/download?doi=10.1.1.60.4077&amp;rep=rep1&amp;type=pdf)。

**C++**

```
// 使用Mantiuk色调映射算法获得24位彩色图像
Mat ldrMantiuk;
Ptr<TonemapMantiuk> tonemapMantiuk = createTonemapMantiuk(2.2,0.85, 1.2);
tonemapMantiuk->process(hdrDebevec, ldrMantiuk);
ldrMantiuk = 3 * ldrMantiuk;
imwrite("ldr-Mantiuk.jpg", ldrMantiuk * 255);

```

**Python**

```
# 使用Mantiuk色调映射算法获得24位彩色图像
tonemapMantiuk = cv2.createTonemapMantiuk(2.2,0.85, 1.2)
ldrMantiuk = tonemapMantiuk.process(hdrDebevec)
ldrMantiuk = 3 * ldrMantiuk
cv2.imwrite("ldr-Mantiuk.jpg", ldrMantiuk * 255)

```

结果如下：

[![HDR tone mapping using Mantiuk's algorithm](/data/attachment/album/201806/16/235252gpgfmq2fg32f2gq1.jpg)](http://www.learnopencv.com/wp-content/uploads/2017/10/hdr-Mantiuk.jpg)

*使用Mantiuk算法的HDR色调映射*

### 订阅然后下载代码

如果你喜欢这篇文章，并希望下载本文中使用的代码（C++ 和 Python）和示例图片，请[订阅](https://bigvisionllc.leadpages.net/leadbox/143948b73f72a2%3A173c9390c346dc/5649050225344512/)我们的电子杂志。 您还将获得免费的[计算机视觉资源](https://bigvisionllc.leadpages.net/leadbox/143948b73f72a2%3A173c9390c346dc/5649050225344512/)指南。 在我们的电子杂志中，我们分享了用 C++ 还有 Python 编写的 OpenCV 教程和例子，以及计算机视觉和机器学习的算法和新闻。

[点此订阅](https://bigvisionllc.leadpages.net/leadbox/143948b73f72a2%3A173c9390c346dc/5649050225344512/)

图片致谢

本文中使用的四个曝光图像获得 [CC BY-SA 3.0](https://creativecommons.org/licenses/by-sa/3.0/) 许可，并从[维基百科的 HDR 页面](https://en.wikipedia.org/wiki/High-dynamic-range_imaging)下载。 图像由 Kevin McCoy拍摄。

---

作者简介：

我是一位热爱计算机视觉和机器学习的企业家，拥有十多年的实践经验（还有博士学位）。

2007 年，在完成博士学位之后，我和我的顾问 David Kriegman 博士还有 Kevin Barnes 共同创办了 TAAZ 公司。 我们的计算机视觉和机器学习算法的可扩展性和鲁棒性已经经过了试用了我们产品的超过 1 亿的用户的严格测试。

---

via: <http://www.learnopencv.com/high-dynamic-range-hdr-imaging-using-opencv-cpp-python/>

作者：[SATYA MALLICK](http://www.learnopencv.com/about/) 译者：[Flowsnow](https://github.com/Flowsnow) 校对：[wxy](https://github.com/wxy)

本文由 [LCTT](https://github.com/LCTT/TranslateProject) 原创编译，[Linux中国](https://linux.cn/) 荣誉推出


使用 Stratis 从命令行管理 Linux 存储

Summary: 通过从命令行运行它，得到这个易于使用的 Linux 存储工具的主要用途。

为什么2G的硬盘挂载后df显示1TB空间，写满2G文件后还可以继续写入，重启后无法挂载分区，使用VMware虚拟机

来自河北唐山的 Microsoft Edge 18.18362|Windows 10 用户

通过从命令行运行它，得到这个易于使用的 Linux 存储工具的主要用途。

>
> 通过从命令行运行它，得到这个易于使用的 Linux 存储工具的主要用途。
>
>
>

![](/data/attachment/album/201806/17/000945iop8quph8z11f1a8.jpg)

正如本系列的[第一部分](/article-9736-1.html)和[第二部分](/article-9743-1.html)中所讨论的，Stratis 是一个具有与 [ZFS](https://en.wikipedia.org/wiki/ZFS) 和 [Btrfs](https://en.wikipedia.org/wiki/Btrfs) 相似功能的卷管理文件系统。在本文中，我们将介绍如何在命令行上使用 Stratis。

### 安装 Stratis

对于非开发人员，现在尝试 Stratis 最简单的方法是在 [Fedora 28](https://fedoraproject.org/wiki/Releases/28/Schedule) 中。

你可以用以下命令安装 Stratis 守护进程和 Stratis 命令行工具：

```
# dnf install stratis-cli stratisd

```

### 创建一个池

Stratis 有三个概念：blockdevs、池和文件系统。 Blockdevs 是组成池的块设备，例如磁盘或磁盘分区。一旦创建池，就可以从中创建文件系统。

假设你的系统上有一个名为 `vdg` 的块设备，它目前没有被使用或挂载，你可以在它上面创建一个 Stratis 池：

```
# stratis pool create mypool /dev/vdg

```

这假设 `vdg` 是完全清零并且是空的。如果它没有被使用，但有旧数据，则可能需要使用 `pool create` 的 `-force` 选项。如果正在使用，请勿将它用于 Stratis。

如果你想从多个块设备创建一个池，只需在 `pool create` 命令行中列出它们。你也可以稍后使用 `blockdev add-data` 命令添加更多的 blockdevs。请注意，Stratis 要求 blockdevs 的大小至少为 1 GiB。

### 创建文件系统

在你创建了一个名为 `mypool` 的池后，你可以从它创建文件系统：

```
# stratis fs create mypool myfs1

```

从 `mypool` 池创建一个名为 `myfs1` 的文件系统后，可以使用 Stratis 在 `/dev/stratis` 中创建的条目来挂载并使用它：

```
# mkdir myfs1
# mount /dev/stratis/mypool/myfs1 myfs1

```

文件系统现在已被挂载在 `myfs1` 上并准备可以使用。

### 快照

除了创建空文件系统之外，你还可以创建一个文件系统作为现有文件系统的快照：

```
# stratis fs snapshot mypool myfs1 myfs1-experiment

```

这样做后，你可以挂载新的 `myfs1-experiment`，它将初始包含与 `myfs1` 相同的文件内容，但它可能随着文件系统的修改而改变。无论你对 `myfs1-experiment` 所做的任何更改都不会反映到 `myfs1` 中，除非你卸载了 `myfs1` 并将其销毁：

```
# umount myfs1
# stratis fs destroy mypool myfs1

```

然后进行快照以重新创建并重新挂载它：

```
# stratis fs snapshot mypool myfs1-experiment myfs1
# mount /dev/stratis/mypool/myfs1 myfs1

```

### 获取信息

Stratis 可以列出系统中的池：

```
# stratis pool list

```

随着文件系统写入更多数据，你将看到 “Total Physical Used” 值的增加。当这个值接近 “Total Physical Size” 时要小心。我们仍在努力处理这个问题。

列出池中的文件系统：

```
# stratis fs list mypool

```

列出组成池的 blockdevs：

```
# stratis blockdev list mypool

```

目前只提供这些最少的信息，但它们将在未来提供更多信息。

### 摧毁池

当你了解了 Stratis 可以做什么后，要摧毁池，首先确保从它创建的所有文件系统都被卸载并销毁，然后使用 `pool destroy` 命令：

```
# umount myfs1
# umount myfs1-experiment (if you created it)
# stratis fs destroy mypool myfs1
# stratis fs destroy mypool myfs1-experiment
# stratis pool destroy mypool

```

`stratis pool list` 现在应该显示没有池。

就是这些！有关更多信息，请参阅手册页：“man stratis”。

---

via: <https://opensource.com/article/18/5/stratis-storage-linux-command-line>

作者：[Andy Grover](https://opensource.com/users/agrover) 选题：[lujun9972](https://github.com/lujun9972) 译者：[geekpi](https://github.com/geekpi) 校对：[wxy](https://github.com/wxy)

本文由 [LCTT](https://github.com/LCTT/TranslateProject) 原创编译，[Linux中国](https://linux.cn/) 荣誉推出


什么是 Linux 服务器，你的业务为什么需要它？

Summary: 想寻找一个稳定、安全的基础来为您的企业应用程序提供未来的动力？Linux 服务器可能是答案。

>
> 想寻找一个稳定、安全的基础来为您的企业应用程序的未来提供动力？Linux 服务器可能是答案。
>
>
>

![](/data/attachment/album/201806/17/185713qwqp6s7wwd7d7lel.png)

IT 组织力求通过提高生产力和提供更快速的服务来提供商业价值，同时保持足够的灵活性，将云、容器和配置自动化等创新融入其中。现代的工作任务，无论是裸机、虚拟机、容器，还是私有云或公共云，都需要是可移植且可扩展的。支持所有的这些需要一个现代且安全的平台。

通往创新最直接的途径并不总是一条直线。随着私有云和公共云、多种体系架构和虚拟化的日益普及，当今的数据中心就像一个球一样，基础设施的选择各不相同，从而带来了维度和深度。就像飞行员依赖空中交通管制员提供持续更新一样，数字化转型之旅应该由像 Linux 这样可信赖的操作系统来指引，以提供持续更新的技术，以及对云、容器和配置自动化等创新的最有效和安全的访问。

Linux 是一个家族，它围绕 Linux 内核构建的自由、开源软件操作系统。最初开发的是基于 Intel x86 架构的个人电脑，此后比起任何其他操作系统，Linux 被移植到更多的平台上。得益于基于 Linux 内核的 Android 操作系统在智能手机上的主导地位，Linux 拥有所有通用操作系统中最大的安装基数。Linux 也是服务器和大型计算机等“大型机”系统的主要操作系统，也是 [TOP500](https://en.wikipedia.org/wiki/TOP500) 超级计算机上唯一使用的操作系统。

为了利用这一功能，许多企业公司已经采用具有高性能的 Linux 开源操作系统的服务器。这些旨在处理最苛刻的业务应用程序要求，如网络和系统管理、数据库管理和 Web 服务。出于其稳定性、安全性和灵活性，通常选择 Linux 服务器而不是其他服务器操作系统。位居前列的 Linux 服务器操作系统包括 [Debian](https://www.debian.org/)、 [Ubuntu Server](https://www.ubuntu.com/download/server)、 [CentOS](https://www.centos.org/)、[Slackware](http://www.slackware.com/) 和 [Gentoo](https://www.gentoo.org/)。

在企业级工作任务中，你应该考虑企业级 Linux 服务器上的哪些功能和优势？首先，通过对 Linux 和 Windows 管理员都熟悉的界面，内置的安全控制和可管理的扩展使你可以专注于业务增长，而不是对安全漏洞和昂贵的管理配置错误担心。你选择的 Linux 服务器应提供安全技术和认证，并保持增强以抵御入侵，保护你的数据，而且满足一个开放源代码项目或特定系统供应商的合规性。它应该：

* 使用集中式身份管理和[安全增强型 Linux](https://en.wikipedia.org/wiki/Security-Enhanced_Linux)（SELinux）、强制访问控制（MAC）等集成控制功能来**安全地交付资源**，这是[通用标准认证](https://en.wikipedia.org/wiki/Common_Criteria) 和 [FIPS 140-2 认证](https://en.wikipedia.org/wiki/FIPS_140-2)，并且第一个 Linux 容器框架支持也是通用标准认证。
* **自动执行法规遵从和安全配置修复** 应贯穿于你的系统和容器。通过像 OpenSCAP 的映像扫描，它应该可以检查、补救漏洞和配置安全基准，包括针对 [PCI-DSS](https://www.pcisecuritystandards.org/pci_security/)、 [DISA STIG](https://iase.disa.mil/stigs/Pages/index.aspx) 等的[国家清单程序](https://www.nist.gov/programs-projects/national-checklist-program)内容。另外，它应该在整个混合环境中集中和扩展配置修复。
* **持续接收漏洞安全更新**，从上游社区或特定的系统供应商，如有可能，可在下一工作日补救并提供所有关键问题，以最大限度地降低业务影响。

作为混合数据中心的基础，Linux 服务器应提供平台可管理性和与传统管理和自动化基础设施的灵活集成。与非付费的 Linux 基础设施相比，这将节省 IT 员工的时间并减少意外停机的情况。它应该：

* 通过内置功能 **加速整个数据中心的映像构建、部署和补丁管理**，并丰富系统生命周期管理，配置和增强的修补等等。
* 通过一个 **简单易用的 web 界面管理单个系统**，包括存储、网络、容器、服务等等。
* 通过使用 [Ansible](https://www.ansible.com/)、 [Chef](https://www.chef.io/chef/)、 [Salt](https://saltstack.com/salt-open-source/)、 [Puppet](https://puppet.com/) 等原生配置管理工具，可以跨多个异构环境实现 **自动化一致性和合规性**，并通过系统角色减少脚本返工。
* 通过就地升级 **简化平台更新**，消除机器迁移和应用程序重建的麻烦。
* 通过使用预测分析工具自动识别和修复异常情况及其根本原因，在技术问题影响业务运营之前 **解决技术问题**。

Linux 服务器正在在全球范围内推动创新。作为一个企业工作任务的平台，Linux 服务器应该为运行当下和未来业务的应用程序提供稳定，安全和性能驱动的基础。

---

via: <https://opensource.com/article/18/5/what-linux-server>

作者：[Daniel Oh](https://opensource.com/users/daniel-oh) 选题：[lujun9972](https://github.com/lujun9972) 译者：[MjSeven](https://github.com/MjSeven) 校对：[wxy](https://github.com/wxy)

本文由 [LCTT](https://github.com/LCTT/TranslateProject) 原创编译，[Linux中国](https://linux.cn/) 荣誉推出


闭包、对象，以及堆“族”

Summary: 它就好像是 makeGreeter 的词法作用域在那个时刻被关闭了，一旦需要时被具体化到一个堆对象（在这个案例中，是指返回的函数的生命周期）。因此叫做闭包。

![](/data/attachment/album/201806/18/110440nuqxqqq88r61qr8t.jpg)

在上篇文章中我们提到了闭包、对象、以及栈外的其它东西。我们学习的大部分内容都是与特定编程语言无关的元素，但是，我主要还是专注于 JavaScript，以及一些 C。让我们以一个简单的 C 程序开始，它的功能是读取一首歌曲和乐队名字，然后将它们输出给用户：

```
#include <stdio.h>
#include <string.h>

char *read()
{
    char data[64];
    fgets(data, 64, stdin);
    return data;
}

int main(int argc, char *argv[])
{
    char *song, *band;

    puts("Enter song, then band:");
    song = read();
    band = read();

    printf("\n%sby %s", song, band);
    return 0;
}

```

*stackFolly.c [下载](https://manybutfinite.com/code/x86-stack/stackFolly.c)*

如果你运行这个程序，你会得到什么？（=> 表示程序输出）：

```
./stackFolly
=> Enter song, then band:
The Past is a Grotesque Animal
of Montreal

=> ?ǿontreal
=> by ?ǿontreal

```

（曾经的 C 新手说）发生了错误？

事实证明，函数的栈变量的内容仅在栈帧活动期间才是可用的，也就是说，仅在函数返回之前。在上面的返回中，被栈帧使用过的内存 [被认为是可用的](https://manybutfinite.com/post/epilogues-canaries-buffer-overflows/)，并且在下一个函数调用中可以被覆写。

下面的图展示了这种情况下究竟发生了什么。这个图现在有一个图片映射（LCTT 译注：译文中无法包含此映射，上下两个矩形区域分别链接至输出的 [#47](https://github.com/gduarte/blog/blob/master/code/x86-stack/stackFolly-gdb-output.txt#L47) 行和 [#70](https://github.com/gduarte/blog/blob/master/code/x86-stack/stackFolly-gdb-output.txt#L70) 行），因此，你可以点击一个数据片断去看一下相关的 GDB 输出（GDB 命令在 [这里](https://github.com/gduarte/blog/blob/master/code/x86-stack/stackFolly-gdb-commands.txt)）。只要 `read()` 读取了歌曲的名字，栈将是这个样子：

![](/data/attachment/album/201806/18/110504ntt1vfkddipcdid6.png)

在这个时候，这个 `song` 变量立即指向到歌曲的名字。不幸的是，存储字符串的内存位置准备被下次调用的任意函数的栈帧重用。在这种情况下，`read()` 再次被调用，而且使用的是同一个位置的栈帧，因此，结果变成下图的样子（LCTT 译注：上下两个矩形映射分别链接至 [#76](https://github.com/gduarte/blog/blob/master/code/x86-stack/stackFolly-gdb-output.txt#L76) 行和 [#79](https://github.com/gduarte/blog/blob/master/code/x86-stack/stackFolly-gdb-output.txt#L79) 行）：

![](/data/attachment/album/201806/18/110506cokphshkmjehrhrh.png)

乐队名字被读入到相同的内存位置，并且覆盖了前面存储的歌曲名字。`band` 和 `song` 最终都准确指向到相同点。最后，我们甚至都不能得到 “of Montreal”（LCTT 译注：一个欧美乐队的名字） 的正确输出。你能猜到是为什么吗？

因此，即使栈很有用，但也有很重要的限制。它不能被一个函数用于去存储比该函数的运行周期还要长的数据。你必须将它交给 [堆](https://github.com/gduarte/blog/blob/master/code/x86-stack/readIntoHeap.c)，然后与热点缓存、明确的瞬时操作、以及频繁计算的偏移等内容道别。有利的一面是，它是[工作](https://github.com/gduarte/blog/blob/master/code/x86-stack/readIntoHeap-gdb-output.txt#L47) 的：

![](/data/attachment/album/201806/18/110507ridhzkqlq0f1pp9f.png)

这个代价是你必须记得去 `free()` 内存，或者由一个垃圾回收机制花费一些性能来随机回收，垃圾回收将去找到未使用的堆对象，然后去回收它们。那就是栈和堆之间在本质上的权衡：性能 vs. 灵活性。

大多数编程语言的虚拟机都有一个中间层用来做一个 C 程序员该做的一些事情。栈被用于**值类型**，比如，整数、浮点数、以及布尔型。这些都按特定值（像上面的 `argc` ）的字节顺序被直接保存在本地变量和对象字段中。相比之下，堆被用于**引用类型**，比如，字符串和 [对象](https://code.google.com/p/v8/source/browse/trunk/src/objects.h#37)。 变量和字段包含一个引用到这个对象的内存地址，像上面的 `song` 和 `band`。

参考这个 JavaScript 函数：

```
function fn()
{
    var a = 10;
    var b = { name: 'foo', n: 10 };
}

```

它可能的结果如下（LCTT 译注：图片内“object”、“string”和“a”的映射分别链接至 [#1671](https://code.google.com/p/v8/source/browse/trunk/src/objects.h#1671) 行、 [#8656](https://code.google.com/p/v8/source/browse/trunk/src/objects.h#8656) 行和 [#1264](https://code.google.com/p/v8/source/browse/trunk/src/objects.h#1264) 行）：

![](/data/attachment/album/201806/18/110508k2i3h7zt2q7k6k3r.png)

我之所以说“可能”的原因是，特定的行为高度依赖于实现。这篇文章使用的许多流程图形是以一个 V8 为中心的方法，这些图形都链接到相关的源代码。在 V8 中，仅 [小整数](https://code.google.com/p/v8/source/browse/trunk/src/objects.h#1264) 是 [以值的方式保存](https://code.google.com/p/v8/source/browse/trunk/src/objects.h#148)。因此，从现在开始，我将在对象中直接以字符串去展示，以避免引起混乱，但是，请记住，正如上图所示的那样，它们在堆中是分开保存的。

现在，我们来看一下闭包，它其实很简单，但是由于我们将它宣传的过于夸张，以致于有点神化了。先看一个简单的 JS 函数：

```
function add(a, b)
{
    var c = a + b;
    return c;
}

```

这个函数定义了一个<ruby> 词法域 <rt>  lexical scope </rt></ruby>，它是一个快乐的小王国，在这里它的名字 `a`、`b`、`c` 是有明确意义的。它有两个参数和由函数声明的一个本地变量。程序也可以在别的地方使用相同的名字，但是在 `add` 内部它们所引用的内容是明确的。尽管词法域是一个很好的术语，它符合我们直观上的理解：毕竟，我们从字面意义上看，我们可以像词法分析器一样，把它看作在源代码中的一个文本块。

在看到栈帧的操作之后，很容易想像出这个名称的具体实现。在 `add` 内部，这些名字引用到函数的每个运行实例中私有的栈的位置。这种情况在一个虚拟机中经常发生。

现在，我们来嵌套两个词法域：

```
function makeGreeter()
{
    return function hi(name){
        console.log('hi, ' + name);
    }
}

var hi = makeGreeter();
hi('dear reader'); // prints "hi, dear reader"

```

那样更有趣。函数 `hi` 在函数 `makeGreeter` 运行的时候被构建在它内部。它有它自己的词法域，`name` 在这个地方是一个栈上的参数，但是，它似乎也可以访问父级的词法域，它可以那样做。我们来看一下那样做的好处：

```
function makeGreeter(greeting)
{
    return function greet(name){
        console.log(greeting + ', ' + name);
    }
}

var heya = makeGreeter('HEYA');
heya('dear reader'); // prints "HEYA, dear reader"

```

虽然有点不习惯，但是很酷。即便这样违背了我们的直觉：`greeting` 确实看起来像一个栈变量，这种类型应该在 `makeGreeter()` 返回后消失。可是因为 `greet()` 一直保持工作，出现了一些奇怪的事情。进入闭包（LCTT 译注：“Context” 和 “JSFunction” 映射分别链接至 [#188](https://code.google.com/p/v8/source/browse/trunk/src/contexts.h#188) 行和 [#7245](https://code.google.com/p/v8/source/browse/trunk/src/objects.h#7245) 行）：

![](/data/attachment/album/201806/18/110509gf48c5rxnhf53zaz.png)

虚拟机分配一个对象去保存被里面的 `greet()` 使用的父级变量。它就好像是 `makeGreeter` 的词法作用域在那个时刻被<ruby> 关闭 <rt>  closed over </rt></ruby>了，一旦需要时被具体化到一个堆对象（在这个案例中，是指返回的函数的生命周期）。因此叫做<ruby> 闭包 <rt>  closure </rt></ruby>，当你这样去想它的时候，它的名字就有意义了。如果使用（或者捕获）了更多的父级变量，对象内容将有更多的属性，每个捕获的变量有一个。当然，发送到 `greet()` 的代码知道从对象内容中去读取问候语，而不是从栈上。

这是完整的示例：

```
function makeGreeter(greetings)
{
    var count = 0;
    var greeter = {};

    for (var i = 0; i < greetings.length; i++) {
        var greeting = greetings[i];

        greeter[greeting] = function(name){
            count++;
            console.log(greeting + ', ' + name);
        }
    }

    greeter.count = function(){return count;}

    return greeter;
}

var greeter = makeGreeter(["hi", "hello","howdy"])
greeter.hi('poppet');//prints "howdy, poppet"
greeter.hello('darling');// prints "howdy, darling"
greeter.count(); // returns 2

```

是的，`count()` 在工作，但是我们的 `greeter` 是在 `howdy` 中的栈上。你能告诉我为什么吗？我们使用 `count` 是一条线索：尽管词法域进入一个堆对象中被关闭，但是变量（或者对象属性）带的值仍然可能被改变。下图是我们拥有的内容（LCTT 译注：映射从左到右“Object”、“JSFunction”和“Context”分别链接至 [#1671](https://code.google.com/p/v8/source/browse/trunk/src/objects.h#1671) 行、[#7245](https://code.google.com/p/v8/source/browse/trunk/src/objects.h#7245) 行和 [#188](https://code.google.com/p/v8/source/browse/trunk/src/contexts.h#188) 行）：

![](/data/attachment/album/201806/18/110511w6uzdegg84g8kd63.png)

这是一个被所有函数共享的公共内容。那就是为什么 `count` 工作的原因。但是，`greeting` 也是被共享的，并且它被设置为迭代结束后的最后一个值，在这个案例中是 “howdy”。这是一个很常见的一般错误，避免它的简单方法是，引用一个函数调用，以闭包变量作为一个参数。在 CoffeeScript 中， [do](http://coffeescript.org/#loops) 命令提供了一个实现这种目的的简单方式。下面是对我们的 `greeter` 的一个简单的解决方案：

```
function makeGreeter(greetings)
{
    var count = 0;
    var greeter = {};

    greetings.forEach(function(greeting){
        greeter[greeting] = function(name){
            count++;
            console.log(greeting + ', ' + name);
        }
    });

    greeter.count = function(){return count;}

    return greeter;
}

var greeter = makeGreeter(["hi", "hello", "howdy"])
greeter.hi('poppet'); // prints "hi, poppet"
greeter.hello('darling'); // prints "hello, darling"
greeter.count(); // returns 2

```

它现在是工作的，并且结果将变成下图所示（LCTT 译注：映射从左到右“Object”、“JSFunction”和“Context”分别链接至 [#1671](https://code.google.com/p/v8/source/browse/trunk/src/objects.h#1671) 行、[#7245](https://code.google.com/p/v8/source/browse/trunk/src/objects.h#7245) 行和 [#188](https://code.google.com/p/v8/source/browse/trunk/src/contexts.h#188) 行）：

![](/data/attachment/album/201806/18/110513waph7ocqo6ptkzcp.png)

这里有许多箭头！在这里我们感兴趣的特性是：在我们的代码中，我们闭包了两个嵌套的词法内容，并且完全可以确保我们得到了两个链接到堆上的对象内容。你可以嵌套并且闭包任何词法内容，像“俄罗斯套娃”似的，并且最终从本质上说你使用的是所有那些 Context 对象的一个链表。

当然，就像受信鸽携带信息启发实现了 TCP 一样，去实现这些编程语言的特性也有很多种方法。例如，ES6 规范定义了 [词法环境](http://people.mozilla.org/%7Ejorendorff/es6-draft.html#sec-lexical-environments) 作为 [环境记录](http://people.mozilla.org/%7Ejorendorff/es6-draft.html#sec-environment-records)( 大致相当于在一个块内的本地标识）的组成部分，加上一个链接到外部环境的记录，这样就允许我们看到的嵌套。逻辑规则是由规范（一个希望）所确定的，但是其实现取决于将它们变成比特和字节的转换。

你也可以检查具体案例中由 V8 产生的汇编代码。[Vyacheslav Egorov](http://mrale.ph) 有一篇很好的文章，它使用 V8 的 [闭包内部构件](http://mrale.ph/blog/2012/09/23/grokking-v8-closures-for-fun.html) 详细解释了这一过程。我刚开始学习 V8，因此，欢迎指教。如果你熟悉 C#，检查闭包产生的中间代码将会很受启发 —— 你将看到显式定义的 V8 内容和实例化的模拟。

闭包是个强大的“家伙”。它在被一组函数共享期间，提供了一个简单的方式去隐藏来自调用者的信息。我喜欢它们真正地隐藏你的数据：不像对象字段，调用者并不能访问或者甚至是看到闭包变量。保持接口清晰而安全。

但是，它们并不是“银弹”（LCTT 译注：意指极为有效的解决方案，或者寄予厚望的新技术）。有时候一个对象的拥护者和一个闭包的狂热者会无休止地争论它们的优点。就像大多数的技术讨论一样，他们通常更关注的是自尊而不是真正的权衡。不管怎样，Anton van Straaten 的这篇 [史诗级的公案](http://people.csail.mit.edu/gregs/ll1-discuss-archive-html/msg03277.html) 解决了这个问题：

>
> 德高望重的老师 Qc Na 和它的学生 Anton 一起散步。Anton 希望将老师引入到一个讨论中，Anton 说：“老师，我听说对象是一个非常好的东西，是这样的吗？Qc Na 同情地看了一眼，责备它的学生说：“可怜的孩子 —— 对象不过是穷人的闭包而已。” Anton 待它的老师走了之后，回到他的房间，专心学习闭包。他认真地阅读了完整的 “Lambda：The Ultimate…" 系列文章和它的相关资料，并使用一个基于闭包的对象系统实现了一个小的架构解释器。他学到了很多的东西，并期待告诉老师他的进步。在又一次和 Qc Na 散步时，Anton 尝试给老师留下一个好的印象，说“老师，我仔细研究了这个问题，并且，现在理解了对象真的是穷人的闭包。”Qc Na 用它的手杖打了一下 Anton 说：“你什么时候才能明白？闭包是穷人的对象。”在那个时候，Anton 顿悟了。Anton van Straaten 说：“原来架构这么酷啊？”
>
>
>

探秘“栈”系列文章到此结束了。后面我将计划去写一些其它的编程语言实现的主题，像对象绑定和虚表。但是，内核调用是很强大的，因此，明天将发布一篇操作系统的文章。我邀请你 [订阅](https://manybutfinite.com/feed.xml) 并 [关注我](http://twitter.com/manybutfinite)。

---

via:<https://manybutfinite.com/post/closures-objects-heap/>

作者：[Gustavo Duarte](http://duartes.org/gustavo/blog/about/) 译者：[qhwdw](https://github.com/qhwdw) 校对：[wxy](https://github.com/wxy)

本文由 [LCTT](https://github.com/LCTT/TranslateProject) 原创编译，[Linux中国](https://linux.cn/) 荣誉推出


我正在运行的 Linux 是什么版本？

Summary: 掌握这些快捷命令以找出你正在运行的 Linux 系统的内核版本和发行版。

掌握这些快捷命令以找出你正在运行的 Linux 系统的内核版本和发行版。

>
> 掌握这些快捷命令以找出你正在运行的 Linux 系统的内核版本和发行版。
>
>
>

![](/data/attachment/album/201806/18/223459z28f88yxxgbxcwtx.png)

“什么版本的 Linux ？”这个问题可能意味着两个不同的东西。严格地说，Linux 是内核，所以问题可以特指内核的版本号，或者 “Linux” 可以更通俗地用来指整个发行版，就像在 Fedora Linux 或 Ubuntu Linux 中一样。

两者都很重要，你可能需要知道其中一个或全部答案来修复系统中的问题。例如，了解已安装的内核版本可能有助于诊断带有专有驱动程序的问题，并且确定正在运行的发行版将帮助你快速确定是否应该使用 `apt`、 `dnf`、 `yum` 或其他命令来安装软件包。

以下内容将帮助你了解 Linux 内核的版本和/或系统上正在运行的 Linux 发行版是什么。

### 如何找到 Linux 内核版本

要找出哪个 Linux 内核版本正在运行，运行以下命令：

```
uname -srm

```

或者，可以使用更长，更具描述性的各种标志的版本来运行该命令：

```
uname --kernel-name --kernel-release --machine

```

无论哪种方式，输出都应该类似于以下内容：

```
Linux 4.16.10-300.fc28.x86_64 x86_64

```

这为你提供了（按顺序）：内核名称、内核版本以及运行内核的硬件类型。在上面的情况下，内核是 Linux ，版本 4.16.10-300.fc28.x86*64 ，运行于 x86*64 系统。

有关 `uname` 命令的更多信息可以通过运行 `man uname` 找到。

### 如何找出 Linux 发行版

有几种方法可以确定系统上运行的是哪个发行版，但最快的方法是检查 `/etc/os-release` 文件的内容。此文件提供有关发行版的信息，包括但不限于发行版名称及其版本号。某些发行版的 `os-release` 文件包含比其他发行版更多的细节，但任何包含 `os-release` 文件的发行版都应该提供发行版的名称和版本。

要查看 `os-release` 文件的内容，运行以下命令：

```
cat /etc/os-release

```

在 Fedora 28 中，输出如下所示：

```
NAME=Fedora
VERSION="28 (Workstation Edition)"
ID=fedora
VERSION_ID=28
PLATFORM_ID="platform:f28"
PRETTY_NAME="Fedora 28 (Workstation Edition)"
ANSI_COLOR="0;34"
CPE_NAME="cpe:/o:fedoraproject:fedora:28"
HOME_URL="https://fedoraproject.org/"
SUPPORT_URL="https://fedoraproject.org/wiki/Communicating_and_getting_help"
BUG_REPORT_URL="https://bugzilla.redhat.com/"
REDHAT_BUGZILLA_PRODUCT="Fedora"
REDHAT_BUGZILLA_PRODUCT_VERSION=28
REDHAT_SUPPORT_PRODUCT="Fedora"
REDHAT_SUPPORT_PRODUCT_VERSION=28
PRIVACY_POLICY_URL="https://fedoraproject.org/wiki/Legal:PrivacyPolicy"
VARIANT="Workstation Edition"
VARIANT_ID=workstation

```

如上面那个例子展示的那样，Fedora 的 `os-release` 文件提供了发行版的名称和版本，但它也标识这个安装的变体（“Workstation Edition”）。如果我们在 Fedora 28 服务器版本上运行相同的命令，`os-release` 文件的内容会反映在 `VARIANT` 和 `VARIANT_ID` 行中。

有时候知道一个发行版是否与另一个发行版相似非常有用，因此 `os-release` 文件可以包含一个 `ID_LIKE` 行，用于标识正在运行的是基于什么的发行版或类似的发行版。例如，Red Hat Linux 企业版的 `os-release` 文件包含 `ID_LIKE` 行，声明 RHEL 与 Fedora 类似；CentOS 的 `os-release` 文件声明 CentOS 与 RHEL 和 Fedora 类似。如果你正在使用基于另一个发行版的发行版并需要查找解决问题的说明，那么 `ID_LIKE` 行非常有用。

CentOS 的 `os-release` 文件清楚地表明它就像 RHEL 一样，所以在各种论坛中关于 RHEL 的文档，问题和答案应该（大多数情况下）适用于 CentOS。CentOS 被设计成一个 RHEL 近亲，因此在某些字段它更兼容其 `ID_LIKE` 系统的字段。如果你找不到正在运行的发行版的信息，检查有关 “类似” 发行版的答案总是一个好主意。

有关 `os-release` 文件的更多信息可以通过运行 `man os-release` 命令来查找。

---

via: <https://opensource.com/article/18/6/linux-version>

作者：[Joshua Allen Holm](https://opensource.com/users/holmja) 选题：[lujun9972](https://github.com/lujun9972) 译者：[MjSeven](https://github.com/MjSeven) 校对：[wxy](https://github.com/wxy)

本文由 [LCTT](https://github.com/LCTT/TranslateProject) 原创编译，[Linux中国](https://linux.cn/) 荣誉推出


Oracle Linux 系统如何去注册使用坚不可摧 Linux 网络（ULN）

![](/data/attachment/album/201806/18/230317ts9jixj6dfn382fl.jpg)

大多数人都知道 RHEL 的订阅 ，但是知道 Oracle 订阅及细节的人却很少。

甚至我也不知道关于它的信息，我是最近才了解了有关它的信息，想将这些内容共享给其他人。因此写了这篇文章，它将指导你去注册 Oracle Linux 系统去使用坚不可摧 Linux 网络（ULN） 。

这将允许你去注册系统以尽快获得软件更新和其它的补丁。

### 什么是坚不可摧 Linux 网络

ULN 代表<ruby> 坚不可摧 Linux 网络 <rt>  Unbreakable Linux Network </rt></ruby>，它是由 Oracle 所拥有的。如果你去 Oracle OS 支持中去激活这个订阅，你就可以注册你的系统去使用坚不可摧 Linux 网络（ULN）。

ULN 为 Oracle Linux 和 Oracle VM 提供软件补丁、更新、以及修复，这些信息同时提供在 yum、Ksplice、并提供支持策略。你也可以通过它来下载原始发行版中没有包含的有用的安装包。

ULN 的告警提示工具会周期性地使用 ULN 进行检查，当有更新的时候它给你发送警报信息。

如果你想在 yum 上使用 ULN 仓库去管理你的系统，需要确保你的系统已经注册到 ULN 上，并且订阅了一个或多个 ULN 频道。当你注册一个系统使用 ULN，它将基于你的系统架构和操作系统去自动选择频道中最新的版本。

### 如何注册为一个 ULN 用户

要注册为一个 ULN 用户，需要你有一个 Oracle Linux 支持或者 Oracle VM 支持的有效客户支持代码（CSI）。

请按以下步骤去注册为一个 ULN 用户。

请访问 [linux.oracle.com](https://linux.oracle.com/register)：

![](/data/attachment/album/201806/18/230345exiy09edjdkkzzpf.png)

如果你已经有一个 SSO 帐户，请点击 “Sign On”。

![](/data/attachment/album/201806/18/230346zoeqehereejyyypj.png)

如果你没有帐户，点击 “Create New Single Signon Account” 然后按屏幕上的要求去创建一个帐户。

![](/data/attachment/album/201806/18/230347kukpboxkq91x6wbo.png)

验证你的电子邮件地址以完成帐户设置。

使用你的 SSO 帐户的用户名和密码去登入。在 “Create New ULN User” 页面上，输入你的 CSI 然后点击 “Create New User”。

![](/data/attachment/album/201806/18/230347yzt6oa0eeo6s9f6o.png)

**注意：**

* 如果当前没有分配管理员去管理 CSI，将会提示你去点击确认让你成为 CSI 管理员。
* 如果你的用户名已经在系统上存在，你将被提示通过点击坚不可摧 Linux 网络的链接去操作 ULN。

### 如何注册 Oracle Linux 6/7 系统使用 ULN

只需要运行下列的命令，并按随后的指令提示去注册系统。

```
# uln_register

```

确保你的系统有一个激活的因特网连接。同时准备好你的 Oracle 单点登录帐户（SSO）的用户名和密码，然后点击 `Next`。

```
Copyright ▪© 2006--2010 Red Hat, Inc. All rights reserved.

▪▪▪▪▪▪▪▪▪▪▪▪▪▪▪▪▪▪▪▪▪▪▪▪▪▪▪▪▪▪▪▪▪▪▪▪▪▪▪▪▪▪▪▪▪▪▪▪▪▪▪▪▪¤ Setting up software updates ▪▪▪▪▪▪▪▪▪▪▪▪▪▪▪▪▪▪▪▪▪▪▪▪▪▪▪▪▪▪▪▪▪▪▪▪▪▪▪▪▪▪▪▪▪▪▪▪▪▪▪▪▪
▪ This assistant will guide you through connecting your system to Unbreakable Linux Network (ULN) to receive software updates,         ▪
▪ including security updates, to keep your system supported and compliant.  You will need the following at this time:                  ▪
▪                                                                                ▪
▪  * A network connection                                                                                ▪
▪  * Your Oracle Single Sign-On Login & password                                                                                ▪
▪                                                                                ▪
▪                                                                                ▪
▪                                                                                ▪
▪                                                                                ▪
▪                                                                                ▪
▪                                                                                ▪
▪                                                                                ▪
▪                                                                                ▪
▪              ▪▪▪▪▪▪▪▪▪▪▪▪▪▪▪▪▪▪▪▪▪▪▪▪▪▪▪▪▪▪▪▪▪▪▪▪                           ▪▪▪▪▪▪▪▪                          ▪▪▪▪▪▪▪▪▪▪             ▪
▪              ▪ Why Should I Connect to ULN? ... ▪                           ▪ Next ▪                          ▪ Cancel ▪             ▪
▪              ▪▪▪▪▪▪▪▪▪▪▪▪▪▪▪▪▪▪▪▪▪▪▪▪▪▪▪▪▪▪▪▪▪▪▪▪                           ▪▪▪▪▪▪▪▪                          ▪▪▪▪▪▪▪▪▪▪             ▪
▪                                                                    ▪
▪▪▪▪▪▪▪▪▪▪▪▪▪▪▪▪▪▪▪▪▪▪▪▪▪▪▪▪▪▪▪▪▪▪▪▪▪▪▪▪▪▪▪▪▪▪▪▪▪▪▪▪▪▪▪▪▪▪▪▪▪▪▪▪▪▪▪▪▪▪▪▪▪▪▪▪▪▪▪▪▪▪▪▪▪▪▪▪▪▪▪▪▪▪▪▪▪▪▪▪▪▪▪▪▪▪▪▪▪▪▪▪▪▪▪▪▪▪▪▪▪▪▪▪▪▪▪▪▪▪▪▪▪▪▪▪

```

输入你的 ULN 登录信息，然后点击 `Next`。

```
Copyright ▪© 2006--2010 Red Hat, Inc. All rights reserved.

▪▪▪▪▪▪▪▪▪▪▪▪▪▪▪▪▪▪▪▪▪▪▪▪▪▪▪▪▪▪▪▪▪▪▪▪▪▪▪▪▪▪▪▪▪▪▪▪▪▪▪▪▪¤ Setting up software updates ▪▪▪▪▪▪▪▪▪▪▪▪▪▪▪▪▪▪▪▪▪▪▪▪▪▪▪▪▪▪▪▪▪▪▪▪▪▪▪▪▪▪▪▪▪▪▪▪▪▪▪▪▪
▪                                                                                ▪
▪ Please enter your login information for Unbreakable Linux Network (http://linux.oracle.com/):                                        ▪
▪                                                                                ▪
▪                                                                                ▪
▪                                          Oracle Single Sign-On Login: thamuthu@gmail.com_                                            ▪
▪                                                             Password: **********__________                                           ▪
▪                                                                  CSI: 12345678____________                                           ▪
▪ Tip: Forgot your login or password? Visit: http://www.oracle.com/corporate/contact/getaccthelp.html                                  ▪
▪                                                                                ▪
▪                  ▪▪▪▪▪▪▪▪                                    ▪▪▪▪▪▪▪▪                                   ▪▪▪▪▪▪▪▪▪▪                   ▪
▪                  ▪ Next ▪                                    ▪ Back ▪                                   ▪ Cancel ▪                   ▪
▪                  ▪▪▪▪▪▪▪▪                                    ▪▪▪▪▪▪▪▪                                   ▪▪▪▪▪▪▪▪▪▪                   ▪
▪                                                                                ▪
▪                                                                                ▪
▪▪▪▪▪▪▪▪▪▪▪▪▪▪▪▪▪▪▪▪▪▪▪▪▪▪▪▪▪▪▪▪▪▪▪▪▪▪▪▪▪▪▪▪▪▪▪▪▪▪▪▪▪▪▪▪▪▪▪▪▪▪▪▪▪▪▪▪▪▪▪▪▪▪▪▪▪▪▪▪▪▪▪▪▪▪▪▪▪▪▪▪▪▪▪▪▪▪▪▪▪▪▪▪▪▪▪▪▪▪▪▪▪▪▪▪▪▪▪▪▪▪▪▪▪▪▪▪▪▪▪▪▪▪▪▪

```

注册一个系统概要 – 硬件信息，然后点击 `Next`。

```
Copyright ▪© 2006--2010 Red Hat, Inc. All rights reserved.

▪▪▪▪▪▪▪▪▪▪▪▪▪▪▪▪▪▪▪▪▪▪▪▪▪▪▪▪▪▪▪▪▪▪▪▪▪▪▪▪▪▪▪▪▪▪¤ Register a System Profile - Hardware ▪▪▪▪▪▪▪▪▪▪▪▪▪▪▪▪▪▪▪▪▪▪▪▪▪▪▪▪▪▪▪▪▪▪▪▪▪▪▪▪▪▪▪▪▪▪
▪                                                                                ▪
▪ A Profile Name is a descriptive name that you choose to identify this                                                           ▪
▪ System Profile on the Unbreakable Linux Network web pages. Optionally,                                                          ▪
▪ include a computer serial or identification number.                                                                             ▪
▪ Profile name: 2g-oracle-sys___________________________                                                                          ▪
▪                                                                                ▪
▪ [*] Include the following information about hardware and network:                                                               ▪
▪ Press  to deselect the option.                                                                                ▪
▪                                                                                ▪
▪                           Version: 6  CPU model: Intel(R) Xeon(R) CPU E5-5650 0 @ 2.00GHz                                       ▪
▪                           Hostname: 2g-oracle-sys                                                                               ▪
▪                           CPU speed: 1199 MHz  IP Address: 192.168.1.101  Memory:                                               ▪
▪                                                                                ▪
▪ Additional hardware information including PCI devices, disk sizes and mount points will be included in the profile.             ▪
▪                                                                                ▪
▪                 ▪▪▪▪▪▪▪▪                                  ▪▪▪▪▪▪▪▪                                  ▪▪▪▪▪▪▪▪▪▪                  ▪
▪                 ▪ Next ▪                                  ▪ Back ▪                                  ▪ Cancel ▪                  ▪
▪                 ▪▪▪▪▪▪▪▪                                  ▪▪▪▪▪▪▪▪                                  ▪▪▪▪▪▪▪▪▪▪                  ▪
▪                                                                                ▪
▪                                                                                ▪
▪▪▪▪▪▪▪▪▪▪▪▪▪▪▪▪▪▪▪▪▪▪▪▪▪▪▪▪▪▪▪▪▪▪▪▪▪▪▪▪▪▪▪▪▪▪▪▪▪▪▪▪▪▪▪▪▪▪▪▪▪▪▪▪▪▪▪▪▪▪▪▪▪▪▪▪▪▪▪▪▪▪▪▪▪▪▪▪▪▪▪▪▪▪▪▪▪▪▪▪▪▪▪▪▪▪▪▪▪▪▪▪▪▪▪▪▪▪▪▪▪▪▪▪▪▪▪▪▪▪▪

```

注册一个系统概要 – 包配置，然后点击 `Next`。

```
Copyright ▪© 2006--2010 Red Hat, Inc. All rights reserved.

▪▪▪▪▪▪▪▪▪▪▪▪▪▪▪▪▪▪▪▪▪▪▪▪▪▪▪▪▪▪▪▪▪▪▪▪▪▪▪▪▪▪▪▪▪▪¤ Register a System Profile - Packages ▪▪▪▪▪▪▪▪▪▪▪▪▪▪▪▪▪▪▪▪▪▪▪▪▪▪▪▪▪▪▪▪▪▪▪▪▪▪▪▪▪▪▪▪▪▪▪
▪                                                                                ▪
▪ RPM information is important to determine what updated software packages are relevant to this system.                           ▪
▪                                                                                ▪
▪ [*] Include RPM packages installed on this system in my System Profile                                                          ▪
▪                                                                                ▪
▪ You may deselect individual packages by unchecking them below.                                                                  ▪
▪                                    [*] ConsoleKit-0.4.1-6.el6                             ▪                                     ▪
▪                                    [*] ConsoleKit-libs-0.4.1-6.el6                        ▪                                     ▪
▪                                    [*] ConsoleKit-x11-0.4.1-6.el6                         ▪                                     ▪
▪                                    [*] DeviceKit-power-014-3.el6                          ▪                                     ▪
▪                                    [*] GConf2-2.28.0-7.el6                                ▪                                     ▪
▪                                    [*] GConf2-2.28.0-7.el6                                ▪                                     ▪
▪                                    [*] GConf2-devel-2.28.0-7.el6                          ▪                                     ▪
▪                                    [*] GConf2-gtk-2.28.0-7.el6                            ▪                                     ▪
▪                                    [*] MAKEDEV-3.24-6.el6                                 ▪                                     ▪
▪                                    [*] MySQL-python-1.2.3-0.3.c1.1.el6                    ▪                                     ▪
▪                                    [*] NessusAgent-7.0.3-es6                              ▪                                     ▪
▪                                    [*] ORBit2-2.14.17-6.el6_8                             ▪                                     ▪
▪                                    [*] ORBit2-2.14.17-6.el6_8                             ▪                                     ▪
▪                                    [*] ORBit2-devel-2.14.17-6.el6_8                       ▪                                     ▪
▪                                    [*] PackageKit-0.5.8-26.0.1.el6                        ▪                                     ▪
▪                                    [*] PackageKit-device-rebind-0.5.8-26.0.1.el6          ▪                                     ▪
▪                                    [*] PackageKit-glib-0.5.8-26.0.1.el6                   ▪                                     ▪
▪                                                                                ▪
▪                 ▪▪▪▪▪▪▪▪                                  ▪▪▪▪▪▪▪▪                                  ▪▪▪▪▪▪▪▪▪▪                  ▪
▪                 ▪ Next ▪                                  ▪ Back ▪                                  ▪ Cancel ▪                  ▪
▪                 ▪▪▪▪▪▪▪▪                                  ▪▪▪▪▪▪▪▪                                  ▪▪▪▪▪▪▪▪▪▪                  ▪
▪                                                                                ▪
▪                                                                                ▪
▪▪▪▪▪▪▪▪▪▪▪▪▪▪▪▪▪▪▪▪▪▪▪▪▪▪▪▪▪▪▪▪▪▪▪▪▪▪▪▪▪▪▪▪▪▪▪▪▪▪▪▪▪▪▪▪▪▪▪▪▪▪▪▪▪▪▪▪▪▪▪▪▪▪▪▪▪▪▪▪▪▪▪▪▪▪▪▪▪▪▪▪▪▪▪▪▪▪▪▪▪▪▪▪▪▪▪▪▪▪▪▪▪▪▪▪▪▪▪▪▪▪▪▪▪▪▪▪▪▪▪

```

按下 “Next” 去发送系统概要到 ULN。

```
Copyright ▪© 2006--2010 Red Hat, Inc. All rights reserved.

▪▪▪▪▪▪▪▪▪▪▪▪▪▪▪▪▪▪▪▪▪▪▪▪▪▪▪▪▪▪▪▪▪▪▪▪¤ Send Profile Information to Unbreakable Linux Network ▪▪▪▪▪▪▪▪▪▪▪▪▪▪▪▪▪▪▪▪▪▪▪▪▪▪▪▪▪▪▪▪▪▪▪▪▪
▪                                                                                ▪
▪ We are finished collecting information for the System Profile.                                                               ▪
▪                                                                                ▪
▪ Press "Next" to send this System Profile to Unbreakable Linux Network.  Click "Cancel" and no information will be sent.  You ▪
▪ can run the registration program later by typing `uln_register` at the command line.                                         ▪
▪                                                                                ▪
▪                 ▪▪▪▪▪▪▪▪                                 ▪▪▪▪▪▪▪▪                                ▪▪▪▪▪▪▪▪▪▪                  ▪
▪                 ▪ Next ▪                                 ▪ Back ▪                                ▪ Cancel ▪                  ▪
▪                 ▪▪▪▪▪▪▪▪                                 ▪▪▪▪▪▪▪▪                                ▪▪▪▪▪▪▪▪▪▪                  ▪
▪                                                                                ▪
▪                                                                                ▪
▪▪▪▪▪▪▪▪▪▪▪▪▪▪▪▪▪▪▪▪▪▪▪▪▪▪▪▪▪▪▪▪▪▪▪▪▪▪▪▪▪▪▪▪▪▪▪▪▪▪▪▪▪▪▪▪▪▪▪▪▪▪▪▪▪▪▪▪▪▪▪▪▪▪▪▪▪▪▪▪▪▪▪▪▪▪▪▪▪▪▪▪▪▪▪▪▪▪▪▪▪▪▪▪▪▪▪▪▪▪▪▪▪▪▪▪▪▪▪▪▪▪▪▪▪▪▪▪

```

发送概要到 ULN 是如下的一个过程。

```
Copyright ▪© 2006--2010 Red Hat, Inc. All rights reserved.

▪▪¤ Sending Profile to Unbreakable Linux Network ▪
▪                                                ▪
▪                       75%                      ▪
▪                                                ▪
▪▪▪▪▪▪▪▪▪▪▪▪▪▪▪▪▪▪▪▪▪▪▪▪▪▪▪▪▪▪▪▪▪▪▪▪▪▪▪▪▪▪▪▪▪▪▪▪▪▪

```

ULN 注册做完后，重新回顾系统订阅的详细情况。如果一切正确，然后点击 `ok`。

```
Copyright ▪© 2006--2010 Red Hat, Inc. All rights reserved.

▪▪▪▪▪▪▪▪▪▪▪▪▪▪▪▪▪▪▪▪▪▪▪▪▪▪▪▪▪▪▪▪▪▪▪▪▪▪▪▪▪▪▪▪▪▪▪▪▪▪▪¤ Review system subscription details ▪▪▪▪▪▪▪▪▪▪▪▪▪▪▪▪▪▪▪▪▪▪▪▪▪▪▪▪▪▪▪▪▪▪▪▪▪▪▪▪▪▪▪▪▪▪▪▪▪▪
▪                                                                                ▪
▪                                                                                ▪
▪ Note: yum-rhn-plugin has been enabled.                                                                                ▪
▪                                                                                ▪
▪ Please review the subscription details below:                                                                                ▪
▪                                                                                ▪
▪ Software channel subscriptions:                                                                                ▪
▪ This system will receive updates from the following Unbreakable Linux Network software channels:                                       ▪
▪ Oracle Linux 6 Latest (x86_64)                                                                                ▪
▪ Unbreakable Enterprise Kernel Release 4 for Oracle Linux 6 (x86_64)                                                                    ▪
▪                                                                                ▪
▪ Warning: If an installed product on this system is not listed above, you will not receive updates or support for that product. If      ▪
▪ you would like to receive updates for that product, please visit http://linux.oracle.com/ and subscribe this system to the             ▪
▪ appropriate software channels to get updates for that product.                                                                         ▪
▪                                                                                ▪
▪                                                                                ▪
▪                                                                                ▪
▪                                                                                ▪
▪                                                                                ▪
▪                                                                                ▪
▪                                                                                ▪
▪                                                                ▪▪▪▪▪▪                                                                  ▪
▪                                                                ▪ OK ▪                                                                  ▪
▪                                                                ▪▪▪▪▪▪                                                                  ▪
▪                                                                                ▪
▪                                                                                ▪
▪▪▪▪▪▪▪▪▪▪▪▪▪▪▪▪▪▪▪▪▪▪▪▪▪▪▪▪▪▪▪▪▪▪▪▪▪▪▪▪▪▪▪▪▪▪▪▪▪▪▪▪▪▪▪▪▪▪▪▪▪▪▪▪▪▪▪▪▪▪▪▪▪▪▪▪▪▪▪▪▪▪▪▪▪▪▪▪▪▪▪▪▪▪▪▪▪▪▪▪▪▪▪▪▪▪▪▪▪▪▪▪▪▪▪▪▪▪▪▪▪▪▪▪▪▪▪▪▪▪▪▪▪▪▪▪▪▪

```

最后点击 `Finish` 完成注册。

```
Copyright ▪© 2006--2010 Red Hat, Inc. All rights reserved.

▪▪▪▪▪▪▪▪▪▪▪▪▪▪▪▪▪▪▪▪▪▪▪▪▪▪▪▪▪▪▪▪▪▪▪▪▪▪▪▪▪▪▪▪▪▪▪¤ Finish setting up software updates ▪▪▪▪▪▪▪▪▪▪▪▪▪▪▪▪▪▪▪▪▪▪▪▪▪▪▪▪▪▪▪▪▪▪▪▪▪▪▪▪▪▪▪▪▪▪▪
▪                                                                                ▪
▪ You may now run 'yum update' from this system's command line to get the latest software updates from Unbreakable Linux Network. ▪
▪ You will need to run this periodically to get the latest updates.                                                               ▪
▪                                                                                ▪
▪                                                           ▪▪▪▪▪▪▪▪▪▪                                                            ▪
▪                                                           ▪ Finish ▪                                                            ▪
▪                                                           ▪▪▪▪▪▪▪▪▪▪                                                            ▪
▪                                                                                ▪
▪                                                                                ▪
▪▪▪▪▪▪▪▪▪▪▪▪▪▪▪▪▪▪▪▪▪▪▪▪▪▪▪▪▪▪▪▪▪▪▪▪▪▪▪▪▪▪▪▪▪▪▪▪▪▪▪▪▪▪▪▪▪▪▪▪▪▪▪▪▪▪▪▪▪▪▪▪▪▪▪▪▪▪▪▪▪▪▪▪▪▪▪▪▪▪▪▪▪▪▪▪▪▪▪▪▪▪▪▪▪▪▪▪▪▪▪▪▪▪▪▪▪▪▪▪▪▪▪▪▪▪▪▪▪▪▪

```

ULN 注册已经成功，为了从 ULN 中得到仓库，运行如下的命令。

```
# yum repolist
Loaded plugins: aliases, changelog, presto, refresh-packagekit, rhnplugin, security, tmprepo, ulninfo, verify, versionlock
This system is receiving updates from ULN.
ol6_x86_64_UEKR3_latest                                                                                | 1.2 kB     00:00
ol6_x86_64_UEKR3_latest/primary                                                                                |  35 MB     00:14
ol6_x86_64_UEKR3_latest                                                                                874/874
repo id                                  repo name                                                                                status
ol6_x86_64_UEKR3_latest                  Unbreakable Enterprise Kernel Release 3 for Oracle Linux 6 (x86_64) - Latest                      874
ol6_x86_64_latest                        Oracle Linux 6 Latest (x86_64)                                                                 40,092
repolist: 40,966

```

另外，你也可以在 ULN 网站上查看到相同的信息。转到 `System` 标签页去查看已注册的系统列表。

![](/data/attachment/album/201806/18/230348w7msk4sqz587ezbz.png)

去查看已经启用的仓库列表。转到 `System` 标签页，然后点击相应的系统。另外，你也能够看到系统勘误及可用更新。

![](/data/attachment/album/201806/18/230349nlcllo0d044emot0.png)

去管理订阅的频道。转到 `System` 标签页，然后点击有关的 `system name`，最后点击 `Manage Subscriptions`。

![](/data/attachment/album/201806/18/230349twcf49slqw1zss0c.png)

---

via: <https://www.2daygeek.com/how-to-register-the-oracle-linux-system-with-the-unbreakable-linux-network-uln/>

作者：[Vinoth Kumar](https://www.2daygeek.com/author/vinoth/) 选题：[lujun9972](https://github.com/lujun9972) 译者：[qhwdw](https://github.com/qhwdw) 校对：[wxy](https://github.com/wxy)

本文由 [LCTT](https://github.com/LCTT/TranslateProject) 原创编译，[Linux中国](https://linux.cn/) 荣誉推出


献给写作者的 Linux 工具

Summary: 这些易用的开源应用可以帮助你打磨你的写作技巧、使研究更高效、更具有条理。

这些易用的开源应用可以帮助你打磨你的写作技巧、使研究更高效、更具有条理。

>
> 这些易用的开源应用可以帮助你打磨你的写作技巧、使研究更高效、更具有条理。
>
>
>

![](/data/attachment/album/201806/20/110135nkivw9v7rac9kr9f.jpg)

如果你已经阅读过[我关于如何切换到 Linux 的文章](https://opensource.com/article/18/2/my-linux-story-Antergos)，那么你就知道我是一个超级用户。另外，我不是任何方面的“专家”，目前仍然如此。但是在过去几年里我学到了很多有用的东西，我想将这些技巧传给其他新的 Linux 用户。

今天，我将讨论我写作时使用的工具，基于三个标准来选择：

1. 当我提交作品或文章时，我的主要写作工具必须与任何发布者兼容。
2. 该软件使用起来必须简单快捷。
3. 免费（自由）是很棒的。

有一些很棒的一体化免费解决方案，比如：

1. [bibisco](http://www.bibisco.com/)
2. [Manuskript](http://www.theologeek.ch/manuskript/)
3. [oStorybook](http://ostorybook.tuxfamily.org/index.php?lng=en)

但是，当我试图寻找信息时，我往往会迷失方向并失去思路，所以我选择了适合我需求的多个应用程序。另外，我不想依赖互联网，以免服务下线。我把这些程序放在显示器桌面上，以便我一下全看到它们。

请考虑以下工具建议 ： 每个人的工作方式都不相同，并且你可能会发现一些更适合你工作方式的其他应用程序。以下这些工具是目前的写作工具：

### 文字处理器

[LibreOffice 6.0.1](https://www.libreoffice.org/)。直到最近，我使用了 [WPS](http://wps-community.org/)，但由于字体渲染问题（Times New Roman 总是以粗体显示）而否定了它。LibreOffice 的最新版本非常适应 Microsoft Office，而且事实上它是开源的，这对我来说很重要。

### 词库

[Artha](https://sourceforge.net/projects/artha/) 可以给出同义词、反义词、派生词等等。它外观整洁、速度快。例如，输入 “fast” 这个词，你会得到字典定义以及上面列出的其他选项。Artha 是送给开源社区的一个巨大的礼物，人们应该试试它，因为它似乎是一个冷僻的小程序。如果你使用 Linux，请立即安装此应用程序，你不会后悔的。

### 记笔记

[Zim](http://zim-wiki.org/) 标榜自己是一个桌面维基，但它也是你所能找到的最简单的多层级笔记应用程序。还有其它更漂亮的笔记程序，但 Zim 正是那种我需要管理角色、地点、情节和次要情节的程序。

### 投稿跟踪

我曾经使用过一款名为 [FileMaker Pro](http://www.filemaker.com/) 的专有软件，它惯坏了我。有很多数据库应用程序，但在我看来，最容易使用的某过于 [Glom](https://www.glom.org/) 了。它以图形方式满足我的需求，让我以表单形式输入信息而不是表格。在 Glom 中，你可以创建你需要的表单，这样你就可以立即看到相关信息（对于我来说，通过电子表格来查找信息就像将我的眼球拖到玻璃碎片上）。尽管 Glom 不再处于开发阶段，但它仍然是很棒的。

### 搜索

我已经开始使用 [StartPage.com](https://www.startpage.com/) 作为我的默认搜索引擎。当然，当你写作时，[Google](https://www.google.com/) 可以成为你最好的朋友之一。但我不喜欢每次我想了解特定人物、地点或事物时，Google 都会跟踪我。所以我使用 StartPage.com 来代替。它速度很快，并且不会跟踪你的搜索。我也使用 [DuckDuckGo.com](https://duckduckgo.com/) 作为 Google 的替代品。

### 其他的工具

[Chromium 浏览器](https://www.chromium.org/) 是 [Google Chrome](https://www.google.com/chrome/) 的开源版本，带有隐私插件。

尽管来自 [Mozilla](https://www.mozilla.org/en-US/) 的 [Thunderbird](https://www.mozilla.org/en-US/thunderbird/) 是一个很棒的程序，但我发现 [Geary](https://wiki.gnome.org/Apps/Geary) 是一个更快更轻的电子邮件应用程序。有关开源电子邮件应用程序的更多信息，请阅读 [Jason Baker](https://opensource.com/users/jason-baker) 的优秀文章：[6 个开源的桌面电子邮件客户端](https://opensource.com/business/18/1/desktop-email-clients)。

正如你可能已经注意到，我对应用程序的喜爱趋向于将最好的 Windows、MacOS 都能运行，以及此处提到的开源 Linux 替代品融合在一起。我希望这些建议能帮助你发现有用的新方法来撰写并跟踪你的写作（谢谢你，Artha！）。

写作愉快！

---

via: <https://opensource.com/article/18/3/top-Linux-tools-for-writers>

作者：[Adam Worth](https://opensource.com/users/adamworth) 译者：[MjSeven](https://github.com/MjSeven) 校对：[wxy](https://github.com/wxy)

本文由 [LCTT](https://github.com/LCTT/TranslateProject) 原创编译，[Linux中国](https://linux.cn/) 荣誉推出


如何在 Linux 中禁用内置摄像头

Summary: 在不使用时用胶带覆盖它或者拔掉相机或者在 BIOS 中禁用它是一个不错的主意。

在不使用时用胶带覆盖它或者拔掉相机或者在 BIOS 中禁用它是一个不错的主意。

![](/data/attachment/album/201806/20/110711r1ltfvf81ulq9fud.jpg)

今天，我们将看到如何禁用未使用的内置网络摄像头或外置摄像头，以及如何在 Linux 中需要时启用它。禁用网络摄像头可以在很多方面为你提供帮助。你可以防止恶意软件控制你的集成摄像头，并监视你和你的家庭。我们已经阅读过无数的故事，一些黑客可以在你不知情的情况下使用你的摄像头监视你。通过黑客攻击你的网络摄像头，用户可以在线共享你的私人照片和视频。可能有很多原因。如果你想知道如何禁用笔记本电脑或台式机中的网络摄像头，那么你很幸运。这个简短的教程将告诉你如何做。请继续阅读。

我在 Arch Linux 和 Ubuntu 上测试了这个指南。它的工作原理如下所述。我希望这也可以用在其他 Linux 发行版上。

### 在 Linux 中禁用内置摄像头

首先，使用如下命令找到网络摄像头驱动：

```
$ sudo lsmod | grep uvcvideo

```

示例输出：

```
uvcvideo 114688 1
videobuf2_vmalloc 16384 1 uvcvideo
videobuf2_v4l2 28672 1 uvcvideo
videobuf2_common 53248 2 uvcvideo,videobuf2_v4l2
videodev 208896 4 uvcvideo,videobuf2_common,videobuf2_v4l2
media 45056 2 uvcvideo,videodev
usbcore 286720 9 uvcvideo,usbhid,usb_storage,ehci_hcd,ath3k,btusb,uas,ums_realtek,ehci_pci

```

这里，**uvcvideo** 是我的网络摄像头驱动。

现在，让我们禁用网络摄像头。

为此，请编辑以下文件（如果文件不存在，只需创建它）：

```
$ sudo nano /etc/modprobe.d/blacklist.conf

```

添加以下行：

```
##Disable webcam.
blacklist uvcvideo

```

`##Disable webcam` 这行不是必需的。为了便于理解，我添加了它。

保存并退出文件。重启系统以使更改生效。

要验证网络摄像头是否真的被禁用，请打开任何即时通讯程序或网络摄像头软件，如 Cheese 或 Guvcview。你会看到如下的空白屏幕。

Cheese 输出：

![](/data/attachment/album/201806/20/110713mwojcttab9s0watf.jpg)

Guvcview 输出：

![](/data/attachment/album/201806/20/110720ftvtg6mxx61revg3.jpg)

看见了么？网络摄像头被禁用而无法使用。

要启用它，请编辑：

```
$ sudo nano /etc/modprobe.d/blacklist.conf

```

注释掉你之前添加的行。

```
##Disable webcam.
#blacklist uvcvideo

```

保存并关闭文件。然后，重启计算机以启用网络摄像头。

这样够了吗？不。为什么？如果有人可以远程访问你的系统，他们可以轻松启用网络摄像头。所以，在不使用时用胶带覆盖它或者拔掉相机或者在 BIOS 中禁用它是一个不错的主意。此方法不仅用于禁用内置摄像头，还用于外部网络摄像头。

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

干杯!

---

via: <https://www.ostechnix.com/how-to-disable-built-in-webcam-in-ubuntu/>

作者：[SK](https://www.ostechnix.com/author/sk/) 选题：[lujun9972](https://github.com/lujun9972) 译者：[geekpi](https://github.com/geekpi) 校对：[wxy](https://github.com/wxy)

本文由 [LCTT](https://github.com/LCTT/TranslateProject) 原创编译，[Linux中国](https://linux.cn/) 荣誉推出


为什么 Linux 比 Windows 和 macOS 更安全？

首先linux没有windows好用！<br />
linux安全？无非是用的人都是比较懂行的安全意识比较高，linux注度不如windows，攻击的人没有windows的多。windows小白多安全意识差不关端口不设密码乱下软件导致出现大量漏洞和病毒。

我觉得还有一个原因，linux用在服务端，运维部署好就不用管了保持环境运行几年十几年，windows作为个人电脑，用户今天装这个，明天玩那个，系统环境瞬息万变，这种比较本身就不靠谱，相反我觉的windows 还是挺好的，因为他在平衡安全易用和灵活。如果让一个普通用户用linux不用病毒，他自己就能把系统整崩溃了。

>
> 多年前做出的操作系统选型终将影响到如今的企业安全。在三大主流操作系统当中，有一个能被称作最安全的。
>
>
>

![](/data/attachment/album/201806/21/010400o08mzi8hfspuwo0i.jpg)

企业投入了大量时间、精力和金钱来保障系统的安全性。最强的安全意识可能就是有一个安全运行中心（SOC），肯定用上了防火墙以及反病毒软件，或许还花费了大量时间去监控他们的网络，以寻找可能表明违规的异常信号，用那些 IDS、SIEM 和 NGFW 之类的东西，他们部署了一个名副其实的防御阵列。

然而又有多少人想过数字化操作的基础之一——部署在员工的个人电脑上的操作系统呢？当选择桌面操作系统时，安全性是一个考虑的因素吗？

这就产生了一个 IT 人士都应该能回答的问题：一般部署哪种操作系统最安全呢？

我们问了一些专家他们对于以下三种选择的看法：Windows，最复杂的平台也是最受欢迎的桌面操作系统；macOS X，基于 FreeBSD 的 Unix 操作系统，驱动着苹果的 Macintosh 系统运行；还有 Linux，这里我们指的是所有的 Linux 发行版以及与基于 Unix 的操作系统相关的系统。

### 怎么会这样

企业可能没有评估他们部署给工作人员的操作系统的安全性的一个原因是，他们多年前就已经做出了选择。退一步讲，所有操作系统都还算安全，因为侵入它们、窃取数据或安装恶意软件的牟利方式还处于起步阶段。而且一旦选择了操作系统，就很难再改变。很少有 IT 组织想要面对将全球分散的员工队伍转移到全新的操作系统上的痛苦。唉，他们已经受够了把用户搬到一个现有的操作系统的新版本时的负面反响。

还有，重新考虑操作系统是高明的吗？这三款领先的桌面操作系统在安全方面的差异是否足以值得我们去做出改变呢？

当然商业系统面临的威胁近几年已经改变了。攻击变得成熟多了。曾经支配了公众想象力的单枪匹马的青少年黑客已经被组织良好的犯罪分子网络以及具有庞大计算资源的政府资助组织的网络所取代。

像你们许多人一样，我有过很多那时的亲身经历：我曾经在许多 Windows 电脑上被恶意软件和病毒感染，我甚至被宏病毒感染了 Mac 上的文件。最近，一个广泛传播的自动黑客攻击绕开了网站的保护程序并用恶意软件感染了它。这种恶意软件的影响一开始是隐形的，甚至有些东西你没注意，直到恶意软件最终深深地植入系统以至于它的性能开始变差。一件有关病毒蔓延的震惊之事是不法之徒从来没有特定针对过我；当今世界，用僵尸网络攻击 100,000 台电脑容易得就像一次攻击几台电脑一样。

### 操作系统真的很重要吗?

给你的用户部署的哪个操作系统确实对你的安全态势产生了影响，但那并不是一个可靠的安全措施。首先，现在的攻击很可能会发生，因为攻击者探测的是你的用户，而不是你的系统。一项对参加了 DEFCON 会议的黑客的[调查](https://www.esecurityplanet.com/hackers/fully-84-percent-of-hackers-leverage-social-engineering-in-attacks.html)表明，“84％ 的人使用社交工程作为攻击策略的一部分。”部署安全的操作系统只是一个重要的起点，但如果没有用户培训、强大的防火墙和持续的警惕性，即使是最安全的网络也会受到入侵。当然，用户下载的软件、扩展程序、实用程序、插件和其他看起来还好的软件总是有风险的，成为了恶意软件出现在系统上的一种途径。

无论你选择哪种平台，保持你系统安全最好的方法之一就是确保立即应用了软件更新。一旦补丁正式发布，黑客就可以对其进行反向工程并找到一种新的漏洞，以便在下一波攻击中使用。

而且别忘了最基本的操作。别用 root 权限，别授权访客连接到网络中的老服务器上。教您的用户如何挑选一个真正的好密码并且使用例如 [1Password](http://www.1password.com) 这样的工具，以便在每个他们使用的帐户和网站上拥有不同的密码

因为底线是您对系统做出的每一个决定都会影响您的安全性，即使您的用户工作使用的操作系统也是如此。

### Windows，流行之选

若你是一个安全管理人员，很可能文章中提出的问题就会变成这样：是否我们远离微软的 Windows 会更安全呢？说 Windows 主导企业市场都是低估事实了。[NetMarketShare](https://www.netmarketshare.com/operating-system-market-share.aspx?options=%7B%22filter%22%3A%7B%22%24and%22%3A%5B%7B%22deviceType%22%3A%7B%22%24in%22%3A%5B%22Desktop%2Flaptop%22%5D%7D%7D%5D%7D%2C%22dateLabel%22%3A%22Trend%22%2C%22attributes%22%3A%22share%22%2C%22group%22%3A%22platform%22%2C%22sort%22%3A%7B%22share%22%3A-1%7D%2C%22id%22%3A%22platformsDesktop%22%2C%22dateInterval%22%3A%22Monthly%22%2C%22dateStart%22%3A%222017-02%22%2C%22dateEnd%22%3A%222018-01%22%2C%22segments%22%3A%22-1000%22%7D) 估计互联网上 88% 的电脑令人震惊地运行着 Windows 的某个版本。

如果你的系统在这 88％ 之中，你可能知道微软会继续加强 Windows 系统的安全性。这些改进被不断重写，或者重新改写了其代码库，增加了它的反病毒软件系统，改进了防火墙以及实现了沙箱架构，这样在沙箱里的程序就不能访问系统的内存空间或者其他应用程序。

但可能 Windows 的流行本身就是个问题，操作系统的安全性可能很大程度上依赖于装机用户量的规模。对于恶意软件作者来说，Windows 提供了大的施展平台。专注其中可以让他们的努力发挥最大作用。

就像 Troy Wilkinson，Axiom Cyber Solutions 的 CEO 解释的那样，“Windows 总是因为很多原因而导致安全性保障来的最晚，主要是因为消费者的采用率。由于市场上大量基于 Windows 的个人电脑，黑客历来最有针对性地将这些系统作为目标。”

可以肯定地说，从梅丽莎病毒到 WannaCry 或者更强的，许多世界上已知的恶意软件早已对准了 Windows 系统.

### macOS X 以及通过隐匿实现的安全

如果最流行的操作系统总是成为大目标，那么用一个不流行的操作系统能确保安全吗？这个主意是老法新用——而且是完全不可信的概念——“通过隐匿实现的安全”，这秉承了“让软件内部运作保持专有，从而不为人知是抵御攻击的最好方法”的理念。

Wilkinson 坦言，macOS X “比 Windows 更安全”，但他马上补充说，“macOS 曾被认为是一个安全漏洞很小的完全安全的操作系统，但近年来，我们看到黑客制造了攻击苹果系统的额外漏洞。”

换句话说，攻击者会扩大活动范围而不会无视 Mac 领域。

Comparitech 的安全研究员 Lee Muson 说，在选择更安全的操作系统时，“macOS 很可能是被选中的目标”，但他提醒说，这一想法并不令人费解。它的优势在于“它仍然受益于通过隐匿实现的安全感和微软提供的操作系统是个更大的攻击目标。”

Wolf Solutions 公司的 Joe Moore 给予了苹果更多的信任，称“现成的 macOS X 在安全方面有着良好的记录，部分原因是它不像 Windows 那么广泛，而且部分原因是苹果公司在安全问题上干的不错。”

### 最终胜者是 ……

你可能一开始就知道它：专家们的明确共识是 Linux 是最安全的操作系统。然而，尽管它是服务器的首选操作系统，而将其部署在桌面上的企业很少。

如果你确定 Linux 是要选择的系统，你仍然需要决定选择哪种 Linux 系统，并且事情会变得更加复杂。 用户需要一个看起来很熟悉的用户界面，而你需要最安全的操作系统。

像 Moore 解释的那样，“Linux 有可能是最安全的，但要求用户是资深用户。”所以，它不是针对所有人的。

将安全性作为主要功能的 Linux 发行版包括 [Parrot Linux](https://www.parrotsec.org/)，这是一个基于 Debian 的发行版，Moore 说，它提供了许多与安全相关开箱即用的工具。

当然，一个重要的区别是 Linux 是开源的。Simplex Solutions 的 CISO Igor Bidenko 说，编码人员可以阅读和审查彼此工作的现实看起来像是一场安全噩梦，但这确实是让 Linux 如此安全的重要原因。 “Linux 是最安全的操作系统，因为它的源代码是开放的。任何人都可以查看它，并确保没有错误或后门。”

Wilkinson 阐述说：“Linux 和基于 Unix 的操作系统具有较少的在信息安全领域已知的、可利用的安全缺陷。技术社区对 Linux 代码进行了审查，该代码有助于提高安全性：通过进行这么多的监督，易受攻击之处、漏洞和威胁就会减少。”

这是一个微妙而违反直觉的解释，但是通过让数十人（有时甚至数百人）通读操作系统中的每一行代码，代码实际上更加健壮，并且发布漏洞错误的机会减少了。这与 《PC World》 为何出来说 Linux 更安全有很大关系。正如 Katherine Noyes [解释](https://www.pcworld.com/article/202452/why_linux_is_more_secure_than_windows.html)的那样，“微软可能吹捧它的大型的付费开发者团队，但团队不太可能与基于全球的 Linux 用户开发者进行比较。 安全只能通过所有额外的关注获益。”

另一个被 《PC World》举例的原因是 Linux 更好的用户特权模式：Noye 的文章讲到，Windows 用户“一般被默认授予管理员权限，那意味着他们几乎可以访问系统中的一切”。Linux，反而很好地限制了“root”权限。

Noyes 还指出，Linux 环境下的多样性可能比典型的 Windows 单一文化更好地对抗攻击：Linux 有很多不同的发行版。其中一些以其特别的安全关注点而差异化。Comparitech 的安全研究员 Lee Muson 为 Linux 发行版提供了这样的建议：“[Qubes OS](https://www.qubes-os.org/) 对于 Linux 来说是一个很好的出发点，现在你可以发现，[爱德华·斯诺登的认可](https://twitter.com/snowden/status/781493632293605376?lang=en)极大地超过了其谦逊的宣传。”其他安全性专家指出了专门的安全 Linux 发行版，如 [Tails Linux](https://tails.boum.org/about/index.en.html)，它旨在直接从 USB 闪存驱动器或类似的外部设备安全地匿名运行。

### 构建安全趋势

惯性是一股强大的力量。虽然人们有明确的共识，认为 Linux 是桌面系统的最安全选择，但并没有出现对 Windows 和 Mac 机器压倒性的倾向。尽管如此，Linux 采用率的小幅增长却可能会产生对所有人都更加安全的计算，因为市场份额的丧失是确定能获得微软和苹果公司关注的一个方式。换句话说，如果有足够的用户在桌面上切换到 Linux，Windows 和 Mac PC 很可能成为更安全的平台。

---

via: <https://www.computerworld.com/article/3252823/linux/why-linux-is-better-than-windows-or-macos-for-security.html>

作者：[Dave Taylor](https://www.computerworld.com/author/Dave-Taylor/) 译者：[fuzheng1998](https://github.com/fuzheng1998) 校对：[wxy](https://github.com/wxy)

本文由 [LCTT](https://github.com/LCTT/TranslateProject) 原创编译，[Linux中国](https://linux.cn/) 荣誉推出