Вопрос или проблема
Следующий код должен продемонстрировать и помочь с тестированием неэффективных выражений Сопоставления шаблонов
в Замене параметров
для строк, разделенных новой строкой, переменной vs. массива.
Цель состоит в достижении как минимум сопоставимой производительности по сравнению с grep
, при фильтрации только результатов git status -s
, которые включают изменения index
(полностью или частично staged
).
Таким образом, каждое изменение, которое начинается с короткого флага статуса Git, как [MTARDC]
(сигнализирующее изменение в staged/index), включая двойные флаги (сигнализирующие частично staged изменения в index и worktree
), за исключением изменений untracked
или unstaged-only
(начинающихся с [ ?]).
Обратите внимание, что флаги изменения R
(переименование) могут сопровождаться несколькими цифрами, см. также примеры в тестовых данных ниже (возможно даже для обеих, index и worktree, например R104R104).
См.: короткий формат статуса Git
Тестовые данные также содержат имена файлов с потенциально проблематичными специальными символами, такими как escape-последовательности, пробел или амперсанд: [\ $*"]
.
Также обратите внимание на то, что замена на основе Сопоставления шаблонов требует отрицания шаблонов, по сравнению с RegEx
для grep с теми же результатами. Чтобы напечатать результаты, просто закомментируйте части &>/dev/null
.
#! /bin/bash
# Активировать расширенное сопоставление шаблонов
shopt -s extglob
clear
unset -v tmpVar tmpArr
# Заполнить tmpVar и tmpArr для тестирования
for i in {1..3}; do
tmpVar+=' A addedW1'[$i]$'\n'
tmpVar+='A addedI1'[$i]$'\n'
tmpVar+='AM addedA1'[$i]$'\n'
tmpVar+=' C copiedW1'[$i]$'\n'
tmpVar+='C copiedI1'[$i]$'\n'
tmpVar+='CR copied A1'[$i]$'\n'
tmpVar+=' D removedW1'[$i]$'\n'
tmpVar+='D removedI1'[$i]$'\n'
tmpVar+='DM removedA1'[$i]$'\n'
tmpVar+=' M modifiedW1'[$i]$'\n'
tmpVar+='M modifiedW1'[$i]$'\n'
tmpVar+='MR modifiedA1'[$i]$'\n'
tmpVar+=' R101 renamedW1'[$i]$'\n'
tmpVar+='R102 renamedI2'[$i]$'\n'
tmpVar+='R103D renamedA1'[$i]$'\n'
tmpVar+=' T typeChangedW1'[$i]$'\n'
tmpVar+='T typeChangedI1'[$i]$'\n'
tmpVar+='TM typeChangedA1'[$i]$'\n'
tmpVar+='?? exec2.bin'[$i]$'\n'
tmpVar+='?? file1.txt'[$i]$'\n'
tmpVar+='?? test.launch2'[$i]$'\n'
tmpVar+='A file00 0.bin'[$i]$'\n'
tmpVar+='A file11*1.bin'[$i]$'\n'
tmpVar+='A file22\03457zwei.bin'[$i]$'\n'
tmpVar+='A file33\t3.bin'[$i]$'\n'
tmpVar+='A file44$4.bin'[$i]$'\n'
tmpVar+='A file55"$(echo EXE)"5.bin'[$i]$'\n'
tmpVar+='M exec1.bin'[$i]$'\n'
tmpVar+=' M test.launch1'[$i]$'\n'
tmpVar+=' M myproject/src/main/java/util/MyUtil.java'[$i]$'\n'
tmpVar+='M myproject/src/test/util/MyUtilTest.java'[$i]$'\n'
tmpVar+='R104R104 myproject/src/test/util/MyUtil2Test.java'[$i]$'\n'
tmpVar+=' A invalidAdd'[$i]$'\n'
tmpVar+='R invalidRename'[$i]$'\n'
tmpArr+=(" A addedW1[$i]")
tmpArr+=("A addedI1[$i]")
tmpArr+=("AM addedA1[$i]")
tmpArr+=(" C copiedW1[$i]")
tmpArr+=("C copiedI1[$i]")
tmpArr+=("CR copied A1[$i]")
tmpArr+=(" D removedW1[$i]")
tmpArr+=("D removedI1[$i]")
tmpArr+=("DM removedA1[$i]")
tmpArr+=(" M modifiedW1[$i]")
tmpArr+=("M modifiedW1[$i]")
tmpArr+=("MR modifiedA1[$i]")
tmpArr+=(" R101 renamedW1[$i]")
tmpArr+=("R102 renamedI2[$i]")
tmpArr+=("R103D renamedA1[$i]")
tmpArr+=(" T typeChangedW1[$i]")
tmpArr+=("T typeChangedI1[$i]")
tmpArr+=("TM typeChangedA1[$i]")
tmpArr+=("?? exec2.bin[$i]")
tmpArr+=("?? file1.txt[$i]")
tmpArr+=("?? test.launch2[$i]")
tmpArr+=("A file00 0.bin[$i]")
tmpArr+=("A file11*1.bin[$i]")
tmpArr+=("A file22\03457zwei.bin[$i]")
tmpArr+=("A file33\t3.bin[$i]")
tmpArr+=("A file44$4.bin[$i]")
tmpArr+=('A file55"$(echo EXE)"5.bin['$i']')
tmpArr+=("M exec1.bin[$i]")
tmpArr+=(" M test.launch1[$i]")
tmpArr+=(" M myproject/src/main/java/util/MyUtil.java[$i]")
tmpArr+=("M myproject/src/test/util/MyUtilTest.java[$i]")
tmpArr+=("R104R104 myproject/src/test/util/MyUtil2Test.java[$i]")
tmpArr+=(" A invalidAdd[$i]")
tmpArr+=("R invalidRename[$i]")
done
# Тест производительности фильтрации массива или строковой переменной через grep
_IFS="$IFS"; IFS=$'\n'
startTime="$EPOCHREALTIME"
grep '^[MTARDC]' <<<"${tmpArr[*]}" &>/dev/null
stopTime="$EPOCHREALTIME"
IFS="$_IFS"
echo
awk 'BEGIN { printf "ELAPSED TIME via grep filtering from ARRAY: "; print '"$stopTime"' - '"$startTime"' }'
# Тест производительности фильтрации массива через Сопоставление шаблонов в Замене параметров
startTime="$EPOCHREALTIME"
printf '%s\n' "${tmpArr[@]/#[? ][?MTARDC]*([0-9]) *}" &>/dev/null
stopTime="$EPOCHREALTIME"
echo
awk 'BEGIN { printf "ELAPSED TIME via parameter substitution from ARRAY: "; print '"$stopTime"' - '"$startTime"' }'
# Тест производительности фильтрации строковой переменной через Сопоставление шаблонов в Замене параметров
startTime="$EPOCHREALTIME"
printf '%s\n' "${tmpVar//[? ][?MTARDC]*([0-9]) *([^$'\n'])?($'\n')}" &>/dev/null
stopTime="$EPOCHREALTIME"
echo
awk 'BEGIN { printf "ELAPSED TIME via parameter substitution from VAR: "; print '"$stopTime"' - '"$startTime"' }'
# РЕЗУЛЬТАТ:
#ELAPSED TIME via grep filtering from ARRAY: 0.054975
#ELAPSED TIME via parameter substitution from ARRAY: 0.00031805
#ELAPSED TIME via parameter substitution from VAR: 4.546
Как видно, grep
хорош, но вариант #2 (фильтрация массива через Замена параметров
) намного быстрее, поэтому для больших массивов есть хорошая альтернатива grep.
Фильтрация строковой переменной через Замена параметров
, с другой стороны, ужасно медленная.
В основном из-за того, что сопоставляющий шаблон не может заканчиваться на *
(что удалило бы все до конца строки с первого совпадения), но потому что нужно *([^$'\n'])?($'\n')
вместо этого, чтобы сопоставить (и удалить) все в совпадении, до следующей новой строки, и в некоторой степени из-за жадного сопоставления tmpVar//
.
Есть ли другой способ/шаблон для примера, чтобы обрабатывать строковые переменные с Сопоставлением шаблонов
аналогично массиву – без использования проблематичного и замедляющего отрицания символов новой строки и чтобы приблизиться к скорости примера массива?
Ответ или решение
В программировании, а особенно в управлении строками и данными, используемыми в операционных системах, эффективность работы кода является ключевым аспектом, особенно когда речь идет о массивах данных и таких операторах, как замена параметров. В данном вопросе рассматривается проблема оптимизации замены параметров в строках, разделенных символом новой строки, с целью достижения производительности на уровне grep
при фильтрации результатов git status -s
, которые связаны с изменениями в индексе.
Теория
Основная задача заключается в том, чтобы отфильтровать изменения, касающиеся индекса, то есть изменения, которые сигнализируются флагами статуса, начинающимися с таких символов, как [MTARDC]
. Сюда входят как полные, так и частично зафиксированные изменения, за исключением флагов для нефайловых изменений (начинающихся с ?
). Флаги изменений, такие как R
(переименование), могут сопровождаться числовыми идентификаторами. Например, R104R104
может сигнализировать о частичном замене.
При переопределении параметров в строках необходимо учитывать наличие специальных символов и избежать чрезмерного использования регулярных выражений, так как это может снизить производительность. Оптимизация требует от нас использования встроенных механизмов Bash и Pattern Matching
, которые поддерживают быстрые и менее ресурсоемкие операции по сравнению с внешними вызовами, такими как grep
.
Пример
Рассмотренный пример иллюстрирует использование Pattern Matching
для фильтрации массивов и строк на Bash. Первый шаг — включение расширенного сопоставления шаблонов:
shopt -s extglob
Задача состоит в фильтрации элементов массива и строки с использованием различных стратегий, чтобы сравнить их с grep
и выявить более эффективное решение. Пример показывает, что использование фильтрации массива через замену параметров (Parameter Substitution
) значительно быстрее, чем использование аналогичной стратегии на строках.
Применение
Для практического применения вашей задачи по фильтрации данных git status -s
, можно воспользоваться следующей стратегией:
-
Используйте массивы: Работа с массивами в Bash позволяет более эффективно управлять данными благодаря внутренним оптимизациям работы с массивами.
-
Минимизируйте использование регулярных выражений: Хотя регулярные выражения мощны, они могут быть медленны и ресурсозатратны. Работайте с
Pattern Matching
, избегая использование*([^$'\n'])?($'\n')
. -
Оптимизация для строк: Если все-таки необходимо использовать строки, попробуйте разделить строку на массив строк с использованием IFS (Internal Field Separator) и работать с каждыми элементами как с элементами массива.
Вот как вы можете добиться уменьшения использования регулярных выражений для строк:
_IFS="$IFS"
IFS=$'\n'
for line in $tmpVar; do
[[ $line =~ ^[MTARDC] ]] && printf '%s\n' "$line"
done
IFS="$_IFS"
-
Обработка специальных символов: Для работы со строками, содержащими специальные символы, такие как пробел, амперсанд или кавычки, всегда обрабатывайте их заранее, правильно экранируя и подготавливая их для обработки.
-
Используйте встроенные средства Bash: В случаях, когда это возможно, используйте встроенные средства Bash вместо вызова системных утилит, таких как
grep
, поскольку это может значительно замедлить выполнение, особенно при обработке больших объемов данных.
Профессиональные разработки требуют постоянной оптимизации и поиска лучших решений, особенно когда речь идет о масштабируемости и эффективности. Надеюсь, эти рекомендации помогут вам сделать ваш код более эффективным, соблюдая баланс между читаемостью и производительностью.