Hacker News new | past | comments | ask | show | jobs | submit login
Mastering Bash and Terminal (blockloop.io)
638 points by blockloop on Jan 14, 2017 | hide | past | web | favorite | 180 comments

Rather than temporarily suspend vim to use the terminal you can get vim to suspend and resume itself with the exclamation mark command. This has the benefit of not wreaking havoc on your vim session and allowing you to read data into vim by prefixing with r.

For example

will execute ls and show you the result (press enter to return)

will read the result of ls in for you

More usefully

    :r!sed -n5,10p that/other/file
will read lines 5-10 from that other file.

However you will most often want to

    :!up build
    :!git status
    :!git commit -am "Fixed #23"

My personal favourite trick with ":r!" is, in a new buffer, running

    :0r! grep -rn $pattern $path_to_directory
or any other command that outputs lines containing "$path_to_file:$line_no" (eg most linters).

With that output read into a buffer, placing the cursor anywhere in the "path:line" part and hitting ^wF will open that file in a new split window, and jump the cursor straight to the indicated line - or you can do ^wgF to do the same in a new tab page.

It's a very handy way to work through a big list of work items spread across many files. You do need to coax the right format for it out of your tools, but fortunately "path:line" is pretty de-facto standard.

(Incidentally, I do ":0r" so it inserts the read-in lines at the very top of the file - just ":r" in an empty buffer leads to a leading blank line, which I find irrationally annoying.)

I know that vim has the built in :grep command that does what you describe. The results are listed in the preview window and pressing enter will open the corresponding file and place the cursor at the desired line.

Well, yes... okay, maybe that was a poor choice of example, :grep is probably more ergonomic for that particular use case.

The trick I described is more for when you're dealing with some unusual program that wouldn't work with :grep - at least not without fiddling with 'grepprg' to invoke it, and/or 'grepformat' to correctly interpret its output.

There is also a silver searcher plugin for vim (ag).

With the likes of

one can turn that file/buffer directly into a quickfix list, where simply enter will take one to the relevant position. Messages with column positions are recognized in quickfix lists (:help errorformat).

For any commands or pipelines, you can use:

    :cex system('git grep -n foo | grep bar') | copen
then :cn and :cp to step through the list easily.

I didn't know about :r in combination with !. That's neat. I typically use :r for reading a file into the current buffer (instead of cp <file> <new>). Thanks for sharing.

I don't find myself using ! commands when I know there are multiple commands that I need to run (like your last example). That's usually when I'll suspend and then use the command line.

Could you elaborate on "wreak havoc on your vim session?" I wasn't aware that suspending the process and bringing it back into the fg would harm anything.

> I didn't know about :r in combination with !

You can even use :w in combination with !. For example

    :w !xclip
to write the vim buffer contents to the clipboard. You could visually highlight part of the buffer and do the same thing for just the text you want.

This is also useful for iterating on a throw-away script - something too long to type on one line in the shell, but not significant enough to be worth saving to a file. Put it together in an unnamed buffer, and ":w !" it to the relevant interpreter (eg ":w !perl") whenever you want to run it.

Though I understand that this is just an example of the power of `!r`, readers should know that VIM in fact can copy to the system clipboards natively.


Only if it's compiled with clipboard support. I don't know about in other places, but in the default Arch package it's not. So this command is a perfect solution for me.

You need to install `gvim` which provides `vim` with that feature.

That requires that vim be compiled with the xterm-clipboard feature enabled. On my Fedora 24 desktop, vim doesn't have it enabled, but gvim does (according to the output of the :version command).

One advantage of the method you describe is that it can be limited to just part of a line. Piping output to xclip is limited to complete lines of text due to the nature of :w !.

for osx peeps: `:w !pbcopy`

> That's usually when I'll suspend and then use the command line.

That's when I run !bash, enter my list of commands, then exit.

If one wants something that isn't limited to one shell, and invokes the interactive shell that the user has chosen, there is of course


