天天看点

Vim实用技巧:global命令

:global

命令结合了Ex命令与Vim的模式匹配这两方面能力。凭借该命令,可以在某个指定模式的所有匹配行上运行Ex命令。就处理重复工作的效率而言,

global

 命令是除点范式以及宏之外,最为强大的Vim工具之一。

技巧98 认识global命令

:global

 命令允许在某个指定模式的所有匹配行上运行Ex命令。首先研究一下它的语法。

:global

命令通常采用以下形式(参见 

:h :g

 )。

:[range] global[!] /{pattern}/ [cmd]
           

首先,在缺省情况下,

:global

命令的作用范围是整个文件(%),这一点与其他大多数Ex命令(包括

:delete

:substitute

 以及 

:normal

)有所不同,这些命令的缺省范围仅为当前行(.)。

其次,

{pattern}

 域与查找历史相互关联。这意味着如果将该域留空的话,Vim会自动使用当前的查找模式。

另外,

[cmd]

可以是除 

:global

命令之外的任何Ex命令。在实际应用中,如表5-1中所列的那些Ex命令,无一不在处理文本过程中起到了极大的作用。顺便提一下,如果不指定任何 

[cmd]

,Vim将缺省使用 

:print

还有,可以用 

:global!

 或者 

:vglobal

(v表示invert)反转

:global

命令的行为。这两条命令将指示Vim在没有匹配到指定模式的行上执行 

[cmd]

。在下一节中,将会分别看到 

:global

 与 

:vglobal

的应用实例。

最后需要指出的是 

:global

命令在指定 

[range]

 内的文本行上执行时通常分为两轮。第一轮,Vim在所有

[pattern]

的匹配行上做上标记。第二轮,再在所有已标记的文本行上执行 

[cmd]

。另外,由于 

[cmd]

 的范围可单独设定,因此可在多行文本段内进行操作,将在技巧101中讲解这项强大的技术。

技巧99 删除所有包含模式的文本行

将 

:global

 命令与 

:delete

命令一起组合使用,可以快速裁剪文件内容。对于那些匹配 

{pattern}

 的文本行,既可以选择保留,也可以将其丢弃。

以下内容取自Vimcasts.org归档网页中有关前几部主题的链接。

global/episodes.html

<ol>
  <li>
    <a href="/episodes/show-invisibles/" target="_blank" rel="external nofollow" >
      Show invisibles
    </a>
  </li>
  <li>
    <a href="/episodes/tabs-and-spaces/" target="_blank" rel="external nofollow" >
      Tabs and Spaces
    </a>
  </li>
  <li>
    <a href="/episodes/whitespace-preferences-and-filetypes/" target="_blank" rel="external nofollow" >
      Whitespace preferences and filetypes
    </a>
  </li>
</ol>
           

显而易见,所有列表项均由两部分数据构成:主题的标题及其URL。接下来,将利用 

:global

命令分别取出这两组数据。

用 ':g/re/d' 删除所有的匹配行

如果只想保留 

<a>

 标签内的标题,而把其他行删掉,该怎么做呢?在本例中,由于每组链接的内容各占一行,而其他文本行只包含或开或闭这两种类型的标签,因此,如果设计一个可以匹配HTML标签的模式,再用它调用 

:global

命令,就可以删掉所有该模式的匹配行了。

以下命令可以做到这一点。

➾ /\v\<\/?\w+>
➾ :g//d
           

如果在Vimcasts.org的归档文件中运行这两条命令,文件的内容将会变为:

Show invisibles
Tabs and Spaces
Whitespace preferences and filetypes
           

与 

:substitute

命令类似,也可以将

:global

命令的查找域留空。这样一来,Vim将会重用最后一次的查找模式(参见技巧91)。这意味着在构造正则表达式的过程中,可以先进行粗粒度匹配,然后再对其进行精细调整,正如技巧85展示的那样。

