One thing I find life-changing is to remap the up arrow so that it does not iterates through all commands, but only those starting with the characters I have already written. So e.g. I can type tar -, then the up arrow, and get the tar parameters that worked last time.
In zsh this is configured with
bindkey "^[OA" up-line-or-beginning-search # Up
bindkey "^[OB" down-line-or-beginning-search # Down
CTRL + W usually deletes everything until the previous whitespace, so it would delete the whole '/var/log/nginx/' string in OP's example. Alt + backspace usually deletes until it encounters a non-alphanumeric character.
Be careful working CTRL + W into muscle memory though, I've lost count of how many browser tabs I've closed by accident...
He had in his path a script called `\#` that he used to comment out pipe elements like `mycmd1 | \# mycmd2 | mycmd3`. This was how the script was written:
```
#!/bin/sh
cat
```
You're typing a long command, then before running it you remember you have to do some stuff first. Instead of Ctrl-C to cancel it, you push it to history in a disabled form.
Prepend the line with # to comment it, run the commented line so it gets added to history, do whatever it is you remembered, then up arrow to retrieve the first command.
Not a fan of the LLM-flavoured headings, and the tips seem like a real mixed bag (and it'd be nice to give credit specifically to the readline library where appropriate as opposed to the shell), but there are definitely a few things in here I'll have to play around with.
One thing I dislike about brace expansions is that they don't play nicely with tab completion. I'd rather have easy ways to e.g. duplicate the last token (including escaped/quoted spaces), and delete a filename suffix. And, while I'm on that topic, expand variables and ~ immediately (instead of after pressing enter).
Something that should be mentioned is starting a command with a space doesn't add it to your history in most shells, really useful for one-off commands that you don't want cluttering your history.
Also, increase your $HISTSIZE to more than you think you would need, there have been cases where it helped me find some obscure command I ran like 3 years before.
Regarding history: I have a function in my ZSH config which excludes certain things from the history. Especially things that can break stuff when my sausage fingers CTRL-R the wrong thing
Something like this:
# Prevent certain strings from appearing in the history
# Anything starting with a leading space is ignored
# Anything containing "--force" or "whatever" is ignored
function zshaddhistory() {
emulate -L zsh
if ! [[ "$1" =~ "(^ |--force|whatever)" ]] ; then
print -sr -- "${1%%$'\n'}"
fc -p
else
return 1
fi
}
With ctrl+r, if you press it twice, it will autofill the search with whatever you last searched for. pressing it more will go back through the history. Been using that a lot recently when doing docker stuff. ctrl+r, type the container name, keep going until I get the compose build command. ctrl+r, ctrl+r, repeat until the log command. Then I can just mash ctrl+r to get the build and log commands. Ctrl+r is your friend. ctrl+r
I'd advise against using sudo !! though since it adds the command to history and then it's very easy to accidentally trigger, running some undesired command as root without any prior confirmation. IMO pressing up, Ctrl-A and typing "sudo " isn't much longer but saves you from running unknown commands as root by accident
The utility of $_ is often voided by tab-completion in the subsequent command, at least in bash. You won't know what it contains, which makes it dangerous, unless you first check it in a way that also carries it forwards:
Just recently, I came up with this in my .bashrc, basically a "deep cd" command:
dcd() {
# If no argument is given, do nothing
[ -z "$1" ] && return
# Find the first matching directory under the current directory
local dir
dir=$(find . -type d -path "*$1*" -print -quit 2>/dev/null)
# If a directory was found, cd into it
[ -n "$dir" ] && cd "$dir"
}
I thought this would be way too slow for actual use, but I've come to love it.
set -e is almost never what you want in your scripts. It means "silently exit immediately if there are any u handled non-zero exit codes". The thing that trips most people up on that is subshells when your trying to catch output into a variable, if it gets a non-zero exit code your entire script suddenly exits.
set -e really only makes sense if you setup a trap to also print what line you exited on, and even then only for debugging (e g. trap 'echo "ERROR: Line $LINENO" ERR')
Conversely, set -o pipefail should be set in every script. It makes scripts with pipelines do what you expect, and the whole string of pipeline commands gets set to an error if any of the commands inside of it exit with an error. Default behavior for historical reasons is still to ignore all exit codes except the last command in a pipeline.
For me the ultimate trick is to open the current prompt in vim with F2 (Ctrl+X ctrl+E seems to work too):
# Use F2 to edit the current command line:
autoload -U edit-command-line
zle -N edit-command-line
bindkey '^[OQ' edit-command-line # f2 is ^[OQ; to double check, run `xargs` and then press f2
I've been using a lot of key combinations and I wasn't aware of these two, and I really think these are awesome additions to handling the console. Thank you for showing me. I've only been using it for 22 years, but I haven't come across these :D
CTRL + U and CTRL + K
CTRL + W
What I like about these key combinations is that they are kind of universal. A lot of programs on Linux and Mac support all these key combinations out of the box. And that's like a game changer in productivity, especially jumping to the start or the end of the line or jumping forward and backward per word is making working only with the keyboard so much more nice. And in editors together so AVY, you can even get a faster flow of jumping around.
On scripts that might handle filenames with spaces, I include:
IFS=' ''
'
Hint: the spaces between the first two apostrophes are actually one .
This does not affect the already written script (you don't need to press Tab instead of space to separate commands and arguments in the script itself), but by making and be the “internal field separators” will allow globbing with less quoting worries while still allowing for files=$(ls) constructs.
Maybe not a shell trick per-se but I have been a very big fan of zoxide. It can jump around your common directories. If you have a ~/workspace/projects and you are anywhere and type cd projects it will take you to that directory. I never realized how much I got hooked onto it, until I used a system without it.
If only somebody had a lifehack for making me remember all these awesome commands.
If I do something the slow way it's usually because I don't do the operation enough to burn it into my memory, or I got burned by accidentally hitting something close but incorrect once and closed the tab or something.
A much larger base for ksh (as a pdksh descendent) is Android. OpenBSD is a tiny community in comparison, although Android has acquired code directly from OpenBSD, notably the C library.
The vi editing mode is always present in ksh, but is optional in dash. If present, the POSIX standard requires that "set -o vi" enable this mode, although other methods to enable it are not prohibited (such as inputrc for bash/readline), and as such is a "universal trick."
The article is relying on some Emacs mode, which is not POSIX.
$_ is not POSIX if I remember correctly.
History in vi mode is easier, just escape, then forward slash (or question mark) and the search term (regex?), then either "n" or "N" to search the direction or its reverse.
I've seen a lot of people who don't like vi mode, but its presence is the most deeply standardized.
This snippet for zsh still has some rough edges but works for the majority of cases. Automatically extends any global alias when space is pressed in zsh. For ex. I have alias -G G='rg -s', so if I type command | G it will autoexpand it to command | rg -s and so on.
globalias() {
local raw word
# raw last blank-separated token, exactly as typed
raw=${LBUFFER##\* }
# shell-parsed last word
word=${${(z)LBUFFER}[-1]}
# if user typed \alias, don't expand
if [[ $raw == \\* ]]; then
zle self-insert
return
fi
if alias -- ${(q)word} &>/dev/null; then
zle _expand_alias
zle expand-word
fi
zle self-insert
My favourite trick is either commenting out a whole command or placing a comment at the end of a command to make it easier to find in my persistent history (thanks eliben) [0], using the # character.
I tried this in zsh and it wasn't the default behaviour which immediately made me nope from the shell altogether, among all the other quirks. I've just been using bash for far too long to switch to something different.
I didn't know the ALT + . trick to repeat the last argument, but what is even more neat (and not mentioned in the article) is that it cycles through your history. At least it does in my shell.
Great write up, had to bookmark so I can go through it more later there so much good stuff in there.
For the CTRL + R tip, you can make it even better if you install fzf. Massively improves searching through history. It's worth the install just for that one feature.
Best thing I ever did as a dev was start spending more time in the terminal. Getting familiar with the tools and how they interact makes life so much easier.
Here's my favorite tip: If you use bash, you can write bash on your prompt (duh). But this is one of the biggest reasons I stick with bash everywhere, as I am quite comfortable and experienced in bash and sometimes it's just easier to write things like for i in *.mp3; do ffmpeg -i $i ... etc. If it's re-usable, I write it to a bash script later.
CTRL+C will send a interrupt signal; in some programs (and some circumstances in some programs) that does not work, but then you might try CTRL+\ which will send a quit signal; sometimes that works even if interrupt does not work.
Also known as "emacs editing mode". Funnily enough, what POSIX mandates is the support for "vi editing mode" which, to my knowledge, almost nobody ever uses. But it's there in most shells, and you can enable it with "set -o vi" in e.g. bash.
Its almost ironical that we still use the Terminal - and many use it like in the eighties using Bash - and seem to have forgotten that we should invent a better terminal & shell than doing all the workarounds to handle the quirks of the current systems.
You'll look like the coolest person in the office running sudo !!. Another personal favorite of mine is using the --now flag for systemctl to enable & start a service in one command (i.e systemctl enable --now nginx)
I ought to migrate away from shell scripting and just keep the shell for interactive use. Unfortunately I have cursed myself by getting competent-ish with P. shell and Bash scripting. Meaning I end up creating maintenance headaches for my future self.
(Echoes of future self: ... so I asked an LLM to migrate my shell scripts to Rust and)
Anyway with the interactive shell stuff. Yeah the I guess Readline features are great. And beyond that I can use the shortcut to open the current line in an editor and get that last mile of interactivity when I want it. I don’t really think I need more than that?
I tried Vim mode in Bash but there didn’t seem to be a mode indicator anywhere. So dropped that.
Edit: I just tested in my Starship.rs terminal: set -o vi. Then I got mode indicators. Just with a little lag.
I knew most of these but the $_ variable and "ESC + ." to reference or insert the last argument of the previous command. I can see getting some use out of that, so thanks for posting.
Another useful "Emergency exit" is CTRL+Z which stops the process and cannot be intercepted.
It's often faster than hitting CTRL+C and waiting for process cleanup, especially when many resources are used. Then you can do e.g. kill -9 $(jobs -p) to kill the stopped tasks.
280 comments
tar -, then the up arrow, and get the tar parameters that worked last time.In zsh this is configured with
A mistake 3 words earlier? No problem:3bcw and I'm good to go.
Want to delete the whole thing? Even easier:cc
I can even usev to open the command inside a fully-fledged (neo)vim instance for more complex rework.
If you use (neo)vim already, this is the best way to go as there are no new shortcuts to learn and memorize.
Be careful working CTRL + W into muscle memory though, I've lost count of how many browser tabs I've closed by accident...
You're typing a long command, then before running it you remember you have to do some stuff first. Instead of Ctrl-C to cancel it, you push it to history in a disabled form.
Prepend the line with # to comment it, run the commented line so it gets added to history, do whatever it is you remembered, then up arrow to retrieve the first command.
$ long_command
$ #long_command
$ stuff_1 $ stuff_2
$ #long_command
$ long_command
One thing I dislike about brace expansions is that they don't play nicely with tab completion. I'd rather have easy ways to e.g. duplicate the last token (including escaped/quoted spaces), and delete a filename suffix. And, while I'm on that topic, expand variables and
~immediately (instead of after pressing enter).Also, increase your
$HISTSIZEto more than you think you would need, there have been cases where it helped me find some obscure command I ran like 3 years before.Something like this:
> cd -: The classic channel-flipper. Perfect for toggling back and forth.
And not only cd. Gotta love 'git checkout -'
printf %s\\n "$_"
Stuff like NVM or Oh My ZSH will add a few seconds to your shell startup time.
set -eis almost never what you want in your scripts. It means "silently exit immediately if there are any u handled non-zero exit codes". The thing that trips most people up on that is subshells when your trying to catch output into a variable, if it gets a non-zero exit code your entire script suddenly exits.set -ereally only makes sense if you setup a trap to also print what line you exited on, and even then only for debugging (e g.trap 'echo "ERROR: Line $LINENO" ERR')Conversely,
set -o pipefailshould be set in every script. It makes scripts with pipelines do what you expect, and the whole string of pipeline commands gets set to an error if any of the commands inside of it exit with an error. Default behavior for historical reasons is still to ignore all exit codes except the last command in a pipeline.CTRL + U and CTRL + K CTRL + WWhat I like about these key combinations is that they are kind of universal. A lot of programs on Linux and Mac support all these key combinations out of the box. And that's like a game changer in productivity, especially jumping to the start or the end of the line or jumping forward and backward per word is making working only with the keyboard so much more nice. And in editors together so AVY, you can even get a faster flow of jumping around.
!!quite a bit to repeat the output of the prior command as an argument.clear -x.CTRL+L clears the visible output but you can still scroll up in your buffer to see the rest, clear will clear that scroll up buffer too.
I've written about and demo'd this in https://nickjanetakis.com/blog/clear-vs-ctrl-l-in-your-shell.
This does not affect the already written script (you don't need to press Tab instead of space to separate commands and arguments in the script itself), but by making and be the “internal field separators” will allow globbing with less quoting worries while still allowing for in it.
files=$(ls)constructs.Example:
Of course this will still fail if there happens to be a filename withcd projectsit will take you to that directory. I never realized how much I got hooked onto it, until I used a system without it.If I do something the slow way it's usually because I don't do the operation enough to burn it into my memory, or I got burned by accidentally hitting something close but incorrect once and closed the tab or something.
The vi editing mode is always present in ksh, but is optional in dash. If present, the POSIX standard requires that "set -o vi" enable this mode, although other methods to enable it are not prohibited (such as inputrc for bash/readline), and as such is a "universal trick."
The article is relying on some Emacs mode, which is not POSIX.
$_ is not POSIX if I remember correctly.
History in vi mode is easier, just escape, then forward slash (or question mark) and the search term (regex?), then either "n" or "N" to search the direction or its reverse.
I've seen a lot of people who don't like vi mode, but its presence is the most deeply standardized.
alias -G G='rg -s', so if I typecommand | Git will autoexpand it tocommand | rg -sand so on. }zle -N globalias bindkey ' ' globalias
These carpal tunnel riddled hands can’t be bothered to reach for ctrl or alt let alone arrow keys.
I tried this in zsh and it wasn't the default behaviour which immediately made me nope from the shell altogether, among all the other quirks. I've just been using bash for far too long to switch to something different.
[0] https://eli.thegreenplace.net/2013/06/11/keeping-persistent-...
ALT + .trick to repeat the last argument, but what is even more neat (and not mentioned in the article) is that it cycles through your history. At least it does in my shell.Makes for a definite return of sanity ..
For the CTRL + R tip, you can make it even better if you install fzf. Massively improves searching through history. It's worth the install just for that one feature.
Best thing I ever did as a dev was start spending more time in the terminal. Getting familiar with the tools and how they interact makes life so much easier.
for i in *.mp3; do ffmpeg -i $i ...etc. If it's re-usable, I write it to a bash script later.> The “Works (Almost) Everywhere” Club
> The Backspace Replacements
Also known as "emacs editing mode". Funnily enough, what POSIX mandates is the support for "vi editing mode" which, to my knowledge, almost nobody ever uses. But it's there in most shells, and you can enable it with "set -o vi" in e.g. bash.
sudo !!. Another personal favorite of mine is using the --now flag for systemctl to enable & start a service in one command (i.esystemctl enable --now nginx)> We’ve all been there.
Close tab.
I ought to migrate away from shell scripting and just keep the shell for interactive use. Unfortunately I have cursed myself by getting competent-ish with P. shell and Bash scripting. Meaning I end up creating maintenance headaches for my future self.
(Echoes of future self: ... so I asked an LLM to migrate my shell scripts to Rust and)
Anyway with the interactive shell stuff. Yeah the I guess Readline features are great. And beyond that I can use the shortcut to open the current line in an editor and get that last mile of interactivity when I want it. I don’t really think I need more than that?
I tried Vim mode in Bash but there didn’t seem to be a mode indicator anywhere. So dropped that.
Edit: I just tested in my Starship.rs terminal:
set -o vi. Then I got mode indicators. Just with a little lag.| sudo tee filewhen current user does not have permission to >fileIt's often faster than hitting CTRL+C and waiting for process cleanup, especially when many resources are used. Then you can do e.g.
kill -9 $(jobs -p)to kill the stopped tasks.fc, norEsc .Quite a few useful ones