How do I run a sudo command in Emacs?
I'm trying to create shortcut keys for some commonly used sudo shell commands (for example, having C-c s
run (shell-command "sudo /etc/init.d/apache2 restart")
).
I tried using a straight-up shell-command call as above, but it just outputs the following to the *Shell Command Output*
buffer:
[sudo] password for Inaimathi:
Sorry, try again.
[sudo] password for Inaimathi:
Sorry, try again.
[sudo] password for Inaimathi:
Sorry, try again.
sudo: 3 incorrect password attempts
It doesn't actually ask for a password. I don't want to have to start up Emacs using sudo开发者_StackOverflow社区 emacs
, but I guess that's an option if nothing else will work.
The ideal solution would be a function from within Emacs (as opposed to OS jiggery-pokery to change the behaviour of the shell or the sudo
command). Something like (sudo-shell-command "dostuff")
, or (with-password-prompt (shell-command "sudo dostuff"))
.
How about:
(shell-command (concat "echo " (shell-quote-argument (read-passwd "Password? "))
" | sudo -S your_command_here"))
I frequently call commands from Emacs like aptitude update
. scottfrazer's solution might not be as useful. Synchronous commands make me wait for a long time, and if you execute an unsupported program (for example, aptitude
, which uses ncurses), you will hang up Emacs (C-g won't help), and CPU load will be 100%. Changing to async-shell-command
solves this.
But it also introduces a new problem. If your command fails, your password will end up in *Messages*
buffer:
echo PASSWORD | sudo -S aptitude: exited abnormally with code 1.
That's why i propose the following solution:
(defun sudo-shell-command (command)
(interactive "MShell command (root): ")
(with-temp-buffer
(cd "/sudo::/")
(async-shell-command command)))
Here "M" in interactive
prompts for program name in minibuffer, with-temp-buffer
creates a sham buffer, in which we change directory to /sudo::/
to use TRAMP for sudo prompt.
This is the solution by David Kastrup from sudo command with minibuffer password prompt @ gnu.emacs.help.
Note, you still shouldn't call aptitude
directly, otherwise the subprocess will be there forever, until you send sudo pkill aptitude
.
Read on shells and processes in manual.
If you're running emacs22 or later, you can just start up a shell from emacs and run your sudo command there. It'll automatically pull you into the minibuffer window for your password:
M-x shell
sudo whoami
This should just ask for your password down at the bottom of the screen (without displaying it).
Workaround (rather than an emacs solution):
Set up a ssh key pair so that no password is necessary.
Procedure:
- run
ssh-keygen
to generate a pair of keys. Give them a useful name to keep them sorted out from all the others you'll make once you get use to this - Copy the public one to
$HOME/.ssh
for the receiving account - Keep the private one in
$HOME/.ssh
of the sending account (you could copy it to multiple sending accounts, but it might be better to make a separate keypair for every incoming machine) - edit
$HOME/.ssh/config
on the sending machine to tell ssh what key to use - Profit
sudo
attempts to open the terminal device (tty) to read the password. Your emacs process may not have a controlling terminal. sudo -S
tells it to use the standard input for a password which should be coming from emacs.
EDIT: Scott's answer above is vastly preferable to this hack. Use that.
A possible solution:
I found out that setting a default password-asking utility solves this problem.
What I had to do is add Defaults:ALL askpass=/usr/lib/openssh/gnome-ssh-askpass
to my /etc/sudoers
file (using sudo visudo
, obviously).
Eval-ing (shell-command "sudo /etc/init.d/apache2 restart")
now prompts me for a password instead of trying to guess it unsuccessfully.
I'm not accepting this answer unless it becomes clear that there's no better solution; ideally I'd like something that solves the problem internally to Emacs instead of requiring you to poke around your /etc
directory.
I used the following to start nmap from emacs as root,
http://nakkaya.com/sudoEl.markdown
The following example uses start-process
, set-process-filter
, and set-process-sentinel
. Although this is an elaborate minimal working example, a similar approach is used all the time when there are known STRINGS output by a running process that require input from the user (or programmatically inputting a preprogrammed response to an inquiry from the running process). It could be a password, a username, a yes / no question, etc. The trick is to do a little testing while setting up the function to ascertain what STRING needs to be matched with a REGEXP. This example was written up on Ubuntu 20.04 running sudo
with the ls
command. The STRING that needs to be matched is: [sudo] password for lawlist:
with a space following the colon, and with my username being lawlist
. Now, the REGEXP could have been something as simple as "password"
, but I wanted to be more specific as a matter of personal preference so that I understand (in the future) what was going when the code was written. The process-filter is normally a separate function, but it can also be included in let
-bound form. When debugging while writing up the code, it may sometimes be necessary to kill a running process using something like M-x list-processes
to inspect what is going on. It may also be helpful to uncomment the DEBUGGING message in the process-filter.
(let ((sudo-process-filter
(lambda (proc string)
;;; DEBUGGING:
;; (message "STRING: `%s`" string)
(cond
((string-match "^\\[sudo\\] password for lawlist: $" string)
(let ((password (read-passwd "PWD: ")))
(process-send-string proc (concat password "\n"))))
(t
(with-current-buffer (messages-buffer)
(let ((inhibit-read-only t))
(goto-char (point-max))
(when (not (bolp))
(insert "\n"))
(insert string)
(when (not (bolp))
(insert "\n"))))))))
(msg-success "SUCCESS!")
(msg-failure "FAILURE!"))
(start-process "ls-proc-name" nil "sudo" "-S" "ls" "-la")
(set-process-filter (get-process "ls-proc-name")
(symbol-value 'sudo-process-filter))
(set-process-sentinel
(get-process "ls-proc-name")
;;; To penetrate the lambda expression with a let-bound variable, we use a
;;; backtick / comma combination. This obviates the need for lexical binding.
`(lambda (p e)
(if (= 0 (process-exit-status p))
(message "%s" ,msg-success)
(message "%s" ,msg-failure)))))
精彩评论