8

I have this alias set in my system /etc/bashrc file:

alias root="sudo !!"

The intention of this being to run the last used command using sudo of course. When used, it of course appears to substitute the last command in history to the bashrc file upon shell initialization, and not the actual command that you would get if you were to run sudo !! in an interactive shell. I've also tried alias root="sudo fc -s" to no avail.

I realize this is probably something to do with how the BASH does command substitutions, but can someone explain why this is, and provide a usable substitute?

I'm running BASH version 3.2.51(1)-release (x86_64-apple-darwin13).

Bernhard
  • 12,272
xanadu
  • 101
  • What OS are you using? – Sepahrad Salour Dec 05 '13 at 11:46
  • I use GNU bash, version 4.1.2(1)-release (x86_64-redhat-linux-gnu) and ls;sudo !! works well! – Sepahrad Salour Dec 05 '13 at 11:51
  • (x86_64-apple-darwin13) - Mac Os X :) – xanadu Dec 05 '13 at 12:09
  • This doesn't work for me either, when in an alias. I do not believe Sepahrad tried this as an alias, if you try ls; sudo !! that will work by itself in a shell. – slm Dec 05 '13 at 12:28
  • worksforme in an interactive shell without sudo. Even the help for fc suggests an alias, so this is more likely to be a problem of switching users/shells. – lynxlynxlynx Dec 05 '13 at 12:33
  • @slm, Yes I didn't test it as alias... It's doesn't work for me too! +1 to Question. – Sepahrad Salour Dec 05 '13 at 12:36
  • Yeah the issue here is that you cannot pass arguments such as this to an alias. Think of aliases as macros that get expanded. So it's impossible to pass !! to an alias, and have the alias expand it later. – slm Dec 05 '13 at 12:49
  • 1
    When using double quotes, the !! is actually expanded when defining the alias, so the alias will sudo-execute the command last used before the definition instead of the command used before calling the alias. – crater2150 Dec 06 '13 at 09:18

2 Answers2

11

The key part in this behavior is explained by 2 bits in the bash manpage:

In the HISTORY EXPANSION section:

History expansion is performed immediately after a complete line is read, before the shell breaks it into words.

In the ALIASES section:

The first word of each simple command, if unquoted, is checked to see if it has an alias.

So basically history expansion occurs before word splitting. Alias expansion occurs after.


Alternate solution

The best way I can think of doing this is the following:

alias root='sudo $(fc -ln -1)'

This will work for simple commands, but for more complex ones we need to tweak it.

alias root='sudo sh -c "$(fc -ln -1)"'

The reason for the change is because of something like this:

# alias root='sudo $(fc -ln -1)'
# echo "I AM $(whoami)"
I AM patrick
# root
"I AM $(whoami)"

As you can see no shell parsing is done. By changing it to use sh -c "..." instead, it does shell interpretation.

# alias root='sudo sh -c "$(fc -ln -1)"'
# echo "I AM $(whoami)"
I AM patrick
# root
I AM root

Another way (I thought of this one first, so keeping it in the answer, but it's not as nice as the one above):

alias root='fc -e "sed -i -e \"s/^/sudo /\""'

The fc -e command will run the specified command passing it a file containing the previously executed command. We just run sed on that file to prefix the command with sudo.

phemmer
  • 71,831
  • Thanks, never even noticed the fc command before. – slm Dec 05 '13 at 13:54
  • +1 Thanks, I never heard fc command before. – Sepahrad Salour Dec 05 '13 at 15:05
  • So, in that case, why does alias root="sudo fc -s" not work then? – xanadu Dec 06 '13 at 09:10
  • 1
    @slm fc is really neat for a range of commands more than a single one (at least to me). – Bernhard Dec 06 '13 at 10:07
  • 1
    @xanadu because 1) it's a shell builtin, not a normal command 2) it uses the history of the current shell. Once you use sudo, you don't necessarily get a shell, and if you do, it's a new shell with new history. – phemmer Dec 06 '13 at 13:16
  • alias root='sudo sh -c "$(fc -ln -1)"' doesn't recognize alias. For example, if I try S neofetch where S is alias for pacman -S first. Then run root, it will prompt sh: S: command not found even if I define alias S='pacman -S' in ~/.bashrc. – Ynjxsjmh Jan 18 '21 at 06:46
  • To save my day, I use alias S='sudo pacman -S' instead. – Ynjxsjmh Jan 18 '21 at 07:02
2

You cannot use an alias in this way. Aliases cannot be passed parameters such as !!. To achieve what you want you could use a function instead.

function root() {
  sudo $(history | tail -2 | head -1 | awk '{$1=$2=$3=""; print $0}');
}

This is a rough idea though and may have some problems. My output from the history command looks like this:

$ history
1081  20131205 08:00:12  ls
1082  20131205 08:00:13  history

So I need to parse this output. In the above I'm running history taking the last 2 commands, then taking the first of the last 2 This is the previously run command. I then use awk to get rid of the first 4 columns, leaving us with the command line that was previously run.

As an alias?

Given we're using the output from history, there's really no longer any reason to use a Bash function. This is needed if you're attempting to pass the previous command in as an argument, but we're getting it via the history command now.

$ alias root="sudo \$(history | tail -2 | head -1 | awk '{\$1=\$2=\$3=\"\"; print \$0}')"

Beyond the trickier escaping required to get this into an alias, the above works, similar to the function.

slm
  • 369,824