178 comments

[ 4.0 ms ] story [ 251 ms ] thread
I upvoted for this alone:

  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.
I hava a mkcd alias to create a directory and changing into it.
I have one called `enter`.

    enter () 
    { 
        mkdir -p "$1";
        cd "$1"
    }
Typing Esc then . does the trick too.
I didn't know this. Really neat!
How does that work?
M-. is readline's yank-last-arg binding.
Nice.

Bash is more than just bash. Composition is a killer technique.

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.

Cool one. "Ctrl + R" also does the same thing to search through past commands and then "Tab" to select the command you are looking for.
May favorite bash "trick" is:

!-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.

And if you only have a single instance to replace, you can use ^old^new^

$ echo "hello hello hello world" hello hello hello world $ ^hello^bonjour^ echo "bonjour hello hello world" bonjour hello hello world

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.

This is great for removing an option from a prior command such as unzip -t ...:

^-t

TIL! Oh this one is nice... now just to get my fingers to remember it.
[ESC + .] and [ALT + .] are most definitely news ones for me. Thank you. Always learn something new here!
Thanks, I discovered C-o thanks to you (even though I have gone through the bash manual several times).

No, more having to press C-r multiple times just to get sequential group of commands from history.

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...)

>However I use it far less frequently than ALT-.

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.

or man bash /Readline Command Names<Enter>
Only in emacs editing mode.

Real programmers use set -o vi

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.

That's great set of tips BTW.

Is !$ the same as $_ ?
Looks like !$ is a readline feature and $_ is a shell variable?
I don't think !$ is properly understood as a readline feature. It's a sh/bash abbreviation.

!! -> last whole command

!!:1 -> first argument of last command

!!:$ -> last argument of last command

!$ -> abbreviation for above

For this exact use case I defined the following many years ago in my bash profile:

    # create a new directory and enter it
    function mkd() {
        mkdir -p "$@" && cd "$_";
    }
And use it like: mkd foo
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)

YMMV.

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 still have to read it up from time to time even though I know it already. The difference is huge.
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.

Thanks a lot for pointing this out. I will happily fix this now!
(comment deleted)
I have an "mkcd" function in my startup files:

mkcd() { mkdir -vp "$1" && cd "$1"; }

Yep, first thing I add to my dotfiles when I don't have it haha
Wait, now I'm confused. I have:

mkcd() { mkdir -p "$@" && cd "$_"; }

Can someone please detail the differences between $! and $_? (Apologies if I'm missing something obvious)

  $ echo "test" > /dev/null && echo !$
  /dev/null
  $ echo "test" > /dev/null && echo $_
  test
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.

(comment deleted)

     alias tcd='a=`date +%s`;mkdir "$a" && cd "$a"'
Yes, that's a good one! My way of remembering is "bang for my buck" -> !$
escpae-dot (.) also works for this: "It also works to use !$ instead of escape-dot, but that is slightly harder to type, so I don’t use that anymore."

Wrote about it here: https://henrikwarne.com/2018/08/11/my-favorite-command-line-...

Which you can type as ALT-.
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."

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...

Also take a look at M-. (Meta+dot) which does the same thing while interactively editing a command...
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".

I vote for creating a « mkcd » alias !!

  # create a directory in the current directory and cd to it

  function mkcd() { mkdir -p "$@" && eval cd "\"\$$#\""; }
Why the eval? This works fine for me:

  mkcd(){ mkdir --parents -- "$@" && cd -- "$@"; }
Here's my answer to that, an 'nd' (newdir?) alias that calls this python script:

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).

I use `z` as a less manual way to search common directories instead of changing $CDPATH. It's based on 'frecency'.

https://github.com/rupa/z

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 .

