复制文件时参数列表太长
我刚问了一个与如何计算特定扩展名的文件有关的问题 。 现在我想将这些文件转换为新的dir
。
我在尝试,
cp *.prj ../prjshp/
和
cp * | grep '\.prj$' ../prjshp/
但他们给出了同样的错误,
bash:/ bin / cp:参数列表太长了
我该如何复制它们?
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
。 由于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
恕我直言,处理大量文件的最佳工具是find
和xargs
。 见man find
。 见man xargs
。 find
,使用-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
变量的影响。 -
某些命令(例如
xargs
和find
知道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的python
或python3
。 下面的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 。
也可以看看:
- 什么定义了命令单参数的最大大小?