3

When I am using this command mv * .., I am getting the error -bash: /usr/bin/mv: Argument list too long. I do understand the reason this occurs is because bash actually expands the asterisk to every matching file, producing a very long command line. How can I fix this error?

ilkkachu
  • 138,973
Patrick
  • 39
  • 1
  • 4

2 Answers2

4

You could do it in two or more steps:

mv [a-k]* ..    # or some other pattern matching a subset of the files
mv -- * ..

Or in a loop,

for name in *; do
    mv -- "$name" ..
done

(But this would call mv for each and every name individually.)

Or get find to help you:

find . -mindepth 1 -maxdepth 1 -exec mv -t .. -- {} +

This would find all names in the current directory, and using GNU mv, would move them to the directory above with as few invocations of mv as possible.

Without GNU mv, but with a find that still know the non-standard -mindepth and -maxdepth predicates,

find . -mindepth 1 -maxdepth 1 -exec sh -c 'mv -- "$@" ..' sh {} +

None of these variations care about name collisions. You should test on data that is properly backed up.

Kusalananda
  • 333,661
  • 1
    See find . ! -name . -prune -exec ... for the standard equivalent of -mindepth 1 -maxdepth 1. Note that find doesn't exclude hidden files. – Stéphane Chazelas Nov 01 '21 at 13:25
4

The limit is not in bash but in the execve() system call used to execute external commands. You could do:

printf '%s\0' * | xargs -r0 mv -t .. --

Since printf is builtin in bash, there's no execve() at that point. And it's xargs job to split the list of arguments so as to avoid that limit. Here using the GNU-specific -t option of GNU mv.

With zsh, you can load the mv builtin:

zmodload zsh/files
mv -- * ..

Or use its zargs helper to do the splitting:

autoload -Uz zargs # best in ~/.zshrc
zargs -r -- ./* -- mv -t ..

You can replace ./* with ./*(D) to also move hidden files or add the oN glob qualifier to skip the sorting of file names, or the N glob qualifier (with zargs -r) to avoid the error if there's no matching file.

zargs -r -- ./*(ND) -- mv -t ..

Same as:

print -rNC1 ./*(ND) | xargs -r0 mv -t ..

But without the dependency to GNU xargs.

On Linux (the kernel typically used on Ubuntu), you can also raise that execve() limit by raising the stacksize resource limit:

bash-5.1$ /bin/true {1..200000}
bash: /bin/true: Argument list too long
bash-5.1$ ulimit -s unlimited
bash-5.1$ /bin/true {1..200000}
bash-5.1$ 

It's not fully unlimited (at least not in current versions of the kernel):

bash-5.1$ /bin/true {1..2000000}
bash: /bin/true: Argument list too long

Note that the limit is on the cumulated size of the arguments and the environment variables passed to execve(), but the computation is not just the sum of the bytes in there and how it's done varies between OS and version thereof.