Ваша попытка создать папки хороша, хотя я бы сделал немного иначе:
tail -n +2 train_labels.csv | cut -d, -f4 | sort -u | xargs mkdir -p
Я использую tail
и cut
для вывода списка папок, который должен быть быстрее чем awk
для очень больших файлов. Тестируя с файлом из 120 тыс. Строк, я получаю около 150 мс для awk
против 100 мс для tail|cut
.
Но наиболее важной точкой импорта является сортировка списка с sort -u
перед подачей mkdir
чтобы минимизировать количество системных вызовов. Это правда, что mkdir -p
не будет создавать один и тот же каталог дважды, но ему все равно необходимо проверить, существует ли каталог, что требует значительных затрат.
Для копирования / перемещения файлов ваше решение работает просто оборачивая его вокруг простого синтаксического анализатора CSV, используя read
с IFS=,
while IFS=, read -r hashed_id country continent scientific_name filename
do
cp train/"$filename" "$scientific_name"/
done < train_labels.csv
Вы можете группировать вызовы копирования / перемещения для строк с одинаковыми scientific_name
, например, cp train/file1 train/file2 train/file3 my_scientific_name/
, но это немного более сложный, он не обязательно быстрее, и вы можете в конечном итоге достичь предела аргумента командной строки.
Создание папок
Если вы хотите заполнить свои папки, например, 1000 файлами, вы Сначала нужно определить синтаксис для ваших сегментов, например, $scientific_name.$index
, где $index
для вашего сегмента. Например, первые 1000 файлов nerodia-sipedon
будут в конечном итоге в nerodia-sipedon.0
, затем следующие 1000 файлов в nerodia-sipedon.1
и так далее. Соглашение об именах является произвольным, и вы можете изменить его, если подумаете о более хорошем соглашении.
Тогда вы можете использовать ассоциативный массив, который подсчитывает количество раз, которое использовалось каждое научное имя c.
declare -A names=()
while IFS=, read -r hashed_id country continent scientific_name filename
do
((index=names[$scientific_name]/1000))
((names[$scientific_name]%1000==0)) && mkdir -p "$scientific_name"."$index"
cp train/"$filename" "$scientific_name"."$index"/
((names[$scientific_name]++))
done < train_labels.csv
По сути, это тот же алгоритм, что и раньше, но массив names
хранит информацию о том, сколько раз уже использовалось научное имя c. Более того, вам не нужно заранее создавать папки, потому что мы можем проверить лучший момент для создания ровно одного каталога для каждого научного c имени.
Давайте разберем алгоритм для имени:
((index=names[$scientific_name]/1000))
Поскольку имена еще не вставлены, names[$scientific_name]
эквивалентно 0
, что означает, что эта строка присваивает 0
index
.
((names[$scientific_name]%1000==0)) && mkdir -p "$scientific_name"."$index"
Поскольку имена не были вставленный, names[$scientific_name]%1000
будет равен 0
, что означает, что каталог будет создан с использованием синтаксиса $scientific_name.$index
.
cp train/"$filename" "$scientific_name"."$index"/
Это просто копирует файл в новый каталог.
((names[$scientific_name]++))
Увеличивает количество записей для этого научного c имени. Поскольку никакие имена еще не были вставлены, число будет увеличено с 0
, что приведет к значению 1
.
В следующий раз, когда будет найдена запись с тем же научным именем c, names[$scientific_name]
вернет 1
:
((index=names[$scientific_name]/1000))
Назначит 0
на index
.
((names[$scientific_name]%1000==0)) && mkdir -p "$scientific_name"."$index"
Будет не создать каталог из-за остатка из 1%1000
не ноль.
cp train/"$filename" "$scientific_name"."$index"/
Копирует файл в существующий каталог.
((names[$scientific_name]++))
Увеличивает число с 1
до 2
.
Это продолжается до 999-й записи для того же научного c имени. Для 1000-й записи index
будет 1
, и будет создан каталог. 1001-й файл, 1002-й файл, et c. будет скопирован в этот новый каталог. Затем 2000-й файл создаст другой каталог. Промойте и повторите.
Я должен упомянуть, что если вы запустите этот алгоритм несколько раз с разными CSV-файлами, ваши сегменты могут увеличиться выше желаемого предела. Вы можете решить эту проблему, предварительно посчитав количество записей для каждого существующего каталога, например, заполните массив names
следующим образом:
declare -A names=()
while IFS= read -r -d '' folder
do
scientific_name=${folder%.*}
(( names[$scientific_name]+=$(find "$folder" -type f | wc -l) ))
done < <(find . -mindepth 1 -maxdepth 1 -type d -name '?*.?*' -printf '%f\0')