Inconsistent syntax native bash methods so its an additional syntax to learn.
> Template mode — single-quoted commands work as shell templates: enumerate -f '*' -- 'cat {} | head -5'
Passing commands a a single string is BAD. Now you have to think about escaping and quoting. What does {} get replaced with if the enumerant contains unsafe characters? Can it be used as part of a larger argument or only on its own? Who knows, it's not bash. Compared to a bash loop its also always a subshell with all the implications that has - even find can be piped into a normal bash loop.
> Filters — --include and --exclude with glob patterns
A fraction of what find or native loops provide. And since this can't replace them in general enumerate is an additional thing to learn on top.
> Extensible — drop a file in lib/enumerators/ to add custom sources
To extend bash loops you don't even need root access, you just add the code to the loop.
One uses xargs or parallel only a few times before they remember some of the quirks that but them. And then they become cautious. And then it’s muscle memory. And if it’s not an often occurrence, they learn to check the man page.
Anyways, in a world of “vibecoding” why add another “tool” to the mix when the LLMs have been trained on all our stackoverflow-posted grievances to begin with?
https://github.com/wallach-game/bashumerate/pull/1/files
to me looks like a right solution, even if I was also tired of writing for loops by hand usually
The obvious solution would be to use something more sane, like PowerShell or nushell, but instead old experts will always defend the skills they have honed for years, while criticizing anything that's different.
Just go with
foo | while read X; do bar "$X"; done ls -1 ./*.sh | xargs -rd\\n sh -c 'for i in "$@" ; do ... ; done' sh
1. not strictly necessary to use -1 as I believe all common ls detect !isatty(stdout) and produce line-by-line output anyway.2. xargs -r just doesn't run the command if there's no input, also not strictly necessary but I'm addicted to using it because it's the sensible default to me.
3. xargs -d\\n makes it collect fields as full lines, which is what you typically want, unless you're able to generate NULs.
4. use whatever shell you want of course, but I don't use bashisms, etc., by default, /bin/sh is fine for me, even if it's dash.
5. the trailing "sh" at the end is due to a quirk of `sh -c` usage, where $0 is the first non option argument, so `printf %s\\n 1 2 3 | xargs -rd\\n sh -c 'for i in "$@" ; do printf "%s " "$i" ; done ; echo'` (note the lack of trailing "sh") would only print "2 3 " as $0 is not included in "$@" ($0 is 1, $1 is 2, $2 is 3). It's very easy to just always give the shell name itself manually as $0 instead of trying to ingest "$0" into your logic.
Of course, you could `find . -maxdepth 1 -type f -name '*.sh' -print0 | xargs -r0 ...` instead, depending on what you're up to, that may be the easiest. It's definitely the simplest -- as long as your xargs has -0 support.
Always use -0. Most gnu utilities support it. It makes them put a null byte after every filename instead of a newline. Completely eliminates the problem of dealing with whitespace in the filenames.
That's not actually simpler though. Simple is removing everything unnecessary. You took commands which could already do what you wanted, and added an extra program which calls them in specific ways. This will add bugs and maintenance headaches, not be portable, etc. This is added complexity.
The reason you made this script is not because you wanted simpler, you wanted easier. There's nothing wrong with that, and I'll grant you it probably is, especially for those unaccustomed to these commands. But easier != simpler. Often you'll find that simple is hard and easy is complexity deferred.
The --dry-run flag of parallel made me confident to do more batch processing than I ever did with xargs.
Parallel has an option for almost everything, it's almost too much.
But I have shopped around for alternatives. The creator Ole Tange maintains a painstakingly long article of the alternatives and their differences. [0]
The gnu parallel book and reading materials [1] are excellent too.
[0] https://www.gnu.org/software/parallel/parallel_alternatives....
Not everyone has the luxury to only work with their own computer, or run random software on IT/customer managed systems.
find -print0 | xargs -0 -I {} "the {} iterated command"(*35 years living and loving sh/ksh/bash/dash etc, only tried pwsh so I could write some comparisons and slag off MS a bit; now it's my default shell on everything)
I wonder if the generation one or two before mine grew up without shells and then had to make them and had no particular preconceptions, but my generation grew up treating the way shells work as a law of physics and thus doesn't innovate. Actually I wonder how much ossification is explained by this in general.
> find . -name '*.log' | xargs rm
No need for prayer:
find . -name '*.log' -print0 | xargs --null rm
That uses null as a delimiter instead of linebreaks.So they wrote a new tool and posted it on HN before checking the manual about null character termination... Not trustable at all, what a waste of time!
Additionally the use of "--" is not what everybody expects: here it is used to introduce one argument, the command, while it's usually meant to introduce multiple arguments without worrying if they have a leading "-".
A possible revised syntax with command as the first argument followed by a list of globs optionally introduced by "--" would allow to enumerate all files with a leading "-", which the current syntax cannot:
enumerate 'whatever {}' -- '-*'
I'm assuming "-f" for simplicity, but the same reasoning holds for "-L" too.Or maybe use: `find ... -print0 | xargs -0 ...`
I guess you're still hoping your filenames don't contain an ASCII NUL, although that's a fairly reasonable assumption for all but the most paranoid use-cases.
find . -name '*.log' -print0 | xargs -0 rm
For this simple example (derived from the article), find also has a delete operator.This null termination is now a POSIX standard.
The printf zero-padding should be a built-in feature, as dealing with lots of filename structures and date structures needs zero-padding.
I often end up doing things like "for i in $(seq -w 1 100); do.." in bash.
My preference would actually be that -r does automatic zero-padding to fit the widest number that will come out of the range, and another variant like -R would suppress that behavior; but it could also be an optional switch.
It might also be nice to have the code recognize when start is greater than end, like -r 10 1, and support backwards-counting.
E.g. removing all temporary files without the need to deal with spaces, glob limits, or silly find syntax:
csv-ls -R -c full_path . | csv-grep -c full_path -e '~$' | csv-exec -- rm -f %full_path
You need to establish that every program's interpretation points to the same object before processing the object through the command chain. Stopping a bad command is more important than running the good ones.
There is no tool that does this one job.
Fails : enumerate -f '.txt' -- 'wc -l {}'
For all use cases
Correct : enumerate -f '.txt' -- 'wc -l -- {}'
seq 1 10|xargs -I@ echo 'bash run.py @'|parallel -j 10
I know the echo is a little silly but then I can remove the |parallel and see if it's right. And if I don't want parallelism, I just pass to bash
Most of this, if not all, is fixable by adding a `export IFS=$'\n'` to your bashrc. I'm not trying to disregard your project, just point out something that took me years to learn and I currently use extensively to solve this very problem. Perhaps you didn't know about it until now... :)
(For enumerating files, I use find or dir/b/s, possibly combined with grep, then pipe the result in.)
People moaning about avoiding non-basic use of xargs and bash (and inadvertently demonstrating in many cases why some of us think that xargs sucks) miss a large part of the point, which is that it's nice to have a tool that does exactly what you want, and works in a way that's convenient for you, and isn't so widely used that you have to worry about modifying it. If you find the tool doesn't work the way you like, you can just change it. You don't have to be answerable to anybody else. I think tptacek's quite good essay could be relevant: https://sockpuppet.org/blog/2026/05/12/emacsification/
(You don't have to use LLMs for this! I wrote my program by hand, it's only like 250 lines of Python, and you could write one too. But, whichever barriers to entry prevent you from creating your own tools for yourself, using an LLM would probably lower at least some of them.)
for x (*.sh); echo "before $x after"
or for x in *.sh; echo "before $x after"
or for x (*.sh) { echo -n "before "; echo -n $x; echo " after" }it feel like the syntax here is odd. it still requires me to write quote my command i want to run? unfortunately i'll have to pass on this.
personally i'd want some variant where i can still auto-complete commands and have just have {} as a placeholder. (maybe time to learn how to use xargs for real?)
for f in *.txt; do
wc -l "$f"
done
No, because `wc` accepts multiple files. And the example given is incorrect for any file having a `.txt` suffix and whitespaces.> Or this?
find . -name '*.sh' -exec wc -l {} +
No.> These all work, but each has its own syntax, its own flags, its own quirks. I wanted something simpler — one consistent way to iterate over anything.
And therein lies the proverbial xkcd standards[0] proof.
`find` has `-print0` and `xargs` has the matching `-0` flag for this. So this specific argument makes me doubt the author knows their tools. Stopped reading at this point.