Scripts and programs should skip having extensions like '.sh' and '.bash'

October 27, 2022

I recently read Shrikant Sharat Kandula's Shell Script Best Practices (via). One of the suggested best practices is:

3. Use the .sh (or .bash) extension for your file. It may be fancy to not have an extension for your script, but unless your case explicitly depends on it, you’re probably just trying to do clever stuff. Clever stuff are hard to understand.

I have the opposite view. Unless you have a strong reason, you should avoid putting an extension like .sh, .bash, or .pl on your scripts and programs. The reason to avoid it is a variant of not making product names visible in messages. Some day you may want to change that shell script into a Perl script, or a Ruby script, or a compiled program (perhaps you get an urge to use Rust). At that point, either you have to find every use of the script elsewhere in your system and change them all, or you have a '.sh' program that's actually written in Perl.

(Python is the one sad exception to this, because as far as I know having an importable thing requires your file to end in '.py', unless you want to rename your program while testing it (or use a symlink).)

I think this advice is especially dangerous for shell scripts if you use '.sh' and '.bash' to differentiate between scripts that are portable Bourne shell and scripts that are Bash specific. It's not unreasonable to move a script back and forth between those two sides depending on the circumstances and then needs (a simple script might evolve so that it now can really use Bash specific things, for example). But with script extensions, you've added a point of friction to doing that. It's not enough to change the script and rename it, now you have to also fix any uses of the script. How many developers will persuade themselves that they don't really need to use that useful Bash-ism after all? Probably not zero.

If you only write personal scripts and programs and never invoke them automatically in any way (from each other, from cron, from Makefiles or other build instructions, etc), then having file extensions is probably harmless and may help you keep track of what is what. Otherwise, please have a strong reason that you need it.

PS: One reason to do this is illustrated by the process of building Go from source, which has you run either './all.bash' on a Unix system or 'all.bat' on a Windows system (there's also an 'all.rc' for Plan 9). Here Go wants to call its build, test, and so on build commands by standard names ('make', 'all', and some others), but Go needs different scripts on different systems, so it uses the file extension to denote the implementation language and implicitly the OS type it's for.

Comments on this page:

By lilydjwg at 2022-10-28 00:43:37:

I don't like extensions in executables either. A lot of my previously scripts have been rewritten in Python (when a shell script becomes complex) or Rust (when it needs to be performant or correctly handle all kinds of edge cases). And there are small public projects switching languages too.

For testing Python scripts I just symlink it as or so at a temporary place. Another way is to start an interactive session from the script itself using the code module.

From at 2022-10-28 11:27:11:

Agree completely. Using extensions on shell (or any *nix script) is a Windows/Mac-ism, and does not belong on *nix scripts. Extensions are needed on Windows and Mac so the system knows what program to use to open the files. The *nix way of handling this is the #! line in the file.

I have definitely ended up with *.sh files that begin #!/usr/bin/env perl because things got too gnarly.

I do use that kind of extension, but with a very simple rule: the file has either an extension or any executable bit set, but not both (nor neither).

Or to partially restate the rule: if the file is run directly then its name Is unadorned, whereas if it’s invoked by passing it as an argument to an interpreter then it has an extension that indicates which one (it’s sh, not just sh foo). I do occasionally have these non-executables for one reason or another. Naturally though the far more common case for a non-executable is not that it’s passed to an interpreter but that’s it’s a library which is loaded from a program (which itself is overwhelmingly likely to be +x and therefore unadorned). The common denominator across these cases is that the type of content in the file matters at the point of its use – in the rare case of a non-executable program, I need to know what interpreter to invoke it with. It also helps if the filename keeps me from doing something silly like sh foo.bash.

Stating the rule in terms of +x just makes it very simple and compact while covering all possible cases because an executable is the one case where the type of content doesn’t matter and it’s the shebang line that is the source of truth.

By jwm at 2022-10-29 22:39:06:

Also relevant is How To Do Things Safely In Bash which goes into more of those points in detail. I prefer not using [[ ]] when I can use [ ] most of the time so as not to be tempted to drop the quotes around variables.

Written on 27 October 2022.
« Our computer security problems are our own fault
People like file extensions whether or not they're necessary »

Page tools: View Source, View Normal, Add Comment.
Login: Password:
Atom Syndication: Recent Comments.

Last modified: Thu Oct 27 23:25:52 2022
This dinky wiki is brought to you by the Insane Hackers Guild, Python sub-branch.