
Пролог
В программировании микроконтроллеров часто приходится писать программные компоненты которые, в общем-то, очень похожие друг на друга по своей структуре и API. Как у нас говорят
Сделай по образу и подобию
Вот вам яркий пример. Надо написать драйвер SPI на основе драйвера UART. Чтобы функции HAL примерно одинаково выглядели за исключением, конечно же, самого названия программного компонента. Это соответствует принципу наименьшего удивления.
В чем проблема?
Как обычно это происходит. Берут драйвер UART копируют его, переименовывают слово UART на SPI и далее вручную прорабатывают конкретные места в коде.
Проблема в то, что переименовывать файлы и ключевые слова это весьма рутинная и утомительная процедура. Очевидно, что надо её как-то автоматизировать.
Терминология
токен - строка без пробела
консистентность - согласованность данных друг с другом
Постановка задачи
Написать консольную утилиту r.exe, которая рекурсивно заменяет одно ключевое слово (токен) на другое ключевое слово (токен) в той папке, в которой вызвали эту утилиту r
1--при замене учитывать регистр в токене
2--заменять ключевое слово в названии файла
3--Утилита должна называться одной буквой r, чтобы её было легко запомнить (rename all) и быстро набирать
4--У утилиты r должно быть только два ключа old new
--> r old_word new_word
Реализация.
Как известно есть bash команды, которые делают эту работу .
Фаза 1 Переименовать ключевое слово в файлах
1--Рекурсивно заменить слово oldtext на newtext во всех файлах внутри директории.
grep -rl oldtext . | xargs sed -i 's/oldtext/newtext/g'
Тут для grep -r означает, что искать внутри папок рекурсивно, -l означает показывать только пути к файлам. Для sed -i означает, что замена слова будет произведена прямо в этом же файле (in place), s означает, что надо заменить первый токен на второй токен, флаг g заменяет все вхождения заменяемого токена.
У этой команды есть три недостатка:
1--токен oldtext надо указывать два раза
2--команда длинная и ее сложно запомнить
3--команда длинная и ее долго набирать
Фаза 2: Переименовать имена файлов
--Взять все файлы в этой папке и в каждом имени файла переименовать подстроку at24c02mtr на at24cxx
find . -type f -exec rename -v at24c02mtr at24cxx {} \;
Эта очень удобная команда, когда надо написать программный компонент по образу и подобию другого программного компонента. Сначала переименовываем названия файлов (find+rename)

