6

Personally I need run many shell command in emacs, I find it is not convenient to run a shell engine (eshell) or using it separately (bash), so is there a elisp function (local provide by emacs or any other third-part library) meet my need?

8
  • 3
    M-! lets you run a shell command in the current directory. If you want something else, please give us more details.
    – choroba
    Commented Apr 16, 2019 at 9:14
  • If using this command I need to move to the directory and then type shell command through M-!, how to combine these to one step and not leaving current working buffer.
    – ccd
    Commented Apr 16, 2019 at 9:17
  • Type cd dir && your command.
    – choroba
    Commented Apr 16, 2019 at 9:28
  • The cd dir seems not smart as well as find-file function, is it possible let them work the same?
    – ccd
    Commented Apr 16, 2019 at 15:24
  • You can try running the cd elisp function instead, but it changes the current directory globally.
    – choroba
    Commented Apr 16, 2019 at 15:27

3 Answers 3

3

The following Elisp code advises shell-command so that you can input a default-directory for the shell-command if you call it with numeric prefix argument.

If the prefix argument is negative the original shell-command code will be called without prefix argument. You get the output of the shell command in *Shell Command Output* buffer in that case.

If the prefix argument is positive the original shell-command gets also that prefix argument and the output is send to the current buffer.

If shell-command is called non-interactively it works like expected if the first argument is a string. Otherwise the second argument should be the default-directory to be used for the shell command and the original arguments of shell-command should start at the third third argument of the advised version.

(defun ad-read-default-directory-args (interactive-spec)
  "Read default directory and apply INTERACTIVE-SPEC.
Return a list (DIRECTORY PREFIX-ARG RESULT-OF-INTERACTIVE-SPEC).
The default directory is only read with numeric `current-prefix-arg'."
  (let ((default-directory (or
                (and
                 (numberp current-prefix-arg)
                 (expand-file-name (read-directory-name "Default directory: " nil nil t)))
                default-directory))
    (current-prefix-arg (and (numberp current-prefix-arg)
                 (null (< current-prefix-arg 0))
                 current-prefix-arg)))
    (append
     (list
      current-prefix-arg ;; never a string
      default-directory)
     (advice-eval-interactive-spec interactive-spec))))

(defun ad-read-default-directory-&-call (fun &rest args)
  "Run FUN with ARGS with `default-directory' set to DIRECTORY and PREFIX-ARG.
Only change `default-directory' if the prefix arg is numeric.
Positive prefix args are passed to FUN negative are not."
  (interactive #'ad-read-default-directory-args)
  (if (stringp (car args))
      (apply fun args)
    (let ((default-directory (or (cl-second args) default-directory))
      (current-prefix-arg (cl-first args)))
      (apply fun (nthcdr 2 args)))))

(advice-add 'shell-command :around #'ad-read-default-directory-&-call)
6
  • When run ad-read-default-directory-&-call, it report entered--Lisp error: (wrong-type-argument sequencep ad-read-default-directory-args) call-interactively(ad-read-default-directory-&-call record nil) error.
    – ccd
    Commented Apr 17, 2019 at 0:44
  • @yuanlai That command is not meant to be run by a user. It is exclusively the advice for shell-command. Please use shell-command only, maybe with a numerical prefix.
    – Tobias
    Commented Apr 17, 2019 at 0:46
  • Something weird, the shell-command only execute the shell command and not let me to select the file path.
    – ccd
    Commented Apr 17, 2019 at 0:51
  • @yuanlai Try a numeric prefix argument to select the default-directory for shell-command. The key sequence is e.g.: C-- C-1 M-! Literally: Hold the control key down and press the "minus" key and the "1" key. Afterwards hold the Alt key down and press :. The actual number is irrelevant you can also input -9 if that is easier for you. Please read the answer carefully to understand what the difference between a positive and a negative prefix argument is.
    – Tobias
    Commented Apr 17, 2019 at 0:54
  • when I run C-1 M-! and then type ls shell command, the result paste on my current working buffer.
    – ccd
    Commented Apr 17, 2019 at 0:59
11

From elisp you can set default-directory temporarily and then run your shell command:

(let ((default-directory "/tmp")) 
    (shell-command "ls"))
3
  • Hard coding the file path and shell command might not as my expect, sorry.
    – ccd
    Commented Apr 17, 2019 at 0:53
  • 4
    @yuanlai This is not your use-case, yes. But, maybe this answer is helpful for other users that stumble across this question. It is the Elisp answer to your question. The string "/tmp" is just an example. The general principle of locally binding default-directory is of importance. If you study the code from my answer you will see that I also used this technique.
    – Tobias
    Commented Apr 17, 2019 at 1:13
  • simple and useful. thanks
    – RoyM
    Commented Jul 19, 2020 at 13:00
3

I execute all of my python code through the shell within Emacs. Here is how I do it.

Although it's not clear what specifically you're trying to call, I suspect that if you understand how this code works, you will be able to adapt it to your needs. Use the built-in Emacs help (C-h f and C-h v) to learn specifically what various parts of the code do.

Create a shell in which to send a command to

This function is basically a convenience wrapper around what @amitp has already posted. It first checks if a shell process currently exists, creating one if none does. It then sends the provided command argument to the shell and executes it, along with some repositioning of the cursor. (Essentially it pastes the command into the shell and presses <RET>.)

    (defun my-sh-send-command (command)
      "Send COMMAND to current shell process.

    Creates new shell process if none exists.

    See URL `https://stackoverflow.com/a/7053298/5065796'"
      (let ((proc (get-process "shell"))
            pbuf)
        (unless proc
          (let ((currbuff (current-buffer)))
            (shell)
            (switch-to-buffer currbuff)
            (setq proc (get-process "shell"))
            ))
        (setq pbuff (process-buffer proc))
        (setq command-and-go (concat command "\n"))
        (with-current-buffer pbuff
          (goto-char (process-mark proc))
          (insert command-and-go)
          (move-marker (process-mark proc) (point))
          )
        (process-send-string proc command-and-go)))

Send a shell command using the currently visited file

I use python, so this function concatenates the current buffer name with a "python " prefix and sends it to the shell. Just change the "python " part in the (concat ...) statement to whatever you need.

    (defun my-buffer-file-to-shell ()
      "Send current buffer file to shell as python call."
      (interactive)
      (my-sh-send-command (concat "python " (buffer-file-name))))

In this case, the path is absolute and comes from buffer-file-name. If you need to execute the code in a different directory, you will need to modify the default-directory, as @amitp states. Note that default-directory is buffer local. You can read more about setting default-directory programmatically here.

Bind everything to a convenient key

I have a global shell command which runs the main project file and is bound to F7. If I want to run the currently visited file with python, I press S-f7.

    (global-set-key (kbd "<S-f7>") 'my-buffer-file-to-shell)

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.