Заметки бессистемного программиста
13 subscribers
70 photos
112 links
Download Telegram
Раз уж сегодня День Дурака, похвастаюсь своими недавним достижением из рубрики "я сделяль": колпачок для разъёма XT60, отлитый из термоклея. По моему, идея повторить призматическую форму разъёма, изготовив опалубку из малярного скотча — это достаточно, ну эээ... празднично. Не то, чтобы такой разъём был необходим, чтобы запитать Orange Pi 5 (это как раз тот самый #маленький_но_гордый_сервер), но просто я уже задолбался экспериментировать со способами уменьшить падение напряжения (нет, на OPi5 всё нормально с питанием через Type-C, просто захотелось избавиться от мешанины кабелей).

Disclaimer: если что, я ни в коем случае не призываю подобное повторять. Тем более там, где разъёмы на десятки ампер действительно необходимы. Просто конкретно мне конкретно в этом случае такой способ показался допустимым (колпачка в комплекте не было, надеть термоусадку и не усадить её при пайке было проблематично, а сооружать комок хитро подсунутой изоленты не хотелось).
Захотел купить более удобное жало для паяльника, решил загуглить, что именно подразумевается под обозначением "900M-T-I" (запрос содержал конкретно эту строку без других слов), а гугл мне предложил помочь с математическими задачками (что бы под этим ни подразумевалось). Такой формат "быстрых ответов" я увидел впервые.
Из примера на cppreference:

class ThreadsafeCounter
{
mutable std::mutex m; // The "M&M rule": mutable and mutex go together
int data = 0;
// ...
}
🔥3
На Алиэкспрессе каталог каждого продавца может быть сгруппирован по категориям, и вот сразу у нескольких продавцов радиодеталей обнаружилась загадочная категория с названием "повел". В принципе, иногда даже "технические" аббревиатуры хорошо звучат, например, "мосфет", а уж "маркетинговый" термин тем более будет красивым. Хоть я и не планировал покупать загадочные по́велы, но интересно же, что так назвали — наверное, какую-нибудь мудрёную и современную микросхему, подающую питание на что-нибудь ещё более заковыристое и высокотехнологичное — ну, там, от power и electronics...

Почему-то в этой категории были просто какие-то светодиоды. Ну, думаю, может особый тип для каких-нибудь умных светодиодных лент... А потом до меня дошло: светодиод — это же грамматическая форма свинца: lead / led / LED. Ну а в названии категории потерялись точки над "ё".
2
Загадка: что может содержать файл с именем proton-pack.c в ядре Linux?
Ответ: противодействие уязвимостям типа Spectre 😀
Пояснение в комментарии в "шапке": "If there's something strange in your neighbourhood, who you gonna call?"
В копилку научного юмора и искусства в целом: Dance Your PhD 2025. Если обладатели главного приза кажутся слишком шумными, то вот, например, красивый спокойный клип про лазерное охлаждение атомов до сверхнизких температур что бы это ни значило :)
Обновил телефон (с Самсунга обратно на Моторолу и прекрасно себя чувствую), обновил ОС на телефоне. После обновления до Android 15 захотел поставить из F-Droid уже упоминавшийся на этом канале SatStat, да не тут то было: оказалось, что в Android 15 слишком старые приложения поставить нельзя (документация). Ну, то есть, не совсем так: во-первых, судя по всё той же странице документации, гайки начали закручивать ещё в Android 14. Во-вторых, поставить приложение всё же можно — с компьютера.

Итак, что я наблюдаю в F-Droid: плашка "Это приложение было разработано для старой версии Андроид..." (что пока ещё ничего не означает: у самого приложения F-Droid, через которое я смотрю каталог, в собственной карточке такая же плашка и ничего) и сказано, что совместимых с моим устройством версий нет. Кнопки "установить", соответственно, тоже. Проверяю предположение: из F-Droid на телефоне перекидываю через любой мессенджер ссылку на приложение себе же в браузер на компьютере. Из списка версий выбираю какую-нибудь посвежее, скачиваю APK-файл и пробую установить через ADB (добавлены переводы строк, чтобы не так сильно растягивать текст по горизонтали):
$ adb install /tmp/com.vonglasow.michael.satstat_30600.apk
Performing Streamed Install
adb: failed to install /tmp/com.vonglasow.michael.satstat_30600.apk:
Failure [INSTALL_FAILED_DEPRECATED_SDK_VERSION: App package must target
at least SDK version 24, but found 23]

