Some time ago I came accross this.
While an alias like sudo $(history -p \!\!)
can be very useful, there are some problems with it. First, it splits up parameters that have spaces in them, effectively breaking the command. Second, it doesn't do parameter substitution, so it might again pass the wrong parameters to your command.
To illustrate I use this simple python script:
#!/usr/bin/env python3 # test.py import sys for a in sys.argv: # for each command line parameter print('<arg>{0}</arg>'.format(a)) # print the parameter surrounded by 'arg' tags print() # add an extra newline at the end of the output
And I ran these commands:
halves@pc > alias oops='sudo $(history -p \!\!)' halves@pc > t='foo bar' halves@pc > ./test.py * "$t" "$(echo quux)" file\ name\ with\ spaces <arg>./test.py</arg> <arg>1</arg> <arg>2</arg> <arg>3</arg> <arg>file name with spaces</arg> <arg>test.py</arg> <arg>test.sh</arg> <arg>foo bar</arg> <arg>quux</arg> <arg>file name with spaces</arg> halves@pc > oops [sudo] password for halves: <arg>./test.py</arg> <arg>1</arg> <arg>2</arg> <arg>3</arg> <arg>file name with spaces</arg> <arg>test.py</arg> <arg>test.sh</arg> <arg>"$t"</arg> <arg>"$(echo</arg> <arg>quux)"</arg> <arg>file\</arg> <arg>name\</arg> <arg>with\</arg> <arg>spaces</arg>
As you can see here, the arguments when using oops
are different. Specifically, the "$t"
is passed without substitution, "$(echo quux)"
is split up and passed without substitution and file\ name\ with\ spaces
is split up. The filename with spaces stays intact when passed as a part of *
though.
To solve these problems, `oops' has to become a bit more complex. In fact, it had to become a function instead of an alias. The function and the same test as I ran for the alias are shown below.
halves@pc > declare -f oops oops() { # create a temporary file local f="$(mktemp)" # write "sudo " to the file without a trailing newline echo -n "sudo " > "$f" # append the previous command from the history to the file history -p !-1 >> "$f" # run the command in this shell so it has access to the same # environment. Also, this way the command will be parsed again in # the same way it was done before, so the parameters will be # interpreted the same way source "$f" # store the return value in a local variable local r="$?" # remove the temporary file /bin/rm "$f" # return the returnvalue from the command in the temporary file return "$r" } halves@pc > t='foo bar' halves@pc > ./test.py * "$t" "$(echo quux)" file\ name\ with\ spaces <arg>./test.py</arg> <arg>1</arg> <arg>2</arg> <arg>3</arg> <arg>file name with spaces</arg> <arg>test.py</arg> <arg>test.sh</arg> <arg>foo bar</arg> <arg>quux</arg> <arg>file name with spaces</arg> halves@pc > oops [sudo] password for halves: <arg>./test.py</arg> <arg>1</arg> <arg>2</arg> <arg>3</arg> <arg>file name with spaces</arg> <arg>test.py</arg> <arg>test.sh</arg> <arg>foo bar</arg> <arg>quux</arg> <arg>file name with spaces</arg>
As you can see, this time results of the original command and oops
are exactly the same, with the exception of sudo being used in the latter.
To use the improved version just add this to your .bashrc:
oops() { local f="$(mktemp)" echo -n "sudo " > "$f" history -p !-1 >> "$f" source "$f" local r="$?" /bin/rm "$f" return "$r" }