Как я могу организовать файлы по первой букве имени файла в папки AZ

15

Я ищу способ (желательно терминальный) организовать более 1000 шрифтов по их первой букве.

В основном создайте каталоги, A-Z, #затем переместите файлы шрифтов в эти каталоги, основываясь на их первом символе в имени файла. Шрифты, которые начинаются с цифр [0-9] или других специальных символов для перемещения в #каталог.

Parto
источник
Вы хотите, чтобы каталоги создавались, даже если нет файлов, начинающихся с этой буквы?
Arronical
@ Arronical Нету. Просто если есть файлы.
Парто
4
Я надеюсь, что эта ссылка поможет вам stackoverflow.com/questions/1251938/…
Karthickeyan

Ответы:

13

Поздний вариант с питоном:

#!/usr/bin/env python3
import os
import sys
import shutil

def path(dr, f): return os.path.join(dr, f)

dr = sys.argv[1]
for f in os.listdir(dr):
    fsrc = path(dr, f)
    if os.path.isfile(fsrc):
        s = f[0]; target = path(dr, s.upper()) if s.isalpha() else path(dr, "#")
        if not os.path.exists(target):
            os.mkdir(target)
        shutil.move(fsrc, path(target, f))

Как пользоваться

  1. Скопируйте скрипт в пустой файл, сохраните его как move_files.py
  2. Запустите его с каталогом в качестве аргумента:

    python3 /path/to/move_files.py /path/to/files
    

Сценарий создаст (под) каталог (-ies) (заглавными буквами), если он действительно нужен

объяснение

Сценарий:

  • выводит список файлов, получает первый символ (определяет исходный путь):

    for f in os.listdir(dr):
        s = f[0]; fsrc = path(dr, f)
  • проверяет, является ли элемент файлом:

    if os.path.isfile(fsrc):
  • определяет целевую папку, если первый символ альфа или нет:

    target = path(dr, s.upper()) if s.isalpha() else path(dr, "#")
  • проверяет, существует ли папка или нет, создает ее, если нет:

    if not os.path.exists(target):
        os.mkdir(target)
  • перемещает элемент в соответствующую папку:

    shutil.move(fsrc, path(target, f))
Якоб Влейм
источник
Привет, Джейкоб. Есть ли проверка прописных первых букв?
Parto
@Parto Абсолютно! Каким образом вам это нужно? (Я могу проверить в 3,5 часа обучения :)
Джейкоб Vlijm
Это действительно так. Идеальная реализация.
Parto
После рассмотрения я решил пойти с этим ответом по нескольким причинам: 1). Объяснение того, что происходит. 2). Ответ выполняется правильно с первой попытки
Parto
11

Кодовый гольф, читаемый всего двумя командами и двумя регулярными выражениями:

mkdir -p '#' {a..z}
prename -n 's|^[[:alpha:]]|\l$&/$&|; s|^[0-9]|#/$&|' [[:alnum:]]?*

Если у вас есть огромное количество файлов для перемещения, слишком много, чтобы поместиться в список аргументов процесса (да, есть предел, и он может составлять всего несколько килобайт), вы можете сгенерировать список файлов с помощью другой команды и канала для prenameНапример:

find -mindepth 1 -maxdepth 1 -name '[[:alnum:]]?*' -printf '%f\n' |
prename -n 's|^[[:alpha:]]|\l$&/$&|; s|^[0-9]|#/$&|'

Это дает дополнительное преимущество: не пытайтесь переместить буквальное имя файла, [[:alnum:]]?*если файлы не соответствуют шаблону glob. findтакже позволяет использовать гораздо больше критериев соответствия, чем сглаживание оболочки. Альтернативой является установка параметра nullglobоболочки и закрытие стандартного потока ввода prename.1

В обоих случаях удалите -n переключатель, чтобы фактически переместить файлы, а не просто показать, как они будут перемещены.

Приложение: Вы можете снова удалить пустые каталоги с помощью:

rmdir --ignore-fail-on-non-empty '#' {a..z}

1 shopt -s nullglob; prename ... <&-

Дэвид Фёрстер
источник
8

Если вы не возражаете против zsh, функция и пара zmvкоманд:

mmv() {echo mkdir -p "${2%/*}/"; echo mv -- "$1" "$2";}
autoload -U zmv
zmv -P mmv '([a-zA-Z])(*.ttf)' '${(UC)1}/$1$2'
zmv -P mmv '([!a-zA-Z])(*.ttf)' '#/$1$2'

mmvФункция делает каталог и перемещает файл. zmvзатем обеспечивает сопоставление с образцом и замену. Сначала перемещаем имена файлов, начиная с алфавита, затем все остальное:

$ zmv -P mmv '([a-zA-Z])(*.ttf)' '${(UC)1}/$1$2'
mkdir -p A/
mv -- abcd.ttf A/abcd.ttf
mkdir -p A/
mv -- ABCD.ttf A/ABCD.ttf
$ zmv -P mmv '([!a-zA-Z])(*.ttf)' '#/$1$2'
mkdir -p #/
mv -- 123.ttf #/123.ttf
mkdir -p #/
mv -- 七.ttf #/七.ttf

Запустите снова без определения echoin mmvдля фактического выполнения перемещения.

Мур
источник
8

У меня не получилось найти хороший способ сделать имена каталогов заглавными (или переместить файлы заглавными буквами), хотя впоследствии вы могли бы сделать это с помощью rename...

