1

我喜欢制作自己的脚本来在 bash 中绘制一些数据。无论如何,现在我有数千个文件要“重命名”、“编辑”等等。

当我尝试管理这些文件时,外壳会说类似

$Arguments list too long

我想知道 Perl 是否可以管理这么多文件?

4

3 回答 3

13

在 Unix 上允许的参数和环境的长度有一个上限。对于许多现代版本的 Unix,这个限制大约是 256 KiB——对于其他版本,它可能会更少。

这不是 shell 本身的限制,也不是 Perl 或任何其他程序的限制,而是 Unix 内核施加的限制。

如果一次处理一个文件,Perl 总共可以处理数百万个文件。困难在于将文件列表传递给 Perl。您可以将名称写入文件并告诉 Perl 读取哪个文件。您可以使用xargs. 您必须担心什么标记了文件名的结尾。安全的答案是空字节;它是唯一不能出现在 Unix 路径名中的字符。getdelim()您会发现使用 POSIX 函数来读取这些行是最容易的。使用换行符是常规的,但不是 100%;文件名可以包含换行符,导致混淆。

您还可以让 Perl 自己生成文件列表,方法是读取目录(零碎,但不会一次将数百万个名称全部拖入内存)或使用glob.

另请参阅SO 18559403:允许的参数列表有多大

此代码可以帮助您确定参数列表的限制;这是我对交叉引用问题的回答的改进。它在 Mac OS X 10.8.5 上告诉我 256 KiB。在一个古老的 Linux 2.6 内核上,我得到了 128 KiB 的限制。

/* SO 18559403: How big an argument list is allowed */

#include <assert.h>
#include <errno.h>
#include <fcntl.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <sys/wait.h>
#include <unistd.h>

extern char **environ;  /* Sometimes in <unistd.h> */

enum { BYTES_PER_KIBIBYTE = 1024 };
enum { BYTES_PER_MEBIBYTE = BYTES_PER_KIBIBYTE * BYTES_PER_KIBIBYTE };
enum { E_GOT_E2BIG =  37 };
enum { E_NOT_E2BIG = 219 };

enum { R_TOO_LARGE = +1, R_TOO_SMALL = -1 };

static char *print_kib(int size, char *buffer, size_t buflen)
{
    snprintf(buffer, buflen, "%d (%d KiB)", size, size / BYTES_PER_KIBIBYTE);
    return buffer;
}

static int test_arg_size(int size)
{
    char buffer[32];
    int result = R_TOO_SMALL;
    assert(size % 8 == 0);
    fflush(0);
    pid_t pid = fork();
    if (pid < 0)
    {
        fprintf(stderr, "Failed to fork at size %s\n",
                print_kib(size, buffer, sizeof(buffer)));
        exit(1);
    }
    else if (pid == 0)
    {
        int self = getpid();
        printf("Child: %d\n", self);
        char *args[10] = { "ls" };
        size_t bytes_per_arg = size / 8;
        for (int j = 1; j < 9; j++)
        {
            args[j] = malloc(bytes_per_arg);
            if (args[j] == 0)
            {
                fprintf(stderr, "Failed to allocate argument space at size %s\n",
                        print_kib(size, buffer, sizeof(buffer)));
                exit(E_NOT_E2BIG);
            }
            memset(args[j], j + '0', bytes_per_arg - 1);
            args[j][bytes_per_arg - 1] = '\0';
        }

        /* Close standard I/O channels so executed command doesn't spew forth */
        int dev_null = open("/dev/null", O_RDWR);
        if (dev_null < 0)
        {
            fprintf(stderr, "Failed to open /dev/null for reading and writing\n");
            exit(E_NOT_E2BIG);
        }
        int dev_stderr = dup(2);
        if (dev_stderr < 0)
        {
            fprintf(stderr, "Failed to dup() standard error\n");
            exit(E_NOT_E2BIG);
        }
        close(0);
        dup(dev_null);
        close(1);
        dup(dev_null);
        close(2);
        dup(dev_null);
        close(dev_null);

        /* Execute ls on big file names -- error is ENAMETOOLONG */
        execvp(args[0], args);

        /* Reinstate standard error so we can report failure */
        dup2(dev_stderr, 2);
        int errnum = errno;
        if (errnum == E2BIG)
        {
            fprintf(stderr, "%d: got E2BIG (%d: %s) at size %s\n",
                    self, errnum, strerror(errnum),
                    print_kib(size, buffer, sizeof(buffer)));
            exit(E_GOT_E2BIG);
        }
        fprintf(stderr, "%d: got errno %d (%s) at size %s\n",
                self, errnum, strerror(errnum),
                print_kib(size, buffer, sizeof(buffer)));
        exit(E_NOT_E2BIG);
    }
    else
    {
        int self = getpid();
        int corpse;
        int status;
        while ((corpse = waitpid(pid, &status, 0)) != -1)
        {
            if (!WIFEXITED(status))
                printf("%d: child %d died with exit status 0x%.4X", self, corpse, status);
            else
            {
                int statval = WEXITSTATUS(status);
                printf("%d: child %d died with exit status %d: ", self, corpse, statval);
                switch (statval)
                {
                case E_GOT_E2BIG:
                    printf("success: got E2BIG");
                    result = R_TOO_LARGE;
                    break;
                case E_NOT_E2BIG:
                    printf("failed: indeterminate error in child");
                    break;
                case 1:
                    printf("command exited with status 1 - it worked");
                    break;
                default:
                    printf("unknown: unexpected exit status %d", statval);
                    break;
                }
            }
            printf(" at size %s\n", print_kib(size, buffer, sizeof(buffer)));
            fflush(stdout);
        }
    }
    return result;
}

static int env_size(void)
{
    int size = 0;
    for (char **ep = environ; *ep != 0; ep++)
        size += strlen(*ep) + 1;
    return size;
}

int main(void)
{
    int env = env_size();
    int lo = 0;
    int hi = BYTES_PER_MEBIBYTE;

    /* Binary search -- the kilobyte slop means termination does not have to be accurate */
    while (lo + 1 * BYTES_PER_KIBIBYTE < hi)
    {
        int mid = (lo + hi) / 2;
        if (test_arg_size(mid) == R_TOO_LARGE)
            hi = mid;
        else
            lo = mid;
    }

    char buffer1[32];
    char buffer2[32];
    printf("Environment size = %d\n", env);
    printf("Best guess: maximum argument size in range %s to %s\n",
           print_kib(lo + env, buffer1, sizeof(buffer1)),
           print_kib(hi + env, buffer2, sizeof(buffer2)));

    return 0;
}
于 2013-09-27T04:35:19.363 回答
4

如果您将拥有大量文件名并因此达到 shell 限制,请避免将文件名放在命令行上。

此外,我会避免使用 xargs,因为这会因为多次启动 Perl 而减慢速度。

最好的解决方案是使用查找库,即http://perldoc.perl.org/File/Find.html

于 2013-09-27T04:48:45.533 回答
1

您可以做的最简单的事情之一是将文件存储在文件中并在脚本中处理该文件。

喜欢:

$ find (your find command) > /tmp/files
$ your_prg.pl /tmp/files

#!perl
my $filelist = shift @ARGV;
open(my $fh,'<',$filelist) or die $!;
while (my $filename = <$fh>){
   chomp $filename;

   ### do whatever want to do with the file

}
于 2013-09-27T11:07:04.327 回答