uz
Feedback
BashTex | Linux

BashTex | Linux

Kanalga Telegram’da o‘tish

Авторский канал для тех, кто хочет глубже погрузиться в мир Linux. Подойдет для разработчиков, системных администраторов и DevOps Реклама: @dad_admin

Ko'proq ko'rsatish
2 514
Obunachilar
Ma'lumot yo'q24 soatlar
-37 kun
+1630 kun
Postlar arxiv
Sparse files: почему файл на 100 ГБ может занимать несколько мегабайт Размер файла и реально занятое место на диске не всегда совпадают. Можно создать файл, который логически имеет размер 100 ГБ, но физически занимает всего несколько мегабайт. Для этого используются sparse files. ▪️Создаём sparse-файл
truncate -s 100G test.img
Теперь:
ls -lh test.img
покажет:
-rw-r--r-- 1 user user 100G test.img
Но:
du -h test.img
может показать всего несколько килобайт. ls смотрит на логический размер, а du - на реально выделенные filesystem blocks. ▪️Что произошло Файл содержит большой диапазон нулей, для которого файловая система не обязана физически выделять блоки. Условно:
логический файл:

[данные][     огромная область нулей     ][данные]
   ↓                 ↓                       ↓
 блоки            hole                    блоки

Эта незаполненная область называется hole.

▪️Можно посмотреть оба размера

stat test.img

Обратите внимание на:

Size:
Blocks:

Size — логический размер файла.
Blocks — сколько filesystem blocks реально выделено.

▪️Почему это важно
Sparse files часто используются для образов виртуальных машин и контейнеров:

VM disk image → 100 GB
actual allocated space → 12 GB

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

Но если эти области постепенно заполняются, реальное потребление диска начинает расти.

▪️Как проверить sparse-файл

