标签 验证 下的文章

我们继续 无密码验证 的文章。上一篇文章中,我们用 Go 写了一个 HTTP 服务,用这个服务来做无密码验证 API。今天,我们为它再写一个 JavaScript 客户端。

我们将使用 这里的 这个单页面应用程序(SPA)来展示使用的技术。如果你还没有读过它,请先读它。

记住流程:

  • 用户输入其 email。
  • 用户收到一个带有魔法链接的邮件。
  • 用户点击该链接、
  • 用户验证成功。

对于根 URL(/),我们将根据验证的状态分别使用两个不同的页面:一个是带有访问表单的页面,或者是已验证通过的用户的欢迎页面。另一个页面是验证回调的重定向页面。

伺服

我们将使用相同的 Go 服务器来为客户端提供服务,因此,在我们前面的 main.go 中添加一些路由:

router.Handle("GET", "/...", http.FileServer(SPAFileSystem{http.Dir("static")}))
type SPAFileSystem struct {
    fs http.FileSystem
}

func (spa SPAFileSystem) Open(name string) (http.File, error) {
    f, err := spa.fs.Open(name)
    if err != nil {
        return spa.fs.Open("index.html")
    }
    return f, nil
}

这个伺服文件放在 static 下,配合 static/index.html 作为回调。

你可以使用你自己的服务器,但是你得在服务器上启用 CORS

HTML

我们来看一下那个 static/index.html 文件。

<!DOCTYPE html>
<html lang="en">
<head>
 <meta charset="utf-8">
 <meta name="viewport" content="width=device-width, initial-scale=1.0">
 <title>Passwordless Demo</title>
 <link rel="shortcut icon" href="data:,">
 <script src="/js/main.js" type="module"></script>
</head>
<body></body>
</html>

单页面应用程序的所有渲染由 JavaScript 来完成,因此,我们使用了一个空的 body 部分和一个 main.js 文件。

我们将使用 上篇文章 中的 Router。

渲染

现在,我们使用下面的内容来创建一个 static/js/main.js 文件:

import Router from 'https://unpkg.com/@nicolasparada/router'
import { isAuthenticated } from './auth.js'

const router = new Router()

