<!-- mobian-agent-page publisher="dailydev" canonical="https://daily.dev/posts/a-shell-exclamation-mark-is-not-for-yelling-be-lazy-ota4wgej1" -->

---
title: A shell exclamation mark is not for yelling. Be lazy
description: Shell event designators — the `!` syntax available in bash, csh, tcsh, and zsh — let you reference and reuse previous commands and their arguments without...
canonical: https://daily.dev/posts/a-shell-exclamation-mark-is-not-for-yelling-be-lazy-ota4wgej1
twitter:card: summary_large_image
twitter:site: @dailydotdev
og:type: website
og:site_name: daily.dev
og:title: A shell exclamation mark is not for yelling. Be lazy | daily.dev
og:description: Shell event designators — the `!` syntax available in bash, csh, tcsh, and zsh — let you reference and reuse previous commands and their arguments without...
og:url: https://daily.dev/posts/a-shell-exclamation-mark-is-not-for-yelling-be-lazy-ota4wgej1
og:image: https://api.daily.dev/og/posts/Ota4WGeJ1.png
og:image:alt: A shell exclamation mark is not for yelling. Be lazy
og:image:width: 1200
og:image:height: 630
og:locale: en
---

> ## Documentation Index
> Fetch the complete documentation index at: https://daily.dev/llms.txt
> Use this file to discover all available pages before exploring further.

# A shell exclamation mark is not for yelling. Be lazy

