My scripts end up longer and more verbose typically (I might just not be great at Python) but also a lot more maintainable (I am not great at writing maintainable Perl and I use Python more often).
I also use Node.js for this a lot because of the ecosystem containing so many accessible scripts and dependency management while not great is heaps better than most other ecosystems.
Of course OpenBSD can't do that with Python rather then Perl (as explained in that email). They have a different set of constraints. I am only saying in practice I have found Python to be the better alternative for my personal stuff.
I've got 20 year old python scripts the have been upgraded over the years that are still maintainable, easy to read and do the job just fine.
Take good old fizzbuzz. Want to write it clear and simple in Perl? No problem:
for (my $n = 1; $n <= 100; ++$n) {
if ($n % 15 == 0) { print "fizzbuzz\n"; }
elsif ($n % 3 == 0) { print "fizz\n"; }
elsif ($n % 5 == 0) { print "buzz\n"; }
else { print "$n\n" };
}
Want to get clever and complex in Python? Here you go: def fizzbuzz(n, *args):
cur = ['' for x in range(1,n+1)]
for m, postfix in args:
cur = [y+postfix if x%m==0 else y for x, y in zip(range(1,n+1), cur)]
cur = [str(x) if y == '' else y for x, y in zip(range(1,n+1), cur)]
return cur
print("\n".join(fizzbuzz(100, (3, 'fizz'), (5, 'buzz'))))
Of course you could do an unclear and overly clever Perl version, too, or a clear and simple Python version. say "foo";
was equivalent to print "foo", "\n";Perl obviously even lets experts shoot themselves in the foot on the simplest code, see the fizzbuzz above me, While a beginner would never write the complex python version that was shown, not even by accident.
An example of a langauge at the extreme other end of the spectrum would probably be APL. It's indecipherable gibberish to me every time I look at it, but I'm not even an APL amateur, much less professional, and that language seems built to be used by people that are very familiar with it. Is that wrong? No, they just aren't optimizing for learnability and community growth, as Python does, instead opting to make it as simple and useful as possible for experts to get what they need accomplished.
You can counter by saying that most code is read more than it's written, so readability is more important, and to that I would say that it depends on the type of code (scripting languages lend themselves to a lot of one-off scripts), and that readability is also a function of experience. If you want your code easily readable my amateurs, then keeping the capabilities you can use to those an amateur can understand is beneficial. If you want your code readable and understandable by experienced programmers, use constructs and control flows that they are experienced with, which is a much wider set of capabilities usually (if the language supports them).
I also wouldn't keep all the blocks on the same line, but it can be forgiven in a comment box.
I actually had that at first...
> I might even skip the `== 0` but just saying `(! $n % 15)`
...and that too. I changed them to be more likely to be obvious to even someone who has never seen Perl before. A lot of languages besides Perl have a "for" loop variant that is like a C "for" loop.
The formatting was indeed because it was in a comment box. I didn't want to take up too much vertical space on the page.
for (1..100) {
if (not $_ % 15) { say 'FizzBuzz' }
elsif (not $_ % 3) { say 'Fizz' }
elsif (not $_ % 5) { say 'Buzz' }
else { say }
}
Just sayingIn that I write it once and 20 years later it still works even though I upgraded the compiler many times.
Is there any non-shell scripting language that makes it as easy as shell to run external commands, redirect I/O etc.? I don't know of any. (Perhaps that is the very definition of a shell.)
I think a good way of doing this would be to make a shell that has more expressive power and a clearer syntax.
Would Scsh count?
If my bash script gets much beyond simple looping, I switch to Perl.
CPAN (http://www.cpan.org/index.html) is quite large and has libraries to do almost everything you can imagine.
I am not sure if CPAN or pip has more libraries though.
>> And it's generally easier to read/write.
Python looks nicer, but not everyone likes significant whitespace.
The Python 2 to 3 change has also caused some adoption issues. The Perl 4 to 5 happened quite a while ago. Perl 5 has been integral to Unix-like systems since the web went mainstream.
Perl 5 lives on and is still developed and maintained: https://www.perl.org/get.html
Perl 6 became Raku: https://www.raku.org/
Python 2 is still in use, but officially deprecated: https://www.python.org/doc/sunset-python-2/
You could probably start a business maintaining a Python 2 distribution and selling support to businesses and organizations that rely on Python 2 and are not in a position to upgrade: https://www.activestate.com/blog/python-2-eol-report-card-is...
So do /bin/sh and /bin/bash scripts.
/usr/bin/python scripts IME depend on what /usr/bin/python points to; some break/broke due to the python 2 to python 3 switching, which hasn't been as backwards-compatible as previous examples have.
"#!/usr/bin/env bash" or "#!/usr/bin/env python" are the recommended ways to run both, AFAIK.
Most (all?) commercial Unix I have used also don't have bash, but those systems are pretty dead these days.
OpenBSD's fork of ksh is actually really good so I typically feel no reason to ever install bash on there. On others, the default shell kinda sucks and I tend to install bash. In particular the old /bin/sh on older Unix tends to be worse at interactive use and not support some constructs like {a,b}
I'm typing this on FreeBSD. It's in /usr/local because it came from ports.
$ which bash
/usr/local/bin/bash
Now I try my OpenBSD machine. It's not installed at all. $ which bash
which: bash: Command not found.Why would you expect it to be?
dash is a POSIX shell:
* https://en.wikipedia.org/wiki/Almquist_shell#dash
Even when called as /bin/sh, which implies Bourne/POSIX behaviour, bash leaks all kinds of functionality into the environment such that if you tru to run your "/bin/sh" script on another shell it may not work.
We had all sorts of issues when Debian made the switch: we had to tell people either (a) fix the code, or (b) change your shebang to bash.
If you want Bash behaviour call bash. It's like when I type in "vi" in the command line and I get an editor with all sorts of colours: WTF? If I wanted Vim I would have typed in "vim", but for editing a small file all I need is POSIX vi without the dog and pony display show. /rant
Sometimes I used to create such a symlink myself but I don't use or run a lot of python day to day so my current systems do not have this.
Consider this:
my %h = (); # an empty hash
$h{'foo'}[3]{'bar'} = 'bletch';
Now my hash contains one entry with key 'foo'. That entry is a reference to an array of 4 elements. The first 3 elements of that array are undef, and the 4th is a reference to another hash. That hash contains one entry with key 'bar' and value 'bletch'.In a lot of other languages you would need to explicitly allocate an array of at least 4 elements for $h{'foo'}, and then explicitly allocate a hash for $h{'foo'}[3].
With Perl they just come into existence when you try to store something in them.
Is this always good? No. There are many problems where this automatic reification would just hide bugs. If you are, for example, processing data that is supposed to be clean and well specified then parsing it into explicitly allocated data structures that match what you expect and will throw errors if you try to put something in out of range is great.
But when you are dealing with data that is not clean and not well specified, being able to get it all in your data structures easily even when there is something wrong or inconsistent in the data can be great. Get it all in, and then you can run cleanup code on it looking for unexpected things, and fixing them or alerting you that you need to add more cases to the fixing code.
There's a similar thing on the reading side of Perl data structures. Try to access a hash via a key that does not exist, or an array by an index past the end? You simply get back undef.
Is this always good. Like automatic reification, the answer is no. There are many problems where it would be a grave error if the code tried to access an element of a hash or array that it had not previously but in there.
For problems where your logic would include a lot of places where you check if an element exists and if it does you add to a total or if it is a non-empty string you include it in something else, the code can be cleaner in a language with Perl's approach. Adding undef to a number is like adding 0, and undef compares as equal to an empty string.
Good thing then that the programmer has the choice to use it or not.
repl>>> no autovivification 'store'
repl>>> my %h
%h = ()
repl>>> $h{'foo'}[3]{'bar'} = 'bletch'
Runtime error: Can't vivify reference at (eval …)
> Is this [returning undef for non-existing keys/indexes] always good. […] the answer is no.Good thing then that the programmer has the choice to use it or not.
repl>>> use Hash::Util 'lock_keys'
repl>>> my %h
%h = ()
repl>>> lock_keys %h
()
repl>>> $h{no_such_key}
Runtime error: Attempt to access disallowed key 'no_such_key' in a restricted hash at (eval …)
repl>>> package Array::Bounded {
use Tie::Array;
use base 'Tie::StdArray';
sub FETCH {
my ($self, $index) = @_;
die "Attempt to access out of bounds index $index in a restricted array" if $index >= $self->@*;
return $self->[$index];
}
}
repl>>> tie my @a, 'Array::Bounded'
repl>>> @a = ('a'..'f')
@a = ('a'..'f')
repl>>> $a[5]
f
repl>>> $a[6]
Runtime error: Attempt to access out of bounds index 6 in a restricted array at (eval …)If you want to make a comparison though, I think perl vs sed is a more meaningful one. You can do things that's possible in sed with perl one-liners in a more portable way. It's installed by default on major Linux distributions, and even works on macOS without the GNU vs BSD incompatibilities.
Unfortunately, simple scripts that are found to be useful often grow into complex scripts.
A few weeks ago, I wanted to write some functionality that seemed "simple" enough to be a good fit for make+bash. A week later, I ended up tossing the whole thing and rewriting it in python.
It is useful and not difficult to be productive in, but does manage to feel more unnatural than both of those though.