Yes, good ones. Running ! commands can also be done in command mode, without using last line mode (those who don't know the difference can see https://gumroad.com/products/vi_quick ). I use variations of commands like this a lot, as needed:

1G (to go to top of file if not already there)

!Gsort (to filter all the lines in the file through the sort command, and replace the file contents with the sorted lines)

which says: filter all the lines would be traversed over as a result of running the G command (which goes to the last line in the file) through the sort command, and use the output of the sort to replace all the lines that were filtered;

sort can be replaced with any other command that is a filter, e.g. fmt, grep, sed, many more;

(note: no need of colon in the above command, just have to be in command mode, not input mode, which you get into by pressing Esc).

Very powerful, just that feature, particularly in programming.

You actually don't have to put ! In front of make. Vim has makefile integration. If all you're doing is typing make, you can do :make. For a custom build command, you can set the variable makeprg, and :make will execute that instead. On my phone so it's hard to type examples, this blog post gives more info:


not only that but it also interprets the errors and goes to the correct line

have a look at :cw[indow]

Regarding the last git command you listed, I actually find it better to do

    :r !git status -v
And at the to of the result, type my formatted git commit message, visually highlight the message I just typed, and then use the following vim command

    :r !git commit -F -
Which reads the commit message from standard input.

With tpope's fugitive.vim this is just :Gstatus and :Gcommit

That's interesting, why not just do `git commit` at the command line?

It's more of a way to be able to commit without having to exit or suspend vim. I typically type the commit message while checking the diff. If I find an error in the diff, I'll switch to the vim window with the file, correct the issue and run :!git add %, delete the original output of the git status -v command I ran earlier and I re-run that command (which I can pull up by using q: to bring up the vim command history window).

Vim users are breaking out to the shell to use git?

Try emacs with magit. It's really great.

It's easier to embrace the Unix philosophy when your text editor doesn't take a half hour to start.

It has a daemon mode, so now you know. And from a flat start emacs takes less than 5 seconds on my pc which isn't that beefy.

I was replying with a glib remark because the suggestion to use emacs instead was stupid to begin with.

Vim users should check it out, even if only for inspiration for a clone.

You can also use :sh which just starts a new subshell in the terminal and puts you back in vim when you exit it.

Didn't know that! That's great.

tmux is a good alternative to the whole paradigm of 'running shell commands within a text editor like vim'. Why run a shell command within vim when one can run the shell command in an actual shell? It seems to me that running a shell command in vim is using vim outside of its intended scope. It might be able to do it but it won't be able to do it well, which is where something like tmux comes in. I might be in the minority with this viewpoint though.

(pedantic note: When you run shell commands in vim, they are running in an actual shell.)

vim can be thought of as a visual and interactive helper for your text-processing utilities. The "intended scope" of vim is text editing, and while you're editing you may want to run commands on the text in your buffer, or just parts of it, and have the results apply immediately without writing and reloading the file, or exiting.

For example, in vim, you may be writing a list somewhere in your document, and you realize you want it sorted alphabetically. You can visually select the range of lines ('<,'>) that comprise the list and then sort them by calling the sort utility [1]:

And then you can continue adding to the list.

What would your workflow be for the above? Some like this...?

1. write the file you are working on in your EDITOR

2. make a mental note of the absolute line numbers for the list in the document

3. open new terminal

4. call an awk command that uses `sort`, or some other script, passing in your line numbers, and the file name

5. switch back to EDITOR and reload file


[1] Of course vim has its own `sort` command, so you can leave out the '!' in that example to do the same.

vi did not have its own `sort`, so people used the external `sort`. vi also didn't have the visual selection, so people could pick lines using ranges:

i.e. sort from the current line to the line 10 lines down, inclusive

It's interesting to read up on the relationship between ed, sed, ex, and vi. In fact, all 4 of those editors have similar ways of addressing line ranges (1 for the first line, $ for the last line, etc).

One interesting feature many people may not know about is that it's possible to address lines using a regular expression.

For instance, if you had the following text in a vim buffer:

and you wanted to delete the lines between "two" and "four" inclusive, you could run the following command in vim:

You could also do the same thing in sed

    sed '/two/,/four/d' filename

I use vim and tmux too. I use this combination, because I can also use 'watch' and 'tail -f' commands to deploy files or watch logs while I code.

If only tmux loaded my environment and not purify it by design

> :!make

IMO, :make is (usually) better, because it adds errors to the quickfix list, and put your cursor on the first error location.

In all honesty, I've never actually done :!make (or :make) as I nearly always have it wrapped behind my own build scripts

You can set :make to execute your own build script instead of make, and you can also tell vim how to interpret the output. See e.g.:


You can also use the ":terminal" command (":te" for short) in Neovim.

You can also give a range before the ! and it remove the lines in the specified range and pass it to the command.

For example, when reading xml I use xmllint to clean it up:

    :%!xmllint --format -

Interesting. Your use of `git commit -am` resounded with me. I can't stand doing `git add .` ; `git commit -m "message"`; `git push`. I aliased "commit" to "ci" in git-config, and then do `git ci -am "message" && git push`. I'm sure at some point I will add a hook to have the push done automatically (though sometimes I prefer to do it manually, so I will have to make a decision.)

I am an avid emacs user though. I really should check out vim.

As an Emacs user, why not use magit? Staging, committing and pushing would be just a few key presses ('C-x g S c c <write message and C-c C-c> p P' assuming magit bound to 'C-x g'). You also get to view the status and diffs along the way.

FYI :make also works

If you want to use git from within vim, go no further than Fugitive.

God, I really need to learn more of these little tricks. I've been using Vim for a while now, but not taking advantage of so many of it's features.

I personally still prefer to background the process. Rarely is it ever just 1 command you want to run. For example, you want to git commit, push, oops need to rebase, oops don't have all of my files added... etc etc.

For that reason I just jump out with a ctrl+z and when I'm done I'm back in with a fg!

You can also install a plug-in like ConqueTerm to devote an entire in-vim buffer to something like a shell session.

Or you can use tmux (which can be have vim bindings) to run vim in one pane.

Tmux is great. I have another writing in progress on my tmux workflow.

I more often

nowadays rather than


Hmm, I always used :.!ls instead of :r!ls...

Is there a difference?

:.!ls means "filter the current line through ls". Since ls ignores stdin, the effect is that the current line gets replaced by its output. :r!ls just inserts the output, without replacing anything.

Great post & thread, a tl;dr:

  Ctrl-r      search history
              - then Ctrl-r again to show next match
              - then Tab to show all options
  Ctrl-p      previous command or arrow up
  Ctrl-n      next command or arrow down
  export HISTCONTROL=ignoreboth:erasedups
              Add to .bashrc to avoid duplicate entries

  Ctrl-a      to beginning of line
  Ctrl-e      to end of line
  Alt-b       one word back
  Alt-f       one word forward
  Ctrl-k      delete to end of line
  Ctrl-u      delete to beginning of line
  Alt-d       delete to end of word
  Ctrl-w      delete to beginning of word
  Alt-Backspc same

  cd -        change to last dir
  pushd <dir> mark current dir and go to <dir>
  popd        go to marked dir 
  z           fuzzy cd, install from https://github.com/rupa/z
  j           fuzzy cd and more, install via autojump

  Ctrl-z      to background & suspend
  bg          recent background app continue running
  fg          bring recent background app to front
  disown -h   remove recent background app from current tty
  fg %n       bring nth app to front, e.g.: fg %2 for second

  less        better than cat, doesn't flood screen, same keys
  find        find files, e.g. find / -name <filename>

  ag          install via the_silver_searcher, faster grep

  tree        shows dir like a GUI app, install
  !!          last command, e.g. sudo !!
  fish        bash alternative with more sensible defaults

  man bash    read more about bash

There is Ctrl-y for pasting deleted strings, which goes along very well with these commands:

  Ctrl-k      delete to end of line
  Ctrl-u      delete to beginning of line
  Alt-d       delete to end of word
  Ctrl-w      delete to beginning of word
  Alt-Backspc same
Bash keybindings behave like Emacs by default, hence every time you invoke one of these, it will put the string into a buffer, which can be later pasted using Ctrl-y.

This is very useful. For instance, if you remembered that you didn't `mkdir /mnt/disk` while in the middle of the command `mount /dev/sdb /mnt/disk`, you can delete what you have already typed with Ctrl-u; issue `mkdir /mnt/disk`; than you can paste the previous command with Ctrl-y. Very useful and I use it all the time! (Ctrl-u have never tied to my muscle memory, so I usually do Ctrl-a Ctrl-k to move to the beginning of the line and delete what comes next).

Other tips:

  cd            goes to home dir
  Ctrl-]        moves cursor to character (such as vim f)
  Ctrl+Meta+]   moves to character backwards (vim F)
