Чи є команда bash, яка рахує файли?


182

Чи є команда bash, яка підраховує кількість файлів, що відповідають шаблону?

Наприклад, я хочу отримати кількість всіх файлів у каталозі, які відповідають цій схемі: log*

Відповіді:


243

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

Також фінал shopt -u nullglobслід пропустити, якщо nullglobйого не було встановлено, тоді ви почали.
трійка

Примітка. Заміна *.logпростою *буде рахувати каталоги. Якщо файли, які ви хочете перерахувати, мають традиційну угоду про іменування name.extension, використовуйте *.*.
AlainD

52

Тут багато відповідей, але деякі не беруть до уваги

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

Ось рішення, яке стосується всіх:

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

Пояснення:

  • -Uзмушує lsне сортувати записи, тобто не потрібно завантажувати весь список каталогу в пам'ять
  • -bдрукує вивіски у стилі С для неграфічних символів, що вирішально спричиняє друк нових рядків \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більше не потрібно, оскільки він лише перераховує вміст поточного каталогу.


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

FYI, якщо ви просто вийдете, -name '*.log'то він буде рахувати всі файли, що мені потрібно для мого використання. Також прапор -maxdepth надзвичайно корисний, дякую!
starmandeluxe

2
Це все ще дає неправильні результати, якщо в них є імена файлів з новими рядками. Вирішення проблеми легко find; просто надрукуйте щось інше, ніж дословне ім'я файлу.
tripleee

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

Тут знайдуться всі файли, які відповідають журналу * і які не починаються з .*- "не ім'я. для знаходження - це включити їх.

Це правильна відповідь і обробляє будь-який тип імені файлу, який ви можете кинути на нього, оскільки ім'я файлу ніколи не передається між командами.

Але, shopt nullglobвідповідь - найкраща відповідь!


Напевно, вам слід оновити оригінальну відповідь, а не відповідати знову.
qodeninja

Я думаю, що використання findvs використовує lsдва різні способи вирішення проблеми. findне завжди присутній на машині, але lsзазвичай є,
mogsie

2
Але тоді коробка сала, яка, findмабуть, не має всіх цих фантазійних варіантів ls.
трійка

1
Зауважте також, як це поширюється на ціле дерево каталогів, якщо ви -maxdepth 1
виймаєте

1
Зауважте, що це рішення буде рахувати файли всередині прихованих каталогів. findробить це за замовчуванням Це може створити плутанину, якщо хтось не зрозуміє, що є прихована дочірня папка, і може зробити її вигідною lsв деяких випадках, яка не повідомляє про приховані файли за замовчуванням.
MrPotatoHead

6

Ось мій один лайнер для цього.

 file_count=$( shopt -s nullglob ; set -- $directory_to_search_inside/* ; echo $#)

Мені знадобилося трохи погуглити, щоб зрозуміти, але це приємно! Тож set -- нічого не робимо, окрім того, як ми готові до того $#, що зберігається кількість аргументів командного рядка, переданих програмі оболонки
xverges

@xverges Так, "shopt -s nullglob" - це не врахування прихованих файлів (.files). set - призначений для зберігання / встановлення кількості позиційних параметрів (кількість файлів у цьому випадку). і # $ для відображення кількості позиційних параметрів (кількість файлів).
zee

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

ви можете використовувати візерунки на грепі


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.083на Cygwin (на випадок, якщо вони використовуються обережно).
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

2

Я багато роздумував над цією відповіддю, особливо зважаючи на матеріали, які не аналізуються . Спочатку я спробував

<ПОПЕРЕДЖЕННЯ! НЕ РОБОТИ>
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}'

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


Зауважте, я потрапив до цього роздуму, отримуючи цю відповідь .



0
ls -1 log* | wc -l

Що означає перерахувати один файл на рядок, а потім передати його команді count count з переключенням параметрів на лічильник рядків.


Опція "-1" не потрібна під час передачі каналу ls. Але ви можете заховати повідомлення про помилку ls, якщо жоден файл не відповідає шаблону. Я пропоную "ls log * 2> / dev / null | wc -l".
ДжонМудд

Дискусія під відповіддю Даніеля є актуальною і тут. Це добре працює, коли у вас немає відповідних каталогів або імен файлів з новими рядками, але хороша відповідь повинна хоча б вказувати на ці граничні умови, і велика відповідь не повинна мати їх. Багато помилок через те, що хтось копіював / вставляв код, якого вони не розуміли; тому вказівка ​​на недоліки принаймні допомагає їм зрозуміти, на що слідкувати. (Звичайно, багато інших помилок трапляються через те, що вони ігнорували застереження, а потім все змінилося після того, як вони подумали, що код, мабуть, достатньо хороший для свого призначення.)
tripleee

-1

Для підрахунку всього просто ls до рядка підрахунку слів:

ls | wc -l

Щоб порахувати з малюнком, спочатку слід обклеїти:

ls | grep log | wc -l
Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.