What it really seems like the issue is is:<p>1. Scripts should be maintained and tested, and<p>2. The language used for scripts necessarily is and should be treated as <i>a</i> project language, and the appropriateness of that choice should have all the factors that go into choosing a langauge for any other purpose, including the impact on complexity if it isn't the main language and fitness for purpose and any additional dev platform, tooling, etc., constraints it imposes, but...<p>This doesn't imply scripts should be in the main project language, any more than it is generally the case that projects must be monolingual.
I do that in my Go projects.<p>In fact my "scripts" are actually part of the main executable. I use cmd-line args to invoke the needed functionality.<p>For example, in the past I would have written a Python script to deploy my Go binary to a server, possibly using tools like Fabric that provide functionality to make it easier.<p>Today I add `-deploy-hetzner` cmd-line to my Go binary and it does the work. It builds itself, copies the binary to the server, kills the old instances, configures caddy if needed, starts newly uploaded instance etc.<p>For example my deploy.go is 409 lines of code, which is not that bad. You can see exactly how this works: <a href="https://github.com/kjk/edna/blob/main/server/deploy.go">https://github.com/kjk/edna/blob/main/server/deploy.go</a><p>I standardized on how I deploy things so deploy.go is mostly re-used among several projects.<p>Writing this code isn't much more difficult that what I used to write in Python.<p>This kind of code can be shorter because I don't have to handle errors, I just panic if something goes wrong.<p>I like that I don't have to switch between different languages and that I have full control and understanding over what happens. Fabric used to be a bit of a black box.<p>I even wrote an article about this idea: <a href="https://blog.kowalczyk.info/article/4b1f9201181340099b698246857ea98d/using-go-instead-of-bash-for-scripts.html" rel="nofollow">https://blog.kowalczyk.info/article/4b1f9201181340099b698246...</a>
Use the right tool for the job. The authors complaints seem to stem from not properly maintaining the supporting scripts in their project, which isn't at all a function of the language.
Scripts should be commented with intent. (I can figure out what it is doing, but I have no clue WHY)<p>Scripts should be testable.<p>Scripts should use functions with appropriately scoped variables. (This really helps with testability)<p>Scripts should list assumptions.<p>Scripts should CHECK assumptions.<p>Scripts should call commands with --long-style-options instead of -L, especially uncommon options.<p>---<p>As someone who migrated a couple hundred shell scripts over the past year, I'd rather have these done before I ask someone to write a script in C.<p>edit: and for ${deity} sake, use shellcheck.
The authors main bullet points are:<p>> The learning curve is minimal since you already know the corners of the language.<p>Learning a new language shouldn't be difficult. Programmers are expected to familiarize themselves with new tech.<p>> Internal language APIs can be leveraged, which drastically changes the mental model to write the script (for the better).<p>This is true. I myself have encountered situations where I needed to call into my C API's from a higher-level language, but since most languages can interface with C this hasn't been an issue for me. For example I've interop'd Go+C, Python+C, and Lua+C.<p>> Scripts feel more natural and eventually maintainability increases. Team members are familiarized with the language!<p>This sounds like a subjective rehash of the first point.<p>> Development machines compatibility increases. Windows users can finally run all scripts.<p>This is true if you're talking about shell scripting, but if you're scripting with a general purpose programming language then it shouldn't be an issue. What language (besides shell) isn't portable these days? And even then, you can install a *nix environment on Windows.
I have a C++ project where the documentation is generated using a script that I wrote in C++. Woof. I didn't want to add a compile-time dependency on another programming language, but C++ is rough as a scripting language. If my script needs were any more complex, I'd be thinking hard about how bad a compile-time dependency on Python really is.
No! General purpose scripts should almost always be written in bash. It's basically the best language for doing simple things with files, it's universally available and it makes almost no assumptions about the environment in which it executes.<p>Have windows users use WSL (the VSCode integration is great!), and mac users should install GNU tools since the system tools are obnoxiously incompatible.<p>The only time I've found that scripts should be in another language is:<p>1. You need to call libs that to do something fancy and it would be too troublesome to make a small Unix style executable to do the thing.
2. The developers on your team lack Unix/bash experience, and you don't trust them to learn in a timely manner (sad).
> If they can use the main language, awesome. If they can’t, a higher-level scripting language with native support (e.g., Python) should be adopted, since it provides the means to increase maintainability in the long run.<p>I think this point is especially important for C++ projects. It is my gut feeling that C++ and Python cluster very closely in terms of developer familiarity. That is, a C++ developer very likely is also a passable Python developer.<p>Given that it tends to take more time to write a C++ program than the equivalent Python program, the stable result is that many C++ projects 1) expose C++ to Python (via e.g. pybind11) and 2) write all scripts in Python.<p>And you get almost all of the benefits that the article suggests, because almost all C++ developers are also Python developers.
Disagreed from me. Though, I'm not sure how hard my disagreement truly is.<p>Discrete programs are superior in many ways, as you do not immediately incur maintenance costs to write them. More, they typically force you to have a discrete API that they work with, and then you can lean on that.<p>Yes, you can do all of this with modular programming techniques. Indeed, "unit tests" are easy to see as similar to what I'm advocating here. Such that I think my assertion is softer than many folks are probably seeing. If you are "scripting" something to add data to the system, it should emphatically <i>not</i> hit the database directly.<p>I don't know where this lands me on the infrastructure as code (IAS) debate. I'm sympathetic to the desire. I start to think of it as navel gazing when I see some of the very engineered testing practices some people take those to.
> For example, writing scripts on JVM languages would require additional effort to build a toolchain that compiles and runs files on the fly, with a short start time.<p>That hasn't been true since at least Java 11. You can execute any .java file using `java foo.java`. No compilation required. You can reference dependencies using the usual classpath options etc.<p>Startup time is minimal.<p>Been using such scripts in exactly the way the author suggests for years. Much more pleasant than messing around with maven or gradle plugins.
I think while the basic idea of advocating for stack consistency between the main project and any support scripts is very nice, I always find it not worthy to pursue in practice for several reasons: a lot of the “environment” around the main codebase will be a weird mix of YAML, bash and a scripting language like Python or Ruby for things like gitlab, airflow, GitHub actions, etc.
Given this heterogeneity of the project environment and additional complexity of “forcing” something to use a language it might not be very well suited for, makes this really a no brainer for me: use the most convenient tool for the job. Plus as a JVM bound developer I love me my occasional Python
Keeping scripts in the main project language usually means the monolith gets larger and more like a final boss than something the team controls.<p>Use the language that best fits the job. For me that's TypeScript on a serverless platform, SwiftUI front-end and Java for business logic.<p>I used to feel like I was winning using only Java for UI, Web and business logic, but the complexity became immense. It's too easy to create yet-another-internal-API that gets forgotten until it needs to be refactored.<p>Also learning a language gives you a new perspective on software engineering, a bit like learning a foreign language gives a new view on human culture.
I like to take this a step further and try to have all my most important tooling be written in my projects major languages. It's not a must have, but having a build system for a go project that is also go under the hood means I'm more comfortable diving into the source if needed.<p>I don't think it's a requirement, but it's an advantage
> The learning curve is minimal since you already know the corners of the language<p>But that does not imply you know how to get the creation date of a file or how to zip a directory.<p>> Internal language APIs can be leveraged, which drastically changes the mental model to write the script (for the better)<p>That sounds like a rather empty statement.<p>> Scripts feel more natural and eventually maintainability increases. Team members are familiarized with the language!<p>Don't you think familiarity with the OS (or OSes) comes first? And that knowledge usually comes with the knowledge of a shell or batch language.<p>> Development machines compatibility increases. Windows users can finally run all scripts.<p>Script development time increases, too. And Windows has WSL nowadays.
For some languages this can make sense, but I think most languages aren't suited for installing things, manipulating the filesystem, etc.<p>I think of scripts as the middleware between the operating system and the shipped code. The code is controlled by the operating system, so the operating system's tools should be used to manage it. In many cases this means bash or make.<p>Plus, I don't want modern Javascript to do things on the filesystem that would require importing dozens of projects that I need to vet before using. Golang or Python perhaps, but the buildchain for modern Javascript is hell as-is; it doesn't need another layer of Javascript.
I've always found it weird that the NPM ecosystem doesn't have something like Rake from the Ruby world to run tasks. Javascript things tend to be VERY task heavy, with dev servers, bundlers, testing, and coverage all being defined in the project normally.<p>The package.json scripts "work", but it's quite clunky, and relying on shell scripts that run node.js scripts causes issues. (cross-env solving a problem that really shouldn't exist.)
I usually go with the Google Shell Style Guide: <a href="https://google.github.io/styleguide/shellguide.html" rel="nofollow">https://google.github.io/styleguide/shellguide.html</a><p><pre><code> * If performance matters, use something other than shell.
* If you are writing a script that is more than 100 lines long, or that uses non-straightforward control flow logic, you should rewrite it in a more structured language now. Bear in mind that scripts grow. Rewrite your script early to avoid a more time-consuming rewrite at a later date.
* When assessing the complexity of your code (e.g. to decide whether to switch languages) consider whether the code is easily maintainable by people other than its author.</code></pre>
Absolutely not. Use the right tool for the job. There's nothing particularly wrong with shell scripts, as long as the people who maintain them are reasonably good at not making them into a dangerous nightmare waiting to delete your home directory or something.
Using the same language allows for eventually placing those scripts in background jobs.<p>In Ruby/Rails, the concept of ad-hoc script execution is baked in via rake.
For Java projects, scripts can be written in a JVM language like Clojure or JRuby. Can still leverage the Java code in the rest of your project, while writing in a more dynamic, interactive language.
Given the resultant comment section I clicked into the article expecting the most sith-like of absolutist proclamations taking itself extremely seriously and actively fanning arbitrary ideological flames.<p>Instead I found a concise, measured and sane opinion that I honestly can’t disagree with. Like, sure, if your language broadly supports the secondary use case (scripting) why not? If it doesn’t, then the juice almost certainly won’t be worth the squeeze.
Scripts are usually no better than they have to be - they're not the main purpose of the system. Scripting languages handle all sorts of issues that take pages of code in other languages so they are the easiest way to do what you need and easy wins.<p>IMO some languages like C/C++ cry out for an embedded scripting language so that you don't write the basic, one off, performance insensitive parts of your code in a "hard" language. This is taking it the other way around - suggesting that as much as possible of your "main project" should be in a scripting language so that you're not wasting "hard" development cycles on areas that don't need it.
I don't agree. From my exp really big problem, when you can't easy see borders between parts of project, and this is case with React and many other fullstack envs.<p>From other view, yes, it is not good, when for example on front you use strict typed lang and on back dynamic, so need to constantly do conversions.<p>So need some reasonable combination. Traditional Java+JS is looking reasonable. JS (front) +Python (back) is also reasonable. C backend from my view is weird, so should be some intermediate lang, for example Lua.
Well, it depends. If your project uses Zig, you obviously use the Zig build system.<p>If your project uses C or C++, you also use the Zig build system.<p>Advocacy - Maintain it With Zig:<p><a href="https://kristoff.it/blog/maintain-it-with-zig/" rel="nofollow">https://kristoff.it/blog/maintain-it-with-zig/</a><p>HN discussion:<p><a href="https://news.ycombinator.com/item?id=35566791">https://news.ycombinator.com/item?id=35566791</a><p>Reference:<p><a href="https://ziglang.org/learn/build-system/" rel="nofollow">https://ziglang.org/learn/build-system/</a>
Really interesting comments here. I haven't found an appealing option for scripts and CI ( I think I am allergic to yaml ), bash is just way too fragile. So I've decided to write my own scripting language that is written in Go, similar in many ways to Lua, but with first class support for executing other commands, running API tests, and manipulating data. Currently dogfooding with plans to share in a few months once I'm confident it's good to start getting feedback. So, one executable plus your scripts, cross platform, no yaml.
Depends on the project, depends on the team, depends on the language, depends on the script. Pick a sensible tool for the job, write understandable code, don't lose sleep over silly generalizations.
I would have agreed more with this a couple of years ago when maintaining a script written in an alternative language required me to know that language.<p>These days I'm much more comfortable both writing and maintaining code in languages that aren't my daily driver (like Bash or jq or AppleScript or even Go) because I can get an LLM to do most of the work for me, and help me understand the bits that don't make sense to me.
We have some Perl scripts that are freaking immortal.<p>The rest of the code base started in Java, then Clojure, now it’s Go. The scripts are there still in their very-not-modern Perl style though. They have a self-evaluating behavior consisting of data blocks that are interpolated. To be honest, I’m not sure exactly how it works. Very discouraging for the casual passerby looking for some cleanup to do.
There is a reason shells and shell scripts exist. Reinventing the basic shell functionality seems pretty dull and better spent staring at a wall to decompress.<p>I always start with a simple shell script which does most of the work as simple as possible, even simpler is using `make` then introducing shell scripts when needed.
I try very hard to do just the opposite. If I need a script to do something existing code can't ,then it is most likely exciting and I'm going to use that energy to learn something new, be it a language or tool.<p>Here's a hotter take: your team colleagues are more likely to wear your clothes than to use your scripts.
This really only applies to a subset of languages and projects with such languages. I wish developers had the courage to face the deeper problem (being unwilling to read code...older than a few weeks? Such a developer will face worse things in the furure, they should train their focus stamina)
The performance concerns might creep up for scripts and tricky to figure out the issue if you are not internal abstractions.<p>Example, <a href="https://github.com/berry-thawson/diff2html">https://github.com/berry-thawson/diff2html</a>
>> Scripts should be written using the project main language<p>... as long as that main language is a scripting language. Otherwise it's just dumb.<p>Also, tunnel vision by the author: "Almost all projects I’ve worked on have scripts we wrote to automate a repetitive process. "<p>Well almost all projects I’ve worked on have scripts we wrote to automate a ONE-TIME process. Like collect some data from the log to figure out a bug, fix it and forget about both the bug and the script. Automate it since can't manually process 30Gb of data and grep only can do so much. Sure as funk won't write the "script" in C++ but Python or Perl or something.
Ehhhhhh. I get the sentiment, but I think this really depends on what the "main" language is and how amenable it is to scripting.<p>Personally, I prefer writing shell scripts regardless of what the main language is in a given project. They're portable (more or less), and can e.g. detect missing dependencies and install them if necessary, which isn't possible with the main language if you're missing its compiler or interpreter.
I like the general idea, but it depends.<p>Sometimes these bash scripts are used in lots of contexts, local dev, CI pipelines, Docker build steps... good luck running Java or Rust in the last two. Even if you get it to work, good luck debugging if there are any issues.
I think it's a strong "it depends", if the script interface with data used/produced by the project, sure, seems natural.. If the script does something more system-near and the main language is not as convenient for it, then.. No.<p>I'd hate to do with typescript what we're doing in scripts with bash.. just like I'd hate doing in bash what we do in typescript..
I'll admit I'm not reading the article, but this is a hard no for C development. Yeah, it can be done not so terribly, but just use a normal scripting language.<p>In C#, I could see doing this.
Experience report: I did this for my company that uses Kotlin as its main language and it worked out brilliantly. We extended Kotlin Scripting into "HShell" and it swiftly replaced basically all uses of bash or Python for our internal scripting needs. The docs are public here, although the product isn't open source. It gives you a flavor of how it works though:<p><a href="https://hshell.hydraulic.dev/14.0/" rel="nofollow">https://hshell.hydraulic.dev/14.0/</a><p>Advantages:<p>• It's as concise as bash but far more readable, logical and less bug-prone thanks to the static type system with plenty of type inference.<p>• IntelliJ can provide a lot of assistance.<p>• You can easily import and use any JVM library, and there are lots of them.<p>• Ditto for internal project modules if you work on the JVM.<p>• If you need to, you can easily mix in and evaluate Python/Java/etc using Graal. It has an integrated disk cache that can be used for storing file trees that manages free disk space automatically, and there's a command to set up a Python virtualenv in the disk cache transparently.<p>• We have a high level shell API that makes console, file and network operations as easy as in bash, and sometimes easier because the commands aren't afraid to deviate from POSIX when that would be more convenient. For example most operations are recursive by default.<p>• A smart progress tracking framework is integrated into every operation including things like wget.<p>• It's fully portable to Windows including edge cases like being able to set POSIX permissions on a file, add it to a tar, and the resulting tar will retain the correct permissions.<p>• SSH is deeply integrated, and so commands "do the right thing" automatically. For example all the commands take Path objects as well as strings, and path objects track which machine they refer to, so if you open up an SSH sub-shell you can easily copy to/from the remote machine using regular copy/move commands. Other commands are "smart" for example the wget() function given a path that's on a remote machine will execute curl or wget remotely rather than download locally then reupload.<p>Although this sounds like it was all a lot of work to build, in reality our main product is a kind of build system (it makes deploying desktop apps easy, see bio for link). So all that functionality is in actuality functionality we built for the product and just kept nicely factored out into modules. The work invested into the scripting-specific parts is probably a couple of weeks over a period of a couple of years, and it was well worth it given the number of scripts we have for things like QA, deployment, server management and so on.
We should use YAML for all of our automation so that non-developers can build and maintain the automation, and then exclusively assign developers who are proficient in a general purpose programming language to build and maintain the YAML automation. </s>