du -h test.img
du --apparent-size -h test.img
Второй вариант показывает размер так, будто sparse-областей нет. Например:
8.0K    test.img
100G    test.img
▪️Практический сценарий Есть сервер виртуализации, и администратор видит:
ls -lh /var/lib/images/*
Образы занимают: 500G Но: du -sh /var/lib/images показывает: 180G Это не обязательно ошибка подсчёта. Часть образов может быть sparse. ▪️Важный нюанс Копирование sparse-файла не всегда сохраняет holes. Например, некоторые способы копирования могут превратить разреженный файл в обычный и реально записать нулевые блоки. Проверить результат можно снова через:
du -h file
du --apparent-size -h file
Поэтому при работе с большими VM-образами важно различать логический размер файла и реально выделенное дисковое пространство. BashTex 📱 #bash #systemd

du vs df: почему Linux показывает разный размер диска Иногда ситуация выглядит странно:
df -h /
показывает, что занято 80 GB. А:
du -sh /
находит только 55 GB. Кажется, что Linux «потерял» 25 GB. На самом деле du и df измеряют разные вещи. ▪️Что считает df df смотрит на файловую систему и её блоки:
df -h /
Он показывает, сколько места файловая система считает занятым и свободным. ▪️Что считает du du обходит дерево каталогов и суммирует пространство, связанное с найденными файлами:
du -xhd1 / 2>/dev/null
-x здесь важен: он не позволяет уйти на другие файловые системы. ▪️Одна из главных причин расхождения Процесс может держать открытым файл, который уже удалили. Например:
rm /var/log/app.log
Имя файла исчезло из каталога, но процесс всё ещё держит его открытым. Для du такого файла уже не существует. А файловая система продолжает считать его блоки занятыми. Проверить:
lsof +L1
Можно увидеть что-то вроде:
app   1234  ...  /var/log/app.log (deleted)
Пока процесс не закроет этот дескриптор, место может не освободиться. ▪️Есть и другие причины Например, du без -x может пересекать mount points и давать неожиданный результат. Или часть пространства файловой системы зарезервирована и не доступна обычным пользователям. Посмотреть файловую систему подробнее:
findmnt /
df -h /
df -i /
▪️Практический сценарий На сервере внезапно заканчивается место:
df -h /
Показывает:
/dev/sda2   100G   95G   5G   95% /
Но:
du -xsh /
показывает только 70G. Первое, что стоит проверить:
lsof +L1
Если там огромный (deleted) лог, причина найдена. ▪️Важный нюанс df отвечает примерно на вопрос: «Сколько блоков файловой системы сейчас занято?» А du: «Сколько места занимают доступные мне файлы в этом дереве?» Поэтому при расхождении df и du не стоит сразу запускать rm -rf по большим каталогам. Сначала нужно понять, куда именно делось пространство. BashTex 📱 #bash #systemd

file vs расширение - как Linux определяет тип файла на самом деле В Linux расширение файла само по себе ничего не определяет. Для системы:
backup.tar.gz
backup.jpg
backup.txt
это просто имена файлов. Можно спокойно сделать: mv image.jpg image.txt и содержимое файла от этого никак не изменится. ▪️Откуда тогда file знает тип Утилита file смотрит не на расширение, а на содержимое файла.
file image.txt
file archive.bin
file unknown
Например:
unknown: PNG image data, 1920 x 1080, 8-bit/color RGB
Даже если файл называется:
photo.txt
file всё равно может определить его как PNG. ▪️Главный источник - magic bytes
Многие форматы имеют характерную сигнатуру в начале файла.

Например, PNG начинается с:

89 50 4e 47 0d 0a 1a 0a

Проверить первые байты можно:

xxd -l 16 photo.png
А file сопоставляет такие сигнатуры с базой magic:
file --version
и использует правила из базы magic. ▪️Можно обмануть расширение
cp image.png document.txt
file document.txt
Результат всё равно будет примерно таким:
document.txt: PNG image data
Потому что имя изменилось, а байты остались прежними. ▪️Но не всё определяется только первыми байтами file может анализировать не только сигнатуру. Для ELF:
file /bin/ls
может показать архитектуру, разрядность и тип исполняемого файла. Для текстовых файлов утилита анализирует содержимое и может определить кодировку, тип текста и другие признаки. ▪️Практический сценарий Скачали файл с подозрительным расширением:
download.exe
Не стоит сразу доверять имени. Сначала:
file download.exe
А затем, если это бинарник:
readelf -h download.exe
или:
xxd -l 32 download.exe
Так можно понять, что реально лежит внутри файла. ▪️Важный нюанс file не является универсальным детектором содержимого. Если формат не имеет узнаваемой сигнатуры или несколько форматов используют похожую структуру, результат может быть приблизительным. И ещё важнее: file определяет тип по содержимому, но не говорит, что файл безопасен. Файл может называться document.pdf, определяться как PDF и при этом содержать вредоносную нагрузку. В Linux расширение - часть имени. А реальный формат гораздо чаще определяется тем, какие байты находятся внутри файла. BashTex 📱 #bash #systemd

BashTex 📱 #мем
BashTex 📱 #мем

systemd-delta: как найти, чем на самом деле переопределён unit Иногда вы открываете unit-файл и видите одну конфигурацию, а systemd ведёт себя иначе. Причина часто в drop-in override, созданном через systemctl edit. Например, основной unit:
/usr/lib/systemd/system/nginx.service
а дополнительная настройка лежит здесь:
/etc/systemd/system/nginx.service.d/override.conf
▪️Посмотреть переопределения
systemd-delta
Команда показывает отличия между vendor-конфигурацией и локальными изменениями. Можно проверить конкретный unit:
systemd-delta nginx.service
Если сервис был переопределён, вы увидите соответствующий drop-in. ▪️Почему это важно Допустим, в основном unit указано:
[Service]
Restart=no

А где-то в /etc/systemd/system/nginx.service.d/override.conf:
[Service]
Restart=always
Открыв только /usr/lib/systemd/system/nginx.service, вы будете смотреть на неполную картину. systemd при запуске учитывает оба слоя. ▪️Посмотреть итоговую конфигурацию Для проверки того, что systemd реально использует:
systemctl show nginx.service
Например:
systemctl show nginx.service -p Restart -p RestartUSec
А список связанных drop-in можно увидеть так:
systemctl cat nginx.service
Здесь systemd покажет основной unit и применённые drop-in-файлы. ▪️Практический сценарий Сервис неожиданно перезапускается:
systemctl status nginx
В основном unit Restart= не настроен. Вместо того чтобы сразу менять конфигурацию, проверяем:
systemd-delta nginx.service
И обнаруживаем старый override:
/etc/systemd/system/nginx.service.d/override.conf
Именно он мог изменить поведение сервиса. ▪️Важный нюанс systemd-delta особенно полезен после обновлений дистрибутива. Пакет может заменить vendor unit в /usr/lib/systemd/system/, но локальный override в /etc/systemd/system/ продолжит действовать. Поэтому при странном поведении unit полезно проверить не только сам файл, но и что поверх него было добавлено или изменено. BashTex 📱 #bash #systemd

PSI: /proc/pressure/* - как понять, чего реально не хватает системе load average показывает, что в системе есть очередь на выполнение или I/O. Но он плохо отвечает на другой вопрос: насколько пользователи и процессы реально страдают от этого дефицита? Для этого в Linux есть PSI - Pressure Stall Information:

cat /proc/pressure/cpu
cat /proc/pressure/memory
cat /proc/pressure/io
Например:

full avg10=0.00 avg60=0.01 avg300=0.00 total=...
▪️some - хотя бы часть задач была вынуждена ждать ресурс. ▪️full - все задачи соответствующей группы одновременно стояли из-за этого ресурса. avg10, avg60, avg300 - процент времени за последние 10, 60 и 300 секунд, когда происходило такое ожидание. Например:

some avg10=18.5
full avg10=7.2
Это уже гораздо интереснее, чем просто увидеть высокий RAM usage. Если memory some растёт - процессы регулярно упираются в нехватку памяти. Если начинает расти memory full - система уже попадает в периоды, когда все учитываемые задачи одновременно не могут нормально продвигаться из-за memory pressure. То же самое для I/O:

cat /proc/pressure/io
Если I/O PSI высокий, а CPU почти не испытывает pressure, проблема может быть не в загрузке процессора, а в storage latency. А CPU PSI позволяет увидеть ситуацию, когда CPU формально загружен не на 100%, но runnable-задачи всё равно регулярно ждут свою очередь. Проверить всё сразу:

for f in /proc/pressure/*; do
    echo "=== $f ==="
    cat "$f"
done
PSI полезен именно как метрика задержки из-за конкуренции за ресурсы, а не просто их потребления. Поэтому вместо вопроса «CPU/RAM/Disk загружены?» иногда правильнее спросить: «Какой ресурс заставляет процессы ждать?» BashTex 📱 #perfomance #linux

Хотите выйти на зарубежный рынок, но непонятно, с чего начать? Если вы: — фрилансер и хотите работать с иностранными заказчик
Хотите выйти на зарубежный рынок, но непонятно, с чего начать? Если вы: — фрилансер и хотите работать с иностранными заказчиками; — IT-специалист или развиваете IT-компанию; — владелец digital- или маркетингового агентства; — дизайнер или руководитель дизайн-студии; — эксперт и хотите продавать свои услуги за пределами локального рынка; — предприниматель, который планирует развивать бизнес за рубежом Меня зовут Альберт Сабиров. Владелец международного digital агентства. Приглашаю вас на свой канал, где я на практике работаю с зарубежными рынками и в канале делюсь опытом, который помогает разобраться: — где искать первых клиентов за рубежом; — как выйти на иностранных заказчиков; — как адаптировать и упаковать свои услуги под зарубежный рынок; — как принимать оплату из других стран; — как открывать зарубежные счета и работать с ними; — как зарегистрировать компанию за границей; — что учитывать в договорах, налогах и законодательстве; — как выстроить маркетинг и привлечение клиентов на новом рынке; — какие ошибки могут стоить денег и времени при выходе за рубеж. Если хотите перестать зависеть только от локального рынка и понять, как системно начать работать за рубежом — присоединяйтесь. Альберт Сабиров | Зарубежный рынок

— где искать первых клиентов за рубежом; — как выйти на иностранных заказчиков; — как адаптировать и упаковать свои услуги под зарубежный рынок; — как принимать оплату из других стран; — как открывать зарубежные счета и работать с ними; — как зарегистрировать компанию за границей; — что учитывать в договорах, налогах и законодательстве; — как выстроить маркетинг и привлечение клиентов на новом рынке; — какие ошибки могут стоить денег и времени при выходе за рубеж. Если хотите перестать зависеть только от локального рынка и понять, как системно начать работать за рубежом — присоединяйтесь. Альберт Сабиров | Зарубежный рынок

failglob - почему Bash должен падать при нераскрывшемся glob Обычно Bash спокойно оставляет glob как есть, если совпадений нет:
rm /var/log/app/*.old
Если .old-файлов нет, команда фактически получит:
rm: cannot remove '/var/log/app/*.old': No such file or directory
Но в автоматизации это может быть проблемой: скрипт продолжит выполнение, хотя вы рассчитывали, что glob что-то нашёл. ▪️failglob меняет поведение
shopt -s failglob

files=(/var/log/app/*.old)
Если совпадений нет, Bash сам выдаст ошибку:
bash: no match: /var/log/app/*.old
То есть проблема обнаруживается на этапе раскрытия glob, ещё до запуска команды. ▪️Особенно полезно в скриптах:
#!/usr/bin/env bash

set -e
shopt -s failglob

for file in /backup/*.tar.gz; do
    process "$file"
done
Без failglob цикл может получить буквально строку /backup/*.tar.gz. С failglob скрипт сразу сигнализирует, что ожидаемых файлов нет. ▪️Но есть нюанс failglob не всегда нужен. Если отсутствие файлов - нормальная ситуация, лучше использовать nullglob:
shopt -s nullglob

files=(/backup/*.tar.gz)

for file in "${files[@]}"; do
    process "$file"
done
Тогда при отсутствии совпадений массив просто будет пустым. Итого получается: failglob → отсутствие совпадения считается ошибкой. nullglob → отсутствие совпадения превращается в пустой список. Для критичных автоматизаций failglob помогает не пропустить логическую ошибку, замаскированную обычным поведением Bash. BashTex 📱 #bash #linux

KillMode=: почему systemctl stop иногда оставляет процессы Остановить сервис через:
systemctl stop myapp.service
не всегда означает, что исчезнет только один процесс. У systemd есть отдельная настройка, определяющая, какие процессы будут остановлены вместе с unit. Это KillMode=. ▪️Например:
[Service]
ExecStart=/opt/myapp/start.sh
KillMode=control-group
По умолчанию systemd работает с control group сервиса. При остановке он может завершить все процессы внутри cgroup, а не только основной PID. Проверить:
systemctl show myapp.service -p KillMode
▪️Самый интересный вариант - process
[Service]
KillMode=process
В этом режиме сигнал отправляется только основному процессу сервиса.
Если start.sh породил:

myapp
 ├─ worker1
 └─ worker2
дочерние процессы могут продолжить работу после остановки unit. ▪️control-group
[Service]
KillMode=control-group
systemd отслеживает cgroup целиком:
myapp.service
 ├─ PID 1200
 ├─ PID 1201
 └─ PID 1202
При остановке unit процессы внутри группы также попадут под завершение. ▪️Проверить, кто реально принадлежит сервису
systemctl status myapp.service
или:
systemctl show myapp.service -p ControlGroup
Затем:
systemd-cgls
Можно увидеть дерево процессов по cgroup. ▪️Ещё один режим
KillMode=mixed
При остановке SIGTERM получает основной процесс, а при последующем принудительном завершении systemd применяет его ко всей control group. Это позволяет дать главному процессу шанс корректно завершиться, не оставляя дочерние процессы навсегда. ▪️Почему это важно Особенно легко получить «призраков» при запуске приложения через shell-обёртку:
ExecStart=/bin/bash /opt/start.sh
Если скрипт запускает фоновые процессы, нужно понимать, в какой cgroup они окажутся и как KillMode повлияет на их завершение. systemctl stop - это не просто отправка SIGTERM одному PID. В systemd процесс существует внутри cgroup, и политика KillMode определяет масштаб остановки. BashTex 📱 #bash #linux

BashTex 📱 #мем
BashTex 📱 #мем

tee + process substitution: один поток и несколько получателей Иногда нужно одновременно: увидеть вывод команды в терминале, записать его в файл и передать на обработку другой программе Если делать это по очереди - потеряется поток данных. Здесь помогает связка tee + process substitution. ▪️ Что делает tee. tee дублирует поток: command | tee file.log вывод остается в терминале и одновременно пишется в file.log ▪️ Несколько получателей. tee может писать сразу в несколько файлов: command | tee out1.log out2.log Но иногда нужно не просто файл, а другую команду. ▪️ Process substitution. Bash позволяет подставить вывод команды как файл: >(command) Это называется process substitution. ▪️ Комбинируем command | tee >(grep ERROR > errors.log) command генерирует поток tee дублирует его Одна копия идет в терминал, а другая в grep. Если найден ERROR, запись попадет в errors.log. ▪️ Более реальный пример. Допустим, идет сбор логов: journalctl -f | tee >(grep ERROR >> errors.log) Теперь: полный поток остается в терминале, ошибки автоматически сохраняются ▪️ Можно делать несколько обработчиков journalctl -f | tee \ >(grep ERROR >> errors.log) \ >(grep WARN >> warn.log) Один поток и сразу несколько фильтров. BashTex 📱 #bash #utils

RuntimeMaxSec: как systemd убивает сервис, который работает слишком долго Иногда сервис не падает и не зависает в классическом смысле - он просто продолжает работать часами, хотя по логике приложения должен завершиться. В systemd для этого есть RuntimeMaxSec=. ▪️Ограничиваем время жизни сервиса В unit-файле:
[Service]
ExecStart=/usr/local/bin/job.sh
RuntimeMaxSec=30min
После 30 минут с момента запуска systemd остановит сервис. Для более точного ограничения:
RuntimeMaxSec=1h 30min
▪️Что происходит после истечения времени Это не просто kill -9. По умолчанию systemd инициирует обычную остановку сервиса. Если процесс не завершится в течение TimeoutStopSec=, systemd применит настроенное принудительное завершение. Например:
[Service]
RuntimeMaxSec=30min
TimeoutStopSec=20s
Получается цепочка:
30 мин → остановка
       ↓
20 сек на graceful shutdown
       ↓
принудительное завершение
▪️Проверить фактические параметры После запуска:
systemctl show myjob.service \
    -p RuntimeMaxUSec \
    -p TimeoutStopUSec
А текущий статус:
systemctl status myjob.service
▪️Что будет при следующем запуске RuntimeMaxSec не запрещает сервису запускаться снова. Если unit настроен так:
[Service]
Restart=on-failure
RuntimeMaxSec=30min
важен результат завершения и дополнительные параметры restart policy. Сервис может быть запущен systemd снова после остановки. Поэтому RuntimeMaxSec стоит рассматривать вместе с Restart=, а не как самостоятельный механизм watchdog. ▪️Практический сценарий Допустим, есть batch-задача:
[Service]
Type=oneshot
ExecStart=/opt/scripts/import.sh
RuntimeMaxSec=2h
Если импорт по какой-то причине зависнет на бесконечной операции, systemd не оставит процесс работать сутки. ▪️Важный нюанс RuntimeMaxSec - это лимит общего времени работы, а не проверка того, отвечает ли приложение. Если сервис должен завершаться за 5 минут, но иногда работает 10 минут по штатной причине, такой лимит будет ошибкой конфигурации. Для контроля «жив ли сервис и отвечает ли он» нужны другие механизмы. BashTex 📱 #bash #linux

local с подстановкой команды: почему local result=$(cmd) скрывает ошибку В Bash есть тонкая ловушка внутри функций. На первый взгляд эти два варианта выглядят одинаково:
result=$(false)
echo "$?"
Здесь $? будет 1. Но если написать:
local result=$(false)
echo "$?"
получим: 0 ▪️Почему так происходит В конструкции:
local result=$(cmd)
выполняются сразу две вещи: 1. Bash выполняет $(cmd) и получает результат. 2. local объявляет переменную. Проблема в том, что код возврата всей команды определяется local, а не самой command substitution. local успешно создал переменную - поэтому функция видит 0. ▪️Особенно неприятно с обработкой ошибок Например:
get_data() {
    local result=$(curl -fsS https://example.com/api)
    echo "status=$?"
}
Если curl завершится с ошибкой, проверка $? после local может показать успешное выполнение. Ошибка потеряна. ▪️Безопасный вариант Разделите объявление и присваивание:
get_data() {
    local result

    result=$(curl -fsS https://example.com/api)
    local status=$?

    echo "status=$status"
}
Теперь $? относится непосредственно к curl. ▪️Ещё лучше - проверять сразу
get_data() {
    local result

    if result=$(curl -fsS https://example.com/api); then
        printf '%s\n' "$result"
    else
        echo "command failed"
        return 1
    fi
}
Здесь условие if проверяет именно exit code command substitution. ▪️Та же ловушка есть не только с local Похожая ситуация возникает с некоторыми встроенными командами, когда результат присваивания объединяется с другой операцией. Поэтому правило простое:
local result
result=$(cmd)
а не:
local result=$(cmd)
▪️Почему это важно В обычном скрипте такая мелочь может остаться незаметной. В функции, которая используется в цепочке:
deploy || exit 1
потерянный exit code уже превращается в реальную логическую ошибку: функция может сообщить об успехе, хотя команда внутри неё завершилась с ошибкой. BashTex 📱 #bash #linux

read -d: как читать Bash-поток не по строкам read обычно ждёт перевод строки:
while IFS= read -r line; do
    printf '%s\n' "$line"
done < file.txt
Но у read можно изменить разделитель. Это особенно полезно для данных, где обычный \n не является надёжной границей записи. ▪️Читать по нулевому байту Например, find умеет выдавать имена файлов через \0:
find /tmp -type f -print0 |
while IFS= read -r -d '' file; do
    printf '%s\n' "$file"
done
-d '' означает: читать до NUL-символа. Это позволяет корректно обработать имя вроде:
report
2026.txt
где внутри имени действительно может быть перевод строки. ▪️Почему обычный read здесь опасен Такой вариант:
find /tmp -type f -print0 |
while read -r file; do
    ...
done
не соответствует формату данных: find разделяет записи \0, а read по умолчанию ищет \n. ▪️Можно использовать другой разделитель Например, прочитать значения через
::
while IFS= read -r -d ':' value; do
    printf '<%s>\n' "$value"
done <<< 'one:two:three:'
Получим:
<one>
<two>
<three>
▪️read умеет ещё и ограничивать количество символов
IFS= read -r -n 1 char
Теперь Bash прочитает только один символ. А:
IFS= read -r -N 8 chunk
будет ждать ровно 8 символов, не считая разделитель строки. ▪️Где это полезно read -d особенно хорошо сочетается с командами, поддерживающими NUL-разделители:
find ... -print0
git ... -z
sort -z
xargs -0
Получается безопасная цепочка, в которой пробелы, кавычки и даже переводы строк внутри имён файлов не ломают обработку. ▪️Почему это важно Разделитель данных должен совпадать с тем, как эти данные реально генерируются. read - это не только «прочитать строку»: с -d, -n и -N он может работать с потоками гораздо точнее. BashTex 📱 #bash #linux

BashTex 📱 #мем
BashTex 📱 #мем

/proc/PID/wchan: где именно завис процесс Если процесс показывает высокий D в ps, сам статус ещё не говорит, на чём именно он остановился. Для этого Linux предоставляет /proc/<PID>/wchan. ▪️Сначала найдём процесс
ps -eo pid,stat,wchan:30,cmd | grep '[D]'
Например:
2418 D    io_schedule     backup
Здесь wchan показывает kernel wait channel - функцию ядра, в которой процесс ожидает продолжения. ▪️Посмотреть напрямую
cat /proc/2418/wchan
Можно получить:
io_schedule
Или:
nfs_wait_bit_killable
Во втором случае уже появляется конкретная зацепка: процесс может ждать операции, связанной с NFS. ▪️Сравнить несколько процессов
for p in /proc/[0-9]*; do
    pid=${p##*/}
    state=$(awk '{print $3}' "$p/stat" 2>/dev/null)
    [ "$state" = D ] || continue

    printf '%-8s %s\n' \
        "$pid" \
        "$(cat "$p/wchan" 2>/dev/null)"