https://github.com/lloeki/dotfiles/blob/master/shell/kd

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`.

https://github.com/wting/autojump

https://github.com/gokcehan/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.

https://github.com/0mp/goat

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.
Same, I don't know how people do without them.

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.

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.

If I were to alias cd ../../ et al I'd probably go for .. 2.. 3.. 4.. or something
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.

Edit: ESC followed by backspace seems to work.

> 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.

https://github.com/junegunn/fzf also has a “cd mode”, by default triggerable using Alt-C, which can be useful.
Binding Ctrl-R to `fzf` for fuzzy history search is a killer feature.
Yeah this is one of the first things I install on a new machine along with zoxide
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.
Those bunch of dots aliases are a bit ridiculous. What are you gonna do, sit and count dots?
Also made mostly redundant by shopt autocd
I keep at least 3 in my aliases. So I go up up to 3 levels, and if I need to go further I type it in again.
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
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.

Its the little things.

'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 believe that a lot the stuff you like from oh-my-zsh is in fish natively. You might check it out sometime.
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.

No need for stacks of `..`. Use this instead:

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

Nice little helper function, thank you. What do you think about renaming it to '..'? For me this feels a bit more natural:

  function ..() { for i in $(seq 1 $1); do cd ..; done }

  ..    # goes up 1 dir
  .. 2  # goes up 2 dirs 
  .. 42 # goes up 42 dirs
Wow I’m just blown away by this.

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:)

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
Thanks for sharing this I’ll try this out
I'm surprised autocd was not mentioned. I saw

  alias ..='cd ..'
  # etc ...
But not.

  shopt -s autocd
zsh also has this. This seems like the fastest way to navigate since you no longer need to type, or retype, cd.
Hell yeah, I've been missing this functionality since the Amiga shell days! I should have known it was in there somewhere.
I just read the title and thought "Switch to Zsh".

Bash is so amazingly clunky in comparison.

Fish’s predictive suggestions are also greatly for quick navigation
The author missed to mention one of my favorite tricks to change back in to the last visited directory:

  cd -
Hint: Change into directory called '-' can be done by

  cd ./-
I also use the mkcd alias / function:

  mkcd() { mkdir -p "$@" && cd !$; }
This allows creating multiple dirs and cd into the first one.

  mkcd test1 test2 test3
cd - is there, it’s one of my favs too
I don't think this is documented anywhere that I've found, but

   git checkout -
checks out the previous branch you were on, similar to cd -
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.
Speaking of extra characters, I'm pretty far in with the `git` commands. `git checkout` -> `g c`, `git status` -> `g s`.

I usually do `g c -` but I guess `g back` wouldn't be bad.

I use jump commands:

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.

> Maybe less well-known is that ~ is an alias for one’s home directory.

And ~mylogin is another alias for ones home directory.

And more generally, ~otherdev is an alias for otherdev's home directory.

This is accomplished with the zsh hash table I believe. `hash -d myproject=~/projects/myproject` will set up the same thing for ~/projects/myproject

$ cd ~myproject

$ pwd

~/projects/myproject

Here is one additional bookmark solution [1]:

    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.

[1]: http://karolis.koncevicius.lt/posts/fast_navigation_in_the_c...

Pretty sure this would change into the symlinks, not the linked directories. I.e. the $PWD would be under `~/.marks/`.
Personally - never noticed a problem, but should be easy to solve just in case:

    alias cd="cd -P"
I have seen many solutions like this but this one is by far the most lightweighted. Thank you for sharing.
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.
(comment deleted)
I haven’t tested this exact scenario but I like doing something like this when needed

    mkdir -p path/to/{one,two,three}/done
To create:

* path/to/one/done

* path/to/two/done

* path/to/three/done

Particularly helpful for quickly appending something to a file name

    mv something{,.old}
Shell alternation globs do ranges too, so be careful :)

  echo -e {A..Z}{a..z}{0..9}/{A..Z}{a..z}{0..9}"\n"|wc -l
  45697601
Runs for a bit too.
I have a few functions in my bashrc for fast directory navigation.

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...

(comment deleted)
Would it possible to alias the escape codes make by the up arrow as 'cd ..'
It is possible. Just source that.

   bind '"\e[A":"cd ..\n"'
But would you really want that rather showing previous command?
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/...

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.

https://fishshell.com/

> 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.

Probably you are smarter then the people who created fish :)
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.