mkdir {a..z} \#; for i in {a..z}; do for f in "$i"*; do if [[ -f "$f" ]]; then echo mv -v -- "$f" "$i"; fi; done; done; for g in [![:alpha:]]*; do if [[ -f "$g" ]]; then echo mv -v -- "$g" \#; fi; done

или более наглядно:

mkdir {a..z} \#; 
for i in {a..z}; do 
  for f in "$i"*; do
    if [[ -f "$f" ]]; then 
      echo mv -v -- "$f" "$i"; 
    fi 
  done
done
for g in [![:alpha:]]*; do 
  if [[ -f "$g" ]]; then 
    echo mv -v -- "$g" \#
  fi
done

Удалить echoпосле тестирования, чтобы фактически переместить файлы

А потом

rename -n 'y/[a-z]/[A-Z]/' *

удалите, -nесли он выглядит хорошо после тестирования и запустите снова.

Занна
источник
2
Вы можете использовать, if [[ -d "${i^}" ]]чтобы сделать переменную iкапитала, и mkdir {A..Z}в начале.
Arronical
Позвольте мне попробовать это и посмотреть
Parto
@ Арроник, спасибо! Я оставлю это однако, так как Вы отправили это по-своему
Занна
@Zanna Мне нравится, что мы пришли к этому с разных сторон, перебирая буквы и используя их в качестве критериев поиска, мне никогда не приходило в голову. Я уверен, что есть умное и быстрое решение с помощью find, но я не могу обойти его!
Arronical
Эй, Занна, это не изменило шрифты, начинающиеся с заглавной буквы. В остальном работал нормально.
Парто
7

Следующие команды в каталоге, содержащем шрифты, должны работать, если вы хотите использовать извне каталога хранения шрифтов, измените for f in ./*на for f in /directory/containing/fonts/*. Это очень основанный на оболочке метод, поэтому довольно медленный и нерекурсивный. Это создаст каталоги, только если есть файлы, которые начинаются с соответствующего символа.

target=/directory/to/store/alphabet/dirs
mkdir "$target"
for f in ./* ; do 
  if [[ -f "$f" ]]; then 
    i=${f##*/}
    i=${i:0:1}
    dir=${i^}
    if [[ $dir != [A-Z] ]]; then 
      mkdir -p "${target}/#" && mv "$f" "${target}/#"
    else
      mkdir -p "${target}/$dir" && mv "$f" "${target}/$dir"
    fi
  fi
done

Как один вкладыш, опять же из каталога хранилища шрифтов:

target=/directory/to/store/alphabet/dirs; mkdir "$target" && for f in ./* ; do if [[ -f "$f" ]]; then i=${f##*/}; i=${i:0:1} ; dir=${i^} ; if [[ $dir != [A-Z] ]]; then mkdir -p "${target}/#" && mv "$f" "${target}/#"; else mkdir -p "${target}/$dir" && mv "$f" "${target}/$dir" ; fi ; fi ; done

Метод с использованием find с аналогичной манипуляцией со строками, использующий раскрытие параметров bash, которое будет рекурсивным и должно быть несколько быстрее, чем версия чистой оболочки:

find . -type f -exec bash -c 'target=/directory/to/store/alphabet/dirs ; mkdir -p "$target"; f="{}" ; i="${f##*/}"; i="${i:0:1}"; i=${i^}; if [[ $i = [[:alpha:]] ]]; then mkdir -p "${target}/$i" && mv "$f" "${target}/$i"; else mkdir -p "${target}/#" && mv "$f" "${target}/#"; fi' \;

Или более наглядно:

find . -type f -exec bash -c 'target=/directory/to/store/alphabet/dirs 
   mkdir -p "$target"
   f="{}"
   i="${f##*/}"
   i="${i:0:1}"
   i=${i^}
   if [[ $i = [[:alpha:]] ]]; then 
      mkdir -p "${target}/$i" && mv "$f" "${target}/$i"
   else
      mkdir -p "${target}/#" && mv "$f" "${target}/#"
   fi' \;
Arronical
источник
Этот тоже работал. Даже для прописных и строчных букв.
Парто
5

Сопоставьте каждое имя файла с именем каталога, используя tr, затем mkdirи mv:

find /src/dir -type f -print0 |
xargs -0 -I{} bash -c \
  'dir=/dest/$(basename "{}" | cut -c1 | tr -C "a-zA-Z\n" "#" | tr "a-z "A-Z"); mkdir -p $dir; mv "{}" $dir'
хп.
источник
Мне очень нравится это, это то, что я ухватил за желание найти. Есть ли способ создать только имена каталогов верхнего регистра и переместить в них имена файлов нижнего регистра?
Arronical
1
Я добавил еще один trдля преобразования в верхний регистр.
хн.
Зачем объезд, xargsчтобы просто позвонить bashснова? Разве не было бы проще и намного более читабельно направить вывод findв цикл while и readзаписать его по записи?
Дэвид Фёрстер
Хороший вопрос, @DavidFoerster. Я предполагаю предвзятость к петлям в одной строке и предпочтение более функционального подхода. Но сценарий bash в строке тоже не очень элегантен, поэтому я бы сказал, что whileверсия цикла ( bit.ly/2j2mhyb ), возможно, лучше.
хн.
Кстати, вы можете избежать неприятную {}замену , если вы позволите xargsAppend аргумент , а затем обратитесь к $1внутри скрипта, например: xargs -0 -n1 -- bash -c 'dir=/dest/$(basename "$1" | ...); ...; mv "$1" "$dir"' _. ( _
Дэвид Фёрстер