本例的正则表达式采用的是 

very magic

模式(在技巧74中有所涉及)。首先,它会匹配左尖括号(

\<

);然后,匹配可选的正斜杠(

\/?

);接下来,再匹配一个或多个单词型字符(

\w+

);最后匹配表示单词结尾的分隔符(

>

)。尽管这个正则表达式并不能匹配所有的标签,但对于这个特定的例子来说,已经够用了。

Grep一词的来历

请仔细琢磨一下 

:global

命令的简写形式:
➾ :g/re/p
           

re

表示regular expression,而 

p

是 

:print

的缩写,它作为缺省的 

[cmd]

使用。如果我们把符号 / 忽略掉,便会发现单词“grep”已然呼之欲出了。

用 ':v/re/d' 只保留匹配行

这一次,我们将进行相反的操作。正如我们前面提到的,

:vglobal

或简写的 

:v

命令恰好与 

:g

命令的操作相反。也就是说,它用于在指定模式的非匹配行上执行Ex命令。

在本例中,包含URL的文本行很容易识别,它们都含有 

href

属性。因此,运行以下命令,可以得到这些文本行。

➾ :v/href/d
           

以上命令可以解读为“删除所有不包含 

href

的文本行”。最终的结果如下。

<a href="/episodes/show-invisibles/">
<a href="/episodes/tabs-and-spaces/">
<a href="/episodes/whitespace-preferences-and-filetypes/">
           

仅仅凭借一条命令,整篇文档就被精炼为我们感兴趣的文本段了。

技巧100 将TODO项收集至寄存器

通过把 

:global

和 

:yank

 这两条命令结合在一起,可以把所有匹配 

{pattern}

的文本行收集到某个寄存器中。

下列代码包含了几行以“TODO”开头的注释行。

global/markdown.js

