top | item 47525243

Shell Tricks That Make Life Easier (and Save Your Sanity)

644 points| zdw | 6 days ago |blog.hofstede.it | reply

279 comments

order
[+] alberto-m|6 days ago|reply
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
[+] ahmedfromtunis|6 days ago|reply
Using the terminal becomes much more cozy and comfortable after I activate vim-mode.

A mistake 3 words earlier? No problem: <esc>3bcw and I'm good to go.

Want to delete the whole thing? Even easier: <esc>cc

I can even use <esc>v 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.

[+] fellerts|6 days ago|reply
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...

[+] hejira|6 days ago|reply
In my terminal it's the exact opposite – Alt-Backspace deletes to the previous space, whereas Ctrl-W deletes to the last non-alphanumeric (such as /). I'm using fish shell in an Alacritty terminal.

Yeah, pressing Ctrl-W accidentially is a pain sometimes ... but Ctrl-Shift-T in Firefox is a godsend.

[+] meatmanek|6 days ago|reply
> Be careful working CTRL + W into muscle memory though, I've lost count of how many browser tabs I've closed by accident...

I still maintain this is why macOS is the best OS for terminal work -- all the common keybindings for GUI tools use a different modifier key, so e.g. ⌘C and ⌘W work the same in your terminal as they do in your browser.

(Lots of the readline/emacs-style editing keybindings work everywhere in macos as well -- ^A, ^E, ^K, ^Y, but not ^U for some reason)

[+] gryfft|6 days ago|reply
Ctrl-Shift-T usually brings that tab right back at least
[+] figmert|6 days ago|reply
> Be careful working CTRL + W into muscle memory though, I've lost count of how many browser tabs I've closed by accident...

This hurts.

Also, for the shell, if you do C+w, you can "paste" it back using C+y. Assuming you have not removed that configuration.

[+] lelanthran|6 days ago|reply
> 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 telling me!!!

(I use vim daily, with multiple splits in a single instance.)

[+] oxag3n|6 days ago|reply
Depends on the shell - bash on my Ubuntu deletes entire '/var/log/nginx/', while after switching to sh it deletes only nginx
[+] fp64|6 days ago|reply
I've installed "More Better Ctrl-W" for Chromium, and mapped Ctrl-W to do nothing, and Ctrl-D to close the current tab
[+] themafia|6 days ago|reply
'man readline' contains all the useful key combinations.
[+] sfink|6 days ago|reply
...which is why I recently went to about:keyboard and removed that hotkey. I love that page.

That, and Ctrl-N. No more forest of blank browser windows when using a terminal emulator in a web page!

(Firefox only)

