find . -maxdepth 1 -name '*.yaml' -print0 | xargs -r0 the_tool
>To say "you're holding it wrong" is just folly. Bash is worthy of the same treatment that "A Fractal of Bad Design" gave PHP, and it has no place in anything that wants to call itself engineering.If you have a problem that involves "run a command with a list of parameters but not if the list is empty" and you don't immediately think of `xargs -r`, then consider that it's because you don't understand the tools, not because the tools are bad.
find has -exec, xargs isn't necessary.
But it's like, I have to turn my head around and stop doing one thing (globs), and switch over to another thing (find, xargs). The language has baited me down one way of doing it, when I shouldn't be doing it that way. I'm well aware of xargs (but not the -r flag). I typically avoid xargs, as our code has to¹ work on macOS & Linux, and xargs is one of those utilities whose available options differ between the two for even simple use cases. (Such as here, for the "does it exec on no args?".)
But it still proves the point: the equivalent Python is just so much more straight-forward, easier to read, and not apt to shoot me at a moments notice, and doesn't have bugs. A reader only familiar with Python's syntax can read and understand Python, and can write Python that is correct. Not so with bash, you have to understand, "oh, no, globs will screw you here, you need this flag from this utility" — no, it's lunacy.
Like, I can go down the road you're suggesting (and in the past, I did) and keep learning the nooks and crannies and intricacies of bash, each one adding to the nightmare fuel that is my understanding of shell. I can keep tweaking the script around various failure modes, letting each line fail in prod 3 different ways. Or, I can just re-write it in Python, or Rust, or any other sane language and have it never fail again, for reasons that would also either apply to bash, or apply and have worse consequences than bash. (E.g., the Python would raise, bash would just plow on with it.)
Your response is exactly the same as my response before I saw the light on PHP, C, etc.: "you're holding it wrong". No, it's a bad tool that guiles the user into holding it wrong. There are better tools that are easier to hold.
¹b/c despite all work being done for Linux, devs get MBPs, b/c the company can't manage more than exactly 1 type of machine, for everyone.
Popen(["the_tool"] + list(Path().glob("*.yml")))While dethanatos's point about Bash's default behavior with a non-matching glob is on target, the remainder of the example demonstrates a problem in the_tool, which will need to be worked around regardless of how you invoke it.
No?
is this a cloud AMD64 opinion that ignores the 2 trillion devices which Gartner predicts will never run python?
I recently finished a ~600 lines ash script and the kids at work which just graduated complained that *"Y u no python?" For them it was difficult to understand because apparently I avoided cat/grep/dirname/basename/etc and used shell syntax wherever possible to avoid shelling out. (the thing I did would only run when system reaches a certain load)
Everyone forgot that the target hardware has only busybox (ash) and was massively resource constrained. My only options were to implement what I was doing as an eBPF, in C, or shell (ash). The shell script took 4 days (and was refactored/optimized several times within that period), the C (or eBPF) program would have taken ~10 days and the eBPF which is the best solution is unmaintainable once I leave the company.
from a performance perspective I take a bash script any time. Also python programs over time have a risk of eventually pulling in a dozen dependencies.
Plus, if you are running shell out of necessity on a constrained system, that doesn't mean shell deserves to be a programming language where there's actual choice.
how so? there is no python or perl or ruby etc on the target. what would have been a maintainable option other than /bin/sh ? C/C++ ?
> where there's actual choice.
again what choice? :) as I said there is no other solution than what I've given. the system literally doesn't have anything else regardless what "I think" it should support.
Typically, time spent interpreting control flow in a bash script is dwarfed by time spent in the programs called by the script.
> It is literally interpret by lines
It's just splitting lines into words to build up commands. It's not compiling C++.
> starting a whole process per if statements..
In the common pattern `if command1; then command2; fi`, if command1 is a builtin (e.g., `test` or `[`), then bash does not create a subprocess.
There are many ways to run a command on every .yaml file in a directory. Personally I like using xtrace. Something like:
echo cd the_directory > 1.sh
echo "test -f *.yaml" >> 1.sh
ls -1 *.yaml|sed 's/^/the_tool /' >> 1.sh
chmod +x 1.sh
less 1.sh
sh -ex 1.sh
As mostly a day-to-day shell user who automates some things, I do not use Python. Never had an outage. if ls *.yaml > /dev/null; then
the_tool *.yaml
fiIs it really right to not process any files just because one of them just disappeared?
In general, situations like this suffer from race conditions and time-of-check/time-of-use problems.
No idea what you mean here, the glob can expand to one or many.
> In general, situations like this suffer from race conditions and time-of-check/time-of-use problems.
I mean, that's a problem in every version presented so far, one that I don't think can be fixed externally without something extra like hardlinking to a new directory, because "the_tool" apparently can't handle missing files and they can go missing when "the_tool" starts up. That's why I didn't even bother.
In fact, handling that types of errors is what bash is suitable for. "If file is readable, process it" is not an unusual construct. Please do take care however to avoid races if this directory is a spool directory that can be populated at any time, in which case it is useful that rename() is atomic, sometimes an additional working directory is required. Again, this is how the operating system is designed and nothing the shell can gloss over.
I have seen many problems from previously unknown race conditions when concurrency is increased. Scripts have to be kept composable and trivial to understand in order to guard against that.
To be honest, I would argue to remove any script that looks like the last example above. It is not clear what side effects of that bizarre array construct are useful to the system. Keep scripts as simple and readable as possible, a trivial truth perhaps, applicable to every language, but one that needs to be reiterated until the end of our profession.
It absolutely can, I have no idea where you’re getting this idea.
(It is late, bash examples are approximate. Don't sweat the syntax.)
There weren't any YAML files, so argv[1] was literally "*.yaml", which then wasn't found, leading to errors, etc. (Don't even get me started on how there's shopt -s and set -o and WTF.)There weren't any YAML files, so argc was == 0, which the program considered an error. Mea culpea, perhaps.
This failed, b/c bash can't represent an empty array! (And we run -u, as one must, if one wants reasonable behavior.)So finally the contorted,
To say "you're holding it wrong" is just folly. Bash is worthy of the same treatment that "A Fractal of Bad Design" gave PHP, and it has no place in anything that wants to call itself engineering.Use it on the command line in your day to day, fine; but if it's getting automated, jump to at least Python.