我刚刚问了一个问题与如何计算特定扩展名的文件有关。现在我想将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
在我看来,处理大量文件的最佳工具是find
和xargs
。请参阅man find
。请参阅man xargs
。find
及其-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/