Как скопировать содержимое папки в несколько папок в зависимости от количества файлов? - PullRequest
1 голос
/ 06 октября 2019

Я хочу скопировать файлы из папки (с именем: 1) в несколько папок в зависимости от количества файлов (здесь: 50).

Работает приведенный ниже код. Я перенес все файлы из папки в подпапки в зависимости от количества файлов, а затем скопировал все файлы в каталоге обратно в исходную папку. Однако мне нужно что-то чище и эффективнее. Извините за беспорядок ниже, я nube.

bf=1 #breakfolder
cd 1 #the folder from where I wanna copy stuff, contains 179 files

flies_exist=$(ls -1q * | wc -l) #assign the number of files in folder 1

#move 50 files from 1 to various subfolders

while [ $flies_exist -gt 50 ]
do

mkdir ../CompiledPdfOutput/temp/1-$bf
set --
for f in .* *; do
  [ "$#" -lt 50 ] || break
  [ -f "$f" ] || continue
  [ -L "$f" ] && continue
  set -- "$@" "$f"
done

mv -- "$@" ../CompiledPdfOutput/temp/1-$bf/
flies_exist=$(ls -1q * | wc -l)
bf=$(($bf + 1))
done

#mover the rest of the files into one final subdir

mkdir ../CompiledPdfOutput/temp/1-$bf
set --
for f in .* *; do
  [ "$#" -lt 50 ] || break
  [ -f "$f" ] || continue
  [ -L "$f" ] && continue
  set -- "$@" "$f"
done
mv -- "$@" ../CompiledPdfOutput/temp/1-$bf/
#get out of 1
cd ..

# copy back the contents from subdir to 1
find CompiledPdfOutput/temp/ -exec cp {} 1 \;

Требуемая структура каталогов:

        parent
  ________|________
  |               |
  1       CompiledPdfOutput
  |               |
(179)           temp
                  |
             ---------------
             |    |    |    |
            1-1  1-2  1-3  1-4
            (50) (50) (50) (29)

Число внутри "()" обозначает количество файлов.

Кстати, последний шаг моего кода дает это предупреждение, был бы рад, если кто-нибудь может объяснить, что происходит, и решение.

cp: -r not specified; omitting directory 'CompiledPdfOutput/temp/'
cp: -r not specified; omitting directory 'CompiledPdfOutput/temp/1-4'
cp: -r not specified; omitting directory 'CompiledPdfOutput/temp/1-3'
cp: -r not specified; omitting directory 'CompiledPdfOutput/temp/1-1'
cp: -r not specified; omitting directory 'CompiledPdfOutput/temp/1-2'

Я также не хочу копировать каталог, простофайлы, дающие -r, будут плохими.

Ответы [ 3 ]

2 голосов
/ 06 октября 2019

Я обнаружил пару проблем с опубликованным сценарием:

  • Логика копирования максимум 50 файлов в папку чрезмерно усложнена, а дублирование кода всего цикла подвержено ошибкам.

  • Использует массив позиционных параметров $@ для внутреннего хранения. Эта переменная не предназначена для этого, было бы лучше использовать новый выделенный массив.

  • Вместо того, чтобы перемещать файлы в подкаталоги и затем копировать их обратно, было бы прощепросто скопируйте их на первом шаге, не двигаясь.

  • Анализ выходных данных ls не рекомендуется.

Рассмотрим эту альтернативу, более простая логика:

  • Инициализация пустого массива to_copy, чтобы сохранить файлы, которые должны быть скопированы
  • Инициализация счетчика папок, чтобы использовать для вычисления целевой папки
  • Зацикливание на исходных файлах
    • Применение фильтров, как и прежде (пропустите, если не файл)
    • Добавить файл в to_copy
    • Если to_copy содержит целевое числозатем файлы:
    • Создайте целевую папку
    • Скопируйте файлы, содержащиеся в to_copy
    • Сбросьте содержимое to_copy в пустое
    • Приращениеfolder_counter
  • Если to_copy не пусто
    • Создать цельпапка
    • Скопируйте файлы, содержащиеся в to_copy

