예를 들어 내 디렉터리에는 다음을 사용하여 생성된 여러 파일이 포함되어 있습니다.
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
.
그러므로,질문:허용되는 최대 길이는 어떻게 계산됩니까?
나는 몇 가지 일을 했습니다:
총 길이는 다음을 통해 확인할 수 있습니다.
$ getconf ARG_MAX 2097152
환경 변수도 매개변수 크기에 영향을 미치므로 다음에서 빼야 합니다
ARG_MAX
.$ env | wc -c 3403
올바른 값을 찾기 전에 다양한 수의 파일을 시도하여 동일한 크기의 최대 파일 수를 결정하는 방법(이진 검색 사용)이 개발되었습니다.
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
그러나 나는 이러한 결과의 논리/관계를 이해할 수 없었습니다.
이 값이 시도되었습니다.답변계산에는 적합하지 않습니다.
썼다씨프로그램은 전달된 매개변수의 전체 크기를 계산합니다. 이 프로그램의 결과는 비슷하지만 계산되지 않은 바이트가 일부 남아 있습니다.
$ ./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
귀하의 질문은 실제로 두 가지 제한의 조합인 실제 "매개변수 수에 대한 제한"이 있다고 가정하는 것 같습니다.
명령줄 인수의 문자열 길이 합계그리고종료 NUL 바이트를 포함한 환경 변수.
단일 명령줄 인수의 최대 문자열 길이입니다.
예를 들어, 한 글자 매개변수 200000개, 두 글자 매개변수 100000개를 사용하여 명령을 호출할 수 있지만 128k바이트를 초과하는 단일 매개변수는 사용할 수 없습니다.
xargs
GNU 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 arg
SUDO_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
사용할 수 있는 최대치라고 생각했지만 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
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 포함)를 계산합니다. ) env
NL))로 대체하고 이 값을 빼고 ARG_MAX
9로 나누어 빈 인수 목록의 최악의 경우를 처리하고 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