router.handle('/', guard(view('home')))
router.handle('/callback', view('callback'))
router.handle(/^\//, view('not-found'))

router.install(async resultPromise => {
    document.body.innerHTML = ''
    document.body.appendChild(await resultPromise)
})

function view(name) {
    return (...args) => import(`/js/pages/${name}-page.js`)
        .then(m => m.default(...args))
}

function guard(fn1, fn2 = view('welcome')) {
    return (...args) => isAuthenticated()
        ? fn1(...args)
        : fn2(...args)
}

与上篇文章不同的是,我们实现了一个 isAuthenticated() 函数和一个 guard() 函数,使用它去渲染两种验证状态的页面。因此,当用户访问 / 时,它将根据用户是否通过了验证来展示主页或者是欢迎页面。

验证

现在,我们来编写 isAuthenticated() 函数。使用下面的内容来创建一个 static/js/auth.js 文件:

export function getAuthUser() {
    const authUserItem = localStorage.getItem('auth_user')
    const expiresAtItem = localStorage.getItem('expires_at')

    if (authUserItem !== null && expiresAtItem !== null) {
        const expiresAt = new Date(expiresAtItem)

        if (!isNaN(expiresAt.valueOf()) && expiresAt > new Date()) {
            try {
                return JSON.parse(authUserItem)
            } catch (_) { }
        }
    }

    return null
}

export function isAuthenticated() {
    return localStorage.getItem('jwt') !== null && getAuthUser() !== null
}

当有人登入时,我们将保存 JSON 格式的 web 令牌、它的过期日期,以及在 localStorage 上的当前已验证用户。这个模块就是这个用处。

  • getAuthUser() 用于从 localStorage 获取已认证的用户,以确认 JSON 格式的 Web 令牌没有过期。
  • isAuthenticated() 在前面的函数中用于去检查它是否没有返回 null

获取

在继续这个页面之前,我将写一些与服务器 API 一起使用的 HTTP 工具。

我们使用以下的内容去创建一个 static/js/http.js 文件:

import { isAuthenticated } from './auth.js'

function get(url, headers) {
    return fetch(url, {
        headers: Object.assign(getAuthHeader(), headers),
    }).then(handleResponse)
}

function post(url, body, headers) {
    return fetch(url, {
        method: 'POST',
        headers: Object.assign(getAuthHeader(), { 'content-type': 'application/json' }, headers),
        body: JSON.stringify(body),
    }).then(handleResponse)
}

function getAuthHeader() {
    return isAuthenticated()
        ? { authorization: `Bearer ${localStorage.getItem('jwt')}` }
        : {}
}

export async function handleResponse(res) {
    const body = await res.clone().json().catch(() => res.text())
    const response = {
        statusCode: res.status,
        statusText: res.statusText,
        headers: res.headers,
        body,
    }
    if (!res.ok) {
        const message = typeof body === 'object' && body !== null && 'message' in body
            ? body.message
            : typeof body === 'string' && body !== ''
                ? body
                : res.statusText
        const err = new Error(message)
        throw Object.assign(err, response)
    }
    return response
}

export default {
    get,
    post,
}

这个模块导出了 get()post() 函数。它们是 fetch API 的封装。当用户是已验证的,这二个函数注入一个 Authorization: Bearer <token_here> 头到请求中;这样服务器就能对我们进行身份验证。

欢迎页

我们现在来到欢迎页面。用如下的内容创建一个 static/js/pages/welcome-page.js 文件:

const template = document.createElement('template')
template.innerHTML = `
    <h1>Passwordless Demo</h1>
    <h2>Access</h2>
    <form id="access-form">
        <input type="email" placeholder="Email" autofocus required>
        <button type="submit">Send Magic Link</button>
    </form>
`

export default function welcomePage() {
    const page = template.content.cloneNode(true)

    page.getElementById('access-form')
        .addEventListener('submit', onAccessFormSubmit)

    return page
}

这个页面使用一个 HTMLTemplateElement 作为视图。这只是一个输入用户 email 的简单表单。

为了避免干扰,我将跳过错误处理部分,只是将它们输出到控制台上。

现在,我们来写 onAccessFormSubmit() 函数。

import http from '../http.js'

function onAccessFormSubmit(ev) {
    ev.preventDefault()

    const form = ev.currentTarget
    const input = form.querySelector('input')
    const email = input.value

    sendMagicLink(email).catch(err => {
        console.error(err)
        if (err.statusCode === 404 && wantToCreateAccount()) {
            runCreateUserProgram(email)
        }
    })
}

function sendMagicLink(email) {
    return http.post('/api/passwordless/start', {
        email,
        redirectUri: location.origin + '/callback',
    }).then(() => {
        alert('Magic link sent. Go check your email inbox.')
    })
}

function wantToCreateAccount() {
    return prompt('No user found. Do you want to create an account?')
}

它对 /api/passwordless/start 发起了 POST 请求,请求体中包含 emailredirectUri。在本例中它返回 404 Not Found 状态码时,我们将创建一个用户。

function runCreateUserProgram(email) {
    const username = prompt("Enter username")
    if (username === null) return

    http.post('/api/users', { email, username })
        .then(res => res.body)
        .then(user => sendMagicLink(user.email))
        .catch(console.error)
}

这个用户创建程序,首先询问用户名,然后使用 email 和用户名做一个 POST 请求到 /api/users。成功之后,给创建的用户发送一个魔法链接。

回调页

这是访问表单的全部功能,现在我们来做回调页面。使用如下的内容来创建一个 static/js/pages/callback-page.js 文件:

import http from '../http.js'

const template = document.createElement('template')
template.innerHTML = `
    <h1>Authenticating you</h1>
`

export default function callbackPage() {
    const page = template.content.cloneNode(true)

    const hash = location.hash.substr(1)
    const fragment = new URLSearchParams(hash)
    for (const [k, v] of fragment.entries()) {
        fragment.set(decodeURIComponent(k), decodeURIComponent(v))
    }
    const jwt = fragment.get('jwt')
    const expiresAt = fragment.get('expires_at')

    http.get('/api/auth_user', { authorization: `Bearer ${jwt}` })
        .then(res => res.body)
        .then(authUser => {
            localStorage.setItem('jwt', jwt)
            localStorage.setItem('auth_user', JSON.stringify(authUser))
            localStorage.setItem('expires_at', expiresAt)

            location.replace('/')
        })
        .catch(console.error)

    return page
}

请记住……当点击魔法链接时,我们会来到 /api/passwordless/verify_redirect,它将把我们重定向到重定向 URI,我们将放在哈希中的 JWT 和过期日期传递给 /callback

回调页面解码 URL 中的哈希,提取这些参数去做一个 GET 请求到 /api/auth_user,用 JWT 保存所有数据到 localStorage 中。最后,重定向到主页面。

主页

创建如下内容的 static/pages/home-page.js 文件:

import { getAuthUser } from '../auth.js'

export default function homePage() {
    const authUser = getAuthUser()

    const template = document.createElement('template')
    template.innerHTML = `
        <h1>Passwordless Demo</h1>
        <p>Welcome back, ${authUser.username} 

运维密码是什么?

这是我们 Linux 中国旗下的 LCTT 技术组开发的第一款小程序,基于微信平台提供 OTP 口令管理功能。

想必绝大部分系统管理员都知道 OTP ,OTP 即 一次性口令 One-Time Password ,最常见的一次性口令是 基于时间的一次性口令 Time-based One-time Password (TOTP),即每隔一段时间(如 60 秒)就生成一个一次性的六位数字的密码。这种一次性口令可以用于各种登录验证系统,比如 SSH、网站登录等等,只要验证系统和验证者持有相同的 OTP 种子,并采用一致的算法即可。

最常见的方式是采用 Google 身份验证器 Google Authenticator 来提供基于时间的一次性口令(TOTP),也有采用 RSA 等公司提供的硬件 OTP 令牌进行一次性口令管理的。而我们这次推出的“运维密码”,即是一款采用和 Google 身份验证器 Google Authenticator 相同的算法的微信小程序。

为什么要开发这样的一款小程序呢,请听我道来~

缘起

为什么要做这样一款小程序?

大概是因为 Google 身份验证器 Google Authenticator 无法满足我的需要,没有办法备份场景。这让我很是担心,假如我丢失了手机,我可能再也无法登录到我的服务器上去了。

而且本身这个工具不是一个高频的应用,所以我们在考虑,能不能有这样的一种可能,让我们可以很方便地使用,同时也不会像App 一样过于侵扰我们的生活。

微信小程序出现后,其随用随走的理念深得我们的喜爱。其强大的线上线下融合能力,也非常适合我们的需求。所以在微信小程序出现后,我们就一直在关注,看我们的想法能不能在微信小程序上得以实现。

为什么选择微信小程序

微信小程序在某些方面的理念和我们对于这款工具的理念是一致的。

  • 随用随走:我们希望我们的产品不会给用户产生过多的困扰,而是很方便就可以使用它。作为一款安全工具,我们不希望它对用户的生活产生太多的麻烦。
  • 线上线下融合:微信小程序的形式让我们的用户只需要打开微信,扫一扫线下的二维码,就可以快捷、方便的拿到自己需要的动态密码。

“运维密码”的优势

备份功能

由于我们对于云服务的安全和隐私的担忧,所以我们将备份的功能做在了本地。当然,后续我们也会根据用户的不同偏好而推出基于云端的备份。

资源占用小

“运维密码”小程序不超过 200K,你只需要花费很少的流量,就可以实现和 Google 身份验证器 Google Authenticator 的全部功能。

产品无感知迭代

我们会对“运维密码”不断的更新,当然,你可以无需担心更新带来的数据问题。我们的小程序会自动帮你做好升级的事情。

开始使用

你可以直接在微信小程序中的搜索框内搜索:

微信小程序搜索框

或者,在微信公众号“运维伙伴”的详情页中也有“运维密码”的入口:

运维伙伴

或者,扫描下方二维码,添加运维密码小程序:

我还制作了一个简单的视频:

如何加入自己的场景?

打开“运维密码”,点击右下角的“扫描二维码”的按钮:

扫码

扫描服务商给你的“种子二维码”(这里包含了场景相关的信息),会自动识别,并且跳转到添加信息的界面:

确认信息

确认信息无误后,点击添加。添加成功后,即可跳转到到运维密码的详情页:

添加成功

视频操作如下:

如何获取密码?

在“运维密码”的首页,你可以看到你所添加的场景的列表,从中可以直接看到当前的一次性密码。也可以用下述的场景二维码来获取。

生成场景二维码

点击某个场景,可以查看该场景的详情,在此可以生成该场景的场景二维码(不同于之前的服务商所提供的“种子二维码”)。将此二维码打印出来,贴于所应用的场景附近,如服务器或终端旁边,这样你可以在该服务器或终端旁边直接用微信扫描即可马上显示该场景当前的一次性密码。

备份与恢复

作为最重要的差异性功能,“运维密码”提供了场景的备份和恢复功能,从此再也不怕丢失了手机后无法登入。

备份

首先进入“设置”,点击“本地场景备份”,会显示你的所有场景:

确认本地备份

确认后,会显示一个二维码图片,这个图片就是你的场景备份信息,千万保存好,也不要随意给别人。

视你所保存的场景多寡,这个二维码图片也尺寸和信息密度也有所不同。生成该备份信息会稍微有一些慢。

场景备份

对此备份,可以截屏保存到本地,妥善保管即可。下一步我们还会推出加密保存,需要使用密码才能解开。

恢复

如果因为某种原因,删除了场景或丢失了场景,你可以通过之前备份信息进行恢复,只需要选择你的备份二维码即可。重复的场景并不会覆盖。

恢复场景

下一步,我们也会推出基于公有云或私有云的云端备份功能。

更多功能

更多使用细节,您可以亲自试试。我们也有一大波新的功能增强正在赶来~

入群体验

对此小程序感兴趣的同学,欢迎加入专属体验群,提出您的建议和反馈:

扫描上述二维码并添加好友后,验证信息:“运维密码”,即可获得加群邀请。

寄语

我们希望运维密码能够给你带来更加舒服的体验,如果你觉得哪里有不足的,也希望你能够告诉我们,让我们一起把它做的更好。

后继我们将对此小程序开源,也欢迎大家提供反馈、补丁和功能请求。

美国 国家标准和技术协会 National Institute of Standards and Technology (NIST)发布了《 数字身份验证指南 Digital Authentication Guideline (DAG)》的最新草案,其中暗示将来会禁用基于短信的 双因子认证方案 Two-Factor Authentication (2FA)。

数字身份验证指南 Digital Authentication Guideline (DAG)》是一系列用于软件商构建安全服务的规则,也被政府和私人机构用于评估其服务和软件的安全性。

NIST 的专家们一直不断地更新该指南,以便应对 IT 领域的快速变化。

基于短信的双因子认证仍然可以用,但是不会太久了

最新的《数字身份验证指南》草案,NIST 正式地不建议公司继续使用基于短信的认证,甚至说将来考虑在该指南中将基于短信的双因子认证视作不安全的。NIST 在该草案中说:

“如果使用基于公共移动电话网络的短信作为带外验证,验证者必须验证其预注册的手机号码是基于移动网络的,而非 VoIP(或者其它基于软件的),然后才能发送短信到预注册手机号码。修改预注册手机号码时如果没有双因子验证是不能进行的。不推荐使用短信进行带外验证,本指南的将来版本中将不再允许这种方式。”

NIST 的指南当中认为基于短信的双因子认证是不安全的,因为用户不会总是带着电话。

在该指南中,推荐软件应用应该使用令牌和软件加密验证器,这可能是手机应用或硬件设备的形式,但是就像手机一样,也可能被偷走或“临时借走”。但该指南认为这种风险是可接受的,而不像令牌或软件加密验证器那样,短信在 VoIP 服务之下是一个影响到了联合信任因子的缺陷。

短信是不安全的,特别是在 VoIP 连接下

因为一些 VoIP 服务允许劫持短信,所以 NIST 建议厂商在基于短信的双因子系统在发送短信验证码之前,对使用 VoIP 连接的访问进行特别检查。

短信是一个广泛使用的不安全协议,仅在上周,Context Information Security 的安全研究人员就披露了又一起依赖于短信协议而危及到了其用户和设备的攻击。随着对这种攻击类型的越来越多的研究,软件厂商、公司以及用户会逐渐认识到应该换到更安全的验证方式上。

当前的 NIST 指南仍在讨论之中,但是基本上可以确定,该指南的将来版本不会再将使用基于短信的认证方式推荐为带外验证的安全方式。

生物识别技术是一个新兴发展方向

在该指南草案中,也提到了生物特征识别技术在特定条件下是可以作为一个验证方式的:

“因此,对生物识别技术的验证是支持的,只需要遵循如下准则:生物识别技术应该与其他(你知道的或你拥有的)认证因子配合使用。”

有人说,安全不是一个产品,而是一个过程(LCTT 注:安全公司 McAfee 认为,安全风险管理是一个方法论,而不是安全产品的堆叠)。虽然 SSH 协议被设计成使用加密技术来确保安全,但如果使用不当,别人还是能够破坏你的系统:比如弱密码、密钥泄露、使用过时的 SSH 客户端等,都能引发安全问题。

在考虑 SSH 认证方案时,大家普遍认为公钥认证比密码认证更安全。然而,公钥认证技术并不是为公共环境设置的,如果你在一台公用电脑上使用公钥认证登录 SSH 服务器,你的服务器已经毫无安全可言了,公用的电脑可能会记录你的公钥,或从你的内存中读取公钥。如果你不信任本地电脑,那你最好还是使用其他方式登录服务器。现在就是“一次性密码(OTP)”派上用场的时候了,就像名字所示,一次性密码只能被使用一次。这种一次性密码非常合适在不安全的环境下发挥作用,就算它被窃取,也无法再次使用。

有个生成一次性密码的方法是通过谷歌认证器,但在本文中,我要介绍的是另一种 SSH 登录方案:OTPW,它是个一次性密码登录的软件包。不像谷歌认证,OTPW 不需要依赖任何第三方库。

OTPW 是什么

OTPW 由一次性密码生成器和 PAM 认证规则组成。在 OTPW 中一次性密码由生成器事先生成,然后由用户以某种安全的方式获得(比如打印到纸上)。另一方面,这些密码会通过 Hash 加密保存在 SSH 服务器端。当用户使用一次性密码登录系统时,OTPW 的 PAM 模块认证这些密码,并且保证它们不能再次使用。

步骤1:OTPW 的安装和配置

在 Debian, Ubuntu 或 Linux Mint 发行版上

使用 apt-get 安装:

$ sudo apt-get install libpam-otpw otpw-bin

打开针对 SSH 服务的 PAM 配置文件(/etc/pam.d/sshd),注释掉下面这行(目的是禁用 PAM 的密码认证功能):

#@include common-auth

添加下面两行(用于打开一次性密码认证功能):

auth       required     pam_otpw.so
session    optional     pam_otpw.so

在 Fedora 或 CentOS/RHEL 发行版上

在基于 RedHat 的发行版中没有编译好的 OTPW,所以我们需要使用源代码来安装它。

首先,安装编译环境:

$ sudo yum git gcc pam-devel
$ git clone https://www.cl.cam.ac.uk/~mgk25/git/otpw
$ cd otpw

打开 Makefile 文件,编辑以“PAMLIB=”开头的那行配置:

64 位系统:

PAMLIB=/usr/lib64/security

32 位系统:

PAMLIB=/usr/lib/security

编译安装。需要注意的是安装过程会自动重启 SSH 服务一下,所以如果你是使用 SSH 连接到服务器,做好被断开连接的准备吧(LCTT 译注:也许不会被断开连接,即便被断开连接,请使用原来的方式重新连接即可,现在还没有换成一次性口令方式。)。

$ make
$ sudo make install

现在你需要更新 SELinux 策略,因为 /usr/sbin/sshd 会往你的 home 目录写数据,而 SELinux 默认是不允许这么做的。如果没有使用 SELinux 服务(LCTT 注:使用 getenforce 命令查看结果,如果是 enforcing,就是打开了 SELinux 服务),请跳过这一步。

$ sudo grep sshd /var/log/audit/audit.log | audit2allow -M mypol
$ sudo semodule -i mypol.pp

接下来打开 PAM 配置文件(/etc/pam.d/sshd),注释下面这行(为了禁用密码认证):

#auth       substack     password-auth

添加下面两行(用于打开一次性密码认证功能):

auth       required     pam_otpw.so
session    optional     pam_otpw.so

步骤2:配置 SSH 服务器,使用一次性密码

打开 /etc/ssh/sshd\_config 文件,设置下面三个参数。你要确保下面的参数不会重复存在,否则 SSH 服务器可能会出现异常。

UsePrivilegeSeparation yes
ChallengeResponseAuthentication yes
UsePAM yes

你还需要禁用默认的密码认证功能。另外可以选择开启公钥认证功能,那样的话你就可以在没有一次性密码的时候使用公钥进行认证。

PubkeyAuthentication yes
PasswordAuthentication no

重启 SSH 服务器。

Debian, Ubuntu 或 Linux Mint 发行版:

$ sudo service ssh restart

Fedora 或 CentOS/RHEL 7 发行版:

$ sudo systemctl restart sshd

(LCTT 译注:虽然这里重启了 sshd 服务,但是你当前的 ssh 连接应该不受影响,只是在你完成下述步骤之前,无法按照原有方式建立新的连接了。因此,保险起见,要么多开一个 ssh 连接,避免误退出当前连接;要么将重启 sshd 服务器步骤放到步骤3完成之后。)

步骤3:使用 OTPW 产生一次性密码

之前提到过,你需要事先创建一次性密码,并保存起来。使用 otpw-gen 命令创建密码:

$ cd ~
$ otpw-gen > temporary_password.txt

这个命令会让你输入密码前缀,当你以后登录的时候,你需要同时输入这个前缀以及一次性密码。密码前缀是另外一层保护,就算你的一次性密码表被泄漏,别人也无法通过暴力破解你的 SSH 密码。

设置好密码前缀后,这个命令会产生 280 个一次性密码(LCTT 译注:保存到 ~/.otpw 下),并将它们导出到一个文本文件中(如 temporary\_password.txt)。每个密码(默认是 8 个字符)由一个 3 位十进制数索引。你需要将这个密码表打印出来,并随身携带。

查看 ./.otpw 文件,它存放了一次性密码的 HASH 值。头 3 位十进制数与你随身携带的密码表的索引一一对应,在你登录 SSH 服务器的时候会被用到。

$ more ~/.otpw

OTPW1
280 3 12 8
191ai+:ENwmMqwn
218tYRZc%PIY27a
241ve8ns%NsHFmf
055W4/YCauQJkr:
102ZnJ4VWLFrk5N
2273Xww55hteJ8Y
1509d4b5=A64jBT
168FWBXY%ztm9j%
000rWUSdBYr%8UE
037NvyryzcI+YRX
122rEwA3GXvOk=z

测试一次性密码登录 SSH 服务器

使用普通的方式登录 SSH 服务器:

$ ssh user@remote_host

如果 OTPW 成功运行,你会看到一点与平时登录不同的地方:

Password 191:

现在打开你的密码表,找到索引号为 191 的密码。

023 kBvp tq/G  079 jKEw /HRM  135 oW/c /UeB  191 fOO+ PeiD  247 vAnZ EgUt

从上表可知,191 号密码是“fOO+PeiD”。你需要加上密码前缀,比如你设置的前缀是“000”,则你实际需要输入的密码是“000fOO+PeiD”。

成功登录后,你这次输入的密码自动失效。查看 ~/.otpw 文件,你会发现第一行变成“---------------”,这表示 191 号密码失效了。

OTPW1
280 3 12 8
---------------
218tYRZc%PIY27a
241ve8ns%NsHFmf
055W4/YCauQJkr:
102ZnJ4VWLFrk5N
2273Xww55hteJ8Y
1509d4b5=A64jBT
168FWBXY%ztm9j%
000rWUSdBYr%8UE
037NvyryzcI+YRX
122rEwA3GXvOk=z

总结

在这个教程中,我介绍了如何使用 OTPW 工具来设置一次性登录密码。你也许意识到了在这种双因子的认证方式中,打印一张密码表让人感觉好 low,但是这种方式是最简单的,并且不用依赖任何第三方软件。无论你用哪种方式创建一次性密码,在你需要在一个不可信任的环境登录 SSH 服务器的时候,它们都很有用。你可以就这个主题来分享你的经验和观点。


via: http://xmodulo.com/secure-ssh-login-one-time-passwords-linux.html

作者:Dan Nanni 译者:bazz2 校对:wxy

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

你有没有感到需要一款Linux下的命令行工具,它可以验证你的拼写?一款可以显示包含特定前缀字符串的行?好的,本篇中,我们会讨论Linux下的 look命令,它满足了上面的这些要求。

Linux下的look命令

下面是来自look命令man页面描述的截图:

测试环境

  • OS – Ubuntu 13.04
  • Shell – Bash 4.2.45
  • Application – look 2.20.1-5.1ubuntu8

简要教程

现在让我们通过一些特定例子来讨论这个命令。

假设你要验证单词'rendezvous'的拼写。你可以轻松地用look命令做到。

这是个例子 :

$ look rendez
rendezvous
rendezvous's
rendezvoused
rendezvouses
rendezvousing

如你所见,我只传入了单词的前面几个字符作为命令行参数,命令产生了所有的相关单词。这些单词通过look命令从文件 /usr/share/dict/words取回。

另外一种情况是需要打印所有包含特定字符串的行。比如,如果我想要显示c文件中所有的头文件,那么我就用下面的方法:

$ look "#include" efence_test.c 
#include 
#include 

如你所见,它生成了所有的在文件efence\_test.c中以"#include"开始的行,因此我知道了原文件中包含的头文件。

默认上,所有look命令的匹配都是大小写敏感的。你可以使用非大小写敏感匹配的选项-f

下面是一些look命令提供了其他一些命令行选项:

想要获取这个命令的更多信息,阅读这个 man 页.

下载/安装/配置

下面是关于look命令的一些链接:

  • 主页 [如果你知道这个工具的主页的话让我知道一下]
  • 下载链接

look命令成了util-linux包的一部分,它在大多数Linux发行版中已经预安装了。

优点

  • 非常容易地在命令行验证拼写。
  • 在大多数Linux发行版中已经预安装。

缺点

  • 依赖于/usr/share/dict/words 来做拼写验证。

总结

一款出色的用来验证拼写的命令行工具。节省了大量时间如果你想要显示以特定字符串为开头的行的话。试一下,你一定会喜欢上它的。


via: http://mylinuxbook.com/look-verify-spellings-and-display-lines/

译者:geekpi 校对:wxy

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