LINUX.ORG.RU

Как разбить по разным каталогам содержимое одного в 8000 файлов?

 , ,


2

2

Есть каталог с более чем 8000 файлов. Для ускорения работы с ним, нужно разбить находящиеся в нём файлы на группы по какому-либо признаку. 1) вариант создать 8 подкаталогов, и переместить в каждый по 1000 файлов. 2) Поскольку файлы добавлялись годами, создать папки с номерами годов, и в каждый поместить созданные в нём файлы.

Вопрос, как такое лучше всего сделать?

Приходит на ум find, но только для варианта 2).

find . -mindepth 1 -newermt '2011-01-01 00:00' ! -newermt 
'2012-01-01 00:00' -ls

Вот только выдача по -ls кривая. Вместо русских имён файлов юникоды: /\320\243\320\261\320\270\321\202\321\214\
Выдача просто по ls корректная.
почему вывод ls через find кривой и как это исправить? man пишет, что русские буквы ему UNUSUAL FILENAMES, поэтому по дефолту выводит так. Как отучить его от этого, пока не дочитал.

★★★

Последнее исправление: hikikomori (всего исправлений: 1)

почему вывод ls через find кривой и как это исправить?

видимо, никак

Unusual characters are always escaped

DllMain
()

Во-первых, тебе -ls тут не нужно. Парсить выхлоп ls (настоящего или его имитации от find) - плохая практика.

Во-вторых, если выхлоп упростить, то его проще парсить:

find -printf '%TY %p\n' |
    while read -r year fname; do
        printf '[%s] [%s]\n' "$year" "$fname"
        ls "$fname"
        # mv "$fname" "$year"/
    done
$ ls
'back\slash'  'dollar $sign'  'double  space'  'space space'   кириллица
$ find -type f -printf '%TY %p\n' | while read -r year fname; do printf '[%s] [%s]\n' "$year" "$fname"; ls "$fname"; done
[2020] [./кириллица]
./кириллица
[2020] [./double  space]
'./double  space'
[2020] [./back\slash]
'./back\slash'
[2020] [./dollar $sign]
'./dollar $sign'
[2020] [./space space]
'./space space'
legolegs ★★★★★
()
Последнее исправление: legolegs (всего исправлений: 2)
Ответ на: комментарий от legolegs

Спасибо за хороший тон с # перед mv, оценил.
Вариант опробую, но переделаю в решение в одну строку, чтобы в history хранился.
Мне мой вариант вывода заданного диапазона тоже понравился, подскажите, на что заменить -ls , чтобы не игнорил киррилицу? Ман прочитал, но без примеров не получается.

hikikomori ★★★
() автор топика
Ответ на: комментарий от hikikomori

Вариант опробую, но переделаю в решение в одну строку, чтобы в history хранился.

Он хранится, если его ввести как у меня. Пока done не введёшь пайплайн считается не введённым и исполняться не начнёт.

Мне мой вариант вывода заданного диапазона тоже понравился

Место . и критерий поиска -mindepth 1 -newermt '2011-01-01 00:00' ! -newermt '2012-01-01 00:00' можно вставить туда же, перед -printf.

С find -printf '%TY %p\n' (без | и всего после) можно спокойно экспериментировать, разберёшься.

PS Не запутайся, тут два разных printf, один встроенный в find, другой команда.

legolegs ★★★★★
()
Последнее исправление: legolegs (всего исправлений: 1)
Ответ на: комментарий от legolegs

Я пробовал, не получается...

$ find . -mindepth 1 -newermt '2011-03-29 00:00' ! -newermt '2011-03-30 00:00' -printf
find: отсутствует аргумент у «-printf»


Про два разных printf я понял, спасибо.

Он хранится, если его ввести как у меня.

Не вариант, я периодически чищу $HISTFILE от дублей, часто это сопряжено с сортировкой, обязательно потеряется что-то.

hikikomori ★★★
() автор топика
Ответ на: комментарий от hikikomori

Кажется нашёл: не -printf -fprint -fprintf, не -ok, а просто print:

find . -newermt '2011-03-29 00:00' ! -newermt '2011-03-30 00:00' -print

hikikomori ★★★
() автор топика
Ответ на: комментарий от legolegs

