Чи є більш лаконічна альтернатива трубопроводу до wc для підрахунку файлів у каталозі


12

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


2
Вам не знадобиться "-1" під час передачі на туалет.
Стів

lsвже дає загальний підрахунок, а як же ls -l | head -1? Зробіть це псевдонімом, якщо хочете чогось коротшого.
Даніель Вагнер

2
@DanielWagner Вихід "total: nnn" ls -lпозначає загальний розмір файлів, а не кількість файлів.
Девід Річербі

2
Майте на увазі, що ls | wc -lви отримаєте неправильний підрахунок, якщо будь-які імена файлів містять нові рядки.
Чепнер

Це залежить від файлової системи та рахує каталоги + 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, щось подібне - це також варіант (зауважте, що це включає приховані файли, яких ваша оригінальна команда не робила):

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

(Зміна -typeдо , -xtypeякщо ви також хочете вважати символічні посилання на звичайні файли).


Не вийде з setладу, якщо файлів дуже багато? Я думаю, що вам, можливо, доведеться використовувати xargsі деякий підсумовуючий код, щоб зробити цю роботу в загальному випадку.
l0b0

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.
kojiro

@DavidRicherby -maxdepth- це не POSIX.
Кріс Даун

4
@MichaelMartinez Написання очевидного коду не є заміною для написання правильного коду.
Кріс Даун

3

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

працює правильно для файлу з пробілами, новими рядками тощо в імені.


чи можете ви надати певний контекст? Чи повинно це йти за баш-сценарієм?
codecowboy

це може. ви також можете помістити його безпосередньо в оболонку. ця версія передбачала, що ви хочете поточного каталогу; я відредагував це, щоб він ближче до вашого питання. в основному він створює змінну масиву оболонок, що містить усі файли в каталозі, після чого друкує кількість цього масиву. повинен працювати в будь-якій оболонці з масивами - bash, ksh, zsh тощо. - але, ймовірно, не просто sh / ash / dash.
Аарон Девіс

2

lsє мульти стовпцями, лише якщо він виводиться безпосередньо на термінал, ви можете видалити параметр "-1", ви можете видалити wcваріант "-l", прочитати тільки перше значення (ліниве рішення, не використовувати для легальних доказів, кримінальне розслідування, критична місія, тактичні операції ..).

ls target | wc 

5
Ця помилка для імен файлів, що містять нові рядки.
l0b0

@Emmanuel Вам потрібно буде проаналізувати результат свого, wcщоб отримати кількість файлів навіть у тривіальному випадку, то як це навіть рішення?
l0b0

@Emmanuel Це може не вдатися, якщо targetце глобус, який при розширенні включає деякі речі, що починаються з дефісів. Наприклад, зробіть новий каталог, увійдіть у нього і зробіть touch -- {1,2,3,-a}.txt && ls *|wc(NB: використовуйте rm -- *.txtдля видалення цих файлів.)
Девід Річербі

Ви мали на увазі wc -l? В іншому випадку ви отримаєте підрахунок lsвиводу рядка, слова та байтів . Ось що сказав Девід Ріхербі: Вам доведеться проаналізувати це ще раз.
Ерік

@erik I ment 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 '^\./|/$|^$'

Це рекурсивно перераховує всі файли - не каталоги - по одному на рядок, включаючи .dotfiles під поточним каталогом, використовуючи глобули оболонки, як потрібно для заміни символів, що не друкуються. grep фільтрує будь-які списки батьківських каталогів або .. або * / або порожні рядки - значить, у файлі має бути лише один рядок - загальна кількість яких grep повертається до вас. Якщо ви хочете, щоб і дочірні каталоги були включені:

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

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


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 '^\.'? (я припускаю, що .у вас \!.є помилка.)
Аарон Девіс
Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.