pointless pain: find and xargs

I have discovered that there are zsh users who torture themselves with unnecessary commands like find and xargs. While I do not think this is something of which to be ashamed, other solutions exist.

zsh has powerful globbing, which makes find irrelevant. For example, if one wanted to get a list of all files under /home that start with the character « q », one could do

find /home -name "q\*" -print

With zsh, one could type

print -l /home/\*\*/q\*

Another trivial example is that

find /home -name "q\*" -exec rm {} \\;

could become

for i (/home/\*\*/q\*) rm $i

I imagine that there are a number of cynics out there saying, « Oh, no, asterisks confuse me, and so it is not worth it for me to change my silly ways, and furthermore, find can do lots of things. » Yes, find can do many things beyond filename comparisons, but zsh is actually more powerful. Want to glob based on file ownership? This find command

find /home -name "q\*" \\! -user clint -print

is so much more pleasant as

print -l /home/\*\*/q\*(\^u:clint:)

The reader is invited to figure out how to accomplish the task of identifying all files owned by the process's effective user ID with zsh, and compare it to the same task with an inferior shell.

Now, to introduce zargs, try a silly xargs example

echo this is a test of the emergency broadcast system | xargs -n2

The Z-Shell version is not much of an improvement

zargs -n2 this is a test of the emergency broadcast system

One may run into length issues when not using shell builtins. For example, this command would work regardless of how many files the glob matches, and regardless of what characters those filenames contain (whitespace will be handled fine, and the command will print one file per line, unless there are newlines within the filenames):

print -l /\*\*/j\*

This command will not work with a very long list of files, unless one is explicitly using zsh's builtin rm command, which is not loaded by default.

rm /\*\*/j\*

To successfully delete all those files beginning with « j » and owned by “mrwiggles”, one can do

for i in /\*\*/j\*(u:mrwiggles:); do rm $i ; done

which will handle any number of arguments, but will fork rm for each file, or one can use

find / -name j\* -user mrwiggles -print0 | xargs -0 rm

(where it is necessary to delimit with NULs to avoid word-splitting problems introduced by whitespace) or, better yet,

zargs -- /\*\*/j\*(u:mrwiggles:) -- rm

Note that for each shell instance, you need to either load the zargs function explicitly, or autoload the zargs function explicitly, as in autoload -U zargs

These examples are fairly simplistic, but with more complex tasks, doing things the zsh way can pay off considerably.

Posted on 2005-06-13
Tags: