The Case for Bash (2021)
neversaw.us
neversaw.us
But especially Sh runs everywhere, so it's good to know. This seems to be the article's point.
Honestly, though? The right answer is to transpile a better language to Sh.
Looking around, there have been several half-hearted, abandoned attempts. But apparently everyone is happy to just fall back on the "accidental syntax" of Bash that really doesn't make sense when compared to ... any other language.
I put up with Bash because there's no better alternative. That doesn't mean we should put up with Bash; just that we must put up with it at least to the point where it can run code written in a real language.
Regardless, the idea of a language that transpiles to bash is kinda interesting.
And it's not installed by default on all systems. It's especially not installed on systems like Alpine.
The point was never to "find a better shell." It was to run install (or other) scripts with some level of logic absolutely anywhere.
Yes. But it's also absolutely great in so many ways.
Yoo-nee-kode
But given that *nix tooling output defaults to text, it's hard to argue against a universal language that talks to all of it.
You sus out what you need to do by hand and then convert your history into a script.
¹I have manipulated arrays in Bash, proving this statement.
Cloudflare uses Go similarly as a scripting language. [2]
[1]: https://go.dev/solutions/google/sitereliability [2]: https://blog.cloudflare.com/using-go-as-a-scripting-language...
Not an official Google product of course but ...
>Bash is great, but when it comes to writing more complex scripts, many people prefer a more convenient programming language. JavaScript is a perfect choice, but the Node.js standard library requires additional hassle before using. The zx package provides useful wrappers around child_process, escapes arguments and gives sensible defaults.
I like this, but I think it's even better to just escape sh entirely. If you are on a system that supports sh, it almost certainly has a c99 compatible compiler which can be used to install lua(JIT)? or another better scripting language. I wrote a little script idempotently installs lua 5.4 (that I tried to include but HN formatting messed up) and then runs a luascript embedded in a heredoc. The first time I ran it, it took 5.4 seconds to print "Hello, world!" including downloading the lua source code from their website and compiling the whole thing. The second time it took 11ms. I really see no great reason to continue using sh for anything new except for the purpose of installing something else.
Personally, the way I avoid footguns is to use a functional style. Filter instead of conditions and map instead of loops. GNU Parallel makes this straightforward, including distributed commands across machines! Aside, it is easy to emulate relational algebra with grep, sed, and awk.
A good essay describing some of the advantages of this approach is: http://widgetsandshit.com/teddziuba/2010/10/taco-bell-progra...
But it's simply not realistic.
Examples:
- Embedded devices that may or may not have an internet connection
- Government classified networks that aren't allowed to grab ANY random software off of the internet
- Simply not wanting to add another dependency to an operation.
I'm a former Lua fan. I've ... written it off at this point. [1]
Honestly if I were to take the approach you describe, I'd likely instead have the shell script only look at the architecture and then choose one of 2-3 precompiled Go binaries. Talk about simple and fast...and the binaries could be hosted behind a firewall somewhere.
Or maybe I was thinking of it wrong. Just embed the various Go binaries into a tar file embedded right in the shell script[2], and choose the right one based on architecture. Done. :)
[1] https://realmensch.org/2016/05/28/goodbye-lua/
[2] https://www.xmodulo.com/embed-binary-file-bash-script.html
It's not meant to be a real language. It's meant to be a shell. Here, let me back that up and reverse it for you.
<JAVA> public Set<String> listFilesUsingJavaIO(String dir) { return Stream.of(new File(dir).listFiles()) .filter(file -> !file.isDirectory()) .map(File::getName) .collect(Collectors.toSet()); } //I'm not handling errors, and this doesn't actually print anything, it just lets me try to.
<SHELL> ls
Shell is meant to be a common way to do common tasks on commandline, and make a little script if you want to save that and run it again. I can do little things in a shell one-liner quick, that would take a page of python or perl.
I put up with my sports car because there's no better alternative. It doesn't mean I should put up with a sports car for transporting my piano, just that I must put up with it at least to the point where the sports car can transport me to the car that transports a piano.
And it's an objectively awful programming language. Sh is even worse.
Korn isn't installed absolutely everywhere like Sh is. It's not even installed in 1/100 of the locations that Bash is installed. So it being better is irrelevant to the discussion of Bash as a programming language.
Because Bash is so awful, the only real justification for using it as a programming language is that it's already installed. Since Korn isn't already installed, that means you'd need to install it. If you're going to be installing something, there are far better choices.
So your comment doesn't at all contribute to the discussion of alternatives to using Bash as a programming language.
And the verbosity of using Java to list a folder contents is equally irrelevant. No one would argue that Java is better ... for listing directory contents.
If you do want to write a script that calls a bunch of shell commands, or even that operates on folders and files, there are far better languages and tools.
See, for example, https://www.npmjs.com/package/tish
Or just Node/TypeScript in general.
If you want a better language for complex scripts, use Python, or even Perl, they are now ubiquitous, or bring someting self-contained like Lua or Janet with you.
It would be great to have a different ubiquitous shell language on Unix machines, but it's unrealistic now.
Having said that, Bash is my favorite programming language, because it is so limiting, and simultaneously not limiting, because it wasn't designed to fulfill what A Real Programmer(TM) thinks a programming language should be.
A computer is supposed to serve a human. Not the other way around. But modern programmers, being wildly unimaginative creatures of habit, think the user is supposed to serve the computer. That the more you type, and the more hoops you jump through, the more "clever" you are with the "way" you tell the machine to do something, that this is superior. Never mind that the end result of all this incantation and tom-foolery doesn't actually result in a good outcome half the time. That the user is often unhappy with the result, if they even factor into the programmer's thinking at all. No, the true purpose of programming is to gratify the programmer and make the machine happy, not to make a human's life easier.
Programming is a mistake. It's a half-measure. A stutter in the evolution of digital computing. We weren't meant to program, we were meant to make a synthetic invention that relieved us of our labor. And what do we have to show for spending 1/3 of our life, and decades of work, for it? A metal and plastic box that can do pretty much exactly the same real work as 30 years ago, but with better graphics, because the hardware people gave us better screens.
Look at it from an average user's perspective. They're thinking "Wow, this spreadsheet would have taken hours to do with a calculator and a pencil".
Most users are not programmers. Programming IS the act of making a synthetic invention that relieves us of our labor. It's no different from what a plumber does. If we had to connect pipes in a custom arrangement every time we wanted to do the dishes we would argue all day about the best pipes and threads and tapes.
But we don't. We use premade fixtures and we don't have to think about plumbing.
Those hoops you have to jump through are like the building codes. They make everything consistent. And more importantly, they make things inspectable. There are a million and one ways to plumb or frame a wall or something that would probably be fine. But that would be Real Engineering to do that and show that it's safe.
Wheras the codes are designed to not allow for "interesting" stuff that only an engineer would understand. The programming conventions developed over time based on programmers experience of what works and what doesn't when writing software that is so complicated not one person on earth could ever understand it.
The difference with programmers is they use programs to make programs. There's no user/designer distinction, and they all want to be creative and clever engineers, not just skilled tradespeople, so they don't see the benefits as much. If most of what you do is coding, you might thing nothing has advanced in the last 30 years, because you used text then and now you have more complicated text.
But from a user perspective, we went from drafting tables to CAD. We have Google Keep. We have voice assistants. We have 4k editing for consumers!
Bash pretty much just makes it explicit right in the language that there's no user/coder distinction and kind of keeps that mindset around.
Newer languages are designed to help people make user facing black boxes, and they do a great job of it it seems.
We make shit programs because our industry has no code, no apprentices, no journeymen, no masters. We have kids with no discipline who don't understand how anything works and just fake it 'til they make it. Everything from the shit designs to the inefficient basic programs to the overinflated titles to the lack of basic rigor shows that, generation after generation, we're actually getting worse at our profession, not better. We inherit good foundations and we shit on them. And we look at the people shitting stylistically and imitate them.
> we went from drafting tables to CAD. We have Google Keep. We have voice assistants. We have 4k editing for consumers!
We had CAD in the fucking 1960s!!! Google Keep is an overcomplicated frontend for RCS. I've literally programmed a better voice assistant in Perl in 1999. 4K isn't even usable on most devices, let alone necessary.
I literally knew more about software when I was 18 than any programmer I have met in the last 5 years. And I'm not that smart.
> Bash pretty much just makes it explicit right in the language that there's no user/coder distinction
Bash makes it explicit that there is no coder. There's only a user. Programmers don't seem to realize it, but the way they act, they don't consider that the user even exists. This is the defining characteristic of 21st century technology. Programmers are self-serving, and users are waiting for someone to make their lives easier. Like children waiting for the chefs to stop fucking around in the kitchen with sourdough starters and hydration ratios, and just bring out some god damn bread.
Deep learning voice recognition didn't exist in 1999, which is the critical tech that makes Assistant usable, along with the fact that there's now Keep to integrate with and IoT to control.
It's true that programmers hate users sometimes, but that's not an issue of tech or skill, that's an issue of programming culture is full of people who would like to go back to tech free simple living and mostly see computers as "bicycles for the mind" rather than "A UI layer for everything manmade we interact with" or "somewhat independent robots". They're in it for the exploration of thought, not because they want paying taxes to be fully automated.
But it's the managers and UI designers, and the small percent of programmers that don't hate tech, who actually direct a lot of the experience.
Bash is basically incapable of making anything I'd want as a user, because it's not good for managing insane amounts of complexity. It's a tool for directly controlling a computer and using it as a passive tool.
Modern languages are tools for essentially using the computer almost as a manager more than an employee, and for creating new models of interaction that the machine is better able to always have your back with.
If I want a block to be 35mm tall in total, but there's terraced layers and curves and stuff, I set a constraint.
I don't have to worry about the math. I'm not just virtually building stuff like a digital simulation of wood, I'm doing something completely synthetic, that was made up by someone who figured out a model for interacting that puts as much of the hard parts in the CPU instead of in the brain. It can't just be one way "I tell the machine what to do" interaction or the whole thing is limited to my own ability just like paper.
It can't be text based or anything else that requires mental translation, or I'd just have an extra task in addition to doing the whole design myself.
Modern software's solution is to create a way to think about the task that is inherently something suited for both humans and machines, and then to teach the user that way. Bash is freeform, it says "think how you want then translate it to what machines know".
It's great if you want control, not great for computer-led experiences.
Sadly it doesn't seem like we are designing many new paradigms of interaction, but I blame lack of interest rather than lack of tech ability. Programmers think about the functionality first, rather than about the interaction model, because they like ideas and dislike software used in the real world when a pencil could have done instead.
We do have building codes. Unit tests, memory safe languages, design patterns, DRY, reuse... It's just that lots of devs don't like them.
Meh. That part is irrelevant in the extreme to the discussion.
Fact is that people write `.sh` and `.bash` scripts to get things done. They use conditional logic and loops and everything.
Functionally, it's a programming language with a (useful) REPL.
And this discussion is 100% about how bash is as a programming language.
> Having said that, Bash is my favorite programming language, because it is so limiting, and simultaneously not limiting, because it wasn't designed to fulfill what A Real Programmer(TM) thinks a programming language should be.
Umm... Then, yeah, I have to agree with your self-assessment of not being a "real programmer."
Sorry, but it's obviously true.
You're a scripter. I've known and worked with many scripters. They can do a lot of valuable work and contribute in a lot of ways to various projects. But they're not programmers.
Which is fine. Script away.
> But modern programmers, being wildly unimaginative creatures of habit, think the user is supposed to serve the computer.
After admitting you're not a programmer, you then go on to demonstrate that you don't really understand how programming works. Which I guess follows?
That's so far from the reality, that it's Not Even Wrong.
> That the more you type, and the more hoops you jump through, the more "clever" you are with the "way" you tell the machine to do something, that this is superior.
Programming languages are exactly the level of complexity needed to, with the least effort and most reliability, craft the code that, literally, civilization relies on.
It can't be made simpler. The complexity is inherent in the domain.
> Programming is a mistake. It's a half-measure. A stutter in the evolution of digital computing. We weren't meant to program, ...
YOU weren't meant to program. You admitted that above. Don't speak for the rest of us.
Everything significant in software that has been and will be accomplished moving forward will be created by programmers using programming languages. Your rant against programmers and programming is based on a lack of understanding of what programming really is and how programmers really think.
It's not even slightly about being clever. It's about the act of primal creation. We're effectively wizards of the modern world. We don't care at all about making a machine happy; we care about building things that work. That the things embody complexity isn't the goal, but it is a thing of beauty when it all works together elegantly.
If I'm going to install a language/shell, I'll likely choose Node, Bun, or Go, depending.
Python isn't actually a much better language from a language design standpoint.
Lisp and Scheme would like to have a word . . .
I hate Lisp.
'nough said.
Normally I’m not very fond of Gos verbosity, especially compared to bash, but i take anything over bash, it is just so filled with footguns, every single line has to be super carefully scrutinized. Note that I say this as someone actually quite good at it, not because I don’t understand it. In fact the more you learn, the more scared you get.
Subjectively, not objectively.
A debugger won't "step in" unless you..."step in" instead of "step over."
STL is also no longer a thing. There's only the C++ standard library. So I think you're dating your usage of C++ by ... a decade or more?
A lot of things that one would use bash for involve servers, and there's Ansible for that, no need to use bash directly most of the time. If it were up to me, distros would include Ansible out of the box.
IMHO doing anything at all that's not programmatically repeatable isn't the best practice, so it's not like I want to be logging onto something and writing a quick script in Nano.
Outside of servers.... nobody uses Linux except embedded systems and FOSS enthusiasts. Nobody is paying me to do FOSS work, so I can just pretend that Pythonless systems don't exist.
Python does have some version compatibility issues, but for the most part it's stable, and at least it will crash with a traceback not a cryptic error code because you used some utility that had some nonstandard syntax for the flags.
On embedded, most of what you're doing is probably simple enough that if you do need to use sh you won't absolutely scream.
So, no, it's not "objectively" awful. At most it's "subjectively" awful for some people.
https://pubs.opengroup.org/onlinepubs/9699919799/utilities/V...
Your script will use sh if it starts with #!/bin/sh (instead of #!/bin/bash).
I can also recommend ShellCheck which is a shell script analysis tool (implemented in Haskell) which can find errors and potential problems in your scripts. On a Debian system, installing ShellScheck is as simple as `sudo apt install shellcheck'.
I don't buy this argument. Ok, it's a standard, but sometimes it's a pain-in-the-hole standard. Bash augmentations lessen some of the pains, and it's just nicer. The only situation is if you're using busybox in a very limited system or you reeeeeally need your script to run on many difference unices, which let's be honest, is not that common nowadays.
The Debian people were concerned, for starters, with how much time the Bourne Again shell spent, at process initialization, setting up things for extensions and interactive features that were never employed in non-interactive "sh" mode; a significant cause for concern given how much of the system was executable shell scripts.
Curious, what are the 3 things ?
But yes, people should always use sh (or ksh) for scripting as opposed to bash, why, it is far more portable to other systems.
* https://bugs.debian.org/cgi-bin/bugreport.cgi?bug=294962
They've added 2 more since.
* https://www.debian.org/doc/debian-policy/ch-files.html#scrip...
Strictly speaking, there are other differences between the Debian Almquist shell and a truly SUS conformant sh.
* https://unix.stackexchange.com/q/697007/5132
But the aforementioned are what Debian people explicitly wanted and couldn't live without. Note that the StackExchange answer is a comparison of a 2022 dash to a 2017 standard, neither of which existed at the time. Debian Policy, the Debian Almquist shell, and the POSIX standard have all been revised since then.
Both sh and bash are terrible, ugly hacks. Anything that requires more than 3 lines of them (including the #! line) should be written in a proper scripting language.
Though I do agree that cross-platformity to that level is rarely meaningful, and so things like python are just as likely available/can be made available.
But I know that you meant it as a rhetoric, so my non-rhetorical answer would be that python3 is almost universally available on distros that are not minified deliberately (containers).
Writing control scripts for use inside containers is currently my biggest application of BASH scripts. What makes BASH (or other shells) handy is the lack of supporting files that are needed - just copy in the script and you're ready to go.
Which version of Bash?
I'm not sure there is a good alternative to sh/bash shell scripts as most dynamic languages have become pretty large dependencies these days.
Any way for: python - 75M perl - 59M ruby - 25M bash - 8M dash - 0.15M
...that said, having to support bash 3.2 for MacOS is a horrible thing.
Due to docker, minimalistic Linux'es have been more common, including trimmed-down versions of distros that would otherwise feature bash.
ShellCheck is absolutely the most important thing to use when writing scripts. Each time that you get a warning that you don't recognise is a learning opportunity - just follow the link provided in the warning. Also, take the time to add the "# shellcheck disable=" comments so that scripts don't issue any warnings from shellcheck.
I also recommend visiting https://mywiki.wooledge.org/BashFAQ for avoiding the many footguns, especially with filename parsing.
POSIX also has $(command) for command substitution. It can certainly be annoying when it's not used, though. I think shellcheck will recommend it over backticks.
Edit: After looking for a problem with POSIX, I found this obscure issue that may well catch out people as under POSIX, IFS is a terminator rather than a separator: https://mywiki.wooledge.org/BashPitfalls#IFS.3D.2C_read_-ra_...
I always use $(...) or rather "$(...)" instead of backticks.
Command substitution shall occur when the command is enclosed as follows:
$(command)
or (backquoted version):
`command`
https://pubs.opengroup.org/onlinepubs/9699919799/utilities/V...https://stackoverflow.com/questions/11376975/is-there-a-mini...
It would be much more correct to say that bash's features are a superset of the shell features standardised by the POSIX standard, as is the case for several other shells (each adding their own parts on top of the standard) too.
Nah I like using bash features and all the systems that I care about have /bin/bash so it doesn't matter.
Meaning /bin/sh is not more portable because you can’t be sure which shell will be used.
Applications should note that the standard PATH to the shell cannot be assumed to be either /bin/sh or /usr/bin/sh, and should be determined by interrogation of the PATH returned by getconf PATH, ensuring that the returned pathname is an absolute pathname and not a shell built-in.
I don't know how this trend started but now it's been cargo-culted to death. Same with the /usr/bin/env thing in shebangs. shellcheck --shell sh myscript.shExactly. The title is just plain wrong. Furthermore, TFA does not argue a "case", it simply tries to specify the language.
or as I've taken to calling it, Ba + sh
echo "$(tr -dc A-Za-z0-9 </dev/urandom | dd count=1 bs=16 2>/dev/null)"
^-- SC2005 (style): Useless echo? Instead of 'echo $(cmd)', just use 'cmd'.
I have experienced so many situations where shellcheck is giving harmful advice or warns me about the thing that is exactly my intention. printf "%s\n" "$(tr -dc A-Za-z0-9 </dev/urandom | dd count=1 bs=16 2>/dev/null)"
If you don't require the line feed, then you can just use: tr -dc A-Za-z0-9 </dev/urandom | dd count=1 bs=16 2>/dev/nullAnd i do require the line feed. Following shellcheck's advice broke the code.
Just tested it and ShellCheck is happy with the printf version:
printf "%s\n" "$(tr -dc A-Za-z0-9 </dev/urandom | dd count=1 bs=16 2>/dev/null)"
It's also got a stylistic improvement (in my opinion, anyhow) in that the line feed is explicit in the printf command rather than being almost a side effect of echo.Edit: I see your point about the "tr" not enabling a dash, so my example falls a bit flat, but my point stands in other scenarios. It's good practise to avoid echo where feasible. I think the double quotes will also protect in this instance. The problem with relying on the tr string to not foul up echo is that you may later adapt the code and use a different translate string which could then introduce a tricky bug to track down.
# shellcheck disable=SC2005
tr -dc A-Za-z0-9 </dev/urandom | dd count=1 bs=16 2>/dev/null; echo
A few less characters to type, you get your newline, and shellcheck doesn't complain.
The call to echo has a likely purpose is to trim some spaces from a string, but it's not at all obvious why this is done or what the benefit is. In the best case, it leaves the reader pondering this weird semi-no-op.
If you think shellcheck, and probably most readers, is wrong in this assertion, just speak your mind and let's learn from one another. But I certainly can't guess your intention here.
(I can't actually recall encountering a filename that includes a linefeed)
Is Bash just surviving because no one knows about it or has remembers it while its been hiding in your system for 20 years?
The massive disadvantage of bash is that it uses a legal filename character " " as a delimiter ("$IFS"). That means it's very easy to write scripts which work fine on your test case but blow up in a different context. And the worst case of that is accidentally deleting the user's file system.
So many of the cute little bash examples you'll see, including I think the awk one on this page, will go wrong if you have a space in the wrong place in your filesystem. Or, god help you, a newline. Or a file named "*". Or "--".
Just don't write bugs, what's so hard about shell programming?
Getting the 10% of the cases that have whitespace or metacharacters in variable values and filenames to work ... takes the other 90% of the time. (-:
I habitually quote variable expansions, even when I know that the variables are never going to contain whitespace. Because long experience has taught me that one day they will.
Just like I've learned never to assume that ${PATH} is always non-blank and so assume that PATH="${PATH}:/something" won't accidentally add the current directory to my search path. (-:
Lots of things written more recently have shown that support for more, eh, inclusive, character encodings can work just fine.
Anyway: When I can "build up from the command line", then apparently whatever I'm building up can be easily and intuitively done in the command line. So, stuffing it into a bash script is fast and easy and should be easy to understand as well.
I just have real-world applications for small scripts that aren't much bigger than those "cute little bash examples". When you work with embedded systems, you do a lot of repeatable stuff via SSH. I once ported an Installer to Python and now I hate it because it has so much unhelpful noise between the actual relevant commands being executed.
Yes, Bash has it's issues and requires some discipline, but so do most other languages.
It looks like a use-case for fabric https://docs.fabfile.org/en/stable/getting-started.html#adde... It is the best of both worlds: shell pipeline for one-liners, Python — for complex logic.
Or if more declarative approach works in you case then something like ansible could help https://docs.ansible.com/ansible/latest/getting_started/inde...
On the very rare occasions I'm using an external dataset that includes them I'll make a copy with renamed files using underscores.
If more junior team members produce files with spaces in their paths I quickly educate them in how problematic it can be.
It's pretty rare this is a problem for me or my team, perhaps we're lucky in what we work on though.
Thankfully I have never seen a file named "*" or "--".
If you have a file whose contents are Shakespeare's "As You Like It", you should be able to name that file "As You Like It" without having to worry that the computer will vomit and die on it. We deserve better tools.
Another common footgun is not including a double dash ('--') at the end of command options so that the following filename won't be interpreted as an option when it begins with a dash.
Yes. And if you have a file name that begins with a dash, and you want to delete it, you can use this command:
$ rm -- -filename
where -filename is that file. Here, the -- (double dash) means "end of the command-line options", so any argument after that is treated as a non-option argument, in this case, a filename for rm to delete.
This -- (double dash) works with many Unix commands. It was introduced relatively early on in Unix history, after the issue of filenames starting with a dash was found.
Although it is more likely that you would want to rename the file, to remove the dash from the name.
There are places in Makefiles where no amount of quoting will help you, and it simply cannot handle files with spaces.
Yeah, this is a problem with the tool that should be fixed, but my understanding is that it will never be fixed because of architectural reasons. And it's a very popular, useful, and most importantly, ubiquitous tool.
So this is another good reason to simply have a policy of banning filenames with spaces (and there are many more reasons).
I find the bash reference manual to be nice enough for me.
https://www.gnu.org/savannah-checkouts/gnu/bash/manual/html_...
Generally my order of preference for shell scripting tasks is: python > bash > pearl > tcl > sh > anything > AppleTalk.
If your shell script keeps breaking, and you keep fixing it by writing more shell, then you're in an abusive relationship with your programming environment.
Exactly. Shell scripting is basically a non-hygenic macro language, with very complex semantics around its primary data type, the string. It also lacks pretty much all of the affordances we use to write reliable programs. I have no doubt someone has written a typechecker or unit test framework for bash, but I've never seen one used. I'll take the ugly code with the straightforward semantics, thanks.
I see no reason why distros like ubuntu or kali would default to zsh.I use the shell for sysadmin stuff, anything complex or app specific gets at least a python treatment.
The whole thing reminds me of python 2->3 or sysv init to systemd. There must be people somewhere deeply invested in these changes and their voice is certainly much more valuable than the average opensource joe user. Defaults matter and the time I and millions had to spend learning to transition for no direct benefit to us has value. The whole distro model of everyone getting a distro they like falls apart when not enough people can fork.
Overall, this devalues opensource as an investment. Because of unpredictable hidden costs like this.
Its power lies in its simplicity and direct access to system level functions, which makes it an efficient tool for system administrators and developers working in DevOps. It's excellent for automating small to medium-sized tasks on UNIX-like systems where the overhead of a full-fledged language would be overkill.
While it's true that bash has its limitations, the value of its ubiquity cannot be overstated. The bash shell is everywhere - from massive server clusters to tiny embedded systems. This means that a bash script written on one system is very likely to work unchanged on another.
TBH it's among the many reasons I've return to bash from zsh
I want to experience first hand the limitations I may hit on other servers.
I also try to use #!/bin/ash in my scripts to live life in hard mode :)
I have a love/hate relationship with is. I started with computers in 2000 and wrote a ton of bash for various things. Compiling software, running cicd, adhoc batch jobs, etc.
I've managed many cicd pipelines with bash in my time. While it's not great, once you know the basics, you can be very successful. That being said, rewriting the bash "program" in another language is much advised. If your bash is over a few hundred lines, chances are you've outgrown it
Some things in bash (mainly pipes and redirection) are just too easy. Job control is also great. The 'process' and 'shell script' are two primitives that make a lot possible.
Trying to do pipes or redirection in python is awful. There are a ton of subtle bugs you have to worry about
I still write bash and love it for the simple things. Now I have the experience to rewrite it early before it gets out of hand.
Of course, they still would not have covered all possible issues known even at that time, because that was not the focus of the book, which was to be a Unix tutorial, not just on shell, but many other commands, general Unix usage, and the environment too (hence the title of the book).
But they did cover and warn about some issues, including giving solutions.
Sounds like a case for shells in general. No other script-execution tools make running other programs (and linking their inputs and outputs) so simple.
To overcome Bash’s problems, one can choose another shell. But I suppose you can’t just pick your personal favorite and script in its dialect, because then how would the human tasked with integrating the script into $wherever know which shells to have installed? You can stick with sh which is guaranteed to be installed, or bash (indeed an improvement over sh) which is nearly guaranteed to be installed.
Of course, after a decade of incremental tinkering I ended up with exactly what you anecdon't want: ~2500 lines of shell script and ~1000 lines of awk... LOC misleads of course, there were 15 modular plugins, so there was a lot of boilerplate, and this includes non-runtime ~1000 lines for install, self-test and sanity checks.
* https://wiki.ubuntu.com/DashAsBinSh
* https://wiki.ubuntu.com/DashAsBinSh/Spec
* https://wiki.debian.org/BootProcessSpeedup#Using_a_faster_sy...
* https://lists.debian.org/debian-release/2007/07/msg00027.htm...
* https://lwn.net/Articles/343924/
* https://www.debian.org/doc/manuals/debian-reference/ch12.en....
The other concerns seem to have a very simple solution: use bash in the shebang instead of sh if your script uses bashisms.
As for efficiency, recall that there were hundreds of /bin/sh scripts in startup, administration, and everyday operation of the system, from all of the /etc/rc.d/ (sub)scripts through things in cron and build systems and package install/deinstall scripts to bunches of commands that were /bin/sh scripts under the covers. So multiply that difference by several orders of magnitude.
Amusingly, this is to a major extent still true. On these operating systems systemd is nowadays parsing .INI files over and over with its own custom .INI file parser, instead of a chosen flavour of /bin/sh parsing shell scripts over and over with whatever (Bison/YACC) parser they were using. They haven't actually gone down the SunOS SMF (and s6) route of compiling things into a machine-readable database. And there are still lots of /bin/sh scripts elsewhere, as service management is only one of several areas.
An interesting alternate history would have been if the Z shell had been /bin/sh on Debian and Ubuntu instead of the Bourne Again shell when this came up, and how much of a difference employing zcompile everywhere would have made.
And one of the links leads to a savings of 1 second on an eeepc. Not very convincing.
To make any progress away from bash, we need to standardize on “all mature shells in the base system” (for some definition of ‘mature’) as default.
As I said, this was over a decade ago. The Debian Almquist shell has been a required part of the operating system for Ubuntu and Debian, and several (perhaps all) of their derivatives, for all of that time. They switched it over to a mandatory ("essential") package years ago.
The Almquist shell is the /bin/sh on NetBSD. A slightly variant Debian Almquist shell is the /bin/sh on FreeBSD. The Debian Almquist shell is in Core in Arch Linux (and Parabola Linux and Hyperbola Linux). The Almquist shell is the sh in busybox. The Almquist shell is the sh in MINIX 3.
One has to travel quite far to the likes of Illumos or OpenBSD or Toybox on Android to find an operating system where it's not in the box as a core part of the operating system. And on several of the ones where it is a core part, it's actually /bin/sh already.
https://chrisdone.com/posts/shell-conduit/
It uses a Haskell streaming library so you can do stuff like shell pipelines and file redirection (but in a more structured, safer and more powerful way)
Except a lot of the times you don’t need this. The standard library may have what you need. There is no reason to pipe ls through jc, when you could use os.listdir().
The shell is great cuz it’s there and it can cover about 50% of use cases for server side automation, but it’s bad for the reasons mentioned above.
I love it and I hate it.
The `sort` blocks until input is closed yet `tail -f` never closes its output.
The original from TFA:
`tail -fq /var/log/nginx/access.log | awk '{print $7}' | sort | uniq -c | sort -rn`
can be replaced with:
`awk '{ a[$7]++ } END { for (i in a) print " " a[i] " " i }' /var/log/nginx/access.log | sort -rn`
or, if you have GNU awk:
`awk '{ a[$7]++ } END { PROCINFO["sorted_in"] = "@val_num_desc"; for (i in a) print " " a[i] " " i }' /var/log/nginx/access.log`
As a serious programmer, i do TDD even with bash, it is called bats-core
As a serious programer I use the right tool for the right job, so I write bash scripts when it makes sense.
Having done all these, I rearly write bash programs, but when I do, it is a joy! Plus i have gteater terminal understanding which is useful beyond bash programming.
Not Ruby!
sh = Shell.cd('/')
sh.transact do
system('tail -fq /var/log/nginx/access.log') | system("awk '{print $7}" | system("sort") | system("uniq -c") | system("sort -rn")
end
Pretty sure one could get without the system method with Shell having def method_missing(meth, * args) to call system([meth.to_s, * args]) or something, turning it into: sh.transact do
tail '-fq' '/var/log/nginx/access.log' | awk '{print $7}' | sort | uniq '-c' | sort '-rn'
end
https://github.com/ruby/shell#pipe-etcprintcap-into-a-file2. They make a terrible language the most convenient language.
3. People use the platform, along with the terrible language.
4. This terrible language is now everywhere.
5. The argument for using this terrible language is #4
Her articles often hit HN front page: https://news.ycombinator.com/from?site=jvns.ca
Indeed, in JS you wouldn't use awk and sort, you'd use JS array functions. It's fine to have examples that are contrived in the sense that the problem is contrived, but the solution should not be.
The much earlier more powerful ITS (Incompatible Timesharing System) shell DDT (whose job name was HACTRN) had an integrated PDP-10 machine language debugger / assembler / disassembler, so you could interactively or batch "script" and patch DDT and even other jobs in full blown PDP-10 assembly language.
Why invent yet another half assed "scripting" language that no other jobs in the system are using, when you can use the same fully powerful machine language that every job in the system is using? And if you need to do anything complicated, there's always LISP!
DDT's syntax was even more obscure than bash (and only a bit less obscure than TECO), but it was much more powerful and elegant, unleashing the full undiluted power of the PDP-10 at your fingertips.
https://github.com/PDP-10/its/blob/master/doc/_info_/ddtord....
You could also write DDT commands in text files like your login file, i.e. assembling a few lines of code to print the prompt by making a system call to get the time and format it as text.
You could examine and deposit code and data, load and change symbols, set breakpoints, in your own and even other user's running jobs (processes)!
You could disconnect without logging out (accidentally or not) and all your jobs would stay around until you logged back in, at which time you could reattach your job tree and continue what you were doing, all without running something like "screen" -- that crucial feature was just built in and always worked.
You could also pass ownership of jobs (like a running ZORK game or LISP interpreter or FOOBAR feeper) back and forth between users to share, like passing a joint. ;)
https://news.ycombinator.com/item?id=22840639
>It helped that ITS had no security whatsoever! But it had some very obscure commands, like $$^R (literally: two escapes followed by a control-R).
>There was an obscure symbol that went with it called "DPSTOK" ("DePoSiT OK", presumably) that, if you set it to -1, allowed you to type $$^R to mess with other people's jobs, dynamically patch their code, etc. (The DDT top level shell had a built-in assembler/debugger, and anyone could read anybody else's job's memory, but you needed to use $$^R to enable writing).
http://www.poppyfields.net/filks/00117.html
The HACTRN
Original song: The Raven
Original artist: Edgar Allan Poe
Filk author: Guy L. Steele Jr.
Intro: Notes for those not familar with the terms in this poem -- see below
[...]DDT ("dee dee tee")
HACTRN ("hack-tran") = top level debugging and job controlling procedure, capable of controlling up to eight simultaneous jobs (which may themselves be DDTs!) and performing other miscellaneous functions. HACTRN specifically denotes a DDT at the top of a job tree, while DDT is the more general term. The two terms refer to the same job in the poem, and are thus treated as synonymous. Note that DDT requires its subjobs to have unique names for obvious reasons; hence the concern over seven jobs all named FOO.
Are you an Emacser?