Polite Bash Commands

Published 4 years, 1 month past

For years, I’ve had a bash alias that re-runs the previous command via sudo.  This is useful in situations where I try to do a thing that requires root access, and I’m not root (because I am never root).  Rather than have to retype the whole thing with a sudo on the front, I just type please and it does that for me.  It looked like this in my .bashrc file:

alias please='sudo "$BASH" -c "$(history -p !!)"'

But then, the other day, I saw Kat Maddox’s tweet about how she aliases please straight to sudo, so to do things as root, she types please apt update, which is equivalent to sudo apt update.  Which is pretty great, and I want to do that!  Only, I already have that word aliased.

What to do?  A bash function!  After commenting out my old alias, here’s what I added to .bash_profile:

please() {
	if [ "$1" ]; then
		sudo $@
	else
		sudo "$BASH" -c "$(history -p !!)"
	fi
}

That way, if I remember to type please apachectl restart, as in Kat’s setup, it will ask for the root password and then execute the command as root; if I forget my manners and simply type apachectl restart, then when I’m told I don’t have privileges to do that, I just type please and the old behavior happens.  Best of both worlds!


Comments (7)

  1. Very cool! Time to be polite to AI. LOL!

  2. Hi. Unfortunately, using sudo $BASH -c like that can sometimes run a different command to using sudo in the first place, because your command line is now being interpreted in root‘s environment rather than your own.

    For a trivial demo try:

    $ echo $HOME
    /home/smylers
    $ please
    /root
    $ sudo echo $HOME
    /home/smylers

    It will affect any environment variable that you and root have set differently, and any path starting with ~/ — which will lead either to an error message (root‘s home directory doesn’t contain a file with that name) or, worse, operating on the wrong file (you’ve just copied an SSH key to root‘s .ssh/ directory rather than your own).

    To avoid this, instead of invoking a nested Bash process, use eval in your existing one:

    eval sudo "$(history -p \!\!)"

    For what it’s worth, my variant of this function also manipulates Bash history to insert the command you actually wanted to run (so if you want to restart Apache again, pressing Up will give you sudo apachectl restart, rather than please). I’m less polite than you, so mine is called rr, for “redo as root”:

    function rr
    # redo as root — repeat the previous command with sudo; credit:
    # https://twitter.com/liamosaur/status/506975850596536320
    {
      cmd=$(fc -ln -1)
      cmd="sudo ${cmd#*  }"
      echo "$cmd"
    
      # Append the sudo-ed command to this shell's history, so that the Up arrow
      # can be used to find it, rather than just the rr command. Weirdly this seems
      # to cause rr itself not to end up in the history, which is an unexpected
      # bonus:
      history -s "$cmd"
    
      # The quotes are needed to preserve any quotes in $cmd, and eval to parse
      # them:
      eval "$cmd"
    }

    That’s literally a tab and a space in the substitution line with a big gap in it. Omit the echo line if you don’t want the command being run to be displayed first.

  3. Er… it seems like you guys are making this way more complicated than it needs to be. Just use !! directly.

    jp@debian:~$ whoami
    jp
    jp@debian:~$ sudo !!
    sudo whoami
    [sudo] password for jp:
    root
    jp@debian:~$
    
  4. Do you know the command : sudo !!
    This put sudo in front of your previous command
    The same thing as your alias

  5. Not clear to me why you don’t just use sudo $(history -p !!) instead of sudo "$BASH" -c "$(history -p !!)" to avoid spinning up a shell with root permissions.

    Btw. instead of history you can also make use of the fc builtin. I.e. sudo $(fc -ln 0).

  6. David, just doing sudo $(history -p !!) will fail if, for instance, the previous command has quote marks in it, or refers to a $FOO environment variable, or uses ~ for a home directory, or anything else which is interpreted by the shell.

    The command line needs interpreting with Bash before running the command, which $(...) doesn’t do — it just passes the text as arguments to sudo literally.

  7. I’ve always used ↑^asudo ⏎ to go to the previous line and add sudo at the beginning and run it. That way if I want to re-run the command in the future, I can find it by going up and I don’t have to run two commands in order.

Add Your Thoughts

Meyerweb dot com reserves the right to edit or remove any comment, especially when abusive or irrelevant to the topic at hand.

HTML allowed: <a href="" title=""> <abbr title=""> <acronym title=""> <b> <blockquote cite=""> <cite> <code> <em> <i> <q cite=""> <s> <strong> <pre class=""> <kbd>


if you’re satisfied with it.

Comment Preview