Ага, то самое изменение из Android 15. По совету из всё той же документации добавляю опцию --bypass-low-target-sdk-block:
$ adb install --bypass-low-target-sdk-block /tmp/com.vonglasow.michael.satstat_30600.apk
Performing Streamed Install
Success

Работает! Едва ли я бы хотел таким способом ставить что-то из Google Play — хотя бы потому, что не совсем понятно, откуда брать "эталонный" APK, который точно-точно без дополнительных троянов, да и при наличии альтернатив, возможно, стоит поискать какое-нибудь менее заброшенное приложение. Но к программам из репозитория* F-Droid лично у меня доверия больше, а правильный APK можно без лишних плясок с бубном скачать по удобной ссылке. В общем, опция удобная полезная, но использовать нужно на свой страх и риск: здесь никто за вас новому apk подпись не проверит.

* Почему важно уточнение именно о "репозитории" F-Droid — потому что существуют сторонние репозитории в том же формате, их так же можно добавлять в официальный клиент F-Droid, но это будут уже совершенно независимые, не контролируемые и не проверяемые авторами F-Droid источники ПО.

#android
Кстати, похоже, что в Андроиде есть разрешения, которые в принципе доступны и без рутования телефона, но выдаваться должны через ADB, а не обычный пользовательский интерфейс (тут, впрочем, нужно оговориться, что я совсем не Андроид-разработчик, и, например, не вполне уверен, что можно заявлять о неком стандартном интерфейсе управления правами, хотя в документации я и вижу достаточно чёткое разделение на normal, runtime a.k.a. dangerous permissions и ряд других типов).

Например, в репозитории F-Droid есть приложение LogFox, которое при первом запуске просит выдать права через ADB:
adb shell pm grant com.f0x1d.logfox android.permission.READ_LOGS

После чего при начале записи логов система переспросит уже на экране устройства, хотим ли мы позволить приложению читать "глобальные" логи. Если разрешили, можно видеть, например, такое:
libPowerHal: [setClusterFreq] system_server: sysfs_freq set cpu freq: 1750000 -1 1850000 -1

Или вот, например, SaverTuner: при первом запуске ждёт, пока ему не выдадут разрешение:
adb shell pm grant s1m.savertuner android.permission.WRITE_SECURE_SETTINGS

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

Лично я для себя пока не нашёл "полезный в быту" пермишн и приложение, его использующее, но сам факт существования таких разрешений кажется мне полезным знанием.

#android
Первого апреля я уже хвастался колпачком для разъёма XT60, отлитым из термоклея. И вот недавно история получила не менее первоапрельское продолжение... Как я уже говорил, столь суровый разъём требовался, чтобы подключить одноплатник, питающийся от пяти вольт, без значительного падения напряжения на разъёме, потому что со штекерами-бочонками я к тому моменту уже намучился. Но кроме одноплатника линии 5 и 12 вольт шли ещё и на "метеостанцию" на ESPHome, роутер и ethernet-свич, поэтому мешанина проводов была та ещё. Я захотел хоть как-то это смотать и убрать в обычную электромонтажную распределительную коробку, а чтобы упростить себе перекоммутацию в стеснённых условиях, повесить эту коробку на край столешницы при помощи примерно таких крючков с Алиэкспресса.

