开发者

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:

  1. 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
  2. Copy the public one to $HOME/.ssh for the receiving account
  3. 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)
  4. edit $HOME/.ssh/config on the sending machine to tell ssh what key to use
  5. 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)))))
0

上一篇:

下一篇:

精彩评论

暂无评论...
验证码 换一张
取 消

最新问答

问答排行榜