复制文件时参数列表太长

我刚问了一个与如何计算特定扩展名的文件有关的问题 。 现在我想将这些文件转换为新的dir

我在尝试,

 cp *.prj ../prjshp/ 

 cp * | grep '\.prj$' ../prjshp/ 

但他们给出了同样的错误,

bash:/ bin / cp:参数列表太长了

我该如何复制它们?

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

一种方法是在块中的文件上运行cpfind命令知道如何执行此操作:

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

另一种方法是使用rsync

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

此命令逐个复制文件,即使有太多的文件要扩展为单个cp命令,它们也能正常工作:

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

恕我直言,处理大量文件的最佳工具是findxargs 。 见man find 。 见man xargsfind ,使用-print0开关,使用-0开关生成一个NUL分隔的文件名列表(文件名可以包含xargs理解的任何字符execpt NUL/ )。 然后xargs构建允许的最长命令(文件名最多,最后没有半文件名)并执行它。 xargs重复这个,直到find更多的文件名。 运行xargs --show-limits 以查看限制。

要解决你的问题,(并在检查man cp后找到--target-directory= ):

 find . -maxdepth 1 -type f -name '*.prj' -print0 | xargs -0 cp --target-directory=../prjshp/ 

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

  • 命令行参数的长度受ARG_MAX变量的限制,其中POSIX定义是“… [m]最大的exec函数参数长度,包括环境数据”(强调添加)“。也就是说,当shell执行a时non-built-it命令,它必须调用exec()来生成该命令的进程,这就是ARG_MAX发挥作用的地方。此外,命令本身的名称或路径(例如/bin/echo )播放一名角色。

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

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

从上面的观点和Kusalananda对相关问题的优秀答案中可以看出 ,当环境很大时, Argument list too long也会出现Argument list too long 。 因此,考虑到每个用户的环境可能会有所不同,并且参数大小以字节为单位,因此很难提出单个数量的文件/参数。

如何处理这样的错误?

关键是不要关注文件的数量,而要关注你要使用的命令是否涉及exec()函数系列和切向 – 堆栈空间。

使用shell内置函数

如前所述,shell内置ARG_MAX不受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 。 如果我们调用外部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 增加堆栈空间 ; 在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 Journal文章中所述的那样开启了漏洞利用的潜力。

避免壳

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

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

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

也可以看看:

  • 什么定义了命令单参数的最大大小?