Another neat short hand is !$ which is an alias for the last argument of the previous command. This can be handy if one creates a new directory and wants to change into it without typing the the directory again. So commands would be
mkdir -p make/new/directory
cd !$
You've no idea how many years I've been thinking "This is Linux fer gawd's sake. Surely there must be some easy way to make a new directory and 'cd' to it in a single command!" But was never able to find or guess the right incantation.
Alt + . does the same thing as well, with the added advantage that you can keep pressing alt and then hit . several times to go through all the "last arguments" in your history one by one.
Further, holding Alt followed by numeric argument followed by dot, gives you an argument at a specific position. For example, Alt + 1, Alt + . copies the first argument.
which runs the last command with all instances of hello replaced by world. !-1 can be any so called event designator, for instance !n to refer to any specific line number from history or !string for last command containing a particular string.
Seriously people, read the man page for bash. Your shell has so many cool features.
I know that this expansion exists, but never use it in practice.
Why? The last command is more general, you can just drop the g from the command I wrote to only replace the first occurrence. Besides, :[g]s/old/new should already be familiar to anyone who has used vim, so that is almost like having to learn zero new things.
Yes I was also impressed when I found out about this one. However I use it far less frequently than ALT-. . ALT-# is also useful in the frequent cases where I am constructing a complicated commmand but I have to run another command before the current one.
Also keep in mind that most interactive command-line tools are built upon readline and can understand all those bindings (think python REPL, mysql/psql...)
I use that on test machines a lot and had put it in bashrc and heard some cussing from other devs so now I just have to type it every time I spend more than acouple seconds in a terminal :) .
I got stuck with exactly that use case today and trying to figure out how I can enter directly to my dir without typing it again. Bash is no so neat at completions like Fish for example. However !$ does the thing and keeps my fingers healthy.
Beware that using "$@" there might not be what you want, as it passes space-separated arguments to mkdir.
What does: "mkd foo bar" do? It creates both directories, and cd's into "bar".
Might be worth checking that only one argument has been passed, or use "$*" which instead would treat all arguments as one parameter, and "mkd foo bar" would create a "foo bar" directory instead (and cd to it)
All Bash manuals and tutorials should start from explaining this issue. And I really mean it. I can't remember how many times I was bitten by it until I finally read it up.
I feel one should also install shellcheck, and follow it religiously while looking up every warning they get - to understand what it's about, why it's a problem, and why the solution / "correct" incantation is so.
It's also a good way to "learn by doing", which works for many.
You're thinking of $_ and !$, and I think they are equivalent - $_ is explicitly the last argument of the previous command, whereas !$ is a history expansion command. ! starts a new history expansion command, and $ is a word designator for the last word of the selected history entry. To quote man bash, " If a word designator is supplied without an event specification, the previous command is used as the event."
I would not that !$ is a bash-ism, whereas $_ works at least on zsh and ksh as well as bash.
This is the `yank-last-arg` readline comment. Bound to M-. and M-_ by default.
"Insert last argument to the previous command (the last word of the previous history entry). With a numeric argument, behave exactly like yank-nth-arg. Successive calls to yank-last-arg move back through the history list, inserting the last word (or the word specified by the argument to the first call) of each line in turn."
Its sibling is `yank-nth-arg`, bound to M-C-y. So if you want the 2nd argument from the last command, you can press M-1 followed by M-C-y (yeah, arguments are indexed from 0)...
I know this is a bash thread but zsh/oh-my-zsh will replace !$ with the actual argument when you type a space so you can see and edit it before executing the command.
bash supports the same, also named magic-space. For bash you can chuck something like "Space: magic-space" in your $INPUTRC, although you'd probably want a guard(see Readline Conditional Constructs in the manpage).
FWIW, zsh without oh-my-zsh can be configured to do too this with "bindkey ' ' magic-space".
This makes all directories with names provided by args, including spaces and multiple levels (slashes) at once. It then enters the resulting directory as well (when aliased including 'cd' as advised in the comment).
After trying such history-based automatic tools I found they lacked predictability for me.
So the very small 50 LOC kd was born:
kd # jumps to project top level (based on .git/Gemfile)
kd foobar "$PWD" # saves an entry named foobar pointing to $PWD
kd foo # jumps to foobar
cp qux $(kd foo) # expands to foobar's value
I should probably make it expand to absolute paths so that I could change "$PWD" to .
g . # add current dir to bookmarks
g # list bookmarks
g del # open bookmarks in vi (should use $EDITOR ...)
g <regex> # jump to first matching bookmark
Wonder how many monthdays I saved compared to colleagues still navigating manually :D
I use `autojump` (aliased as `j`), which sounds quite similar as it also records your most frequently visited directories. It's as simple as entering `j down` to enter /some/path/to/MyDownloadFolder.
For exploring massive file trees, a terminal file manager that allows you to preview the contents of subdirectories without entering them is huge time saver. I use `lf`.
I use (and develop) `goat`. It's like a shortcut manager for aliases for directories you go to often. And a way to make "cd ...." work for any number of dots.
zsh, oh-my-zsh and z are the must haves that I add to any new environment immediately. Z and similar tools is such an elegant solution to directory navigation.
Good selection of tips, like Normille I didn't know $! even though I have read Bash's man page through more than once.
Note that many of these are not just for Bash. pushd/popd is near universal among shells and CDPATH is standard among SYSV shells.
The idea of a shortcut for cd .. and its repetitions is good, but I'd prefer alias p='cd ..', pp='cd ../..' and so on, since I balk at the idea of having to distinguish, e.g., ..... and ...., but I find pppp and ppp are easier to discriminate by eyeball.
The shells I use most often are bash and the Emacs eshell, and over the years I appreciate eshell more and more. It has a few footguns, but the availability of the various selection-narrowing libraries, dired and tramp are huge pluses. zsh can pretty much do anything bash can do and more, and it's default on Macos and Arch Linux.
I'm an old-timer so was introduced to Unix before bash was ubiquitous. Actually it was csh/tcsh which later introduced me to "interactive" command history/editing. With interactive command history/editing, the old tricks to avoid excessive typing became less essential.
But I still use some of the old techniques as they continue to save time/typing - particular "$!". And even though I've reduced my reliance on aliases, I'm so used to jumping between directories that "pd" (pushd) and "nd" (pushd +1) are some of the few I continue to find myself installing.
The other shell techniques (strictly speaking editing features) that I was surprised to find are generally less used these days are the shortcuts like CTRL-w to delete the word to the left of the cursor or CTRL-a, CTRL-e to navigate to the start and end of the current line.
Is there a way to tell CTRL-w to break words at / as well as space? I often use it to remove a parameter from a command I've copy-pasted or got from the history, but for the cd use case it removes too much.
> I'm an old-timer so was introduced to Unix before bash was ubiquitous.
Me too, my first contact with UNIX was with SunOS 4 in 1988, which had csh as its default shell. I don't think I switched to bash until I started administering a Linux box in 1999, although I did script in POSIX shells before then. Before I had a system I was in control of, my attitude to bash was that it was unportable, because of the major differences between its v1 and v2 syntax.
In my opinion this replaces (and even does a much better job i.e. less typing in the end, less commands to remember) almost everything in the article, especially when combined with https://github.com/rupa/z/. (or the PS counterpart ZLocation). Might be the type of work of course, but ever since I started using this I can go by weeks without needing to manually type cd. After all most of my time is spent in the same directories anyway. And if not, I'm really not manually going to type it, not even tab it, if it can be fuzzy matched since that is nearly always faster. tldr; people do yourself a favor and stop cd'ing around.
I have a simple pair of functions which I use to go up a level based on typing the portion of the path I want to go up until:
# When called as `argu string`, prints the closest ancestor directory which
# contains "string". If none is found, or "string" is empty, the current
# working directory is printed. This function is useful if you need to specify
# a file as an argument, but the relative path to it involves moving several
# directories up from the current directory. For example, instead of doing:
# $ pwd
# /this/is/a/very/long/directory/path/for/demonstration/purposes/
# $ ls ../../../../another/directory/something.log
# You could do:
# $ ls `argu dir`/another/directory/something.log
argu() {
if [[ "$1" == "" ]]; then
echo "`pwd`/"
return
fi
echo "`pwd | sed "s/^\(.*"$1"[^/]*\)\/.*$/\1/g"`/"
}
# When called as `cdu string`, changes directories into the closest ancestor
# directory of the current directory which contains "string". If none is found,
# or "string" is empty, no changes are made to the current directory.
cdu() {
cd `argu $1`
}
I also put the following in ~/.zsh/completion/_cdu so I can get tab-completion of the portions of the path:
#compdef cdu
_cdu_parents=$(pwd | tr '/' ' ')
_arguments "1:parents:($_cdu_parents)"
Or bash (in ~/.bash/completion/cdu):
# bash completion for cdu
_cdu()
{
local cur prev split=false
COMPREPLY=()
_get_comp_words_by_ref -n : cur prev
_dirs=$( pwd | tr '/' ' ' )
COMPREPLY=( $( compgen -W "$_dirs" -- "$cur" ) )
}
complete -F _cdu cdu
# Local variables:
# mode: shell-script
# sh-basic-offset: 4
# sh-indent-comment: t
# indent-tabs-mode: nil
# End:
# ex: ts=4 sw=4 et filetype=sh
I'm using this bash script that adds a web-browser like history management (going forward with "+" and backward with "-"). It's been used for a couple of decades now.
For me once I moved to zsh and added `oh my zsh` it really became so much easier.
I like the autocomplete, the fact that when cd-ing a known path you can just type the first leter of each directory with slashes, showing the git branch in working dir.
'oh my zsh' is fun, but they really throw the kitchen sink in there. I had to get rid of it because terminal startup speed and doing anything in large folders was incredibly slow
If all the components of 'oh my zsh' were compiled into the shell and shared data structures, I bet it would be as fast as bash and only limited by IO
I independently also have a stack of '...', '....' etc aliases almost exactly the same as this! Extremely useful I reckon!
Another thing that I've found surprisingly invaluable is an alias (i went for 'ccd') that figures out (via python, so a bit slower on the 1st run) what part of the argument is a valid directory and then cd's you there. It's very handy indeed when working with file paths all the time (a film-vfx-on-Linux use-case) otherwise you end up using backspace to delete the file part constantly! In fact, about that: hitting Esc-Backspace in sequence, should delete back to the next word separator instead of one char at a time.
Really cool trick to navigate directories fast I’m definitely going to add this to my .zshrc
Honestly commandline has made so many things easier for me that I don’t even use regular apps besides my browser for browsing web anyhow using commandline has really made me productive
And I always look for automating or making the workflow better
For Zsh, there are better stuff available. For example, you can use `cd +<tab>` to change to a past directory. And instead of aliases for `.....`, you can use:
# Expand ... to ../..
function vbe-expand-dot-to-parent-directory-path() {
case $LBUFFER in
(./..|* ./..) LBUFFER+='.' ;; # In Go: "go list ./..."
(..|*[ /=]..) LBUFFER+='/..' ;;
(*) LBUFFER+='.' ;;
esac
}
zle -N vbe-expand-dot-to-parent-directory-path
bindkey "." vbe-expand-dot-to-parent-directory-path
bindkey -M isearch "." self-insert
Big fan of git-aliasing this to `git back`. I actually use it a ton and that small amount of extra chars plus the "-" makes a pretty decent difference for me.
export MARKPATH=$HOME/.marks
function jump {
cd -P $MARKPATH/$1 2>/dev/null || echo "No such mark: $1"
}
function ju {
cd -P $MARKPATH/$1 2>/dev/null || echo "No such mark: $1"
}
function mark {
mkdir -p $MARKPATH; ln -s $(pwd) $MARKPATH/$1
}
function unmark {
rm -i $MARKPATH/$1
}
function marks {
ls -l $MARKPATH | sed 's/ / /g' | cut -d' ' -f9- | sed 's/ -/\t-/g' && echo
}
export CDPATH=.:~/.marks/
function mark {
ln -sr "$(pwd)" ~/.marks/"$1"
}
mark @name # create a bookmark
cd @name # jump to bookmark
cd @<tab> # list bookmarks
cd @n<tab> # auto-complete
cd @name/<tab> # can access sub-directories within bookmarks
Works best when bookmarks are prefixed by "@" or other special symbol. Additional advantage is that you can use the same function `cd` to navigate both bookmarked and regular directories.
Might be worth pointing out the author of the post you linked settled in the end on `cd` and `ls` in a different style of organization over bookmarks or `z` or any other tool.
Cool, and duh about the previous command. I supposed some key that's not used for anything on the command line would be needed. Keypad up arrow instead of regular up arrow. Or a single ESC?
These sorts of things are really useful and it's one of the reasons I think everyone should keep their dotfiles on github or similar so they can share their little tricks. Mine are just a compilation of stuff like this that I've picked up from other people over the years. (I even try to put attributions in there if I got them from friends)
My favorite one is c to change to my ~/code directory with tab completion from anywhere.
So I can type c pr<tab><enter> to cd in to ~/code/project from anywhere.
(Full disclosure This is zsh and I don't have a bash equivalent for that)
Another thing I can't live without and some people hate is ls -la anytime I cd in to a directory. In a gui, I get that visual feedback immediately and it's usually the first thing I type when I land anyway so I just have a function for it https://github.com/gpspake/dotfiles/blob/master/zsh/gpspake/...
There're only few directories I need to change to daily. So when I have to visit them, I need to type only `z first_two_characters_of_the_directory_name`
Or you just use a shell like fish that already comes with a lot of useful defaults. No need to customize everything by yourself, just use what other smart people already created.
> No need to customize everything by yourself, just use what other smart people already created.
Every once and then I decide to use and love the fish shell, and I commit to using it exclusively for a few days. My first session always starts with 40 minutes trying to disable all stupid colors. I don't like my terminal to look like a kitsch Christmas tree! Then, I feel frustrated with "jumps" of the interface when editing multi-line command lines; inevitably leading me to quit fish and go back to bash. I'm obviously not smart enough to appreciate the defaults that these smart people have created.
Yeah, to echo -- this probably works for people new to the whole thing, but like many here, it's easier to train myself incrementally with individual features added to bash than to try to "start over."
I used bash for maybe 7 years before trying fish. I was productive in bash, and never really had any complaints. But fish made life so much easier once I got used to it and learned how to customize it to my liking.
It get critiqued a lot, but I'd suggest others try it out before dismissing it and see if it works for them.
178 comments
[ 4.0 ms ] story [ 251 ms ] threadBash is more than just bash. Composition is a killer technique.
Further, holding Alt followed by numeric argument followed by dot, gives you an argument at a specific position. For example, Alt + 1, Alt + . copies the first argument.
!-1:gs/hello/world
which runs the last command with all instances of hello replaced by world. !-1 can be any so called event designator, for instance !n to refer to any specific line number from history or !string for last command containing a particular string.
Seriously people, read the man page for bash. Your shell has so many cool features.
$ echo "hello hello hello world" hello hello hello world $ ^hello^bonjour^ echo "bonjour hello hello world" bonjour hello hello world
Why? The last command is more general, you can just drop the g from the command I wrote to only replace the first occurrence. Besides, :[g]s/old/new should already be familiar to anyone who has used vim, so that is almost like having to learn zero new things.
^-t
No, more having to press C-r multiple times just to get sequential group of commands from history.
Also keep in mind that most interactive command-line tools are built upon readline and can understand all those bindings (think python REPL, mysql/psql...)
Haha of course. I use it so frequently I can't imagine how anyone uses their shell without it.
>can understand all those bindings
I knew that but just being reminded helped me realize that C-o is probably going to be useful in the REPL as well. Thanks.
Real programmers use set -o vi
That's great set of tips BTW.
¹ https://github.com/ohmyzsh/ohmyzsh/blob/master/lib/functions...
And !$ is documented here: https://www.gnu.org/savannah-checkouts/gnu/bash/manual/bash....
!! -> last whole command
!!:1 -> first argument of last command
!!:$ -> last argument of last command
!$ -> abbreviation for above
What does: "mkd foo bar" do? It creates both directories, and cd's into "bar".
Might be worth checking that only one argument has been passed, or use "$*" which instead would treat all arguments as one parameter, and "mkd foo bar" would create a "foo bar" directory instead (and cd to it)
YMMV.
It's also a good way to "learn by doing", which works for many.
mkcd() { mkdir -vp "$1" && cd "$1"; }
mkcd() { mkdir -p "$@" && cd "$_"; }
Can someone please detail the differences between $! and $_? (Apologies if I'm missing something obvious)
I would not that !$ is a bash-ism, whereas $_ works at least on zsh and ksh as well as bash.
Wrote about it here: https://henrikwarne.com/2018/08/11/my-favorite-command-line-...
"Insert last argument to the previous command (the last word of the previous history entry). With a numeric argument, behave exactly like yank-nth-arg. Successive calls to yank-last-arg move back through the history list, inserting the last word (or the word specified by the argument to the first call) of each line in turn."
https://tiswww.case.edu/php/chet/readline/readline.html#IDX9...
Its sibling is `yank-nth-arg`, bound to M-C-y. So if you want the 2nd argument from the last command, you can press M-1 followed by M-C-y (yeah, arguments are indexed from 0)...
https://tiswww.case.edu/php/chet/readline/readline.html#IDX9...
FWIW, zsh without oh-my-zsh can be configured to do too this with "bindkey ' ' magic-space".
https://github.com/danwills/shell-scripts/blob/bb989985d270c...
This makes all directories with names provided by args, including spaces and multiple levels (slashes) at once. It then enters the resulting directory as well (when aliased including 'cd' as advised in the comment).
https://github.com/rupa/z
So the very small 50 LOC kd was born:
I should probably make it expand to absolute paths so that I could change "$PWD" to .https://github.com/lloeki/dotfiles/blob/master/shell/kd
https://gist.github.com/pcdv/3c52d82f59fe9042793033d5e249b14...
Wonder how many monthdays I saved compared to colleagues still navigating manually :DFor exploring massive file trees, a terminal file manager that allows you to preview the contents of subdirectories without entering them is huge time saver. I use `lf`.
https://github.com/wting/autojump
https://github.com/gokcehan/lf
https://github.com/huyng/bashmarks
https://github.com/0mp/goat
I love Z but I cannot believe there isn't something better yet?
One thing I'd love is something that helps navigating the current git project easier.
Note that many of these are not just for Bash. pushd/popd is near universal among shells and CDPATH is standard among SYSV shells.
The idea of a shortcut for cd .. and its repetitions is good, but I'd prefer alias p='cd ..', pp='cd ../..' and so on, since I balk at the idea of having to distinguish, e.g., ..... and ...., but I find pppp and ppp are easier to discriminate by eyeball.
The shells I use most often are bash and the Emacs eshell, and over the years I appreciate eshell more and more. It has a few footguns, but the availability of the various selection-narrowing libraries, dired and tramp are huge pluses. zsh can pretty much do anything bash can do and more, and it's default on Macos and Arch Linux.
But I still use some of the old techniques as they continue to save time/typing - particular "$!". And even though I've reduced my reliance on aliases, I'm so used to jumping between directories that "pd" (pushd) and "nd" (pushd +1) are some of the few I continue to find myself installing.
The other shell techniques (strictly speaking editing features) that I was surprised to find are generally less used these days are the shortcuts like CTRL-w to delete the word to the left of the cursor or CTRL-a, CTRL-e to navigate to the start and end of the current line.
Edit: ESC followed by backspace seems to work.
https://jblevins.org/log/kbd
Me too, my first contact with UNIX was with SunOS 4 in 1988, which had csh as its default shell. I don't think I switched to bash until I started administering a Linux box in 1999, although I did script in POSIX shells before then. Before I had a system I was in control of, my attitude to bash was that it was unportable, because of the major differences between its v1 and v2 syntax.
https://gist.github.com/euske/4f06a6e060131949085c493bd28a0c...
edit: url
I like the autocomplete, the fact that when cd-ing a known path you can just type the first leter of each directory with slashes, showing the git branch in working dir.
Its the little things.
If all the components of 'oh my zsh' were compiled into the shell and shared data structures, I bet it would be as fast as bash and only limited by IO
Another thing that I've found surprisingly invaluable is an alias (i went for 'ccd') that figures out (via python, so a bit slower on the 1st run) what part of the argument is a valid directory and then cd's you there. It's very handy indeed when working with file paths all the time (a film-vfx-on-Linux use-case) otherwise you end up using backspace to delete the file part constantly! In fact, about that: hitting Esc-Backspace in sequence, should delete back to the next word separator instead of one char at a time.
function up() { for i in $(seq 1 $1); do cd ..; done }
# usage:
up # goes up 1 directory
up 2 # goes up 2 directories
up 42 # goes up 42 directories
https://github.com/clvv/fasd
Really cool trick to navigate directories fast I’m definitely going to add this to my .zshrc
Honestly commandline has made so many things easier for me that I don’t even use regular apps besides my browser for browsing web anyhow using commandline has really made me productive
And I always look for automating or making the workflow better
Anyways great post thanks for sharing:)
https://github.com/huyng/bashmarks
It also autocompletes bookmarks.Bash is so amazingly clunky in comparison.
I usually do `g c -` but I guess `g back` wouldn't be bad.
export MARKPATH=$HOME/.marks function jump { cd -P $MARKPATH/$1 2>/dev/null || echo "No such mark: $1" } function ju { cd -P $MARKPATH/$1 2>/dev/null || echo "No such mark: $1" } function mark { mkdir -p $MARKPATH; ln -s $(pwd) $MARKPATH/$1 } function unmark { rm -i $MARKPATH/$1 } function marks { ls -l $MARKPATH | sed 's/ / /g' | cut -d' ' -f9- | sed 's/ -/\t-/g' && echo }
I think the original source is #http://jeroenjanssens.com/2013/08/16/quickly-navigate-your-f...
Another neat trick is to set you .inputrc to vi mode with
set editing-mode vi set keymap vi
and then put this in your .bashrc
bind -m vi-insert -x '"\C-h"':'"pushd . 1>/dev/null 2>/dev/null && cd ../ && echo -e ${LILA} $PWD && ls"' bind -m vi-insert -x '"\C-n"':'"popd 1>/dev/null 2>/dev/null && echo -e ${LILA} $PWD && ls"' bind -m vi-insert -x '"\C-b":"cd $HOME && echo -e ${LILA} $PWD && ls"'
This enables you to go a directory up using ctrl-h and go back using ctrl-n.
And ~mylogin is another alias for ones home directory.
And more generally, ~otherdev is an alias for otherdev's home directory.
$ cd ~myproject
$ pwd
~/projects/myproject
[1]: http://karolis.koncevicius.lt/posts/fast_navigation_in_the_c...
* path/to/one/done
* path/to/two/done
* path/to/three/done
up <#> - cd .. # times
upto <dir> - find dir in the current path above you and cd to it
down <dir> - find dir in the current tree below you including inside any number of subdirectories and cd to it
https://github.com/robmccoll/dotfiles/blob/master/bashrc#L15...
My favorite one is c to change to my ~/code directory with tab completion from anywhere. So I can type c pr<tab><enter> to cd in to ~/code/project from anywhere. (Full disclosure This is zsh and I don't have a bash equivalent for that)
Another thing I can't live without and some people hate is ls -la anytime I cd in to a directory. In a gui, I get that visual feedback immediately and it's usually the first thing I type when I land anyway so I just have a function for it https://github.com/gpspake/dotfiles/blob/master/zsh/gpspake/...
There're only few directories I need to change to daily. So when I have to visit them, I need to type only `z first_two_characters_of_the_directory_name`
https://fishshell.com/
Every once and then I decide to use and love the fish shell, and I commit to using it exclusively for a few days. My first session always starts with 40 minutes trying to disable all stupid colors. I don't like my terminal to look like a kitsch Christmas tree! Then, I feel frustrated with "jumps" of the interface when editing multi-line command lines; inevitably leading me to quit fish and go back to bash. I'm obviously not smart enough to appreciate the defaults that these smart people have created.
It get critiqued a lot, but I'd suggest others try it out before dismissing it and see if it works for them.