Repeat last command with "sudo" - bash

I often forget to run commands with sudo. I'm looking for a way to make a bash function (or alias) for repeating the last command with sudo. Something like:
S() {
sudo $(history 1)
}
Any ideas?

You can write:
sudo !!
(See ยง9.3 "History Expansion" in the Bash Reference Manual.)

Not enough?
sudo !!
if you want the S simply put:
alias S=sudo
and use it
S !!
the !! mean the last command

Use alias redo='sudo $(history -p !!)'. This is the only thing that I
have found that works with aliases. The other answers do not work in aliases for some reason, having tried them myself, even though they do work when directly running them.

!! can be used to reference the last command. So:
sudo !!

Adding and expanding upon aaron franke's response (which is correct but lacking a why) such that simply typing redo works after configuring the alias alias redo='sudo $(history -p !!);
This is the only thing I found that works with aliases
There are a few things going on that warrant explanation.
alias redo='sudo !!'
doesn't work as it doesn't execute the sudo portion, though it does resolve the !!.
To get the sudo !! to resolve correctly, a shell resolution directive has to execute and concatenate along with sudo. So we do;
alias redo='sudo $(history -p !!)'
By specifying the right side of the alias to $(history -p !!), what this does is say to the shell;
redo is an alias, assess the right side of the =
sudo remains as is and it concatenated to...
$() is a shell directive, to execute the contents within the current execution process (as oppose to a sub-shell ${}, which spawns a different process to execute within)
Resolve history -p !!
The !! gets expanded to history -p <lastCommandHere> as a result of step 4.
The -p part says to history to only print the results, not execute
The sudo, executes the remaining (now printed out on the same command line), with elevated privileges, assuming password was entered correctly
This ultimately means that the command history -p !! effectively writes out the resolution rather than executing after the sudo.
NOTE: The ' is significant. If you use ", the !! gets interpolated twice and to achieve our purpose we need to very carefully control the resolution steps; single/double quotes in bash
PS
If you're using zsh + oh-my-zsh, there is a sudo alias setup for _ such that you can do;
_ !!
Which will rerun the last command as sudo. However, if you're configuring the alias in ~/.zshrc, the alias redo='sudo $(history -p !!) will not work as is from the zsh config as the resolution steps are different.
That said, you can put the alias into ~/.bashrc instead of ~/.zshrc and have the same result when executing the alias redo from zsh (assuming you're sub-shelling zsh from bash, though this is admittedly a bit of a kludge - though a functional one).

Related

replace rm with gio trash [duplicate]

I am trying to use the solution of using sudo on my existing aliases as covered in many existing answers already and i am so confused as to why it is not working
alias sudo='sudo '
I keep all my aliases in .bash_aliases. Inside .bash_aliases I have this
function _test {
echo 'test!'
}
alias test='_test'
I reload the .bashrc each time; source .bashrc but when I run sudo test I always get
sudo: _test: command not found
The only strange thing that happens is that I get the following on reload and not on a new terminal
dircolors: /home/MYHOME/.dircolors: No such file or directory
but i feel this is a red herring.
As l0b0 says, aliases cannot be used in this way in bash.
However, you can pass a function through (and really, there's basically never a good reason to use an alias instead of sticking to functions alone).
_test() {
echo 'test!'
}
sudo_test() {
sudo bash -c "$(declare -f _test)"'; _test "$#"' sudo_test "$#"
}
...will define a command sudo_test that runs the function _test via sudo. (If your real-world version of this function calls other functions or requires access to shell variables, add those other functions to the declare -f command line, and/or add a declare -p inside the same command substitution to generate a textual description of variables your function needs).
To run an alias like alias_name it must be exactly the first word in the command, so sudo alias_name will never work. Ditto 'alias_name', \alias_name and other things which eventually expand to the alias name.

bash alias using sudo returning command not found

I am trying to use the solution of using sudo on my existing aliases as covered in many existing answers already and i am so confused as to why it is not working
alias sudo='sudo '
I keep all my aliases in .bash_aliases. Inside .bash_aliases I have this
function _test {
echo 'test!'
}
alias test='_test'
I reload the .bashrc each time; source .bashrc but when I run sudo test I always get
sudo: _test: command not found
The only strange thing that happens is that I get the following on reload and not on a new terminal
dircolors: /home/MYHOME/.dircolors: No such file or directory
but i feel this is a red herring.
As l0b0 says, aliases cannot be used in this way in bash.
However, you can pass a function through (and really, there's basically never a good reason to use an alias instead of sticking to functions alone).
_test() {
echo 'test!'
}
sudo_test() {
sudo bash -c "$(declare -f _test)"'; _test "$#"' sudo_test "$#"
}
...will define a command sudo_test that runs the function _test via sudo. (If your real-world version of this function calls other functions or requires access to shell variables, add those other functions to the declare -f command line, and/or add a declare -p inside the same command substitution to generate a textual description of variables your function needs).
To run an alias like alias_name it must be exactly the first word in the command, so sudo alias_name will never work. Ditto 'alias_name', \alias_name and other things which eventually expand to the alias name.

Cannot properly execute bash script because of a redirection in an environment where root is the owner

My script is executable and I run it as sudo. I tried many workarounds and alternatives to the ">>" operator but nothing seemed to work properly.
My script:
#! /bin/bash
if [[ -z "$1" || -z "$2" ]]; then
exit 1
else
root=$1
fileExtension=$2
fi
$(sudo find $root -regex ".*\.${fileExtension}") >> /home/mux/Desktop/AllFilesOf${fileExtension}.txt
I tried tee, sed and dd of, I also tried running it with bash -c or in sudo -i , nothing worked. Either i get an empty file or a Permission denied error.
I searched thoroughly and read many command manuals but I can't get it to work
The $() operator performs command substitution. When the overall command line is expanded, the command within the parentheses is executed, and the whole construct is replaced with the command's output. After all expansions are performed, the resulting line is executed as a command.
Consider, then, this simplified version of your command:
$(find /etc -regex ".*\.conf") >> /home/mux/Desktop/AllFilesOfconf.txt
On my system that will expand to a ginormous command of the form
/etc/rsyslog.conf /etc/pnm2ppa.conf ... /etc/updatedb.conf >> /home/mux/Desktop/AllFilesOfconf.txt
Note at this point that the redirection is separate from, and therefore independent of, the command in the command substitution. Expanding the command substitution therefore does not cause anything to be written to the target file.
But we're not done! That was just the expansion. Bash now tries to execute the result as a command. In particular, in the above example it tries to execute /etc/rsyslog.conf as a command, with all the other file names as arguments, and with output redirected as specified. But /etc/rsyslog.conf is not executable, so that will fail, producing a "permission denied" message. I'm sure you can extrapolate from there what effects different expansions would produce.
I don't think you mean to perform a command substitution at all, but rather just to run the command and redirect its output to the given file. That would simply be this:
sudo find $root -regex ".*\.${fileExtension}" >> /home/mux/Desktop/AllFilesOf${fileExtension}.txt
Update:
As #CharlesDuffy observed, the redirection in that case is performed with the permissions of the user / process running the script, just as it is in your original example. I have supposed that that is intentional and correct -- i.e. that the script is being run by user 'mux' or by another user that has access to mux's Desktop directory and to any existing file in it that the script might try to create or update. If that is not the case, and you need the redirection, too, to be privileged, then you can achieve it like so:
sudo -s <<END
find $root -regex ".*\.${fileExtension}" >> /home/mux/Desktop/AllFilesOf${fileExtension}.txt
END
That runs an interactive shell via sudo, with its input is redirected from the heredoc. The variable expansions are performed in the host shell from which sudo is executed. In this case the redirection is performed with the identity obtained via sudo, which affects access control, as well as ownership of the file if a new one is created. You could add a chown command if you don't want the output files to be owned by root.

Is it possible to specify the bash prompt using a command-line option?

I use vim a lot and often find it useful to drop into the command line using !bash.
However, I need to type exit to return to vim and sometimes I'm not sure whether I'm in a subshell or whether that will close my session.
What I'd really like to do is type something like !bash -prompt "subshell" so that I get something like this:
subshell$ <commands go here>
Is this possible?
The most direct way to do this is to set the PS1 environment variable within vim:
:let $PS1="subshell$ "
And start your sub-shells using the command :shell instead of :!bash.
Using the $ sign with let modifies an environment variable. Add this to your .vimrc to persist the setting.
Alternately, using :shell you can specify a more specific command, including arguments, using the shell option, see help shell and help 'shell'.
So:
:set shell=bash\ --rcfile\ ~/.vimbashrc
In .vimbashrc add PS1="subshell ", and invoke the sub-shells using :shell instead of !bash. Add this to your .vimrc to persist the setting.
So you have two options:
Add let $PS1="subshell " to your .vimrc, and start sub-shells using :shell instead of :!bash.
Make a custom rc file for your vim sub-shells, add your specific PS1="subshell " to it, and modify the shell option in your .vimrc: set shell=bash\ --rcfile\ ~/.vimbashrc.
Finally, if you must use :!bash to start the sub-shells there are a couple of more options. Note that you can also pass a more specific command line using !, e.g.:
:PS1="subshell$ " bash should work.
:!bash\ --rcfile\ ~/.vimbashrc, and set PS1 in .vimbashrc as above
But you'll need to type these every time, or define a mapping for it.
Use shell variable $SHLVL seems to be another option here. Add $SHLVL in your $PS1:
export PS1="$PS1 $SHLVL"
so your prompt looks like this:
[tim#RackAblade47 ~]$ 2
when you start shell from VIM, $SHLVL will increase:
[tim#RackAblade47 ~]$ 4
Yes - you can change the prompt inside the shell before running your commands
PS1="subshell"
checkout this guide for all the options http://www.linuxselfhelp.com/howtos/Bash-Prompt/Bash-Prompt-HOWTO-2.html
If you really must do it via the bash command you can use '--rcfile' to specify an RC file that runs the PS1 command for you (you usually put the PS1= line your .bashrc to customize the prompt at login)
To answer your original question, you can say inside Vim:
:!VIMPROMPT="(vim) " bash
and change your prompt (in your .bashrc, presumably) from something like
PS1='\u#\h:\w\$ '
to
PS1='$VIMPROMPT\u#\h:\w\$ '
this will change your prompt from
me#host:~$
to
(vim) me#host:~$
if run inside Vim.
I'm personally using
case $(ps $PPID) in *vim|*bash)
PS1="$(ps $PPID | awk '{print $NF}' | sed 1d) $PS1" ;;
esac
in my prompt script that's sourced by my .bashrc, taken from triplee's comment.
I have been able to change the prompt for a vim subshell process by checking for the MYVIMRC variable which is exported inside vim, and will then update PS1 accordingly. I updated my .bashrc file with the following.
PS1='\$ '
# when in vim subshell change PS1 for clarity
if [[ $MYVIMRC ]]; then PS1='>> '; fi;
Put this in your .bashrc after any existing PS1= statements.
if ps | grep -q vim; then
export PS1="[VIM]$PS1"
fi
Tested on Ubuntu.
You can send it to the background with CTRL+Z and then bring it back with the fg command. With jobs you see all the jobs you have stopped.
This was you can have multiple instances of vim in parallel and chose which one you want to bring back. If there's no running you will just get a no current job error and that's it.
This doesn't specifically answer your question, but addresses the problem underneath it.

Using aliases with nohup

Why doesn't the following work?
$ alias sayHello='/bin/echo "Hello world!"'
$ sayHello
Hello world!
$ nohup sayHello
nohup: appending output to `nohup.out'
nohup: cannot run command `sayHello': No such file or directory
(the reason I ask this question is because I've aliased my perl and python to different perl/python binaries which were optimized for my own purposes; however, nohup gives me troubles if I don't supply full path to my perl/python binaries)
Because the shell doesn't pass aliases on to child processes (except when you use $() or ``).
$ alias sayHello='/bin/echo "Hello world!"'
Now an alias is known in this shell process, which is fine but only works in this one shell process.
$ sayHello
Hello world!
Since you said "sayHello" in the same shell it worked.
$ nohup sayHello
Here, a program "nohup" is being started as a child process. Therefore, it will not receive the aliases.
Then it starts the child process "sayHello" - which isn't found.
For your specific problem, it's best to make the new "perl" and "python" look like the normal ones as much as possible. I'd suggest to set the search path.
In your ~/.bash_profile add
export PATH="/my/shiny/interpreters/bin:${PATH}"
Then re-login.
Since this is an environment variable, it will be passed to all the child processes, be they shells or not - it should now work very often.
For bash: Try doing nohup 'your_alias'. It works for me. I don't know why back quote is not shown. Put your alias within back quotes.
With bash, you can invoke a subshell interactively using the -i option. This will source your .bashrc as well as enable the expand_aliases shell option. Granted, this will only work if your alias is defined in your .bashrc which is the convention.
Bash manpage:
If the -i option is present, the shell is interactive.
expand_aliases: If set, aliases are expanded as described above under ALIASES. This option is enabled by default for interactive shells.
When an interactive shell that is not a login shell is started, bash reads and executes commands from /etc/bash.bashrc and ~/.bashrc, if these files exist.
$ nohup bash -ci 'sayHello'
If you look at the Aliases section of the Bash manual, it says
The first word of each simple command, if unquoted, is checked to see
if it has an alias.
Unfortunately, it doesn't seem like bash has anything like zsh's global aliases, which are expanded in any position.

Resources