[+] tkocmathla|6 days ago|reply
I love this, from a comment on the article:

  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
  ```
[+] hikarudo|6 days ago|reply
One trick I use all the time:

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

<Home, #>

$ #long_command

<Enter>

$ stuff_1 $ stuff_2

<Up arrow a few times>

$ #long_command

<home, del>

$ long_command

[+] zahlman|6 days ago|reply
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).

[+] croemer|6 days ago|reply
Not just the heading is LLM-flavoured. So is the writing, e.g. "The shell is a toolbox, not an obstacle course."
[+] umanwizard|6 days ago|reply
Readline is close enough to being part of bash that it’s not really inaccurate to call these all shell features imo.
[+] ta8903|6 days ago|reply
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.

[+] account42|6 days ago|reply
HISTCONTROL=erasedups can also help keeping more obscure commands in your history, at the expense of context around commands.
[+] l72|5 days ago|reply
Also a good thing to remember if you are ever dealing with credentials:

  $  APIKEY=asdfdfds
  $ curl -H "x-api-key: $APIKEY" https://example.com
[+] elric|6 days ago|reply
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
    }
[+] rgrau|6 days ago|reply
That's very cool!

To take advantage of the "leading space" one, I have this, to mark some commands that I never want to record:

       unhist () {
         alias $1=" $1"
       }
       unhist unhist
       unhist fzf
       unhist rghist     #custom command that greps .zhistory,...
[+] voidUpdate|6 days ago|reply
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
[+] arcanemachiner|6 days ago|reply
Make sure to add fzf + shell integration for maximum Ctrl+r goodness.
[+] talkin|6 days ago|reply
> cd -: The classic channel-flipper. Perfect for toggling back and forth.

And not only cd. Gotta love 'git checkout -'

[+] piekvorst|6 days ago|reply
The '-' shortcut is weird. In 'git commit -F -', the '-' is actually /dev/stdin.
[+] nasretdinov|6 days ago|reply
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
[+] kgwxd|6 days ago|reply
Decades ago, i used a small dns host. I wanted to switch a personal site and they just couldn't get the final step of the transfer to work. A ton of "try now" emails spanning several weeks.

Then one day, I was trying to setup MySQL on a personal Linux machine, and it wouldn't let me use my "standard password" for the admin account. I knew I could just use a different one, but I really wanted to know what the problem was. Took a long time, and I don't remember how I figured it out, but I eventually tracked it to the password ending with '!!'.

It took a while to put it together, and I never confirmed with the dns host support it's what fixed the issue but, I changed my password there, tried the transfer again, and it worked without any help from support. I suspect my plaintext password played some part in a script used in the transfer process, and was outputting the previous command in place of the !! I wish I had asked them if that was it, but if it was, they would have to admit to having my plain text password, or lie about it.

[+] em-bee|6 days ago|reply
i never found !! useful at all when i can just use up arrow to get the entry i want. it becomes more interesting when you can recall older commands, but then too i prefer search because i want to verify what command i am going to run.

and i only use sudo to open a root shell. never to run anything directly. i don't want normal and root commands mixed in the same history.

i could keep sudo commands out of the history, but then i don't have any history for stuff done as root.

with tmux i can switch terminals easily, so i am also not tempted to run things as root that i shouldn't despite having a root shell open.

[+] teo_zero|5 days ago|reply
Pressing up, Ctrl-A and typing "sudo " adds the command to history, too. What's the difference?
[+] 000ooo000|6 days ago|reply
I have a bash key binding, Ctrl+Y, that prepends sudo to the current command and submits it. I also don't use sudo-rs. No one has died yet.
[+] cocoto|6 days ago|reply
Prepend your command with a space and now your command is not saved in the history.
[+] kwar13|6 days ago|reply
in zsh pressing esc twice appends sudo to the last command
[+] aaravchen|3 days ago|reply
`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.

[+] amelius|6 days ago|reply
What confuses me is that Ctrl+Y "yank" means the opposite of what it means in Vim. Certainly does not help with keeping my sanity.
[+] Walf|6 days ago|reply
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:

printf %s\\n "$_"

[+] kleiba|6 days ago|reply
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.
[+] prodigycorp|6 days ago|reply
There's one thing you need to only think about once, and has the potential to save you a ton of time: profile your ZSH startup time!

Stuff like NVM or Oh My ZSH will add a few seconds to your shell startup time.

[+] wewtyflakes|6 days ago|reply
Agreed. I lazy-load NVM to get around that:

  lazy_nvm() {
    unset -f nvm node npm npx
    [ -s "$NVM_DIR/nvm.sh" ] && . "$NVM_DIR/nvm.sh"
  }
  nvm()  { lazy_nvm; nvm "$@"; }
  node() { lazy_nvm; node "$@"; }
  npm()  { lazy_nvm; npm "$@"; }
  npx()  { lazy_nvm; npx "$@"; }
[+] chrisweekly|6 days ago|reply
good call

if you care about perf, fnm is better/faster/cleaner than nvm. (also, mise is able to manage "all the things", not just node)

IME omzsh slowness usu relates to overloading it w plugins, which I've never found a need for...

[+] gchamonlive|6 days ago|reply
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
[+] tzot|6 days ago|reply
> # f2 is ^[OQ; to double check, run `xargs` and then press f2

I remember using `cat -v` before learning that `xargs` exists… or maybe before `xargs` actually existed on systems I used :)

[+] 0xcb0|6 days ago|reply
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.

[+] martinflack|6 days ago|reply
I use `!!` quite a bit to repeat the output of the prior command as an argument.

    # it's in my PATH but can't remember where
    which myscript
    vi `!!`