I also put these in my .bashrc for searching history pressing up/down.

  "\e[A": history-search-backward 
  "\e[B": history-search-forward 
This is different of searching with Ctrl-r. When you type part of a command, such as

  mkdir /dev
and press up, it will complete with previous ocurrences of commands starting with `mkdir /dev`. It is faster than Ctrl-r if you already know what to do.

  "\e[A": history-search-backward 
  "\e[B": history-search-forward 
These have to go in `~/.inputrc`, right?

That's right! Sorry for the late reply.

Here are some notes I took about ZSH that may be helpful to someone

* zsh bang

previous commands:

   !!  Previous command
   !-1 Previous command
   !-2 Second previous command
Getting individual arguments:

   !$   Last arg
   !*   All args
   !$:h Last argument, strip one level
   !!:1 First arg
Modifying commandlines

   !ls:$:h             Head
   !ls:$:t             Tail
   !ls:$:r             Rm Suffix
   !ls:$:s/user/dude/  Substitute user by dude
* Filename Modifiers

  :h	head (dirname)
  :t	tail (basename)
  :e	extension
  :r	remove extension

  :l	lowercase conversion
  :u	uppercase conversion

  :p	print command but do not execute
  :q	quote substituted words

* zsh globbing

  **/* 	Picks out all files and directories, recursively.
  ***/*	Ditto, following symlinks
  *	Any string, including the null string.
  ?	Any character.
  [...]	Any of the enclosed characters. Ranges of characters can be specified by
  separating two characters by a -.
  [^...]	Inverse
  [!...]	Inverse
  <x-y>	Any number in the range x to y, inclusive. Either can be omitted.
  ^x	Anything except the pattern x.
  x|y	Either x or y.

  x#	Zero or more occurrences of the pattern x.
  x##	One or more occurrences of the pattern x.
globbing qualifiers

* file type

    (.) 	Regular files
    (/)     Directories
    (@)     Symlinks
    (=)	Sockets
    (p)	Named pipes
    (%)	Device
    (%b)	Block devices
    (%c)	Character devices
* permissions

    (r)     Readable by owner
    (w)     Writable by owner
    (x)     Executables by owner
    (R)     Readable by world
    (W)     Writable by world
    (X)     Executable by world
    (A)	Readable by group
    (I)	Writable by group
    (E)	Executable bu group
alternatively a chmod style syntax is supported:

    ls **/*(.:g-w:)
* Ownership

    (U)		Your own user
    (G)		Your own group
    (u:userid:)	User with userid
    (g:userid:)	group with groupid
example: list files belonging to user www-data with ls -l /(u:www-data:)

Modification and access time

    (m)	Modification time
    (a)	Access time

    (-)	Before
    (+)	After

    (M)	Months
    (w)	Weeks
    (h)	Hours
    (m)	Minutes
    (s)	Seconds
example: list files accessed last month with ls /(.aM-1)

file size

    (L)	File size (defaults to bytes)
    (k)	use kilobytes
    (m)	use megabytes
    (p)	use 512-byte blocks
example: list all files larger than 10 megabytes with ls /(.Lm+10)

dos2unix /~.(gif|png|jpg)(.) # ~ excludes

dos2unix (#i)/~.(gif|png|jpg)(.) # case-insensitive

ls =bob # Lists file anywhere in $PATH

ls /some_file # Lists file under current directory zargs -- /(.) -- ls -l # Ditto

* zsh Iteration

  for i (/home/**/q*) rm $i
  for i in /**/b*(u:miklophone:); do rm $i ; done
  for f in http://zsh.sunsite.dk/Guide/zshguide{,{01..08}}.html; do...
  zargs -- /**/b*(u:miklophone:) -- rm

* Renaming files with Zsh

Numerical prefix:

   $  i=1; for j in *; do mv $j $i.$j; ((i++)); done

   $  i=1; for f in *; do mv $f $(echo $i| awk '{ printf("%03d", $0)}').$f;
   ((i++)); done

   $  integer i=0; for f in *; do mv $f $[i+=1].$f; done
Rename all files from name.mp3 to Name.mp3:

   $  zmv `([a-z])(*).mp3` `${(C)1}$2.mp3`
