Writing Safe Shell Scripts (2019)
sipb.mit.edu
sipb.mit.edu
It really tells many of the rules you're supposed to abide by and helps you write cleaner shell script.
Also, look at oilshell[1]; it is bash compatible out-of-the-box, but has several options to make it incompatible, but safer (e.g. no field splitting of parameter expansion by default, making quotes much less needed).
The most likely thing is that Oil is going to be a much better language this year (more expressive, with safety features, and it should be fast), but the interactive UI will still be more bash-like than fish-like:
https://www.oilshell.org/blog/2020/01/making-plans.html
So I encourage anyone who's interested in that to get involved now as the interactive shell is also a years-long effort :) There are many links on the home page about how to get involved, and I wrote many blog posts so people can understand how it works.
----
Here are a couple posts on why Oil is a good foundation for an interactive shell:
https://www.oilshell.org/blog/2020/01/history-and-completion...
Although I have to say I do feel the tradeoff right now between having a strong design / globally consistent code vs. allowing local variation / a lot of people to contribute quickly. That is, allow people to get in, add their useful patch, their useful bit of knowledge, and get out, without caring at all about the rest of the program.
I think Linux, git, and GNU are the prototypical examples of the latter. Those projects are too big for one person, so the code is set up in a way to allow hacking locally. They're also arguably a mess. Anyone who's ever written against the container APIs in Linux knows what I mean (namespaces, cgroups, etc.)
The git UI is another famous example, i.e. the plethora of commands and flags that make little sense. Empirically it seems that you can move faster if you disregard consistency and coherence.
The shell language of course grew like that over 50 years. Bourne shell is pretty consistent, but one thing I learned is that ksh added a lot of bash misfeatures (they're ksh-isms not bash-isms), and it's pretty bad.
But shell is actually a significantly smaller problem than git (after all bash is maintained more or less by one person). So it should be possible to fix it and redesign it. Unfortunately I'm not sure if the lessons generalize to bigger projects, but I would like it if they do.
-----
On a more practical note, feel free to send more people this way if you want to see the project succeed :) The design is there, and it will hold up, but it needs a bit more manpower, especially for things like the interactive shell.
Most single-person project maintainers have trouble relaxing the latter and end up staying a single-person project because of it.
Somebody integrated ShellCheck into Google's code review system about four years ago, right before I left.
So the result was that every code review I sent with a shell script was filled with red squigglies -- "add double quotes here". Most code reviewers don't really know shell, but if they see red squigglies, they will say "fix this".
The double quotes are of course technically correct, but when you're writing a shell script in a subdir 8 levels deep in the repo that only operates on 3 files in that subdir, it's overkill and makes everything ugly.
In other words, it was the typical "static analysis has annoying false positives" issue.
Most of the shell scripts I write deal with "trusted" filenames -- i.e. those checked into the repo. If you want to deal with untrusted filenames, then there are a lot of ugly techniques to do that.
So Oil is basically meant to make the default thing the right thing, and the common thing the short thing (Huffman coding, as Larry Wall calls it).
Once it becomes habit the cost is zero, though.
And the alternative you're implicitly suggesting is: "Follow shellcheck except you are allowed to make an expert judgement and ignore it when you know it won't be dangerous and have determined that the code is unlikely to change in the future so that this exception will be safe going forward too."
I think it's clear that this isn't a practical rule on a team with many people -- especially where they are not all experts in shell, which is 100% of teams of many people.
Making the experts "add those annoying double quotes they don't really need in this case" is the far lesser of the two sins.
But as soon as the script is something that should be potentially sooner or later maintained by anybody else than the original author (and in any successful project that's practically sure) the only right policy is to produce the source code that's obvious and passes the source checks.
An "expert" would have no problem to in no time adjust his three-line code to fulfill all the expectations of ShellCheck.
If it takes more than that, that only proves that the checks are actually beneficial and even more important than the "expert" tells to himself: it proves that writing that specific code "cleanly" is actually hard for everybody.
I just find it annoying, hence the new shell :)
There's a lot more to Oil, but that's definitely one of the surface annoyances I want to fix. Although zsh actually does fix that, and for some reason I've almost never seen a zsh script.
I guess it's because zsh is not POSIX compatible by default.
Quotes are tools that affect the correctness of your control flow quite vividly. Of course if there are multiple tools for solving the correctness issue you're welcome to choose any of them (and maybe the choice between the tools is something you can ascribe to religion), but that doesn't absolve you of the need to solve the issue itself. When the argument is that a piece of code is executing objectively wrong instructions (and ones that would be so obviously wrong in other languages!), that's pretty much the diametrical opposite of what you can casually dismiss as religious dogma...
As mentioned, the default should be right in Oil. The defaults are wrong in shell, and ShellCheck is meant to alert you of that.
Oil should have a style linter / formatter, but if it's designed correctly it shouldn't need the analogue of ShellCheck, which is more about correctness.
It does today. Will it change tomorrow? Will someone rename one of the files to include a space? It sounds like people saying "I don't need to escape this value in a query, it comes from a static list and it's safe." Yes - it's safe right now...
The only way you know this is being a person who routinely writes shell scripts.
There is no substitute for knowing what you're doing.
Try running shellcheck on configure scripts.
Or try this
curl -4O https://ftp.netbsd.org/pub/NetBSD/NetBSD-release-8/src/build.sh
shellcheck build.sh
Or try shellcheck on the build.sh from buildroot.org.These scripts are "safe" enough for hundreds or thousands of competent users to be running them every day.
double quotes, mixing string and array, unused variables. Why is this normal?
Indeed, which is why Linux is never found in a professional setting. /s
2) Not everything in the FOSS world is GPL'd.
3) There are plenty of users who have this restriction in their work and it is a real restriction to be aware of when discussing tooling.
4) That's just how the code is licensed and is related to the topic at hand.
Most companies that are anti-GPL aren’t so open about it.
Just because you can do something, doesn't necessarily mean you should. That rule applies to the implementation of corporate policies regarding licensing, too.
I'll probably get flamed for this, but Python is an absolute fucking trainwreck of a platform. Even at its core the 2.x vs 3.x is going to break all kinds of stuff in Jan 1, 2020. Just look at the package system: cross-system portability is a mess (don't believe me? run on Arm for a week), and unfortunately because there IS a package system people think they HAVE to use it, which means: BLOAT BLOAT BLOAT your BOAT, gently down the stream!
It's kinda like what Dewalt did to miter saws. We've gone from something that could be kept perfectly square and did a great job and took up a little bit of space, to 30+kg behemoths with sliding rails and double bevels, tons of blade travel and deflection, and are a challenge to keep true and take up 3x the space and weigh a ton.
Keep your mission-critical tools simple. Period.
PR's welcome. ;)
Python makes sense for scripts that need good argument parsing, or complicated intermediate input processing. But it's pretty annoying to get a shell pipeline working in Python. `foo | bar | baz` is about 15 characters in bash.
Here's an example. I use `llbranch` all the time: https://github.com/shawwn/scrap/blob/master/llbranch
It lists all branches in a project. It's nicely colorized. What would the equivalent Python be? Who knows, but it'd be much longer.
A shell script that consists of a one-line exec isn't what people concerned about.
FWIW I'm a 99.9% POSIX masochist (probably closer to 4N)
I have maybe 3 standalone shell scripts in my PATH, despite writing thousands of lines of shell.
What moved me to putting them in a bin directory was that it's just easier to edit a standalone script.
To my mind, the parent post was referring to lengthier, more complicated scripts. Speaking as one who wrote and routinely maintained such scripts at one point, I cannot agree more with the sentiment that most if not all of them could and should have been written in Python or Ruby or some other scripting language instead.
https://www.oilshell.org/blog/2018/01/28.html#shouldnt-scrip...
There's a reason AT&T used to run ads that showed their secretaries, managers, and so on using and writing shell scripts and there's never been a Python ad claiming that just anyone could write it.
I reckon that less than 0.1% of the population can read shell scripts.
Shell is required to be basically everywhere. It's part of POSIX. It's also standardized. Shell scripts written 30 years ago still work, and will probably work 30 years from now too. Shell is probably on your TV.
Not everyone has Python. The most common version in the field is Python2, but that's officially obsolete. Python3 is in many places, but not everywhere. The two Pythons are basically incompatible unless you're willing to put in an effort. You have to work to make Python scripts compatible with both versions, especially if you can only use built-ins, and the results are way clunkier than that 2-line shell script you're trying to replace.
Python is a terrible choice if your goal is a relatively short script that calls other programs. You can call out to other programs in Python, but it's much clunkier and more painful to maintain. And while it often doesn't matter (because neither are speedy), CPython 3 takes more than 50x more time to start than dash and 27x time to start than bash (see: https://github.com/bdrung/startup-time , which shows Python3 3.6.4 at 197.79 ms, Bash 4.4.12 at 7.31 ms, and dash 0.5.8 at 2.81 ms). That speed is helpful when you want to have a "simple script in a loop".
Python is much better than shell when you start needing more sophisticated data types, libraries, modularity, etc. But the typical use for a shell script doesn't need any of that. If you need that, shell is a terrible tool, because it's the wrong tool for the job, not because it's never useful.
If you're writing shell, use shellcheck. Once you do that, your error likelihood goes way down. Many of the reports of problems writing shell come from a time when shellcheck didn't exist.
POSIX shell being fixed forever though also means it will /never/ get "fixed".
Shell's silent-failure-by-default, implicit-by-default, principle-of-most-surprise semantics will always be there, and they are /bad/.
I have read lots of shell scripts, by many people, from the experienced UNIX greybeard to to the novice web developer, and they were almost all flawed. Pointing out the flaws, the devlopers of each level were surprised by them. It is close to impossible to write correct shell script, no matter the level of expertise, because the language offers neither semantics nor tooling that foster correctness.
Forgetting `set -e`, forgetting that it gets turned back off in subshells, quoting mistakes, pipefail semantics, you have to have made many mistakes to even /discover/ that these pitfalls exist (or use shellcheck, which will find some, but certainly not all of them).
Shell is a language that looks simple on the surface, but has 1000s of exceptions you have to master to write even minimal non-bugged scripts.
The lack of any form of reasonable data structures and strings being the prevalent data types also do not help. For example, NixOS's linker wrapper script (written in bash) was accidentally quadratic because of quadratic append-to-strings (https://github.com/NixOS/nixpkgs/issues/27609); and that was despite NixOS having many competent bash programmers. When scripts grow, these things happen.(Note that shellcheck does /not/ point out quadratic string appends; it can only find obvious mistakes and this is hard to detect in general). Fixing it was very difficult, and was not understood by many. If you have a language like Python that offers reasonable set/dict data structures that people understand and use intuitively, this is trivially avoided.
Finally, POSIX does not help much when the main thing shell does is calling other programs, and there are no guarantees on what the APIs for those programs are. For example, I have encountered many shell scripts that themselves are technically "portable", but they use arguments to e.g. `rm` that do not work on e.g. busybox, thus crashing the boot processe and resulting in hours of time lost for hundreds of people.
By not using shell, we can support that the next SOMETHING-standard will NOT mandate shell, but something better.
A Python script is not the answer when I'm trying to do something quick and dirty at the shell.
The beauty of shell scripting is that it evolves seamlessly from trying to solve simple problems at my command prompt. I pipe a couple of things together, and then I realize I could use a loop and a few conditionals, and suddenly it makes sense to store this in file in case I want to do this again. Boom, program done.
It now works on every computer I own.
I love Python. I use it almost every day. But I'll be damned if I'm going to go rewrite every shell snippet I hack together into Python just because it exists.
Different problems, different solutions.
This kind of automation adds a lot of value for low marginal effort and probably explains a lot of the short scripts I have laying around in directories.
The major differentiator is usually "shelling out" in Python kind of sucks. It's verbose, output collection and error handling suck, and escaping can be miserable. I often will reimplement things in pure Python if I have the time.
A recent example was I needed to tar+split large files. `tar cf - -C / $filename | split --bytes ${size}MB --verbose - $tmpfile.` My pure-Python implementation used the Tarfile library and I wrote a custom file-like object to split it--at least 50 lines. Both methods have pros/cons, but I had both implementations handy depending on the context I needed it in.
Another recent example was something I wrote to merge multiple video file "parts" into a single video using ffmpeg. It's 4 lines of shell script and would at least be 3x as long to write in Python.
The rule of thumb I have is if it's longer than 20 lines reevaluate if a shell scripting language is appropriate and I haven't really seen a need to change that in ~15 years I've used it.
Since I did this at work it belongs to my employer, so I can't currently publish it freely, but it's not part of our core product so they may be open to publishing it under MIT licence or similar at some stage.
So it can be done, and has been done, but I guess most people are sufficiently happy with Powershell, Python, etc to not bother bridging the gap for C# too.
I think it's more incongruence between the languages. os.system() will technically call a command. subprocess is a big step up from popen2, but Perl was much more streamlined and terse. That also applies to one-liners when you're in a shell.
With Python you just have differences in a bunch of things; error handling? I often have to look up error codes, catch the exception then check for error codes. Pipe data in? Test for sys.stdin.isatty() then read from sys.stdin or fallback to sys.argv--it's not obscure, just not particularly Pythonic. The list goes on and on. It will probably take ~20 lines to properly deal with shelling out and if you do things wrong you could deadlock[1]. On the plus side, discouraging shelling out means your code is more portable =P
[1] https://docs.python.org/3.8/library/subprocess.html#subproce...
Bash, and Bash-like shells, are literally everywhere. I have to be wary about what Python 3 features I use, and if there will even be a Python interpreter available. My OpenWRT router has a Bash shell, but I don't care to install and maintain other interpreters or runtimes on an embedded platform.
Even job management and parallelism are easier in Bash and GNU Parallel.
It really feels like piping and easy process invocation and compile-time directory awareness wouldn't be massively onerous to add to an existing full-featured programming language so you wouldn't have to sacrifice a good type system and powerful syntax when you want to do scripty things.
Why do you think it is not a programming language?
PowerShell meets both requirements as it's tuned for the Windows environment, but can still play nicely with the rest of the world.
Of course, now that WSL is a thing, it's a real question why someone would want to use PS unless they had no other options.
https://www.heavybit.com/library/podcasts/to-be-continuous/e...
I use it because I only have to learn one tool, and not a ton of different ones with different UX.
The 'language' is sane, at least to me, as it's object oriented doesn't revolve around parsing strings.
edit: your comment inspired me so I installed PowerShell via Snap and am going to give it a go on Linux.
That's the good part. The bad part is that it's easily one of the wartiest languages I've ever used, especially as a young language it's really got an inexcusable amount of legacy problems.
A colleague of mine (who also uses it heavily) says "two Googles per line".
Just to get sane parameter and variable checking you have to throw up a bunch of attributes and set a bunch of flags.
Be sure to set up strict mode, use cmdletbinding on your parameter blocks, and set ErrorActionPreference to "stop" in every script you write.
That and the quality of many of the first-party official PowerShell modules is appallingly low by Microsoft standards. The good side is that the .NET framework is accessible so whenever the PowerShell module is failing you, you can drop down into leveraging raw c#-style assemblies, which are much higher quality but they speak PowerShell with a very thick accent.
But I'm using it on Windows, YMMV on Linux.
I'm infatuated with the concept, but the implementation leaves a lot to be desired.
Check out dshell: https://pub.dev/packages/dshell
Next Generation Shell - mostly there. If something of the above is missing - it's a bug and you are welcome to open an issue. I'm the author.
Just curious, but how would one do process substitution with NGS?
The problem is that very few people know shell scripting well, but once you get to know it, it’s not that bad, in quite specific cases.
Always use Shellcheck, though.
I wanted to create a custom git command and started with Rust/libgit2, but found it was missing too much. Shell scripting proved to be the most natural, best supported approach (though it was nevertheless very painful).
Eventually it motivated me to switch the language for shell scripting for the second time, and nowadays I recommend Haskell's Turtle (https://hackage.haskell.org/package/turtle-1.5.16/docs/Turtl...) to anyone who is interested in safer shell scripting and still likes a concise and terse syntax (and it's blazingly fast too).
If we understand "box" as "environment", in general, and not literally a physical or virtual machine...
Not really. E.g. see: every clean Ubuntu Docker container. They don't have Python, nor should have to include it by default.
And if you are going to do something like this:
apt-get update && apt-get install -y stuff
wget https://file.tgz
tar xf file.tgz
cd file
make
make install
value="$(cat result | grep something)"
mvn clean install -Dapp.value="$value"
Yeah, this would need proper securing and Shellcheck, but still I'd rather have it written like this, a concise and to the point script, instead of having to install a whole secondary language interpreter and writing a script among 3 to 10 times longer for the same result.I recently did the opposite of what everybody seems to be doing: moved an 800-line Python script, which you had to read carefully to understand what it was doing, to a 150-line Bash script that you only have to look at, because the important things to know is to see what other, external programs are being called. Just the apt-get stuff in Python was a mess with the poorly documented Python API for Apt, and some 50 lines to just do the same of an apt-get install... effectively wasted mental cycles.
(EDIT: formatting)
There seems to be something very wrong with this expression. Why would one need API for apt? It's a command-line tool.
An application should never rely on a shell script. If you have to execute another program, use the syscalls (fork and exec on posix).
Could you explain why? I'm very happy with programs calling shell commands/scripts. You execute another program the same way whether interactively from a shell, or by calling system() from another program. The simplicity and universality of the call syntax is an advantage.
IMO, The inflection point where you should stop using shell is very low, though. The Google style guide for shell recommends that you should rewrite scripts once they are more than 100 lines, and I think that it probably too generous.
I think the main issue is: just because someone gets something to work on their system with their Python install, they think it works everywhere. And that is not at all true. There's a reason bin/sh|bash are so pervasive in administration, they are small, simple, well-understood, and resistant to the kind of bloat the Python chokes on.
python is basically this:
https://www.amazon.com/Wenger-16999-Swiss-Knife-Giant/dp/B00...
If you need to do a very specific task that involves interpreting/modifying data structures/formats, if you have non-trivial logic, if you need to use a module, if you need to access an operating system primitive which Unix shells don't really expose, if there's no Unix tool that does what you want, or you just need more control/certainty/reliability, you want Python or something else.
I've been thinking that we are kind of stuck with it. Much like javascript.
So, we could go the route of typescript and have a translation layer and outputting shell scripts. I actually think that could work quite well from a technical standpoint.
A big part of shellscripts though is having the source available. So maybe even have the translated, original code, and the generated shell output in the same file.
The source code on top (out of view from a shell interpreter) and the generated code below. This would allow it to be readable by anyone and executable by anyone, but modifying it would require the transpiler (which by itself would probably be pretty lightweight as well - certainly compared to python).
Certainly not perfect, but preferable to rediscovering the nuances and gotchas of shell-scripts every other day. Or maybe such a solution would only prolong the suffering and we should start all over.
Performance for shell scripts isn't super critical in my eyes but the startup time of python feels too much for something you might want to run in an inner-loop from find or something. Not sure if python IO performance is suitable for shell scripts either.
Also isn't nearly as universal as shell-scripts nor something you necessarily have/want on small systems.
But I will admit that I'm channeling prejudices and don't feel I have enough of a complete picture to say anything definite about it. Others in this thread touch upon it though.
I probably wouldn’t wrap GNU find and python, I’d just walk the directory tree in python and use regex.
IO is suitable for everyday tasks and shouldn’t be an issue in Python anyway, if it is either you’re doing it wrong or your shell script should be written in C and not touch interpreted/bytecode compiled languages at all.
And there are lots of really optimized tools that are as quick as can be, throwing them out is a hefty price to pay.
perl still exists, and is a wonderful alternative to python for "shell scripts plus". The syntax should be at least vaguely familiar to anyone who has touched bash, awk, sed, etc.
I mean, if it would work as you expect it to, it certainly is a good idea to exit a script as soon as something fails. But sadly not all implementations behave similarly [1] and if you call a function from within a condition, 'set -e' gets deactivated/doesn't work.
For illustration, take a look at the following example:
#!/bin/bash
foo() {
set -e
false
echo "Sucks"
}
printf 'Normal: %s\n' "$(foo)"
printf 'Condition: %s\n' "$(foo || true)"
Output: Normal:
Condition: Sucks
Probably not what you would have expected. Not using 'set -e' is no good solution either, so, for the time being, I still use it, but I am still looking for a better solution.bash has inherit_errexit to fix a longstanding bug, but even that's not enough.
Oil has shopt -s strict_errexit and more_errexit that fixes even more.
https://github.com/oilshell/oil/issues/476
I'm still looking for feedback and there's probably one more thing I want to change.
Although the fundamental problem is that errors (success/fail) and boolean values (true/false) are conflated in the status code, so it's a tough problem.
In Oil you will be encouraged to use expressions for booleans, not statements, e.g.
ls / # success/fail
if (x > 0) { } # true/false
rather than ls /
if test $x -lt 0; then ...But, in this case, because you're running it in a subcommand, you need to add pipefail.
set -euo pipefail
This way of making sure nothing misbehaves also locks out unset vars, which can be surprising, but also helpful.In my opinion, the problem is that calling a function from a test expression, evaluates it in a different manner than calling it from elsewhere. That is so absurd that I wonder how it wasn't changed over the years.
And while I appreciate the thoroughness of handling errors the Go way, I find it tiring and verbose. In many cases, you just want to exit with a specific exit code and adding 4 lines just to specify that exit code doesn't feel right.
The problem I have with errexit is that you can't rely on it. You can write your function with it (even enabling it explicitly), but later someone comes along, calls it in the wrong context and boom, your function runs without errexit.
In another script I was leery about running rm -f -- /path/foo* so I instead used
find /path -mindepth 1 -maxdepth 1 -name "foo*" -delete
because then the glob can't affect find's behaviour in weird ways.Most likely set -e is enough protection against mktemp failing and the rm -f glob probably would've been just fine, but shell scripting has enough footguns that sometimes I can't help but go overboard with paranoia.
Yes it can. You should use its exit status (or set -e). See http://man.openbsd.org/mktemp
Note that there is a nasty trap if you're using local to declare and set a variable:
a () { local M=`ls /asdf` && echo y; }
b () { N=`ls /asdf` && echo y; }
a ; b ;
One will print y, the other will not.For example, "TMPDIR=/dev/null mktemp -d" will reliably fail.
You shouldn't be validating it starts with "/tmp" though, because on quite a few systems, people set TMPDIR=/var/tmp.
You absolutely should check if mktemp exited nonzero, but if it exited 0, the directory should be safe to use. If you want to be really paranoid, you can check if the output is an empty string too after checking the exit code.
I think you're shying a little too far away from bash's globbing. Yeah, bash has footguns, but using clever workarounds can also be a recipe for creating confusing and error prone code in its own way.
In the end I think shelling is mostly about controlling your inputs.
The only thing that checking the exit code won't catch is bugs in mktemp/bash, or bad memory / solar flare bitflips.
rm foo.[cC]
will remove a file named literally foo.[Cc] if neither foo.c nor foo.C exist, despite the fact that foo.[Cc] does not match the glob. nullglob is probably a good default for bashscripts, but I don't think it exists in posix.My defensiveness generally takes a sharp upturn when I write scripts that will be run as root or ones that delete data, but I'll admit not trusting mktemp's exit code is probably taking it a bit too far.
Hopefully not by generating a megabyte of polyfill runtime code; more just by the compiler refusing to compile code in $lang unless it has a direct equivalent in /bin/sh. Any $lang source file the compiler accepted, would just look like “a shell script translated into $lang” already. But the compiler would inject $lang’s safe semantics (exceptions, type-checking, etc.) during the compilation, so you’d at least get that benefit.
Alternately, I’d also be satisfied with “emscripten for shell”: a compiler that generated shell scripts containing a small WASM-like emulator and an embedded bytecode stream to feed it. As long as it was lightweight enough. (A large point of these environments’ use of /bin/sh is that they’re small and embedded and can’t load too much into memory at once.)
My only guess for why this hasn’t happened, is that the people who write things like shell scripts that execute in initramfs, or shell scripts that are intended to install stuff even on lesser-known UNIXes, are all old-hand ops people who know shell-scripting cold, and certainly aren’t developers with any experience in compiler theory.
Also, I wouldn’t mind if this $lang that compiles to shell script is its very own language I’d have to learn, just like TypeScript is its very own language you have to learn. As long as I don’t have to manually write five layers of guards using impenetrable [ “y${x:-1}” -eq “f” ] style code, and then duplicate the hierarchy of cleanup behaviours after each failed guard, I’d be happy.
Mathematica and other software platforms use bash as universal installer for Mac and Linux. I have seen languages that compile to bash but use is limited based on the language.
In general, this article seems very light. No mention of LD_LIBRARY_PATH, for example. I imagine there's probably a better guide to writing secure scripts somewhere else.
set -euf -o pipefail
please consider set -o errexit
set -o nounset
set -o noglob
set -o pipefail
which is easier to lookup/search for if the reader is less familiar with shell scripting, and makes for cleaner diffs when a flag is removed/added.Caveat: some of the longer forms might be Bashisms (e.g. not present in ksh, dash, etc.)
From my experiment:
Googling "set euf" (without quotes) -- 838,000 results, first 6 results are all helpful and explain it in detail.
Googling "set o nounset" (without quotes) -- 38,700 results, entire first page seems helpful.
I dunno, looks like they're identically easy to learn the meaning of.
On the other hand, this is amazing: https://github.com/speed47/spectre-meltdown-checker
Here is my take on bash vs Python and friends. I am frustrated by both.
bash - domain specific language and huge library (CLI utilities) and lets you get the job done but not a language I would like to use (syntax, error handling, very limited structured data support).
Python - okayish as a language but doing domain specific things (working with files and processes) is so much more verbose than bash.
My solution in NGS - have a high level, modern language with the typical goodies like ... exceptions (wow, completely new concept!) and the language is still domain specific. Working with processes for example has it's own syntax and is much more concise and straightforward (stole bits from bash).
Right now the project has the language, which is useful enough to write scripts (which we do at work). Regarding contribution to the project, my idea is that as much as possible should be in NGS language (as opposed to the lower level C). The UI will be implemented completely in NGS, allowing contributing to the project using the same language you are writing your scripts in.
https://github.com/ngs-lang/ngs
As a side note, while the UI is not there yet, I do plans for it which include interaction with objects on the screen as opposed to current "here is your dump of text" situation... and in general be more considerate of the user.
https://gist.github.com/crizCraig/f42bc250754bed764ada5f95d1...
raw: https://gist.githubusercontent.com/crizCraig/f42bc250754bed7...
case "$(readlink /proc/$$/exe)" in */bash) set -euo pipefail ;; *) set -eu ;; esac
That takes care of setting -eu for shells that don't support -o pipefail and pipefail for bash.http://robertmuth.blogspot.com/2012/08/better-bash-scripting...
x=(1 2)
echo "${x[0]}"
And associative arrays declare -A x
x[hi]=20
x[bye]=30
echo "${x[hi]}"
for i in "${!x[@]}"; do
echo "k: $i, v: ${x[$i]}"
doneZsh is also really heavy, or it was last time I checked. Larger binary & slower startup time than bash, and lots of features I don’t use. Modules, calendar, tcp, ftp client ... that’s a lot of stuff in the shell that is arguably better put into separate executables.
This is like adding a setting to your Java or Python program to immediately exit if any method call throws any kind of exception, with no possibility of catching and handling that exception.
This does not seem to be a smart thing to do as compared to checking return codes, etc.
Every script I've seen with set -e has been buggier than without it.
The main problem with scripts is that they often do not go through a normal software review and testing process.
deno, node, python, julia--anything but shell scripting.
#!/bin/bash
set -euf -o pipefail
cd $(dirname $0)
Then add "" everywhere ;) And they say, write Python instead, except I’m dubious because python programs can have a lot of dependencies which can be tricky to install.You should use /usr/bin/env bash for the shebang. Some distros, such as nixos, don't have /bin/bash.
You need more quoting in the dirname line. cd "$(dirname "$0")" is what you need. The outer quotes are in case the directory has a space or special character in it, the inner ones are in case the script does.
That being said, you may also want $BASH_SOURCE rather than $0, but the reasons behind that are complicated. You may also want to support cases where the script is a symlink depending on what you're doing.
#!/usr/bin/env bash
If you really want a predictable environment I usually go with something like this after the shebang, it supports bash and not-bash shells: case "$(readlink /proc/$$/exe)" in */bash) set -euo pipefail ;; *) set -eu ;; esac
PATH=/usr/sbin:/usr/bin:/sbin:/bin
\unalias -a
I don't usually cd anywhere unless I need to."Quote liberally" - also ripe. Mind your aPostrophes and Quotes because they do things.
I like writing for me and what I need done, not what or how others might have me write or how others might think it should be done.
The shell is a base orchestration and interface tool for your os. If you think it's just that bad then just stick to your language of choice but please refrain from suggesting lazy habits make a better shell, they don't.
Fly your own kite. It's much more fun.
#!/usr/bin/env bash
bell=`tput bel`
tock='Blastoff!'
do_ring()
{
if [ "$1" ]; then # true
echo -n $bell; sleep 0.1
echo -n $bell; sleep 0.1
echo -n $bell; sleep 0.1
echo $tock
else
echo -n $bell; sleep 0.1
fi
}
i=3
while [ "$i" -ge "0" ];
do
if [ $i = 0 ]; then
sleep 0.5
echo ok
else
echo $i $bell
sleep 0.5
fi
i=`expr $i - 1`
done
do_ring $1 # do something different if arg
sleep 1
echo neat.
Semantic qualities aside there is something here that breaks with -e and if you're used to using -e by default you might be baffled and think shell sucks, for example.One should not rely on these magics, the ones that alter runtime behaviors, without truly understanding what you're after and what you're writing. Or mostly understand. They're fine and good if you have narrow routines with a very strict focus - "I'm in, I'm out, bang."
The best switches available to the shell without a bunch of feature and package and library this or that bloat are exactly
-x
-n
That's all that you need to ensure that your scripting is what you need it to be. Introduce the magic after you've written the thing. When it's ready and it passes your tests then test it again.Every single utility and package on that system is at your fingertips. Demand accuracy. I do.