你对Vim的问题是你不了解vi。
你提到切割yy
并抱怨你几乎从不想切割整条线。事实上,编辑源代码的程序员经常希望在整行、行范围和代码块上工作。但是,yy
这只是将文本拉入匿名复制缓冲区(或在vi中调用的“注册” )的众多方法之一。
vi的“禅”是你在说一种语言。首字母y
是动词。该声明yy
是 的同义词y_
。y
加倍以使其更易于键入,因为它是一种常见的操作。
这也可以表示为dd
P
(删除当前行并将副本粘贴回原处;在匿名寄存器中留下副本作为副作用)。they
和d
“动词”以任何动作为“主语”。因此yW
是“从这里(光标)拉到当前/下一个(大)单词的末尾”并且y'a
是“从这里拉到包含名为' a '的标记的行”。
如果您只了解基本的上、下、左和右光标移动,那么vi对您来说不会比“记事本”副本更有效率。(好吧,您仍然可以使用语法高亮和处理大于约 45KB 左右的文件的能力;但在这里与我一起工作)。
vi有 26 个“标记”和 26 个“寄存器”。使用该m
命令将标记设置到任何光标位置。每个标记由一个小写字母指定。因此ma
将“ a ”标记设置为当前位置,并mz
设置“ z ”标记。'
您可以使用(单引号)命令移动到包含标记的行。因此移动到包含“ a ”标记'a
的行的开头。您可以使用(反引号)命令移动到任何标记的精确位置。从而将直接移动到' z '标记的确切位置。`
`z
因为这些是“运动”,所以它们也可以用作其他“陈述”的主题。
因此,剪切任意文本选择的一种方法是删除一个标记(我通常使用' a '作为我的“第一个”标记,' z '作为我的下一个标记,' b '作为另一个标记,' e '作为还有一个(我不记得在使用vi的 15 年中曾经交互式地使用过四个以上的标记;一个人创建了自己的惯例,即宏如何使用标记和寄存器,而不会干扰一个人的交互式上下文)。然后我们去到我们所需文本的另一端;我们可以从任一端开始,没关系。然后我们可以简单地使用d`a
剪切或y`a
复制。因此整个过程有 5 次击键开销(如果我们从“插入”开始,则为 6 "模式和需要Esc出命令模式)。一旦我们剪切或复制,然后粘贴副本是一个单一的击键:p
.
我说这是剪切或复制文本的一种方法。然而,它只是众多之一。通常,我们可以更简洁地描述文本范围,而无需四处移动光标并放下标记。例如,如果我在一段文本中,我可以分别使用{
和}
移动到段落的开头或结尾。因此,为了移动一段文本,我使用{
d}
(3 次击键)剪切了它。(如果我碰巧已经在段落的第一行或最后一行,那么我可以简单地分别使用d}
或d{
。
“段落”的概念默认为通常直观合理的东西。因此,它通常适用于代码和散文。
我们经常知道一些模式(正则表达式)来标记我们感兴趣的文本的一端或另一端。向前或向后搜索是vi中的移动。因此,它们也可以用作我们“陈述”中的“主题”。所以我可以使用d/foo
从当前行剪切到包含字符串“foo”的下一行,并y?bar
从当前行复制到包含“bar”的最近(上一个)行。如果我不想要整行,我仍然可以使用搜索动作(作为他们自己的陈述),放下我的标记并使用 `x
前面描述的命令。
除了“动词”和“主语”,vi还有“宾语”(在语法意义上)。到目前为止,我只描述了匿名寄存器的使用。但是,我可以通过在“对象”引用前加上(双引号修饰符)来使用 26 个“命名”寄存器中的任何一个。"
因此,如果我使用"add
我将当前行剪切到' a '寄存器中,如果我使用"by/foo
然后我将文本的副本从这里拉到包含“foo”的下一行到' b '寄存器中。要从寄存器粘贴,我只需在粘贴前加上相同的修饰符序列:粘贴“ a ”寄存器"ap
的副本"bP
将“ b ”中的副本粘贴到当前行之前。
这种“前缀”的概念还将语法“形容词”和“副词”的类似物添加到我们的文本操作“语言”中。大多数命令(动词)和动作(动词或对象,取决于上下文)也可以采用数字前缀。因此3J
意思是“加入接下来的三行”,d5}
意思是“从当前行删除到从这里开始的第五段的末尾”。
这都是中级vi。这些都不是Vim特有的,如果你准备好学习它们, vi中还有更高级的技巧。如果您要掌握这些中间概念,那么您可能会发现您很少需要编写任何宏,因为文本操作语言足够简洁和富有表现力,可以使用编辑器的“原生”语言轻松完成大多数事情。
更高级技巧的示例:
有许多:
命令,最值得注意的是:% s/foo/bar/g
全局替换技术。(这不是高级的,但其他:
命令可以)。整个:
命令集在历史上被vi以前的化身继承为ed(行编辑器)和后来的ex(扩展行编辑器)实用程序。事实上vi之所以如此命名,是因为它是ex的可视化界面。
:
命令通常在文本行上运行。 ed和ex是在终端屏幕不常见并且许多终端是“电传打字机”(TTY)设备的时代编写的。因此,从文本的打印副本开始工作是很常见的,通过极其简洁的界面使用命令(常见的连接速度是 110 波特,或者大约是每秒 11 个字符 - 这比快速打字员慢;滞后在多用户交互会话;此外,通常还有一些节省纸张的动机)。
所以大多数:
命令的语法包括一个地址或地址范围(行号),后跟一个命令。自然可以使用文字行号::127,215 s/foo/bar
在 127 和 215 之间的每一行上将第一次出现的“foo”更改为“bar”。也可以分别使用一些缩写,例如.
或$
用于当前行和最后一行。也可以使用相对前缀+
并-
分别指代当前行之后或之前的偏移量。因此::.,$j
意思是“从当前行到最后一行,将它们全部连接成一行”。 是(所有行):%
的同义词。:1,$
:... g
and命令有一些解释,:... v
因为它们非常强大。 :... g
是“全局”的前缀,将后续命令应用于与模式(正则表达式)匹配的所有行,同时:... v
将此类命令应用于与给定模式不匹配的所有行(“conVerse”中的“v”)。与其他ex命令一样,这些可以通过寻址/范围引用作为前缀。因此:.,+21g/foo/d
意味着“从当前行到接下来的 21 行中删除任何包含字符串“foo”的行”,而:.,$v/bar/d
意味着“从这里到文件末尾,删除任何不包含字符串“bar”的行。
有趣的是,常见的 Unix 命令grep实际上是受到这个ex命令的启发(并以它记录的方式命名)。ex命令:g/re/p
(grep)是他们记录如何“全局”“打印”包含“正则表达式”(re)的行的方式。当使用ed和ex时,该:p
命令是任何人最先学习的命令之一,并且通常是在编辑任何文件时使用的第一个命令。这是您打印当前内容的方式(通常一次只使用一页:.,+25p
或类似的内容)。
请注意:% g/.../d
or (它的 reVerse/conVerse 对应物::% v/.../d
是最常见的使用模式。但是还有一些其他ex
命令值得记住:
我们可以使用m
来移动线条,并j
加入线条。例如,如果您有一个列表,并且您想分隔所有匹配的内容(或者相反地不匹配某些模式)而不删除它们,那么您可以使用类似的内容::% g/foo/m$
...并且所有“foo”行都将被移动到文件的结尾。(注意关于使用文件末尾作为暂存空间的另一个提示)。这将保留所有“foo”行的相对顺序,同时从列表的其余部分中提取它们。(这相当于执行以下1G!GGmap!Ggrep foo<ENTER>1G:1,'a g/foo'/d
操作:(将文件复制到它自己的尾部,通过 过滤尾部grep
,并从头部删除所有内容)。
要连接行,通常我可以为所有需要连接到其前身的行找到一个模式(例如,在某些项目符号列表中,所有以“^”而不是“^ *”开头的行)。对于这种情况,我会使用:(:% g/^ /-1j
对于每个匹配的行,向上一行并加入它们)。(顺便说一句:对于试图搜索项目符号行并加入下一个项目符号列表的原因有几个原因......它可以将一个项目符号行连接到另一个项目符号行,并且它不会将任何项目符号行加入到所有它的延续;它只会在比赛中成对工作)。
几乎不用说,您可以将我们的老朋友s
(substitute) 与g
and v
(global/converse-global) 命令一起使用。通常你不需要这样做。但是,考虑某些情况,您只想对匹配其他模式的行执行替换。通常,您可以使用带有捕获的复杂模式并使用反向引用来保留您不想更改的行部分。但是,将匹配项与替换项分开通常会更容易::% g/foo/s/bar/zzz/g
-- 对于包含“foo”的每一行,将所有“bar”替换为“zzz”。(就像是:% s/\(.*foo.*\)bar\(.*\)/\1zzz\2/g
仅适用于同一行中“foo”之前的“bar”实例;它已经够难看的了,必须进一步处理才能捕捉到“bar”在“foo”之前的所有情况)
关键是命令集中不仅仅是p
, s
, 和d
行。ex
:
地址也可以参考标记。因此,您可以使用:将包含字符串 foo 的任何行连接到其后续行,如果它位于“ a ”和“ b ”标记:'a,'bg/foo/j
之间的行之间。(是的,所有前面的命令示例都可以通过使用这些类型的寻址表达式作为前缀来限制为文件行的子集)。ex
这很模糊(在过去的 15 年中,我只使用过几次类似的东西)。但是,我会坦率地承认,如果我花时间想出正确的咒语,我经常以迭代和交互方式完成一些事情,这些事情可能会更有效地完成。
另一个非常有用的vi或ex命令是:r
读入另一个文件的内容。因此::r foo
在当前行插入名为“foo”的文件的内容。
更强大的是:r!
命令。这将读取命令的结果。这与暂停vi会话、运行命令、将其输出重定向到临时文件、恢复vi会话以及从 temp 中读取内容相同。文件。
更强大的是!
(bang) 和:... !
( ex bang) 命令。它们还执行外部命令并将结果读入当前文本。但是,它们还通过命令过滤我们的文本选择!这我们可以使用1G!Gsort
(G
是vi "goto" 命令;它默认转到文件的最后一行,但可以以行号为前缀,例如 1,第一行) 对文件中的所有行进行排序。这相当于ex变体:1,$!sort
。作者经常使用!
Unix fmt或fold实用程序来重新格式化或“自动换行”文本选择。一个非常常见的宏是{!}fmt
(重新格式化当前段落)。程序员有时通过缩进或其他代码重新格式化工具使用它来运行他们的代码,或者只是其中的一部分。
使用:r!
and!
命令意味着任何外部实用程序或过滤器都可以被视为我们编辑器的扩展。我偶尔将这些与从数据库中提取数据的脚本一起使用,或者与从网站中提取数据的wget或lynx命令一起使用,或者与从远程系统中提取数据的ssh命令一起使用。
另一个有用的ex命令是:so
(的缩写:source
)。这会将文件的内容作为一系列命令读取。当您正常启动vi时,它会隐式地执行 a :source
on ~/.exinitrc
file(而Vim通常会这样做 on ~/.vimrc
,这很自然)。这样做的用途是,您可以通过简单地获取一组新的宏、缩写和编辑器设置来即时更改您的编辑器配置文件。如果您很狡猾,您甚至可以将其用作存储ex编辑命令序列以按需应用于文件的技巧。
例如,我有一个七行文件(36 个字符),它通过wc运行一个文件,并在包含该字数数据的文件顶部插入一个 C 样式的注释。我可以使用以下命令将该“宏”应用于文件:vim +'so mymacro.ex' ./mytarget
(vi和Vim+
的命令行选项通常用于在给定的行号处启动编辑会话。然而,一个鲜为人知的事实是,可以跟随任何有效的ex命令/表达式,例如“source”命令我已经在这里完成了;举个简单的例子,我有一些脚本可以调用:在我重新映像一组服务器时,以非交互方式从我的 SSH 已知主机文件中删除一个条目)。+
vi +'/foo/d|wq!' ~/.ssh/known_hosts
通常使用 Perl、AWK、 sed(实际上,就像grep受ed命令启发的实用程序一样)编写这样的“宏”要容易得多。
该@
命令可能是最不起眼的vi命令。在近十年来偶尔教授高级系统管理课程的过程中,我遇到的人很少使用它。 @
像执行vi或ex命令一样执行寄存器的内容。
示例:我经常使用::r!locate ...
在我的系统上查找某个文件并将其名称读入我的文档中。从那里我删除任何无关的点击,只留下我感兴趣的文件的完整路径。而不是费力地Tab通过路径的每个组件(或者更糟糕的是,如果我碰巧被卡在没有 Tab 完成支持的机器上在它的vi副本中)我只是使用:
0i:r
(将当前行变成有效的:r命令),
"cdd
(将行删除到“c”寄存器中)和
@c
执行该命令。
这只是 10 次击键(对我来说,这个表达式"cdd
@c
实际上是一个手指宏,所以我几乎可以像任何常见的六个字母单词一样快速地输入它)。
发人深省的想法
我只是触及了vi功能的表面,我在这里所描述的甚至都不是vim命名的“改进”的一部分!我在这里描述的所有内容都应该适用于20 或 30 年前的任何旧vi副本。
有些人使用vi的能力比我以往任何时候都多。