你用Vim的问题是你不懂vi。
你提到用yy剪,抱怨说你几乎从来不想剪整行。事实上,程序员在编辑源代码时,经常希望处理整行、整行或整块代码。然而,yy只是将文本拉入匿名复制缓冲区(或在vi中称为“寄存器”)的许多方法之一。
vi的“禅意”是你在说一种语言。开头的y是动词。语句yy是y_的同义词。由于y是一种很常见的操作,所以将y改为两倍以便更容易输入。
这也可以表示为dd P(删除当前行并将副本粘贴回原位;在匿名登记册中留下一份作为副作用)。y和d“动词”以任何动作为主语。因此yW是“从这里(光标)拉到当前/下一个(大)单词的末尾”,y’a是“从这里拉到包含标记为‘a’的行”。
如果你只了解基本的上下左右光标移动,那么vi对你来说不会比“notepad”的拷贝更有效率。(好吧,你仍然有语法高亮显示和处理大于45KB左右的文件的能力;但在这里和我一起工作)。
vi has 26 "marks" and 26 "registers." A mark is set to any cursor location using the m command. Each mark is designated by a single lower case letter. Thus ma sets the 'a' mark to the current location, and mz sets the 'z' mark. You can move to the line containing a mark using the ' (single quote) command. Thus 'a moves to the beginning of the line containing the 'a' mark. You can move to the precise location of any mark using the ` (backquote) command. Thus `z will move directly to the exact location of the 'z' mark.
因为这些是“动作”,它们也可以作为其他“陈述”的主语。
So, one way to cut an arbitrary selection of text would be to drop a mark (I usually use 'a' as my "first" mark, 'z' as my next mark, 'b' as another, and 'e' as yet another (I don't recall ever having interactively used more than four marks in 15 years of using vi; one creates one's own conventions regarding how marks and registers are used by macros that don't disturb one's interactive context). Then we go to the other end of our desired text; we can start at either end, it doesn't matter. Then we can simply use d`a to cut or y`a to copy. Thus the whole process has a 5 keystrokes overhead (six if we started in "insert" mode and needed to Esc out command mode). Once we've cut or copied then pasting in a copy is a single keystroke: p.
I say that this is one way to cut or copy text. However, it is only one of many. Frequently we can more succinctly describe the range of text without moving our cursor around and dropping a mark. For example if I'm in a paragraph of text I can use { and } movements to the beginning or end of the paragraph respectively. So, to move a paragraph of text I cut it using { d} (3 keystrokes). (If I happen to already be on the first or last line of the paragraph I can then simply use d} or d{ respectively.
“段落”的概念通常是直觉上合理的。因此,它不仅适用于散文,也适用于代码。
Frequently we know some pattern (regular expression) that marks one end or the other of the text in which we're interested. Searching forwards or backwards are movements in vi. Thus they can also be used as "subjects" in our "statements." So I can use d/foo to cut from the current line to the next line containing the string "foo" and y?bar to copy from the current line to the most recent (previous) line containing "bar." If I don't want whole lines I can still use the search movements (as statements of their own), drop my mark(s) and use the `x commands as described previously.
In addition to "verbs" and "subjects" vi also has "objects" (in the grammatical sense of the term). So far I've only described the use of the anonymous register. However, I can use any of the 26 "named" registers by prefixing the "object" reference with " (the double quote modifier). Thus if I use "add I'm cutting the current line into the 'a' register and if I use "by/foo then I'm yanking a copy of the text from here to the next line containing "foo" into the 'b' register. To paste from a register I simply prefix the paste with the same modifier sequence: "ap pastes a copy of the 'a' register's contents into the text after the cursor and "bP pastes a copy from 'b' to before the current line.
“前缀”的概念也为我们的文本操作“语言”增加了语法“形容词”和“副词”的类似物。大多数命令(动词)和移动(动词或对象,取决于上下文)也可以带数字前缀。因此,3J表示“连接接下来的三行”,d5}表示“从当前行删除,直到第五段结束。”
这些都是vi的中级水平。它们都不是Vim特有的,如果你准备好学习的话,vi中还有更高级的技巧。如果您只是掌握了这些中间概念,那么您可能会发现您几乎不需要编写任何宏,因为文本操作语言足够简洁和富有表现力,可以使用编辑器的“本机”语言轻松地完成大多数事情。
以下是一些更高级的技巧:
有许多:命令,最著名的是:% s/foo/bar/g全局替换技术。(这不是高级的,但其他:命令可以)。整个:命令集在历史上被vi之前的版本继承为ed(行编辑器)和后来的ex(扩展行编辑器)实用程序。事实上,vi之所以如此命名,是因为它是ex的可视化界面。
: commands normally operate over lines of text. ed and ex were written in an era when terminal screens were uncommon and many terminals were "teletype" (TTY) devices. So it was common to work from printed copies of the text, using commands through an extremely terse interface (common connection speeds were 110 baud, or, roughly, 11 characters per second -- which is slower than a fast typist; lags were common on multi-user interactive sessions; additionally there was often some motivation to conserve paper).
So the syntax of most : commands includes an address or range of addresses (line number) followed by a command. Naturally one could use literal line numbers: :127,215 s/foo/bar to change the first occurrence of "foo" into "bar" on each line between 127 and 215. One could also use some abbreviations such as . or $ for current and last lines respectively. One could also use relative prefixes + and - to refer to offsets after or before the curent line, respectively. Thus: :.,$j meaning "from the current line to the last line, join them all into one line". :% is synonymous with :1,$ (all the lines).
The :... g and :... v commands bear some explanation as they are incredibly powerful. :... g is a prefix for "globally" applying a subsequent command to all lines which match a pattern (regular expression) while :... v applies such a command to all lines which do NOT match the given pattern ("v" from "conVerse"). As with other ex commands these can be prefixed by addressing/range references. Thus :.,+21g/foo/d means "delete any lines containing the string "foo" from the current one through the next 21 lines" while :.,$v/bar/d means "from here to the end of the file, delete any lines which DON'T contain the string "bar."
It's interesting that the common Unix command grep was actually inspired by this ex command (and is named after the way in which it was documented). The ex command :g/re/p (grep) was the way they documented how to "globally" "print" lines containing a "regular expression" (re). When ed and ex were used, the :p command was one of the first that anyone learned and often the first one used when editing any file. It was how you printed the current contents (usually just one page full at a time using :.,+25p or some such).
注意:% g/…/d或(它的反向/反向对应物::% v/…/d是最常见的使用模式。然而,还有其他一些ex命令值得记住:
We can use m to move lines around, and j to join lines. For example if you have a list and you want to separate all the stuff matching (or conversely NOT matching some pattern) without deleting them, then you can use something like: :% g/foo/m$ ... and all the "foo" lines will have been moved to the end of the file. (Note the other tip about using the end of your file as a scratch space). This will have preserved the relative order of all the "foo" lines while having extracted them from the rest of the list. (This would be equivalent to doing something like: 1G!GGmap!Ggrep foo<ENTER>1G:1,'a g/foo'/d (copy the file to its own tail, filter the tail through grep, and delete all the stuff from the head).
To join lines usually I can find a pattern for all the lines which need to be joined to their predecessor (all the lines which start with "^ " rather than "^ * " in some bullet list, for example). For that case I'd use: :% g/^ /-1j (for every matching line, go up one line and join them). (BTW: for bullet lists trying to search for the bullet lines and join to the next doesn't work for a couple reasons ... it can join one bullet line to another, and it won't join any bullet line to all of its continuations; it'll only work pairwise on the matches).
Almost needless to mention you can use our old friend s (substitute) with the g and v (global/converse-global) commands. Usually you don't need to do so. However, consider some case where you want to perform a substitution only on lines matching some other pattern. Often you can use a complicated pattern with captures and use back references to preserve the portions of the lines that you DON'T want to change. However, it will often be easier to separate the match from the substitution: :% g/foo/s/bar/zzz/g -- for every line containing "foo" substitute all "bar" with "zzz." (Something like :% s/\(.*foo.*\)bar\(.*\)/\1zzz\2/g would only work for the cases those instances of "bar" which were PRECEDED by "foo" on the same line; it's ungainly enough already, and would have to be mangled further to catch all the cases where "bar" preceded "foo")
关键是在ex命令集中不止有p、s和d行。
地址也可以指标记。因此,您可以使用::'a,'bg/foo/j将包含字符串foo的任何行连接到它的下一行,如果它位于'a'和'b'标记之间的行之间。(是的,所有前面的ex命令示例都可以通过添加这些类型的寻址表达式来限制为文件行的子集)。
这是相当晦涩的(在过去15年里,我只使用过几次这样的东西)。然而,我可以坦率地承认,我经常迭代和交互式地完成一些事情,如果我花时间思考正确的咒语,可能会更有效地完成这些事情。
另一个非常有用的vi或ex命令是:r,用于读入另一个文件的内容。因此::r foo将名为“foo”的文件的内容插入到当前行。
更强大的是:r!命令。它读取命令的结果。这与暂停vi会话、运行一个命令、将其输出重定向到一个临时文件、恢复您的vi会话并从temp.文件中读入内容是相同的。
Even more powerful are the ! (bang) and :... ! (ex bang) commands. These also execute external commands and read the results into the current text. However, they also filter selections of our text through the command! This we can sort all the lines in our file using 1G!Gsort (G is the vi "goto" command; it defaults to going to the last line of the file, but can be prefixed by a line number, such as 1, the first line). This is equivalent to the ex variant :1,$!sort. Writers often use ! with the Unix fmt or fold utilities for reformating or "word wrapping" selections of text. A very common macro is {!}fmt (reformat the current paragraph). Programmers sometimes use it to run their code, or just portions of it, through indent or other code reformatting tools.
使用:r!和!命令意味着任何外部实用程序或过滤器都可以被视为编辑器的扩展。我偶尔会将它们用于从数据库中提取数据的脚本,或从网站中提取数据的wget或lynx命令,或从远程系统中提取数据的ssh命令。
Another useful ex command is :so (short for :source). This reads the contents of a file as a series of commands. When you start vi it normally, implicitly, performs a :source on ~/.exinitrc file (and Vim usually does this on ~/.vimrc, naturally enough). The use of this is that you can change your editor profile on the fly by simply sourcing in a new set of macros, abbreviations, and editor settings. If you're sneaky you can even use this as a trick for storing sequences of ex editing commands to apply to files on demand.
例如,我有一个七行文件(36个字符),它通过wc运行一个文件,并在包含单词计数数据的文件顶部插入一个c风格的注释。我可以使用vim +'so mymacro '这样的命令将“宏”应用到文件中。“交货。/ mytarget
(vi和Vim的+命令行选项通常用于在给定的行号上启动编辑会话。然而,一个鲜为人知的事实是,可以通过任何有效的ex命令/表达式跟随+,例如我在这里所做的“source”命令;对于一个简单的例子,我有脚本调用:vi +'/foo/d|wq!~ /。ssh/known_hosts以非交互方式从我的ssh known hosts文件中删除一个条目,而我正在重新映像一组服务器)。
通常,使用Perl、AWK、sed(实际上,就像grep一样,是受ed命令启发的一个实用程序)编写这样的“宏”要容易得多。
The @ command is probably the most obscure vi command. In occasionally teaching advanced systems administration courses for close to a decade I've met very few people who've ever used it. @ executes the contents of a register as if it were a vi or ex command.
Example: I often use: :r!locate ... to find some file on my system and read its name into my document. From there I delete any extraneous hits, leaving only the full path to the file I'm interested in. Rather than laboriously Tab-ing through each component of the path (or worse, if I happen to be stuck on a machine without Tab completion support in its copy of vi) I just use:
0i:r(将当前行转换为有效的:r命令),
cdd(删除行进入“c”寄存器)和
@c执行该命令。
这只需要10次按键(表达式“cdd @c”对我来说实际上是一个手指宏,所以我几乎可以像打任何常见的六个字母的单词一样快)。
发人深省的想法
我只是触及了vi功能的表面,我在这里所描述的甚至都不是vim命名的“改进”的一部分!我在这里描述的所有内容都应该适用于20或30年前的任何旧vi副本。
有人比我更能使用vi的力量。