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?
3 Answers
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)
-
When run
ad-read-default-directory-&-call, it reportentered--Lisp error: (wrong-type-argument sequencep ad-read-default-directory-args) call-interactively(ad-read-default-directory-&-call record nil)error.ccd– ccd2019-04-17 00:44:11 +00:00Commented 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 useshell-commandonly, maybe with a numerical prefix.Tobias– Tobias2019-04-17 00:46:11 +00:00Commented Apr 17, 2019 at 0:46 -
Something weird, the
shell-commandonly execute the shell command and not let me to select the file path.ccd– ccd2019-04-17 00:51:52 +00:00Commented Apr 17, 2019 at 0:51 -
@yuanlai Try a numeric prefix argument to select the
default-directoryforshell-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-9if 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– Tobias2019-04-17 00:54:14 +00:00Commented Apr 17, 2019 at 0:54 -
when I run
C-1 M-!and then typelsshell command, the result paste on my current working buffer.ccd– ccd2019-04-17 00:59:43 +00:00Commented Apr 17, 2019 at 0:59
From elisp you can set default-directory temporarily and then run your shell command:
(let ((default-directory "/tmp"))
(shell-command "ls"))
-
Hard coding the file path and shell command might not as my expect, sorry.ccd– ccd2019-04-17 00:53:40 +00:00Commented 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 bindingdefault-directoryis of importance. If you study the code from my answer you will see that I also used this technique.Tobias– Tobias2019-04-17 01:13:21 +00:00Commented Apr 17, 2019 at 1:13
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)
M-!lets you run a shell command in the current directory. If you want something else, please give us more details.M-!, how to combine these to one step and not leaving current working buffer.cd dir && your command.cd dirseems not smart as well asfind-filefunction, is it possible let them work the same?cdelisp function instead, but it changes the current directory globally.