How can I recall a command using ! without executing it immediately? - bash

In Bash, typing for example !make followed by Enter will recall and also execute the last command that starts with "make" in the history.
What I'd like is to just recall that command, but not evaluate it right away until I hit Enter again, to allow me to look at it, or edit it if needed.
Is there a way to do that in Bash?

You can use CTRL+p to get the previous command
You can also use CRL+r to reverse search for a command in your history.

The p modifier after any history expansion prints the result to the command line for further editing, rather than executing it.
$ echo foo
foo
$ !!:p
$ echo foo
^
|
cursor remains here

You can expand history on the current line using the history-expand-line key (M-^).
The M modifier key is usually mapped to Alt.
$ !make
AltShift6
$ make -C mydir
if your last make command was make -C mydir. You can then edit the command line in place.
Alternatively, you can scroll through history as outlined in other answers.

Related

What does !!:2 mean in Mac or Linux?

Today I saw a command in Mac:
touch !!:2/{f1.txt, f2.txt}
I know the use of touch command but what does !!:2 does in this command. I don't have Mac and tried in Linux It is giving some weird output. If anyone could explain more expression like this it would be great.
touch updates file timestamp (to current time, given no arguments)
!! is 'History expansion' operation, retrieving previous command from bash history log in this form (two exclamation dots), alias for '!-1'
:2 is word specifier, retrieving 2nd command argument. E.g. if previous history command was ls -l /tmp, !!:2 will render to '/tmp'
{f1.txt,f2.txt} is called 'Brace expansion'. Brace expansion requires single word string without unescaped white spaces (it's definitely a typo in the question). For example, foo{bar,baz} will be expanded to 'foobar foobaz'
So, let's assume we run bash command
ls -l /tmp
Now, touch !!:2/{f1.txt,f2.txt} will produce
touch /tmp/f1.txt /tmp/f2.txt
https://tiswww.case.edu/php/chet/bash/bashref.html
!! refers to the previous command. This is a synonym for ‘!-1’.
:2 refers to the second argument.
So for example :
echo "content" > foo
cp foo bar
cat !!:2
Displays the content of bar.
!!:2 is the second argument of the previous command.
Which one was it in your example?

Backspace deletes prompt in terminal

In the following scenario, my bash prompt looks like this:
Username#Hostname ~ $
If I type any number of characters, and then press the delete button one-too-many times, it will clear the entire line. For example:
Username#Hostname ~ $ ls
then I delete s
Username#Hostname ~ $ l
then I delete l
|
I'm then left with my cursor without any sort of prompt. I can type up commands normally and everything seems to function fine--but why does the prompt disappear? Is there a way to stop this behavior?
If you are using escape sequences to color (or otherwise add content) to your prompt without marking the escapes as "non-printing", bash will lose track of the column, and result in behavior like this.
bash's manual suggests that you put nonprinting characters in your prompt within \[ and \] markers.
For the given example
PS1='\[\033[1;32m\u#\h\[\033[00m \[\033[1;34m\w $\[\033[00m '
a possible improved version would be
PS1='\[\033[1;32m\]\u#\h\[\033[00m\] \[\033[1;34m\]\w $\[\033[00m\] '

Bash shortcut to get parent of last used parameter

In my workflow I do very usually:
cat this/very/long/filename.txt
cd !$
bash: cd: this/very/long/filename.txt: Not a directory
Which is to be expected :(
And now I recover the last command, remove manually the file part, and repeat the cd, which now works. That is too much typing!
It would be sooo nice if there was a bash shortcut like:
cd !§
Which could give me the parent of the last used parameter. I know !§ does not exist, I just wished it did! Is there something which can satisfy this?
Et voilà. History modifiers come to the rescue!
$ cd !!:$:h
which can be abbreviated to
$ cd !$:h
This command takes the last argument of the previous command, and removes the trailing path name.
In more details:
!! expands to the previous command
:$ expands to the last argument of the previous command
:h takes the header; that is, removes the file name (which is the trailing part of the above last argument)
As an aside,
!!:$:t
does exactly the opposite.
For an in-depth discussion, please refer to the Bash documentation.
This shorter version would also work:
cd !$:h
Details:
!$ is synonymous to !!:$ which presents the last argument of the previous command.
: separates the modifier from the event-word designator.
h is the modifier that removes the trailing file name component.
Building on the answer presented by several other people,
the ideal workflow is to type cd !$:h after the cat this/very/long/filename.txt command. 
This answer, cd !$:h, will still work after cd !$, but,  once you’ve made that mistake,
you can use the even shorter !:h, which repeats the last command (cd this/very/long/filename.txt),
but taking only the head of the last word.
I'm using these little-known shorthands:
!word_designator is equivalent to !!:word_designator
for non-numeric word_designators (e.g., !$ is equivalent to !!:$) and
!:modifier is equivalent to !!:modifier
(e.g., !:h is equivalent to !!:h).
In this case, I usually do: (I have emacs key binding)
cd Alt-dotAlt-b(n times)Ctrl-K
better:
cdAlt-dotALT-Backspace(n times)
note alt-b or ALT-Bs could be pressed multiple times, till you are satisfied with the path.
cat this/very/long/filename.txt
cd !!^:h
cd this/very/long
!! refers to the last command
^ first argument, second word(word designators)
:h remove trailing pathname component, leaving the head(modifers).
Instead of navigating through the history, you can also consider the use of the $_ variable to refer to the last argument of the previous command (see _ under the Special Parameters section of bash(1)).
To further strip the filename component ("everything after, and including, the last slash"), use parameter expansion (Remove matching suffix pattern):
$ cat this/very/long/filename.txt
$ cd ${_%/*}
The last command will be cd this/very/long since /filename.txt got stripped. A difference with history expansion is that this command (cd ${_%/*}) gets added to the history.

How do I prevent commands from showing up in Bash history?

Sometimes, when I run commands like rm -rf XYZ, I don't want this to be recorded in Bash history, because I might accidentally run the same command again by reverse-i-search. Is there a good way to prevent this from happening?
If you've set the HISTCONTROL environment variable to ignoreboth (which is usually set by default), commands with a leading space character will not be stored in the history (as well as duplicates).
For example:
$ HISTCONTROL=ignoreboth
$ echo test1
$ echo test2
$ history | tail -n2
1015 echo test1
1016 history | tail -n2
Here is what man bash says:
HISTCONTROL
A colon-separated list of values controlling how commands are saved on the history list. If the list of values includes ignorespace, lines which begin with a space character are not saved in the history list. A value of ignoredups causes lines matching the previous history entry to not be saved. A value of ignoreboth is shorthand for ignorespace and ignoredups. A value of erasedups causes all previous lines matching the current line to be removed from the history list before that line is saved. Any value not in the above list is ignored. If HISTCONTROL is unset, or does not include a valid value, all lines read by the shell parser are saved on the history list, subject to the value of HISTIGNORE. The second and subsequent lines of a multi-line compound command are not tested, and are added to the history regardless of the value of HISTCONTROL.
See also:
Why is bash not storing commands that start with spaces? at unix SE
Why does bash have a HISTCONTROL=ignorespace option? at unix SE
In your .bashrc/.bash_profile/wherever you want, put export HISTIGNORE=' *'. Then just begin any command you want to ignore with one space.
$ ls # goes in history
$ ls # does not
Even better use HISTIGNORE. This allows you to specify a set of patterns to be ignored (such as rm). It is better (I think) than just piping all history to /dev/null.
kill -9 $$
I know that is not as best as the previous answers, but this will kill the current Bash shell without saving anything, useful when HISTCONTROL is not set by default, you forgot to set it, or pure and simple you forgot to put a leading space and you just typed in some passwords and don't want them to remain permanently in history.
This is the quick way, and something like erasing the history file is not as good because you need to do it outside a history saving shell (log in as different user and use su/sudo, creating a background job, etc.)
You can do one of two things:
export HISTFILE=/dev/null
Or, begin the command with a space.
Or
unset HISTFILE
(similar to the previous answer only shorter: export HISTFILE=/dev/null)
I added an "Incognito" functionality to my .bashrc for when I want to run some commands without being saved without having to add spaces before each one.
Do note though that the in-memory history of the current terminal session will still be saved, but when I open a new terminal the commands issued in a past terminal's incognito session will never be seen because they were never written to the HISTFILE.
To your .bashrc:
ignoreHistory="false"
DEFAULT_HISTFILE=~/.bash_history
HISTFILE="$DEFAULT_HISTFILE"
# Toggle incognito mode
incognito() {
if [[ "$ignoreHistory" == "true" ]]; then
echo -e "\e[33mExited incognito mode\e[39m"
ignoreHistory="false"
HISTFILE="$DEFAULT_HISTFILE"
else
echo -e "\e[33mEntered incognito mode\e[39m"
ignoreHistory="true"
HISTFILE=/dev/null
fi
}
Nice little utility I think some people may find use in, you can even change the prompt to reflect whether you're in incognito mode or not.
At shell startup, I explicitly cleanup the history from the entries that I don't want to be there. For example, I don't want any rm -rf in the history (it's trauma after removing a directory full of results processed overnight, just with a single Arrow-Up + Enter :)
I put the following snippet in my init file (works with .zshrc, should also work with .bashrc)
# ...
HISTFILE=~/.zshhistory
# ...
# remove dangerous entries from the shell history
temp_histfile="/tmp/$$.temp_histfile"
grep -v -P '^rm .*-rf' $HISTFILE > $temp_histfile
mv $temp_histfile $HISTFILE

Can I have a shell alias evaluate a history substitution command?

I'm trying to write an alias for cd !!:1, which takes the 2nd word of the previous command, and changes to the directory of that name. For instance, if I type
rails new_project
cd !!:1
the second line will cd into the "new_project" directory.
Since !!:1 is awkward to type (even though it's short, it requires three SHIFTed keys, on opposite sides of of the keyboard, and then an unSHIFTed version of the key that was typed twice SHIFTed), I want to just type something like
cd-
but since the !!:1 is evaluated on the command line, I (OBVIOUSLY) can't just do
alias cd-=!!:1
or I'd be saving an alias that contained "new_project" hard-coded into it. So I tried
alias cd-='!!:1'
The problem with this is that the !!:1 is NEVER evaluated, and I get a message that no directory named !!:1 exists. How can I make an alias where the history substitution is evaluated AT THE TIME I ISSUE THE ALIAS COMMAND, not when I define the alias, and not never?
(I've tried this in both bash and zsh, and get the same results in both.)
For bash:
alias cd-='cd $(history -p !!:1)'
Another way to accomplish the same thing:
For the last argument:
cd Alt-.
or
cd Esc .
For the first argument:
cd Alt-Ctrl-y
or
cd Esc Ctrl-y
For zsh:
alias cd-='cd ${${(z)$(fc -l -1)}[3]}'
How this works:
$(fc -l -1) is evaluated. fc -l {start} [{end}] means «list history commands from {start} till {end} or last if {end} is not present».
${(z)...} must split ... into an array just like the shell does (see «Parameter Expansion Flags» in man zshexpn), but in fact it splits on blanks. Maybe it is only my bug.
${...[3]} takes third value from the array. First value is a number of a command, second is command and third and later are arguments.

Resources