I just use xargs -n1. Or -exec with find.
post
Don't use ls if you want to get filenames, it does a bunch of stuff to them. Use a shell glob or find.
Also, because filenames can have newlines, if you want to loop over them, it's best to use one these:
for x in *; do do_stuff "$x"; done # can be any shell glob, not just *
find . -exec do_stuff {} \;
find . -print0 | xargs -0 do_stuff # not POSIX but widely supported
find . -print0 | xargs -0 -n1 do_stuff # same, but for single arg command
When reading newline-delimited stuff with while read, you want to use:
cat filenames.txt | while IFS= read -r x; do_stuff "$x"; done
The IFS= prevents trimming of whitespace, and -r prevents interpretation of backslashes.
A bit of a tangent, but I almost never use xargs in the shell anymore, and instead use “while read line ; do *SOMETHING* $line ; done”, because xargs doesn’t have access to the shell’s local variables, aliases, or functions.
True that the loop is easier to work with, though you can still pass args/env into the sub shell, and xargs' -P is one of my favorites depending on the task (may not be desired in this case). Sometimes I've done both: echo assembled commands in a loop/find -exec, sanity check output, then pipe to xargs ... bash -c to run in parallel.
Good point! A while loop is probably more flexible here and easier to expand too. I will experiment a bit more and maybe I'll change to a while readline implemented as a Bash function.
Great minds, lol. I have almost the exact same command set up as a little script. Mine has an extra modification for my use case, and I named mine iter, but foreach is a good name for it too.
Oh iter is a good one too. :D
Here's the code:
#!/bin/bash
cmd=$@
if echo $cmd | grep '/$'
then
xargs -rd '\n' -I {} $cmd{}
else
xargs -rd '\n' -I {} $cmd {}
fi
Usage is like:
ls *zip | iter shasum
or
ls *zip | iter shasum ../zipfile_archive/
The second one would get the shasum of zip files that have the same name as ones in the cwd
This assumes, of course, that the input files have sane names, ie, no spaces, etc
Thanks for posting. I find the echo part and extra use of variable is a little bit flaky. Here is a modified version. But I am not 100% sure if its doing what your script is doing.
I skipped the extra variable and echo and grep, by comparing its content with ${*}, which is similar to ${@}, but won't separate each argument and create a single string instead. The =~ /$ is a regex comparison, which Bash supports native. Then I am using ${@} for the call, which separates each argument. Maybe this could be done with ${*} instead. I'm not sure which of them is the correct one for this case. At least it seems filenames with spaces work. Otherwise, not claiming it would be better. Just giving some food for thoughts.
#!/usr/bin/bash
if [[ "${*}" =~ /$ ]]; then
xargs -rd '\n' -I {} "${@}"{}
else
xargs -rd '\n' -I {} "${@}" {}
fi
How to call xargs is typically one of those things I always forget. The foreach alias is a great solution!
My current solution was to use tldr for all of these tools, but yeah if I find myself having to do a for each line, I'll definitely steal your alias.
Luckily (knocks on wood) I almost exclusively work with yaml and json nowadays so I should just learn yq.
all 16 comments