Есть ли команда bash, которая считает файлы?

183

Есть ли команда bash, которая подсчитывает количество файлов, которые соответствуют шаблону?

Например, я хочу получить количество всех файлов в каталоге, которые соответствуют этому шаблону: log*

Hudi
источник

Ответы:

244

Этот простой однострочный текст должен работать в любой оболочке, а не только в bash:

ls -1q log* | wc -l

ls -1q выдаст вам по одной строке на файл, даже если они содержат пробелы или специальные символы, такие как символы новой строки.

Выходные данные передаются в wc -l, который считает количество строк.

Даниил
источник
10
Я бы не стал использовать -l, поскольку это требует stat(2)для каждого файла и для целей подсчета ничего не добавляет.
Camh
12
Я бы не использовал ls, так как он создает дочерний процесс. log*расширяется оболочкой, нет ls, так что простой echoбудет делать.
cdarke
2
Кроме того, эхо не будет работать, если у вас есть имена файлов с пробелами или специальными символами.
Даниэль
4
@WalterTross Это правда (не то, чтобы эффективность была требованием исходного вопроса). Я также только что обнаружил, что -q заботится о файлах с символами новой строки, даже когда вывод не является терминалом. И эти флаги поддерживаются всеми платформами и оболочками, на которых я тестировал. Обновление ответа, спасибо вам и camh за вклад!
Даниэль
3
Если в рассматриваемом каталоге есть каталог logs, то будет учитываться и содержимое этого каталога журналов. Это, вероятно, не намеренно.
mogsie
54

Вы можете сделать это безопасно (то есть не будете ошибаться файлами с пробелами или \nих именами) с помощью bash:

