我可以通过点击快速运行 shell 命令M-!。我想做的一件事是对当前文件执行 shell 快速操作。一个例子是通过 perforce 检出文件:
M-! p4 edit buffer-file-name
RET
(是的,有 perforce 集成,但我对 minishell/变量问题而不是特定的工作流程更感兴趣)
当然,buffer-file-name
在命令发送到 shell 之前不会评估变量。
有没有一种简单的即时方式来做到这一点?还是我必须推出自定义 elisp 函数?
当前的 Emacs 似乎内置了一些东西来达到预期的效果,在M-!
( shell-command
) 按下后<down>
,您将在提示符上获得当前正在访问的文件名。现在您可以对其进行编辑以添加要在其上运行的命令。
其中dired-mode
将为您提供光标当前所在的文件。
确实使用 Cu M-: 几乎是正确的。我不太确定使用shell-quote-argument
ineval-to-shell-argument
因为它仅适用于字符串,因此无法用于eval-to-shell-argument
插入数字或符号。您可以尝试以下方法:
(defun sm-minibuffer-insert-val (exp)
(interactive
(list (let ((enable-recursive-minibuffers t))
(read-from-minibuffer "Insert: "
nil read-expression-map t
'read-expression-history))))
(let ((val (with-selected-window (minibuffer-selected-window)
(eval exp)))
(standard-output (current-buffer)))
(prin1 val)))
然后在你的 minibuffer 中绑定这个函数(define-key minibuffer-local-map [?\M-:] 'sm-minibuffer-insert-val)
。当然,如果您只想插入缓冲区文件名,那么您execute-shell-command-on-buffer
就更简单了。
我确实推出了自己的 elisp 函数,它看起来像这样:
(defun execute-shell-command-on-buffer (shell-command-text)
(interactive "MShell command:")
(shell-command (format shell-command-text (shell-quote-argument buffer-file-name)))
)
https://gist.github.com/2367513
我将它绑定到M-",所以现在我的示例可以通过以下方式完成:
M-"p4 edit %s
RET
我不会接受这个作为答案,因为我确实要求不需要函数的解决方案。
你不能用 来做到这一点M-!,但你可以从 minibuffer 中评估任意 elisp,所以编写一个函数并不是绝对必要的:
M-: (shell-command (format "p4 edit %s" (shell-quote-argument buffer-file-name)))
RET
但是,在这种情况下,我认为eshell
您要使用的是:
M-x eshell-command
RET p4 edit (eval buffer-file-name)
RET
编辑:不幸的是,这不起作用,因为在*eshell cmd*
评估时选择了缓冲区。一种解决方案是:
M-x eshell-command
RET p4 edit (eval buffer-file-name (other-buffer nil t))
RET
(不太优雅,抱歉。)
您可以使用C-u M-:
(eval-expression
带有通用前缀参数) 来评估任何 Lisp 表达式并将其值插入当前缓冲区中的点(包括迷你缓冲区,只要您enable-recursive-minibuffers
设置为非nil
值)。
在您的示例中:C-u M-: buffer-file-name RET
.
请注意,表达式的结果以 Lisp 形式打印:也就是说,以这样一种方式引用,以便后续调用read
将构造一个equal
Lisp 值。对于字符串,这意味着用双引号括起来,这可能会被劣质 shell 解释为您所期望的。但是,您可能会遇到包含特殊字符的字符串的问题,这需要 Elisp 和 shell 进行不同的转义。
更正确的方法使用shell-quote-argument
,如 phils 的解决方案。这是一个快速定义,它读取 Lisp 表达式并将其值插入到点作为正确引用的 shell 单词:
(defun eval-to-shell-argument (form)
(interactive "XEval: ")
(insert (shell-quote-argument form)))
通过使用 a"X"
作为 的参数,读取和评估步骤会自动发生interactive
。
编辑添加:正如@tenpn 所指出的,上述解决方案不适用于插入缓冲区局部变量,例如弹出的小buffer-file-name
缓冲区(更准确地说,它插入了小缓冲区的缓冲区局部值,这不太可能M-!
有用)。这是一个似乎可行的修订版。如果 minibuffer 处于活动状态,它会使先前选择的窗口的缓冲区在读取和评估表达式时暂时处于活动状态。
最终编辑:从@Stefan 的回答中,我看到我应该习惯于(minibuffer-selected-window)
找到先前选择的窗口。我还添加了一个(format "%s" ..)
允许插入非字符串值,同时仍然引用字符串中的特殊字符。这是最终版本:
(defun eval-to-shell-argument ()
(interactive)
(let* ((buffer
(if (minibufferp)
(window-buffer (minibuffer-selected-window))
(current-buffer)))
(result
(with-current-buffer buffer
(eval-minibuffer "Eval: "))))
(insert (shell-quote-argument (format "%s" result)))))
每个人似乎都在滚动他们自己的版本,所以这是我的——它将替换当前文件名或标记的 dired-files 或当前的 dired 文件,只要 a%
在 shell 命令中。它遵循相同的约定,M-!
因此我将其绑定到该约定。
(defun my-shell-command (command &optional output-buffer error-buffer)
"Run a shell command with the current file (or marked dired files).
In the shell command, the file(s) will be substituted wherever a '%' is."
(interactive (list (read-from-minibuffer "Shell command: "
nil nil nil 'shell-command-history)
current-prefix-arg
shell-command-default-error-buffer))
(cond ((buffer-file-name)
(setq command (replace-regexp-in-string "%" (buffer-file-name) command nil t)))
((and (equal major-mode 'dired-mode) (save-excursion (dired-move-to-filename)))
(setq command (replace-regexp-in-string "%" (mapconcat 'identity (dired-get-marked-files) " ") command nil t))))
(shell-command command output-buffer error-buffer))