What does this zsh solution to “the argument list is too long” do

fileszsh

I read in this answer from @Gilles the following:

In zsh, you can load the mv builtin:

setopt extended_glob
zmodload -Fm zsh/files b:zf_\*
mv -- ^*.(jpg|png|bmp) targetdir/

as a solution to the "mv: Argument list too long” problem. The answer suggests using zsh's mv (as opposed to GNU's) but what exactly does this line do?:

zmodload -Fm zsh/files b:zf_\*

Best Answer

The best way, to look at zsh documentation is using info.

If you run info zsh, you can use the index (think of a book's index) to locate the section that describes the zmodload command.

Press i, then you can enter zmo and press Tab. You'll get straight to the zmodload builtin description which will tell you all about it.

In short, zmodload -F loads the module (if not loaded) and enables only the specified features from that module.

With -m, we enabled the features that match a pattern, here b:zf_*. b: is for builtin, so the above command loads the zsh/files module (see info -f zsh -n 'The zsh/files Module,' for details on that) and only enables the builtins whose name starts with zf_.

zmodload -F zsh/files

loads the module, but doesn't enable any feature:

$ zmodload -FlL zsh/files
zmodload -F zsh/files -b:chgrp -b:chown -b:ln -b:mkdir -b:mv -b:rm -b:rmdir -b:sync -b:zf_chgrp -b:zf_chown -b:zf_ln -b:zf_mkdir -b:zf_mv -b:zf_rm -b:zf_rmdir -b:zf_sync

lists the features of that module specifying which are currently enabled (none for now). You'll notice there's both a mv and zf_mv builtin.

$ zmodload -mF zsh/files 'b:zf_*'
$ zmodload -FlL zsh/files
zmodload -F zsh/files -b:chgrp -b:chown -b:ln -b:mkdir -b:mv -b:rm -b:rmdir -b:sync +b:zf_chgrp +b:zf_chown +b:zf_ln +b:zf_mkdir +b:zf_mv +b:zf_rm +b:zf_rmdir +b:zf_sync

You'll notice the zf_mv builtin has been enabled, but not the mv one (same for the other builtins). That means, those builtin versions of the system commands have been enabled, but without overriding the system one:

$ type zf_mv
zf_mv is a shell builtin
$ type mv
mv is /bin/mv

Now that you have a builtin mv, as zf_mv, not mv, you can do:

zf_mv -- ^*.(jpg|png|bmp) targetdir/

Because zf_mv is builtin, there's no execve() system call, so you won't hit the Too many args limit associated with it.

Of course, you can also do:

zmodload zsh/files # without -F, all the features are enabled
mv -- ^*.(jpg|png|bmp) targetdir/

But beware that replaces the system's mv with zsh builtin equivalent.

To overcome the E2BIG execve() error (the Too many args upon executing an external command), zsh also provides with a zargs function.

You run:

autoload zargs # in ~/.zshrc if you use it often

To mark it for autoloading.

Then you can use:

zargs -- ^*.(jpg|png|bmp) -- mv -t targetdir/

(here assuming GNU mv for the -t option). zargs will run as many mv commands as necessary to avoid the E2BIG (as xargs would do).

Related Question