Honest question, as I’m struggling to leave the shell environment once the program gets too large. I could use Perl, but $? and the likes get quickly out of hand. Python’s support for pipes was difficult last time I used it, but that may have changed. What would you recommend?
Sharing this because its the route I went, anything I'd have written in Bash I'd now do in Perl.
Python to me, is too far away from shell/unix. It is a programming language for writing applications. For the use case of writing shell scripts but in a more powerful language, perl is still the king here (or it should be. Sadly it doesn't appear to be the case. No one is using it except for die hard gray beards.)
Raku is a modern (still a big) language with kitchen sink. Again doesn't appear to be much uptake.
Nim is statically typed and (generally) native-compiled, but it has very low ceremony ergonomics and a powerful compile-time macro/template system as well as user-defined operators (e.g., you can use `+-` to make a constructor for uncertain values so that `9 +- 2` builds a typed object as in https://github.com/SciNim/Measuremancer .
My use case is approx. like this: I can get 80% what I want with ls … | sed … | grep -v … but then it gets complicated in the script and I’d like to replace the sed or grep part with some program.
import posix; for line in popen("ls", "r").lines: echo line
in Nim, though you obviously need to replace `echo line` with other desired processing and learn how to do that.You might also want to consider `rp` which is a program generator-compiler-runner along the lines of `awk` but with all the code just Nim snippets interpolated into a program template: https://github.com/c-blake/bu/blob/main/doc/rp.md . E.g.:
$ ls -l | rp -pimport\ stats -bvar\ r:RunningStat -wnf\>4 r.push\ 4.f -eecho\ r
RunningStat(
number of probes: 26
max: 31303.0
min: 23.0
sum: 84738.0
mean: 3259.153846153846
std deviation: 6393.116633069013
)While it may not be in any ANSI/ISO spec for C, even Windows has popen these days. There are some tiny popenr/popenw wrappers in https://github.com/c-blake/cligen/blob/master/cligen/osUt.ni...
Depending upon how balanced work is on either side of the pipe, you usually can even get parallel speed-up on multicore with almost no work. For example, there is no need to use quote-escaped CSV parsing libraries when you just read from a popen()d translator program producing an easier format: https://github.com/c-blake/nio/blob/main/utils/c2tsv.nim
Just the inclusion of argparse alone is worth it IMO.
> Python’s support for pipes was difficult
Well, the idea would be to replace a lot of your pipe usage.
Off the wall, but Scala has a concise syntax for process operations, but startup time is likely prohibitive.
Even allows to add dependencies and if necessary compile the script on the fly.
Spending extra time doing simple things — because you need to Google e.g. "how to pass multiple space-separated arguments from a string to a command" — is also a waste of time.
I have that taken care of already
GNU xargs implements limited parallelization, and is compiled C. This functionality is present within busybox, including the Windows version.
https://www.linuxjournal.com/content/parallel-shells-xargs-u...
GNU Parallel will have much greater functionality, but it will not reach as far as xargs.
Time to rewrite it in Rust /s
:p
It isn't even just the newer shells that have solved this, zsh also has a solution out of the box¹. The extensive globbing support in zsh can largely replace `find`, and things like zargs allow you to reuse your common knowledge throughout the shell.
For example, performing your first example with zargs would use regular option separators(`--`), regular expansion(`{1..5}`), and standard shell constructs for the commands to execute.
I'll contrive up an example based around your file counter, but slightly different to show some other functionality.
f() { fs=($1/*(.)); jo $1=$#fs }
zargs -P 32 -n1 -- **/*(/) -- f
That should recursively list directories, counting only the files within each, and output² jsonl that can be further mangled within the shell². You could just as easily populate an associative array for further work, or $whatever. Unlike bash, zsh has reasonable behaviour around quoting and whitespace too.Edit to add: I'm not suggesting zargs is a replacement for parallel, but if you're only using a small subset of its functionality then it may be able to replace that.
¹ https://zsh.sourceforge.io/Doc/Release/User-Contributions.ht...
If you feel like the answer is rewriting the shell, the answer is practically never rewriting the shell. It's learning to use it.
In a proper programming language, we'd have something like
Instead, the shell environment forces programs to invent their own parameter separator (:::), a templating format ({1}), and a way to output a list of structures (CSV-like). You can see the same issues in `find`, where the exec separator is `\;`, the template is `{}`, and the output is delimited by \n or \0.It's very hard to acquire and retain mastery with a toolbox where every tool is reinvents the basics. If you ever found yourself searching "find exec syntax" multiple times in a week, it's not your fault.
As for alternatives, I'm a fan of YSH[1] (Javascript-like), Nushell[2] (reinvented from first-principles for simplicity and safety) and Fish[3] (bash-like but without the footguns). Nushell is probably my favorite from the bunch, here's a parallel example:
[1] https://www.oilshell.org/release/latest/doc/ysh-tour.html[2] https://github.com/nushell/nushell
[3] https://fishshell.com/