Question

I can run a shell command quickly by hitting M-!. One thing I'd like to do is perform shell quick operations on the current file. An example would be checking the file out through perforce:

M-! p4 edit buffer-file-name RET

(Yes there are perforce integrations, but I'm more interested in the minishell/variable problem rather than a specific workflow)

Of course, the buffer-file-name variable is not evaluated before the command is sent to the shell.

Is there an easy on-the-fly way to do this? Or will I have to roll a custom elisp function?

Was it helpful?

Solution

Indeed using C-u M-: is almost right. I'm not so sure about using shell-quote-argument in eval-to-shell-argument since it only works on strings making it impossible to use eval-to-shell-argument to insert a number or a symbol. You could try something like:

(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)))

and then bind this function in your minibuffer with (define-key minibuffer-local-map [?\M-:] 'sm-minibuffer-insert-val). Of course, if the only thing you ever want to insert is the buffer-file-name, then your execute-shell-command-on-buffer is simpler.

OTHER TIPS

It seems current Emacs has something built-in to achieve the desired result, after M-! (shell-command) press <down>, you will get the file name you are currently visiting on the prompt. Now you can edit it to add the command you want to run on it.

In dired-mode it will give you the file your cursor is currently on.

I did roll my own elisp function, and it looks like this:

(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

I bound it to M-", so now my example can be completed with:

M-"p4 edit %sRET

I won't accept this as the answer, because I did ask for solutions that don't require a function.

You can't do that with M-!, but you can evaluate arbitrary elisp from the minibuffer, so writing a function isn't strictly necessary:

M-: (shell-command (format "p4 edit %s" (shell-quote-argument buffer-file-name))) RET

In this case however, I think eshell is what you want to use:

M-x eshell-command RET p4 edit (eval buffer-file-name) RET

Edit: Except unfortunately that doesn't work, as the *eshell cmd* buffer is selected when that is evaluated. One solution would be:

M-x eshell-command RET p4 edit (eval buffer-file-name (other-buffer nil t)) RET

(Not quite as elegant, sorry.)

You can use C-u M-: (eval-expression with a universal prefix argument) to evaluate any Lisp expression and insert its value at point in the current buffer (including minibuffers, as long as you have enable-recursive-minibuffers set to a non-nil value).

In your example: C-u M-: buffer-file-name RET.

Note that the result of the expression is printed in Lisp form: that is, quoted in such a way that a subsequent call to read would construct an equal Lisp value. For strings, this means enclosing in double quotes, which will probably be interpreted as you expect by the inferior shell. However, you may run into problems with strings that contain special characters, which need different escaping by Elisp and the shell.

The more correct way uses shell-quote-argument, as in phils' solution. Here's a quick defun that reads a Lisp expression and inserts its value at point as a properly quoted shell word:

(defun eval-to-shell-argument (form)
  (interactive "XEval: ")
  (insert (shell-quote-argument form)))

The read-and-evaluate step happens automatically by using an "X" as the argument to interactive.

Edited to add: As @tenpn notes, the above solution doesn't work for inserting buffer-local variables like buffer-file-name in a minibuffer like the one M-! pops up (more precisely, it inserts the buffer-local value of the minibuffer, which is unlikely to be useful). Here is a revised version which seems to work. If the minibuffer is active, it makes the buffer of the previously-selected window temporarily active while reading and evaluating an expression.

Final edit: From @Stefan's answer I see that I should have used (minibuffer-selected-window) to find the previously-selected window. I've also added a (format "%s" ..) to allow inserting non-string values, while still quoting special characters in strings. Here's the final version:

(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)))))

Everyone seems to be rolling their own version, so here's mine -- it will substitue the current filename or marked dired-files or current dired file wherever a % is in the shell command. It follows the same conventions as M-! so I bind it to that.

(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))
Licensed under: CC-BY-SA with attribution
Not affiliated with StackOverflow
scroll top