done
Получится примерно:
2418     io_schedule
2421     io_schedule
2517     nfs_wait_bit_killable
Если десятки процессов одновременно находятся в одном wchan, это уже может указывать на общий узкий участок. ▪️Посмотреть стек ядра Для более глубокого разбора:
cat /proc/2418/stack
Можно увидеть цепочку вызовов ядра, в которой находится поток. Например:
io_schedule
schedule
...
Доступ к /proc/PID/stack зависит от прав и настроек безопасности. ▪️Почему kill -9 иногда не помогает Процесс в состоянии D находится в uninterruptible sleep. Если он ждёт завершения определённой операции ядра, SIGKILL не может мгновенно вывести его из этого состояния. Поэтому вместо бесконечного:
kill -9 2418
полезнее сначала посмотреть:
ps -o pid,stat,wchan,cmd -p 2418
cat /proc/2418/wchan
cat /proc/2418/stack
Так можно понять, почему процесс не реагирует. ▪️Почему это важно ps показывает симптом - D. wchan помогает увидеть причину ожидания на уровне ядра. Для проблем с дисками, NFS, storage и зависшими I/O-операциями это один из самых быстрых способов перейти от «процесс завис» к пониманию, где именно он застрял. BashTex 📱 #bash #linux

BASH_SOURCE и FUNCNAME: как понять, откуда реально вызвали функцию В больших Bash-скриптах функция может вызываться из другой функции, которая пришла из отдельного файла. Обычный $0 в такой ситуации мало что объясняет. Для этого у Bash есть специальные массивы:
BASH_SOURCE
FUNCNAME
▪️Простой пример
foo() {
    echo "file: ${BASH_SOURCE[0]}"
    echo "caller: ${BASH_SOURCE[1]}"
    echo "function: ${FUNCNAME[0]}"
}