Примерно так:

#!/usr/bin/env bash

set -euo pipefail

distribute_to_folders() {
    local src=$1
    local target=$2
    local max_files=$3

    local to_copy=()
    local folder_counter=1

    for file in "$src"/* "$src/.*"; do
        [ -f "$file" ] || continue

        to_copy+=("$file")
        if (( ${#to_copy[@]} == max_files )); then
            mkdir -p "$target/$folder_counter"
            cp -v "${to_copy[@]}" "$target/$folder_counter/"
            to_copy=()
            ((++folder_counter))
        fi
    done

    if (( ${#to_copy[@]} > 0 )); then
        mkdir -p "$target/$folder_counter"
        cp -v "${to_copy[@]}" "$target/$folder_counter/"
    fi
}

distribute_to_folders "$@"

Для распространения файлов в path/to/1в каталогах максимум 50 файлов под path/to/compiled-output, вы можете вызвать этот скрипт с помощью:

./distribute.sh path/to/1 path/to/compiled-output 50

Кстати, последний шаг моего кода дает это предупреждение, был бы рад, если кто-нибудьможет объяснить, что происходит, и решение.

Конечно. Команда find CompiledPdfOutput/temp/ -exec cp {} 1 \; находит файлы и каталоги и пытается их скопировать. Когда cp встречает каталог, а параметр -r не указан, выдается предупреждение, которое вы видели. Вы можете добавить фильтр для файлов с помощью -type f. Если файлов не слишком много, тогда простой оболочка сделает эту работу:

cp -v CompiledPdfOutput/temp/*/* 1
2 голосов
/ 06 октября 2019

Предполагая, что вам нужно что-то более компактное / эффективное, вы можете использовать существующие инструменты (find, xargs) для создания конвейера, избавляя от необходимости программировать каждый шаг, используя bash.

Следующие файлы будут перемещеныв разделенную папку. Он найдет файлы, сгруппирует их по 50 в каждую папку, использует awk для создания выходной папки и переместит файлы. Решение не такое элегантное, как оригинал: - (

find 1 -type f |
    xargs -L50 echo |
    awk '{ print "CompliedOutput/temp/1-" NR, $0 }' |
    xargs -L1 echo mv -t

В качестве примечания, текущий скрипт перемещает файлы из папки '1' в пронумерованные папки, а затем копирует файл обратно в оригиналПапка. Почему бы просто не скопировать файлы в пронумерованные папки. Вы можете использовать 'cp -p', чтобы сохранить метку времени, если это необходимо.

Поддержка имен файлов с новыми строками (и пробелами)

Пояснение к вопросу указывает, что решение должно работать с именами файлов со встроенными новыми строками (и пробелами). Это требует незначительных изменений для использования символа NUL в качестве разделителя.

  # Count number of output folders
DIR_COUNT=$(find 1 -type f -print0 | xargs -0 -I{} echo X | wc -l)
  # Remove previous tree, and create folder
OUT=CompiledOutput/temp
rm -rf $OUT
eval mkdir -p $OUT/1-{1..$DIR_COUNT}

# Process file, use NUL as separator
find 1 -type f -print0 | 
   awk -vRS="\0"  -v"OUT=$OUT" 'NR%50 == 1 { printf "%s/1-%d%s",OUT,1+int(NR/50),RS } { printf "%s", ($0 RS) }' |
   xargs -0 -L51 -t mv -t

Сделалограниченное тестирование с использованием как места, так и новых строк в файле. На моем компьютере выглядит нормально.

0 голосов
/ 06 октября 2019

Это скопирует файлы в несколько папок фиксированного размера. Измените source, target и folderSize согласно вашему требованию. Это также работает с именами файлов со специальными символами (например, 'file 131!@#$%^&*()_+-=;?').

source=1
target=CompiledPDFOutput/temp 
folderSize=50

find $source -type f -printf "\"%p\"\0" \
| xargs -0 -L$folderSize \
| awk '{system("mkdir -p '$target'/1-" NR); printf "'$target'/1-" NR " %s\n", $0}' \
| xargs -L1 cp -t
Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...