Начитавшись отзывов, мол, "крючки хорошие, но мне пришли без липучек", осмотрел то, что прислали мне. С грустью отметил, что у меня тоже просто крючки, и стал лепить на двусторонний скотч. А они, заразы, ещё и как из вредности к скотчу не липнут... Ладно, к распределительной коробке с горем пополам прилепил на термоклей (который, разумеется, тоже отваливался, но я упорный, я через край чисто механически прихватил, а потом долго подрезал ножом, чтобы нигде не клинило...). Но не буду же я к столу ответную часть тоже на термоклей лепить... Тут я вспомнил про сантехнический скотч, решил сделать из него кольцо клеевой стороной наружу и хоть как-то уже прилепить ответную часть крючка.

Отклеил от мотка скотча небольшой участок, приложил туда крючок... а он отваливается!!! Да вы что, издеваетесь, что же это за крючки такие, что их даже чудо-скотч не берёт?!? И тут-то я пригляделся и заметил, что на скотче осталась прозрачная плёночка... 🤦‍♂️ В общем, всё нормально: честь сантехнического скотча восстановлена, а я усвоил урок, что если тебе кажется, что некая поверхность отваливается от двустороннего скотча, термоклея и прочих нормальных липучек, словно специально спроектирована, чтобы легко отваливаться от любого клея, то возможно, тебе не кажется. После снятия защитной плёнки "предустановленный" клеевой слой крючка благополучно прилип, куда следовало.
Недавно я решил приобщиться к современным технологиям и обновить свой роутер до чего-нибудь на AArch64 (64-битном ARM) вместо MIPS, и с поддержкой нового wi-fi стандарта "ax". Как оказалось, относительно недорогие устройства в продаже есть, в том числе и с критичной для меня возможностью поставить OpenWRT, но почти у всех 4 ethernet-порта, а не 5, как было на моих прошлых роутерах. Это проблема: один порт WAN, ещё два — мой настольный компьютер и провод к powerline-адаптеру, ещё один — на торрентораздавалку, а хочется иметь запасной порт "на мелкие расходы": временно какой-нибудь raspberry pi подключить для настройки, например.