foo
BASH_SOURCE[0] показывает файл, в котором выполняется текущая функция, а BASH_SOURCE[1] - файл, откуда она была вызвана. ▪️Посмотреть весь стек вызовов
foo() {
    printf '%s\n' "${FUNCNAME[@]}"
}

bar() {
    foo
}

bar
Можно получить примерно:
foo
bar
main
То есть Bash хранит стек функций. ▪️Особенно полезно для логирования Например:
log() {
    printf '[%s] %s: %s\n' \
        "$(date '+%F %T')" \
        "${FUNCNAME[1]}" \
        "$*"
}
Теперь:
deploy() {
    log "starting deployment"
}

deploy
может вывести:
[2026-08-24 12:30:01] deploy: starting deployment
Лог сразу показывает, какая функция создала сообщение. ▪️А если скрипт состоит из нескольких файлов Допустим:
main.sh
lib.sh
utils.sh
и функции вызывают друг друга через source. Тогда:
printf '%s\n' "${BASH_SOURCE[@]}"
покажет цепочку файлов, через которые прошёл вызов. ▪️Чем это отличается от $0
echo "$0"
обычно показывает имя запущенного скрипта или shell. А:
echo "${BASH_SOURCE[0]}"
показывает конкретный Bash-файл, где находится текущий код. Это особенно заметно при использовании:
source lib.sh
▪️Почему это важно При отладке больших shell-проектов сообщение вроде: ERROR: failed почти бесполезно. А: ERROR: deploy() in lib/deploy.sh уже позволяет быстро найти источник проблемы. BASH_SOURCE и FUNCNAME превращают Bash-функции из непрозрачного набора вызовов в трассируемый стек. BashTex 📱 #bash #linux