Markdown.dialects.Gruber = {
  lists: function() {
      // TODO: Cache this regexp for certain depths.
      function regex_for_depth(depth) { /* implementation */ }
  },
  "`": function inlineCode( text ) {
      var m = text.match( /(`+)(([\s\S]*?)\1)/ );
      if ( m && m[2] )
          return [ m[1].length + m[2].length ];
      else {
          // TODO: No matching end code found - warn!
          return [ 1, "`" ];
      }
  }
}
           

假设想把所有TODO项收集到一起。只需输入以下命令,这些信息就会变得一览无余。

➾ :g/TODO
《 // TODO: Cache this regexp for certain depths.
        // TODO: No matching end code found - warn! 
           

请牢记,

:print

是 

:global

命令的缺省 

[cmd]

,它只是简单地回显所有匹配单词“TODO”的文本行。这并没什么用处,因为一旦执行了其他命令,这些信息将会消失。

这里介绍另外一种做法。先将所有包含单词“TODO”的文本行复制到某个寄存器,再把寄存器的内容粘贴到其他文件中,以备不时之需。

这一次,将用到寄存器a。首先运行 

qaq

,将其清空。对这个命令进行分解。

qa

会让vim开始录制宏,并把它存到寄存器a中,最后的 

q

则负责终止录制。由于在录制宏的过程中,我们没有敲击任何按键,因此寄存器最终被清空了。可以用下面的命令印证一下。

➾ :reg a
《--- Registers ---
  "a
           

现在,可以把包含TODO注释的行复制到此寄存器中了。

➾ :g/TODO/yank A
➾ :reg a
《"a // TODO: Cache this regexp for certain depths.
       // TODO: No matching end code found - warn! 
           

此处有一个窍门,即要用大写字母A引用寄存器。这意味着Vim将把内容附加到指定的寄存器,用小写字母 

a

的话,则会覆盖原有寄存器的内容。因此,这条global命令可以被解读为“将所有匹配模式 /TODO/ 的文本行依次附加到寄存器 

a

。”

这一次,当再次运行 

:reg a

时,会发现寄存器 

a

已经存有两组源自文档的TODO项了。(为了方便阅读,已将这些内容调整为两行,但在Vim中,换行符实际会显示为 ^J。)此后,只需在任意分割窗口中打开一个新缓冲区,再运行 

"ap

命令,就可以将寄存器 

a

的内容粘贴进去了。

结论

本例只收集了两个TODO项,即使手动操作也可以很快地完成。但是,以上介绍的技术具有很好的扩展性。如果某篇文档包含十几个TODO项,采用该技巧将使我们事半功倍。

甚至可以将 

:global

命令与 

:bufdo

或 

:argdo

一起搭配使用,从一组文件中收集所有的TODO项。这个任务就留给你作为练习吧,可以参考技巧36中类似的工作流程。

还有另外一种方案:

➾ :g/TODO/t$
           

这里用到的 

:t

命令已经在技巧29中介绍。该命令是将所有TODO项复制到当前文件的末尾,而不是把它们附加到寄存器。一旦运行完该命令,就可以在文件的末尾看到这些TODO项了。由于此法不会影响寄存器的内容,因此相对简单直接,但它在与 

:argdo

以及

:bufdo

命令一起使用时不太干净利落。

技巧101 将CSS文件中所有规则的属性按照字母排序

当Ex命令与 

:global

一起组合使用时,也可以为

[cmd]

单独指定范围。Vim允许以 

:g/{pattern}

为参考点,动态地设定范围。接下来,看看如何利用这一点,将CSS文件中每一条规则的所有属性均按照字母顺序排列。

用以下 CSS 文件作为演示。

global/unsorted.css

Line 1 html {
       -   margin: 0;
       -   padding: 0;
       -   border: 0;
       5   font-size: 100%;
       -   font: inherit;
       -   vertical-align: baseline;
       - }
       - body {
      10   line-height: 1.5;
       -   color: black;
       -   background: white;
       - }
           

假设想把每一组规则内的属性都按照字母顺序排序。借助Vim的内置命令 

:sort

(参见

:h :sort

 ),就可以实现这一功能。

对单条规则的属性进行排序

先用 

:sort

命令在该文件的子集上练练手(参见表15-1)。

首先,使用文本对象 

vi{

,可以轻易地选中一段由 {} 所围的文本块。然后,运行 

:'<,'>sort

,便可以将这些文本行按照字母顺序重新排列了。如果每次仅对一条规则进行排序,此法完全可以胜任,但假设我们遇到的是一个包含数百条规则的样式表呢?如果能把这一过程自动化岂不更好么?

表15-1 对文件的子集进行排序

按键操作 缓冲区内容
{start}

html {

 margin: 0;

 padding: 0;

 border: 0;

 font-size: 100%;

 font: inherit;

 vertical-align: baseline;

}

vi{

html {

 margin: 0;

 padding: 0;

 border: 0;

 font-size: 100%;

  font: inherit;

 vertical-align: baseline; 

}

:'<,'>sort

html {

 border: 0;

 font-size: 100%;

 font: inherit;

 margin: 0;

 padding: 0;

 vertical-align: baseline;

}

对所有规则的属性进行排序

其实,可以用一条 

:global

 命令对文件中所有规则的属性进行排序。假设在本例的样式表中运行以下命令。

➾ :g/{/ .+1,/}/-1 sort
           

最终会得到以下结果。

html {
  border: 0;
  font-size: 100%;
  font: inherit;
  margin: 0;
  padding: 0;
  vertical-align: baseline;
}
body {
  background: white;
  color: black;
  line-height: 1.5;
}
           

这条排序命令会在每条规则的{} 块内执行。尽管本例中的样式表仅仅包含十几行文本,但对于内容更多的CSS文件,此法也同样适用。

这条命令很复杂,但掌握其机理后,将会由衷地赞叹 

:global

命令的强大。

:global

命令的标准格式如下。

:g/{pattern}/[cmd]
           

请牢记,Ex命令通常都会接受“范围”作为其参数(正如技巧28讨论的那样)。对于

:global

命令内部的 

[cmd]

,该规则依然有效。因此,可以将命令的模板扩展成以下形式。

:g/{pattern}/[range][cmd]
           

实际上,可以用 

:g/{pattern}

 匹配作为参考点,动态设置 

[cmd]

的 

[range]

。. 符号通常表示光标所在行,但在 

:global

命令的上下文中,它则表示 

{pattern}

 的匹配行。

可以把原有的命令拆分成两条单独的Ex命令进行讲解,先分析命令的后半部分。以下是一条有效的Ex命令。

➾ :.+1,/}/-1 sort
           

如果去掉范围中的偏移,该范围可简化为 

.,/}/

,其含义是“从当前行开始,一直到匹配模式 /}/ 的那一行为止”。偏移值 +1 与 —1 仅仅用于缩小操作范围,让我们把目光集中在 {} 之间的内容上面。对于排序前的原始CSS文件,如果把光标置于第1行或第9行,以上这条Ex命令将会对相应 {} 之内的规则按照字母顺序重新排序。