Требований к устройству у меня было и без того немало, а критерий наличия 5+ ethernet-портов портил картину окончательно. В общем, небольшой лайфхак: вычеркнуть из списка требований количество портов (ну, на самом деле, снизить его до безобидных 3+) поможет неуправляемый ethernet-коммутатор — что-то вроде вот такого устройства. Подобный коммутатор — штука довольно простая, не требующая и, как минимум в моём случае, даже не допускающая настройки: просто воткнул сетевые кабели, подал питание, и через мгновение она уже пуляет входящие ethernet-фреймы в те порты, где последний раз видели нужный MAC-адрес — никаких тебе линуксов, веб-интерфейсов настройки и долгой загрузки при включении. В итоге требование на минимальное количество ethernet-портов роутера снизилось до 3, то есть количества зон файрвола: WAN, LAN и "публично доступные серверы". Правда, потребуется запитать ещё одно устройство от розетки — для меня это, к счастью, оказался пройденный этап, но это уже совсем другая история...
👍1
В только что лет уяснил для себя очевидный "опосля" факт: понадобилось купить монитор, и по такому случаю решил проэкспериментировать, и вместо 24" 1080p@60Hz купить 27" 1440p@100Hz (увеличение диагонали — не самоцель, просто достаточно бюджетных 24" с такими параметрами я не нашёл, а так бы только рад был ещё большей плотности пикселей).

Иногда в обсуждениях мониторов с большими диагоналями или частотами обновления я видел сетования на то, что придётся на новую видеокарту раскошеливаться, но проходил мимо с мыслями: "Ну мне-то не для игр, мне чтобы за целый день от глядения в текст глаза не уставали, а с отрисовкой интерфейсов даже встроенный видеочип всегда справлялся без вопросов". В итоге принёс монитор, подключил — и фигушки: нативное разрешение 2560x1440 завелось без проблем, но на 60Hz. Загуглил, и оказалось, что через имеющийся на моей системной плате видеовыход HDMI версии 1.4 можно прокачать только 8.16 Gbit/s несжатых видеоданных, а значит 2560x1440x100x24 ну никак не пролезет. Кстати, по быстрым прикидкам должно бы пролезать аж 92Hz, но Википедия пишет про 85Hz — видимо, накладные расходы на "поля" вокруг видимой области или просто 85Hz — это частота, которая "ну хоть сколько-то стандартная в отличие от".

Почему с нативным разрешением не завелось, скажем, на 75Hz — не знаю, почему-то в EDID монитора такой режим не прописан. Буду разбираться, но это явно выглядит не столь неожиданным осознанием, как то, что я, человек далёкий от AAA-игр, столь запросто умудрился упереться в ограничение интерфейса HDMI.
🤔1
Линуксоидное воспоминание из школьных времён, или что бывает, когда не слушают мудрые советы и парсят вывод ls.

Пункт первый: однажды я узнал, что у команды ls (если точнее, то у той её реализации, что "в линуксе", то есть из GNU coreutils, но тогда я, наверное, о таких деталях не задумывался) есть параметр --color[=WHEN], принимающий одно из трёх значений: never, auto и always (при этом "always" подразумевается по умолчанию, если передан просто --color). С этим параметром каталоги будут печататься одним цветом, архивы — другим, текстовые файлы — третьим... Недолго думая, прописал себе на постоянной основе в .bash_profile
alias ls="ls --color"

Изучать, чем отличаются опции auto и always — зачем?..

Пункт второй: я захотел написать shell-скрипт, который пройдёт по списку файлов в каталоге и для каждого создаст ещё какой-то файл с "производным" именем. Ну, что-то вроде "был file.c — создадим file.c.backup1". Сейчас по запросу don't parse ls гугл выдаёт довольно много всего — наверное, выдавал и тогда, но я же был не какой-то зануда, соблюдающий best practices, я выяснял, как сохранить вывод ls в переменную и разобрать его на части.

Пункт третий: решив посмотреть, что же мой скрипт нагенерировал, я с удивлением обнаружил, что теперь цвет намертво въелся в имена свежесозданных файлов — хоть ты с --color=always печатай, хоть с never...

Сейчас, правда, coreutils поумнели, и попытка напечатать "раскрашенное" имя сразу выдаёт неладное, но всё-таки воспроизвести фокус труда не составляет. А разгадка — когда-нибудь в следующих сериях.
Заметки бессистемного программиста
Линуксоидное воспоминание из школьных времён, или что бывает, когда не слушают мудрые советы и парсят вывод ls. Пункт первый: однажды я узнал, что у команды ls (если точнее, то у той её реализации, что "в линуксе", то есть из GNU coreutils, но тогда я, наверное…
Итак, пришло время собраться с мыслями и доработать черновик от 7 сентября (первым делом выкинув вступление про "не откладывая в долгий ящик", ага). На самом деле, угадайку я не планировал — просто не был готов описать всё за один присест. За один месяц, как оказалось, тоже ;)

Откуда же взялась возможность "раскрашивать" имена файлов? Дело в том, как в юниксовой консоли описывается форматирование выводимого текста: большинство атрибутов и команд идут в том же потоке байтов, что и сам текст — такое вот подобие языков разметки. Вероятно, это как-то связано с тем фактом, что раньше терминалы были отдельными железками, подключаемыми к мейнфрейму ограниченным количеством проводов, и нельзя было просто сказать "хочешь переместить курсор — дёрни прерывание, оно обновит неуточнённые структуры данных в памяти, потом как-нибудь нарисуем". А в "однокомпьютерном" 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 символ. Последовательности, начинающиеся с \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 на удалённую систему и скачать там что-нибудь из интернета. Обычно в таких случаях я просто переходил в каталог, куда нужно скачать файл, командовал wget https://example.org/path/to/file.tar.gz и радовался. Правда, иногда вместо wget был только curl — тогда радовался я чуть меньше, потому что нужно было вспомнить минимальный набор опций: что-то вроде -L, чтобы идти, куда послали обрабатывать перенаправления в HTTP и -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.

Поскольку любым способом качать пришлось бы долго, запустил команду наподобие такой
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, и получив от библиотеки ответ, что это не терминал, а вообще непойми что, сами откатывались на "некрасивый" формат вывода.