たとえば、私のディレクトリには、次のように生成された複数のファイルが含まれています。
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
あなたの質問は、実際には2つの制限の組み合わせである実際の「パラメータ数の制限」があると仮定しているようです。
コマンドライン引数の文字列長の合計そして終了 NUL バイトを含む環境変数。
単一のコマンドライン引数の最大文字列長。
たとえば、1文字パラメータ200000個、2文字パラメータ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
}
E2BIGexecve()
制限の適用はシステム(およびそのバージョン)によって異なり、スタックサイズ制限などの要因によって異なります。通常、argv[]
各文字列のサイズ(NUL終了文字を含む)と通常、これらのポインタ配列(および終了NULLポインタ)のサイズを考慮しますenvp[]
(したがって、引数のサイズと数によって異なります)。シェルは最後の瞬間にいくつかの環境変数を設定することもできます(たとえば、_
一部のシェルは変数を実行中のコマンドのパスに設定します)。
また、実行可能ファイルの種類(ELF、スクリプト、binfmt_misc)によって異なります。たとえば、スクリプトを使用すると、通常は長いパラメータリスト(goes)を使用して2番目の操作を実行できますexecve()
。execve()
["myscrip", "arg", NULL]
["/path/to/interpreter" or "myscript" depending on system, "-<option>" if any on the shebang, "myscript", "arg"]
また、一部のコマンドは、同じパラメーター・リストといくつかの追加の環境変数を使用して別のコマンドを実行します。たとえば、その環境内で実行しますsudo cmd arg
(引数リストを保持するために必要なスペースの2倍)。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