Понятно. Я так глубоко find не изучал. Для примера, как ему этот формат подать?

hikikomori ★★★
() автор топика
Ответ на: комментарий от legolegs

Пробовал, но какие-то глюки начались, поэтому закомментил

export HISTFILESIZE=999999
export HISTSIZE=999999
# export HISTCONTROL=ignoreboth:erasedups
# export HISTIGNORE="ls:ps:history"
PROMPT_COMMAND="history -a; history -n"

hikikomori ★★★
() автор топика
Ответ на: комментарий от legolegs

А как-то можно без цикла вместо перечисления указать диапазон для группового создания директорий?
mkdir ./{2011,2020}

hikikomori ★★★
() автор топика
Последнее исправление: hikikomori (всего исправлений: 1)
Ответ на: комментарий от legolegs

Спасибо) А то я нагородил: mkdir ./$(for x in {2011..2014};do echo $x;done)

hikikomori ★★★
() автор топика
Ответ на: комментарий от legolegs

А насчёт формата printf не подскажите? А то хочу вывести с сортировкой по дате:

find . -mindepth 1 -newermt '2019-01-01' ! -newermt $(date +%Y-%m-%d) -print

hikikomori ★★★
() автор топика
Ответ на: комментарий от legolegs

то его проще парсить:

Это сломается, если файл будет начинаться или заканчиваться пробелом.

while true; do IFS= read -r -d '' y; IFS= read -r -d '' f; [[ -z $y ]] && break; [[ $f = . ]] && continue; ls "$f"; done  < <(find -printf '%TY\0%p\0'; printf '\0\0')

vodz ★★★★★
()
Последнее исправление: vodz (всего исправлений: 3)
Ответ на: комментарий от vodz

Вы не поясните ваш скрипт? В чём соль?

Я тут свой вариант пробую, вроде бы всё скопировал, но почему-то cp ругается на каждой строчке: один и тот же файл

y=2011; find . -mindepth 1 -newermt $y+'-01-01 00:00' ! -newermt $y+'-12-31 23:59' -exec cp -p {} $y \;

hikikomori ★★★
() автор топика
Ответ на: комментарий от hikikomori

Вы не поясните ваш скрипт? В чём соль?

Там всё придельно прозрачно.

vodz ★★★★★
()
Ответ на: комментарий от legolegs

В какое место его поместить? Я всюду перепробовал, но если помещаю в начало после точки, то find: warning: you have specified the global option -mindepth after the argument -type, перед или после -print то cp: не удалось выполнить stat.

hikikomori ★★★
() автор топика
Ответ на: комментарий от hikikomori

cp: не удалось выполнить stat.

Это уже другая ошибка, cp не видит файл, который нашел find. Что-то ты напутал.

legolegs ★★★★★
()
Ответ на: комментарий от anonymous

А если папки уже существуют, будет просто ошибка создания, или скрипт прервётся? 2>&1 это перенаправление stderr в stdout? А без этого он разве не выведется?
Где бы почитать понятным языком о параметрах printf в find?

hikikomori ★★★
() автор топика
Ответ на: комментарий от legolegs

Вот вставил. Работает, копирует, но с теми же ошибками, что и без -type f

 y=2011; find . -mindepth 1 -type f -newermt $y+'-01-01 00:00' ! -newermt $y+'-12-31 23:59' -exec cp -p {} $y \;


Эффект один:
cp: './2011/filename' и '2011/filename' - один и тот же файл

hikikomori ★★★
() автор топика
Ответ на: комментарий от hikikomori

Если папки существуют, то просто выведется ошибка и команда продолжит выполняться. 2>&1 — да, это перенаправление stderr в stdout, это сделано для того, чтобы убрать дубликаты сообщений об ошибках через sort | uniq.

В man find расписаны все опции printf.

anonymous
()

можно взять python и набросать небольшой скрипт

buratino ★★★★★
()
Ответ на: комментарий от anonymous

Спасибо, теперь про 2>&1 понятно.
Маны очень тяжело читать, тем более на малознакомом языке. Я уже его прошерстил насчёт -ls, ещё раз не смогу, мозги закипят. Поэтому и спросил, нет ли где по-русски живым языком с примерами почитать?

