Есть ли более лаконичная альтернатива трубопроводам в wc для подсчета файлов в каталоге

12

Если я это сделаю ls -1 target_dir | wc -l, я получу количество файлов в каталоге. Я нахожу это немного громоздким. Есть ли более элегантный или лаконичный способ?

codecowboy
источник
2
Вам не нужно "-1" при подключении к туалету.
Стив
lsуже дает общее количество, так как насчет ls -l | head -1? Сделайте псевдоним, если вы хотите что-то короче.
Даниэль Вагнер
2
@DanielWagner Вывод «total: nnn» ls -lуказывает на общий размер файлов, а не на количество файлов.
Дэвид Ричерби
2
Имейте в виду, что ls | wc -lэто даст вам неправильный счет, если какие-либо имена файлов содержат символы новой строки.
chepner
Это зависит от файловой системы, и количество каталогов + 2 в каталоге. Ответ имеет 2 лишних (как считает себя, так и своего родителя). stat -c %h .дает ту же информацию, что иls -ld . | cut -d" " -f 2
ctrl-alt-delor

Ответы:

12

Предполагая bash 4+ (который есть в любой поддерживаемой версии Ubuntu):

num_files() (
    shopt -s nullglob
    cd -P -- "${1-.}" || return
    set -- *
    echo "$#"
)

Назовите это как num_files [dir]. dirне является обязательным, в противном случае он использует текущий каталог. Ваша оригинальная версия не учитывает скрытые файлы, так же как и это. Если ты этого хочешь, shopt -s dotglobраньше set -- *.

Ваш исходный пример содержит не только обычные файлы, но также каталоги и другие устройства - если вы действительно хотите только обычные файлы (включая символические ссылки на обычные файлы), вам необходимо проверить их:

num_files() (
    local count=0

    shopt -s nullglob
    cd -P -- "${1-.}" || return
    for file in *; do
        [[ -f $file ]] && let count++
    done
    echo "$count"
)

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

num_files() {
    find "${1-.}" -maxdepth 1 -type f -printf x | wc -c
}

(измените -typeна, -xtypeесли вы также хотите считать символические ссылки на обычные файлы).