$ shopt -s nullglob
$ logfiles=(*.log)
$ echo ${#logfiles[@]}

Вам нужно включить, nullglobчтобы вы не получили литерал *.logв $logfiles массиве, если не найдено ни одного файла. (См. Как «отменить» set -x для примеров того, как безопасно сбросить его.)

Мат
источник
2
Возможно, прямо указать, что это ответ только на Bash , особенно для новых посетителей, которые еще не совсем
разбираются
Кроме того, финал shopt -u nullglobдолжен быть пропущен, если nullglobон не был сброшен, тогда вы начали.
tripleee
Примечание: замена *.logтолько *будет считать каталоги. Если файлы, которые вы хотите перечислить, имеют традиционное соглашение об именах name.extension, используйте *.*.
AlainD
52

Здесь много ответов, но некоторые не принимают во внимание

  • имена файлов с пробелами, символами новой строки или управляющими символами в них
  • имена файлов, начинающиеся с дефисов (представьте файл с именем -l)
  • скрытые файлы, которые начинаются с точки (если *.logвместоlog*
  • каталоги, которые соответствуют глобу (например, каталог, logsкоторый соответствует log*)
  • пустые каталоги (т.е. результат равен 0)
  • очень большие каталоги (перечисление их всех может исчерпать память)

Вот решение, которое обрабатывает все из них:

ls 2>/dev/null -Ubad1 -- log* | wc -l

Объяснение:

  • -Uвызывает lsне сортировать записи, то есть не нужно загружать весь список каталогов в память
  • -bпечатает экранирование в стиле C для неграфических символов, что принципиально приводит к тому, что переводы строк печатаются как \n.
  • -aраспечатывает все файлы, даже скрытые файлы (не требуется строго, когда глобус не log*подразумевает скрытых файлов)
  • -dраспечатывает каталоги, не пытаясь составить список содержимого каталога, что lsобычно делается
  • -1 удостоверяется, что он находится в одном столбце (ls делает это автоматически при записи в канал, поэтому это не является строго обязательным)
  • 2>/dev/nullперенаправляет stderr, чтобы при наличии 0 файлов журнала игнорировать сообщение об ошибке. (Обратите внимание, что вместо этого shopt -s nullglobможет lsбыть указан весь рабочий каталог.)
  • wc -lиспользует список каталогов по мере его создания, поэтому вывод lsникогда не находится в памяти в любой момент времени.
  • --Имена файлов отделяются от команды, используя их --так, чтобы их нельзя было понимать как аргументы ls(в случае log*удаления)

Оболочка будет расширяться log*в полный список файлов, которые могут исчерпать память , если много файлов, поэтому затем запустить его через Grep это лучше:

ls -Uba1 | grep ^log | wc -l

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

mogsie
источник
48

Для рекурсивного поиска:

find . -type f -name '*.log' -printf x | wc -c

wc -cбудет подсчитывать количество символов в выводе find, а -printf xподскажет findраспечататьx для каждого результата.

Для нерекурсивного поиска сделайте это:

find . -maxdepth 1 -type f -name '*.log' -printf x | wc -c
Уилл Вусден
источник
6
Даже если у вас нет файлов с пробелами, другой пользователь вашего скрипта может столкнуться со злонамеренно названным файлом, что приведет к сбою скриптов. Кроме того, другие люди, сталкивающиеся с этим в StackOverflow, могут иметь файлы с символами новой строки, и им необходимо знать подводные камни.
mogsie
К вашему сведению, если вы просто пропустите, -name '*.log'он будет считать все файлы, что мне и понадобилось для моего варианта использования. Также флаг -maxdepth чрезвычайно полезен, спасибо!
starmandeluxe
2
Это все еще дает неправильные результаты, если есть имена файлов с символами новой строки в них. Обойти это легко find; просто напечатайте что-то еще, кроме имени файла.
Трипли
8

Принятый ответ на этот вопрос неправильный, но у меня низкая репутация, поэтому я не могу добавить к нему комментарий.

Правильный ответ на этот вопрос дает Мат:

shopt -s nullglob
logfiles=(*.log)
echo ${#logfiles[@]}

Проблема с принятым ответом состоит в том, что wc -l считает количество символов новой строки и считает их, даже если они выводят на терминал как «?» в выводе 'ls -l'. Это означает, что принятый ответ НЕУДАЧЕТ, когда имя файла содержит символ новой строки. Я проверил предложенную команду:

ls -l log* | wc -l

и он ошибочно сообщает значение 2, даже если существует только 1 файл, соответствующий шаблону, имя которого содержит символ новой строки. Например:

touch log$'\n'def
ls log* -l | wc -l
Дэн Ярд
источник
6

Если у вас много файлов и вы не хотите использовать элегантное shopt -s nullglobрешение и массив bash, вы можете использовать find и т. Д., Если вы не распечатываете имя файла (которое может содержать символы новой строки).

find -maxdepth 1 -name "log*" -not -name ".*" -printf '%i\n' | wc -l

Это найдет все файлы, которые соответствуют log * и не начинаются с .* - «not name. *» Избыточно, но важно отметить, что по умолчанию для «ls» не показываются точечные файлы, но по умолчанию для поиска, чтобы включить их.

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

Но shopt nullglobответ - лучший ответ!

mogsie
источник
Вам, вероятно, следует обновить исходный ответ, а не отвечать снова.
qodeninja
Я думаю, что использование findпротив использования lsдвух разных способов решения проблемы. findне всегда присутствует на машине, но lsобычно есть,
mogsie
2
Но тогда коробка сала, у которой нет, findвероятно, не имеет всех этих причудливых вариантов lsни для одного.
Трипли
1
Обратите также внимание на то, как это распространяется на все дерево каталогов, если вы возьмете-maxdepth 1
tripleee
1
Обратите внимание, что это решение будет подсчитывать файлы внутри скрытых каталогов. findделает это по умолчанию. Это может создать путаницу, если вы не поймете, что есть скрытая дочерняя папка, и может сделать его выгодным lsв некоторых случаях, когда по умолчанию не отображаются скрытые файлы.
MrPotatoHead
6

Вот мой единственный вкладыш для этого.

 file_count=$( shopt -s nullglob ; set -- $directory_to_search_inside/* ; echo $#)
зет
источник
Мне потребовалось некоторое время, чтобы понять, но это приятно! Так set -- что ничего не делает, кроме как подготовить нас к тому $#, что хранит количество аргументов командной строки, которые были переданы программе оболочки
xverges
@xverges Да, «shopt -s nullglob» не учитывает скрытые файлы (.files). set - для хранения / установки количества позиционных параметров (в данном случае, num файлов). и # $ для отображения количества позиционных параметров (количество файлов).
Зи
3

Вы можете использовать опцию -R, чтобы найти файлы вместе с файлами внутри рекурсивных каталогов.

ls -R | wc -l // to find all the files

ls -R | grep log | wc -l // to find the files which contains the word log

Вы можете использовать шаблоны на grep

Мох. С
источник
3

Важный комментарий

(недостаточно репутации, чтобы комментировать)

Это БАГГИ :

ls -1q some_pattern | wc -l

Если shopt -s nullglobон установлен, он печатает количество ВСЕХ обычных файлов, а не только файлы с шаблоном (протестировано на CentOS-8 и Cygwin). Кто знает, что имеют другие бессмысленные ошибки ls?

Это ПРАВИЛЬНО и намного быстрее:

shopt -s nullglob; files=(some_pattern); echo ${#files[@]};

Это делает ожидаемую работу.


И время работы отличается.
1-й: 0.006на CentOS и 0.083Cygwin (если он используется с осторожностью).
2-й: 0.000на CentOS и 0.003на Cygwin.

Маленький мальчик
источник
2

Вы можете легко определить такую ​​команду, используя функцию оболочки. Этот метод не требует никакой внешней программы и не порождает дочерний процесс. Он не пытается выполнить опасный lsанализ и обрабатывает «специальные» символы (пробелы, переводы строки, обратные слэши и т. Д.) Просто отлично. Он опирается только на механизм расширения имени файла, предоставляемый оболочкой. Он совместим как минимум с sh, bash и zsh.

Строка ниже определяет вызываемую функцию, countкоторая печатает количество аргументов, с которыми она была вызвана.

count() { echo $#; }

Просто назовите его с нужным рисунком:

count log*

Чтобы результат был корректным, когда шаблон глобализации не соответствует, опция оболочки nullglob(или failglob- это поведение по умолчанию в zsh) должна быть установлена ​​во время расширения. Это может быть установлено так:

shopt -s nullglob    # for sh / bash
setopt nullglob      # for zsh

В зависимости от того, что вы хотите посчитать, вас также может заинтересовать опция оболочки dotglob.

К сожалению, по крайней мере, с bash, нелегко установить эти параметры локально. Если вы не хотите устанавливать их глобально, самое простое решение - использовать функцию более замысловато:

( shopt -s nullglob ; shopt -u failglob ; count log* )

Если вы хотите восстановить легкий синтаксис count log* или действительно хотите избежать порождения подоболочки, вы можете взломать что-то вроде:

# sh / bash:
# the alias is expanded before the globbing pattern, so we
# can set required options before the globbing gets expanded,
# and restore them afterwards.
count() {
    eval "$_count_saved_shopts"
    unset _count_saved_shopts
    echo $#
}
alias count='
    _count_saved_shopts="$(shopt -p nullglob failglob)"
    shopt -s nullglob
    shopt -u failglob
    count'

В качестве бонуса эта функция имеет более общее использование. Например:

count a* b*          # count files which match either a* or b*
count $(jobs -ps)    # count stopped jobs (sh / bash)

Превратив функцию в файл сценария (или эквивалентную программу на C), вызываемую из PATH, ее также можно составить из таких программ, как findи xargs:

find "$FIND_OPTIONS" -exec count {} \+    # count results of a search
Maëlan
источник
2

Я много думал об этом ответе, особенно учитывая материал не-parse-ls . Сначала я попробовал

<ВНИМАНИЕ! НЕ РАБОТАЕТ>
du --inodes --files0-from=<(find . -maxdepth 1 -type f -print0) | awk '{sum+=int($1)}END{print sum}'
</ ВНИМАНИЕ! НЕ РАБОТАЕТ>

который работал, если было только имя файла, как

touch $'w\nlf.aa'

но не удалось, если я сделал имя файла, как это

touch $'firstline\n3 and some other\n1\n2\texciting\n86stuff.jpg'

Я наконец-то придумал, что я ставлю ниже. Обратите внимание, что я пытался получить количество всех файлов в каталоге (не включая какие-либо подкаталоги). Я думаю, что наряду с ответами @Mat и @Dan_Yard, а также наличием по крайней мере большинства требований, изложенных @mogsie (я не уверен насчет памяти.) Я думаю, что ответ @mogsie правильный, но я всегда стараюсь держаться подальше от разбора, lsесли это не очень специфическая ситуация.

awk -F"\0" '{print NF-1}' < <(find . -maxdepth 1 -type f -print0) | awk '{sum+=$1}END{print sum}'

Более читабельно:

awk -F"\0" '{print NF-1}' < \
  <(find . -maxdepth 1 -type f -print0) | \
    awk '{sum+=$1}END{print sum}'

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

Чтобы ответить на вопрос ФП, необходимо рассмотреть два случая.

1) нерекурсивный поиск:

awk -F"\0" '{print NF-1}' < \
  <(find . -maxdepth 1 -type f -name "log*" -print0) | \
    awk '{sum+=$1}END{print sum}'

2) Рекурсивный поиск. Обратите внимание, что то, что находится внутри -nameпараметра, может потребоваться изменить для немного другого поведения (скрытые файлы и т. Д.).

awk -F"\0" '{print NF-1}' < \
  <(find . -type f -name "log*" -print0) | \
    awk '{sum+=$1}END{print sum}'

Если кто-то хотел бы прокомментировать, как эти ответы сравниваются с теми, которые я упомянул в этом ответе, сделайте это.


Обратите внимание, я получил этот мыслительный процесс, получая этот ответ .

bballdave025
источник
1

Вот что я всегда делаю:

ls log * | awk 'END {print NR}'

Шуан Лян
источник
awk 'END{print NR}'должны быть эквивалентны wc -l.
Musiphil
0
ls -1 log* | wc -l

Это означает, что нужно перечислить один файл в строке, а затем передать его команде word count с переключением параметров на счетчик строк.

nudzo
источник
Опция «-1» не обязательна, когда передается вывод ls. Но вы можете скрыть сообщение об ошибке ls, если файл не соответствует шаблону. Я предлагаю "ls log * 2> / dev / null | wc -l".
JohnMudd
Дискуссия под ответом Даниила и здесь актуальна. Это прекрасно работает, когда у вас нет соответствующих каталогов или имен файлов с символами новой строки, но хороший ответ должен по крайней мере указать эти граничные условия, а хороший ответ не должен иметь их. Многие ошибки связаны с тем, что кто-то копирует / вставляет код, который он не понимает; поэтому указание на недостатки, по крайней мере, помогает им понять, на что следует обратить внимание. (Конечно, много ошибок происходит потому, что они игнорировали предостережения, а затем все изменилось после того, как они посчитали, что код, вероятно, достаточно хорош для своих целей.)
tripleee
-1

Чтобы посчитать все, просто отправьте ls в строку подсчета слов:

ls | wc -l

Чтобы сосчитать с шаблоном, сначала нужно выполнить поиск по трубе

ls | grep log | wc -l
jturi
источник