Як повідомити про кількість файлів у всіх підкаталогах?


24

Мені потрібно перевірити всі підкаталоги та повідомити, скільки файлів (без подальшої рекурсії) вони містять:

directoryName1 numberOfFiles
directoryName2 numberOfFiles

Чому ви хочете використовувати, findколи Bash зробить це? (shopt -s dotglob; for dir in */; do all=("$dir"/*); echo "$dir: ${#all[@]}"; done): для всіх каталогів підраховуйте кількість записів у цьому каталозі (включаючи приховані точкові файли, виключаючи .та ..)
janmoesen

@janmoesen Чому ти не відповів на це? Я новачок в сценарії оболонок, але я не бачу жодних проблем з вашим методом. Мені це виглядає як найкращий спосіб. Ніхто не підтримував ваш коментар, але ніхто не коментував, чому це може бути погано. Отримані відповіді мають набагато більше відповідей, ніж ви, тому я змушує мене замислитись, чи я щось пропускаю.
токсалот

@toxalot: Я не намагався додавати це як відповідь, оскільки він був таким коротким (і, можливо, трохи поблажливим у тонусі). Не соромтеся прийняти коментар. :-) Також питання дещо розпливчасте щодо того, що означає "скільки файлів". Моє рішення нараховує "звичайні" файли та каталоги; можливо, плакат справді означав "файли, а не каталоги". Ще одна річ, яку слід пам’ятати, - це те, що цей глобус не враховує «прихованих» точкових файлів. Хоча існують способи навколо обох цих гатчів. Але ще раз: не впевнений у точних вимогах оригінального афіші.
janmoesen

Відповіді:


30

Це робить це безпечним і портативним способом. Це не заплутається дивними іменами.

for f in *; do [ -d ./"$f" ] && find ./"$f" -maxdepth 1 -exec echo \; | wc -l && echo $f; done

Зверніть увагу, що спочатку буде надруковано кількість файлів, а потім ім'я каталогу в окремому рядку. Якщо ви хочете зберегти формат ОП, вам знадобиться подальше форматування, наприклад

for f in *; do [ -d ./"$f" ] && find ./"$f" -maxdepth 1 -exec echo \;|wc -l|tr '\n' ' ' && echo $f; done|awk '{print $2"\t"$1}'

Якщо у вас є певний набір цікавих підкаталогів, ви можете замінити *їх.

Чому це безпечно? (і тому сценарій гідний)

Імена файлів можуть містити будь-який символ, крім /. Є кілька символів, які спеціально обробляються або оболонкою, або командами. До них відносяться пробіли, нові рядки та тире.

Використання for f in *конструкції - це безпечний спосіб отримання кожного імені файлу, незалежно від того, що він містить.

Коли у вас є ім'я файлу в змінній, вам все одно доведеться уникати таких речей find $f. Якщо воно $fмістило б ім'я файлу -test, findскаржиться на варіант, який ви йому щойно надали. Спосіб уникнути цього - використання ./перед іменем; таким чином він має те саме значення, але він більше не починається з тире.

Нові рядки та пробіли також є проблемою. Якщо $fміститься "привіт, приятель" як ім'я файлу find ./$f, є find ./hello, buddy. Ти кажеш findподивитися ./hello,і buddy. Якщо їх не існує, він скаржиться, і він ніколи не загляне ./hello, buddy. Це легко уникнути - використовуйте лапки навколо змінних.

Нарешті, імена файлів можуть містити нові рядки, тому підрахунок нових рядків у списку імен не буде працювати; ви отримаєте додатковий підрахунок для кожного імені файлу з новим рядком. Щоб уникнути цього, не рахуйте нові рядки у списку файлів; натомість порахуйте нові рядки (або будь-який інший символ), що представляє один файл. Ось чому findкоманда просто -exec echo \;і ні -exec echo {} \;. Я хочу надрукувати лише один новий рядок з метою підбору файлів.


1
Чому в світі є людина, яка використовує нові рядки у назві файлів? Дякую за відповідь.
ShyBoy

1
Імена файлів можуть містити будь-які символи, окрім / та нульового символу, я вважаю. dwheeler.com/essays/fixing-unix-linux-filenames.html
Flimm

2
У число буде включений сам каталог. Якщо ви хочете виключити це з підрахунку, використовуйте-mindepth 1
toxalot

Ви також можете використовувати -printf '\n'замість -exec echo.
токсалот

1
@toxalot ви можете, якщо у вас є пошук, який підтримує -printf, але не, якщо ви хочете, щоб він працював на FreeBSD, наприклад.
Шон Дж. Гофф

6

Якщо припустити, що ви шукаєте стандартне рішення для Linux, то це досить просто find:

find dir1/ dir2/ -maxdepth 1 -type f | wc -l

Там, де findпроходять два зазначені підкаталоги, до -maxdepthчисла 1, що запобігає подальшій рекурсії, і лише повідомляє файли ( -type f), розділені новими рядками. Потім результат передається wcдля підрахунку кількості цих рядків.


У мене більше 2 dirs ... Як я можу поєднати вашу команду з find . -maxdepth 1 -type dрезультатом?
ShyBoy

Ви можете або (a) включити потрібні каталоги у змінну, find $dirs ...або, (b) якщо вони є виключно в одному каталозі вищого рівня, глобусі з цього каталогу,find */ ...
jasonwryan

1
Це повідомить про неправильні результати, якщо будь-яке ім’я файлу має символ нового рядка.
Шон Дж. Гофф

@Shawn: спасибі Я думав, що у мене є назви файлів із пробілами, але я не розглядав нові рядки: якісь пропозиції щодо виправлення?
jasonwryan

Додайте -exec echoдо команди пошуку - таким чином вона не буде перегукуватися з іменем файлу, а лише з новим рядком.
Шон Дж. Гофф

5

Під "без рекурсії" ви маєте на увазі, що якщо directoryName1є підкаталоги, то ви не хочете рахувати файли в підкаталогах? Якщо так, ось спосіб підрахунку всіх регулярних файлів у зазначених каталогах:

count=0
for d in directoryName1 directoryName2; do
  for f in "$d"/* "$d"/.[!.]* "$d"/..?*; do
    if [ -f "$f" ]; then count=$((count+1)); fi
  done
done

Зауважте, що -fтест виконує дві функції: він перевіряє, чи є запис, узгоджений одним із глобусів вище, звичайним файлом, і перевіряє, чи була відповідність запису (якщо один із глобусів нічого не відповідає, шаблон залишається таким, як є¹). Якщо ви хочете порахувати всі записи в даних каталогах незалежно від їх типу, замініть -fна -e.

У Ksh є можливість зразки збігатися з крапковими файлами та створювати порожній список, якщо жоден файл не відповідає шаблону. Тож у ksh ви можете порахувати звичайні файли на зразок цього:

FIGNORE='.?(.)'
count=0
for x in ~(N)directoryName1/* ~(N)directoryName2/*; do
  if [ -f "$x" ]; then ((++count)); fi
done

або всі файли просто так:

FIGNORE='.?(.)'
files=(~(N)directoryName1/* ~(N)directoryName2/*)
count=${#files}

У Bash є різні способи зробити це простішим. Для підрахунку звичайних файлів:

shopt -s dotglob nullglob
count=0
for x in directoryName1/* directoryName2/*; do
  if [ -f "$x" ]; then ((++count)); fi
done

Для підрахунку всіх файлів:

shopt -s dotglob nullglob
files=(directoryName1/* directoryName2/*)
count=${#files}

Як завжди, в zsh це навіть простіше. Для підрахунку звичайних файлів:

files=({directoryName1,directoryName2}/*(DN.))
count=$#files

Змінити, (DN.)щоб (DN)рахувати всі файли.

¹ Зауважте, що кожен шаблон відповідає самій собі, інакше результати можуть бути вимкнені (наприклад, якщо ви рахуєте файли, які починаються з цифри, ви не можете просто зробити це, for x in [0-9]*; do if [ -f "$x" ]; then …тому що може бути названий файл [0-9]foo).


2

На основі сценарію підрахунку , відповіді Шона та хитрості Баша, щоб переконатися, що навіть імена файлів з новими рядками друкуються в зручній формі в одному рядку:

for f in *
do
    if [ -d "./$f" ]
    then
        printf %q "$f"
        printf %s ' '
        find "$f" -maxdepth 1 -printf x | wc -c
    fi
done

printf %qполягає в тому, щоб надрукувати цитовану версію рядка, тобто однорядковий рядок, який ви можете ввести в сценарій Bash, щоб його інтерпретувати як буквальний рядок, що включає (потенційно) нові рядки та інші спеціальні символи. Наприклад, див echo -n $'\tfoo\nbar'проти printf %q $'\tfoo\nbar'.

findКоманда працює, просто друк одного символу для кожного файлу, а потім рахуючи тих , замість підрахунку рядків.


1

Ось «груба сила» -ish спосіб отримати результат, використовуючи find, echo, ls, wc, xargsі awk.

find . -maxdepth 1 -type d -exec sh -c "echo '{}'; ls -1 '{}' | wc -l" \; | xargs -n 2 | awk '{print $1" "$2}'

Ця робота. Але результат вийшов зіпсований, якщо у вас є dirs, які мають пробіл у назві.
ShyBoy

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

-1
for i in *; do echo $i; ls $i | wc -l; done

4
Ласкаво просимо до U&L. Відповіді мають бути довгими формами з поясненнями, а не просто краплями коду. Розгорніть це і поясніть, що відбувається. Також це дуже неефективний спосіб зробити це, наприклад, не обробляючи файли з пробілами.
slm

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