例如,我有通过这种方式创建的多个文件的目录:
touch files/{1..10231}_file.txt
Run Code Online (Sandbox Code Playgroud)
我想将它们移动到新目录中new_files_dir。
最简单的方法是:
for filename in files/*; do
mv "${filename}" -t "new_files_dir"
done
Run Code Online (Sandbox Code Playgroud)
这个脚本在我的电脑上运行了10秒。它很慢。由于mv对每个文件执行命令而导致速度变慢。
###编辑开始###
我明白,在我的例子中,最简单的方法就是
mv files/* -t new_files_dir
Run Code Online (Sandbox Code Playgroud)
或者,如果“参数列表太长”:
printf '%s\0' files/* | xargs -0 mv -t new_files_dir
Run Code Online (Sandbox Code Playgroud)
但上述案例是任务的一部分。整个任务都在这个问题中:在 linux 中根据文件名将大量文件移动到目录中。因此,必须将文件移动到相应的子目录中,其对应关系基于文件名中的数字。这是for我的代码片段中循环使用和其他奇怪之处的原因。
###编辑结束###
有可能通过将一堆文件mv而不是单个文件传递给命令来加速此过程,如下所示:
batch_num=1000
# Counting of files in the directory
shopt -s nullglob
file_list=(files/*)
file_num=${#file_list[@]}
# Every file's common part
suffix='_file.txt'
for((from = 1, to = batch_num; from <= file_num; from += batch_num, to += batch_num)); do
if ((to > file_num)); then
to="$file_num"
fi
# Generating filenames by `seq` command and passing them to `xargs`
seq -f "files/%.f${suffix}" "$from" "$to" |
xargs -n "${batch_num}" mv -t "new_files_dir"
done
Run Code Online (Sandbox Code Playgroud)
在这种情况下,脚本工作0.2秒。因此,性能提高了 50 倍。
但是有一个问题:在任何时候程序都可能因“参数列表太长”而拒绝工作,因为我不能保证文件名长度小于最大允许长度。
我的想法是计算batch_num:
batch_num = "max allowable length" / "longest filename length"
Run Code Online (Sandbox Code Playgroud)
然后batch_num在xargs.
因此,问题是:如何计算最大允许长度?
我做了一些事情:
可以通过这种方式找到总长度:
$ getconf ARG_MAX
2097152
Run Code Online (Sandbox Code Playgroud)
环境变量也会影响参数大小,因此可能应该从ARG_MAX以下中减去它们:
$ env | wc -c
3403
Run Code Online (Sandbox Code Playgroud)
通过在找到正确值之前尝试不同数量的文件(使用二进制搜索),制定了一种确定相同大小文件的最大数量的方法。
function find_max_file_number {
right=2000000
left=1
name=$1
while ((left < right)); do
mid=$(((left + right) / 2))
if /bin/true $(yes "$name" | head -n "$mid") 2>/dev/null; then
left=$((mid + 1))
else
right=$((mid - 1))
fi
done
echo "Number of ${#name} byte(s) filenames:" $((mid - 1))
}
find_max_file_number A
find_max_file_number AA
find_max_file_number AAA
Run Code Online (Sandbox Code Playgroud)
输出:
Number of 1 byte(s) filenames: 209232
Number of 2 byte(s) filenames: 190006
Number of 3 byte(s) filenames: 174248
Run Code Online (Sandbox Code Playgroud)
但我还不能理解这些结果背后的逻辑/关系。
已经尝试过这个答案中的值进行计算,但它们不适合。
编写了一个C程序来计算传递参数的总大小。这个程序的结果很接近,但还剩下一些未计算的字节:
$ ./program {1..91442}_file.txt
arg strings size: 1360534
number of pointers to strings 91443
argv size: 1360534 + 91443 * 8 = 2092078
envp size: 3935
Overall (argv_size + env_size + sizeof(argc)): 2092078 + 3935 + 4 = 2096017
ARG_MAX: 2097152
ARG_MAX - overall = 1135 # <--- Enough bytes are
# left, but no additional
# filenames are permitted.
$ ./program {1..91443}_file.txt
bash: ./program: Argument list too long
Run Code Online (Sandbox Code Playgroud)
程序.c
#include <stdio.h>
#include <string.h>
#include <unistd.h>
int main(int argc, char *argv[], char *envp[]) {
size_t chr_ptr_size = sizeof(argv[0]);
// The arguments array total size calculation
size_t arg_strings_size = 0;
size_t str_len = 0;
for(int i = 0; i < argc; i++) {
str_len = strlen(argv[i]) + 1;
arg_strings_size += str_len;
// printf("%zu:\t%s\n\n", str_len, argv[i]);
}
size_t argv_size = arg_strings_size + argc * chr_ptr_size;
printf( "arg strings size: %zu\n"
"number of pointers to strings %i\n\n"
"argv size:\t%zu + %i * %zu = %zu\n",
arg_strings_size,
argc,
arg_strings_size,
argc,
chr_ptr_size,
argv_size
);
// The enviroment variables array total size calculation
size_t env_size = 0;
for (char **env = envp; *env != 0; env++) {
char *thisEnv = *env;
env_size += strlen(thisEnv) + 1 + sizeof(thisEnv);
}
printf("envp size:\t%zu\n", env_size);
size_t overall = argv_size + env_size + sizeof(argc);
printf( "\nOverall (argv_size + env_size + sizeof(argc)):\t"
"%zu + %zu + %zu = %zu\n",
argv_size,
env_size,
sizeof(argc),
overall);
// Find ARG_MAX by system call
long arg_max = sysconf(_SC_ARG_MAX);
printf("ARG_MAX: %li\n\n", arg_max);
printf("ARG_MAX - overall = %li\n", arg_max - (long) overall);
return 0;
}
Run Code Online (Sandbox Code Playgroud)
我在 StackOverflow 上问了一个关于这个程序正确性的问题:argv, envp, argc (命令行参数) 的最大汇总大小总是远离 ARG_MAX limit。
Gil*_*il' 28
让 xargs 为您做计算。
printf '%s\0' files/* | xargs -0 mv -t new_files_dir
Run Code Online (Sandbox Code Playgroud)
您的问题似乎假设存在实际的“参数数量限制”,而实际上它是两个限制的组合:
命令行参数和环境变量的字符串长度总和,包括它们的终止 NUL 字节。
单个命令行参数的最大字符串长度。
例如,您可能能够调用具有 200000 个单字母参数和 100000 个双字母参数的命令,但不能使用超过 128k 字节的单个参数。
假设xargs来自 GNU coreutils,xargs --show-limits </dev/null将显示您的系统上的这些限制。
在任何系统上,在构建命令行时xargs都不会使用系统的最大限制,但会选择一些合理的东西(以这种方式对系统施加压力是没有意义的)。
只需使用内置或可以内置的 shell mv,就不会出现问题(这是execve()系统调用的限制,因此只能使用外部命令)。您拨打多少次也并不重要mv。
zsh、busybox sh、ksh93(取决于它的构建方式)是其中一些 shell。和zsh:
#! /bin/zsh -
zmodload zsh/files # makes mv and a few other file manipulation commands builtin
batch=1000
files=(files/*(N))
for ((start = 1; start <= $#files; start += batch)) {
(( end = start + batch - 1))
mkdir -p ${start}_${end} || exit
mv -- $files[start,end] ${start}_${end}/ || exit
}
Run Code Online (Sandbox Code Playgroud)
E2BIGexecve()限制的应用因系统(及其版本)而异,可能取决于堆栈大小限制等因素。它通常会考虑每个字符串的大小argv[](envp[]包括终止 NUL 字符),通常还会考虑这些指针数组(以及终止 NULL 指针)的大小(因此它取决于参数的大小和数量)。请注意,shell 也可以在最后一分钟设置一些环境变量(例如_某些 shell 设置为正在执行的命令的路径的变量)。
它还可能取决于可执行文件的类型(ELF、脚本、binfmt_misc)。例如,对于脚本,execve()最终会使用execve()通常更长的参数列表(["myscrip", "arg", NULL]变为["/path/to/interpreter" or "myscript" depending on system, "-<option>" if any on the shebang, "myscript", "arg"])执行第二次操作。
另请注意,某些命令最终会执行具有相同参数列表和可能一些额外环境变量的其他命令。例如,在其环境中sudo cmd arg运行(将保存参数列表所需的空间加倍)。cmd argSUDO_COMMAND=/path/to/cmd arg
您也许能够为当前的 Linux 内核版本、当前的 shell 版本和您想要执行的特定命令提出正确的算法,以最大化您可以传递给 的参数数量execve(),但这可能不再适用对内核/shell/命令的下一版本有效。更好的方法是采取xargs方法并给予足够的余裕来考虑所有这些额外的变化或使用xargs。
GNUxargs有一个--show-limits选项详细说明了它如何处理它:
$ getconf ARG_MAX
2097152
$ uname -rs
Linux 5.7.0-3-amd64
$ xargs --show-limits < /dev/null
Your environment variables take up 3456 bytes
POSIX upper limit on argument length (this system): 2091648
POSIX smallest allowable upper limit on argument length (all systems): 4096
Maximum length of command we could actually use: 2088192
Size of command buffer we are actually using: 131072
Maximum parallelism (--max-procs must be no greater): 2147483647
Run Code Online (Sandbox Code Playgroud)
您可以看到ARG_MAX在我的例子中是 2MiB,xargs认为它最多可以使用2088192,但选择将其限制为 128KiB。
正如:
$ yes '""' | xargs -s 230000 | head -1 | wc -c
229995
$ yes '""' | strace -fe execve xargs -s 240000 | head -1 | wc -c
[...]
[pid 25598] execve("/bin/echo", ["echo", "", "", "", ...], 0x7ffe2e742bf8 /* 47 vars */) = -1 E2BIG (Argument list too long)
[pid 25599] execve("/bin/echo", ["echo", "", "", "", ...], 0x7ffe2e742bf8 /* 47 vars */) = 0
[...]
119997
Run Code Online (Sandbox Code Playgroud)
它无法传递 239,995 个空参数(NUL 分隔符的字符串总大小为 239,995 字节,因此适合 240,000 个缓冲区),因此用一半的参数再次尝试。这是一个很小的数据量,但您必须考虑到这些字符串的指针列表是 8 倍大,如果我们将这些加起来,我们将超过 2MiB。
当我 6 年前在Linux 3.11 的问答中进行相同类型的测试时,我得到了一种不同的行为,这种行为最近已经发生了变化,表明提出正确的算法来最大化数量的练习通过参数有点毫无意义。
在这里,平均文件路径大小为 32 字节,缓冲区为 128KiB,仍然传递了 4096 个文件名,mv并且与重命名/移动所有这些文件的成本相比,启动成本mv已经变得可以忽略不计。
对于不太保守的缓冲区大小(传递给xargs -s),但至少对于过去版本的 Linux 的任何 arg 列表仍然有效,您可以这样做:
$ (env | wc; getconf ARG_MAX) | awk '
{env = $1 * 8 + $3; getline; printf "%d\n", ($0 - env) / 9 - 4096}'
228499
Run Code Online (Sandbox Code Playgroud)
我们计算环境使用的空间的高估计(env输出中的行数应该至少与envp[]我们传递给的指针数一样大env,并且我们为每个字节计算 8 个字节,加上它们的大小(包括 NUL)替换env为 NL)),减去该值ARG_MAX并除以 9 以涵盖空参数列表的最坏情况,并添加 4KiB 的松弛。
请注意,如果将堆栈大小限制为 4MiB 或更低(例如limit stacksize 4Min ),则这会比 GNU的默认缓冲区大小(在我的情况下仍为 128K,并且无法正确传递空变量列表)更加保守。zshxargs
$ limit stacksize 4M
$ (env | wc; getconf ARG_MAX) | awk '
{env = $1 * 8 + $3; getline; printf "%d\n", ($0 - env) / 9 - 4096}'
111991
$ xargs --show-limits < /dev/null |& grep actually
Maximum length of command we could actually use: 1039698
Size of command buffer we are actually using: 131072
$ yes '""' | xargs | head -1 | wc -c
65193
$ yes '""' | xargs -s 111991 | head -1 | wc -c
111986
Run Code Online (Sandbox Code Playgroud)