Capitalize file

   $  zmv '([a-z])(*).pdf' '${(C)1}$2.pdf'
Replaces spaces with underlines

   $  zmv '* *' '$f:gs/ /_'
FOO to foo

   $  for i in *(.); mv $i ${i:l}
Rename pic1.jpg to pic0001.jpg ...

   $  zmv 'pic(*).jpg' 'pic${(1:4::0:)1}.jpg'
   $  zmv '(**/)pic(*).jpg' '$1/pic1:4::0:)2}.jpg'  # recursive
Remove spaces from filenames

   $  for a in ./**/*\ *(Dod); do mv $a ${a:h}/${a:t:gs/ /_}; done
Substitute r for l


   Unless preceded immediately by a g, with no colon between, the
   substitution is done only for the first string that matches l. For
   arrays and filename expansion, this applies to each word of the
   expanded text.

   The left-hand side of substitutions are not regular expressions,
   but character strings.

   Any character can be used as the delimiter in place of '/'. A
   backslash quotes the delimiter character.

   The character '&', in the right-hand-side r, is replaced by the
   text from the left-hand-side l. The '&' can be quoted with a

* zsh if # see also -z below

  if [[ $foo = '' ]]; then
  print The parameter foo is empty
# quote the term to avoid pattern matching (not regexp, btw)

  if [[ biriyana = b* ]]; then
  print Word begins with a b
# Numbers

  if [[ $number -gt 3 ]]; then
  if [[ $number -lt 3 ]]; then
  if (( $number > 3 )); then
  if (( 3 )); then
# Files

  if [[ file1 -nt file2 ]]; then		# newer than
  if [[ file1 -ot file2 ]]; then		# older than
  if [[ file1 -ef file2 ]]; then		# same file
# Variables

  if [[ -z "$var is blah; test fails" ]]; then		# zero length?
  if [[ -n "$var is blah; test passes" ]]; then		# non-zero length?

For all but the simplest renaming tasks, I use "qmv" (from the renameutils[1] package). It loads up the target filenames/paths in a buffer in your $EDITOR, and then you can use the full power of your editor to rename. When you save the buffer, qmv does a sanity check and then does the actual renaming.

Here's a simple example: [2], but it can get arbitrarily complex -- in a much more visual and interactive way than what a shell alone would be capable of.

[1] - http://manpages.ubuntu.com/manpages/wily/en/man1/qmv.1.html

[2] - http://unix.stackexchange.com/questions/1136/batch-renaming-...

Ctrl+l clears screen. This is very useful even if you are inside a REPL like python, ruby, mysql, etc...

The article is nice but a small part of it rubs me the wrong way:

> I know there are some cool newcomers out there like zsh and fish, but after trying others out I always found that some of my utilities were missing or ill-replaced.

First of all bash was first released in 1989 and zsh arrived just 1 year later so zsh is in no way a newcomer.

Secondly zsh is almost strictly a bash superset so I don't know what he was missing (or what he found "ill-replaced").

I wasn't so much commenting on the release date when I said "newcomers." Mostly commenting on my personal experience. I've been using bash for a long time and only recently have I seen zsh get so popular. It's entirely possible that only the people I know have only recently taken an interest in it.

I would have to run zsh again to remember the pieces that I didn't like. I might have been able to configure my way around it, but I do remember things not working as I expected them to.

When I started using Unix in the early '90s, zsh was more popular than bash by a long way. And tcsh was more popular still. You had to be a GPL purist or something to want to use bash. Its success is really down to its status as the Linux default. If you try zsh again, you'll find the mailing list and IRC are full of helpful people if some things aren't as you expect.

Any ideas why tcsh was so popular and bash was considered oddball? A lot of the big old vfx houses I would work for came from Unix, so they still default everyone to tcsh. A lot of their tooling is based around it, so you're kind of required to use tcsh.