也就是说,只需将光标置于每个{} 块的起始位置,再运行 

:.,/}/ sort

 命令,即可将其中的规则按照字母顺序重新排序了。明白了么?现在,试着用 

:global

命令中的 

{pattern}

 执行一次查找。

➾ /{/
           

以上命令会将光标置于某个 {} 块的起始位置,即我们的目标所在。现在,再重新将 

:global

 与 Ex命令 

[cmd]

组合在一起。

➾ :g/{/ .+1,/}/—1 sort
           

其中,模式 { 会匹配每个 {} 块的起始行。而对于每个匹配行,

:sort

会在匹配行到 {} 块的结尾这个

[range]

 范围内执行。最终,每一条规则的CSS属性都会按照字母顺序排列整齐。

结论

:global

命令的广义形式如下。

:g/{start}/ .,{finish} [cmd]
           

可以将其解读为“对从 

{start}

 开始,到 

{finish}

 结束的所有文本行,执行指定的 

[cmd]

”。

对于 

:global

命令与任意Ex命令的组合,都可以采用相同的范式。例如,假设想对某一段指定范围内的文本内容进行缩进,用Ex命令 :>(参见

:h >

 )就可以实现。

➾ :g/{/ .+1,/}/—1 >
《 6 lines >ed 1 time
   3 lines >ed 1 time
           

注意:

 

与 :sort不同的是,每当调用 :> 命令时,Vim都会提示一条信息。如果在 [cmd] 的前面加上 :slient(参见 

:h :sil

 ),就可以屏蔽这些信息:
➾ :g/{/sil .+1,/}/−1 >
           
此法尤其适用于 

:g/{pattern}

 匹配大量文本行的情况。

本文摘自《Vim实用技巧》(第2版)

Vim实用技巧:global命令

Vim是一款功能丰富而强大的文本编辑器,其代码补全、编译及错误跳转等方便编程的功能特别丰富,在程序员中得到非常广泛的使用。Vim能够大大提高程序员的工作效率。对于Vim高手来说,Vim能以与思考同步的速度编辑文本。同时,学习和熟练使用Vim又有一定的难度。

本书为那些想要提升自己的程序员编写,阅读本书是熟练掌握高超的Vim技巧的必由之路。全书共21章,包括123个技巧。每一章都是关于某一相关主题的技巧集合。每一个技巧都有针对性地解决一个或一类问题,帮助读者提升Vim的使用技能。本书示例丰富,讲解清晰,采用一种简单的标记方法,表示交互式的编辑效果,可以帮助读者快速掌握和精通Vim。

本书适合想要学习和掌握Vim工具的读者阅读,有一定Vim使用经验的程序员,也可以参考查阅以解决特定的问题。