hikikomori ★★★
() автор топика
Ответ на: комментарий от hikikomori

cp: ‘./2011/filename’ и ‘2011/filename’ - один и тот же файл

Логично. Надо эти папки в исключения добавить. Например, ограничив максимальную глубину поиска через -maxdepth 1 или каждый год дописать в условие поиска с отрицанием: -not -path './2011/*'

Но это нужно только для порядка и для чистоты вы выхлопе.

А маны надо читать, тут ничего не поделаешь. Можно поискать на опеннете, там есть какие-то переведённые маны.

legolegs ★★★★★
()

На, доделал. Обзываешь директорию с твоими фотками например FOTO.
Стартуешь скрипт c параметром = путь к этой директории.
!!! Осторожно: cкрипт переместит все файлы из указанной директории ${NAME} и её поддиректорий в директории ${NAME}_${year}. !!!

#!/bin/bash

USAGE() {
echo "Use: $(basename $0) path-to-directory";
return 0;
}

DO() {
find "${1}" -type f -printf '%TY %p\n' |
while read -r year fname; do
        printf '[%s] [%s]\n' "$year" "$fname"
	td="${1}_${year}/"
	mkdir -p "${td}"
	#cp --backup=numbered --preserve "${fname}" "${td}"
	mv --backup=numbered "${fname}" "${td}"
done
}

if [ $# -eq 0 ]; then
    USAGE;
    exit 0
fi

echo "Source dir.: $1"
echo "Press 'y' for continue..."
read -s -n 1 key

case $key in
[y,Y])
DO "${1}"
exit 0
;;
*) 
exit 1
esac
Если в поддиректориях были файлы с одинаковыми именами, то они будут переименованы с добавлением суффикса как тут:
$ ls -a1
.
..
'Я и Маша.jpg'
'Я и Маша.jpg.~1~'
'Я и Маша.jpg.~2~'
'Я на лыжах.jpg'
'Я на лыжах.jpg.~1~'
'Я на лыжах.jpg.~2~'

superuser ★★★★☆
()
Последнее исправление: superuser (всего исправлений: 1)
Ответ на: комментарий от legolegs

-maxdepth 1 помог. Я почти дописал свой вариант скрипта, но не удаётся впендюрить проверку наличия каталога в рамках конструкции find ... -exec:

$ for y in {2011..2020}; do find . -maxdepth 1 -newermt $y+'-01-01 00:00' ! -newermt $y+'-12-31 23:59' -exec if ! [ -d ./$y ]; then mkdir $y ; fi ; cp -p {} $y \ ; ; done


Вот тут всё работает, но по отдельности if ! [ -d ./$y ]; then mkdir $y ; fi ; и остальное. А вместе
bash: синтаксическая ошибка рядом с неожиданным маркером «then»

ЧЯДНТ?

hikikomori ★★★
() автор топика
Ответ на: комментарий от superuser

Спасибо, изучу ваш вариант, интересный, не пропадёт. Оказывается в баше есть ( USAGE() {} ) что-то вроде функций? Но я хочу в рамках однострочника. Теперь столкнулся с проблемой описанный выше в пред. сбщ.. Почти получилось)

hikikomori ★★★
() автор топика
Ответ на: комментарий от hikikomori

Точки с запятой в команде, передаваемой find в качестве -exec необходимо заэкранировать: \;

anonymous
()
Ответ на: комментарий от anonymous

Спасибо! Но всё равно что-то не так:

for y in {2011..2020}; do find . -maxdepth 1 -newermt $y+'-01-01 00:00' ! -newermt $y+'-12-31 23:59' -exec if ! [ -d ./$y ] \; then mkdir $y \; fi \; cp -p {} $y \; ; done
find: paths must precede expression: `then'

hikikomori ★★★
() автор топика
Ответ на: комментарий от hikikomori

Но я хочу в рамках однострочника.

Думаю, что смысла нет в однострочнике. И к тому-же занятие сие м.б. чревато - можно случайно наперемещать того чего не ожидал. А в скрипте можно всё перепроверить и переспросить.
Помести его в /usr/local/bin/ и вызывай в «однострочнике» своём.

superuser ★★★★☆
()
Ответ на: комментарий от hikikomori

-exec command ; Execute command; true if 0 status is returned. All following arguments to find are taken to be arguments to the command until an argument consisting of `;’ is encountered.

