解决“ MV:参数列表过长”?


Answers:


82

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

当变量参数列表位于末尾时,这两种方法都更易于实现,在这里不是这种情况:to的最后一个参数mv是目标。使用GNU实用程序(即在非嵌入式Linux或Cygwin上)时,-tto 的选项mv很有用,它首先传递目标。

如果文件名没有空格或\"',则只需提供文件名作为输入即可xargs(该echo命令是内置的bash,因此不受命令行长度限制):

echo !(*.jpg|*.png|*.bmp) | xargs mv -t targetdir

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

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

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

find . -name . -o -type d -prune -o \
       -name '*.jpg' -o -name '*.png' -o -name '*.bmp' -o \
       -exec mv -t targetdir/ {} +

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

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

find . -name . -o -type d -prune -o \
       -name '*.jpg' -o -name '*.png' -o -name '*.bmp' -o \
       -exec sh -c 'mv "$@" "$0"' targetdir/ {} +

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

setopt extended_glob
zmodload zsh/files
mv -- ^*.(jpg|png|bmp) targetdir/

或者,如果您愿意让mv和其他名称继续引用外部命令:

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

或带有ksh样式的glob:

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

或者,使用GNU mvzargs

autoload -U zargs
setopt extended_glob
zargs -- ./^*.(jpg|png|bmp) -- mv -t targetdir/

1
前两个命令返回“ -bash:!:未找到事件”,后两个命令根本不移动任何文件。如果您应该知道的话,我将使用CentOS 6.5
Dominique

1
@Dominique我使用了与问题中使用的相同的语法。您需要shopt -s extglob启用它。我错过了find命令中的步骤,已经修复了它们。
吉尔斯2014年

我通过find命令“ find:invalid expression;使用了二进制运算符'-o',之前没有任何内容”来获取该信息。我现在将尝试其他方法。
多米尼克(Dominique)

@Dominique find我发布(现在)的命令起作用。复制粘贴时,您必须保留一部分。
吉尔斯2014年

Gilles,对于find命令,为什么不使用“ not”运算符!?比起奇数尾部,它更明确,更容易理解-o。例如,! -name '*.jpg' -a ! -name '*.png' -a ! -name '*.bmp'
CivFan 2015年

13

如果使用Linux内核就足够了,那么您只需

ulimit -s 100000

之所以会奏效,是因为Linux内核大约在10年前包含了一个补丁,该补丁将参数限制更改为基于堆栈大小:https : //git.kernel.org/pub/scm/linux/kernel/git/torvalds/linux.git/提交/?​​id = b6a2fea39318e43fee84fa7b0b90d68bed92d2ba

更新:如果您感到勇敢,可以说

ulimit -s unlimited

只要有足够的RAM,您就可以进行任何Shell扩展。


那是骇客。您怎么知道将堆栈限制设置为什么?这也会影响在同一会话中启动的其他进程。
库萨兰达

1
是的,这是骇客。在大多数情况下,这种黑客都是一次性的(无论多长时间手动手动移动大量文件?)。如果您确定该过程不会消耗掉所有RAM,则可以进行设置ulimit -s unlimited,它将对几乎不受限制的文件起作用。
Mikko Rantalainen

ulimit -s unlimited实际的命令行限制为2 ^ 31或2 GB。(MAX_ARG_STRLEN在内核源代码中。)
Mikko Rantalainen

9

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

for x in *; do case "$x" in *.jpg|*.png|*.bmp) ;; *) mv -- "$x" target ;; esac ; done

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

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

有了超过一百万个文件,这将反过来产生一百万个以上的mv进程,而不是仅使用POSIX find解决方案@Gilles发布的几个进程。换句话说,这种方式导致大量不必要的CPU流失。
CivFan 2015年

@CivFan另一个问题是说服自己修改后的版本等同于原始版本。显而易见,case关于*扩展结果以过滤出几个扩展名的语句等同于原始!(*.jpg|*.png|*.bmp)表达式。该find答案其实不等同; 它分为子目录(我看不到-maxdepth谓词)。
哈兹2015年

-name . -o -type d -prune -o防止下降到子目录。-maxdepth显然不符合POSIX,尽管我的find手册页中没有提到。
CivFan 2015年

回滚至修订版1。问题未提及任何有关源或目标变量的内容,因此这给答案增加了不必要的麻烦。
哈兹2015年

5

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

将大小MAX_ARG_PAGES增加到大于32 的大小。这将增加内核将用于程序参数的内存量,从而使您可以为一百万个文件或正在执行的操作指定您的mvrm命令。重新编译,安装,重新启动。

谨防!如果为系统内存设置的值太大,然后运行带有很多参数的命令,则会发生错误的事情!对多用户系统执行此操作时要格外谨慎,这会使恶意用户更容易耗尽您的所有内存!

如果您不知道如何手动重新编译和重新安装内核,那么最好只是假装此答案暂时不存在。


5

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

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

感谢@Score_Under

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

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

要做一个更通用的移动所有文件的解决方案,您可以执行以下操作:

for file in "$origin"/*; do mv -- "$file" "$destination" ; done

如果执行缩进,则如下所示:

for file in "$origin"/*; do
    mv -- "$file" "$destination"
done 

这将把原始文件中的每个文件都一一地移到目的地。如果$file文件名中包含空格或其他特殊字符,则必须使用引号引起来。

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

for file in "/Users/william/Pictures/export_folder_111210/"*.jpg; do
    mv -- "$file" "/Users/william/Desktop/southland/landingphotos/";
done

您可以在for循环中使用原始glob之类的内容,以更接近所需的解决方案。
2015年

你是什​​么意思
白猫2015年

抱歉,这有点含糊,我指的是问题中的问题!(*.jpg|*.png|*.bmp)。您可以通过遍历将其添加到for循环中,"$origin"/!(*.jpg|*.png|*.bmp)从而避免了对Kaz答案中使用的开关的需求,并保持了for循环的简单主体。
2015年

很棒的分数。我纳入了您的评论并更新了我的答案。
Whitecat

3

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

import glob, shutil

for i in glob.glob('*.jpg'):
  shutil.move(i, 'new_dir/' + i)

1

mv如果您不介意多次运行此限制,则可以在继续使用的同时绕开该限制。

您可以一次移动部分。例如,假设您有一长串字母数字文件名。

mv ./subdir/a* ./

这样可行。然后敲掉另一个大块。几步走后,您可以回到使用状态mv ./subdir/* ./


0

这是我的两分钱,请附加到 .bash_profile

mv() {
  if [[ -d $1 ]]; then #directory mv
    /bin/mv $1 $2
  elif [[ -f $1 ]]; then #file mv
    /bin/mv $1 $2
  else
    for f in $1
    do
      source_path=$f
      #echo $source_path
      source_file=${source_path##*/}
      #echo $source_file
      destination_path=${2%/} #get rid of trailing forward slash

      echo "Moving $f to $destination_path/$source_file"

      /bin/mv $f $destination_path/$source_file
    done
  fi
}
export -f mv

用法

mv '*.jpg' ./destination/
mv '/path/*' ./destination/
By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.