index current line in bash - bash

I was curious if there is a way to index the current line in bash as it might save quite a bit of typing.
e.g.
$ command longarg1 longarg2 longarg3 | command2 <something_to_expand_longarg2>
I tried !#2 (!# will give expand the contents of the line so far)
However it did not work as it will expand the whole line and then put 2.
-Thanks

From the bash(1) man page, HISTORY EXPANSION section, Word Designators subsection:
A : separates the event specification from the word designator.
$ command longarg1 longarg2 longarg3 | command2 !#:2

Not indexing as you have asked, and not ideal exactly, but how about:
Using the up / down arrow keys to cycle through history
Selecting the long command with mouse cursor, Ctrl+Shift+C to copy and then Ctrl+Shift+V to paste?
(environment dependent)

Related

Bash prompt with custom PS1 including current git branch gets corrupt when using reverse-i-search etc [duplicate]

I recently added some color to my bash prompt, and now when I type a command the text doesn't wrap properly.
Here is an example:
MacBook-Pro:~ williamgiles$
long commandwilliamgiles$ this is a really long command,this is a really
My original PS1:
\h:\W \u\$
My new one with color:
\e[0;35m\h:\W \u$ \e[m
You need to mark the color-changing sequences as something that does not contribute to the length of the prompt on-screen.
PS1='\[\e[0;35m\]\h:\W \u$ \[\e[m\]'
bash already knows to take the expansion of \h, \W, and \u into account; it has no idea that \e[0;35m is not simply 7 characters that the terminal will display on screen.
Adding to #chepner's answer, I personally use \1 and \2 in my .inputrc
Here's an example:
set vi-ins-mode-string \1\e[38;5;015m\2╰─\1\e[0m\2\1\e[38;2;191;254;33m\2(ins)⫸\1\e[0m\2\1\e[5 q\2\1\e]12;rgb:bf/fe/21\e\\\2
set vi-cmd-mode-string \1\e[38;5;015m\2╰─\1\e[0m\2\1\e[38;2;191;121;33m\2(cmd)⫸\1\e[0m\2\1\e[2 q\2\1\e]12;rgb:bf/79/21\e\\\2
\1 denotes the start of a zero-length section, and \2 denotes the end.
(\001 and \002 also work)
I wrote a blog post about the topic not long ago. You might find it interesting.
What my shell ends up looking like (The top part is possible by using Oh-My-Posh:

Word forward/backward delimiter difference between bash and zsh

Having been dragged into the 21st century by macOS Catalina now defaulting to zsh instead of bash for its shell, I'm trying to restore some of the bash's line editing behavior. Specifically, if I am at the end of the line:
bash$ ls /foo/bar/baz
and I hit Alt-Left Arrow, the cursor jumps to "baz", then "bar", then "foo". It is treating the slash as a word delimiter, as do most environments (such as Safari and apps that use Cocoa text editing features).
In zsh, when I try to do the same thing, the first Alt-Left Arrow jumps the cursor all the way back to the beginning of "/foo/bar/baz", which makes it much less useful for editing parts of a long directly.
Can this be configured in zsh? If so, how?
Characters in zsh variable WORDCHARS are considered part of a word.
You just need to remove / from it
% echo "$WORDCHARS"
*?_-.[]~=/&;!#$%^(){}<>
% WORDCHARS="${WORDCHARS/\//}"
# Now Alt-left-arrow will stop at /
Just be aware that $WORDCHARS is used in other situations as well. See man zshall

How to get second-to-last argument from previous bash command? (in an interactive bash shell)

How can I easily get the second-to-last (penultimate) word/argument from the previous command in a bash interactive shell? I often run commands in the background, and I would like to get the file that was specified before the &, e.g.,
% echo foo > /tmp/foo &
% cat !$
% &
In the example above, !$ gives the last word, &. But I want the second-to-the-last argument, /tmp/foo
Note that it is possible to use word designators with a range like !-1:3, but this is impractical for a command with a large number of words where it's not quickly obvious how many words there are, e.g.,
% (set -x; date; pwd; git status; git diff; git log | tail -30; date; args=--verbose time make test; date) >& /tmp/log/make.test.20150122-Thu-0834 &
% echo !-1:30
/tmp/log/make.test.20150122-Thu-0834
The example above works, but you have to count and know that the word you want is the 30th word, which is time-consuming and error-prone.
Is there an easy way to get the second-to-last (penultimate) word?
UPDATE:
Note that I'm looking for something to type on the command line (e.g., a history expansion, like the !! event designator, or the $ word designator), as opposed to using readline key bindings (e.g., the esc key).
(Note that this question refers to the arguments of a previous command in an interactive shell, and not to arguments passed to a shell script from the command-line, as some answers and comments here are referring to.)
Interactively, you can get the second to last argument of the previous command line with esc - 1 esc . with the default bindings.
More generally, the sequence esc . gets the final token from the previous command line, and you can pass it a numeric argument to specify a different one (for example, esc 1 esc . gets the first argument, and esc 0 esc . gets the command itself).
esc is one of the keybindings for Meta; on many modern keyboards, you can use Alt as Meta as well (you press it at the same time, not as a prefix modifier). I prefer esc as meta because when my muscle memory learned these things, we didn't have no (reliable, consistent) Alt key, and it's still portable all the way to VT100 and Sun keyboards; and at least on my current keyboard (Mac OSX Yosemite) e.g. alt-- does something else than specify a negative numeric argument.
From a previous compound command like this
echo moo; echo bar
the sequence esc 2 esc . gets the semicolon, because that's the second token.
I'm sure there is a way with ! history expansion as well, but I vastly prefer to see what I'm doing. This mechanism brings you the text you want to refer to into your current command line, so you can edit it if you like as well.
Use this to run a command using the 3rd argument of the last command in history:
echo foo > /tmp/foo &
cat !-1:3

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

KornShell (ksh) wraparound

Okay, I am sure this is simple but it is driving me nuts. I recently went to work on a program where I have had to step back in time a bit and use Redhat 9. When I'm typing on the command line from a standard xterm running KornShell (ksh), and I reach the end of the line the screen slides to the right (cutting off the left side of my command) instead of wrapping the text around to a new line. This makes things difficult for me because I can't easily copy and paste from the previous command straight from the command line. I have to look at the history and paste the command from there. In case you are wondering, I do a lot of command-line awk scripts that cause the line to get quite long.
Is there a way to force the command line to wrap instead of shifting visibility to the right side of the command I am typing?
I have poured through man page options with no luck.
I'm running:
XFree86 4.2.99.903(174)
KSH 5.2.14.
Thanks.
Did you do man ksh?
You want to do a set -o multiline.
Excerpt from man ksh:
multiline:
The built-in editors will use multiple lines on the screen for
lines that are longer than the width of the screen. This may not
work for all terminals.
eval $(resize) should do it.
If possible, try to break the command down to multiple lines by adding \
ie:
$ mycommand -a foo \
-f bar \
-c dif
The simple answer is:
$ set -o multiline
ksh earlier than 5.12, like the ksh shipped with NetBSD 6.1, doesn't have this option. You will have to turn off current Interactive Input Line Editing mode, which is usually emacs:
$ set +o emacs
This turns off a lot of featuers altogether, like tab-completion or the use of 'Up-arrow' key to roll back the previous command.
If you decide to get used to emacs mode somehow, remember ^a goes to the begining of the line ("Home" key won't workk) and ^e goes to the end.
I don't know of a way of forcing the shell to wrap, but I would ask why you'd be writing lines that long. With awk scripts, I simply wrap the script in single quotes, and then break the lines where I want. It only gets tricky if you need single quotes in the script -- and diabolical if you need both single and double quotes. Actually, the rule is simple enough: use single quotes to wrap the whole script, and when you want a single quote in the script, write '\''. The first quote terminates the previous single-quoted string; the backslash-single quote yields a single quote; and the last single quote starts a new single quoted string. It really gets hairy if you need to escape those characters for an eval or something similar.
The other question is - why not launch into an editor. Since I'm a die-hard vim nutcase (ok - I've been using vi for over 20 years, so it is easier for me than the alternatives), I have Korn shell set to vi mode (set -o vi), and can do escape-v to launch the editor on whatever I've typed.
This is kind of a pragmatic answer, but when that's an issue for me I usually do something like:
strings ~/.history | grep COMMAND
or
strings ~/.history | tail
(The history file has a little bit of binary data in it, hence 'strings')

Resources