解决“mv:参数列表太长”?

Dom*_*que 94 shell bash arguments mv

我有一个文件夹,里面有超过一百万个文件需要排序,但我真的什么也做不了,因为一直在mv输出这条消息

-bash: /bin/mv: Argument list too long
Run Code Online (Sandbox Code Playgroud)

我正在使用此命令移动无扩展名的文件:

mv -- !(*.jpg|*.png|*.bmp) targetdir/
Run Code Online (Sandbox Code Playgroud)

Gil*_*il' 111

xargs是工作的工具。那,或find-exec … {} +. 这些工具多次运行一个命令,一次传递尽可能多的参数。

当变量参数列表位于末尾时,这两种方法都更容易执行,这里的情况并非如此:最后一个参数mv是目标。对于 GNU 实用程序(即在非嵌入式 Linux 或 Cygwin 上),-t选项 tomv很有用,可以先传递目标。

如果文件名没有空格也没有任何\"',那么您可以简单地提供文件名作为输入xargs(该echo命令是 bash 内置命令,因此它不受命令行长度限制;如果您看到!: event not found,则需要使用shopt -s extglob)启用通配语法:

echo !(*.jpg|*.png|*.bmp) | xargs mv -t targetdir
Run Code Online (Sandbox Code Playgroud)

您可以使用该-0选项来xargs使用以空分隔的输入,而不是默认的带引号的格式。

printf '%s\0' !(*.jpg|*.png|*.bmp) | xargs -0 mv -t targetdir
Run Code Online (Sandbox Code Playgroud)

或者,您可以使用find. 为避免递归到子目录中,请使用-type d -prune. 由于未为列出的图像文件指定任何操作,因此仅移动其他文件。

find . -name . -o -type d -prune -o \
       -name '*.jpg' -o -name '*.png' -o -name '*.bmp' -o \
       -exec mv -t targetdir/ {} +
Run Code Online (Sandbox Code Playgroud)

(这包括点文件,与 shell 通配符方法不同。)

如果您没有 GNU 实用程序,则可以使用中间 shell 以正确的顺序获取参数。此方法适用于所有 POSIX 系统。

find . -name . -o -type d -prune -o \
       -name '*.jpg' -o -name '*.png' -o -name '*.bmp' -o \
       -exec sh -c 'mv "$@" "$0"' targetdir/ {} +
Run Code Online (Sandbox Code Playgroud)

在 zsh 中,您可以加载mv内置函数:

setopt extended_glob
zmodload zsh/files
mv -- ^*.(jpg|png|bmp) targetdir/
Run Code Online (Sandbox Code Playgroud)

或者,如果您更喜欢 letmv和其他名称继续引用外部命令:

setopt extended_glob
zmodload -Fm zsh/files b:zf_\*
zf_mv -- ^*.(jpg|png|bmp) targetdir/
Run Code Online (Sandbox Code Playgroud)

或使用 ksh 样式的 glob:

setopt ksh_glob
zmodload -Fm zsh/files b:zf_\*
zf_mv -- !(*.jpg|*.png|*.bmp) targetdir/
Run Code Online (Sandbox Code Playgroud)

或者,使用 GNUmvzargs

autoload -U zargs
setopt extended_glob
zargs -- ./^*.(jpg|png|bmp) -- mv -t targetdir/
Run Code Online (Sandbox Code Playgroud)

  • 前两个命令返回“-bash: !: event not found”,接下来的两个命令根本没有移动任何文件。如果你知道的话,我在 CentOS 6.5 上 (2认同)

Mik*_*nen 33

如果使用 Linux 内核就足够了,你可以简单地做

ulimit -S -s unlimited
Run Code Online (Sandbox Code Playgroud)

这将起作用,因为 Linux 内核在大约 10 年前包含一个补丁,该补丁将参数限制更改为基于堆栈大小:https : //git.kernel.org/pub/scm/linux/kernel/git/torvalds/linux.git/ commit/?id=b6a2fea39318e43fee84fa7b0b90d68bed92d2ba

如果你不想要无限的堆栈空间,你可以说例如

ulimit -S -s 100000
Run Code Online (Sandbox Code Playgroud)

将堆栈限制为 100MB。请注意,您需要将堆栈空间设置为正常堆栈使用(通常为 8 MB)加上您想要使用的命令行的大小。

您可以按如下方式查询实际限额:

getconf ARG_MAX
Run Code Online (Sandbox Code Playgroud)

