Hacker News new | past | comments | ask | show | jobs | submit login
Zsh Tricks to Blow Your Mind (twilio.com)
348 points by 2pEXgD0fZ5cF 62 days ago | hide | past | favorite | 209 comments

A lot of these can be easily done in Bash, too!

1. `mkcdir () { mkdir -- "$1" && cd -- "$1"; }`

2a. `bind '"\e[A":history-search-backward'; bind '"\e[B":history-search-forward'`

2b. Default behavior for Bash.

3. `shopt -s autocd`

4. `mmv`, `perl-rename`

5. `qalc` from libqalculate: https://github.com/Qalculate/libqalculate

8. Default behavior for Bash.

9. Default behavior for Bash.

This is always my complaint about posts like this. I read them looking for the killer feature to make me consider switching from bash and it's always stuff bash does natively or with a one line alias.

As many others here have noted, terse syntax for things like `repeat` loops or normal `for`/`while` loops or shell var expansion can all be addictive, but some specific things I miss in Bash that are easy in Zsh are:

    1) auto cd to any evar just from its name
       e.g. lb=/usr/local/bin; lb
       ($lb works in bash, but I always forget
       the $.  I know I could do aliases...Yuck.)

    2) auto cd does not seem to use $CDPATH
       this is a real pain since I am used to
       never typing "cd ".

    3) **c instead of **/*c for a recursive glob
       At 2 vs. 4 chars (or 3 vs. 6 keydowns) it
       may not *look* much briefer, but it feels
       much easier to type once you're used to it.

    4) On the fly command line colorization along
       the lines of zsh-syntax-highlighting.
       Spot syntax errors/missing cmds *before*
       you hit <ENTER>.

    5) vared MyVar
       Use the ZLE to easily modify any evar.

    6) Zsh's REPORTTIME
       *automatically* report time/memory of only
       "expensive" programs/pipelines.

    7) Zsh's extended history format that includes
       both the time started as well as *duration*
       of all my commands.

    8) Floating point arithmetic and $[]
       forget all this calculator jazz.  I just
       do `alias ec=echo` and go `ec $[1234./56]`
       or even just : $[1234./56]<TAB>

    9) glob qualifiers (forget `find` or `fd`)
       My main use cases are *(.) *(@) & *(/),
       sometimes with a ^ for "not" in there,
       e.g. *(^/).  The codes are mostly the
       old ls -F codes to be easy to recall.
If anyone knows how to make Bash do any of those, I would appreciate the tip. Otherwise, maybe they count as "killer" features to someone.

Also, I tend to use a global alias "/n" for "/dev/null". I know I could just use $n with n=/dev/null, but I enjoy having it look like a path in / that never actually exists.

> Zsh's extended history format that includes both the time started as well as duration of all my commands.

Woah, how do I enable this? From what I can tell this information is not being stored in $HISTFILE:

  : 1613659860:0;sleep 1
  : 1613659863:0;sleep 3
  : 1613659871:0;exec zsh
  : 1613659880:0;vim $HISTFILE

Not sure why you are getting all zeros in your duration column. Should be setopt ExtendedHistory (Zsh setopts, like Nim identifiers, are "style insensitive").

From the Zsh man page:

EXTENDED_HISTORY <C> Save each command's beginning timestamp (in seconds since the epoch) and the duration (in seconds) to the history file. The format of this prefixed data is: `: <beginning time>:<elapsed seconds>;<command>'.

(EDIT: My best guess explanation is some competing/conflicting setopt, but the Zsh mailing list could almost surely help you. Also, `history -fD` may show the correct answer even if the saved file has all 0s.)

Thank you! How strange. `history -fD` does work, but only for commands in the current zsh session. It seems durations are being stored in memory but written out as 0's. I'll try the mailing list soon :)

Looks like you probably also have SHARE_HISTORY set? [1]

[1] https://unix.stackexchange.com/questions/396809/zsh-share-hi...

In my case, I have INC_APPEND_HISTORY enabled. It appears I get 0 in the duration column because it's writing to the history file before executing the command. I see there's the alternative option INC_APPEND_HISTORY_TIME to append to history after the command finishes in order to include the duration.

It makes sense. It's preferring to add to history immediately so that it's available to other shells without having to wait for the command to finish.

From the manpage:

> INC_APPEND_HISTORY_TIME This option is a variant of INC_APPEND_HISTORY in which, where possible, the history entry is written out to the file after the command is finished, so that the time taken by the command is recorded correctly in the history file in EXTENDED_HISTORY for‐ mat. This means that the history entry will not be available immediately from other instances of the shell that are using the same history file. This option is only useful if INC_APPEND_HISTORY and SHARE_HISTORY are turned off. The three options should be considered mutually exclusive.

Hah. @jolmg & I almost collided to the second. :-) That stackexchange link had an answer with the INC_APPEND_HISTORY_TIME setopt which seems to work without SHARE_HISTORY (I think using his whole stackexchange answer might still result in 0s..at least it did for me when I ran it in a precmd() instead of adding the hook, but it could always be something else varying between his & my setopts).

Read this thread [1] at the Zsh mailing list to the end.

It looks like either share_history or inc_append_history break recording duration in a fundamental way. The TL;DR is that the history line is written out before the command in question completes. I guess the thinking is that inc/shared history with some very long running command is not as useful if you wait until it ends and you have elapsed time data to append that line. I feel like that might be a personal issue - like if you almost only ever run commands 1-90 seconds then you are fine with the delay.

Anyway, this also explains why it is available from the `history -fD` command but not in the file.

[1] https://www.zsh.org/mla/workers/2011/msg01297.html

For your #8 a function might be helpful to remove the $[] characters.

     $ function c;{echo $[$*]}
     $ c 1234./56
     $ a=33;c a / 3

If I did that & something for bash that used awk | python to do FP arithmetic I might be able to evolve to a more "portable habit/muscle memory" even if Bash never learns FP arithmetic. Solid suggestion, @tux1968. Thanks. { EDIT: 1 down, a dozen to go! lol :-) and I'll go with a(){..} for a)rithmetic not c since I use that for clear. }

What's Yuck about using aliases? I use those extensively. Is that a bad practice?

I use aliases, too. But an alias for every possible "bookmarked" directory seems excessive. Like this guy [1] in the current thread, I tend to just say x=`pwd` (no need to always `export`). Agreed there is probably a way to set up a shell function `a() {...}` that establishes an alias (or even a shell function) almost as simply, but another problem that remains is that aliases and variables are separate namespaces. So, if you do it the variable way like Zsh then you don't have to worry about clobbering some existing alias or shell function. You do have to keep these separated namespaces in your head, but the updating of the prompt to the variable name is a pretty good reinforcement cue (at least for me, in practice). Also, the variable lets you use it in composed paths like `$x/bin/foo` while an alias/function would only change your directory.

TL;DR - Yuck = namespace squishing in this application only, but then again one man's "collapse of namespaces being yucky" is another's "yay, I do not have to remember 3..4 namespaces!".

EDIT: for what it's worth, I never intended my 9..11 items to be "in order of importance or persuasiveness". Mostly in order of personally driving me batty when I use Bash and just meant to actually be things Zsh could do that (present)Bash could not, to my knowledge due to @deadbunny's complaint. I do try to keep a side Bash config that is as close as possible to my Zsh config, because it's not always worth my time to compile/install Zsh on a system. But when I do that and start using Bash, I notice all these missing things. { That's why I would genuinely appreciate anyone who knows how to replicate them in Bash saying how. I'm not just "daring the Bash apologists to a challenge" or whatever. :-) }

[1] https://news.ycombinator.com/item?id=26177537

One yuck about aliases, it doesn’t give you autocomplete. See abbr in fish shell for an alternative.

9 goes quite far. My most common uses are downloads/*(oc[1]) for the latest downloaded file (order by time of inode change and return just first result), or downloads/*(c0) for all the files downloaded today (created 0 days ago in downloads directory).

10) =() expansion. It's like <() which both bash and zsh have, but instead of substituting with the path to a pipe, it substitutes with the path to a temporary regular file. Sometimes pipes don't suffice. For example, I can do `viewnior =(maim -s)` to select a region of the screen and see it in an image viewer immediately. `viewnior <(maim -s)` fails because viewnior can't display an image from a pipe.

11) short syntax for control structures. For example, you can do `for x in $(xs); foo $x` without bothering with `do` and `done`.

12) variable expansion in brace expansion. In bash, `x=5; echo {3..$x}` outputs `{3..5}`. zsh outputs `3 4 5`.

13) Command can be just redirection without any executable. `<<< foo` is equivalent to `cat <<< foo`, `> foo.txt` is equivalent to `cat > foo.txt`.

14) Able to convert numbers between any bases. To convert base-5 10 to base-4: `<<< $(( [#4] 5#10 ))` outputs `4#11` which means 11 in base-4.

15) Named directories. I have my project files organized under deep directories like ~/work-for/.../.../.../foo/, and have configured all those as named directories to be able to access them as `~foo`, for instance. Different from using simple variables for this, named directories are compacted when presented by the shell. So in my prompt, I see my current directory is e.g. `~foo/bar` instead of the whole thing from the home dir. Named directories come in 2 forms, static and dynamic. Static named dirs are when you set them up in a hash of name => directory. Dynamic named dirs are when you define a function that expands arbitrary names to paths and compacts arbitrary paths to names.

16) `=foo` expands to the full path of executable foo. So you can, e.g. see the source of `pass` with `less =pass`.

17) Variable expansion syntax, glob qualifiers, and history modifiers can be combined/nested quite nicely. For example, this outputs all the commands available from $PATH: `echo ${~${path/%/\/*(*N:t)}}`. `${~foo}` is to enable glob expansion on the result of foo. `${foo/%/bar}` substitutes the end of the result of foo to "bar" (i.e. it appends it); when foo is an array, it does it for each element. In `/*(*N:t)`, we're adding the slash and star to the paths from `$path`, then the parentheses are glob qualifiers. `*` inside means only match the executables, `N` is to activate NULL_GLOB for the match so that we don't get errors for globs that didn't match anything, `:t` is a history mod used for globs that returns just the "tail" of the result, i.e. the basename. IIRC, bash can't even nest multiple parameter expansions; you need to save each step separately.

18) Suffix aliases can be used to define how to open arbitrary files. `./foo.mp4` opens it up in with `mpv`, because I have `alias -s mp4=mpv`.

19) Global aliases are useful for expanding stuff anywhere. For example, I have `alias -g %LD='~/downloads/*(oc[1])'` so that `%LD` expands to the latest download as part of any argument to a command.

20) Globbing flags. There are various flags for globbing, that enable e.g. backreferences in globs, allow n-number of errors while matching (approximate matching), etc. The only one I've really used though is `i` which enables a glob to be case-insensitive. So `(#i)*.pdf` matches `foo.pdf` as well as `bar.PDF`.

21) Multios. You can use multiple files in individual redirections. For example, `foo > bar | baz` is equivalent to `foo | tee bar | baz`. That accepts globbing and brace expansion too: `foo > *.{c,h}` is the same as `foo | tee *.{c,h}`. Also works for reading: `foo < *.txt` is practically the same as `cat *.txt | foo`. I must admit that I haven't really used this, though.

It's because these posts are late.

Do you know why "bash does [these things] natively"? Because zsh had them 20 years ago when bash was barebones and people were envious and over time bash started copying these features over. Sometimes poorly.

And for the "one line alias", in a bunch of cases it's with additional commands, which are not always available, the most famous example being rename.

Which rename? The binutils one (I think) or the Perl one (which has more features)?

Does your distro even package both?

Things can be a lot more complex than they seem.

I agree. I have been using Zsh since about early 1993. I went through a pretty thorough "first I'll convert to tcsh!" then "to Bash!" then "to Zsh!" evaluation back then and Zsh was absolutely the clear winner.

While Bash has "caught up" some in the ensuing decades by copying features (some poorly as you note), I think it still misses quite a few nice "usability" features. I've listed some here [1] from memory, but I am probably forgetting other pretty nice ones. Like maybe ${(f)"$(mycmd)"} to split the output of `mycmd` strictly by newlines. (or ps:\0: for even stricter splitting).

[1] https://news.ycombinator.com/item?id=26176897

Honestly you probably can do these with bash as well with some tweaking. But on a daily basis these are most used by me

Replaces internally cd with pushd (plus a few more: to ignore dups, be silent about it).

Global aliases (interpolated anywhere in the commandline)

    alias -g G='| grep -i'
    cat file G something

Replace on the fly long directory names with short paths

    /some/long/path $ export X=`pwd`
    ~X $ 
All the tweaks to ignore dups in the history, when saving/searching.

Short form loops are also great. Someone mentioned short for loops already, but I don't think anyone has mentioned these beauties:

    repeat 10 echo hi
    while {sleep 1} echo hi

I'm happy in zsh or bash but the one thing I love in zsh is that completion options for program arguments. Having a list with a snippet of documentation to tab through is a huge benefit.

Bash supports completions as well.

IIRC, bash doesn't support extended completion editor features of ZSH, which GP alluded to - like generating a table of options with help strings that can be selected by arrow keys and the like.

A (slightly contrived) example:

  > rsync --e«TAB»
  Completing file
  Completing host
  Completing option
  --cvs-exclude                      -- auto-ignore files the same way CVS does
  --delete-excluded                  -- also delete excluded files on the receiving side
  --exclude                          -- exclude files matching pattern
  --exclude-from                     -- read exclude patterns from specified file
  --executability                    -- preserve executability
  --ignore-existing                  -- ignore files that already exist on receiving side
  --ignore-non-existing  --existing  -- ignore files that do not exist on receiving side
There are various ways to present the completion; I have the simple "keep typing letters" way, but Zsh can also present a table with an entry to highlight.

If the dizzying assortment of shell parameter expansion features (see man zshexpn), isn't enough to convince you, then the cause is lost.

A "dizzying assortment of features" is not automatically a good thing.

The features need to be able to do useful things better somehow, and that's where articles demoing such applications come in.

"Better" also doesn't necessarily mean "more concise," otherwise APL would be one of the best programming languages. A language with a smaller number of accessible, comprehensible, memorable features can prove more useful in practice than one with a "dizzying assortment" that's hard to remember, difficult to read, etc.

I've used Zsh for 15 years or more, and when I started I read the manual. (I realize this is unthinkable nowadays.)

I noted down the things I thought would be useful day-to-day, and started using them. A few months later, I skimmed through the manual again. I still do so, every year or two. My work gradually changes, and I find Zsh has features that are now useful to me.

This is the suggested manpage: https://linux.die.net/man/1/zshexpn

There are an endless number of programs I could be using. There are an endless number of programs you could be using.

The issue is how to decide whether something is worth looking at.

An article which gives examples that can easily be achieved by some more mainstream system is unconvincing. That's what was being discussed here:

> This is always my complaint about posts like this. I read them looking for the killer feature to make me consider switching from bash and it's always stuff bash does natively or with a one line alias.

> This is the suggested manpage: https://linux.die.net/man/1/zshexpn

Yes, the comment I replied to already suggested that. You're not doing much to convince me that your judgment is worth respecting.

#7 is one of my favorite zsh features though, even though it doesn't seem like that big of a deal if you haven't used it.

defaults matter.

Yes, but on the other hand most proponents of zsh have installed ohmyzsh and a dozen plugins. (Just like a lot of long-time users have a long .bashrc.)

And the default shell on all my machines is bash ;)

    for el in `mycommand`; myothercmd $el
That's my killer feature at least. I usually write my scripts in bash, but for one liners in interactive mode, the simplified syntax is a god send.

While I agree that these are not killer features, I'd suggest reading more about zsh. It's not meant to "replace" bash. It's meant to be easily extensible (addons, etc.) The target is folks that stay from bash scripting.

> 4. `mmv`, `perl-rename`

I didn't have this on my system. I like the article because this was a command I've needed many times without realizing it.

You may also like the renameutils programs that let you freely manipulate file names in your text editor.


7. in bash, I use the: [Esc], [#] combo, equivalent to: [Alt]-[Shift]-[3] (a.k.a.: "M-#" in the official docs).

This is a shortcut which basically prefixes the current line with '#' comment character and then simulates hitting Enter (pushing the commented-out line into history buffer).

And then the inverse operation, uncommenting and running, is "M-3 M-#" (any number will do, but 3 is on the same key as #).

Bash supports at least a subset of emacs motion keys.

7. C-a C-k <enter other command> C-y # to park a command

These are provided by Readline. Many command line programs support Readline, and for those that don’t, rlwrap is like magic (even automatically adds history!).

Well, actually... Using C-x C-e you can use EVERY key emacs offers.

(since it invokes your editor, and you can configure it to be emacs)

> 5. `qalc` from libqalculate: https://github.com/Qalculate/libqalculate

Or, you know, any REPL available.

Or a simple `=() { bc -l <<< "scale=10;$1" }` in the usual bash file and you can use bc to do calculations by starting with a =.

Just a little shortcut but REPLs like Python's take a while to spin up, so it's pretty convenient.

That's a nifty idea!

Maybe without lower scale and with the semicolon:

  = () { bc -l <<< "$*"; }
"Stealing" the equals sign for this purpose feels a bit risky, but maybe it isn't.

I'd guess that xorcist added a space at the end of the function name because zsh uses =( to specify one type of process substitution. Without the space or an escape for = zsh will error on alpaca128's definition believing you wanted something else entirely.

To that point zsh has some excellent additions for process substitution beyond bash's <(list), but it also makes for yet more weird incompatibilities for when that matters. I only looked at this to start with because I assumed the space was because =() would have been treated as the full path modifier for a command called (), because it would mean that with any other character including other bracket types. TIL.

> Just a little shortcut but REPLs like Python's take a while to spin up, so it's pretty convenient.

Really? Loading python takes basically 0 seconds for me... the slowest I could get running `time python3` and hitting ctrl-d (to exit) is like 0m0.104s.

python comes up in way less than a second on my old mac air?

It isn't shown in TFA, but the zcalc keymap is bindable in the line editor so not only can you use it like qalc/irb/luap/whatever you can also take advantage of any other functionality of zsh within it. Admittedly, you could do some of that with some magic $if/$endif guards in your ~/.inputrc if your REPL uses readline, but the integration is nice functionality if you're a heavy zsh user. I think of it more like the expression register in vim or eval-print-expression in emacs.

my favourite is pari/gp, that can be used as a simple calculator, but also has quite fancy math at your fingertips. For example, it is the easiest way to compute entire series: you type "exp(sin(x))" and you get "1+x+1/2*x^2-1/8*x^4-1/15*x^5 ..."

7. alt-# to comment and accept the current line, alt-1 alt-# to uncomment and execute it


Re 1., if you put a -p on the mkdir it becomes a bit more powerful. I've had equivalents of that command going back to DOS batch files on a 286. I've always called it "ad", for "add directory" (though I'll concede that's mainly a good name because it's short).

The one thing I really don’t want to live without is “intellisense” that changes the default input to a prefix-search that ghosts the top result ahead of your typing.

The feature was stolen from fish it’s 100% worth it to me.

Thank goodness, I can close this tab with no fomo.

I'm more amazed how that page is unreadable, starting from typography up to color scheme.

I personally don't think the typography in the page itself is bad, it's the screenshots (and indeed their color schemes and types) and the lack of vertical rhythm of the whole thing that pose the biggest problems.

the kerning in the screenshots is "hilarious"

looks like a version of Comic Sans, possibly http://www.comicneue.com/

or as recently discussed Comic Mono: https://news.ycombinator.com/item?id=25520510 https://dtinth.github.io/comic-mono-font/

Those would be better kerned. This is probably using the default Comic Sans forced to fixed width. Which is predictably bad.

Thanks for pointing it out. Aaaaaargh

I've been using Fish instead and it's much much simpler than configuring ZSH to my liking. It's already good out of the box, without having to carry around any kind of configuration. The only stuff I usually do is to setup `powerline-go` as prompt, and voilà, that's pretty much it.

I rather liked Fish's zero config approach but I found their handling of the PATH very hard to work with day to day. Perhaps it was just my familiarity with having a bunch of statements to alter the PATH in a config file but it felt very opaque, I always had to look up how to add stuff to the PATH and I had great trouble trying to remove stuff from the PATH.

That is a valid issue. Should be remedied in an upcoming release.

Oh wow that's great to hear. Do you have any resources I could reference? A Github issue discussing the intended solution or something?

I LOVE the way fish handles PATH. I just modify the $fish_user_paths variable (which is a universal variable) and I don’t have to modify any config files.

E.g. to permanently add `~/.cargo/bin/` to your PATH just do

    set fish_user_paths $fish_user_paths ~/.cargo/bin
once from an interactive shell, and you’re done!

It's easy once you understand universal global and local variables.

The main drawback for fish is that is not compatible with bash scripting. If you need help with something specific, you can throw out almost all help available online.

I can never understand this complaint. I'm frequently on Windows and use Cygwin.

Do you know what I do?

I use zsh. Let's say I need bash. "bash<Enter>". I'm in bash :-) I need cmd (yup, that can happen). "cmd<Enter>".

I need to run just one command with bash: "bash command<Enter>".

I need to run just one command with cmd: "cmd /c command<Enter>".

These problems are trivially solvable :-)

I just have both installed. Fish for interacting and bash for scripting.

Switched to fish for daily use a year ago and have only had to fallback to bash maybe 3-4 times for a gnarly command invocation that was bash only.

I've been thinking about a more fluid way to hybridize these different shells.

I'd like a way to get all the benefits without the cost or any extra required effort for the user.

Some painless master shell program. No epiphanies yet though.

Same, I use starship[1] as my prompt, it's easy to install and has sane defaults.

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

I am a happy Fish user, too. The first downside that comes to my head is that if you want to share a script you wrote that you have to translate it to bash first. Much less of an issue if you're using zsh.

I'm a zsh user, and I disagree on it not being much of an issue. It is very easy to inadvertently rely on all manner of zsh-specific behaviour in scripts. Short form logic syntax, builtins with different arguments, internal stat/attr/datetime/etc manipulation, my personal favourite $RANDOM behaves differently to bash in subshells, and much more.

zsh does have the emulate builtin available to help with some basic things, but it is still easy to produce scripts littered with zsh'isms.

However, I do use zsh for personal scripts. In fact, I go so heavy on zsh'isms I know I'd need to rewrite things to share them if non-zsh users are an issue. It is easier to keep track of things that way, instead of tripping over the little easily forgotten quirks.

I just write my scripts in bash or python, depending on the use case.

Aliases and macros are fish script though.

Do people... actually use this font in their terminal? I would go absolutely nuts looking at the spacing and color scheme.

> Do people... actually use this font in their terminal?

seems so. recently discussed: https://news.ycombinator.com/item?id=25520510 https://dtinth.github.io/comic-mono-font/


see, the kerning is fine on that though. its not my taste, but it works. This article looks like either something is just wrong with the font, or letterspace needs to be adjusted in the terminal's config.

I wouldn't be surprised if this was coerced by the marketing department, trying to imbue that "ooh, look at me, I'm a bit crazy, so creative" vibe for a bloody shopping list.

I have no comments about this particular font. But if your marketing department has any say in your terminal fonts, you should quit this creepy place as soon as possible!

That would be almost as unlikely as someone using that horrid colorscheme, but they might have a say about the blog layout and content.

I know comic sans is often used by dyslexic people, as they can read it more easily, so I'd expect it's something similar.


I'm not sure why this isn't a part of zsh already, but if you want to see the last few commands you ran in the current directory, that little utility will do that for you. Great for reminding yourself what you did last time you were in a directory.

Wow jog is tiny:

    grep -a "${PWD}   " ~/.zsh_history_ext | cat | cut -f1 -d"|" | tail

Why does it pipe to cat?

Maybe instead of `grep --color=never`? (i.e. to force non-terminal behaviour)

Then again, cut would do the same thing anyway.

When I was exploring projects that did this I found an alternative that spun up a database and was literally thousands of lines of code. This seemed to fit exactly what I was looking for without insane levels of overhead.

mcfly is a nice alternative https://github.com/cantino/mcfly

My favourite feature of zsh (it might be oh-my-zsh), is the tab expansion of unambiguous abbreviated paths. Say if you do:

    $ cd /u/b/l<TAB>
That will expand into /usr/bin/local. If there are ambiguities at any point, it will expand as much as it can and let you fix it at the point where a decision needs to be made (pressing tab again will show you the options, like Bash et al). For example:

    $ /u/b/gr<TAB><TAB>
    $ /usr/bin/gr
    grep  groff  groups

As far as I know, that's not oh-my-zsh, since I can do that and I don't have omz installed.

You're right, all it requires is a call to compinit from base zsh: `autoload -Uz compinit; compinit` And compinstall can be used to configure things like the case-sensitivity or amount of ambiguity accepted in matches using a nice menu system.

Pretty clickbaity title. Nothing "mind blowing" about what's in that article really. I used to use Zsh but switched back to bash a few years ago because most of the things I liked in Zsh turned out to be doable in Bash and I just didn't look enough.

I am using zsh and several of these "tricks" don't work.

1- "take" is not a default built-in

2a- that up-arrow is not default behavior

2b- ok, ctrl-r works as expected (works in bash too)

3- automatic cd is not default behavior

4- ok, zmv is a standard module that is explicitly autoloaded in the article

5- zcalc, like zmv, requires loading a standard module, the article doesn't mention that

6- ok, oh-my-zsh has plugins

7- ok, ctrl-q works as expected

8- ctrl-x-e doesn't work for me (but works in bash)

9- ok, ctrl-l works as expected (works in bash too)

All these are probably specificities of oh-my-zsh, a very popular module that I don't use personally, or require special configuration. Nothing wrong with that, but these should be mentioned.

My company is fond of putting source code under very deep directory structures, so I quite like Z: https://github.com/agkozak/zsh-z

Not ZSH specific, but it's also handy to load project-specific environment variables automatically on entering directories: https://direnv.net/.

I very much agree on z. Also fzf [0]. It is super fast and makes a huge difference when working in code bases with code 12 levels deep.

[0] https://github.com/junegunn/fzf

Using fzf and ctrl+r for fuzzy finding commands is a godsend.

Basically all snippets of code that I ever run are straight from this.

Forget how to exactly reset a committed local file that you want to place into another commit? Not something I do everyday, so may require some Googling if I can't remember exactly.

What I do know, is that I use something with `git reset`.

I ctrl+r and type `git reset` and here is what comes up:

> 9488 git reset HEAD^

> 10535* git reset --hard

> 10555* git reset HEAD file

> 10999* git reset --soft HEAD

Oh right, I need to reset the commits and then remove that file from the commit and I can recommit.

It is a complete gamechanger.

Also recommend the fzf vim extension. I use it to navigate my Vimwiki Zettelkasten.

Direnv has made it a lot easier to source Bash-styled environment variable in a fisd shell and has `use nix` built in for using the Nix package manager for projects and getting a shell environment with all of your local project dependencies.

None of these blew my mind.

If you like terminal productivity I recommend: fzf, Facebook path picker (aka fpp), fd, ripgrep, lf, tig.

Some honorable mentions: tokei, hyperfine, lazydocker, ctop, ncspot.

Solid selection. I'd also add nnn as the fastest tool to navigate directories I've ever seen.

Have you been able to get it to cd on quit? it's the only thing I can't seem to get. It's amazing though.

Hmm, if you mean the C-g behaviour then you need to set $NNN_TMPFILE:

    nnn() {
      declare -x +g NNN_TMPFILE=$(mktemp --tmpdir $0.XXXX)
      trap "rm -f $NNN_TMPFILE" EXIT
      =nnn $@
      [ -s $NNN_TMPFILE ] && source $NNN_TMPFILE
You can use a static file if you're sure you'll never be running more than one instance.

I'd prefer something like:

    nnn() {
      local tmp=$(mktemp --tmpdir $0.XXXX)
      trap "rm -f $tmp" EXIT
      =nnn -p $tmp $@
      [ -s $tmp ] && cd ${"$(< $tmp)":h}
That will cd to the selected file's directory with enter/right, or do nothing if you simply quit. I guess it depends if you use it for browsing a lot or simply picking a file.

Pressing `!` in the target directory will open a new terminal session within nnn, with that path as working dir. When you `exit` you'll land in nnn again.

It's not exactly the same but close enough for me.

Thanks for sharing! These tools actually blew my mind (fpp, nnn) and those are so useful!

nnn is certainly very fast.

lf is also fast but has ranger's look and feel.

Some other tools I use in case you liked the list above: bat, exa, percol, GNU dialog or zenity, xsv, ministat, gnuplot, tshark, mitmproxy.

Plus any project from sharkdp and burntsushi and any tools recommended by Brendan Gregg.

ht editor is a personal favorite too (press F6 and go to image to get started).

I'm getting pretty into fasd as well. It's basically a drop-in replacement for z, but it's got extra goodies that merge in some of fzf as well.

Ugrep instead of ripgrep. Otherwise great selection.

My favorite alias, which is not really a zsh exclusive thing, is combining cd && ls :

    cl() { cd "$@" && ls; }
It's wonderfully simple and saves me so much time by giving me an instant refresher of what's in the directory I just changed to.

Run mc, navigate to dir, press ^O to switch back to shell.

Knew most of those, not much interested in "shell plugins".

One thing that many people miss are parameter expansions. Those are in Posix, so pretty much any bare shell should be able to use that, too. So dash/bash/zsh/ksh…

    echo Hello ${PLANET:-World} 
to have a default if you're not sure a variable is set. Replace "-" with "=" to actually set it, not just use it for this statement.

Or removing suffixes:

    echo ${filename%.*} # -> /tmp/foobar.tar
    echo ${filename%%.*} # -> /tmp/foobar
Saves you some useless sed/awk.

I still wish the Linux shells (bash/zsh) would start to finally get some more features from friggin' ksh93.

  echo $filename:r
is the shorter, Zsh way to strip a file extension.

What am I missing from Ksh93?

Hmm, let me check, I think zsh does have associative arrays and floating point, two features that bash took quite a bit longer to get (and which aren't in the bash in OS X). So at least that's okay.

Compound and active variables are quite convenient in longer scripts. As are namerefs, but I think zsh might have something similar enough.

That's a really neat way of trimming the file extension, something that can end up being quite tedious to do with SED.

Do you know of a cheatsheet that describes what all the percentage sign means and other things you can do with parameter expansions?

The bash hackers wiki has a pretty good list of the bash/posix one, tldp does too, but I find it less straight forward.



The zsh docs are good for the zsh specific expansions, but more man page than cheatsheet, though they do have an intro doc on it that's more cheatsheet like but less complete.



The old way to remove a file extension is using basename:

   basename -s .png example.png

Also strips the path and you have to know the extension name(s). For my examples, "cut" would be appropriate. But those substitutions tend to work in anything POSIX, not just bash/zsh.

zsh4humans is the most recent zsh “trick” I’ve discovered. zsh is a bit like emacs when first installed: it can do a lot of things but it’s not nearly as useful without a lot of manual configuration. I’m honestly surprised it doesn’t have more stars on GitHub.


Is this similar to Oh My Zsh [1]? I personally have been using Oh My Zsh for years and I've always been a fan of it. I can quickly add themes and plugins and have my shell ready to go how I like it quite quickly on new systems.

[1]: https://ohmyz.sh/

zsh4humans is like ohmyzsh but much better IMO because when you first install zsh4humans, it walks you through a setup wizard that walks you through choosing your fonts, prompt style, completion style, etc. and automatically installs the appropriate zsh plugins and things like powerline fonts according to your choices.

Oh wow, that actually sounds much nicer! I wonder if it's worth switching to this or if I may as well stay with ohmyzsh for now and switch to zsh4humans in future installs.

I just recently ditched oh-my-zsh as I wanted to understand and have more control over my shell. I have to say that just using native zsh capabilities I don't miss oh-my-zsh at all.

A good guide (although a bit old and maybe too thick :) ) is this one: http://zsh.sourceforge.net/Guide/zshguide.pdf

If you're looking to go full rabbit hole, the zsh native scripting handbook is good for a few examples. Easy to end up in linenoise if you're not careful though. A fair few really nice zsh things in zdharma's GitHub repos too.


If you’re on 10.15 (Catalina) there’s no need to install zsh, it is the default shell. If you’ve upgraded and are still using bash, just do ‘chsh -s /bin/zsh’ to set the default to zsh.

What I would like is a blog post about making zsh less different than bash. I'll get to the mind-blowing tricks eventually, but for now I want the old tab completion and word stops back. Maybe I should just chsh back to bash, but the macOS copy of bash is really old now.

What was the reason for them doing this do you know? From my experience with it over the last year, zsh is slightly worse at autocomplete than bash, with no advantages.

Is there something I'm missing, like a good default .zshrc?

Completion is one of the biggest reasons people switch to zsh. Did you initialize the completion system in your .zshrc?


I had done that, but my ssh completions weren't working great at all. I've installed ohmyzsh as recommended below and it's great, definitely a step up from bash.

Check out ohmyzsh for a great ~/.zshrc

Awesome, that's pretty amazing, thanks a mill!

Bash licensing (GPL)

To be precise - Apple was stuck on the last GPL 2 version of bash and finally gave up and moved to zsh which doesn’t have the GPL 3 license but is bash compatible and they can keep it updated.

Apple really doesn’t like GPL 3.

Is this OS X? Is zsh the default there?


... Why are all shell snippets in Comic Sans? Is that legal?

I'd also dare to bet the author's favourite pizza is Hawaiian/pineapple

Salty/sweet contrast is the core of Hawaiian pizza appeal, and flavour/texture contrasts are the foundation of some of the most interesting foods, for instance salted caramel, pork and apples, etc.

Pork and pineapple is arguably good; the problem here is the pizza around it, which clashes horribly with pineapple.

This is especially true for tomato, which unless very sweet and acidic like in sticky sauce, it doesn't go well at all with pineapple.

That makes a lot of sense. Admittedly the only Hawaiian pizza I've ever had came from the freezer.

Whenever the local Italian restaurant opens back up I'll go and try a properly made Hawaiian pizza :)

Pineapple, banana pepper, and jalapeño may be my favorite pizza.

Pineapple, mushroom, artichoke, caper.

The author is probably dyslexic.

> Ctrl-q "parks" the command

What is the zsh functionality behind this? I couldn't test it because my terminal eats Ctrl-q and a search for "zsh ctrl-q" didn't turn up anything useful.

Yes please -- this sounds great, I used to use `ctrl-u` then `ctrl-y` with bash, and that doesn't work with zsh, alas. I tried searching for `zsh "park" command` and got nothing useful.

It's a feature of the zsh line editor called "push-line". http://zsh.sourceforge.net/Doc/Release/Zsh-Line-Editor.html

Thank you, I love it. This is so great for a quick ls or cd in between.

Turns out it wasn't the terminal that was eating my Ctrl-q, but the key wasn't bound in my zsh.

The page you linked says push-line (^Q ESC-Q ESC-q) (unbound)(unbound)

Does that mean that in a standard config push-line is bound to Ctrl-q?

From the man page:

> The following is a list of all the standard widgets, and their default bindings in emacs mode, vi command mode and vi insert mode (the `emacs', `vicmd' and `viins' keymaps, respectively).

What that means is that if you issue 'bindkey -e' from a bare .zshrc it will enable the emacs bindings and C-q will work. The other two unbound entries state that it isn't enabled by default if you want to use vi-style editing.

The KEYMAPS section in the man page has a full explanation. The easy way to play with it is to start an extra zsh with "zsh -f" so it ignores your configs, call "bindkey" to see what the default bindings are like, then "bindkey -e; bindkey" to see all the goodies that are enabled in emacs mode for example.

Thanks very much! Adding:

    bindkey '^q' push-line
works perfectly. Day improved!

You can also operate on the stack of pending commands programatically using the read and print builtins along with the common to both -z option. It can be a really nice way to build up or edit complex commands/variables in combination with expansion flags and such. 'print -z goodbye; print -z hello' for the five second example of what happens.

Alternatively, you could use vi mode with 'bindkey -v'. Then you could use 'dd'/'cc' and 'p' to achieve the same thing.

A lot of the tips from TFA can be done in BASH as well and I'm sure the following of my own zsh tips also have a bash equivelant, but I'd like to share anyway:

Pure theme - My absolute favorite shell theme. Has almost all the bells and whistles of the powerlevel9k shown in the article, but looks, much cleaner and less in-your-face.

After downloading the theme, I've added this to my `.zshrc` file:

    # PURE theme - see https://github.com/sindresorhus/pure
    autoload -U promptinit; promptinit
    prompt pure
Fuzzy find makes searching history much more powerful than what is listed in the article. I only use it together with ctr-r, but you can set it up for even more goodness.

    # Fuzzy find - see https://github.com/junegunn/fzf
    [ -f ~/.fzf.zsh ] && source ~/.fzf.zsh
Lastly I've added a syntax highlighter, which is really neat.

      # ZSH Syntax Highlighter - see https://github.com/zsh-users/zsh-syntax-highlighting
      source $HOME/.zsh/zsh-syntax-highlighting/zsh-syntax-highlighting.zsh
I don't use oh-my-zsh, but I think my terminal is as fully fledged as even the best oh-my-zsh can offer.

would like to chime in a faster theme written in rust: https://starship.rs/

Last time I checked it was slower than https://github.com/romkatv/powerlevel10k, because it doesn't decouple the rendering of the prompt from the background processes collecting status information. Ergo no instant prompt. Also, some other cool tricks w.r.t. prompt vs. backscroll.

Has that changed?

Happy fish+starship user here. Recently all modules where refactored to load lazily: https://github.com/starship/starship/issues/2111 A PR adding full async support is close to being merged, which should fix lag issues for good: https://github.com/starship/starship/pull/2223

The power of powerlevel10k is that it can draw the plain prompt immediately, and respond to keyboard immediately, and then backfill any advanced prompt information (and properly shifting the already added text to the right and wrapping it correctly). I know that zsh has support for this, and bash does not (powershell neither); my prime reason why I use zsh. Fish, I don't know how it handles prompts.

This is very powerful in large git repos etc. You're never waiting for the prompt. You just type immediately, w/immediate response. So, you only wait for the status information to show up when you need it. So you also don't pay an excessive penalty if you prefer overly chatty prompts.

AFAICT the lazy-loading and async-support tickets above talk about internal implementation details, timeout support (so cancelling stuff like a too slow status report and showing a simplified prompt). I may misunderstand that, I only gave it a quick glance.

Maybe starship already supports this for zsh, or not, but at least they don't promote it front and center.

It seems fast enough but once you enter an actual directory of code where it needs to get the version of node, kotlin, package, etc it slows down noticeably. I've ended up disabling most packages because it is absurd to (I assume) be running node --version every time a prompt is generated.

It's a shame they can't do these things asyncronously or cache them.

Caching is a bit dangerous, and most shells don't support asynchronous prompt updates. And cross-shell support seems to be a prime feature. Spending probably a lot of effort just for improving zsh experience might just not have been important enough yet.

Yeah, it is tricky. Perhaps if the prompt was generated async?

Turns out my main issue was due to having a slow node --version command due to using asdf!

This project was a huge help: https://github.com/danhper/asdf-exec

That's crazy. People actually write whole programs to print a whole, ugly, slow, kitsch prompt? What happened of the elegant PS1='; ' ?

Tacky indeed - but the main issue it's that it takes precious space for information that you don't need to look again and again after each command.

Instead you can have a 1-letter command to print out a slew of useful info only when you want.

<sigh!> The fatuous use of animated images to illustrate the wonders of a tool that is fundamentally text/character based is (ahem!) puzzling, considering that those most likely to be interested are also those most likely to wish they could do simple Cut/Paste of the examples for experimentation, now difficult or impossible because of all the glitz....

Is oh-my-zsh still dog slow? I've been using zgenom and just picking the plugins I want and it's blazing fast.

(YMMV for all the below...)

Having done this sort of thing for years, I've come to realize that the biggest barrier to getting more efficient on the command line isn't "sets of features," i.e. the space of the things the shell can do -- it's figuring out how to retrain myself?

A common thing: I see a new (to me) thing like fzf or fd and can immediately conceive of how adding it to my workflow would be phenomenal, but it takes so much more to make it a part of the day. I'm curious if anyone has come up with ways to teach/train themselves better? The best I've come up with so far is to just add something stupid to my .bashrc like

echo "hey, try out that fzf trick"

> take

And if you use my (8 year old) PR you can "take" remote URLs, downloading and extracting tarballs and cloning git repos:



    take https://github.com/torvalds/linux.git
    # you're now in the cloned directory

Take, alone, is an amazing unknown to me.

fish shell has a lot of this built-in.

Recently fell in love with fish. Sadly, subshell functionality is still absent, so I use it in interactive mode only.

Funny, like the author I also have a 'desktop not icloud' folder but I just call it 'scratch'

If you opt-in to the iCloud personal folder mirroring feature it will move your Desktop folder into the cloud. This gets kinda troublesome if you are the kind of person who uses your desktop as a scratch space for random crap.

I now have a ~/scratch folder for this. I also added a ~/screenshots folder and used the app Onyx to change where macOS saves screenshots.

Now I get the best of both worlds.

The #2 behavior (up-down arrow cycles through usages of command) doesn't work OOTB on macOS's zsh. Is it a new-ish feature or a plugin that I don't have?

'bindkey "${key[Up]}" up-line-or-search' is probably the incantation you're looking for if you don't want to use oh-my-zsh or whatever. The $key hashmap is generated by zsh's zkbd so that your config can be terminal independent, but you can insert the actual key if you prefer not to use zkbd or only use one terminal type.

There are a bunch of other search types available, zshzle(1) has all the gory details.

You need oh-my-zsh

+1 for the `zsh-autosuggestions`: https://github.com/zsh-users/zsh-autosuggestions

Bonus: `zsh-syntax-highlighting` is excellent: https://github.com/zsh-users/zsh-syntax-highlighting

When I saw comic sans in terminal I knew what's up.

I always liked tcsh for how they did for loops. It's taken me 20 years of bash and I still always get the syntax wrong the first time. I need to figure out how to alias it.

I like the ^R trick. In bash I usually just !(whatever the string is) which only matches the last one or the more infamous !?(whatever the file/obj you were applying the command to)?

The `take` command doesn't seem to have anything to do with vanilla zsh.. I don't have that on my linux system in zsh.

Indeed, this comes from oh-my-zsh (lib/functions.zsh)

Here's a nice tool for sporadic terminal users like myself: https://github.com/marlonrichert/zsh-autocomplete

It sometimes has some minor issues but a very responsive maintainer. Also integrates pretty well with other tools/scripts.

Been using zsh for years and many of these were new to me. I love #3 and use it daily. I find it most helpful to use this trick to type ".." or "..." and quickly navigate back a dir or two. Of course you can do this in bash (and other shells) with aliases, it does not come out of the box like so many niceties in zsh.

I have a favorite alias `u` (stands for "up") to go up a directory. 50% keystroke savings ;)

I have a shell function `.` that goes up one:

    .() { if [ $# -eq 0 ]; then cd ..; else source $\*; fi; }

    alias ..='cd ../..'
    alias ...='cd ../../..'
So that "however many periods" goes up however many directory levels. Seems a coherent shorthand to me, though I guess uuuu also works.

What's the problem with everyone abusing hyperboles like that? No, my mind hasn't been blown by teh fact you can create a directory and enter it, and if I care enough I can reimplement this one and all others in Bash too and keep compatibility with all the machines I work on.

Apple has kind of herded me into zsh for work in the terminal. So far it has been pleasant, with interesting plugins, but it's periodic updates are a little annoying. Despite remaining in zsh while in a terminal, I still find myself writing only bash scripts when a script it needed.

>Despite remaining in zsh while in a terminal, I still find myself writing only bash scripts when a script it needed.

I do this too. I think it's good practice; zsh is amazing as an interactive shell, but bash is everywhere.

Bash is not everywhere, but /bin/sh is..!

If portability is important, I'd recommend Bourne shell instead.

Sometimes there's a justification for writing shell scripts in bash. E.g. if you only care about Linux forever.

Frequently though, a simple script will unexpectedly grow, and start to require non-trivial data structures, etc. If there's no time for a rewrite in a more appropriate language (Python/Ruby/Perl), then moving to bash can be a compromise.

>if you only care about Linux forever.

You caught me, and you're right: sh (not bash) is the portable option.

Several of these are not just common to all shells, but even work in other tools, like emacs,

E.g. ctrl-l and ctrl-r

I can't live without oh my zsh's git aliases: https://github.com/ohmyzsh/ohmyzsh/wiki/Cheatsheet#git

Zsh is the default shell on MacOS. It has been since Catalina. Oh-my-zsh supercharges your zsh setup its great https://github.com/ohmyzsh/ohmyzsh.

Never use sudo with `npm install` as was done in example 6.

I know it's off topic, but it would be good if the author could change that example. In general, one should never use sudo with package managers.

oh-my-zsh tricks to blow your mind seems like a more appropriate title. I tried some of them in zsh, and they didn't work, because they assume you also installed oh-my-zsh.

Knew a few of these, but "7. Park a command" and "8. Easily edit a command after you've typed it on the command line" are great to know!

So this is the HN frontpage now? Thats the same clickbaity title every old blog/news site uses and I try to avoid as good as I can.

My favourite is setting "git" as a suffix alias to clone repo urls I copy paste:

    alias -s git="git clone"

> If you've typed or pasted a long command and decide you need to edit it before running, ctrl-x-e opens it in an editor

Jeez, this is great!

This works in bash too. To see all keyboard shortcuts in bash, type "bind -P". There are lots of neat things in there.

There is always a group of people wanting to "hack" terminals into GUI-targeting, glittering app ;)

Mind not blown.

I just got a summer internship offer from these people. How is it there?

This person uses comic sans for their terminal font!?

some of them aren't really zsh but plugins

did twilio suddenly become medium? why are they posting zsh tricks blogspam

They always had "developer tips" even on their YouTube

This is called Inbound Marketing, which consists of creating actual useful content slightly tangential to your own product/service so to attract audience which will, potentially, convert to customers.

Sometimes it’s well executed (see Realm’s) sometimes it’s not.

In article is Comic Sans?

I am still trying to figure out if I should stick to fish or switch over to zsh.

zcalc... hmm...

My trick is to type:


one letter, boom calculator! (and so much more)

Please, no. Bash (or better, sh) are just fine. They are good enough, and they are standard. I can log in anywhere and do things.

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