Исходный код
Исходный код тут тривиальный так как основную работу делают внешние утилиты sed, grep, find и rename
#include "sed.h"
#include <stdio.h>
#include <string.h>
#include "log.h"
#include "win_utils.h"
char* getcwd(char* buf, size_t size);
bool win_cmd_run(const char* const command) {
bool res = false;
if(command) {
LOG_DEBUG(PC, "ExeCmd:[%s],Size:%u", command, strlen(command));
int ret = system(command);
if(0 == ret) {
res = true;
} else {
LOG_ERROR(PC, "Ret,%d=%s", ret, CmdRetToStr(ret));
}
}
return res;
}
bool sed_replace(const char* const old, const char* const new) {
bool res = false;
if(old) {
if(new) {
LOG_INFO(SED, "Replace:[%s]->[%s]", old, new);
char CurDir[500] = {0};
char* str = getcwd(CurDir, sizeof(CurDir));
if(str) {
LOG_INFO(SED, "Current working dir: %s\n", CurDir);
} else {
LOG_ERROR(SED, "getcwd() error");
}
char replaceToken[1000] = {0};
char format[100] = "grep -rl %s . | xargs sed -i 's/%s/%s/g'";
snprintf(replaceToken, sizeof(replaceToken), format, old, old, new);
LOG_INFO(SED, "CMD,replaceToken:[%s]", replaceToken);
res = win_cmd_run(replaceToken);
log_res(SED, res, "CmdT");
char repl_file[1000] = {0};
char formatF[100] = "find . -type f -exec rename -v %s %s {} \;";
snprintf(repl_file, sizeof(repl_file), formatF, old, new);
LOG_INFO(SED, "CMD,ReplaceFileName:[%s]", repl_file);
res = win_cmd_run(repl_file);
log_res(SED, res, "CmdF");
}
}
return res;
}
Проверка работы утилиты r
Вот так отработала утилита r.exe
C:\workspace\code_base\source\mcal\mcal_yuntu\uart1>r uart spi
I,[SYS] argc 3
I,[SYS] Arg0 [r]
I,[SYS] Arg1 [uart]
I,[SYS] Arg2 [spi]
I,[Sed] Replace:[uart]->[spi]
I,[Sed] Current working dir: C:\workspace\code_base\source\mcal\mcal_yuntu\uart1
I,[Sed] CMD,replaceToken:[grep -rl uart . | xargs sed -i 's/uart/spi/g']
I,[Sed] CMD,ReplaceFileName:[find . -type f -exec rename -v uart spi {} ;]
`./uart.gvi' -> `./spi.gvi'
`./uart.mk' -> `./spi.mk'
`./uart_custom_commands.c' -> `./spi_custom_commands.c'
`./uart_custom_commands.h' -> `./spi_custom_commands.h'
`./uart_custom_const.h' -> `./spi_custom_const.h'
`./uart_custom_diag.h' -> `./spi_custom_diag.h'
`./uart_custom_drv.h' -> `./spi_custom_drv.h'
`./uart_custom_isr.c' -> `./spi_custom_isr.c'
`./uart_custom_isr.h' -> `./spi_custom_isr.h'
`./uart_custom_types.h' -> `./spi_custom_types.h'
`./uart_drv_old21.c' -> `./spi_drv_old21.c'
`./uart_mcal.c' -> `./spi_mcal.c'
`./uart_preconfig.mk' -> `./spi_preconfig.mk'
`./uart_verify.txt' -> `./spi_verify.txt'
C:\workspace\code_base\source\mcal\mcal_yuntu\uart1>
Разумеется после скачивания утилиты надо прописать к ней путь в переменную PATH.
Итог
Удалось написать работающую и полезную утилиту авто замены ключевых слов, которая повышает производительность разработки программного обеспечения и помогает улучшать консистентность репозитория.
Как видите, программирование микроконтроллеров - это не только варить прошивки. Это ещё и разработка самих средств разработки. Да... Это могут быть утилиты loader-ы, обновители версий, стилистические анализаторы и прочее.
Если у Вас есть пожелания к улучшению утилиты r, то пишите в комментариях.
Ссылки
Название |
URL |
Полезные Заготовки Вызова Утилит Командной Строки |
|
Бинарь утилиты r.exe |
|
Стилистический-Анализатор: Проверка Наличия Комментария в Конце Фигурной Скобки |
|
Стилистический анализатор: синхронизация объявлений и определений static функций |
|
Стилистический Анализатор: Синхронизация порядка объявлений и определений функций |
|
Автоматическое Обновление Версии Прошивки |
|
C Language: system function (Perform Operating System Command) |
https://www.techonthenet.com/c_language/standard_library_functions/stdlib_h/system.php |
Комментарии (10)
alexs963
21.06.2025 19:33#!/bin/sh OLD="$1" NEW="$2" grep -rl $OLD . | xargs sed -i "s/$OLD/$NEW/g" find . -type f -exec rename -v "$OLD" "$NEW" {} \;
beefdeadbeef
21.06.2025 19:33так появились на свет энциклонги :]
mvv-rus
21.06.2025 19:33... а также - бинтеллектага в одной из русефекаций древней игрушки "Arx Fatalis".
А уж что получилось при массовой замене "огр" на "людоед" в другой русефекации из слова "погребальный" - это я написать тут не решаюсь.
beefdeadbeef
21.06.2025 19:33Я слышал, есть такие clangd или ccls -- попробуйте, вдруг зайдёт.
Или даже, чем чёрт не шутит, coccinelle.
Jijiki
21.06.2025 19:33вообще у подхода несколько решений, можно грепом, можно оттолкнуться от токенизатора, и сперва отталкиваться от языка который парсим тоесть надо знать особенности языка который будет анализироваться,
например С подобный язык хорошо кладётся на основу скобочек, сперва реализовывается просчет всех скобочек(скобочки как балансиры {}[]() - я начинал путешествие с фигурных скобок, поиск всех слов от 1 слова как функционал, все стилистические штуки просто захардкодил, но там тоже красиво можно сделать ), и тут в этой позиции, просто открываем файл в дирректории проекта анализируем хидеры, далее пока идёт анализ текущего файла надо учесть то что анализируем заходя если надо в файлы которые заинклюдены в текущий файл, это надо для того чтобы учесть вхождение), конечно можно обойти эту суету с деревом или синтаксимческим анализатором, какими-то скриптамиможно вообще уйти от рекурсии работая только с текущим файлом, учитывая слова, строки, отступы, и слово которое ищем по всему файлу, тогда анализатор всё равно будет отрабатывать просто окном будет текущий файл проекта
например в яве на свинге так прокатывает тоже, но там вилка из двух подходов, декоративная работа и работа где надо точно делать потомучто буферы текста не простые там соотв есть подход через обнуление, и через просто проходы с применением стиля оба метода вроде работают, но я знаю способ ток покачто через обнуление и хардкод
yappari
21.06.2025 19:33У этой команды есть три недостатка:
1--токен oldtext надо указывать два раза
2--команда длинная и ее сложно запомнить
3--команда длинная и ее долго набирать
Это ведь такая шЮтка юмора, да?
randomsimplenumber
21.06.2025 19:33Даже не знаю что более удивительное - перепиливания драйвера UART в SPI, или переименовывание не через IDE.
gev
Мы сначала делаем интерфейс. Для UART выглядит так, например
UART interface