复制文件时参数列表过长


Answers:


36

cp *.prj ../prjshp/是正确的命令,但是您遇到一种罕见的情况,即遇到大小限制。您尝试的第二个命令没有任何意义。

一种方法是对cp文件进行分块运行。该find命令知道如何执行此操作:

find -maxdepth 1 -name '*.prj' -exec mv -t ../prjshp {} +
  • find 递归遍历当前目录及其下的目录。
  • -maxdepth 1 表示停在1的深度,即不要递归到子目录中。
  • -name '*.prj'表示仅对名称与指定模式匹配的文件起作用。请注意模式周围的引号:它将由find命令而不是shell解释。
  • -exec … {} +表示对所有文件执行指定的命令。如有必要,它将多次调用命令,请注意不要超过命令行限制。
  • mv -t ../prjshp将指定的文件移动到中../prjshp。在此-t使用该选项是因为find命令的局限性:找到的文件(由标记{})作为命令的最后一个参数传递,您不能在其后添加目标。

另一种方法是使用rsync

rsync -r --include='*.prj' --exclude='*' . ../prjshp
  • rsync -r … . ../prjshp../prjshp递归复制当前目录。
  • --include='*.prj' --exclude='*'表示复制匹配的文件*.prj并排除其他所有文件(包括子目录,因此.prj将找不到子目录中的文件)。

3
rsync,是迄今为止最简单的解决方案。
ntk4

有点挑剔,第二个命令cp * | grep '\.prj$' ../prjshp/ 没有任何意义,但是如果*扩展到文件列表,最后一个是目录(aka cp SOURCE1 SOURCE2....DEST),则在语法上是有效的。当然,管道没有任何意义,但就shell而言,它dup()在语法上仍然有效-它将使文件描述符很好,只是管道的读取器端将不会获取任何数据,因为cp不会写入任何数据。
Sergiy Kolodyazhnyy

find和rsync都对同一个参数列表产生了太长的错误。for循环是最简单的解决方法。
Meezaan-ud-Din

确实,rsync是进行任何大规模复制的方式,尽管我为Linux带来的发展感到困惑,并且我们有一个愚蠢的缺陷/错误,是的,我会认为它是缺陷/错误。
MitchellK

22

该命令一个接一个地复制文件,即使它们太多而*无法扩展为一个cp命令,该文件也将起作用:

for i in *; do cp "$i" ../prjshp/; done

这对我有用。
1rq3fea324wre

1
简单有效。我遇到了类似的问题,删除了从一个项目的视频中提取的约1/4万个jpeg。这是我使用的方法。
年长者怪胎

5

面对Argument list too long错误时,请牢记3个关键点:

  • 命令行参数的长度受到ARG_MAX变量的限制,根据POSIX的定义,变量是“ ... [m] 包含环境数据的exec函数的参数的最大长度”(加重)。即,当shell执行非-built-it命令,它必须调用其中之一exec()来产生该命令的过程,而这正是ARG_MAX发挥作用的地方,此外,命令本身的名称或路径(例如/bin/echo)也起作用。

  • Shell内置命令由Shell执行,这意味着Shell不使用exec()函数族,因此不受ARG_MAX变量影响。

  • 某些命令(例如xargs和)find知道ARG_MAX变量,并在该限制下重复执行操作

从以上几点可以看出,如库萨兰南达对相关问题的出色回答所示,Argument list too long环境大时也会发生这种情况。因此,考虑到每个用户的环境可能会有所不同,并且参数大小(以字节为单位)是相关的,因此很难提出单个文件/参数的数目。

如何处理此类错误?

关键是不关注文件的数量,而是关注您将要使用的命令是否涉及exec()函数族,并且切向涉及堆栈空间。

使用shell内置

如前所述,shell内置程序ARG_MAX不受限制,也就是说,诸如for循环,while循环,内置echo和内置之类的东西printf-所有这些都将表现良好。

for i in /path/to/dir/*; do cp "$i" /path/to/other/dir/; done

相关的问题有关删除文件,有一个解决方案,例如:

printf '%s\0' *.jpg | xargs -0 rm --

请注意,这使用了shell的内置功能printf。如果我们正在调用external printf,则将涉及exec(),因此将失败,并带有大量参数:

$ /usr/bin/printf "%s\0" {1..7000000}> /dev/null
bash: /usr/bin/printf: Argument list too long

bash数组

根据jlliagre 的回答bash它没有对数组施加限制,因此也可以完成文件名数组的构建以及每次循环迭代使用切片,如danjpreron的回答所示:

files=( /path/to/old_dir/*.prj )
for((I=0;I<${#files[*]};I+=1000)); do 
    cp -t /path/to/new_dir/ "${files[@]:I:1000}" 
done

但是,这具有特定于bash和非POSIX的局限性。

增加堆栈空间

有时候,你可以看到人们建议增加堆栈空间使用ulimit -s <NUM>; 在Linux上,每个程序的ARG_MAX值是堆栈空间的1/4,这意味着按比例增加堆栈空间会增加自变量的空间。

# getconf reports value in bytes, ulimit -s in kilobytes
$ getconf ARG_MAX
2097152
$ echo $((  $(getconf ARG_MAX)*4 ))
8388608
$ printf "%dK\n" $(ulimit -s) | numfmt --from=iec --to=none
8388608
# Increasing stack space results in increated ARG_MAX value
$ ulimit -s 16384
$ getconf ARG_MAX
4194304

根据引用Linux Journal 的Franck Dernoncourt的回答,人们还可以重新编译Linux参数更大的Linux内核,以获取最大的内存页参数,但是,这比必要的工作更多,并且有被引用的Linux Journal文章所述的潜力。

避免壳

另一种方法是在Ubuntu上默认使用pythonpython3。下面的python + here-doc示例是我个人用来在40,000项范围内复制大型文件目录的内容:

$ python <<EOF
> import shutil
> import os
> for f in os.listdir('.'):
>    if os.path.isfile(f):
>         shutil.copy(f,'./newdir/')
> EOF

对于递归遍历,可以使用os.walk

也可以看看:


2

恕我直言,用于处理大量文件的最佳工具是findxargs。请参阅man find。请参阅man xargsfind凭借其-print0开关,产生一个NUL文件名-分隔列表(文件名可能包含任何字符execpt NUL/)该xargs理解,使用该-0开关。xargs然后构建允许的最长命令(文件名最多,末尾没有半文件名)并执行它。xargs重复此操作,直到find不再提供文件名。运行xargs --show-limits </dev/null以查看限制。

为了解决您的问题,(并在man cp找到后进行查找--target-directory=):

find . -maxdepth 1 -type f -name '*.prj' -print0 | xargs -0 cp --target-directory=../prjshp/
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.