1. 81
    A shell exclamation mark is not for yelling. Be lazy refp.se
  1.  

    1. 54

      I know I might go to posix hell for this but I just use fish shell and I don't have to worry about all that nonsense. It just Auto completes nicely. I kind of take it for granted.

      1. 13

        It is mindboggling to me that so many people use bash or zsh and not the user-friendlier alternatives like fish, nushell and other. The arguments I hear against switching from the default usually fall into two categories:

        • (online) I need to use legacy systems where only (ba)sh is available and I'm not root, it's just easier to always use this, I'm used to it. Fair enough. I do ssh fairly often into my institution computing cluster and it has bash. I downloaded the fish binary (or built it maybe?), added a line in .bashrc and boom, fish there too. I understand this is not always an option.
        • (coworkers) I am scared of anything CLI, I don't really understand what bash is, and I just chatgpt my way through it all, I am not interested in digging deeper into this. Pretty sad, but I mean, you do you, it's fine, I know I am the weirdo who actually enjoy learning about this stuff.
        1. 13

          I tried fish and sure it's obviously nicer than bash. I bounced off it because the syntaxes for things like for loops and variable assignments are quite different from sh and it was fighting my muscle memory.

          1. 1

            My experiences with POSIX shell scripting was that the syntax never arrived (or at least stayed) in muscle memory. This also goes for fish. Either way I’m consulting docs when I’m writing scripts but at least with fish the syntax described in the docs I look up isn’t baffingly obtuse.

          2. 5

            Third category: shell is the right tool for some programs and bash is more than just the sufficient almost-always-installed default, it's pleasant to work with once familiar! set -euo pipefail and shellcheck are great at helping the uninitiated aim footguns.

            Aside, I don't think bash gets enough credit for string based near-meta programming. I think it's stronger than tcl w.r.t argument parsing.

            dryrun() {  ${DRYRUN:+echo} "$@"; }
            # runs "long_process" w/specified arguments; DRYRUN is undefined
            dryrun long process "extra complicated" arguments
            
            # shows command and arguments instead of running it
            DRYRUN=1
            dryrun long_process "extra complicated" arguments
            
            1. 4

              it's pleasant to work with once familiar!

              Stockholm syndrome detected. Also funny thing, you made typos between your two snippets.

              1. 3

                Although I can’t technically speak for @wwfn, I don’t think it’s Stockholm syndrome. I know how to write a loop or an if statement in POSIX shell because I learned it decades ago. A new shell doesn’t give me a clear benefit but it does force me to learn and remember a new syntax. But I can spend that effort better elsewhere.

                1. 2

                  Ha! Were I using fish with a real command, the syntax highlighter would have clued me into the typo.

                  I originally had "It's not (just) Stockholm syndrome" in my comment but axed it for being too defensive [1]. Still, I don't think it fits. Sunken costs or blub paradox are likely better explanations for my inflated view of bash. @gnyeki gets at sunken costs in the sibling comment. While it definitely contributes, bash still has merits separate from the momentum of historical usage.

                  Using the "language power spectrum" a la blub, I'd argue bash is above fish (and other alternative shells, save oil). The submitted story here mentions using cd !$:h to jump to the directory of the last argument of the previous command. I can't find an analogous fish shell facility. The closest I can get is cd (dirname [alt+.]) and then would need string splitting for something like cd !:2:h.

                  Similarly, expanding my example above, not only does bash have parameter expansion (${DRYRUN:+echo}) absent from fish, I don't see a way to use a maybe-empty command variable with fish

                  (set -q DRYRUN && echo echo) date
                  # fish: command substitutions not allowed in command position
                  
                  set -q DRYRUN && set DRYRUN echo || set DRYRUN ""
                  $DRYRUN date
                  # fish: The expanded command was empty
                  

                  I hope this my failure to read the manual; I'd love to be shown I'm wrong and fish is more capable than bash!

                  [1] and because, albeit a useful phrase, I'm wary of using a made-up condition invented to cover for police incompetence

                2. 2

                  Except -e is a footgun, -u forces you to write extremely un-ergonomic bash (shellcheck already works well here) and -o pipefail is contextual and not always desirable (can be 50:50 but it depends on what kind of thing you're doing).

                  Moreover, as far as avoiding footguns, -euo pipefail does not get you very far. It's still trivial to introduce RCE by accident.

                3. 5

                  I want to use fish, but I use zsh at work for one simple reason: absolutely none of our f*cking machine configuration tools support fish. I could rewrite the machine generated config to work for fish, but man there's so much and it regularly changes. I already do that for jj and it's a pain.

                  1. 2

                    Is there not a supported way to source env files written for bash in fish?

                    I think I used to do roughly exec bash -c "source env && exec fish"

                    1. 4

                      There's babelfish and bass; one does the rough equivalent of that, the other does static translation (can't remember which).

                      1. 1

                        The traditional answer from fish docs was to just start fish as a sub-shell, never as a login shell. (This was the official advice around 2012 when I made the switch).

                        It’s still how I do things today and I’ve never had an environment setup issue as a result.

                        1. 1

                          As well as various things editing .profile, I'd fun into things like python venv which expect you to do source ./venv/bin/activate.

                          1. 2

                            Python venvs actually have a bin/activate.fish at least since python 3.12. Ofc there are other things too, but at least with venvs specifically that's not an issue anymore

                            1. 1

                              Just an example. It took years before that was added. There are more tools that do something similar.

                    2. 4

                      It's also not that hard to make zsh behave like fish using zsh-autosuggestions and some other modifications. This is the solution I settled on after working on some projects where sourcing bash code was required to do anything.

                      1. 3
                        1. Using installed programs is often less friction than trying to get something approved for install. If you change jobs or contracts, your preferred program might not be allowed. I've also used bash for so long now that anything else would feel weird at this point anyways.
                        2. Many things I do have sufficient complexity to be rerun many times and do non-trivial logic. I'll just write the tool in python for easier maintenance, and avoid needing a custom shell since python is almost ubiquitous already.
                        1. 2

                          Tried fish for a year but went back to zsh, I get all the same features but much more configurable and less bespoke than some weird things in fish such as removing environment variables

                          1. 3

                            (me) all the most effective developers I know just use bash, and anybody I see using fish seems to spend more time fumbling than us poor benighted bash users

                            1. 7

                              Fish user here, for years. I never fumble with it.

                              1. 1

                                Congratulations!

                              2. 4

                                Sweet sane defaults is actually one of the things I enjoy the most about fish, so this does not match my experience.

                              3. 1

                                Weirdo trying to learn, is your local .bashrc read to install things on a remote while sshing? Or how do you do it? Would you have to set this up on every machine?

                                1. 3

                                  I don't even know what's in my local bashrc, fish is my default local shell. I'm not sure what "read to install things" means?

                                  I have to set this up on every machine, but I don't connect to that many machines so it isn't an issue in practice. On my institution computing cluster, you get the same env/home/bashrc on each computing node, so I only had to do that once.

                                2. 1

                                  I understand this is not always an option.

                                  Yea. I admin some "classic"-style enterprise apps. Sadly, RHEL doesn't come with fish packaged, and we have no EPEL there.

                                  1. 1

                                    But can't you just wget the fish precompiled binary from https://github.com/fish-shell/fish-shell/releases if you want to use it?

                                3. 5

                                  As one of the few guys who runs powershell on linux I'm with you, it's nice to skip a lot of the bash weirdness.

                                  1. 8

                                    Nushell joining the non-posix gang.

                                    1. 3

                                      Xonsh joining in, also with fish-like autosuggestions.

                                      1. 2

                                        underrated shell. has powershell features but better syntax (for some subjective definition of "better", of course)

                                    2. 4

                                      I've modded fish to accept these substitutions. Very hard to live without them.

                                      1. 3

                                        It also has keybinds that match much of the aforementioned functionality. Like alt+e to edit the command line in your editor and alt+up to use the last argument.

                                        1. 1

                                          I found them annoying, I don't like auto complete and I don't like colors in my CLI.

                                        2. 12

                                          I don't like using these: they litter your history with history-dependent commands

                                          1. 28

                                            At least in zsh, the history only stores the expanded versions:

                                            % echo 'hello world'
                                            hello world
                                            
                                            % !:s/world/lobsters
                                            echo 'hello lobsters'
                                            hello lobsters
                                            
                                            % history
                                            ...
                                            10052  echo 'hello world'
                                            10053  echo 'hello lobsters'
                                            
                                            1. 3

                                              The event-designators themselves do not end up in the history, the expanded commands are what is stored.

                                              What you are describing is actually something that confuse many when getting into event-designators, and some would even argue that it would make sense if the behavior you described was default — but it isn't, neither in bash nor zsh.

                                              1. 2

                                                Agreed. Most of the time I just use zsh autocomplete.

                                                1. 1

                                                  Would it be better if the values were expanded in the history, thus no longer being history-dependent?

                                                  1. 2

                                                    My experience with ctrl-alt-e in bash tells me that it would also break escaping, although this is something that could be fixed.

                                                2. 12

                                                  The problem with ! is that— AFAIK— you can't see the expansion before running it.

                                                  1. 5

                                                    The bash manual mentions a M-^ readline command which I guess does something like what you are thinking of. Dunno how old this feature is: I think if it had been available in the 1990s I might not have decided history expansion is utter poison. OTOH there’s still a high chance of a nasty surprise if I don’t spot the ! or forget to press M-^ to check what happens…

                                                    history-expand-line (M-^)

                                                    Perform history expansion on the current line. See HISTORY EXPANSION below for a description of history expansion.

                                                    1. 3

                                                      In Zsh, it by default shows the expansion first and you have to explicitly run it. I think that's the default behaviour (it has at least been the case on all my Zsh installations), and is controlled by the histverify/HIST_VERIFY option.

                                                    2. 7

                                                      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'
                                                      
                                                      1. 3

                                                        Note that there is also setopt HIST_VERIFY. And if you find yourself doing foo>bar with no space much you might also want HistLexWords (but there are like 20 history setopts because zsh is gonna zsh).

                                                      2. 7

                                                        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 but there are other good options, including just Ctrl-R or up and down arrows.

                                                        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.

                                                        1. 1

                                                          my first question actually is, why are you still using find instead of the rg (ripgrep) mode that just searches filenames, or fd (fd-find)?

                                                          https://ripgrep.org/

                                                          https://github.com/sharkdp/fd

                                                          1. 3

                                                            Not a subop, but there's one big reason - find is a standard command (sure, there are differences between GNU find, BSD versions and Busybox, but it's mostly the same)

                                                            1. 3

                                                              fd does not have an expression language, rg is not a file search tool.

                                                              1. 1

                                                                rg actually has an excellent file search mode; if you need an expression language in a CLI, perhaps that is a better use-case for writing that logic in a different context, such as a luajit script

                                                                1. 2

                                                                  I cannot agree. There is a wide array of tasks where find (or something like bfs) provides just right amount of an expression language in a CLI. Having to reach for an actual scripting language for any of those tasks would be a giant overkill, hugely unwieldy, and most likely slower to boot. Why would I do that?

                                                          2. 7

                                                            It is a hack, but I recently discovered ctrl+o in Bash (or I guess most readline stuff).

                                                            After you recall a command from history (with ctrl+r, for example), ctrl+o executes it and moves to the next command in the history.

                                                            1. 3

                                                              I like ctrl+o a lot! If you're rotating between two or three commands, like vim some.config followed by run-something, there's a chance you only need to type and run these commands once, Alt-2 or 3 Up and ctrl+o forever. It rotates back to the first of the bunch after you ctrl+o the last command of your history.

                                                              This breaks if you need to change or run a different command somewhere in between, forcing you to bring back each of the previous commands you want to rotate between to the bottom of your history to set-up ctrl+o again, so the benefits are questionable, but I enjoy it.

                                                            2. 6

                                                              I wish I could disable this explanation mark magic. The only time I ever encountered it is by accident, when having a ! in a commit message. (Yeah I should use '...' rather than "..." but muscle memory is hard to change...) And when that happens you can't even arrow-up and fix the invocation since an incorrectly used ! makes the shell reject the command so early it never becomes part of the history... very annoying.

                                                              1. 8

                                                                Indeed you can! The post has a section titled "! just gets in the way, how do I turn this off?".

                                                                1. 5

                                                                  Ah I should have read all the way to the end. :) Thanks!

                                                                2. 3

                                                                  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!)

                                                                  1. 3

                                                                    ++, I use M-. a lot myself. I've been told M-. is not straightforward on macOS, so maybe that's why it's not so popular, but it's so handy.

                                                                    edit: never learned history expansion myself- I want to say intentionally because I think it's harmful... but mostly because I'm lazy and it doesn't click with me.

                                                                3. 5

                                                                  This is incredible. Thank you, especially for the short summary in the "Yell responsibly" section. I will keep referring to this and maybe eventually get a muscle memory for the different ways event designators can be used.

                                                                  1. 3

                                                                    I used to use these, but got burnt too many times from not seeing the expansion ahead of time. Now I just use fish, and completion from history is automatic and 100000x easier and less error-prone.

                                                                    1. 1

                                                                      a gem, thanks!