Расширение параметра Bash: лучшие практики для скорости?

Мне просто интересно, если кто-нибудь знает какие-либо лучшие практики или есть какие-либо документы по этой теме:

Сценарий поиска / поиска в файлах журнала. Чтобы выразить свою точку зрения, я буду использовать ls, Итак, скажем, что я бегу ls перечислить ряд файлов в каталоге

/var/log/remote/serverX.domain.local/ps/ps2.log.2014-mm-dd.gz

Где mm и dd являются числами месяца и дня, кроме serverX есть еще целый ряд серверов (для примера я использую 4,5,9,10 (это реальные серверы)

Я запустил ls со временем, используя сначала список параметров в фигурных скобках, а затем изменил его на звездочку, чтобы увидеть различия. Я, конечно, не ожидал, что звездочка будет работать лучше.

   emartinez@serverlog:~$ time ls /var/log/remote/server{4,5,9,10}.domain.local/ps/ps2.log.2014-10-0{1,2}.gz
    /var/log/remote/server10.domain.local/ps/ps2.log.2014-10-01.gz  
    ...
    /var/log/remote/server5.domain.local/ps/ps2.log.2014-10-02.gz

real    0m0.004s
user    0m0.010s
sys     0m0.000s

Затем я заменяю последнюю фигурную скобку звездочкой:

time ls /var/log/remote/server{4,5,9,10}.domain.local/ps/ps2.log.2014-10-0*.gz

И я получаю следующую статистику:

    real      0m0.028s
    user      0m0.020s
    sys   0m0.020s

Это большая разница, хотя есть только 2 варианта, поскольку доступные даты - только 01 и 02 октября.

Я снова запустил тест, но на этот раз я заменил месяцы на список {1..12}, который соответствует результатам:

ps2.log.2014-{1..12}-0{1,2}.gz : real 0m0.010s
ps2.log.2014-{1..12}-0*.gz     : real 0m0.168s

Это большая разница только для одной звездочки!!! Имеет смысл, что это медленнее, но есть ли какие-то критерии относительно того, насколько медленнее, и есть ли какие-нибудь лучшие практики, изложенные где-нибудь?

2 ответа

Решение

Может показаться, что prefix-* должно быть легко превратить, например, в prefix-1 prefix-2, так как мы привыкли видеть отсортированные списки каталогов. Но оказывается, что очень немногие файловые системы могут на самом деле создавать отсортированные списки имен файлов, и, кроме того, не существует стандартного API для запроса отсортированных списков имен файлов.

Если программа - такая как ls или, в этом отношении, bash - нужен список имен файлов, он должен прочитать весь список каталогов, который будет создан в некотором случайном порядке (часто порядок связан со временем создания; иногда он основан на хэше имени файла; но в большинстве случаев нет дело это простой алфавитный порядок). Итак, чтобы решить prefix-* Вам нужно прочитать весь каталог и проверить каждое имя файла по шаблону. Поскольку наиболее дорогостоящей частью этой процедуры является чтение каталога, не имеет значения, насколько сложен шаблон или сколько имен файлов соответствуют шаблону.

В итоге, расширение имени пути ("разрешения глобусов") будет медленным в большом каталоге. Это причина избегать больших каталогов, а не причина избегать глобусов.

Но есть еще одно важное назначение данных: prefix-{1,2} не расширение пути Это " расширение скобок " и расширение стандарта оболочки Posix (хотя его реализуют практически все оболочки). Существует несколько различий между расширением скобок и расширением пути, но одно важное и важное различие заключается в том, что расширение скобок не зависит от существования файлов. Разбивка скобок - это простая строковая операция.

Как следствие, prefix-{1,2} всегда будет расширяться до prefix-1 prefix-2 независимо от того, существуют эти файлы или нет. Это означает, что он может быть расширен без чтения каталога и без stat в любом файле. Понятно, что это будет быстро. Но есть и обратная сторона: невозможно определить, соответствует ли результат реальным файлам.

Рассмотрим следующий простой пример:

$ mkdir test && cd test
$ touch file1 file2 file4
$ ls file*
file1 file2 file4
$ ls file[1234]
file1 file2 file4
$ ls file{1,2,3,4}
ls: cannot access file3: No such file or directory
file1 file2 file4

Конечный момент: расширение пути выполняется оболочкой, а не ls, С расширением пути мы могли бы также использовать echo:

$ echo file*
file1 file2 file4
$ echo file[1234]
file1 file2 file4

А также echo будет производить список несколько быстрее, потому что все echo нужно сделать, это распечатать свои аргументы, в то время как ls (который получает те же аргументы) должен stat каждый аргумент, чтобы убедиться, что это файл. Тот stat - который не является дешевым вызовом - полностью избыточен в случае расширения пути, потому что оболочка уже использовала список каталогов для фильтрации списка файлов и, следовательно, каждое имя файла передавалось ls как известно, существует. (Если глобус не совпадал ни с какими файлами.)

Кроме того, эхо является bash встроенный, поэтому он может быть вызван без создания дочернего процесса.

В случае расширения скобки, тем не менее, echo не дает тот же результат:

$ echo file{1,2,3,4}
file1 file2 file3 file4

Таким образом, мы могли бы использовать ls, перенаправляя вывод ошибок в область битов:

$ ls file{1,2,3,4}
file1 file2 file4

и в этом случае stat вызовы не являются избыточными, потому что оболочка никогда не проверяла имена файлов.

Если ваши каталоги не очень большие, ничего из этого не будет иметь большого значения, и глоб будет намного легче писать. Если ваши каталоги действительно огромны, вам следует рассмотреть возможность их разделения на более мелкие подкаталоги.

Например, вместо путей вроде:

/var/log/remote/serverX.domain.local/ps/ps2.log.2014-mm-dd.gz

Вы можете использовать:

/var/log/remote/serverX/domain.local/ps/ps2.log.2014-mm-dd-gz

И если вы храните журналы навсегда, вы можете извлечь год, чтобы избежать бесконечно увеличивающегося размера каталога:

/var/log/remote/2014/serverX/domain.local/ps/ps2.log.2014-mm-dd-gz

(2014 намеренно повторяется.)

Разделение каталогов, как правило, будет большой победой, поскольку оно обеспечивает механизм для оптимизации глобализации. Как уже упоминалось выше, оболочка не может оптимизировать

/var/log/remote/server[2357].domain.local/ps/ps2.log.2014-10-*-gz

но это может оптимизировать

/var/log/remote/server[2357]/domain.local/ps/ps2.log.2014-10-*-gz

Во втором случае server[2357] нужно только сопоставить с именами каталогов, и как только это будет сделано, ps2.log.2014-10-*-gz нужно сопоставлять только с именами файлов в соответствующих каталогах.

Расширение оболочки всегда выполняется в определенном порядке; Расширение скобок выполняется первым, расширение имени файла выполняется последним.

Таким образом, команда как

echo {1..3}*

сначала расширяется до

echo 1* 2* 3*

затем выполняется расширение имени файла для 1*, 2* а также 3*, Каждое расширение включает в себя просмотр всех имен файлов в каталоге и сравнение их с шаблоном.

По мере роста количества слов и / или количества файлов в каталоге это становится постепенно медленнее. Даже в пустой директории,

shopt -s nullglob  # print nothing for non-matching words
echo {1..1000000}* # prints nothing
shopt -u nullglob  # back to the default

занимает почти пять секунд на моей машине. Это совсем не удивительно, если учесть, что расширение имени файла выполняется миллион раз...

Гораздо более быстрой альтернативой является, по возможности, избегать объединения обоих типов расширения оболочки.

Команда

echo [1-1000000]* # also prints nothing

ищет те же имена файлов, но использует один шаблон. Это займет 33 миллисекунды на моей машине.

Использование квадратных скобок вместо фигурных скобок имеет дополнительные преимущества:

$ touch 13
$ echo {1..20}*
13 13
$ echo [1..20]*
13

Первый подход нашел файл дважды, так как он соответствует шаблонам 1* а также 13*, Этого не происходит с "чистым" расширением имени файла.

Другие вопросы по тегам