일괄 처리를 위해 일부 명령에 인수로 전달될 수 있는 파일 수를 계산하는 방법은 무엇입니까?

일괄 처리를 위해 일부 명령에 인수로 전달될 수 있는 파일 수를 계산하는 방법은 무엇입니까?

예를 들어 내 디렉터리에는 다음을 사용하여 생성된 여러 파일이 포함되어 있습니다.

touch files/{1..10231}_file.txt

나는 그것들을 새로운 디렉토리로 옮기고 싶습니다 new_files_dir.

가장 간단한 방법은 다음과 같습니다.

for filename in files/*; do
    mv "${filename}" -t "new_files_dir"
done

이 스크립트는 다음에서 작동합니다.10내 컴퓨터에서 몇 초. 매우 느립니다. 각 파일에 대한 명령 실행 으로 인해 mv속도가 느려집니다 .

###수정 시작###

내 경우에는 가장 간단한 방법이

mv files/* -t new_files_dir

또는 "매개변수 목록이 너무 깁니다"인 경우:

printf '%s\0' files/* | xargs -0 mv -t new_files_dir

그러나 위의 경우는 임무의 일부입니다. 전체 작업은 다음 질문에 있습니다.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

이 경우 스크립트는 다음에서 작동합니다.0.2두번째. 따라서 성능이 50배 향상됩니다.

하지만 문제가 있습니다.이 파일 이름 세트가 최대 허용 길이보다 작다는 보장이 없기 때문에 언제든지 "매개변수 목록이 너무 김"으로 인해 프로그램이 작동을 거부할 수 있습니다.

내 생각계산은 다음과 같습니다 batch_num.

batch_num = "max allowable length" / "longest filename length"

그런 다음 batch_num에서 사용하십시오 xargs.

그러므로,질문:허용되는 최대 길이는 어떻게 계산됩니까?


나는 몇 가지 일을 했습니다:

  1. 총 길이는 다음을 통해 확인할 수 있습니다.

     $ getconf ARG_MAX
     2097152
    
  2. 환경 변수도 매개변수 크기에 영향을 미치므로 다음에서 빼야 합니다 ARG_MAX.

     $ env | wc -c
     3403
    
  3. 올바른 값을 찾기 전에 다양한 수의 파일을 시도하여 동일한 크기의 최대 파일 수를 결정하는 방법(이진 검색 사용)이 개발되었습니다.

     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
    

    산출:

     Number of 1 byte(s) filenames: 209232
     Number of 2 byte(s) filenames: 190006
     Number of 3 byte(s) filenames: 174248
    

    그러나 나는 이러한 결과의 논리/관계를 이해할 수 없었습니다.

  4. 이 값이 시도되었습니다.답변계산에는 적합하지 않습니다.

  5. 썼다프로그램은 전달된 매개변수의 전체 크기를 계산합니다. 이 프로그램의 결과는 비슷하지만 계산되지 않은 바이트가 일부 남아 있습니다.

     $ ./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
    

    프로그램.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;
     }
    

    나는 이 프로그램의 정확성에 대해 StackOverflow에 질문을 했습니다:argv, envp, argc(명령줄 인수)의 최대 요약 크기는 항상 ARG_MAX 제한에서 멀리 떨어져 있습니다..

답변1

xargs가 계산을 하도록 하세요.

printf '%s\0' files/* | xargs -0 mv -t new_files_dir

답변2

귀하의 질문은 실제로 두 가지 제한의 조합인 실제 "매개변수 수에 대한 제한"이 있다고 가정하는 것 같습니다.

  1. 명령줄 인수의 문자열 길이 합계그리고종료 NUL 바이트를 포함한 환경 변수.

  2. 단일 명령줄 인수의 최대 문자열 길이입니다.

예를 들어, 한 글자 매개변수 200000개, 두 글자 매개변수 100000개를 사용하여 명령을 호출할 수 있지만 128k바이트를 초과하는 단일 매개변수는 사용할 수 없습니다.

xargsGNU coreutils에서 가져온 것이라고 가정하면 xargs --show-limits </dev/null시스템에 이러한 제한 사항이 표시됩니다.

xargs어떤 시스템에서든아니요명령줄을 작성할 때 시스템의 최대 제한을 사용하되 합리적인 것을 선택하십시오(이런 방식으로 시스템에 스트레스를 줄 필요는 없습니다).

답변3

정말 중요한 경우 batch-move파일 목록을 표준 입력으로 사용하고 관련 Unix 시스템 호출을 사용하여 파일을 이동하는 프로그램을 C로 직접 작성할 수 있습니다.

그렇지 않다면 "한계를 찾아 목표를 향해 노력하라"는 것이다.정확히xargs(1)(여기서는 Linux의 GNU 버전) 나는 당신이 더 빨리 얻을 수 있을지 의심됩니다.

답변4

mv그냥 내장되거나 내장될 수 있는 쉘을 사용하면 문제 없을 것이다. (이것은 execve()시스템 호출의 한계이므로 외부 명령만 사용할 수 있다.) 몇 번이나 전화하는지는 중요하지 않습니다 mv.

zsh, busybox sh, ksh93(제작 방법에 따라)는 이러한 쉘 중 일부입니다. 그리고 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
}

E2BIG execve()제한의 적용은 시스템(및 해당 버전)마다 다르며 스택 크기 제한과 같은 요인에 따라 달라질 수 있습니다. 일반적으로 argv[]각 문자열의 크기 (NUL 종료 문자 포함)와 일반적으로 이러한 포인터 배열(및 종료 NULL 포인터)의 크기를 고려합니다 envp[](따라서 인수의 크기와 수에 따라 다름). 쉘은 마지막 순간에 일부 환경 변수를 설정할 수도 있습니다(예를 들어, _일부 쉘은 변수를 실행 중인 명령의 경로로 설정함).

또한 실행 파일 유형(ELF, 스크립트, binfmt_misc)에 따라 달라질 수도 있습니다. 예를 들어, 스크립트를 사용하면 일반적으로 더 긴 매개변수 목록( goes )을 사용하여 두 번째 작업을 수행하게 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 커널 버전, 현재 셸 버전 및 전달할 수 있는 인수 수를 최대화하기 위해 실행하려는 특정 명령에 적합한 알고리즘을 생각해낼 수 있지만 execve()이는 더 이상 사실이 아닐 수도 있습니다. 커널/쉘/명령은 다음 버전에 유효합니다. 더 나은 접근 방식은 접근 방식을 취하고 xargs이러한 모든 추가 변경이나 사용을 설명할 수 있는 충분한 여유를 허용하는 것입니다 xargs.

GNU에는 이를 처리하는 방법을 자세히 설명하는 옵션이 xargs있습니다 .--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

ARG_MAX제 경우에는 2MiB인 것을 볼 수 있습니다 . xargs사용할 수 있는 최대치라고 생각했지만 2088192128KiB로 제한하기로 결정했습니다.

다음과 같이:

$ 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

239,995개의 빈 인수(NUL로 구분된 전체 문자열 크기는 239,995바이트이므로 240,000개의 버퍼에 맞습니다)를 전달하는 데 실패했기 때문에 인수의 절반을 사용하여 다시 시도했습니다. 이는 적은 양의 데이터이지만 이러한 문자열에 대한 포인터 목록이 8배 더 크다는 점을 고려해야 하며 이를 합산하면 2MiB가 넘을 것입니다.

6년 전에 같은 시험을 쳤을 때Q&A는 여기Linux 3.11에서는 최근에 변경된 다른 동작이 나타납니다. 이는 전달될 인수 수를 최대화하기 위해 올바른 알고리즘을 찾는 연습이 약간 의미가 없음을 시사합니다.

여기서 평균 파일 경로 크기는 32바이트이고 버퍼는 128KiB이며 여전히 4096개의 파일 이름이 전달되고 모든 파일의 이름을 바꾸거나 이동하는 비용에 비해 mv시작 비용은 무시할 수 있습니다 .mv

덜 보수적인 버퍼 크기( 에 전달됨 xargs -s)이지만 최소한 이전 버전의 Linux에서는 모든 인수 목록에 여전히 유효하려면 다음을 수행할 수 있습니다.

$ (env | wc; getconf ARG_MAX) | awk '
  {env = $1 * 8 + $3; getline; printf "%d\n", ($0 - env) / 9 - 4096}'
228499

환경에서 사용하는 공간의 높은 추정치를 계산합니다(출력의 줄 수는 env최소한 envp[]우리가 전달한 포인터 수만큼 커야 하며 env각각에 대해 8바이트와 해당 크기(NUL 포함)를 계산합니다. ) envNL))로 대체하고 이 값을 빼고 ARG_MAX9로 나누어 빈 인수 목록의 최악의 경우를 처리하고 4KiB의 여유 시간을 추가합니다.

스택 크기를 4MiB 이하(예: )로 제한하면 limit stacksize 4M이는 zsh다음과 같습니다.GNU의 기본 버퍼 크기보다 더 보수적입니다 xargs(제 경우에는 여전히 128K이고 빈 변수 목록을 올바르게 전달하지 못합니다).

$ 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

관련 정보