I like to come up with one-liners when doing stuff as a challenge and not being able to do a for-loop as a single line drove me nuts (keeping it one line is really useful when you're iterating over a command and using history). I also find subshells very handy in bash.

zsh was quite popular when I first went spelunking in *NIX-land (2005). oh-my-zsh, which was a decent boost to zsh popularity, seems to have been around since 2009 (http://ohmyz.sh/).

zsh has always been held back by the "default browser"-syndrome: Linux and Mac OS both come with "good enough" default shells, so few people actually want to go through the effort of switching.

Especially since there is a bit of a learning curve to becoming more productive with zsh than you already are with bash.

The problem with installing zch on my desktop is that I won't have it on the servers I SSH into. I need to know the proper spells and incantations that will work on a wide variety of distros and versions, many of which I don't personally maintain. Therefore, bash.

Indeed. The same argument applies to other exotic tooling, interesting vim plugins with many dependencies, etc.

It's one reason to keep one's environment relatively standard and boring. Otherwise, one comes to be dependent -- psychologically, at least -- on one's meticulously configured custom environment, and either chafes at its absence elsewhere or spends a great deal of time copying it everywhere.

I must admit guilt to dragging my .vimrc and sometimes the whole of ~/.vim/ into machines that I'll be SSHing into for only a short time.

Me too. :-)

This was the argument that persuaded me to switch from zsh to bash decades ago. But after a while I realized that it really didn't matter whether you used zsh on your own machine. When you ssh in to another machine that has bash on it, it's going to be similar enough that you're not going to feel lost.

Bash and zsh only differ in some of their more advanced features, which I rarely use or miss on machines I ssh in to -- unless I spend most of my time on those remote machines, in which case I'll just install zsh there too. The main thing from zsh that I do miss on bash is advanced globbing, which is more convenient than the find command, but when I'm forced to use bash I'll just use find, and it's really no big deal.

So I encourage you not to let the fact that most machines have bash installed on them deter you from switching to zsh, if you're interested in zsh. I know I regret the years lost that I didn't use zsh myself.

Conversely: I have the Z shell on all of the systems that I SSH into, ranging from OpenBSD to Debian Linux. I even have it on Interix on Windows as well. Many of them also have the Almquist shell and various flavours of the Korn shell available. A few even have the Thompson shell. (-:

Also, when you use a lot of scripts that only work with bash (there are more than you'd expect, despite zsh saying it is bash compatible) then you have the choice of either rewriting these scripts or using bash. Most ppl will choose not to rewrite.

Many scripts are not written to be portable, bash or otherwise. The main reason those scripts rely on bashisms is that bash has long been the default shell in most distro.

IIRC debian provides a checkbashisms to know which scripts will pose problems when ran against /bin/sh

zsh never claimed it was compatible with bash. I'm not sure where you got that impression from. Maybe you're thinking of sh -- the bourne shell -- which zsh is in fact compatible with (as long as you limit your scripts to the bourne shell subset of zsh).

I'm not sure, where I've read it specifically, but it was a feature I was looking fore before switching to zsh about 5 years ago. What I found now is this: https://wiki.archlinux.org/index.php/zsh

Anyways. The core point is that compatibility often means compatible in 90%-99% of the cases. It's really really hard to be 100% compatible, which also requires one to emulate bugs etc.

The web page you link to is mistaken. The zsh command "emulate sh" does not emulate bash. As the name says, it emulates sh, which is the bourne shell -- not bash.

You're probably right about oh-my-zsh. I really only hear people refer to zsh as "oh my zsh" which makes me smile.

The piece you didn't like about zsh is probably the usual: it comes with no preconception of how you want to use it , i.e. with no default settings.

It requires some work to build zsh settings to one's personal preferences but once done you are at home. Alternatively you can use one of the many settings you can find around, it is faster and simpler but now you're living in someone else' home.

On my version of zsh I was not able to do the following which is available on bash:


Well, from what I see here: http://zsh.sourceforge.net/Doc/Release/Zsh-Line-Editor.html#..., there's no predefined function for Emacs mode (I could only find the vi mode one: vi-find-next-char).

On the other hand, if you look at the whole widgets section, I'm pretty sure you could hack it yourself since zsh supports used defined widgets (widgets are basically functions used by the zsh command editor).

I never said that it's easy, just that zsh is basically a bash superset. However that does mean that in some occasions this means that you want a Prius and instead get a Ford Mustang kit that you have to assemble yourself :D

Binding the vi widget in emacs mode works:

    bindkey '^]' vi-find-next-char

Oh, it does? Cool. I didn't think that you could use a widget from one mode in another.

On both version 4.3.12 on Interix and version 5.2 on FreeBSD, "f" followed by a character works perfectly fine in "vi" mode and Control-X Control-F followed by a character, as per the zshzle manual, works perfectly fine in "emacs" mode. What were you doing wrong?

Regarding macOS and bash, it is slightly remiss of the article to not at least mention that the version of bash in macOS is

1. Ancient.

2. Will most likely never be updated by Apple

(Most GNU- and Linux-based systems, and also Windows, on the other hand, continue to use the latest versions.)

I don't use the version that ships with MacOS. You can install the latest version with `brew install bash`. I'm currently using 4.4.5.

Nevertheless, I updated the post to add bash 4 to the assumptions.

Why are you saying that Apple will not update bash? Wouldn't that be in the best interest of its users?

It would indeed be in the best interest of its users, but Apple has apparently decided that it would not be in its own best interest:



Anyway, the message is pretty obvious: Apple won’t ship anything that’s licensed under GPL v3 on OS X. Now, why is that?

There are two big changes in GPL v3. The first is that it explicitly prohibits patent lawsuits against people for actually using the GPL-licensed software you ship. The second is that it carefully prevents TiVoization, locking down hardware so that people can’t actually run the software they want.

So, which of those things are they planning for OS X, eh?

I’m also intrigued to see how far they are prepared to go with this. They already annoyed and inconvenienced a lot of people with the Samba and GCC removal. Having wooed so many developers to the Mac in the last decade, are they really prepared to throw away all that goodwill by shipping obsolete tools and making it a pain in the ass to upgrade them?

Or, it could just switch the default shell to Z shell. First thing I do on a new Mac, is chsh -> zsh. Ships with an up to date (enough) version of that.

It could, but "just" switching shells is definitely an inconvenience to your users as well.

zsh was even the default shell for an OS X release, 10.3 or 4 can't remember at this point.

Nope, before switching to bash in 10.3, tcsh was the OS X default.

Yep looks like you're right, I could have sworn that zsh was after tcsh then bash from there on out. Went looking in my Mac OS X for unix geeks book and it was right on the transition so bad memory no cookie. I'll punish my brain with beer for its transgressions.


Personally, I found the following to be extremely easy and powerful. A no-brainer that should be a bash default really.

  if [ -t 1 ]
    #   search for commands that start off with the same characters already typed
    bind '"\e[A":history-search-backward'
    bind '"\e[B":history-search-forward'
One of my friends also recommended version-controlling your config files and storing them on gitlab, which I'm only sad I didn't do sooner. It's been such a help in keeping my aliases and configs in sync, as I make changes across numerous different machines.

A good starting point for versioning dotfiles is https://dotfiles.github.io/. I like to use Dotbot [1] for managing and installing my dotfiles, but there are plenty of other options listed.

[1] https://github.com/anishathalye/dotbot/

Having a .dotfiles/ repo makes life so much easier. Seriously.

That these, probably the most useful Readline functions by far, are not bound to any keys by default is crazy. To me it makes more sense to bind them to M-p and M-n though.

Yes! This is the first thing I do in a new Linux OS.

Also, the author does not mention hitting tab for autocomplete (or displaying the remaining options that match what has been typed so far).

You can also put those in your ~/.inputrc

I'm sorry, can you explain how to use this?

Just stick that snippet in your .bashrc file

I actually resolved most of that common issues with just bash: :)

Substring history search, so you can use just a substring to look for a argument,command. Binded to ctr+r/s by default. ;)


Changing directories: Last n directories, transparent popd/pushd.


Movements: vim-surround for your cli, so you can do ysiw" o whatever... ;)


Control-n right: So just type the start and control+n to search for the arguments/commands starting with whatever. And the classical up/down to look up for a complete history line:



There're a ton of hidden functionality and customization behind the classical bash instalation. :)

That is amazing, thanks for sharing.

I'm gonna steal some ideas from asyncBash

I'm glad you liked it. :)

I don't understand why the default bash/readline doesn't get something related for substring search or way better autocompletions, it's really not that hard definitely (actually quite easy) and even when that's the reason for a lot of people to choose zsh over bash.

I also assume you're using bash. I know there are some cool newcomers out there like zsh and fish

While there is much useful in this post, I always find comments like this one odd. bash was released back in 1989, zsh was released one year later, in 1990. One year difference in age almost thirty years ago means that you can't really call zsh a newcomer. Maybe he's talking about adoption, though.

I was definitely talking about adoption ;-). See my comment https://news.ycombinator.com/item?id=13400707

