Random zsh tips
Tags: zsh
Published: 2022-01-03 20:04:12

---
title: Random zsh tips
published: true
date: 2020-10-24 15:47:22 UTC
tags: zsh
canonical_url: https://hoelz.ro/blog/random-zsh-tips
---

# Random zsh tips

My friend [fREW](https://blog.afoolishmanifesto.com/) and I both use zsh, and we've picked up a number of tricks over the years. We decided to collaborate on a post sharing some assorted tips that can really improve the zsh experience!

# Noclobber

This is a small thing, but it's saved me from many a headache. I (Rob) will often be working on some data, and I'll squirrel away a long-running or expensive computation to a temporary file, like so:

```
$ long-running-or-expensive-command > /tmp/data
```

All's well and good, until I search through history too far, or until I mistype a search that yields the above command line, and I overzealously hit enter - whoops, there goes that data! This is especially frustrating if the data you just clobbered changes over time, and there was a case in the old data that isn't present in the new! Or maybe you saved the data, got on a plane, and wanted to work with it.

Well, if you `set noclobber` in your zshrc, zsh will prevent you from making this mistake - it will refuse to run commands that would destroy a file via shell redirection (something like `curl -Lo my-data.txt http://example.com/data.txt` will still clobber `my-data.txt`). You can override this via the `>|` operator - personally, I prefer to explicitly `rm` the destination file and then re-run the command.

On a side note - wouldn't it be cool if the shell were smart enough to only clobber the destination file in the case of success, or if the shell somehow knew that `long-running-or-expensive-command` could result in different results? Or maybe it would be neat to have a facility to keep a history of temporary files as you work with them!

# Vi Mode

Like noclobber, another little tweak that changed how I use zsh: `bindkey -v` to enable Vi mode! I was skeptical about this until I tried it, but if you're a Vim user, I recommend you at least try it out and see how you like it - I feel like it's made me so much more efficient at editing command lines over the years!

# Widgets & Keybindings

zsh provides a feature called "widgets", which are really just functions that have a special relationship with the line editor. They're used to implement all of the various keybindings - for example, the default keymap assigns (most) keys to the `self-insert` widget, which inserts the character corresponding to the key you just typed into the line editor.

You can view the current keybindings via `bindkey -L` (if you're using vi mode, you might want to check out `bindkey -L -M vicmd` as well to see what widgets are bound in command mode) - this can reveal some interesting key combinations you didn't know about before! There are plenty of widgets that aren't bound to anything by default - you can find a bunch of interesting widgets in `man zshzle` and `man zshcontrib`.

It's also pretty easy to write your own - for example, I will sometimes accidentally type `!4` instead of `!$`, which expands to the fourth entry in my history. This is _never_ what I want, so I wrote a little widget to detect this situation and fix it to expand `!$` instead.

# Tips by fREW

Here are some random, but cool, features that I (fREW) really enjoy:

## `push-line`

I bound the `q` key in the `vicmd` mode to `push-line` like this:

```
bindkey -M vicmd "q" push-line
```

What this does is set aside the current commandline, give you a fresh commandline to modify, and then pop the current line back. I typically use it in situations like this:

```
$ vi some/dir/some/file<ESC>q
$ mkdir -p some/dir/some/
$ vi some/dir/some/file
```

## `AUTO_PUSHD`

In both zsh and bash you can use `pushd some/dir` to change directory into`some/dir`, and then if you use `popd` you'll change directory back to where you came from. If you set the `AUTO_PUSHD` option, `cd` will automatically`pushd` for you. I'll then use `popd` (or my alias for it, the symettrical`mk`) to get back to where I started.

## global aliases

In `zsh` you can have aliases that are allowed anywhere in a commandline. I use `G` and `L` constantly below, and always forget that I've defined `V` even though I do that action manually a lot:

```
alias -g G="| grep"
alias -g L="| $PAGER"
alias -g V="| vim -"
```

Example:

```
$ long-out-is-long G needle # finds lines matching needle
$ curl https://blog.afoolishmanifest.com/ L # runs with pager
```

You can also pass arguments! This works fine:

```
$ ls G -P '^f'
```

# History Events

This tip has been shared a lot over the years, but honestly it's one of my favorites, and I feel like it saves me _so_ much time and typing! Let's say you're on the command line, and you issue the following commands:

```
$ mkdir a-really-long-directory-name
$ cd a-really-long-directory-name
```

It's no fun having to type that long directory name twice, is it? Fortunately, you can use _history expansion_ (see "History Expansion" in `man zshexpn` for more details) to save yourself some time here:

- You can use `!$` to refer to the last argument of the previous command - so that `cd` above could be written as `cd !$`. This is probably my most-used expansion, especially with `cd` - I have even tweaked my `cd` so that if my previous command was a `git clone`, `cd !$` will do the right thing and change directory into the repository I just cloned!
- You can use `!^` to refer to the first argument of the previous command - I sometimes will do something like `mv file.txt dir/` and then `vim !$/!^` to edit the file I just moved!
- `!!` expands to the entire previous command line - you'll often come across this in the idiom `sudo !!` to re-invoke a command with sudo permissions, and I will sometimes do `time !!` to time a command I just ran. Another way I use this is diffing output of commands - I'll do something like `some-command > /tmp/good-results`, run `some-other-command` and eyeball the results, and then do `combine <(!!) not /tmp/good-results` to compare the two!
- `!*` expands to the entire previous command line's arguments - so the same as `!!`, just without the command itself.
- `...:h`, `...:t` allows you to do some path manipulation on the expansion - `:h` removes a trailing path component, `:t` extracts the trailing path component, etc. It comes in handy if you do something like `vim long/path/to/a/file.txt`, and then you want to search the directory `file.txt` is in - it's just an `ack SEARCH_STRING !$:h` away!
- `!!:2` allows you to refer to the second argument of the previous command - you can use any number `n` in `!!:n`, but honestly I only ever find myself really using `!!:2`, because at a point it's easier to copy and paste or something rather than count how many tokens into your command line the argument is.

We hope you found something useful that you're itching to try out here! If you're interested in checking out our zsh configs, here they are:

- [Rob's](https://github.com/hoelzro/zsh-config)
- [fREW's](https://github.com/frioux/dotfiles/tree/main/zsh)

Many thanks to fREW for contributing to this post!