mapfile: как загружать вывод команды в массив без while read Если результат команды нужно сохранить построчно, часто используют:
items=()

while IFS= read -r line; do
    items+=("$line")
done < <(some_command)
В Bash для этого есть встроенный mapfile. ▪️Простой вариант
mapfile -t files < <(find /tmp -type f)
Теперь каждая строка - отдельный элемент массива:
printf '%s\n' "${files[@]}"
-t убирает символ переноса строки из каждого элемента. ▪️Зачем это лучше Нет отдельного while, нет проблемы с subshell и не нужно вручную добавлять элементы:
mapfile -t users < <(cut -d: -f1 /etc/passwd)

echo "Users: ${#users[@]}"
Можно обращаться к конкретному элементу: echo "${users[0]}" ▪️Можно читать файл напрямую
mapfile -t lines < config.txt
А затем:
for line in "${lines[@]}"; do
    printf 'CONFIG: %s\n' "$line"
done
▪️Читать только часть вывода Например:
mapfile -t -n 10 lines < access.log
В массив попадут только первые 10 строк. ▪️Есть важный нюанс mapfile ориентирован именно на разделённые строки данные. Если содержимое может содержать \n внутри логического элемента, обычный построчный массив уже не подходит. Для других разделителей можно использовать -d:
mapfile -d ',' -t items < data.txt
Теперь разделителем считается запятая. ▪️Где это удобно Например, получить список systemd-сервисов:
mapfile -t services < <(
    systemctl list-units --type=service --state=running --no-legend |
    awk '{print $1}'
)
После этого массив можно безопасно передавать дальше:
for service in "${services[@]}"; do
    systemctl is-active "$service"