I've had a similar thing recently. Somebody asked me about learning "old technologies", like Perl and CFEngine. Perl 5.x was published in 1991, four years after initial version, Python was published in the same year, and Ruby merely 4 years later. CFEngine 3.x, a complete rewrite that carries over very little and only very high level ideas from earlier versions, was released in 2008, three years after Puppet, and only one year before Chef.

Some people just lack the perspective.

bash is an extended clone of Bourne shell, though, and sh dates to 1977.

For changing directories, you can also use something like z (https://github.com/rupa/z) to jump around:

       Tracks your most used directories, based on 'frecency'.

       After  a  short  learning  phase, z will take you to the most 'frecent'
       directory that matches ALL of the regexes given on the command line, in

       For example, z foo bar would match /foo/bar but not /bar/foo.

Also fasd, which has a z-compatible interface and does a little more: https://github.com/clvv/fasd

I've been thinking about wanting something akin to this, I just had no idea it existed, so what I've been doing instead is I've defined short aliases for some of my most frequent directories. I use such an alias which I have named "no" which does "cd /var/www/no.nordstroem.www/pub/htdocs" on my Raspberry Pi where I host my website. For most directories on my laptop and desktop rather than using an alias even though I might have defined one is I first try a reverse search for it in the shell history, for example ^Rklon to get to ~/src/github.com/eriknstr/klondike/. However reverse search will fail whenever I haven't worked with a directory for a while or if I've been doing a lot of things in one directory since I have chosen to leave the length of the shell history at its default.

I would have liked to maybe start using the command you linked but unfortunately it is using the WTFPL so I will have to pass on it. But from the short description you quoted maybe I'll implement a similar tool myself some day. Then again maybe I will just keep doing things the way I'm doing them now because usually most of what I do is I go to a specific directory and do a lot of work there without moving much, typically said directory will be the root of the repository I have for what I'm working on. If I'm working in several project root directories at the same time I'll usually have multiple terminals open.

I've tried bash, zsh, and fish. After trying all three, I'm staying with fish. bash and zsh don't have sensible defaults, and configuring them is tedious. fish works great out of the box, and it's really fast. When I picked up zsh I used oh-my-zsh and later prezto, but it was slow and figuring out what everything all the framework did was complicated.

With fish I have a setup.fish script that defines all my universal exports, for when I setup a new computer. This is for private tokens, like HOMEBREW_GITHUB_API_TOKEN. For aliases and utilities, I wrote a fisherman [0] plugin. It has a functions folder and a fishfile for the few other plugins I use.

[0] https://github.com/fisherman/fisherman

I tried fish and found it had some interesting new features but not worth switching from zsh, good news is others thought the same so I just had to spend 2 minutes and add 3 lines to my zsh configuration to get those fish features. tada !




I use fish too but I'm thinking about switching back to zsh. Mostly due to incompatible software here and there (like nvm) and lack of completions for a lot of cli tools.

asdf [0] is a great alternative to nvm. The best part is that it supports multiple languages and it's fast. I use the plugins for elixir, erlang, nodejs, and ruby.

Many CLI tools are missing completions, but I think it's slowly getting better over time. The pros outweigh the cons for me.

[0] https://github.com/asdf-vm/asdf

[1] https://github.com/fisherman/fisherman

The Bass wrapper script can help with some of the incompatibilities:


I leave my $SHELL set to bash, but have tmux launch fish. That way, any software that tries to start a shell of its own gets the POSIX sh-compatible shell it expects.

In the spirit of sharing: one of my favorite lines in my bashrc is the alias of `rm` to `rm -i`. This prompts for a confirmation. You might not need it but I deleted some important, not-yet-checked-in-git files in the past by accident.

If you want to delete everything and don't want to keep typing yes just do `yes | rm bla`.

No need to do that: just pass -f. The last flag "wins":

  $ rm -fi blah
  remove blah?
  $ rm -if blah
  $ # confirmation suppressed

With GNU rm, you can put the -f at the end, which I do for safety.

  rm -i x y z -f

The one tool that saved me quite possibly a few months of typing paths over my lifetime is autojump: https://github.com/wting/autojump. I can't imaging a terminal workflow without it.

This should be shouted out regulary to anyone using any type of command line until everybody uses it. I cringe everytime I see someone spelling out cd /mega/long/path/to/a/place/i/visit/multiple/times/every/single/day. Ok some of them already know about tab completion, but it just hurts to see that knowing all that's really needed is j d or z d or so.

One that I learned from Mr. Robot: put a space before the command to avoid being registered in the history.

That only works if $HISTCONTROL includes "ignorespace" or "ignoreboth".

> "tool that every developer uses regardless of language, platform, or framework it's the terminal"

I'm not sure I agree with that. If you work in a decent IDE and your vcs is not git then you can do pretty well without a terminal. Especially on Windows.

Worth it just for finding out about `stty -ixon`. I never would have guessed from the `stty` man page description that this option would give me back C-s and C-q to bind to something actually useful.

It is described perfectly, but it does presume the reader knows the meaning of "XON/XOFF flow control".

Whether the reader knows that meaning immediately is a rather good proxy for just how long they have been working and/or playing with computers. Long ago, in a world of RS-232 connected display terminals and/or analog telephone modems, one became very familiar with "XON/XOFF flow control".

bash (and shells in general) are so hacky (in a bad way) that I wouldn't want to waste my time mastering them. Whenever a shell script grows beyond 10 or 20 lines, I try to rewrite it in a "real" programming language. Fancy shell tricks are a code smell to me, and clarity and simplicity are of far greater importance.

For me, as far as shells go it's usually enough to know the basics and be able to look stuff up when debugging other people's shell scripts.

That might be a good reason to not write programs in shell, but it sounds like mastering the interactive features might still be worth the time.

While I understand this sentiment, I think it's a point that needs a bit more thought; it's not a black and white issue.

On my personal machines, I often find places where I want to either clean up my desktop experience or automate some workflow, where I break some shell out. Most of the time, the overhead to drop into an actual programming language is quite heavy, and even though I try to comment my scripts and write them in a maintainable fashion, I only end up editing them maybe a few times a year.

I feel like discouraging shell scripts is part of what's driving us to seek increasingly heavyweight solutions like systemd in our architecture. Sometimes a lightweight environment where we trust the programmer is needed.

I'm about as anti-systemd as you can get, but I also cringe whenever I look at some of those massive, convoluted shell scripts in SysVinit. There is a middle path, with runit[1], which mostly uses short, simple shell scripts for its process initialization and supervision.

Brevity and simplicity are really the keys when it comes to shell scripts. I really don't have a problem with them if they're short and don't try to be too clever. Whenever I've let my shell scripts get long or complicated, I've always regretted it, and always wound up rewriting them in a "real" programming language anyway, and then realizing that I would have been better off rewriting them much earlier, as soon as they'd outgrown the short, simple stage.

There's no shortage of relatively light-weight "real" programming languages like Perl, Python, Ruby, Lua, and Go -- for even moderately sophisticated tasks, all of them would be better choices than shell scripting.

[1] - http://smarden.org/runit/

I love `set -o vi`. It gives you vim motion in the terminal.

Instead of ctrl-z and bg why not just do # <cmd> & to push it to the background.

I don't know if you can bring a process back to the foreground using the

it does work. I just tried it.

#> watch -n 1 dmesg & #> fg -- brought it back

On macOS, instead of

    ip addr show en0 | grep -inet\ | awk '{ print $3 }' | awk -F/ '{print $1}' | pbcopy
you can use:

    ipconfig getifaddr en0
If you wanna stick with ip addr, a more compact command is:

    ip addr show en0 | awk '/inet/{split($2,a,"/"); print a[1]}'

One thing I am struggling with is that when I use several terminal windows then the history is not recorded immediately and history from one window is not available in other windows. Some time ago I was looking up how to solve this but didn't find a solution that would work for me.

This bugs me as well. I use a tiling windows manager, so terminal windows are completely disposable to me, and I'll open and close them at will, often having double digit terminals open across 10 desktops.

My bash history does not reflect my use of multiple terminals.

About bindings, it would have been better to redirect to:


assuming it exists on mac.

About suspend, what's the benefit of suspending vim using C-z? Shouldn't you use a terminal multiplexer instead, or even terminal tabs if you don't want to learn how to use tmux or screen (which I find weird if you already spent time to learn how to use vim but alright)?

I only ever suspend a program when I want it to stop, for instance because it slows other programs and I realize I would rather resume it when I'm not in front of my computer. Even in that case, I often just renice the program instead. Stopping a program just because you want to run some bash commands looks like an anti-pattern to me, but maybe there are better motives I'm not aware of.

I use tmux. The benefit of using suspend is not having to move around multiple windows/tabs. I try to keep those to a minimum (usually one or two). With suspend I can stick with the same tmux window for editing and cli within the same context. It's a personal preference over managing multiple windows.

To stay on the same window I would personally use a split-window depending on the size of the display. I didn't think about the context though, good point!

Rather than trying to remember all those commands for moving around in your command, I recommend turning on vim mode for the command line, and remapping ESC to "jk".

bindkey -v bindkey -M viins 'jk' vi-cmd-mode

Then you can edit your command line the same way you would edit a line in vim.

But come on, the default mode is "emacs" for a reason.

Those Emacs commands you mention for moving around on the line also work in a lot of other text fields, in a lot of other programs.

Notably they don't work in MS Office, but they work in web browsers and practically every other app I have on my Mac.

They work in so many things because they're the default keybindings for the movement commands in readline. So anything using readline gets them for free.

I'm curious about the popularity of Bash vis a vis, say, Csh. Is Bash more popular due to superior features, or is simply because it is the default on quite a few *nix distros (and macOS)?

I used to use zsh way back in the day because it was clearly superior to bash, then decades ago switched to bash because it was everywhere by default and my job involved using lots of computers that only had bash on them, then about 4 or 5 years ago I switched back to zsh because I realized that it really didn't matter that I used zsh on my own machine and bash on others, and zsh was still better than bash.

These days bash seems to have a lot closer feature parity to zsh, and I'd be curious to read an up-to-date comparison of both shells to determine if either is clearly better than the other.

shell is a bit like JavaScript: both languages have a lot of warts, but if you interact with UNIX-like systems / browsers at intermediate or above levels, you must be comfortable with them. Unlike browsers, you have a few choices for shells, and IMHO [t]csh lost because a) Linux "won" over *BSDs, b) POSIX basically standardized on sh, c) [t]csh is a worse scripting language.

