zsh命令行编辑器:在输入命令时返回到之前的行

6
请注意,我所说的不是历史记录中的先前行。我说的是当前多行命令中的先前行。
例如,在zsh中输入多行命令时(_表示光标):
$ for i in {1..5}; do
for> echo i_

此时我可能会改变主意,想让i循环遍历{1..10},因此需要回到上一行。但是,我无法弄清如何做到这一点,因为⌫、←、C-b或者我能想到的任何操作都会停在第二行的开头。那么,有没有可能返回上一行呢?提前感谢。
实际上,这不仅限于zsh。例如,在bash中,我也从未解决过这个问题。
我已经花了相当多的时间在Google上搜索,但没有发现任何结果,所以如果这很明显,请原谅我并归咎于我的Google技巧不佳。
供参考,我使用的是Emacs键绑定和:
$ bindkey | grep delete
"^D" delete-char-or-list
"^H" backward-delete-char
"^[[3~" delete-char
"^?" backward-delete-char

不确定这是否有帮助。


我认为至少在bash中不可能实现,但对于zsh我不确定。问题是,一旦您在输入第一行后按下Enterbash将记住该行,但不再接受该行的输入。 - merlin2011
1
如果您想经常编辑多行命令而不必将它们放入shell脚本中,至少在bash中,如果您处于vi模式下,可以键入v打开编辑器,其路径存储在$VISUAL中。我不确定emacs键绑定的等效方式。 - merlin2011
@merlin2011 感谢提供的信息。我对 vi 不是很熟悉,你能解释一下“输入 v 打开编辑器...”吗?我的意思是,我需要输入什么才能打开编辑器(肯定不只是一个 v,对吧)? - 4ae1e1
我会将这个写成一个答案,不是因为它回答了你的问题,而是因为我需要多行。 - merlin2011
对于那些投票关闭的人:这个问题确实与编程有关,至少是间接相关的。此外,在SO上有很多命令行编辑的问题;如果您坚持要关闭它们,请先关闭它们。 - 4ae1e1
3个回答

5
这里是如何在bash的vim模式下使用可视化模式的方法。
.bashrc文件中添加以下几行代码。
set -o vi

# I do not remember which one of these is used by `v`, so I set both
export VISUAL=/usr/bin/nano
export EDITOR=/usr/bin/nano

重新加载bash,然后按下Esc 进入normal模式。输入v,应该会加载 nano。现在,您可以将多行命令输入nano中。保存并退出后,多行命令将被执行。
对于zsh,您的rc文件中以下行可能会起作用。我从此回答中窃取了最后三行,并在我的zsh上进行了测试。
bindkey -v
autoload -U edit-command-line
zle -N edit-command-line
bindkey -M vicmd v edit-command-line

@Qix,问题在于他询问的是zsh,而这个解决方案描述的是bash。我猜OP会告诉我们它是否有助于他的情况。 :) - merlin2011
谢谢你的回答!它确实有效。不过我还会继续寻找 Emacs 的解决方案(不幸的是,Emacs 是我的最爱)。顺便说一下,bash 代码中有一个错别字:应该是 export VISUAL 而不是 export $VISUAL - 4ae1e1
@KevinSayHi 我的看法是,export -f 是一个丑陋的黑客技巧,它有两个问题:1. 添加了另一个攻击向量(你可以导出一个名为 echocat 的函数!);2. 使得直接获取特定环境变量的值变得不可能(在 export -f 后查看 env 的输出,并尝试使用通常的 $var 语法获取新变量)。如果你想要的话,我认为你可以在 zsh 中使用 command_not_found_handler 来模拟 export -f 的行为(在 man zshmisc 中搜索此名称),但对我来说,export -f 听起来像是反对使用 bash 的又一个论据。 - ZyX
@KevinSayHi 并行看起来不错,但即使在我使用并行执行的任务上,它也缺乏一些灵活性。我不需要一些高级功能,比如分布式执行,但我想配置对信号的反应及其输出 (--bar)。顺便说一下,我还没有提到如何在zsh中获取函数体:使用 whence -f,你可以直接将它传递给 zsh -c - ZyX
@ZyX 是的,尽管我通常不使用信号,但我在我的使用情况中也发现了一些其他限制。但关键是它比xargs好得多。而且它正在积极开发,所以如果您愿意,可以建议功能。whence看起来不错(看起来像来自ksh?在bash中从未听说过...),我会研究一下的。 - 4ae1e1
显示剩余7条评论

3

实际上这是可以做到的。

在第二行时,按下Esc + X以执行一个命令,然后输入push-line-or-edit(注意您可以使用Tab键进行自动补全),最后按下return键。

提示框将会变成传统模式,取消了for>续行符号,您将会看到一个新的缓冲区,其中包含了所有之前输入的文本,现在您可以使用众所周知的C-aC-b键轻松编辑它们了。


是的。而 push-line-or-edit 通常绑定到 C-qM-q(我相信),或者可以使用 bindkey 设置自定义键绑定。(当我提出这个问题时,我完全是 ZLE 的新手。) - 4ae1e1
1
@kYuZz 这是一个很好的答案,正如我在上面的评论中指出的那样,当这个答案发布时,我已经解决了它(但“通常绑定”的部分在技术上是不正确的;它默认是未绑定的,但Prezto将其绑定到M-q,遮盖了push-line)。然而,我认为在授予答案超过一年后取消接受是粗鲁的。如果您喜欢这个答案,请授予一个赞。 - 4ae1e1

1

我不认为这是可能的,但以下是其他解决方法:

  1. 你可以将所有内容都写在一行中。这样不容易编辑,但是多行字符串也不容易编辑。
  2. 你可以绑定一些键(例如,如果你可以告诉终端在这种情况下不要输出<C-m>,则为<C-Enter>)到

    function _-add-newline()
    {
        LBUFFER="$LBUFFER"$'\n'
    }
    zle -N add-newline _-add-newline
    bindkey "\Ca" add-newline
    

    。这样,如果你按下<C-a>,换行符将出现在缓冲区中,但它不会触发accept-line widget,以前的行仍然可以编辑。你可以使用左/右箭头移动到它。

  3. 如果你输入for x in y ; do<CR>echo foo<CR><C-c><Up>,你将在缓冲区中看到for x in y ; do\necho foo,并且所有文本都可以编辑。注意:你需要<CR>来保留最后一行(由<C-c>中止的行不会保存),以及<C-c>来中止输入;如果你已经输入了done,则此变体将无法工作(第一个<CR>将运行循环)。在最后一种情况下,你可以使用<C-c>丢弃最后一行并重新输入它。
  4. 理论上,你可以用一个代码覆盖accept-line widget,该代码解析你的输入并确定是否已完全编写命令,如果没有,则只向缓冲区附加\n(就像上面的函数中一样),不运行原始的accept-line,否则运行原始的accept-line。不过,这种变体要难得多,所以我说“理论上”。

谢谢你的帮助!你的答案对这个问题已经足够好了,我接受它。然而,你知道吗,这个问题实际上有点欺骗性。秘密地说,我需要回去的最重要的原因是有时候我会不小心提前按下回车键;)所以解决方案(1)被排除了。解决方案(3)是我一直在做的,但会导致一个丑陋的命令行和脏历史记录。解决方案(2)非常有趣(我想指出C-a已经绑定了),但仍然无法防止意外发生。我想总体答案是“不行”...我被困在(3)中。 - 4ae1e1

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接