Zsh Tricks to Blow Your Mind
twilio.com
twilio.com
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.
Or, you know, any REPL available.
Just a little shortcut but REPLs like Python's take a while to spin up, so it's pretty convenient.
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.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.
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.
7. C-a C-k <enter other command> C-y # to park a command
(since it invokes your editor, and you can configure it to be emacs)
> rsync --e«TAB»
Completing file
some-file-with-extra-hyphens
Completing host
a-n-example.local
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.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 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
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.
auto_pushd
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. repeat 10 echo hi
while {sleep 1} echo hi 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.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.
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) 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.
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 $HISTFILEFrom 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.)
[1] https://unix.stackexchange.com/questions/396809/zsh-share-hi...
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.
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.
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. :-) }
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.
$ function c;{echo $[$*]}
$ c 1234./56
22.035714285714285
$ a=33;c a / 3
11This 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).
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.
The feature was stolen from fish it’s 100% worth it to me.
https://www.gnu.org/savannah-checkouts/gnu/bash/manual/bash....
or as recently discussed Comic Mono: https://news.ycombinator.com/item?id=25520510 https://dtinth.github.io/comic-mono-font/
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.
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 :-)
Aliases and macros are fish script though.
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.
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!seems so. recently discussed: https://news.ycombinator.com/item?id=25520510 https://dtinth.github.io/comic-mono-font/
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.
grep -a "${PWD} " ~/.zsh_history_ext | cat | cut -f1 -d"|" | tailThen again, cut would do the same thing anyway.
$ 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 groups1- "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.
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.
lf is also fast but has ranger's look and feel.
It's not exactly the same but close enough for me.
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.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).
Not ZSH specific, but it's also handy to load project-specific environment variables automatically on entering directories: https://direnv.net/.
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.
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.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:
filename=/tmp/foobar.tar.gz
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.
Do you know of a cheatsheet that describes what all the percentage sign means and other things you can do with parameter expansions?
basename -s .png example.png
examplehttps://wiki.bash-hackers.org/syntax/pe
https://tldp.org/LDP/abs/html/parameter-substitution.html
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.
echo $filename:r
is the shorter, Zsh way to strip a file extension.What am I missing from Ksh93?
Compound and active variables are quite convenient in longer scripts. As are namerefs, but I think zsh might have something similar enough.
[1]: https://ohmyz.sh/
A good guide (although a bit old and maybe too thick :) ) is this one: http://zsh.sourceforge.net/Guide/zshguide.pdf
https://zdharma.org/Zsh-100-Commits-Club/Zsh-Native-Scriptin...
Is there something I'm missing, like a good default .zshrc?
Apple really doesn’t like GPL 3.
Whenever the local Italian restaurant opens back up I'll go and try a properly made Hawaiian pizza :)
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.
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.
bindkey '^q' push-line
works perfectly. Day improved!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?
> 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.
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
fpath+=$HOME/.zsh/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.Has that changed?
It's a shame they can't do these things asyncronously or cache them.
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
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.
Instead you can have a 1-letter command to print out a slew of useful info only when you want.
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"
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.
There are a bunch of other search types available, zshzle(1) has all the gory details.
And if you use my (8 year old) PR you can "take" remote URLs, downloading and extracting tarballs and cloning git repos:
https://github.com/ohmyzsh/ohmyzsh/pull/2029
Example:
take https://github.com/torvalds/linux.git
# you're now in the cloned directoryBonus: `zsh-syntax-highlighting` is excellent: https://github.com/zsh-users/zsh-syntax-highlighting
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)?
It sometimes has some minor issues but a very responsive maintainer. Also integrates pretty well with other tools/scripts.
.() { if [ $# -eq 0 ]; then cd ..; else source $\*; fi; }
then 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.I do this too. I think it's good practice; zsh is amazing as an interactive shell, but bash is everywhere.
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.
You caught me, and you're right: sh (not bash) is the portable option.
E.g. ctrl-l and ctrl-r
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.
alias -s git="git clone"Jeez, this is great!
Sometimes it’s well executed (see Realm’s) sometimes it’s not.
My trick is to type:
r
one letter, boom calculator! (and so much more)