У меня много каталогов; у многих из них есть только 1 файл или 2 в них.

Я хочу скопировать каталоги, содержащие более 2 файлов, в другой каталог, как я могу проверить это и переместить каталоги?

Я зашел так далеко со сценарием, но я не уверен в этом.

#!/bin/bash

mkdir "subset"
for dir in *; do
        #if the file is a directory
        if [ -d "$dir" ]; then
            #count number of files
            count=$(find "$dir" -type f | wc -l)
            #if count>=2 then move
            if [ "$count" -ge 2 ]; then
                #move dir
                #   ...
            fi
        fi
done

Команда mv $dir .. перемещает каталог на единицу вверх, но возможно ли переместиться на единицу вверх и вниз в subset без использования полного пути mv $dir complete_path/subset?

1
Pleasant94 3 Май 2019 в 16:22

3 ответа

Лучший ответ

Есть много ловушек и ловушек, если вы хотите обрабатывать произвольные имена каталогов и их содержимое. Этот Shellcheck -чистый код пытается избежать всех из них:

#! /bin/bash -p

shopt -s nullglob   # glob patterns that match nothing expand to nothing
shopt -s dotglob    # glob patterns expand names that start with '.'

destdir='subset'

[[ -d $destdir ]] || mkdir -- "$destdir"

for dir in * ; do
    [[ -L $dir ]] && continue               # Skip symbolic links
    [[ -d $dir ]] || continue               # Skip non-directories
    [[ $dir -ef $destdir ]] && continue     # Skip the destination dir.

    numfiles=$(find "./$dir//." -type f -print | grep -c //)
    (( numfiles > 2 )) && mv -v -- "$dir" "$destdir"
done
  • shopt -s nullglob означает, что код будет работать, если он выполняется в пустом каталоге. (В противном случае он попытается обработать поддельную запись в каталоге с именем '*'.)
  • shopt -s dotglob позволяет коду обрабатывать каталоги, имена которых начинаются с '.' (например, .mydir).
  • Позже вы можете избежать проверки каталога в коде, изменив защиту цикла на for dir in */ ..., но это немного усложнит проверку на наличие символических ссылок.
  • Код предполагает, что вы не хотите перемещать символические ссылки в каталоги, содержащие более двух файлов ([[ -L $dir ]] && continue). Уберите строку, если это предположение неверно.
  • Подсчет количества файлов в каталоге довольно сложен, поскольку в именах файлов могут быть символы новой строки, а это означает, что find ... | wc -l может работать неправильно. См. Как узнать количество файлов в каталоге с помощью командной строки?.
    Свернутый Первый аргумент find ("./$dir//.") предназначен для того, чтобы избежать нескольких ловушек. Кавычки запрещают использование специальных символов в именах каталогов, что вызывает проблемы. Префикс ./ позволяет избежать использования аргумента как опции, если имя каталога начинается с -. Суффикс //. означает, что в строке будет ровно один символ «//» для каждого файла, найденного find, поэтому grep -c // точно подсчитает количество файлов.
  • Аргумент -- для команд mkdir и mv должен гарантировать, что они работают правильно, если $dir или $destdir начинаются с - (который приведет к тому, что они будут рассматриваться как варианты). См. Подводные камни Bash # 2 (cp $ file $ target).
4
pjh 3 Май 2019 в 19:40

Это лучшее решение, которое я дошел до сих пор.

Я хотел знать, есть ли более элегантное решение:

#!/bin/bash

mkdir "OOOO3_LESS_THAN_TWO"

for dir in *; do
        #if the file is a directory
        if [ -d "$dir" ]; then
            #count number of files
            count=$(find "$dir" -type f | wc -l)
            #if count<=2 then move
            if [ "$count" -le 2 ]; then
                #move dir
                mv -v "$dir" /completepath/"OOOO3_LESS_THAN_TWO"
            fi
        fi
done
0
Pleasant94 3 Май 2019 в 18:38

Ваш общий подход в порядке. К сожалению, нет лучшего способа подсчета количества файлов в каталоге.

Вы можете избавиться от проверки -d, выполнив цикл */. Конечный / означает, что будут совпадать только каталоги.

Вы можете объединить задание и проверить. Вам может понравиться или может показаться странным.

Вы также можете использовать ((...)) для арифметики. Лично я нахожу это немного более читабельным.

for dir in */; do
    if count=$(find "$dir" -type f | wc -l) && ((count >= 2)); then
        ...
    fi
done

Или вы можете полностью исключить переменную count.

for dir in */; do
    if (($(find "$dir" -type f | wc -l) >= 2)); then
        ...
    fi
done

Все это можно сделать одним вызовом find, но теперь мы определенно находимся на территории arcane .

find . -type d \
    -exec bash -c '(($(compgen -G "$1/*" | wc -l) > 2))' bash {} \; \
    -exec mv {} subset/ \;

Команда mv $dir .. перемещает каталог на единицу вверх, но возможно ли переместиться на единицу вверх и вниз в subset без использования полного пути mv $dir complete_path/subset?

Вы не изменили каталоги, поэтому используйте любой относительный путь, который вам нравится. Если я понимаю вопрос, это может быть так же просто, как mv <dir> subset/.

0
John Kugelman 3 Май 2019 в 18:53