16

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 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")).

Inaimathi
  • 13,853
  • 9
  • 49
  • 93
  • Related (near duplicate?): http://stackoverflow.com/questions/95631/open-a-file-with-su-sudo-inside-emacs and the vote to migrate to Super User may be correct. @Inaimathi: If this is closely tied to programming, now would be a good time to explain why, or this questions will likely be moved to a more suitable site. – dmckee --- ex-moderator kitten Mar 18 '10 at 18:25
  • 2
    Given that the OP is trying to bind some elisp to a key this is programmy enough for me. – dmckee --- ex-moderator kitten Mar 18 '10 at 18:34
  • Restructured question to alleviate some of the confusion. – Inaimathi Mar 18 '10 at 19:21
  • You don't use this. You could just open the file with TRAMP, `/sudo::/etc/init.d/apaches2` and then start a shell from that file with `M-x shell`. – Anders Feb 24 '22 at 04:51

8 Answers8

14

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.

Community
  • 1
  • 1
Mirzhan Irkegulov
  • 17,660
  • 12
  • 105
  • 166
14

How about:

(shell-command (concat "echo " (shell-quote-argument (read-passwd "Password? "))
                       " | sudo -S your_command_here"))
Gareth Rees
  • 64,967
  • 9
  • 133
  • 163
scottfrazer
  • 17,079
  • 4
  • 51
  • 49
  • 2
    Perfect. In other words, I could easily do something like `(defun sudo-shell-command (command) (shell-command (concat "echo " (read-passwd "Password: ") " | sudo -S " command)))`. Much better than actually writing the password in source-code too. – Inaimathi Mar 18 '10 at 21:07
6

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).

ramesh
  • 61
  • 2
3

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
dmckee --- ex-moderator kitten
  • 98,632
  • 24
  • 142
  • 234
1

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.

msw
  • 42,753
  • 9
  • 87
  • 112
  • `(shell-command "sudo -S /etc/init.d/apache2 restart")` does the same thing (ie. it tries an arbitrary password 3 times, and sends the 3 unsuccessful attempts to the `*Shell Command Output*` buffer. – Inaimathi Mar 18 '10 at 18:47
  • And `(shell-command "echo password | sudo -S /etc/init.d/apache2 restart")`? (Not that it's a good solution to encode the password in the command.) – Dave Bacher Mar 18 '10 at 19:55
  • 1
    Yeah. I wouldn't want my root password in my code somewhere. That sounds like it might be worse than running `sudo emacs`. +1 for suggesting the -S approach (even if I did misunderstand it at first) – Inaimathi Mar 18 '10 at 21:10
1

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.

Inaimathi
  • 13,853
  • 9
  • 49
  • 93
  • Better yet, if you have sudoers control, you can set it up so *you* are not prompted for a password for these programs that you run frequently. – Dave Bacher Mar 18 '10 at 19:50
1

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)))))
lawlist
  • 13,099
  • 3
  • 49
  • 158
0

I used the following to start nmap from emacs as root,

http://nakkaya.com/sudoEl.markdown

Hamza Yerlikaya
  • 49,047
  • 44
  • 147
  • 241