How do I run (async-)shell-command (but no other shell-mode buffers) in view-mode?

  • I looked for (async-)shell-command-hook but didn't find anything. Feb 16, 2015 at 9:24

2 Answers 2


Do can define your another wrapper function that calls async-shell-command which enables the view-mode after calling that function.

(defun my/async-shell-command (command &optional output-buffer error-buffer)
    (read-shell-command "Async shell command: " nil nil
            (let ((filename
                ((eq major-mode 'dired-mode)
                 (dired-get-filename nil t)))))
              (and filename (file-relative-name filename))))
  ;; call the original function
  (async-shell-command command output-buffer error-buffer)
  ;; switch to the shell command output buffer
  (switch-to-buffer "*Async Shell Command*")
  ;; enable `view-mode'

and bind that to the default async-shell-command binding M-&.

Note that the (interactive (list ..)) portion of the wrapper funciton is copied directly from the source code for async-shell-command. It's useful to retain the input arguments of the original function when writing a wrapper around it. The same applies if you were to use advice-add (emacs 24.4+) instead.

Similar thing can be done for shell-command too.

  • Awesome! I'll try this out tomorrow. From reading your code though I'm guessing that the last three lines would be enough, right? I don't quite understand the interactive part. Feb 16, 2015 at 23:31
  • @MattiasBengtsson Nope, you will need the interactive part too. I have simply copied that portion from the source code. That portion is important as it creates the mechanism for entering the shell command in the minibuffer. Feb 16, 2015 at 23:40
  • Did you test it? I'm wondering whether you won't get errors for trying to print on a readonly buffer.
    – Malabarba
    Feb 17, 2015 at 0:21
  • @Malabarba I tested this to work as the OP wants. Calling this function, executes the async command as entered in the minibuffer and then makes the async buffer read-only (view-mode). So when you try to type in that buffer, you won't be able to. Re-executing the same command, discards the old async buffer and shows the new content, again making that read-only. Feb 17, 2015 at 0:40
  • Hm it doesn't quite work. The original async-shell-command will popup and then the switch-to-buffer call will add a new view of the buffer. The old view won't be in view-mode. See here: youtube.com/watch?v=DI4I-KOGA90 Hm, I wonder if it would be possible to construct an advice around (async-)shell-command? I'll try that out later this week. Feb 17, 2015 at 18:04

Seeing as there are no shell command hooks, an alternative is to use advice. All asynchronous processes need to specify a sentinel function, which in the case of shell commands is shell-command-sentinel:

(define-advice shell-command-sentinel (:after (process _msg) my-view-output)
  "Enable `view-mode' in `*Async Shell Command*' buffers."
  (let ((buffer (process-buffer process)))
    (and (memq (process-status process) '(exit signal))
         (buffer-live-p buffer)
         (with-current-buffer buffer

Synchronous shell commands, on the other hand, all go through the command shell-command-on-region:

(define-advice shell-command-on-region (:after (&rest _) my-view-output)
  "Enable `view-mode' in `*Shell Command Output*' buffer."
  (let ((buffer (get-buffer "*Shell Command Output*")))
    (when (buffer-live-p buffer)
      (with-current-buffer buffer

The latter advice is actually a bit more brittle than the former because it hard-codes the buffer name, but it should work for most cases. You should tweak it to suit your needs and use of shell commands.

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Not the answer you're looking for? Browse other questions tagged or ask your own question.