Почему `ls -l` считает больше файлов, чем я?

25

Видимо я не могу сосчитать. Я думаю, что есть три файла в/media

$ tree /media
/media
├── foo
├── onex
└── zanna
3 directories, 0 files

Тем не менее, ls -lнаходит 12.

$ ls -l /media
total 12
drwxr-xr-x  2 root root 4096 Jul 31 20:57 foo
drwxrwxr-x  2 root root 4096 Jun 26 06:36 onex
drwxr-x---+ 2 root root 4096 Aug  7 21:17 zanna

И, если я делаю, ls -laя получаю только .и ..в дополнение к вышеупомянутому, но количествоtotal 20

Какое объяснение?

Занна
источник

Ответы:

33

12Вы видите, не количество файлов, но количество дисковых блоков потребляется.

От info coreutils 'ls invocation':

 For each directory that is listed, preface the files with a line
 `total BLOCKS', where BLOCKS is the total disk allocation for all
 files in that directory.  The block size currently defaults to 1024
 bytes, but this can be overridden (*note Block size::).  The
 BLOCKS computed counts each hard link separately; this is arguably
 a deficiency.

Общее идет от 12к , 20когда вы используете ls -laвместо ls -lпотому что вы рассчитываете два дополнительных каталогов: .и ... Вы используете четыре дисковых блока для каждого (пустого) каталога, поэтому ваш общий объем возрастает с 3 × 4 до 5 × 4. (По всей вероятности, вы используете один дисковый блок по 4096 байт для каждого каталога; как infoпоказывает страница, Утилита не проверяет формат диска, но принимает размер блока, 1024если не указано иное.)

Если вы хотите просто получить количество файлов, вы можете попробовать что-то вроде

ls | wc -l
user4556274
источник
13
ls | wc -lпотерпит неудачу, если есть файлы с новой строкой в ​​имени файла. Это более устойчиво:find . -mindepth 1 -maxdepth 1 -printf . | wc -c
Flimm
20
«если в именах файлов есть новая строка» ... дрожь
Петах
8
Как man lsвам скажут, вы можете избежать контрольных символов с -b(избегает их) или -q(опускает их). Таким образом, для подсчета, ls -1q | wc -lявляется безопасным и точным для отображения не скрытых файлов. ls -1qA | wc -lсчитать скрытые файлы (но не .а ..). Я использую -1вместо, -lпотому что это должно быть быстрее.
Оли
18

Пользователь 4556274 уже ответил на вопрос « почему» . Мой ответ служит только для предоставления дополнительной информации о том, как правильно считать файлы.

В сообществе Unix общий консенсус заключается в том, что синтаксический анализ выходных данных lsявляется очень плохой идеей , поскольку имена файлов могут содержать управляющие символы или скрытые символы. Например, из-за символа новой строки в имени файла мы ls | wc -lсказали, что в выводе есть 5 строк ls(которые есть), но на самом деле в каталоге только 4 файла.

$> touch  FILE$'\n'NAME                                                       
$> ls                                                                         
file1.txt  file2.txt  file3.txt  FILE?NAME
$> ls | wc -l
5

Способ № 1: найти утилиту

Команда find, которая обычно используется для работы с синтаксическим анализом имен файлов, может помочь нам, напечатав номер инода . Будь то каталог или файл, он имеет только один уникальный номер inode. Таким образом, используя -printf "%i\n"и исключая .через, -not -name "."мы можем иметь точное количество файлов. (Обратите внимание на использование -maxdepth 1для предотвращения рекурсивного спуска в подкаталоги)

$> find  -maxdepth 1 -not -name "." -print                                    
./file2.txt
./file1.txt
./FILE?NAME
./file3.txt
$> find  -maxdepth 1 -not -name "." -printf "%i\n" | wc -l                    
4

Способ № 2: глобстар

Простой, быстрый и в основном портативный способ:

$ set -- * 
$ echo $#
228

setКоманда используется для установки позиционных параметров оболочки ( $<INTEGER>переменных, как в echo $1). Это часто используется, чтобы обойти /bin/shограничение отсутствия массивов. Версия, которая выполняет дополнительные проверки, может быть найдена в ответе Джилла на Unix & Linux.

В оболочках, которые поддерживают массивы, такие как bash, мы можем использовать

items=( dir/* )
echo ${#items[@]}

как предложено Steeldriver в комментариях .

Аналогично findметоду, который использовался, wcи globstar можно использовать statдля подсчета номеров инодов в строке:

$> LC_ALL=C stat ./* --printf "%i\n" | wc -l                                          
4

Альтернативный подход заключается в использовании подстановочного знака в forцикле. (Обратите внимание, что в этом тесте используется другой каталог для проверки, подходит ли этот подход к подкаталогам, чего нет - 16 - это проверенное количество элементов в моем ~/bin)

$> count=0; for item in ~/bin/* ; do count=$(($count+1)) ; echo $count ; done | tail -n 1                                
16

Метод № 3: другие языки / переводчики

Python также может иметь дело с проблемными именами файлов путем печати длины списка с учетом моей os.listdir()функции (которая не является рекурсивной и будет перечислять только элементы в каталоге, указанном в качестве аргумента).

$> python -c "import os ; print os.listdir('.')"                              
['file2.txt', 'file1.txt', 'FILE\nNAME', 'file3.txt']
$>  python -c "import os ; print(len(os.listdir('.')))"                    
4

Смотрите также

Сергей Колодяжный
источник
2
В bash другим вариантом будет использование массива, например items=( dir/* ); echo ${#items[@]}(добавление shopt -s dotglobдля включения скрытых файлов).
SteelDriver
1
Печать номеров узлов позволяет легко фильтровать жесткие ссылки, если это необходимо, с помощью find | sort -u | wc -l.
Питер Кордес
@steeldriver: я думаю, что метод bash-array вряд ли будет быстрее. Если вы хотите, чтобы он был рекурсивным, вам нужно использовать items=( dir/** )shopt -s globstar), но bash не использует дополнительные метаданные из readdir, поэтому он регистрирует каждую запись каталога, чтобы увидеть, является ли она самой директорией. Многие файловые системы хранят тип файла в записи каталога, поэтому readdir может вернуть его, не обращаясь к inode. (Например, последняя версия XFS, отличная от используемой по умолчанию, имеет это, и я думаю, что в ext4 она была дольше.) Если вы straceобнаружите, вы увидите намного меньше statсистемных вызовов, чем в bash.
Питер Кордес
2
Почему бы просто не использовать print(len(os.listdir('.')))? Меньше символов для ввода, а также избегает доступа к атрибутам с двойной подчеркиванием.
edwinksl
1
@edwinksl отредактировано, спасибо
Сергей Колодяжный