Linux

如何計算可以作為參數傳遞給批處理命令的文件數量?

  • September 20, 2020

例如,我有通過這種方式創建的多個文件的目錄:

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_numxargs.

因此,**問題是:**如何計算最大允許長度?


我做了一些事情:

  1. 總長度可以通過這種方式找到:
$ getconf ARG_MAX
2097152
  1. 環境變數也會影響參數大小,因此可能應該從 中減去它們ARG_MAX
$ env | wc -c
3403
  1. 通過在找到正確值之前嘗試不同數量的文件來確定相同大小文件的最大數量的方法(使用二進制搜尋)。
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. 編寫了一個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

程序.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 限制

讓 xargs 為您計算。

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

您的問題似乎假設存在實際的“參數數量限制”,而實際上它是兩個限制的組合:

  1. 命令行參數環境變數的字元串長度總和,包括它們的終止 NUL 字節。
  2. 單個命令行參數的最大字元串長度。

例如,您可以使用 200000 個單字母參數、100000 個雙字母參數呼叫命令,但不能使用超過 128k 字節的單個參數。

假設xargs來自 GNU coreutils,xargs --show-limits </dev/null將顯示這些限制在您的系統上。

在任何系統上,在建構命令行時都不會xargs使用系統的最大限制,但會選擇一些合理的東西(以這種方式對系統施加壓力是沒有意義的)。

引用自:https://unix.stackexchange.com/questions/609574