Back in uni I used to use tcsh interactively, script in [ba]sh, and program in a "real language" like C, Java, etc., but I wasn't doing anything very complicated with any of them. Once I started working and begin using these tools seriously, I simply couldn't learn and retain all of them. Something had to go, and tcsh was it.

>If there is one tool that every developer uses regardless of language, platform, or framework it's the terminal.

Sounds like the author never heard of Windows development.

> Now that we know we don't need the up and down arrow keys, what about the left and right? Unfortunately, these keys are still needed for single character movements...

What? No. Use Ctrl-f and Ctrl-b. I use this probably more than any other readline shortcut.

Ctrl-h for backspace, Ctrl-d for delete. Half my keyboards don't even have arrow keys and I don't miss them.

> 8. alt-w - delete the word behind of the cursor

He means “ctrl-w”. But since that only works in bash, not in Emacs or other tools with Emacs key bindings, it makes more sense to use (in his terminology) “alt-backspace”. This does the same thing, and works both in the shell and in Emacs-like environments.

Another advantage to using alt-backspace over ctrl-w is that the former has the same semantics as alt-b. ctrl-w considers a word to be anything surrounded by spaces, whereas alt-b and alt-backspace consider a word to be any consecutive string of word characters.

The default key bindings in bash are listed in the readline section of the bash man page (they mostly match the emacs key bindings).

