I use Zsh, it's such a tremendous improvement over Bash. Not only does it handle parameter expansion much better than any Unix-style shell, but it also has a lot more useful built-ins that can save you from having to call out to external programs to do basic tasks, like getting the absolute path of the directory that contains the current script.
It is so unbelievably feature-rich that it's almost impossible to describe all of its features and merits, but you can safely ignore most of them and the basic day-to-day experience will just be "Bash, but better".
No program is perfect, but Zsh I think is pretty damn close. That's not because it doesn't have flaws (it definitely has flaws), it's because it does such a good job at doing what it does, that all its flaws seem minor and none of them fundamentally get in the way of it doing that job.
One additional thing I'll say is: it is probably the only Unix-style shell where I would feel comfortable writing 1000+-line applications with a CLI, tests, etc. I would never do such a thing with Bash or some minimal Posix-compatible shell. It's a very sensible system scripting language, alongside Python, Perl, etc.
It may be better bash, but it's just an incremental improvement, no more. It still follows the same philosophy as bash.
On the other hand, fish shell feels like a bash rethought and optimized for interactive use. It's now my daily driver on all of my Linux and Windows machines (msys2 port). After using it only for interactive sessions, I also started to write my utilities in fish, which is a breath of fresh air compared to both bash and zsh.
The only place where I still use bash is when I need to write a script that needs to be shared with others, everything else is now fish.
Within 30 seconds of installing fish, I shouted, "How the hell do I disable these annoying suggestions?! This is worse than Clippy!" So I searched fish's docs and learned that it wasn't possible to disable the suggestions! The fish docs state, "Configurability is the root of all evil"[1]. As an Emacs user, this was the most offensive thing I've ever read, which is saying a lot because I once read a work of erotic Star Trek fan fiction about a love triangle involving Dr. Crusher, Wesley and Worf.
I have to admit, I’m not surprised the fish user would footnote with (an unclosed) ¹ on HN. :) As for me, I prefer the predictability of [1] and /bin/bash.
I use fish as my default user interactive shell but I still agree with you. I despise this philosophy of "configurability is the root of all evil" coming from fish and "every preference has a cost" coming from GNOME.
I think these projects are extremely opinionated and they will inevitably drive a lot of users away. Both fish and GNOME strive to provide "good" defaults out of the box but those defaults will never appeal to everyone. I ended up liking fish but I still despise GNOME and everything related to it.
Likewise, I don't use fish for exactly this reason. It seems extremely fine tuned to meet exactly the preferences of its developers, and eschews all further configurability. The Clippy comparison is brilliant, by the way, pretty much summarizes my exact feelings about the suggestions.
I ended up back on zsh. It's a real shame, because fish adds quite a lot in terms of improved subshell syntax and intelligent string manipulation.
Seems like someone should have been able to disable the suggestions using a plugin or something, given that IIRC fish has a plugin interface. But as far as I can tell no such thing exists.
The advantage of that, though, is that you can still benefit from the many "how do I do X in bash" posts on various fora, Stack Exchange, etc. With the more esoteric shells you have a much smaller community.
I prefer to keep my utilities scripting and interactive shell the same. That way I don't have to keep two sets of gotchas in my head, and don't accidentally use the wrong syntax in one place or the other sometimes leading to confusing misbehaviours.
While I don't mix bash/zsh/sh/... I will consider python/node/other if I need specific things that they do well or I'm making something more than a simple tool or automation (for which bash is either underkill or just thoroughly unsuited). They are sufficiently different that I'm not going to cause myself the sort of mix-up I might with flipping between shell scripting syntaxes.
The really cool thing about shell scripts is that they are just a saved version of your interactions with the ability to edit and refine later. If you stop using the shell interactively you've just lost the main advantage of using it for scripting.
That's a very good point. I have stuck with it because I am comfortable with it, but I have been wanting to spend some quality time with these alternative shells for a while. Have you tried Elvish? I am particularly curious about that one, because it seems like the primary Fish "competitor", but seems comparatively a lot less popular.
I also use fish on basically every linux machine, desktop or server, but I've also tried elvish for a bit. My shell scripts are still mostly bash or even POSIX though, because I haven't rewritten them yet, or they're setup scripts that should work right after the install without installing an additional shell first.
I'll admit that I haven't really dug into scripting with elvish, because I quickly abandoned it again.
My main reason for leaving elvish again was the good syntax completion in fish. Yes, you can create your own completions in elvish and they'll probably be just as good, but that takes time and I ultimately decided not to bother with it.
> it's just an incremental improvement, no more. It still follows the same philosophy as bash.
It is actually the other way round, with bash being an incremental improvement over zsh. Starting from v3, bash has adopted many features that zsh had had for a long time.
Both, bash and zsh, are incremental improvements over the Korn shell.
> It's a very sensible system scripting language, alongside Python, Perl, etc.
I’ve always assumed that the main benefit of bash scripts is that they’re portable and universal. If you’re willing to compromise that, why not just go with python? I don’t really understand which gap zsh fills here.
I can't speak for anyone else, but for me, it's because Python is cumbersome for doing the sorts of things that are easy in a shell. You want to capture the output of a command, parse it as a tab-separated table, extract the 3rd and 4th columns, and then use them to create new users with the values supplying usernames and passwords. Can you do that easily in Python?
For all its syntactic sugar, dealing with files in python (which comprises probably 90% of what I open a shell for) still requires a fair amount of boilerplate. Which is good for a general scripting language, but makes it poor for the the kind of tasks I expect of a shell.
I'm not arguing for Python over bash, I'm asking about the value of zsh as opposed to bash or python. So why couldn't one do what you describe in bash? The answer probably is you very well could. So why use zsh for scripting?
I've generally ported _to_ Python to see improved portability: the problem with bash is that it's not just what versions of bash you have installed (if you support RHEL, MacOS, etc. that can limit you significantly) but also all of the utilities which you call so you probably end up needing to wrap things like readlink once you need to run on more than one of GNU, BSD, busybox, etc.
Obviously you can get a fair amount done with pure POSIX but for me it's almost inevitably hit the point where you rewrite it in something else and realize that it's like 30% fewer lines of easier to understand code.
If you choose vim keybindings for navigation, it utterly breaks reverse-r search and the hacks you have to use to fix it do not make it an equivalent experience.
It's like the zsh folks said, "fuck those vim guys".
If I didn't have to use a Mac at work and our tooling didn't behave oddly in bash, I wouldn't use it. Bash is much more comfortable.
I'd say that if you want vim-like bindings, zsh is a much better option. It provides Vim's text objects, surround.vim bindings, and visual selection which bash does not. Also unlike bash, you're free to create your own keybinding if none of the builtin ones suffice. I did exactly that to get an increment/decrement operator that behaves exactly like Vim's ^a/^x.
It's like the zsh folks said, "I like those vim guys".
As a vim user, zsh's vim mode is far better. You do need a few lines of config for stuff like allowing deleting back past the insert position. However, I have never found it lacking, unlike bash which sometimes lacks some motion I try to use.
I didn't mean to imply that Zsh was somehow more portable than Python. And I agree with the sibling commenter that Python is probably more portable than Bash.
I use Zsh for many of my personal scripts, but at work I would never even try to get it into one of our production containers.
I am very aligned with what you said but when it comes to shipping shell scripts (say sysops stuff, init, cron, ..) I tend to stick with the ksh/bash ecosystem mainly due to more tooling (shellcheck, shfmt, bats/bash_unit).
Most things that works in bash obviously works fine in zsh, but as the article very eloquently points out - lots of gotchas.
I hear this argument from zsh purists all the time, I'm pretty sure it's made up. I've used oh-my-zsh for years with several plugins and the startup time is basically imperceptible. Maybe it's slow if you're running on raspberry pi or something.
Yeah, I can't quite comprehend what makes the terminal experience on MacOS feel so sluggish. At first I thought I was being spoiled by Alacritty, but even Konsole feels more responsive than iTerm these days.
Doesn't take long to track down the culprits. nvm is a huge contributor to startup lag, IME. I am not a frontend dev, and rarely have to touch Node, so I comment out anything nvm-related for daily work.
I agree nvm is suboptimal. I've been doing web-related things for a living since 1998 and I care about tooling. YMMV but I have a strong preference for managing node versions via `n` (installed via tiny util `n-install`).
It is easy to go overboard with oh my zsh for sure, but startup time, as long as it's less than ~5 seconds, is not likely to be a big problem for most people. On slower machines the real killer is the time it takes for the next prompt to appear after each command. There are mitigations, but I've definitely had machines where it is almost unbearable.
Five seconds really is unusably slow, in my opinion. Even a tenth of that is grating. If I want to run a one-off command, I open a new terminal to do so, and close it when I’m done.
My pretty extensively customized zsh config with ohmyzsh takes maybe a quarter of a second to load... Maybe I don't have as customized config as I thought because if it is taking a second or more I can't imagine what is being loaded.
Or zgenom[0]. Or if you want to get ESPECIALLY froggy and async with your shell startup, zinit[1]. I ran zinit for years before moving to zgenom. A large swath of electron apps use some weird NPM library to “resolve” your shell environment by kicking up a full interactive shell and then reading the environment variables out of it. zinit messed with that and would hang a lot because of all it’s async loading.
Would love to be able to transpile zsh or any other higher level scripting language into Bash for portability reasons alone. Is there something that converts bash, sh, or zsh into an AST?
Also useful would be a tool that tells me if I'm using a zsh construct that isn't supported in bash... or even better, how to do it in bash
Not really what you're asking for, but a language that compiles to bash for portability reminded me of Batsh [0], a C-like language that compiles to bash for Linux/Mac and batch for Windows.
I switched to fish a few years back after a couple decades of using bash. It took about 10 minutes to make that permanent everywhere – because so much is built-in and the defaults are good, there was no need to spend time on more than the package install plus chsh.
For scripting, I’d already set a personal policy that anything complicated use Python. For me there’s a fairly big difference between interactive shell sessions and scripts which solidly shifts the balance to a full language with a rich standard library and robust error handling. My scripts were edited using shfmt and shellcheck interactively anyway, so switching to Python wasn’t an increase in terms of tool support and the richer language means that I’m writing less code to get more functionality and especially error handling.
Same here. It's just so ergonomic. For instance, here's how to make a fancy prompt in fish:
- Write a function called "fish_prompt" with "echo", "set_color", and whatever else you want.
- Put it in a file in fish's "functions" directory called "fish_prompt.fish"
That's it. Done. Every time fish needs to display a prompt, it does so by calling the "fish_prompt" function. Where does fish find the definition of a function called "foo"? In a file called "foo.fish".
Oh, do you want one of those very cool right-side-of-the-window prompts showing the date, maybe? Write a function called "fish_right_prompt.fish". There, you're finished.
There are a million little niceties like this that seem so obvious in retrospect. Fish is what happens when someone completely rethinks a shell from the perspective of how things should be done today after we have a few decades of experimentation under our belt.
I’m glad that works for you but that sounds like a horrible solution to me. Maybe better than Bash but magic named files are probably my least favourite feature in scripting languages.
Don't think of it as magic, but as convention. Where do you put your function definitions? In the folder named "functions". There are only a handful of such things to learn.
In fish, you put the functions inside function blocks. There's nothing at all magical about that. However, if you write "function foo" inside a file named "foo.fish", then type the command "foo" at the shell prompt, then fish will look for a file named "foo.fish" and then execute the function "foo" defined inside it. That's its autoload mechanism.
That's the only remotely magical thing added here: by convention, an autoload function named "foo" is defined in ~/.config/fish/functions/foo.fish.
Your .bashrc, .zshrc, .profile, etc. files are also special. All that's going on is that there is folder where you can leave functions to be included in the main namespace, I don't see the big difference.
The difference as I understand it is your Fish equivalent of a $PS1 (and other Fish shell behaviours) have to be defined via that path. If I understand it correctly, it sounds a lot like git hooks but at a global scale rather than per repo.
Now I’ve got nothing against git hooks nor Fish per se, I just don’t see this particular model for defining behaviour to be convenient (eg what if you want to quickly change the prompt of a session without affecting other sessions?)
There might be more detail I’m missing and if that’s the case I apologise. But from what I’ve read thus far I’m not sold. It might appeal to others and good for them.
How would you normally do it? Because you can still put redefinitions of all the standard functions into a single file and load them with `.` as you would with other shells.
Well I’m not suggesting the following is a better alt shell, but in murex you have something that’s a little bit akin to a Windows Registry in that all of the shell settings are navigable through a builtin called ‘config’
You can set prompt functions with that; strings, ints and Booleans too. And ‘config’ comes with descriptions for each configurable thing, choices of options in many cases, and an easy way to default back to shell defaults too. So it’s dead easy to play around configuring whatever you want (you never need to leave the shell to look up an option).
The shell has its own problems though but it’s an interesting alternative take for grouping shell config.
>The difference as I understand it is your Fish equivalent of a $PS1 (and other Fish shell behaviours) have to be defined via that path.
They don't. You have to define the function somehow, but fish doesn't care about how that happens. It's just that if it's not defined yet it'll try to load it from the file.
If you want, you can just do it all in config.fish, like you'd do it all in .bashrc for bash.
>what if you want to quickly change the prompt of a session without affecting other sessions?
Then you can redefine the function, just like you could switch the value of $PS1.
I was going to mount a defense of fish about how it doesn't provide the same mechanisms or provide the same incentives that had people contorting Ruby into the most convoluted forms on their way to spit out HTML or JSON from an HTTP request, but then I found that the thing has a `fish_command_not_found` that you can overload as in Ruby.
You could make it run arbitrary things, but then you're quite clearly misusing it and e.g. there's no way to make it return a different status, fish will still view the command as failed.
Okay so I think I understand your objection a bit more. So fish only wants you to define a function called fish_prompt in order to customize the prompt. That function can be defined however you want. It can be in fish’s equivalent of .bashrc (~/.config/fish/config.fish). Putting it in ~/.config/fish/functions/fish_prompt.fish is just leveraging more tooling around shell function autoloading and sane defaults.
You can achieve PS1-like functionality “simply” by placing this bit of code somewhere in your startup:
function fish_prompt
set -l prompt_symbol '$'
fish_is_root_user; and set prompt_symbol '#'
echo -s $hostname (set_color blue) (prompt_pwd) \
(set_color yellow) $prompt_symbol (set_color normal)
end
It’s very verbose but it runs as fast as both bash’s PS1 and zsh’s PROMPT and is much easier for someone new to fussing with prompts to comprehend.
Even as someone who doesn’t use fish day to day, I think fish’s prompt customization is much more approachable. If I had a powerline prompt I wanted to tweak, I’d know to start with `functions fish_prompt` to print out the current contents of the function and have the complete code to how the prompt is built. zsh requires a lot more reading and understanding of the prompt system before you can dig into how exactly it all works. IIRC, most dynamic bash prompts have similar issues. PS1 is just interpolated globals and color codes, creating a disconnect between the prompt variable and the shell functions that update it.
Fish doesn't work with a light theme. (Dark text on white background.) Also the keybindings in fish are extremely unergonomic.
Honestly I just want a bash that saves every command entered, ever, and never deletes history. (I think I can spare the 100 kilobytes of disk space to hold command history forever.)
And fish doesn't get this right either, even though it's a complete no-brainer.
Sigh.
`fish` is amazing. I used to have a big old dotfiles repo, but nowadays I add starship[0] to my env and I'm pretty much good to go.
On the non-interactive scripting front, I've found fish to be a very competent language for it, but you're right, the error handling (among other things) are lacking compared to "real" languages
I have to say, I use fish and I customize my prompt... is starship really necessary? I recognize starship is cross-shell, but if I standardize on installing fish on my machines, I'm not sure I need that feature.
Is there a "killer app" for starship that you can't get from writing out your prompt in a fish shell script?
It's a 3-line prompt with a timestamp on the first line, my username@host + pwd on the 2nd line, and the actual prompt with the cursor on the 3rd line. I'm now considering swapping the timestamp to the right side after that possibility was mentioned in other comments in this thread.
I used fish for around a year and a half and wound up switching back to zsh. fish's startup time is way too long for my tastes, and the devs seem completely uninterested in providing a "don't load plugins" flag. I open up new tmux panes/windows/etc all the time, so fish's slowness here is really noticeable. It also negatively affects using things like `tmux popup`, which spawns a shell. Things like this that take no noticeable time under zsh are sluggish under fish.
I switched to fish for about six months, and recently went back to zsh when I realised that the three things I really liked about it (abbreviations, syntax highlighting and autosuggestions) were all doable in zsh with extensions [0]. (It feels pretty slow, even slower than say OMZ, but that's not such a big deal for me.)
I switched to Fish a couple years ago, and I love it too — the only thing I ever find myself missing from bash is `!!`. The (new) Alt+S keybind to prepend `sudo` to a command covers many of these use cases, but not all of them. For example, sometimes I want to re-run the last two commands; in Bash I could do `<up><up> && !!` but there doesn’t seem to be a good way to do this in Fish without having to re-type one of the commands.
Alt+Up is great for recalling individual arguments, but am I missing something or is there no way to recall a whole previous line in-place, without overwriting what’s currently on the command line?
Up-up-Ctrl+K to kill the line; down to go down one in the history; end to go to end of line; `; and` or ` &&` to join the commands; ctrl+Y to yank the line from the killring. [0]
I tried out zsh and just could not get used to how different tab completion was vs bash, so went back to bash quickly. Is tab completion on fish more similar to bash than zsh is?
No, fish actually has super fancy autocompletion with suggestions, which is even more complicated and visually messy than the zsh defaults. Furthermore, none of it can be disabled in fish. (This is why I don't use fish, fwiw.)
On the other hand, you can disable all these advanced features in zsh and get something a bit closer to a bash readline shell, with a few extra features. That's what I use.
I didn't find it too much of a switch — it works well and things like Option-><arrow> to complete it word by word are really handy with repetitive tasks like using the AWS CLI.
I liked fish a lot but various tooling at work just expected you to be working in something sh-like. I handled sourcing files of env vars with some utility functions but I still found myself having to drop into bash/zsh pretty often.
I feel torn because I really can empathize with making a clean break. But it also precludes a lot of people from making the shell a daily driver when your co-workers are shoving shell scripts your supposed to source into your env into github, lol.
Does fish still not have reverse search? I have a hard time taking a shell seriously if you can't perform an operation at least as good as this. Even Powershell supports reverse search!
In bash, I can type ctrl+r then type a few characters, and the last command that contains that string will get pulled up and I can hit [enter] to run it.
Not sure if fish has this or not, but I use reverse search in bash exhaustively.
I find it to be more polished and especially like the way it works with fragments (e.g. if you start typing "git log" and hit Option/Alt-ꜛ it will find commands prefixed with that whereas bash Control-R requires you to hit Control-R first and then type that, which isn't as handy when you realize you want to search halfway through).
That separate mode is incremental search and it's a big advantage. It lets you keep extending the search string until it matches the command you're looking for.
fish is fantastic. abbreviations are very nice, the fish_config gui is helpful if you actually need to configure something, and there's just a lot of utility things like fish_add_path. It's just very pleasant to use.
While these are good examples of some of the fragile parts of bash, I think switching shells is the wrong approach in this case. If you find yourself dealing with binary data, floating point math, complex quoting, and so on...you probably shouldn't be scripting with a shell. Use Python, Perl, Ruby, or something else more suited to the task.
I don't agree with this. Shell syntax is uniquely flexible and powerful when it comes to composing pipelines. It often is precisely the right tool for the job.
Floating point math and "complex" quoting come up pretty often in a wide range of relatively basic tasks. Even something as simple as computing a percentage literally cannot be done in Bash without forking out to an external program. Sure, you could use a tool that does that one thing and does it well, or you could just have it in your shell and not worry about it.
The whole point about quoting is that it's hard to get right even when you're trying. It gets even harder when you are dealing with things like arrays and command substitution. You could switch to another programming language... or you could just use a shell that does these things right from the outset!
I used to say this, but Powershell has really proven to me that we need a shell that is also a good scripting language. Powershell has its faults, but it's as good as any scripting language, especially when time is a factor. And it does this without sacrificing its utility as a shell.
I haven't spent time with elvish but I think it's moving more in the right direction. Zsh and friends are better than bash but they are still too sh-like and a lot of those conventions are just always going to be problems.
There are all sorts of cases where you have a simple script that's perfectly fine, but you want to just add two fractions, or store a bit of binary data in a variable. I think those are reasonable expectations, and I don't think that "rewrite it to {Python,Ruby,...}!" is really a great solution (partly because for some simple tasks writing it in Python, Ruby, etc. is a lot more work). "If you want it, then this is not the right tool" doesn't strike me as a very strong argument for missing basic features such as this. For advanced complex features? Maybe. But not this.
I also don't think shell scripting is quite as bad as people make it out to be, or rather, one of the reasons people hate it is because they use bash or POSIX sh, and yeah, that gets problematic fast. The point of the article was to demonstrate that actually, you can solve a decent chunk of the problems and get something much less problematic without a lot of effort, while still retaining the key advantages of shell scripting.
However, even if we exclude all of scripting: you can't write your ~/.{bash,zsh}rc in Python, and it's useful for interactive usage as well.
It comes with a lot of aliases and does fuzzy matching on arguments. For example:
gci -r
The -r will fuzzy match to -Recurse. It’s a change going to an OO shell from bash but I run it on windows and WSL. The windows integration is really good too.
Off topic, but I do like powershell. The lack of a reasonable way to have it NOT pop a console window is regrettable though. What is provided still pops a console window, just for a shorter amount of time.
So I read this with a great deal of interest and nodded my head say “yes, that’s true” at most of it. Despite this I just do not want to switch. Heck, first thing I do when I get a new Mac (now that ash is the default)is switch my shell to bash, install homebrew, then tmux, vim, powerline and git pull my dot files. I think maybe I am just old. Get off my lawn!
I am really working on not writing every little thing I want to script as a shell script. The other day I wanted to scan a dir of ~10k image files and move anything that was corrupt to another dir. I wrote it in bash and it worked. Then I was like, okay you should write this in python because you want to run it on 4 different OS. So I did. The code looks so much cleaner but was half as fast as the shell script. Oh well.
This is me as well - unabashedly a bash person. My reasons to not switch are not driven by the feature set offered by the various shells, but by the fact that I don't want to learn/think or be bothered with the nuances when I log in to various remote hosts, containers, and what have you. When I write shell scripts or installers, I want a single language that is reasonably likely to be present wherever it runs - and that today is bash.
Agreed - people have told me that other shells are better (and I'm sure they are better than bash in some ways) but for a shell, ubiquity is arguably the most important feature.
Also agree. For me, I want to be able to do simple "for each file run this command" loops in the shell. But if those loops get much more complicated than that then I switch over to Ruby.
We had _just_ settled the emacs vs vi argument when this starts ...
I guess debian/ Ubuntu folks will choose bash, osx people will continue using zsh and the openbsd users will keep using that vanilla sh because of the vintage feel.
On a more serious note: I can't see my commands in manjaro thanks to all the bells and whistles. For some reason that situation reminds me of spacemacs users.
> I guess debian/ Ubuntu folks will choose bash, osx people will continue using zsh and the openbsd users will keep using that vanilla sh because of the vintage feel.
I believe that using zsh means, for the vast majority of users, using just a small subset of functionality that gives a better UX when compared to Bash.
Both me and my colleagues use zsh this way, and the times I've tried to figure out more advanced functionalities, it wasn't as simple as I expected.
I've read a few points of the article, and although they make sense as improvements, they apply mostly to the scripts domain. I don't recommend writing zsh scripts, because they're subtly different from the (unfortunately) standard that is Bash, besides not being supported (at least until a short time ago) by (the necessary) Shellcheck. If one wants a better experience for scripting, they're better off with a non-shell scripting language altogether (ie. Python).
> I believe that using zsh means, for the vast majority of users, using just a small subset of functionality that gives a better UX when compared to Bash.
What about adding only these functionalities you may care about?
When I tried zsh, what I liked was the history search. Like you, everything else "wasn't as simple as I expected". So I fixed my bash to add and expand what I had enjoyed!
- stores everything into a sqlite database so separate bash in separate terminals can access each other history on the go (without waiting for the session to end and the history being committed),
- add extra details to the history like when the command started, stopped, which with return code, in which directory (more on what that enables below),
- for accessing the history, uses fzy for fuzzy finding, (the one thing I mostly enjoyed in zsh, not zsh but fzy!!)
- provides 2 separate history search contexts: either global (ctrl-t) or "this directory only" (ctrl-r), with extra goodies like excluding commands with a non-zero return error code thanks to the extra things saved
I included a few examples of the SQL queries you can run.
I hate to admit I'm in that camp. Used emacs because it has great support for editing over ssh. When vscode came out with remote connections for ssh and wsl, I switched over. Emacs is a great operating system-I mean editor, but it's pretty hard to extend it to have the feature set of newer editors while maintaining reasonable performance.
It takes a while for the time spent learning emacs/vi to amortise itself. You need to learn the key bindings, the ad hoc scripting language, and the substantial API. And throughout it all, there's the constant temptation to switch to something easier like vscode.
I tried learning to use Org-mode as a Markdown replacement over Doom Emacs. At some point, I switched back to vscode+Markdown and it felt like a breeze. I'm not a SWE, so the payoff would've probably taken too long to happen.
I also tried using the Maxima mode for Emacs as an interactive CAS. The only thing I got was Latex-formatted output and the same Emacs-like key bindings that you get in any terminal emulator. No auto-suggestion/Intellisense to help you navigate and avoid learning the API, so I wasn't overly impressed.
I have been using vi like key bindings for the past 15 years, so learning to do differently would be a significant investment I'm not willing to make, even moreso that the benefits would not be clear ;)
But yes, I'm 100% convinced that it's not for everyone, and depends a lot of the quality of the integration/mode of your specific use case.
Vim bindings are pretty good if you use the nvim plugin in vs code. It's not emulating (n)vim, you need to have the actual nvim installed and it will use that to provide the keyvindings.
I also switched from vs code to doom emacs though, just because I wanted to.
A decade ago after noticing such a conversation I'd have thought about and explored about which was better and which one to try and switch to; at least for few weeks until the novelty wore off.
I am older now (not sure smarter or lazier as well or both) and at this point I couldn't care less. As an Android developer I am essentially forced to use Android Studio - a rebranded IntelliJ and a shameless resource eater - it does the work and employer pays for better Macs every now and then, so it's good.
I know this is a joke, but I actually used to handwrite all of my programs.
Back then, I would then keypunch them the next time I was on campus and could access a keypunch machine. Writing even a modest 1000 line program was quite a chore because you'd have to carry around a box of 1000 cards and keep them from getting mixed up. Here's a picture of a full 2000 card box[1]; notice how we used to write diagonal lines on the edges of the cards to help put them in order after spilling the box on the floor!
See [2] for a picture of the first kind of keypunch machine I ever used, the IBM 026. The subsequent model, the IBM 029, was much better.
Of course the turnaround time wasn't measured in seconds back then. Once, I was given an assignment that required writing a program in IBM 360 assembler, it was the first time I ever had to write assembly language. With the aid of IBM's excellent manual, IBM 360 Principles of Operation, I hand wrote this program, went to campus to keypunch the program, and turned it in for one of the seven scheduled overnight submissions we were allowed. After around the fifth attempt I was starting the think that I wouldn't get credit for this assignment; miraculously, on my seventh and last submission the program finally worked.
On my case, it is more like being dragged into VSCode than actually switching to it by choice, specially when Microsoft makes some stuff available as VSCode plugins only (e.g. Powershell and ARM templates editing).
The Remote Development extensions being locked away is the kicker for me. Everything else I've been able to find on alternative "markets" (can we just call them repositories? we're all developers here...)
I feel attacked. In all seriousness: so long as you have a POSIX shell available (and set as default), you don't have to use a POSIX shell. I rarely do anything with fish syntax because I mostly use it for the REPL experience.
From the article...
> P.S. maybe fish is even better by the way, but I could never get over the bright colouring and all these things popping in and out of my screen; it's such a "busy" chaotic experience! I'd have to spend time disabling all that stuff to make it usable for me, and I never bothered – and if you're disabling the key selling points of a program then it's probably not intended for you anyway.
I have no idea what "things popping in and out of my screen" could possibly mean. I use Fish and it's a beautiful, calm experience.
It does all these "autocomplete" things; for example I type "ls" and then I automatically get "| less" after that (why "| less"? Not sure), also the colours of the text of what I'm typing is constantly changing based on what I typed.
I appreciate that some people really like this sort of thing and that great, but it's not for me – I find it very distracting and "chaotic". Completion is brilliant, but I only want it on-demand (same with autocomplete when writing code).
Not sure where it's getting that from, because I installed fish just for that comment and I don't recall running it any time recently (not for years), and even after deleting all "fish" related things I can find in my ~ it keeps doing it, hmm...
Fish is really great for an interactive shell, I love using it. Not so great when it comes to scripting though. For example I think there's still no way to easily exit a script on error except to manually check exit codes.
However I haven't tried writing scripts in zsh yet, just sh/bash.
I'm really far from a JS fanboy, but Python's whitespace thing really is annoying enough that I avoid it when possible.
Ruby'd be my pick for "most-pleasant scripting language" so long as I can avoid any libraries/frameworks that do too much metaprograming "gee, wouldn't you love to never be able to track down where this is defined?" crap. Maybe Lua, but deps are a bit of a pain to manage there, and it's a huge step down in third party library availability compared with the other three languages here mentioned.
Really, if not for up-to-date library availability being higher in other languages, I'd probably still be using Perl for everything. It feels more "command-line native" than the other options. Installed everywhere. The community's not too in-love with trendy, cute bullcrap. A project you wrote five years ago will probably still run fine with no effort. It's kinda like supercharged bash with way fewer footguns.
Beware that it was created at a time when many programs were written in plain text editors, with no syntax highlighting or autocompletion, with the printed-book language manual on your desk, on a screen that could legibly display only a small fraction of the text that a modern display can (or even ones that came along a few years later). Hence: sigils for variables (though unlike PHP these make some sense and are actually used to do things, and aren't just there because... well, because Perl did it, so PHP should too, I guess); single-character magical variables for very commonly-used loop- or input-related variables; et c.
I don't think anyone would make something quite like it these days, starting form scratch.
Sorry, I didn't mean to start a flame war. I find regexes, HOF, and especially asyncio/Promises much more palatable in JS, which are things I look forward to when moving out of shell scripting land.
> for anything more involved than a one-liner alias kind of script, I turn to python
I agree with this. If you can trust your users to have python3 in their environment, I don't see a good reason to use sh/bash instead of python. Non-trivial shell scripts are unreadable to me, and the semantics are too different from normal programming languages.
The only scenario I can see is a script that has to call multiple commands over and over, which can be a pain in python if you're piping a lot of stuff and all that. No, piping grep to cut and sed doesn't count; you can do that in python itself.
> If you can trust your users to have python3 in their environment, I don't see a good reason to use sh/bash instead of python
You would need to ensure said users have all the pip dependencies your python script imports as well. shell scripts don’t have that issue unless you’re calling some external program that isn’t installed.
>shell scripts don’t have that issue unless you’re calling some external program that isn’t installed.
Good thing that bash has such an extensive amount of standard libraries and that you're almost never calling external programs? And unlike Python, which is seemingly almost exclusively calling third-party libraries from PyPI?
I made dash my sh and started writing shell scripts targeting it instead of bash. If there's some bash-ism that can't be elegantly expressed in POSIX sh, it's time to bring out something like Python.
https://github.com/romkatv/powerlevel10k claims to do this (and other things) while remaining extremely fast (apparently the dev was aiming to keep it below the threshold of human perception; not 100% sure that extends to all features or not)
I suppose that's the nano equivalent:) And that's not the insult you might expect: It sometimes is viewed as a toy, but it actually has depth and does a lot more than one might expect, all while staying very user friendly, albeit at the expense of being rather different than the other major options.
Despite the similar names, what oh-my-fish and oh-my-zsh provide isn't comparable.
E.g. oh-my-zsh adds a bunch of keybindings and a git prompt, and sets a bunch of options (like turning on history deduplication, or comments in interactive sessions).
Fish already does equivalents of much of that, by default, so oh-my-fish doesn't have to do it. They mostly add more prompts (with their own, separate, git prompt implementation) and provide a package manager.
That means fish without oh-my-fish is already close to the experience of zsh with oh-my-zsh.
(disclaimer: I'm a fish developer, and not really involved with oh-my-fish - I sent a few courtesy patches over the years)
I very strongly disagree that you need or even want OMZ. You don't need it for Bash, why would you need it for Zsh?
It might be nice when you're getting started, so you don't have to learn all the ins and outs of the shell to have cool prompts and other useful functionality.
What I like best about zsh is how easy it makes normal tasks.
* dont know the full name of the folder you want to cd into? Type the 1st few characters, and press tab, zsh will let you select which one you want
* Oh, and search is case insensitive. No more spending hours banging at the tab, wondering why autocomplete isnt working, just because you typed "projects" instead of "Projects"
* history search is great-- type the 1st few lines of a command and press up, it will take you to the last time the command was used
* Oh my zsh gives you a lot of cool utilities-- instead of
I feel obligated to point out that this is mostly doable with BASH.
* I don't understand how this is different than usual tab-completion; might just be missing something. You can stick `set show-all-if-ambiguous on` in .inputrc to make it activate on 1 tab instead of 2.
I'm glad you like ZSH, for those wondering if you can do similar things in Bash (lots of non technical folks on the site these days).
* I have to admit I tend to run mlocate on my systems and grep for full file paths instead of using tab completion if I don't know what the directory path is. However if you want auto complete with a list to pick from like ZSH
bind 'set show-all-if-ambiguous on'
bind 'TAB:menu-complete'
in your inputrc will do the trick.
* echo set completion-ignore-case on | sudo tee -a /etc/inputrc
(use .bashrc instead of input if you only want to set this for your own user)
* ctrl + r type part of your command history search in bash
* alias gcam='git commit -a -m'
ZSH definitely has a lot of cool things (globbing is really nice for instance), but Bash is ubiquitous and it's good to know how to make your life easier if you are working on a server that doesn't have your fave shell installed.
Happy hacking!
When I started typing this there were no replies, I got distracted by a work slack, and came back to hit submit, and there are two basically duplicates. An excellent reminder that none of us are actually the main character so to speak.
Control+r is search and not quite the same, since it matches everywhere rather than just at the start (maybe there's a way to do it, but I never liked the way C-r behaved in the first place so I never checked). However, you can use "history-search-backward" (and -forward) in your inputrc to get it.
> Click to see NSFW content. Not suitable for children under 18!
click
> For example, this can be used to show the longest element in an array:
> print ${array[(r)${(l.${#${(O@)array//?/X}[1]}..?.)}]}
> NUL bytes aren’t that uncommon, think of e.g. find -print0, xargs -0, etc. That all works grand, right up to the point you try to assign it to a variable.
Well, duh. Environment variables are NUL-terminated.
I don't understand arguments like this. "Learn something new so that you can avoid learning something old". None of the arguments on this page show me that zsh has a shallower learning curve than bash - only that zsh makes "more sense", once you've learned it, to you.
It's perfectly valid and common to create variables that aren't environment variables. Variables silently dropping off characters causes problems for these use cases. For that reason, inextricably tying normal variables to environment variables would make them less useful, not more.
Real world code from numerous prominent open source projects begs to differ. Almost every random script I've opened in random open source projects made use of unexported shell variables. Take a look at the source repositories of the Linux kernel, Git, coreutils, GCC, LLVM, CMake, GDB, cURL, OpenSSL, Firefox, Chromium, Vim, and tmux. It's so prevalent that I don't even know why I have to point this out.
I tried switching to zsh, but I felt like out of the box it's almost the same as bash. I don't want to spend a lot of time configuring my shell, I want it to get out of my way and let me type commands.
I use fish shell mostly these days. Magic autocompletion is life-changing.
Whether you use bash, zsh, or most any other shell, back up your configuration files. On almost any internet-connected system, I'm three steps away from my setup:
1. Install prereqs with package manager (curl, git, zsh, vim)
2. Download and install oh-my-zsh
3. Pull .zshrc, .vimrc, etc. from Github
4. (optional) Add workarounds because Windows Terminal still can't handle color and cursor position codes.
People talk about zsh being more advanced than Bash as if that's a good thing. But remember the shell is mostly useful as an interface to other programs; it's not supposed to be filled with features. Bash has way too many features, I think, for the same reason zsh does: people thought it was philosophically superior to rely on the shell having built-in features, rather than finding or developing composeable applications..
That is, ironically, one of the main reasons I decided to switch to fish instead of zsh — it’s even less compatible with bash. I’ll never be in a position where a script is subtly broken because it’s running in zsh instead of bash. Rather, it’ll just fail altogether and I can work from there.
> I’ll never be in a position where a script is subtly broken because it’s running in zsh instead of bash
I would set the hashbang to "#!/usr/bin/env bash" and then bail out if $BASH_VERSION isn't set on the first line, while also naming the script "foo.bash" (rather than foo.sh).
This way you can "enforce" a specific interpreter. Scripts using #!/bin/sh while relying on bash features was a common annoyance when I used FreeBSD – although it's probably less common now that some Linuxes have started using dash and /bin/sh. I don't mind if people use bash, but please make it explicit, especially because these types of errors are not necessarily obvious to the user.
It's not just about "more advanced", it's also about being less surprising and having fewer caveats. And besides, if you're going to add things like globbing in your shell then you might as well do it in such a way to make it much more useful for common operations.
If you want less surprising and fewer caveats, use a POSIX shell. There is nothing less surprising than a program using a standard that's older than you are.
Is there a way to change the autocomplete key in zsh from tab to esc? I know it's a minor thing but it really does screw up my desire to switch. I looked around and the solution wasn't obvious or my google-fu is severally lacking.
Use what you like. I use zsh at home. At work, it is ksh, sh, or bash on the servers. I don't see that changing (except ksh going away when we replace Solaris) for a very long time, if ever.
I strive to use bash as minimally as possible anyway, I don't quite see the point of switching to zsh when I want to avoid doing things in the shell (rather than scripting or using cron jobs or similar).
Why would people want to use an alternate shell? How much do you need to be constantly sat in the shell typing out the same commands rather than either scripting them away or doing something within a text editor?
It's not that I don't like it, I just don't see the purpose of remaining there once I'm done writing a script or whatever. Your use-case is one of those where it does makes sense to do that.
I use those functions in both scripts and my interactive shell (which is bash).
I cannot do the same with zsh as my interactive shell if scripting is supposed to stay POSIX/bourne shell. Which I want as I often do this in systemwide scripts or modify existing scripts (all plain sh). The incompatibilities do matter.
I tried zsh and have not found anything that is truly superior to bash. After some investigation, it seems that this idea of moving from bash is based more on the fact that zsh is the default for Apple, due to their dispute with GNU licensing terms.
Honestly, all these shells are too simple or at least too generic-task oriented. For me, and I guess most users, the shell is not where I live, but where I do recipes. It would be nice to have a shell+vim style shell where you edit your command plan and can see its execution results right below the commands after pressing <C-CR>:
So you could move and edit pages (or OBTF) of recipes and see their status right there. My observation is that all you do in a shell is following some patterns again and again. For example, select last three commands and hit C-CR to execute them.
I agree, but I got pretty far towards this when I got fzf's ctrl+r replacement to search history quite nicely.
Before I used to also have a per folder history file, so I could pull up the last thing I did there. That also helped a lot, especially when switching between different personal projects and forgetting how it all works.
I tried zsh, but it was slower than bash in some cases and less convenient in others, so I switched to fish instead. It fixes some of the rough corners of most Bourne-derived shells, but also provides great user experience out of the box.
> But zsh doesn't even use readline, which is bananas to me. Sure, provide an alternative but at least support readline.
Why? ZLE is what makes zsh compelling for users. It provides a high level of customizability into command line editing that is simply not possible with bash. It powers zsh's most touted completion feature as well as popular plugins such as zsh-syntax-highlighting[1] and zsh-autosuggestions[2]. Replacing it with readline would be a sure way to lose a majority of users in a blink of an eye.
Absolutely not. Using readline means completion and keybinding customization would be limited to what bash can do, or even worse as is typical with other readline based REPLs. What you're asking for is to gut a definitive feature of zsh only so that it can be bash. That provides zero value to zsh users.
> Bash is not perfect and that's ok. It can't handle floating point math? bc can, so use it. That's the beauty of the Unix philosophy.
Nailed it. It's perfectly okay that bash doesn't have every feature under the sun because it's main goal is to let the user interact with the OS, which includes calling the right program for the right job.
No. Learn Bash. The biggest benefit of bash is that you're virtually guaranteed to find it on any linux server. It's ubiquity is its strength. Write a bash script once and take comfort in the fact that it will be able to run on virtually any server for years to come. Does bash have a bunch of cryptic syntax and weird quirks? Sure, but that cryptic syntax and those weird works should remain pretty stable in the coming decades.
Depends a bit why you're learning shell programming.
If you want to write scripts for your personal use, or maybe for some project you have then I recommend zsh as it's so much more useful and easier. The zsh User Guide is quite good by the way (not updated in a long time, but still relevant).
I you want to write shell scripts at $DAYJOB or some such bash is probably the wiser choice, as it's just more common. Whether that's a good or bad thing is kinda besides the point here; it just is.
I like bash completion much more than zsh. I tend to write all my scripts to be POSIX compliant anyhow, so zsh vs bash isn't really a thing for me. However, I can see how easy it would be as a Mac user to bash bash, because often people just feel the need for validation.
It is so unbelievably feature-rich that it's almost impossible to describe all of its features and merits, but you can safely ignore most of them and the basic day-to-day experience will just be "Bash, but better".
No program is perfect, but Zsh I think is pretty damn close. That's not because it doesn't have flaws (it definitely has flaws), it's because it does such a good job at doing what it does, that all its flaws seem minor and none of them fundamentally get in the way of it doing that job.
One additional thing I'll say is: it is probably the only Unix-style shell where I would feel comfortable writing 1000+-line applications with a CLI, tests, etc. I would never do such a thing with Bash or some minimal Posix-compatible shell. It's a very sensible system scripting language, alongside Python, Perl, etc.