复制文件时参数列表太长

复制文件时参数列表太长

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

我在尝试,

cp *.prj ../prjshp/

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

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

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

我如何复制它们?

答案1

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找不到子目录中的文件)。

答案2

此命令将逐个复制文件,即使文件太多而无法*扩展到单个cp命令中,它也能正常工作:

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

答案3

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

  • 命令行参数的长度受ARG_MAX变量限制,该变量由POSIX 定义是“...[m]参数的最大长度exec 函数包括环境数据”(强调添加)”。也就是说,当 shell 执行非内置命令时,它必须调用 之一exec()来生成该命令的进程,这就是ARG_MAX发挥作用的地方。此外,命令本身的名称或路径(例如/bin/echo)也起着作用。

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

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

从以上几点可以看出Kusalananda 的精彩回答在相关问题上,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。如果我们调用外部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

根据Franck Dernoncourt 的回答,其中引用了 Linux Journal,人们还可以使用更大的参数最大内存页面值来重新编译 Linux 内核,但是,这比必要的工作要多,并且如所引用的 Linux Journal 文章中所述,存在被攻击的可能性。

避免使用 shell

另一种方法是使用Ubuntu 默认自带的python或。python +python3此处文档下面的示例是我个人用来复制大约 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

也可以看看:

答案4

在我看来,处理大量文件的最佳工具是findxargs。请参阅man find。请参阅man xargsfind及其-print0开关生成一个NUL以 - 分隔的文件名列表(文件名可以包含除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/

相关内容