until an argument consisting of `;’ is encountered

Поставь условие создания папки перед find. Типа for y in {2011..2020}; do if ! [ -e ./$y ]; then mkdir $y; fi; find ... done.

anonymous
()
Ответ на: комментарий от anonymous

Так работает, но логичнее было бы осуществлять проверку непосредственно перед записью в папку. Хотелось бы разобраться, что за ограничения в этом поле -exec \; ; ?

for y in {2011..2020}; do if ! [ -d ./$y ] ; then mkdir $y ; fi ; find . -maxdepth 1 -newermt $y+'-01-01 00:00' ! -newermt $y+'-12-31 23:59' -exec cp -p {} $y \; ; done

hikikomori ★★★
() автор топика
Ответ на: комментарий от legolegs

C ! -path не сработало, ведь cp нет дела до ключей find:

for y in {2011..2020}; do if ! [ -d ./$y ] ; then mkdir $y ; fi ; find . -maxdepth 1 -newermt $y+'-01-01 00:00' ! -newermt $y+'-12-31 23:59' ! -path ./$y -exec cp -p {} $y \; ; done

cp: не указан -r; пропускается каталог './2015'

hikikomori ★★★
() автор топика
Ответ на: комментарий от hikikomori

Программа find ничего не знает ни про какие if then (про них знает шелл). После -exec идёт одна команда с аргументами.

legolegs ★★★★★
()
Ответ на: комментарий от legolegs

Вы правы, но это не спасает, он всё равно находит в них файлы и исполняет после -exec:

for y in {2011..2020}; do if ! [ -d ./$y ] ; then mkdir $y ; fi ; find . -maxdepth 1 -newermt $y+'-01-01 00:00' ! -newermt $y+'-12-31 23:59' ! -path ./$y/* -exec cp -p {} $y \; ; done
cp: не указан -r; пропускается каталог '.'
cp: не указан -r; пропускается каталог './2015'

hikikomori ★★★
() автор топика
Ответ на: комментарий от hikikomori

! -path ./$y/*

Кавычки потерял, поэтому звёздочку раскрывает шелл. Используй ! -path "./$y/*"

Ну и начальную директорию . надо уже наконец-то исключить, через -type f, -mindepth 1 или ! -name .

legolegs ★★★★★
()
Ответ на: комментарий от goingUp

Питон как то защитит от ошибок ‘копируем файл в себя’?

legolegs ★★★★★
()
Ответ на: комментарий от goingUp

На питоне я могу, но надо развиваться.

hikikomori ★★★
() автор топика
Ответ на: комментарий от legolegs

Используй ! -path "./$y/*"

Попробовал, без разницы.

Ну и начальную директорию . надо уже наконец-то исключить

Тогда где find вообще будет искать? Он из неё файлы берёт. Исключать что-то в cp хз как. Тем паче, что там может быть и не cp, а mv, rsync. Хотя варианты с последними не заработали вообще или странно заглючили.

hikikomori ★★★
() автор топика
Ответ на: комментарий от hikikomori

Используй ! -path «./$y/*»

Попробовал, без разницы.

Что-то не так делаешь, значит. Ну или вместо «./» надо поставить путь поиска, который ты find первым аргументом передаёшь.

Тогда где find вообще будет искать?

Искать будет везде где сказано, находить то, что соответствует поисковому запросу. Убираешь -exec и проверяешь, это легко. Вообще-то прежде чем совать cp стоило бы довести до ума работу с find.

legolegs ★★★★★
()

Тебе всё равно придётся перемещать каждый файл поэтому возня с find лишня. Пишу с мобилы поэтому не гарантирую что скрипт будет рабочий

for FILE in $(ls *); do YEAR=$(stat -c%y $FILE | awk -F'-' {print $1}); mkdir -p .. /$YEAR; mv $FILE ../$YEAR; done

ya-betmen ★★★★★
()
Вы не можете добавлять комментарии в эту тему. Тема перемещена в архив.