Линуксоидное воспоминание из школьных времён, или что бывает, когда не слушают мудрые советы и парсят вывод
Пункт первый: однажды я узнал, что у команды
Изучать, чем отличаются опции
Пункт второй: я захотел написать shell-скрипт, который пройдёт по списку файлов в каталоге и для каждого создаст ещё какой-то файл с "производным" именем. Ну, что-то вроде "был
Пункт третий: решив посмотреть, что же мой скрипт нагенерировал, я с удивлением обнаружил, что теперь цвет намертво въелся в имена свежесозданных файлов — хоть ты с
Сейчас, правда, coreutils поумнели, и попытка напечатать "раскрашенное" имя сразу выдаёт неладное, но всё-таки воспроизвести фокус труда не составляет. А разгадка — когда-нибудь в следующих сериях.
ls.Пункт первый: однажды я узнал, что у команды
ls (если точнее, то у той её реализации, что "в линуксе", то есть из GNU coreutils, но тогда я, наверное, о таких деталях не задумывался) есть параметр --color[=WHEN], принимающий одно из трёх значений: never, auto и always (при этом "always" подразумевается по умолчанию, если передан просто --color). С этим параметром каталоги будут печататься одним цветом, архивы — другим, текстовые файлы — третьим... Недолго думая, прописал себе на постоянной основе в .bash_profilealias ls="ls --color"
Изучать, чем отличаются опции
auto и always — зачем?..Пункт второй: я захотел написать shell-скрипт, который пройдёт по списку файлов в каталоге и для каждого создаст ещё какой-то файл с "производным" именем. Ну, что-то вроде "был
file.c — создадим file.c.backup1". Сейчас по запросу don't parse ls гугл выдаёт довольно много всего — наверное, выдавал и тогда, но я же был не какой-то зануда, соблюдающий best practices, я выяснял, как сохранить вывод ls в переменную и разобрать его на части.Пункт третий: решив посмотреть, что же мой скрипт нагенерировал, я с удивлением обнаружил, что теперь цвет намертво въелся в имена свежесозданных файлов — хоть ты с
--color=always печатай, хоть с never...Сейчас, правда, coreutils поумнели, и попытка напечатать "раскрашенное" имя сразу выдаёт неладное, но всё-таки воспроизвести фокус труда не составляет. А разгадка — когда-нибудь в следующих сериях.
Какой развесистый комикс вышел у xkcd :)
xkcd
Shielding Chart
Заметки бессистемного программиста
Линуксоидное воспоминание из школьных времён, или что бывает, когда не слушают мудрые советы и парсят вывод ls. Пункт первый: однажды я узнал, что у команды ls (если точнее, то у той её реализации, что "в линуксе", то есть из GNU coreutils, но тогда я, наверное…
Итак, пришло время собраться с мыслями и доработать черновик от 7 сентября (первым делом выкинув вступление про "не откладывая в долгий ящик", ага). На самом деле, угадайку я не планировал — просто не был готов описать всё за один присест. За один месяц, как оказалось, тоже ;)
Откуда же взялась возможность "раскрашивать" имена файлов? Дело в том, как в юниксовой консоли описывается форматирование выводимого текста: большинство атрибутов и команд идут в том же потоке байтов, что и сам текст — такое вот подобие языков разметки. Вероятно, это как-то связано с тем фактом, что раньше терминалы были отдельными железками, подключаемыми к мейнфрейму ограниченным количеством проводов, и нельзя было просто сказать "хочешь переместить курсор — дёрни прерывание, оно обновит неуточнённые структуры данных в памяти, потом как-нибудь нарисуем". А в "однокомпьютерном" DOS так сделать было можно, поэтому вроде бы даже в Винде до недавнего времени форматирование текста в консоли делалось через некие вызовы WinAPI. Впрочем, тут меня уже понесло в сторону домыслов. Что же, попробую кратенько изложить структуру типичных escape-последовательностей, авось в процессе и сам наконец-то разберусь, почему зелёный цвет — это
Насколько я понял, важной вехой в терминалостроении стал VT100 и его вариации — это был один из первых терминалов, поддержавший унифицированный стандарт управляющих последовательностей: ANSI escape codes, которые были задуманы в качестве замены туче несовместимых фирменных систем кодов у каждого производителя. На самом деле, довольно интересно читать описание этой железки: восьмибитный процессор Intel 8080, возможность докупить платы расширения — прямо отдельная персоналка (ну... восьмибитная — так 1978 год на дворе) для общения большой машины с пользователем. Впоследствии ANSI-коды оказались в той или иной мере поддержаны во многих эмуляторах терминалов — видимо поэтому, несмотря на существование утилиты
Для начала поймём, что такое
Откуда же взялась возможность "раскрашивать" имена файлов? Дело в том, как в юниксовой консоли описывается форматирование выводимого текста: большинство атрибутов и команд идут в том же потоке байтов, что и сам текст — такое вот подобие языков разметки. Вероятно, это как-то связано с тем фактом, что раньше терминалы были отдельными железками, подключаемыми к мейнфрейму ограниченным количеством проводов, и нельзя было просто сказать "хочешь переместить курсор — дёрни прерывание, оно обновит неуточнённые структуры данных в памяти, потом как-нибудь нарисуем". А в "однокомпьютерном" DOS так сделать было можно, поэтому вроде бы даже в Винде до недавнего времени форматирование текста в консоли делалось через некие вызовы WinAPI. Впрочем, тут меня уже понесло в сторону домыслов. Что же, попробую кратенько изложить структуру типичных escape-последовательностей, авось в процессе и сам наконец-то разберусь, почему зелёный цвет — это
^[[32m :)Насколько я понял, важной вехой в терминалостроении стал VT100 и его вариации — это был один из первых терминалов, поддержавший унифицированный стандарт управляющих последовательностей: ANSI escape codes, которые были задуманы в качестве замены туче несовместимых фирменных систем кодов у каждого производителя. На самом деле, довольно интересно читать описание этой железки: восьмибитный процессор Intel 8080, возможность докупить платы расширения — прямо отдельная персоналка (ну... восьмибитная — так 1978 год на дворе) для общения большой машины с пользователем. Впоследствии ANSI-коды оказались в той или иной мере поддержаны во многих эмуляторах терминалов — видимо поэтому, несмотря на существование утилиты
tput для вывода правильных последовательностей для конкретного терминала, я тем не менее вижу захардкоженные коды в убунтовском .bashrc для раскрашивания приглашения командной строки.Для начала поймём, что такое
^[. На самом деле все эти ^C, ^H и прочие — это не просто жаргонное обозначение для сочетаний Ctrl+<буква>, а так называемые Caret notation: ^A — это control-code 1 (фактически, просто символ с кодом 0x01), ^C — control-code 3, ^Z — control-code 26, а control codes с последующими номерами используют символы, идущие в таблице ASCII после заглавных букв: следом за Z идёт как раз [, то есть ^[ — это control-code 27, он же символ с кодом 0x1b, он же ESC. Тут ещё отдельный вопрос: ^[ и caret notation — это, конечно, замечательно, но ни printf в C, ни правила экранирования строк в bash этого не поймут. Поэтому пока предлагаю остановиться на конкретном способе выводить такие символы: shell-команде printf, довольно сильно мимикрирующей под одноимённую функцию языка C. Команда printf так же принимает "строку формата" своим первым аргументом, и в ней ^[ можно описать как \e.С caret notation разобрались, теперь допишем оставшийся n-1 символ. Последовательности, начинающиеся с
Посмотрим теперь на конкретный пример: вывод
Мы видим символы "A", "B", "C" (с кодами 0x41, 0x42 и 0x43, соответственно), потом опять "A", а после — перевод строки (код 0x0a).
Теперь попробуем перейти в пустой каталог, создать в нём подкаталог с именем
Скучный, "чёрно-белый" ls просто напечатал уже привычные четыре буквы и перевод строки, а вот в "цветном" варианте размахнулся аж на 21 байт, где не сразу и заметишь наш знакомый маркер "41 42 43 41" и перевод строки "0a". Оставшиеся 16 байт — это последовательности Select Graphic Rendition: сначала двухбайтный префикс
*
*
*
*
*
Такую же последовательность байт (с таким же эффектом) можно самостоятельно выдать в терминал командой
\e[ (то есть двух байтов: ESC и всамделишного [), являются частным случаем Fe escape sequences. После префикса из двух байтов ESC и [ в общем случае идёт сначала ноль и более "байтов-параметров", потом ноль и более "промежуточных байтов", а следом — ровно один "финальный байт". И "параметры" и "промежуточные" байты принадлежат алфавитам из 16 символов: байты-параметры имеют коды 0x30-0x3F (цифры 0-9 и ещё шесть "спецсимволов", следующих за ними в таблице: :;<=>?), но обычно представляют собой последовательность десятичных чисел, разделённых точкой с запятой, а промежуточные байты — 0x20-0x2F (!"#$%&'()*+,-./ — сплошные "спецсимволы"). Ассортимент финальных байтов гораздо богаче: этот байт должен иметь код из диапазона 0x40-0x7E, что включает в себя все заглавные и строчные буквы, плюс 11 "спецсимволов" (а вот непечатный символ DEL с кодом 0x7F не включает — упражнение на внимательность).Посмотрим теперь на конкретный пример: вывод
ls. Чтобы увидеть, какие байты печатаются в терминал, перенаправим вывод команды на вход утилиты hd, которая выдаст три колонки: смещение от начала файла (или, как у нас, потока байтов), строчку из 16 байтов в шестнадцатеричном виде и те же байты, но текстом (заменяя непечатные символы на точку). Для начала поймём, как будет выглядеть обычный текст без "украшений":$ echo ABCA | hd
00000000 41 42 43 41 0a |ABCA.|
00000005
Мы видим символы "A", "B", "C" (с кодами 0x41, 0x42 и 0x43, соответственно), потом опять "A", а после — перевод строки (код 0x0a).
Теперь попробуем перейти в пустой каталог, создать в нём подкаталог с именем
ABCA и "увидеть" его при помощи ls:$ ls --color=never | hd
00000000 41 42 43 41 0a |ABCA.|
00000005
$ ls --color=always | hd
00000000 1b 5b 30 6d 1b 5b 30 31 3b 33 34 6d 41 42 43 41 |.[0m.[01;34mABCA|
00000010 1b 5b 30 6d 0a |.[0m.|
00000015
Скучный, "чёрно-белый" ls просто напечатал уже привычные четыре буквы и перевод строки, а вот в "цветном" варианте размахнулся аж на 21 байт, где не сразу и заметишь наш знакомый маркер "41 42 43 41" и перевод строки "0a". Оставшиеся 16 байт — это последовательности Select Graphic Rendition: сначала двухбайтный префикс
\e[, потом последовательность чисел-команд через точку с запятой и завершающая буква m:*
\e[0m (4 байта, команда 0) — сбросить все атрибуты (reset or normal)*
\e[01;34m (8 байтов, команды 1 и 34) — поярче (bold or increased intensity) и синим текстом (blue foreground)*
ABCA (4 байта) — собственно, само имя каталога*
\e[0m (4 байта, команда 0) — прибираем рабочее место, опять сброс*
\n (1 байт) — перевод строкиТакую же последовательность байт (с таким же эффектом) можно самостоятельно выдать в терминал командой
printf '\e[0m\e[01;34mABCA\e[0m\n'
Мелочь, а удобно. Иногда нужно зайти по SSH на удалённую систему и скачать там что-нибудь из интернета. Обычно в таких случаях я просто переходил в каталог, куда нужно скачать файл, командовал идти, куда послали обрабатывать перенаправления в HTTP и
Однажды скачиваемый файл оказался не просто
и curl сам разберётся со счётчиком, и скачает все 20 файлов один за другим. Кавычки нужны не для curl (он их как раз не увидит), а чтобы bash (думаю, ровно то же применимо и к zsh) даже не пытался сам раскрывать шаблоны с квадратными скобками, а просто достал содержимое из одиночных кавычек, и передал как ровно один аргумент запускаемой программе. Кроме приведённого выше примера у curl есть ещё несколько аналогичных вариантов раскрытия одного урла в последовательность — они удобно указаны прямо в начале man-страницы.
wget https://example.org/path/to/file.tar.gz и радовался. Правда, иногда вместо wget был только curl — тогда радовался я чуть меньше, потому что нужно было вспомнить минимальный набор опций: что-то вроде -L, чтобы -O, чтобы просто сохранить файл под ожидаемым именем file.tar.gz.Однажды скачиваемый файл оказался не просто
file.tar.gz, а file-1.tar.gz, file-2.tar.gz, ..., file-20.tar.gz (а там ведь и весь остальной URL https://example.org/path/... к каждому приписан) — получается очень длинная командная строка, и запросто можно запутаться и что-нибудь пропустить. Тут-то обнаружилась оборотная сторона курловых хитростей: можно просто написать (обратите внимание на одиночные кавычки)curl -L -O 'https://example.org/path/to/file-[1-20].tar.gz'
и curl сам разберётся со счётчиком, и скачает все 20 файлов один за другим. Кавычки нужны не для curl (он их как раз не увидит), а чтобы bash (думаю, ровно то же применимо и к zsh) даже не пытался сам раскрывать шаблоны с квадратными скобками, а просто достал содержимое из одиночных кавычек, и передал как ровно один аргумент запускаемой программе. Кроме приведённого выше примера у curl есть ещё несколько аналогичных вариантов раскрытия одного урла в последовательность — они удобно указаны прямо в начале man-страницы.
А вот ещё одна байка на стыке двух предыдущих тем, но совсем свежая: о том, как я решил сохранить локально подборку iso-образов с исходниками Debian (суммарно около 90 гигов). И в тот момент мне показалось логичным (наверное, это нифига не логично, но тогда показалось), что торренты торрентами, пусть файлы не валяются без дела, а раздаются, но вот скачать не столь популярные образы будет эффективнее по HTTP с географически близкого зеркала, вот прям curl-ом, ага, такой вот принудительный web seed.
Поскольку любым способом качать пришлось бы долго, запустил команду наподобие такой
с помощью стандартной утилиты
И вот я запустил загрузку, потом периодически смотрел прогресс и видел там отчёты вроде
Вывод прямо, как "в прямом эфире", только последняя строчка (текущий загружаемый файл) не обновляется каждую секунду, ведь это всего лишь напечатанный в терминал файл, каким он был на момент печати. Ещё удивлялся, какой curl умный: проверил, что дескрипторы stdout/stderr указывают на обычный файл (с помощью isatty что ли?..) и стал аккуратно позиционироваться в
Секрет фокуса оказался в том, что фокус мне никто не показывал и даже не собирался: файл на полсотни отображаемых строк весил мегабайт с лишним. Оказывается, curl просто печатал форматированный вывод в выходной поток, не смотря ни на что, прямо со всеми "вернись к началу строки и напиши заново" каждую секунду. Когда же я в очередной раз выполнял
Поскольку любым способом качать пришлось бы долго, запустил команду наподобие такой
nohup curl --limit-rate 3M -L -O 'https://...-source-DVD-[1-20].iso' &
с помощью стандартной утилиты
nohup обезопасив curl от завершения вместе с командной оболочкой при отключении от ssh. Важно, что при этом утилита nohup перенаправляет вывод программы в файл nohup.out в текущем каталоге.И вот я запустил загрузку, потом периодически смотрел прогресс и видел там отчёты вроде
# cat nohup.out
% Total % Received % Xferd Average Speed Time Time Time Current
Dload Upload Total Spent Left Speed
100 4476M 100 4476M 0 0 3075k 0 0:24:50 0:24:50 --:--:-- 3078k
% Total % Received % Xferd Average Speed Time Time Time Current
Dload Upload Total Spent Left Speed
100 4481M 100 4481M 0 0 3075k 0 0:24:51 0:24:51 --:--:-- 3073k
...
Вывод прямо, как "в прямом эфире", только последняя строчка (текущий загружаемый файл) не обновляется каждую секунду, ведь это всего лишь напечатанный в терминал файл, каким он был на момент печати. Ещё удивлялся, какой curl умный: проверил, что дескрипторы stdout/stderr указывают на обычный файл (с помощью isatty что ли?..) и стал аккуратно позиционироваться в
nohup.out, обновлять строчку текущей загрузки, никакой тебе мешанины, как у некоторых, когда на каждую десятую процента выводится новая строка...Секрет фокуса оказался в том, что фокус мне никто не показывал и даже не собирался: файл на полсотни отображаемых строк весил мегабайт с лишним. Оказывается, curl просто печатал форматированный вывод в выходной поток, не смотря ни на что, прямо со всеми "вернись к началу строки и напиши заново" каждую секунду. Когда же я в очередной раз выполнял
cat nohup.out, мегабайт текста вперемешку с escape-последовательностями в очередной раз оказывался отправлен по SSH в "программу-терминал" (или, говоря корректнее, эмулятор терминала) на "большой компьютер", которая в очередной раз интерпретировала мегабайтный вывод курла, отображая в конечном итоге полсотни строк текста. Теперь мне уже интересно, почему НЕ все программы ведут себя столь же прозрачно, и могут формировать много повторяющихся строк при перенаправлении вывода. Наверное, они пытаются поступать правильно и работать с произвольным терминалом через какой-нибудь ncurses, а не захардкоженные ANSI escape sequences, и получив от библиотеки ответ, что это не терминал, а вообще непойми что, сами откатывались на "некрасивый" формат вывода.Последнее время я начал присматриваться, как вообще можно подступиться к использованию нейросетей в быту, но, разумеется так, чтобы локальный запуск, безопасность, все дела... Так вот, один из известных "производителей" LLM, пригодных для локального инференса — французская Mistral AI, но, конечно, пообщаться с её нейронками можно и в облаке при помощи продукта под названием le Chat. И меня веселит это название, поскольку в школе я сначала изучал французский, и до сих пор помню, что "le chat" — это "кот" (произносится как "лё ша"). В общем, начитавшись новостей о всех этих новомодных тайминг-атаках на кэш процессора и историй о переполнении буфера в firmware сетевой карты, я уже давно ценю замечательное изречение: "Паранойя — это не когда разговариваешь только со своим котом, паранойя — это когда при коте боишься сболтнуть лишнего". А тут оно прямо заиграло новыми красками.
Знаете ли вы?
... что среди RFC-документов, описывающих многие важные стандарты, существует RFC 2119, определяющий смысл терминов MUST (NOT) / SHOULD (NOT) / MAY, используемых в других документах. Более того, есть также RFC 8174, разъясняющий, что RFC 2119 относится только к терминам, написанным заглавными буквами, а остальные слова подчиняются обычным правилам английского языка.
Последующие документы просто ссылаются на эти разъяснения:
... что среди RFC-документов, описывающих многие важные стандарты, существует RFC 2119, определяющий смысл терминов MUST (NOT) / SHOULD (NOT) / MAY, используемых в других документах. Более того, есть также RFC 8174, разъясняющий, что RFC 2119 относится только к терминам, написанным заглавными буквами, а остальные слова подчиняются обычным правилам английского языка.
Последующие документы просто ссылаются на эти разъяснения:
1.1. Conventions Used in This Document
The key words "MUST", "MUST NOT", "REQUIRED", "SHALL", "SHALL NOT", "SHOULD", "SHOULD NOT", "RECOMMENDED", "NOT RECOMMENDED", "MAY", and "OPTIONAL" in this document are to be interpreted as described in BCP 14 [RFC2119] [RFC8174] when, and only when, they appear in all capitals, as shown here.
...
Win2000 на раритетном ПК, ностальгия... А вот и нет! Свежайшая OpenSUSE MicroOS, ставящаяся на малюсенький одноплатник с управлением установкой через USB-UART converter. Универсальный iso-шник для AArch64 систем на базе UEFI, между прочим (современный U-Boot предоставляет минимальный набор UEFI-сервисов, достаточный для передачи управления загрузчику с воткнутой в одноплатник флешки — практически, как на x86_64 десктопе).
🔥2
#знаетеливы что "в линуксе" можно запретить изменять некоторый файл даже пользователю root. Поддержка разнится от ФС к ФС, но как минимум на Ext4 и Btrfs присутствует. При помощи утилиты chattr и админских прав можно выставить флаг immutable:
chattr +i /path/to/file, и теперь даже root не сможет изменить файл (пока не снимет этот атрибут при помощи chattr -i ...).A file with the 'i' attribute cannot be modified: it cannot be deleted or renamed, no link can be created to this file, most of the file's metadata can not be modified, and the file can not be opened in write mode. Only the superuser or a process possessing the CAP_LINUX_IMMUTABLE capability can set or clear this attribute.
🔥1
#загадка
Решил на праздниках почистить старые ветки в локальных копиях репозиториев. Причём локальных копий у меня обычно по две штуки — по одной на каждом из компьютеров (а иногда и не по одной, но не будем о грустном), поэтому помимо задачи "удалить то, что давно влито в main / заменено лучшим решением / ещё почему-то не пригодилось" есть ещё "удалить ненужные дубликаты между локальными клонами репозиториев" (ну или осознано сделать резервную копию того, что важно). И вот смотрю я на один такой дубликат (назовём его feature-branch) и вижу, что более свежая версия синхронизирована с моим личным форком на гитхабе (назовём его my-fork). Что же, одной проблемой меньше: нет надобности возиться с fetch-ем между машинами (об этом как-нибудь потом), можно будет просто сравнить более старую версию ветки с той, что с гитхаба.
И тут началось странное: я зашёл на вторую машину по ssh и обнаружил, что старая версия тоже совпадает с
В итоге я всё-таки вспомнил об одной особенности git с настройками по умолчанию, зашёл на гитхаб через веб, чтобы убедиться, и действительно: ...
... ветку на гитхабе я уже удалил ранее, а git fetch my-fork с настройками по умолчанию передвигает в локальном "зеркале" my-fork/<branch-name> указатели тех веток, которые всё ещё присутствуют в удалённом репозитории — даже, если это будет force update — но вот чтобы он удалял "удалённо-пропавшие" ветки и из локального "зеркала", нужно указать опцию --prune. А ещё есть опция --dry-run, чтобы сначала посмотреть, что будет удалено.
Ну и дисклеймер: сначала стоит почитать документацию и прикинуть, как опция --prune будет работать с вашим репозиторием и вашим workflow: может, не просто так её не включили по умолчанию.
Решил на праздниках почистить старые ветки в локальных копиях репозиториев. Причём локальных копий у меня обычно по две штуки — по одной на каждом из компьютеров (а иногда и не по одной, но не будем о грустном), поэтому помимо задачи "удалить то, что давно влито в main / заменено лучшим решением / ещё почему-то не пригодилось" есть ещё "удалить ненужные дубликаты между локальными клонами репозиториев" (ну или осознано сделать резервную копию того, что важно). И вот смотрю я на один такой дубликат (назовём его feature-branch) и вижу, что более свежая версия синхронизирована с моим личным форком на гитхабе (назовём его my-fork). Что же, одной проблемой меньше: нет надобности возиться с fetch-ем между машинами (об этом как-нибудь потом), можно будет просто сравнить более старую версию ветки с той, что с гитхаба.
И тут началось странное: я зашёл на вторую машину по ssh и обнаружил, что старая версия тоже совпадает с
my-fork/feature-branch. Ну, давно не делал fetch из удалённого репозитория, сейчас запущу git fetch my-fork и... ой, а ничего не поменялось! Ещё раз сделал git fetch my-fork на каждой из машин, перепроверил написание имени ветки в обоих репозиториях — глухо: и там, и там git log feature-branch показывает по две метки (локальную и my-fork/feature-branch) на головном коммите. Но коммиты-то разные!В итоге я всё-таки вспомнил об одной особенности git с настройками по умолчанию, зашёл на гитхаб через веб, чтобы убедиться, и действительно: ...
Ну и дисклеймер: сначала стоит почитать
Ну и в продолжение вчерашнего: #знаетеливы что "удалённый репозиторий" в разнообразных "сетевых" командах git (clone, fetch, push, ...) — это не обязательно гитхаб не обязательно какой-то хитрый сервер, настроить который под силу только профессиональному админу. Речь о неожиданно банальных, бытовых конфигурациях, от использования которых отделяет, скорее, незнание, что "а так можно было". Ключевая особенность — не надо настраивать никаких серверов.
Командукто бы мог подумать уже занято:
В новом репозитории
Если же у вас есть доступная по ssh машина
и не надо ни на какие гитхабы сначала загружать, потом выгружать — всё напрямую.
Наконец, вместо использования URL со схемой
Команду
git clone можно "натравить" на самую обычную локальную копию репозитория: вместо git clone git@github.com:username/repo можно выполнить git clone file:///home/username/projects/repo (с абсолютным путём к каталогу репозитория и тремя слешами после file:). Ну разве что, если оригинал и копия находятся в одном каталоге, то придётся передать git clone ещё один аргумент: "куда клонировать" — просто потому, что последний компонент исходного пути будет именем клона по умолчанию, а оно $ cd ~/projects
$ git clone file:///home/username/projects/repo
fatal: destination path 'repo' already exists and is not an empty directory.
$ git clone file:///home/username/projects/repo new-repo-copy
Cloning into 'new-repo-copy'...
В новом репозитории
git remote -v будет честно указывать file:///... в качестве origin. Ну и, конечно, никто не мешает регистрировать новые remotes с локальными URL и работать с ними, как с любыми другими:$ git remote add my-local-fork file:///home/username/projects/second-repo
$ git fetch my-local-fork
Если же у вас есть доступная по ssh машина
other-desktop, на которую вы заходите как remoteuser, то можно взаимодействовать с репозиториями оттуда:$ git clone ssh://remoteuser@other-desktop:/path/to/repo
и не надо ни на какие гитхабы сначала загружать, потом выгружать — всё напрямую.
Наконец, вместо использования URL со схемой
file:// можно и просто указать локальный путь. Насколько я понимаю, отличие будет в том, что при указании источника по URL в новом клоне в "скрытом" каталоге .git будут "загруженные" копии исходных объектов, а при использовании обычного пути к каталогу — по умолчанию будут создаваться жёсткие ссылки на исходные файлы, если возможно. С одной стороны — экономия места, с другой — если сильно поломать оригинал, есть возможность разломать и копию: в общем, думайте сами, решайте сами, читайте документацию.❤1
Можно, кстати, даже именованных remotes не добавлять, а вместо этого передавать URL целиком прямо в
git fetch! Правда, тогда придётся проявлять некоторую ловкость, чтобы добраться до результата — локального "зеркала" удалённых веток никто уже создавать не будет:$ git fetch file:///some/repo
remote: Enumerating objects: 1234, done.
...
From file:///some/repo
* branch HEAD -> FETCH_HEAD
$ git log FETCH_HEAD
... тут какие-то коммиты ...
$ git fetch file:///some/repo other-remote-branch
remote: Enumerating objects: 4321, done.
...
From file:///some/repo
* branch other-remote-branch -> FETCH_HEAD
$ git log FETCH_HEAD
... тут какие-то другие коммиты ...
Неочевидно, но факт: радиус кривизны рук — это величина типа "больше — лучше".
Давно было пора рассказать о полезном проекте ESPHome, да как-то хорошего примера не попадалось. И вот на новогодних праздниках пример наконец-то появился. Рассказ будет состоять из двух частей: костыльной аппаратной и, собственно, красивой программной на ESPHome.
Недавно я купил кольцевую лампу с питанием от USB и закрепил её за монитором. Днём позади монитора у меня окно, а вечером позади монитора у меня темно и грустно, так что лампа сглаживает контраст между ярким экраном и "фоном" шторы.
Я уже давно использую "умную" лампу (обычную "свечу" с цоколем E14) в качестве светового будильника: HomeAssistant получает с моего смартфона время срабатывания ближайшего будильника и постепенно зажигает лампу за 20 минут до. Вот я и решил попрактиковаться, и организовать управление замониторной лампой из HA, чтобы её пульт на столе не валялся.
План заключался в том, чтобы аккуратно (ну, почти) вывести из пульта лампы питание USB и необходимые сигналы, и присоединить к мелкой плате с ESP32 вроде этой — в общем, задача на полдня (ага, щаз...):
* GND и 5V питания от USB
* "входные" сигналы для четырёх кнопок ("вкл-выкл", "тёплый-холодный свет", "яркость +" и "яркость -")
* "выходные" сигналы: питание "тёплой" и "холодной" секций светодиодов для обратной связи (иначе я бы смог только "удалённо клацать по кнопкам" — такой себе интерфейс).
#esphome
Недавно я купил кольцевую лампу с питанием от USB и закрепил её за монитором. Днём позади монитора у меня окно, а вечером позади монитора у меня темно и грустно, так что лампа сглаживает контраст между ярким экраном и "фоном" шторы.
Я уже давно использую "умную" лампу (обычную "свечу" с цоколем E14) в качестве светового будильника: HomeAssistant получает с моего смартфона время срабатывания ближайшего будильника и постепенно зажигает лампу за 20 минут до. Вот я и решил попрактиковаться, и организовать управление замониторной лампой из HA, чтобы её пульт на столе не валялся.
План заключался в том, чтобы аккуратно (ну, почти) вывести из пульта лампы питание USB и необходимые сигналы, и присоединить к мелкой плате с ESP32 вроде этой — в общем, задача на полдня (ага, щаз...):
* GND и 5V питания от USB
* "входные" сигналы для четырёх кнопок ("вкл-выкл", "тёплый-холодный свет", "яркость +" и "яркость -")
* "выходные" сигналы: питание "тёплой" и "холодной" секций светодиодов для обратной связи (иначе я бы смог только "удалённо клацать по кнопкам" — такой себе интерфейс).
#esphome
AliExpress
Estardyn ESP32 C3 Super Mini плата на AliExpress
Смотри, что есть на AliExpress! И еще более 200 миллионов товаров со всего мира с возможностью поиска по фото и большими скидками. Погрузись в увлекательный мир шопинга без границ!
Итак, часть аппаратная, костыльная.
И сразу не лишним будет напомнить о технике безопасности: да, на USB пять вольт (если не быстрая зарядка). Но это между GND и Vbus пять вольт, а относительно, например, радиатора центрального отопления там может быть 100-200 вольт, просто ток микроскопический, если блок питания исправен. Несколько лет назад я осознал это внезапно, когда возился с какой-то подключённой к USB платой и ткнул ногой в тёплую батарею. Тут-то палец слегка кольнуло. К счастью, БП компьютера был исправен, так что ток оказался мизерный.
Ну так вот, методом осторожного тыка мультиметром я выяснил, что "управляющие" выводы "пультовой" микросхемы подтянуты к 5V, а нажатие кнопки замыкает их на GND. Питание светодиодов тоже между 0 и 5V. Вот и первая проблема: судя по документации, ESP32 не 5V tolerant. Ну, со входом (со стороны ESP32) сигнала питания светодиодов проблем нет, поможет делитель напряжения: ставим два резистора на десять килоом, посередине снимаем сигнал 0-2.5V. С выдачей сигнала на кнопки сложнее: сам я тривиальной схемы не придумал, а "в интернетах" тоже советуют либо забить, поставить токоограничительный резистор, и надеяться, что ESP32 не сгорит, либо соорудить нехитрый конвертер уровней. Я выбрал второй вариант, решив не срезать углы в учебном проекте, и вместо этого заняться традиционной русской забавой: новогодним поиском транзисторов.
Пожалуй, не буду строить из себя специалиста-электронщика и рекомендовать кому-то конкретный дизайн своих костылей, скажу лишь, что на каждую кнопку у меня приходится по одному полевому транзистору BSS138 и одному резистору (между истоком и затвором, чтобы статикой не побило), а питание каждого из двух каналов светодиодов измеряется при помощи делителя на двух 10k резисторах и сглаживающего фильтра из конденсатора и ещё одного резистора.
#esphome
И сразу не лишним будет напомнить о технике безопасности: да, на USB пять вольт (если не быстрая зарядка). Но это между GND и Vbus пять вольт, а относительно, например, радиатора центрального отопления там может быть 100-200 вольт, просто ток микроскопический, если блок питания исправен. Несколько лет назад я осознал это внезапно, когда возился с какой-то подключённой к USB платой и ткнул ногой в тёплую батарею. Тут-то палец слегка кольнуло. К счастью, БП компьютера был исправен, так что ток оказался мизерный.
Ну так вот, методом осторожного тыка мультиметром я выяснил, что "управляющие" выводы "пультовой" микросхемы подтянуты к 5V, а нажатие кнопки замыкает их на GND. Питание светодиодов тоже между 0 и 5V. Вот и первая проблема: судя по документации, ESP32 не 5V tolerant. Ну, со входом (со стороны ESP32) сигнала питания светодиодов проблем нет, поможет делитель напряжения: ставим два резистора на десять килоом, посередине снимаем сигнал 0-2.5V. С выдачей сигнала на кнопки сложнее: сам я тривиальной схемы не придумал, а "в интернетах" тоже советуют либо забить, поставить токоограничительный резистор, и надеяться, что ESP32 не сгорит, либо соорудить нехитрый конвертер уровней. Я выбрал второй вариант, решив не срезать углы в учебном проекте, и вместо этого заняться традиционной русской забавой: новогодним поиском транзисторов.
Пожалуй, не буду строить из себя специалиста-электронщика и рекомендовать кому-то конкретный дизайн своих костылей, скажу лишь, что на каждую кнопку у меня приходится по одному полевому транзистору BSS138 и одному резистору (между истоком и затвором, чтобы статикой не побило), а питание каждого из двух каналов светодиодов измеряется при помощи делителя на двух 10k резисторах и сглаживающего фильтра из конденсатора и ещё одного резистора.
#esphome
Теперь часть красивая, программная. Иногда в интернете можно встретить ругань на скетчи Arduino, мол непрофессионально это всё. Что ж, не будем писать кривой-косой код в Arduino IDE. Вместо этого будем, как взрослые серьёзные люди, код вообще не писать, а вместо него пользоваться YAML-конфигурацией!
ESPHome — это такой инструмент, который на вход принимает очень компактное декларативное описание подключённой "периферии" и требуемые автоматизации (а в простейшем случае — просто выставляет "крутилочки" наружу). Потом вы передаёте этот YAML питоновской тулзе, которая сама генерирует требуемый код "прошивки" на C, скачивает тулчейн для требуемого микроконтроллера и заливает прошивку (а при последующем обновлении даже можно заливать прошивку "по воздуху"!). Как говорится, просто откиньтесь на спинку табуретки :)
... и вот уже esphome знает 1) какое у нас железо и, соответственно, какой компилятор качать, 2) к какой сети подключаться и 3) как представиться местному DHCP-серверу. Плюс прошивка будет печатать стандартные логи. Но правильнее будет создать отдельный файл
а в основной конфигурации (которую можно положить, например, в гит) указать
Каждый возможный параметр я описывать не буду, поскольку есть довольно удобная документация.
Теперь можно каким-нибудь способом установить питоновский пакет
Прошиваем (текстовый скринкаст):
#esphome
ESPHome — это такой инструмент, который на вход принимает очень компактное декларативное описание подключённой "периферии" и требуемые автоматизации (а в простейшем случае — просто выставляет "крутилочки" наружу). Потом вы передаёте этот YAML питоновской тулзе, которая сама генерирует требуемый код "прошивки" на C, скачивает тулчейн для требуемого микроконтроллера и заливает прошивку (а при последующем обновлении даже можно заливать прошивку "по воздуху"!). Как говорится, просто откиньтесь на спинку табуретки :)
esp32:
board: esp32-c3-devkitm-1
framework:
type: esp-idf
wifi:
domain: .lan
fast_connect: true
ssid: "mywifinetwork"
password: "RovOgsovhed9" # ... is not my password :)
esphome:
name: monitor-lamp
logger:
# тут пусто
... и вот уже esphome знает 1) какое у нас железо и, соответственно, какой компилятор качать, 2) к какой сети подключаться и 3) как представиться местному DHCP-серверу. Плюс прошивка будет печатать стандартные логи. Но правильнее будет создать отдельный файл
secrets.yamlwifi_ssid: "mywifinetwork"
wifi_password: "RovOgsovhed9"
а в основной конфигурации (которую можно положить, например, в гит) указать
wifi:
domain: .lan
fast_connect: true
ssid: !secret wifi_ssid
password: !secret wifi_password
Каждый возможный параметр я описывать не буду, поскольку есть довольно удобная документация.
Теперь можно каким-нибудь способом установить питоновский пакет
esphome (я для этих целей относительно недавно открыл для себя pipenv) и подключить плату по USB для первоначальной прошивки. Важно: в описании моей платы явно указано, что нужно питать плату либо от USB, либо "через пины", но не одновременно двумя способами.Прошиваем (текстовый скринкаст):
pipenv run esphome run backlight-lamp.yaml
#esphome
asciinema.org
ESPHome initial flashing
Recorded by atrosinenko
Итак, минимальная конфигурация готова. Поскольку, как я уже говорил, при подключении к лампе мне придётся отключить питание от USB, то и перепрошивать придётся, постоянно переключаясь туда-сюда. Поэтому сразу добавим OTA-обновления и "api" (без него, как я понял, не получится по сети прочитать лог работы прошивки). Ещё раз перешьём по кабелю:
Теперьпитание компьютера тьфу, то есть USB-кабель можно отключить и подключить микроконтроллер к лампе. Вроде, с моей схемой подключения не должно быть фатальных проблем при любой начальной конфигурации пинов, хоть на вход, хоть на выход. Впрочем, если бы проблемы могли возникать, наверное, всё равно могло бы сложиться состояние гонки в момент начальной инициализации (кажется, документация на взаимоисключающие переключатели говорит примерно о том же).
Итак, плата при подключении не сгорела, аппаратные кнопки на пульте также работают штатно. Попробуем снять показания АЦП: добавим в конфиг первое описание сенсора и перешьем:
Теперь в логах можно наблюдать изменение яркости:
Светодиоды в лампе подключены с общим анодом, а измеряем мы, соответственно, напряжение на катодах, поэтому максимальная яркость — это 0, а минимальная, получается, примерно 1.3V (кажется, с расчётом то ли делителя напряжения, то ли фильтра, я всё-таки налажал...).
#esphome
api:
ota:
- platform: esphome
port: 4321
password: !secret ota_password
Теперь
Итак, плата при подключении не сгорела, аппаратные кнопки на пульте также работают штатно. Попробуем снять показания АЦП: добавим в конфиг первое описание сенсора и перешьем:
sensor:
- platform: adc
pin: GPIO03
update_interval: 1s
attenuation: auto
id: adc_led_warm
name: "Warm LEDs output level"
Теперь в логах можно наблюдать изменение яркости:
pipenv run esphome logs backlight-lamp.yaml
Светодиоды в лампе подключены с общим анодом, а измеряем мы, соответственно, напряжение на катодах, поэтому максимальная яркость — это 0, а минимальная, получается, примерно 1.3V (кажется, с расчётом то ли делителя напряжения, то ли фильтра, я всё-таки налажал...).
#esphome
asciinema.org
ESPHome ADC readings
Recorded by atrosinenko
Осталось научиться работать с кнопками. Тут описание в ESPHome состоит из двух частей: компонента GPIO Output и самой Generic Output Button:
Вот только как на эту кнопку нажать? Если есть настроенный сервер Home Assistant, то добавить в него новое устройство и нажимать :) А если нет? Тогда просто добавляем на верхний уровень конфига
Остались мелочи: добавить в конфиг встроенный в ESP32 датчик температуры и распаянный на плате светодиод:
и пересчитывать в проценты значения измеренной яркости (обратите внимание на именованную ссылку
Если честно, пока всё работает не настолько гладко, как хотелось бы, и кое-что придётся ещё отладить, но уже "за кадром". Впрочем, думаю, мне удалось хотя бы немного продемонстрировать выразительность ESPHome.
#esphome
output:
- platform: gpio
pin: GPIO20
id: pin_button_on_off
button:
- platform: output
name: "Power on/off"
output: pin_button_on_off
duration: 100ms
Вот только как на эту кнопку нажать? Если есть настроенный сервер Home Assistant, то добавить в него новое устройство и нажимать :) А если нет? Тогда просто добавляем на верхний уровень конфига
web_server: с настройками по умолчанию, и можно заходить в браузере на http://backlight-lamp.lan/, вот так вот просто!Остались мелочи: добавить в конфиг встроенный в ESP32 датчик температуры и распаянный на плате светодиод:
sensor:
- platform: internal_temperature
name: "Internal temperature"
output:
- platform: gpio
pin:
number: GPIO08
inverted: true
id: pin_status_led
light:
- platform: binary
name: "Status LED"
output: pin_status_led
и пересчитывать в проценты значения измеренной яркости (обратите внимание на именованную ссылку
led_output_filters — это стандартная возможность в yaml):- platform: adc
# ...
name: "Warm LEDs output level"
unit_of_measurement: "%"
accuracy_decimals: 0
filters: &led_output_filters
- calibrate_linear:
- 0.0 -> 100.0
- 1.3 -> 0.0
- clamp:
min_value: 0.0
max_value: 100.0
- platform: adc
# ...
name: "Cold LEDs output level"
unit_of_measurement: "%"
accuracy_decimals: 0
filters: *led_output_filters
Если честно, пока всё работает не настолько гладко, как хотелось бы, и кое-что придётся ещё отладить, но уже "за кадром". Впрочем, думаю, мне удалось хотя бы немного продемонстрировать выразительность ESPHome.
#esphome