done
▪️Почему это важно mapfile - встроенный механизм Bash для преобразования потоковых данных в массив. Он делает код короче и избавляет от целого класса проблем, связанных с while read, пайпами и сохранением переменных. BashTex 📱 #bash #linux

noclobber: как запретить Bash случайно перезаписать файл Обычный оператор: echo "new config" > config.txt без предупреждения удалит старое содержимое config.txt. Для скриптов, где случайная перезапись критичного файла недопустима, в Bash есть режим noclobber. ▪️Включаем его
set -o noclobber
Теперь:
echo "new config" > config.txt
если файл уже существует, завершится ошибкой:
bash: config.txt: cannot overwrite existing file
▪️Проверить состояние
set -o | grep noclobber
Или короткая форма:
set -C
Выключить обратно:
set +C
▪️Но >> продолжает работать noclobber защищает именно от перезаписи через >:
echo "new line" >> config.txt
добавит данные в конец файла. ▪️А как принудительно перезаписать? Есть специальная конструкция:
echo "new config" >| config.txt
Оператор >| игнорирует noclobber и разрешает перезапись. Это удобно, когда защита включена глобально, но конкретный участок скрипта действительно должен заменить файл. ▪️Где это полезно Например, скрипт генерирует конфигурацию: set -C
generate_config > /etc/myapp/config.conf
Если файл уже существует, операция остановится вместо того, чтобы молча уничтожить текущую конфигурацию. ▪️Важный нюанс noclobber не является полноценной защитой от всех способов изменения файла. Это именно настройка shell для оператора >. Её задача проще: превратить потенциально опасную случайную перезапись в явную ошибку. ▪️Почему это важно В Bash > выглядит безобидно, но фактически означает «открыть файл на запись и обнулить его». noclobber позволяет добавить дополнительный барьер там, где потеря существующего содержимого недопустима. BashTex 📱 #bash #linux