2016-04-08 4 views
1

У меня есть каталог с 2,5 миллионами файлов и более 70 ГБ.Разделить большой каталог в подкаталоги

Я хочу разбить это на подкаталоги, каждый из которых содержит 1000 файлов.

Вот команда, я попытался с помощью:

i=0; for f in *; do d=dir_$(printf %03d $((i/1000+1))); mkdir -p $d; mv "$f" $d; let i++; done 

Эта команда работает для меня в небольшом масштабе, но я могу оставить ее работать в течение нескольких часов на этой директории, и это, кажется, не делать ничего ,

Я открыт для этого в любом случае с помощью командной строки: Perl, Python и т.д. Просто любой способ будет самым быстрым, чтобы это было сделано ...

+1

Ваш '*' собирается потратить некоторое время на расширение. Возможно, начните с более целевого поднабора имени файла, такого как 'a *', и посмотрите, вернется ли он в более разумное время. Вы также можете использовать 'find' вместо for-loop. Также меня беспокоит создание подкаталогов в этом уже слишком большом каталоге. Рассматривали ли вы их создание в другом месте? – LinuxDisciple

+0

Я бы порекомендовал обрабатывать результаты 'find'. –

+0

Только очень хорошо написанные оболочки могут обрабатывать строки, полученные в результате команд или подстановочных расширений, которые находятся в диапазоне размеров нескольких МБ. Чтение каталога в программе происходит быстро и легко (см. @ikegami). - Если вы хотите использовать скрипт оболочки: разделите проблему, перебирая 'ls' в' while, пока читаете ... done', чтобы получить файлы по одному. – laune

ответ

7

Я подозреваю, что если вы проверили, вы «Я заметил, что ваша программа действительно перемещает файлы, хотя и очень медленно. Запуск программы довольно дорого (по крайней мере, по сравнению с системным вызовом), и вы делаете это три или четыре раза за файл! Таким образом, следующий должен быть намного быстрее:

perl -e' 
    my $base_dir_qfn = "."; 
    my $i = 0; 
    my $dir; 
    opendir(my $dh, $base_dir_qfn) 
     or die("Can'\''t open dir \"$base_dir_qfn\": $!\n"); 

    while (defined(my $fn = readdir($dh))) { 
     next if $fn =~ /^(?:\.\.?|dir_\d+)\z/; 

     my $qfn = "$base_dir_qfn/$fn"; 

     if ($i % 1000 == 0) { 
     $dir_qfn = sprintf("%s/dir_%03d", $base_dir_qfn, int($i/1000)+1); 
     mkdir($dir_qfn) 
      or die("Can'\''t make directory \"$dir_qfn\": $!\n"); 
     } 

     rename($qfn, "$dir_qfn/$fn") 
     or do { 
      warn("Can'\''t move \"$qfn\" into \"$dir_qfn\": $!\n"); 
      next; 
     }; 

     ++$i; 
    } 
' 
+0

Оптимизированные и исправленные ошибки. – ikegami

+0

Добавлено объяснение. – ikegami

+0

Проблема заключается в 2,5 миллионах итераций _invoking нескольких внешних утилит_, не создавая внутренний массив всех имен файлов спереди, через '*'. Простой цикл за 2,5 миллиона файлов занимает около 25 секунд на моем ноутбуке под управлением Ubuntu 14.04: 'time for f in *; делать :; done' Добавление простой _one_ простой внешней утилиты в микс - например, 'время для f в *; дата; done>/dev/null' - значительно увеличивает время выполнения; Я убил команду примерно через 30 минут. Ваше решение Perl намного быстрее, потому что все выполняется в процессе. – mklement0

1

, если каталог не находится под использованием, я предлагаю следующее

find . -maxdepth 1 -type f | split -l 1000 -d -a 5 

это создаст н количество файлов с именем x00000 - x02500 (только убедитесь, что 5 цифр, хотя 4 будут работать тоже). Затем вы можете переместить 1000 файлов, перечисленных в каждом файле, в соответствующий каталог.

возможно set -o noclobber, чтобы исключить риск переопределений в случае столкновения имени.

переместить файлы, то проще использовать расширение распорки перебирать имена файлов

for c in x{00000..02500}; 
do d="d$c"; 
    mkdir $d; 
    cat $c | xargs -I f mv f $d; 
done 
+0

++, но (a) с помощью _GNU_ 'find' результирующий список имен файлов не будет сортироваться, в отличие от glob' * ', (b) если предположить, что' xargs' OP 'поддерживает' -0', используя 'tr ' \ n '' \ 0 '<"$ c" | xargs -0 -J f mv f "$ d" 'будет намного быстрее; (c), как я сделал в фрагментах в (b), я предлагаю использовать двойные цитаты ссылок на переменные для продвижения хороших привычек, хотя это здесь не является абсолютно необходимым. – mklement0

+0

yes Файлы не сортируются, но я не уверен, что это требование. – karakfa

+0

Это не указано как требование, но, учитывая, что OP основывает свой подход на '*', различие стоит упомянуть. Я просто понял, что '-J' является опцией _BSD_' xargs'; с утилитами GNU, более эффективная команда должна быть указана как 'tr '\ n' '\ 0' <" $ c "| xargs -0 mv -t "$ d" '; и просто для того, чтобы быть явным: ваш подход '-I f' перемещает каждый файл _individually_. – mklement0

-2

Я хотел бы использовать следующее из командной строки:

find . -maxdepth 1 -type f |split -l 1000 
for i in `ls x*` 
do 
    mkdir dir$i 
    mv `cat $i` dir$i& 2>/dev/null 
done 

Ключа является «&» который выталкивает каждый оператор mv.

Благодаря karakfa для раздельной идеи.

+1

Хотя вы можете избежать использования 'for' в случае' ls x * ', потому что имена файлов не имеют встроенных пространств или метафоров с металификацией. Это [плохая идея вообще] (http: //mywiki.wooledge .org/DontReadLinesWithFor); Кроме того, '\' cat $ i' \ 'заставляет результирующие слова подвергать разбиению слов, на которые вы полагаетесь, с одной стороны, но который вызывает сбой с именами файлов со встроенными пространствами. Кроме того, полученная командная строка может оказаться слишком длинной с помощью этой техники. Учитывая, что у вас уже есть _file_ '$ i', вы не можете создать _directory_ с тем же именем. – mklement0

+1

Спасибо, отличная обратная связь. –

3

Примечание: ikegami's helpful Perl-based answer это путь - он выполняет всю операцию в одного процесса и, следовательно, гораздо быстрее, чем решение + стандартные утилиты Баш ниже.


bash основанное решение должно избежать петли, в которых внешние утилиты называются заказ на выполнение разумно.
Ваше собственное решение вызывает два внешних утилит и создает подоболочка в каждой итерации цикла, что означает, что вы будете в конечном итоге создание около 7500000 процессов (!) в общей сложности.

Следующее решение избегает циклов, но, учитывая огромное количество входных файлов, все равно потребуется довольно много времени для завершения (вы в конечном итоге создадите 4 процесса для каждых 1000 входных файлов, т. Е. Ca.10000 процессов в общей сложности):

printf '%s\0' * | xargs -0 -n 1000 bash -O nullglob -c ' 
    dirs=(dir_*/) 
    dir=dir_$(printf %04s $((1 + ${#dirs[@]}))) 
    mkdir "$dir"; mv "[email protected]" "$dir"' - 
  • printf '%s\0' * печатает NUL разделенный список всех файлов в директории.
    • Обратите внимание, что поскольку printf является Bash встроенного, а не внешней утилита, максимальная. длина командной строки, указанная getconf ARG_MAX. не применяется.
  • xargs -0 -n 1000 вызывает указанную команду с кусками из 1000 имен входных файлов.

    • Обратите внимание, что xargs -0 является нестандартным, но поддерживается как на Linux, так и на BSD/OSX.
    • Использование входа с NUL-разъемом надежно передает имена файлов, не опасаясь непреднамеренно разбить их на несколько частей и даже работает с именами файлов со встроенными новыми линиями (хотя такие имена файлов встречаются очень редко).
  • bash -O nullglob -c выполняет указанную командную строку с параметром nullglob включена, что означает, что подстановка шаблон, который не совпадает ни с чем расширится в пустую строку.

    • Строка команды подсчитывает выходные каталоги, созданные до сих пор, с тем, чтобы определить имя следующего вывода директории со следующим более высоким индексом, создает следующий выходной реж и перемещает текущую партию (до) 1000 файлов.
+0

Вы можете позволить себе цикл по каталогам (но не файлам). – laune

+0

@laune: Да, но нет простого способа _robustly_ пропускать партии из 1000 имен файлов в цикле 'while' непосредственно из' xargs' (ваш подход не работает с именами файлов со встроенными пространствами и/или именами файлов, которые действительно являются действительными шарики). Позволяя 'xargs' передавать каждую партию из 1000 имен файлов в виде отдельных аргументов (без участия оболочки) в' bash', они корректно сохраняются. – mklement0

-1

Это, вероятно, медленнее, чем программа на Perl (1 минута для 10000 файлов), но он должен работать с любой совместимой оболочкой POSIX.

#! /bin/sh 
nd=0 
nf=0 
/bin/ls | \ 
while read file; 
do 
    case $(expr $nf % 10) in 
    0) 
    nd=$(/usr/bin/expr $nd + 1) 
    dir=$(printf "dir_%04d" $nd) 
    mkdir $dir 
    ;; 
    esac 
    mv "$file" "$dir/$file" 
    nf=$(/usr/bin/expr $nf + 1) 

сделано

С Баш, вы можете использовать арифметическое расширение $ ((...)).

И, конечно, эту идею можно улучшить с помощью xargs - не должно занимать дольше 45 секунд для 2,5 миллионов файлов.

nd=0 
ls | xargs -L 1000 echo | \ 
while read cmd; 
do 
    nd=$((nd+1)) 
    dir=$(printf "dir_%04d" $nd) 
    mkdir $dir 
    mv $cmd $dir 
done 
+0

Процитируйте двойные цитаты ссылкой на переменные, чтобы защитить их от непреднамеренного разбиения и разбиения слов. Для обеспечения надежности ваши команды 'read' должны иметь префикс' IFS = 'и использовать' -r': 'IFS = read -r ...'. В то время как редки, могут быть имена файлов с ведущими или конечными пробелами или встроенными символами '\'. – mklement0

+0

Ваши первые 2 команды обрабатывают каждый файл по отдельности, а именно то, что сам ОП пытался, безуспешно. 2,5 миллиона вызовов нескольких внешних утилит занимают много времени. – mklement0

+0

Решение 'xargs' не является надежным: оно ломается с именами файлов со встроенными пространствами. – mklement0