It may be worth noting that C-w also works in Vim insert mode.

So, for me, it works in both shell and editor.

Ah yes. Thank you for the correction.

Another trick is to use "!!" which is the last command launched, especially useful if you forgot a "sudo" in front of your command, just write

  sudo !!
In the same way, "!*" is all the arguments of your previous command, and "!$" only the last one.

Came here to mention the sudo-bang-bang trick, which really has the best name of all the commands I know.

Regarding history, another simple trick is to increase $HISTSIZE and $HISTFILESIZE, for the defaults are woefully tiny for 2017. I set mine to a million. You could have 100 terminals open and they would still consume less RAM than <insert "native" Electron app(s) of your choice>.

Personally I found "Learn Enough Command Line to Be Dangerous" to be really helpful to get started with actually using the command line at all.


It's missing my favourite: CTRL + _ for Undo.

Also there's a big difference between Alt + Backspc and Ctrl + w. The first will delete a word consisting of only alphanumerics, while Ctrl + w also deletes the word, but word can be made of any characters other than space.

> Now that we know we don't need the up and down arrow keys, what about the left and right? Unfortunately, these keys are still needed for single character movements

Don't ctrl-f and ctrl-b work?

I would suggest https://github.com/BurntSushi/ripgrep over ag

When going past something with ctrl+r, use backspace rather than ctrl+s. less has movements similar to vim. In less, hit v to open the file in your editor.


Alt-l converts next word to lowercase

Alt-u converts next work to uppercase

A very usefull command is Alt-m. This command repeat the last digited string in the current line. It only works in ZSH.

Tipp for OSX-Terminal:

Option + Cursor-Left/Cursor-Right to jump words

I never knew of `cd -` before -- very useful!

Kindly help me out on how to learn this tool in Windows10 without using VM?

I recommend to install mc (Midnigth Commander) and bash-completion (if bash is your shell of choice) as first terminal tools.

mc allows to explore system efficiently while not standing in my way, because I can always press ctrl-O and get my shell back.

bash-completion saves time on typing of commands.

Other tools I install often are htop (better ps) and strace.

all of these are my go-to tools. I also edit inputrc to make pgup and pgdn history search - something I picked up from SuSE version 15 years ago, and stuck.

would you mind to share and elaborate a bit. tq.

It would be good to mention that this article is MacOS centric.

I use most of it on both GNU Linux and Mac. I have installed coreutils (GNU bins) and bash v4.4.5 via homebrew on Mac. It's probably safer to say these are more specific to Linux.

Seriously you can get on fi(r)st page of HN with 5% of man bash?

5% of a manual could be hugely valuable, depending on which 5%. I recommend reading the bash man-page end-to-end, though - there's a lot there!

Consider for a moment that it might be better written and explained than man bash.

Which is quite sad, if you think about it.

If one thinks about it, one realizes that there's a difference between tutorials/guides and reference doco, and the world is happy to have both.


  ctrl-r     search in history, ctrl-r again to skip-
  ctrl-p     previous command (instead of arrow up)

Registration is open for Startup School 2019. Classes start July 22nd.

Guidelines | FAQ | Support | API | Security | Lists | Bookmarklet | Legal | Apply to YC | Contact