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.– ccdCommented 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-command
only, maybe with a numerical prefix.– TobiasCommented 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.– ccdCommented Apr 17, 2019 at 0:51 -
@yuanlai Try a numeric prefix argument to select the
default-directory
forshell-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.– TobiasCommented Apr 17, 2019 at 0:54 -
when I run
C-1 M-!
and then typels
shell command, the result paste on my current working buffer.– ccdCommented 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.– ccdCommented 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-directory
is of importance. If you study the code from my answer you will see that I also used this technique.– TobiasCommented 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 dir
seems not smart as well asfind-file
function, is it possible let them work the same?cd
elisp function instead, but it changes the current directory globally.