**[Lobsters](https://daily.dev/sources/lobsters)** · 9 min read · 0 upvotes · 0 comments

## Summary

Shell event designators — the `!` syntax available in bash, csh, tcsh, and zsh — let you reference and reuse previous commands and their arguments without retyping them. The post explains the three-part pattern `![event][:word][:modifier]`, covering event designators like `!!`, `!ssh`, `!-2`, word designators like `!:$`, `!:*`, `!:1-2`, and modifiers like `:h` (dirname), `:t` (basename), `:r` (strip extension), and substitution with `:s/foo/bar/`. It also covers the POSIX `fc` command for editing previous commands in your editor of choice, including bulk replacement across multiple history entries. Four practical shortcuts worth memorizing are highlighted: `!$`, `!:0`, `!$:h`, and `!$:t`.

## Full article

daily.dev links to this article rather than hosting it. Read it at the original source: <https://refp.se/articles/your-shell-and-the-lazy-exclamation-mark>

## Questions this post answers

### How do I rerun the last command with sudo in bash?

Use `sudo !!` — the `!!` event designator expands to the entire previous command. So if you ran `apt install awesome-package` and got a permissions error, typing `sudo !!` reruns it as `sudo apt install awesome-package` without retyping anything.

_Developers who live in the terminal pick up tricks like these on daily.dev._

### How do I cd to the directory of the last argument I typed in bash?

Use `cd !$:h` — `!$` refers to the last argument of the previous command, and the `:h` modifier strips the filename to give just the directory path (equivalent to `dirname`). For example, after `touch ~/projects/work/awesome.sh`, running `cd !$:h` expands to `cd ~/projects/work`.

_Shell productivity patterns like this surface regularly for developers who follow command-line topics on daily.dev._

### How do I edit a previous shell command in my text editor instead of on the command line?

Use the POSIX `fc` command. Running `fc` opens the previous command in your editor (controlled by `$FCEDIT` or `$EDITOR`, falling back to `ed`). You can also run `fc -3 -1` to edit the last three commands in one buffer, or `fc -e 'sed -i s/prod-01/test-01/g' -3 -1` to apply a substitution across multiple history entries and rerun them. Not available in minimal shells like dash or busybox ash.

_Keeping up with shell tooling like fc is easier when terminal-focused posts reach you through daily.dev._

## Community take

How the wider developer community reacted, aggregated from 2 discussions and 32 comments across lobsters, hackernews (as of 2026-08-07).

**TL;DR:** The community is split: some find shell history expansion genuinely useful and appreciate the tutorial, but many consider it a footgun they've deliberately disabled, preferring alternatives like fish, zsh autosuggestions, or simple Ctrl-R/arrow-key navigation instead.

**Sentiment:** 30% positive · 40% mixed · 30% skeptical

**The case for**

- zsh can tab-expand event designators before execution, making them safer to use
- The `fc` command and shortcuts like `!$` and `M-.` offer real productivity gains for those who internalize them
- The post itself includes a section on how to disable history expansion for those who don't want it

**The pushback**

- History expansion runs immediately without showing the expansion first, making it easy to execute unintended commands
- Using these shortcuts litters history with history-dependent commands that are hard to replay in isolation
- Many find it easier and safer to just edit the previous command directly (via Ctrl-R, atuin, or arrow keys) rather than use `!` syntax
- Fish, nushell, and zsh-autosuggestions cover most of the same use cases with far less footgun risk
- The `!` syntax can fire unexpectedly in double-quoted strings (e.g., commit messages), and the failed command never even enters history

**By community**

- lobsters (mixed): Lively debate between those who appreciate the tutorial and those who have deliberately disabled history expansion in favor of fish, nushell, or readline shortcuts like M-. and Ctrl-R.
- hackernews (mixed): No comments were available to assess sentiment.

**Hottest debate:** Whether shell history expansion is a useful productivity tool or a dangerous footgun that should be disabled in favor of modern alternatives like fish or zsh-autosuggestions.

**Open questions**

- Is there a reliable way to preview the expansion of `!` commands before executing them in bash?
- How does history expansion interact with escaping in edge cases like commit messages or complex quoting?

**Highlights**

> Yeah I decided history expansion is a massive footgun because it’s so hard to work out what it will do even when using it deliberately, or what went wrong when it happens unintentionally. I have had `set +o histexpand` in my .bashrc for nearly 30 years. Instead I sometimes use M-. to insert the final argument from the previous command, but more often mouse copy and paste. (History expansion is a csh feature that bash copied, and it made me think that it wasn’t just csh programming that should be [considered harmful](https://harmful.cat-v.org/software/csh)!)
> — [fanf on lobsters · 2 points, 1 comments](https://lobste.rs/s/0mjyep/shell_exclamation_mark_is_not_for_yelling#c_iem9c3)

> Honestly, being able to quickly find and edit a previous line feels so much better than this for most cases, especially for commands that can be destructive - which is a large fraction of shell commands. As an example, I recently got bitten by the gotchas below: ``` find . -name '*~' find . -name '*~' -print0 find . -name '*~' -o -name '*.bak' find . -name '*~' -o -name '*.bak' -print0 ``` (If you don't see the problem and you use `find`, you should find out). These kind of problems would be a lot worse if I was doing `!find` to pull in the last invocation. If I actually edit the line, there is a much greater chance I'll see that it isn't what I intended. In terms of quickly finding and editing previous commands, I currently use [atuin](https://docs.atuin.sh/latest/) but there are other good options, including just `Ctrl-R` or [up and down arrows](https://stackoverflow.com/questions/1030182/how-do-i-change-bash-history-completion-to-complete-whats-already-on-the-line#21271407). The good news is that in `zsh`, as noted in the other comments, you can use `<tab>` to expand so that you see what you are executing before you hit enter.
> — [spookylukey on lobsters · 3 points](https://lobste.rs/s/0mjyep/shell_exclamation_mark_is_not_for_yelling#c_lqxx4h)

> The problem with `!` is that— AFAIK— you can't see the expansion before running it.
> — [quad on lobsters · 2 points, 1 comments](https://lobste.rs/s/0mjyep/shell_exclamation_mark_is_not_for_yelling#c_a92mfk)

> `zsh` handles tab-expansion for event designators to some degree… ``` $ zsh --version zsh 5.9 (arm64-apple-darwin25.0) $ ls ~/some/path ls: /some/path: No such file or directory $ mkdir !:$  #<tab> will expand to ~/some/path in the readline $ !z #<tab> again will expand to the most recent closest-match, i.e. 'zsh --version' ```
> — [banna on lobsters · 3 points](https://lobste.rs/s/0mjyep/shell_exclamation_mark_is_not_for_yelling#c_1sxpay)

> I don't like using these: they litter your history with history-dependent commands
> — [pbsds on lobsters · 3 points, 3 comments](https://lobste.rs/s/0mjyep/shell_exclamation_mark_is_not_for_yelling#c_nuopx4)

**Source threads**

- [lobsters](https://lobste.rs/s/0mjyep/shell_exclamation_mark_is_not_for_yelling) · 52 points · 32 comments
- [hackernews](https://news.ycombinator.com/item?id=49197565) · 4 points · 0 comments

## Similar posts on daily.dev

- [A shell colon does nothing. Use it anyway](https://daily.dev/posts/a-shell-colon-does-nothing-use-it-anyway-iozfojz8n) · Lobsters · 0 upvotes · 0 comments
- [Zsh: manipulate filenames with modifiers like :r \(root\) and :e \(extension\)](https://daily.dev/posts/zsh-manipulate-filenames-with-modifiers-like-r-root-and-e-extension--0bvafqoyh) · Adam Johnson · 1 upvotes · 0 comments
- [Getting More Out of Claude Code in the Terminal](https://daily.dev/posts/getting-more-out-of-claude-code-in-the-terminal-o7xasutw7) · marmelab · 2 upvotes · 0 comments

---

Tags: [#cli](https://daily.dev/tags/cli), [#shell](https://daily.dev/tags/shell), [#bash](https://daily.dev/tags/bash)

[View this post on daily.dev](https://daily.dev/posts/a-shell-exclamation-mark-is-not-for-yelling-be-lazy-ota4wgej1)

```json
{"@context":"https://schema.org","@graph":[{"@type":"Organization","@id":"https://daily.dev/#organization","name":"daily.dev","url":"https://daily.dev","logo":{"@type":"ImageObject","url":"https://daily.dev/apple-touch-icon.png","width":180,"height":180},"sameAs":["https://twitter.com/dailydotdev","https://github.com/dailydotdev","https://www.linkedin.com/company/daily-dev-ltd"]},{"@type":"WebSite","@id":"https://daily.dev/#website","url":"https://daily.dev","name":"daily.dev","publisher":{"@id":"https://daily.dev/#organization"},"potentialAction":{"@type":"SearchAction","target":{"@type":"EntryPoint","urlTemplate":"https://daily.dev/search?q={search_term_string}"},"query-input":"required name=search_term_string"}}]}
{"@context":"https://schema.org","@type":"TechArticle","headline":"A shell exclamation mark is not for yelling. Be lazy","url":"https://daily.dev/posts/a-shell-exclamation-mark-is-not-for-yelling-be-lazy-ota4wgej1","mainEntityOfPage":{"@type":"WebPage","@id":"https://daily.dev/posts/a-shell-exclamation-mark-is-not-for-yelling-be-lazy-ota4wgej1"},"datePublished":"2026-08-07T09:35:41.759Z","dateModified":"2026-08-07T17:38:24.768Z","description":"Shell event designators — the `!` syntax available in bash, csh, tcsh, and zsh — let you reference and reuse previous commands and their arguments without...","image":"https://media.daily.dev/image/upload/f_auto,q_auto/v1/posts/b42e0b9ce80f4ce946f6e18dc0c46700?_a=AQAEuop","thumbnailUrl":"https://media.daily.dev/image/upload/f_auto,q_auto/v1/posts/b42e0b9ce80f4ce946f6e18dc0c46700?_a=AQAEuop","isAccessibleForFree":true,"articleSection":"Lobsters","inLanguage":"en","publisher":{"@type":"Organization","name":"daily.dev","url":"https://daily.dev","logo":{"@type":"ImageObject","url":"https://daily.dev/apple-touch-icon.png","width":180,"height":180}},"author":{"@type":"Organization","name":"Lobsters","logo":"https://media.daily.dev/image/upload/s--tl8v_Fku--/f_auto,t_logo/v1698841318/logos/lobste.jpg","url":"https://daily.dev/sources/lobsters"},"commentCount":0,"discussionUrl":"https://daily.dev/posts/a-shell-exclamation-mark-is-not-for-yelling-be-lazy-ota4wgej1","interactionStatistic":[{"@type":"InteractionCounter","interactionType":{"@type":"LikeAction"},"userInteractionCount":0},{"@type":"InteractionCounter","interactionType":{"@type":"CommentAction"},"userInteractionCount":0}],"keywords":"cli,shell,bash","timeRequired":"PT9M"}
{"@context":"https://schema.org","@type":"BreadcrumbList","itemListElement":[{"@type":"ListItem","position":1,"name":"Home","item":"https://daily.dev"},{"@type":"ListItem","position":2,"name":"Lobsters","item":"https://daily.dev/sources/lobsters"},{"@type":"ListItem","position":3,"name":"A shell exclamation mark is not for yelling. Be lazy"}]}
{"@context":"https://schema.org","@type":"FAQPage","@id":"https://daily.dev/posts/a-shell-exclamation-mark-is-not-for-yelling-be-lazy-ota4wgej1#faq","mainEntity":[{"@type":"Question","name":"How do I rerun the last command with sudo in bash?","acceptedAnswer":{"@type":"Answer","text":"Use `sudo !!` — the `!!` event designator expands to the entire previous command. So if you ran `apt install awesome-package` and got a permissions error, typing `sudo !!` reruns it as `sudo apt install awesome-package` without retyping anything. Developers who live in the terminal pick up tricks like these on daily.dev."}},{"@type":"Question","name":"How do I cd to the directory of the last argument I typed in bash?","acceptedAnswer":{"@type":"Answer","text":"Use `cd !$:h` — `!$` refers to the last argument of the previous command, and the `:h` modifier strips the filename to give just the directory path (equivalent to `dirname`). For example, after `touch ~/projects/work/awesome.sh`, running `cd !$:h` expands to `cd ~/projects/work`. Shell productivity patterns like this surface regularly for developers who follow command-line topics on daily.dev."}},{"@type":"Question","name":"How do I edit a previous shell command in my text editor instead of on the command line?","acceptedAnswer":{"@type":"Answer","text":"Use the POSIX `fc` command. Running `fc` opens the previous command in your editor (controlled by `$FCEDIT` or `$EDITOR`, falling back to `ed`). You can also run `fc -3 -1` to edit the last three commands in one buffer, or `fc -e 'sed -i s/prod-01/test-01/g' -3 -1` to apply a substitution across multiple history entries and rerun them. Not available in minimal shells like dash or busybox ash. Keeping up with shell tooling like fc is easier when terminal-focused posts reach you through daily.dev."}}]}
```