这将输出以字节为单位的最大命令行长度。例如,Ubuntu 默认将2097152其设置为大约 2 MB。如果我使用无限堆栈运行,我会得到4611686018427387903正好是 2^62 或大约 46000 TB。如果您的命令行超出该范围,我希望您能够自己解决该问题。

  • 是的,这是一个黑客。大多数时候,这种黑客行为是一次性的(您手动移动大量文件的频率如何?)。如果您确定该进程不会耗尽您所有的 RAM,您可以设置“ulimit -s unlimited”,它将适用于几乎无限的文件。 (3认同)

Kaz*_*Kaz 9

操作系统的参数传递限制不适用于 shell 解释器中发生的扩展。因此,除了使用xargsor 之外find,我们还可以简单地使用 shell 循环将处理分解为单独的mv命令:

for x in *; do case "$x" in *.jpg|*.png|*.bmp) ;; *) mv -- "$x" target ;; esac ; done
Run Code Online (Sandbox Code Playgroud)

这仅使用 POSIX Shell 命令语言功能和实用程序。这个单行通过缩进更清晰,删除了不必要的分号:

for x in *; do
  case "$x" in
    *.jpg|*.png|*.bmp) 
       ;; # nothing
    *) # catch-all case
       mv -- "$x" target
       ;;
  esac
done
Run Code Online (Sandbox Code Playgroud)

  • 如果文件超过一百万个,这将反过来产生超过一百万个“mv”进程,而不仅仅是使用 @Gilles 发布的 POSIX“find”解决方案所需的少数进程。换句话说,这种方式会导致大量不必要的 CPU 变动。 (2认同)

小智 8

有时只写一个小脚本是最简单的,例如在 Python 中:

import glob, shutil

for i in glob.glob('*.jpg'):
  shutil.move(i, 'new_dir/' + i)
Run Code Online (Sandbox Code Playgroud)


Whi*_*cat 7

使用"$origin"/!(*.jpg|*.png|*.bmp)代替 catch 块的更简单的解决方案:

for file in "$origin"/!(*.jpg|*.png|*.bmp); do mv -- "$file" "$destination" ; done
Run Code Online (Sandbox Code Playgroud)

感谢@Score_Under

对于多行脚本,您可以执行以下操作(注意删除;之前的done):

for file in "$origin"/!(*.jpg|*.png|*.bmp); do        # don't copy types *.jpg|*.png|*.bmp
    mv -- "$file" "$destination" 
done 
Run Code Online (Sandbox Code Playgroud)

要执行移动所有文件的更通用的解决方案,您可以执行单行:

for file in "$origin"/*; do mv -- "$file" "$destination" ; done
Run Code Online (Sandbox Code Playgroud)

如果您进行缩进,它看起来像这样:

for file in "$origin"/*; do
    mv -- "$file" "$destination"
done 
Run Code Online (Sandbox Code Playgroud)

这将获取源中的每个文件并将它们一个一个地移动到目的地。如果$file文件名中有空格或其他特殊字符,则需要使用引号。

这是此方法完美运行的示例

for file in "/Users/william/Pictures/export_folder_111210/"*.jpg; do
    mv -- "$file" "/Users/william/Desktop/southland/landingphotos/";
done
Run Code Online (Sandbox Code Playgroud)


小智 6

尝试这个:

find currentdir -name '*.*' -exec mv {} targetdir \;
Run Code Online (Sandbox Code Playgroud)
  • find: 搜索文件夹
  • -name: 匹配所需的条件
  • -exec: 运行下面的命令
  • {}: 插入找到的文件名
  • \;: 标记exec命令的结束

  • 在“-exec”之前添加“-maxdepth 1”有助于将文件移动到子目录,以避免在移动后再次匹配它们。 (5认同)

Per*_*ins 5

要获得比以前提供的更激进的解决方案,请调出您的内核源代码并编辑 include/linux/binfmts.h

将 的大小MAX_ARG_PAGES增加到大于 32。这会增加内核允许程序参数的内存量,从而允许您为一百万个文件或您正在做的任何事情指定您的mvrm命令。重新编译,安装,重启。

谨防!如果您将它设置为系统内存太大,然后运行带有大量参数的命令,将会发生坏事!对多用户系统执行此操作时要格外小心,它会使恶意用户更容易耗尽您的所有内存!

如果您不知道如何手动重新编译和重新安装您的内核,那么您最好只是假装这个答案目前不存在。