Крис Даун
источник
Не setвыйдет ли из строя, если файлов очень много? Я думаю, что вам, возможно, придется использовать xargsи некоторый суммирующий код, чтобы это работало в общем случае.
10
1
Также, shopt -s dotglobесли вы хотите, чтобы файлы, начинающиеся с, .были подсчитаны
Digital Trauma
1
@ l0b0 Не думаю, setчто при таких обстоятельствах ничего не получится, так как мы на самом деле не делаем exec. То есть, в моей системе getconf ARG_MAXвыдает 262144, но если я это сделаю test_arg_max() { set -- {1..262145}; echo $#; }; test_arg_max, он с радостью отвечает 262145.
Кодзиро
@DavidRicherby -maxdepth- это не POSIX.
Крис Даун
4
@MichaelMartinez Написание очевидного кода не заменяет написание правильного кода.
Крис Даун
3

f=(target_dir/*);echo ${#f[*]}

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

Аарон Дэвис
источник
Можете ли вы предоставить некоторый контекст? Должно ли это быть в скрипте bash?
codecowboy
это могло бы. Вы также можете положить его прямо в оболочку. эта версия предполагала, что вам нужен текущий каталог; Я отредактировал это так, чтобы это было ближе к вашему вопросу. в основном он создает переменную массива оболочки, содержащую все файлы в каталоге, а затем печатает счетчик этого массива. должен работать в любой оболочке с массивами - bash, ksh, zsh и т. д. - но, вероятно, не простой sh / ash / dash.
Аарон Дэвис
2

lsявляется многостолбцовым, только если он выводится непосредственно на терминал, вы можете удалить опцию «-1», вы можете убрать wcопцию «-l», прочитать только первое значение (ленивое решение, не будет использоваться для юридических доказательств, уголовные расследования, критические задачи, тактические операции ..).

ls target | wc 
Эммануэль
источник
5
Этот сбой для имен файлов, содержащих символы новой строки.
2014 года
@ Emmanuel Вам нужно проанализировать результат, wcчтобы получить количество файлов даже в тривиальном случае, так как же это решение?
10
@Emmanuel Это может потерпеть неудачу, если targetэто глоб, который при расширении включает в себя некоторые вещи, которые начинаются с дефисов. Например, создайте новый каталог, зайдите в него и сделайте touch -- {1,2,3,-a}.txt && ls *|wc(NB: используйте, rm -- *.txtчтобы удалить эти файлы.)
Дэвид Ричерби,
Вы имели в виду wc -l? В противном случае вы получите новые строки, слова и количество байтов lsвывода. Вот что сказал Дэвид Ричерби: «Вы должны разобрать это снова.
Эрик
@erik У меня wcнет аргументов, вам не нужно анализировать, если ваш мозг знает, что первым аргументом является перевод строки.
Эммануэль
2

Если это лаконичность вы после (а не точная правильности при работе с файлами с новой строкой в их именах, и т.д.), я рекомендую просто искажения wc -lв lc( «число строк»):

$ alias lc='wc -l'
$ ls target_dir|lc

Как уже отмечали другие, вам не нужна эта -1опция ls, поскольку она автоматически lsзаписывает данные в канал. (Если у вас нет lsпсевдонима, чтобы всегда использовать режим столбца. Я видел это раньше, но не очень часто.)

В lcобщем, псевдоним довольно удобен, и для этого вопроса, если вы посмотрите на случай «посчитать текущий каталог», он ls|lcбудет как можно более кратким.

Аарон Дэвис
источник
2

Пока что Аарон - единственный подход, более краткий, чем ваш. Более правильная версия вашего подхода может выглядеть так:

ls -aR1q | grep -Ecv '^\./|/$|^$'

Это рекурсивно перечисляет все файлы - не каталоги - по одному на строку, включая файлы .dotfile под текущим каталогом, используя при необходимости глобусы оболочки для замены непечатаемых символов. grep отфильтровывает любые списки родительских каталогов или .. или * / или пустые строки - поэтому в файле должна быть только одна строка - общее количество, которое возвращает вам grep. Если вы хотите включить дочерние каталоги, сделайте:

ls -aR1q | grep -Ecv '^\.{1,2}/|^$'

Удалите -Rв любом случае, если вы не хотите рекурсивных результатов.

mikeserv
источник
1
Я предпочитаю делать подобные вещи с find. Если вам нужен только счет, это должно сработать: find -mindepth 1 -maxdepth 1 -printf '\n'|wc -l(уберите регуляторы глубины, чтобы получить рекурсивные результаты).
Аарон Дэвис
@AaronDavies - это на самом деле не работает. Поместите новую строку в любое из этих имен файлов и убедитесь сами. Кроме того, чтобы сделать то же самое переносимо, вы делаете: find . \! -name . -prune | wc -l- что, конечно, все еще не работает.
mikeserv
1
Я не следую - printfинструкция печатает постоянную строку (новую строку), которая вообще не включает имя файла, поэтому результаты не зависят от каких-либо странных имен файлов. Этот трюк не может быть сделан вообще с, findкоторый не поддерживает printf, конечно.
Аарон Дэвис
@AaronDavies - о, правда. Я предположил, что имя файла было включено. Это можно сделать переносно, хотя, конечно:find .//. \!. -name . -prune | grep -c '^\.//\.'
mikeserv
блестящий! /Будучи единственным другим символом, который не может появиться в именах файлов, .//.последовательность гарантированно появится ровно один раз для каждого файла, верно? пара вопросов, хотя - почему .//.и почему -prune? когда это будет отличаться от find . \! -name . | grep -c '^\.'? (Я полагаю, что это .ваша \!.опечатка.)
Аарон Дэвис