Reputation: 10326
I have a command which is attempting to generate UUIDs for files:
find -printf "%P\n"|sort|xargs -L 1 echo $(uuid)
But in the result, xargs
is only executing the $(uuid)
subshell once:
8aa9e7cc-d3b2-11e4-83a6-1ff1acc22a7e file1
8aa9e7cc-d3b2-11e4-83a6-1ff1acc22a7e file2
8aa9e7cc-d3b2-11e4-83a6-1ff1acc22a7e file3
Is there a one-liner (i.e not a function) to get xargs
to execute a subshell command on each input?
Upvotes: 22
Views: 21274
Reputation: 158010
This is because the $(uuid)
gets expanded in the current shell. You could explicitly call a shell:
find -printf "%P\n"| sort | xargs -I '{}' bash -c 'echo $(uuid) {}'
Btw, I would use the following command:
find -exec bash -c 'echo "$(uuid) ${1#./}"' -- '{}' \;
without xargs
.
Upvotes: 25
Reputation: 438008
hek2mgl's answer explains the problem well and his solution works well; this answer looks at performance.
The accepted answer is a tad slow, because it creates a bash
process for every input line.
While xargs
is generally preferable to and faster than a shell-code loop, in this particular case the roles are reversed, because shell functionality is needed in each iteration.
The following alternative solution uses a while
loop to process the input lines, and, on my machine, is about twice as fast as the xargs
solution.
find . -printf "%P\n" | sort | while IFS= read -r f; do echo "$(uuid) $f"; done
while
rather than for
, because for
cannot robustly parse command output (in short: filenames with embedded whitespace would break the command - see http://mywiki.wooledge.org/DontReadLinesWithFor).If you're concerned about filenames with embedded newlines (very rare) and use GNU utilities, you could use NUL bytes as separators:
find . -printf "%P\0" | sort -z | while IFS= read -d '' -r f; do echo "$(uuid) $f"; done
Update: The fastest approach is to not use a shell loop at all, as evidenced by ᴳᵁᴵᴰᴼ's clever answer. See below for a portable version of his answer.
Compatibility note:
The OP's find
command implies the use of GNU find
(Linux), and uses features (-printf
) that may not work on other platforms.
Here's a portable version of ᴳᵁᴵᴰᴼ's answer that uses only POSIX-compliant features of find
(and awk
).
Note, however, that uuid
is not a POSIX utility; since Linux and BSD-like systems (including OSX) have a uuidgen
utility, the command uses that instead:
find . -exec printf '%s\t' {} \; -exec uuidgen \; |
awk -F '\t' '{ sub(/.+\//,"", $1); print $2, $1 }' | sort -k2
Upvotes: 6
Reputation: 19194
With a for loop:
for i in $(find -printf "%P\n" | sort) ; do echo "$(uuid) $i"; done
Edit: another way to do this:
find -printf "%P\0" -exec uuid -v 4 \; | sort | awk -F'\0' '{ print $2 " " $1}'
this outputs the filename followed by the uuid (no subshell required) for letting the sort to happen, then swaps the two columns separated by null.
Upvotes: 4