fa
Feedback

فریب کلاهبرداران را نخورید! تل‌متریو این کانال‌ها را شناسایی و برچسب‌گذاری می‌کند 👉 برای مشاهده برچسب، اشتراک تهیه کنید

BashTex | Linux

BashTex | Linux

رفتن به کانال در Telegram

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

نمایش بیشتر
2 506
مشترکین
اطلاعاتی وجود ندارد24 ساعت
-47 روز
+130 روز
آرشیو پست ها
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

nullglob: когда *.log внезапно превращается в строку Bash умеет раскрывать glob-шаблоны:
*.log
Но если файлов с таким расширением нет, по умолчанию шаблон может остаться обычной строкой. Например:
for file in /var/log/*.log; do
    echo "$file"
done
Если .log файлов нет, цикл может получить буквально:
/var/log/*.log
Это легко превращается в ошибку в автоматизации. ▪️Включаем nullglob
shopt -s nullglob
Теперь шаблон без совпадений исчезает:
files=(/var/log/*.log)

echo "${#files[@]}"
Если файлов нет: 0 А не 1 с буквальным *.log. ▪️Почему это важно для массивов Без nullglob:
files=(/tmp/*.tmp)
может создать массив из одного элемента:
/tmp/*.tmp
После:
shopt -s nullglob
получаем действительно пустой массив. ▪️nullglob влияет только на текущий shell Его можно включить локально внутри функции:
check_logs() {
    local old_nullglob
    shopt -q nullglob
    old_nullglob=$?

    shopt -s nullglob

    files=(/var/log/*.log)

    ((${#files[@]})) && printf '%s\n' "${files[@]}"

    ((old_nullglob == 0)) || shopt -u nullglob
}
Но для простых скриптов обычно достаточно включить настройку в начале. ▪️А если отсутствие файлов должно быть ошибкой? Тогда существует противоположная настройка:
shopt -s failglob
При отсутствии совпадений Bash выдаст ошибку вместо передачи буквального шаблона дальше. ▪️Почему это важно Glob кажется простой частью Bash, пока скрипт не запускается на сервере, где ожидаемых файлов просто нет. nullglob позволяет отличить настоящий список файлов от строки с нераскрытым шаблоном - особенно полезно при работе с массивами, логами и автоматической обработкой каталогов. BashTex 📱 #bash #linux

lastpipe: как Bash меняет поведение последней команды в pipeline Одна из особенностей Bash - команды в pipeline обычно выполняются в отдельных subshell. Из-за этого переменные, изменённые внутри while, после цикла могут не сохраниться. Например:
count=0

printf '%s\n' a b c |
while read -r line; do
    ((count++))
done

echo "$count"
В некоторых случаях результатом будет 0. Но у Bash есть настройка lastpipe, которая меняет это поведение. ▪️Включаем lastpipe
shopt -s lastpipe
Теперь последняя команда pipeline может выполняться в текущем shell.
count=0

printf '%s\n' a b c |
while read -r line; do
    ((count++))
done

echo "$count"
При подходящих условиях получим: 3 ▪️Есть важное условие lastpipe работает только когда job control отключён:
set +m
shopt -s lastpipe
Именно поэтому поведение может отличаться между интерактивным Bash и обычным скриптом. ▪️Проверить настройку
shopt lastpipe
Получим:
lastpipe        off
или:
lastpipe        on
▪️Почему это не универсальная замена Вместо изменения поведения shell часто проще использовать редирект:
while read -r line; do
    ((count++))
done < <(printf '%s\n' a b c)
Так код не зависит от настройки lastpipe. ▪️Где lastpipe действительно интересен Это полезно знать при разборе чужих Bash-скриптов: одна и та же конструкция с pipeline может вести себя по-разному в зависимости от настроек shell и режима job control. lastpipe - хороший пример того, как небольшая опция Bash меняет фундаментальное поведение pipeline и область видимости изменений. BashTex 📱 #bash #linux

АЙТИШНИКИ БЕСПЛАТНОЕ ОБУЧЕНИЕ Проект «Terminal» стал крупнейшей библиотекой бесплатного образования. В одном канале собраны к
АЙТИШНИКИ БЕСПЛАТНОЕ ОБУЧЕНИЕ Проект «Terminal» стал крупнейшей библиотекой бесплатного образования. В одном канале собраны курсы, книги, полезные инструменты и практические тренажёры для всех разработчиков: • Практические курсы и задания • Книги и статьи известных авторов • Полезные инструменты и ресурсы • IT-новости и инсайды Обучение по всем направлениям: SQL, Python, ML, Frontend, PHP, C++, Go, Git, Linux, QA, Java, Vibe-coding, InfoSec и др. ⌨️ подписаться

$? после &&, || и if: почему код возврата Bash бывает неочевидным В Bash $? содержит код завершения последней выполненной команды. Но конструкции &&, || и if меняют то, какие команды вообще выполняются. ▪️Простая цепочка
false && echo "OK"
echo $?
echo после && не запускается, потому что false вернула 1. Итоговый $? - 1. А здесь:
true && echo "OK"
echo $?
последней командой становится echo "OK", поэтому $? уже равен 0. ▪️С || ситуация аналогичная
false || echo "fallback"
echo $?
false запускается, затем выполняется echo, потому что первая команда завершилась ошибкой. В итоге $? будет 0 - код echo, а не false. Если нужен исходный результат:
false
status=$?

if [ "$status" -ne 0 ]; then
    echo "Command failed: $status"
fi
▪️Почему if не создаёт обычный 1
if false; then
    echo "success"
fi

echo $?
Здесь if возвращает статус последней выполненной команды в выбранной ветке. Если ни одна ветка не выполнялась, результатом становится код проверки условия. ▪️Частая ошибка
command || echo "failed"
status=$?
status здесь содержит результат echo, а не command. Если важно сохранить код ошибки:
if ! command; then
    status=$?
fi
Но здесь есть ещё один нюанс: ! инвертирует статус, поэтому для получения исходного кода лучше сохранять его непосредственно после команды. Например:
command
status=$?

if [ "$status" -ne 0 ]; then
    echo "failed: $status"
fi
▪️Почему это важно В сложных Bash-скриптах $? легко прочитать слишком поздно. Любая следующая команда - даже echo, printf или test - может заменить предыдущий код возврата. Поэтому если результат команды важен, сохраняйте его сразу. BashTex 📱 #bash #linux

return против exit: как не завершить весь скрипт внутри функции В Bash функция может вернуть управление вызывающему коду или полностью завершить текущий shell. Эти два случая часто путают. ▪️exit завершает весь скрипт
check_config() {
    if [ ! -f config.conf ]; then
        echo "Config not found"
        exit 1
    fi
}

check_config

echo "Продолжаем работу"
Если config.conf нет, выполнение закончится прямо внутри функции. Строка Продолжаем работу никогда не выполнится. ▪️return завершает только функцию
check_config() {
    if [ ! -f config.conf ]; then
        echo "Config not found"
        return 1
    fi
}

check_config

echo "Продолжаем работу"
Теперь функция завершилась с кодом 1, но сам скрипт продолжил выполнение. Именно поэтому return обычно используют внутри функций, а exit - на уровне самого скрипта. ▪️Код возврата можно обработать
if check_config; then
    echo "Config OK"
else
    echo "Config error"
fi
Или сохранить результат:
check_config
status=$?

if [ "$status" -ne 0 ]; then
    echo "Ошибка: $status"
fi
▪️Важный нюанс return можно использовать только внутри функции или в контексте, где Bash допускает возврат из текущего sourced-файла. Например:
return 1
в обычном запускаемом скрипте верхнего уровня приведёт к ошибке:
return: can only `return' from a function
▪️Типичная ошибка
deploy() {
    check_config || exit 1
    start_service
}
Если deploy вызывается из другого скрипта как функция, exit завершит уже весь shell. Чаще правильнее:
deploy() {
    check_config || return 1
    start_service
}
А решение о завершении оставить вызывающему коду:
deploy || exit 1
Так функция сообщает об ошибке, а не решает за весь скрипт, что делать дальше. BashTex 📱 #bash #linux

/proc/PID/cwd: когда процесс работает не из того каталога У каждого Linux-процесса есть текущий рабочий каталог. Обычно мы видим его через shell, но для уже запущенного процесса можно посмотреть напрямую через /proc.
readlink /proc/1234/cwd
Например:
/var/lib/app
Это каталог, относительно которого процесс разрешает относительные пути. ▪️Найти процессы с неожиданным cwd
for p in /proc/[0-9]*; do
    pid=${p##*/}
    cwd=$(readlink "$p/cwd" 2>/dev/null) || continue
    printf '%-8s %s\n' "$pid" "$cwd"
done
Теперь можно быстро увидеть, какие процессы находятся в /tmp, /dev/shm, домашних каталогах или других необычных местах. ▪️Почему это может быть важно Приложение может запускаться из:
/opt/myapp
но после работы оказаться в другом каталоге из-за chdir(). Особенно интересны процессы с:
/tmp
/dev/shm
/home/user
если для конкретного сервиса такое поведение не ожидается. ▪️Связь с systemd Для unit можно задать:
[Service]
WorkingDirectory=/var/lib/myapp
А фактическое значение уже запущенного процесса проверить через /proc. Это полезнее, чем тупо смотреть конфигурацию: /proc/PID/cwd показывает состояние конкретного процесса прямо сейчас. BashTex 📱 #bash #SSH

📘 DevOps-инженер: от основ до продакшена — новый курс на Mentorix Пишете код, но деплой и прод — чёрный ящик? Курс про инжен
📘 DevOps-инженер: от основ до продакшена — новый курс на Mentorix Пишете код, но деплой и прод — чёрный ящик? Курс про инженерию эксплуатации: от первого сервера до автоматизированной CI/CD-инфраструктуры, которая держит нагрузку и не падает. ⚙️ Стек: Docker, Kubernetes, Ansible, Terraform, CI/CD, Prometheus + Grafana, логи ELK 🧩 Задания с автопроверкой + лабы в настоящем терминале 💻 Финальный проект в портфолио 🎓 Сертификат · доступ навсегда 🏷 −35% по промокоду DEVOPS35 — только 48 часов 13 990 ₽ → 9 094 ₽ ➜ Забрать курс со скидкой ━━━━━━━━━━━━━━ 🎁 А ещё на платформе — бесплатные курсы: Языки программирования ⚡ Golang — основы языка 🦀 Rust — основы языка 🐍 Основы Python Инфраструктура и DevOps 🖥 Основы DevOps 🐳 Docker: первые шаги 🔧 Git для начинающих Базы данных • SQL с нуля • MongoDB с нуля 📚 Все бесплатные курсы Mentorix

setsid: как действительно отделить процесс от SSH-сессии nohup часто используют, чтобы процесс пережил закрытие терминала: nohup ./backup.sh & Но nohup в основном защищает от SIGHUP. Сам процесс всё ещё может оставаться связанным с текущей session. Для полного создания новой сессии существует setsid. ▪️Что происходит обычно После подключения по SSH можно посмотреть:
ps -o pid,ppid,sid,tty,cmd
У процессов будут общие SID и управляющий терминал. При закрытии SSH это может иметь значение для поведения процессов. ▪️Запуск через setsid
setsid ./backup.sh
Процесс становится лидером новой session и получает новый SID. Проверить:
ps -o pid,ppid,sid,tty,cmd -C backup.sh
Можно увидеть, что SID процесса больше не связан с вашей SSH-сессией. ▪️Почему одного & недостаточно
./backup.sh &
Фоновый процесс всё равно является частью текущей shell-сессии. & лишь не блокирует терминал ожиданием процесса. ▪️А nohup?
nohup ./backup.sh &
Это уже лучше для сценария с закрытием терминала, но nohup и setsid решают разные задачи. nohup изменяет обработку SIGHUP и обычно перенаправляет стандартный вывод. setsid создаёт новую session и отделяет процесс от текущей session. ▪️Комбинация Для простого запуска полностью отдельно:
setsid ./backup.sh >/tmp/backup.log 2>&1 < /dev/null &
Теперь процесс не зависит от стандартного ввода текущего терминала и находится в отдельной session. ▪️Где это полезно • запуск долгих задач из SSH; • анализ поведения daemon-like процессов; • shell-обвязки для сервисов; • понимание PID, PPID, SID и TTY. ▪️Почему это важно &, nohup и setsid часто воспринимают как взаимозаменяемые способы “запустить в фоне”. На самом деле они меняют разные свойства процесса. Если нужно понять, почему программа продолжает зависеть от SSH или терминала, смотреть стоит не только на PID, но и на SID, PPID и управляющий TTY. BashTex 📱 #bash #SSH

/proc/PID/environ: какие переменные реально получил процесс env показывает окружение текущего shell. Но для диагностики сервиса этого недостаточно: после запуска процесс мог получить совсем другой набор переменных. Linux хранит окружение каждого процесса здесь:
cat /proc/1234/environ
Значения разделены не переносами строк, а нулевыми байтами, поэтому удобнее:
tr '\0' '\n' < /proc/1234/environ
▪️Найти конкретную переменную
tr '\0' '\n' < /proc/1234/environ | grep '^PATH='
Или проверить настройки прокси:
tr '\0' '\n' < /proc/1234/environ |
grep -Ei 'proxy|http_proxy|https_proxy'
▪️Сравнить окружение двух процессов Например, приложение запущено вручную и через systemd:
tr '\0' '\n' < /proc/1234/environ | sort > /tmp/app.env
tr '\0' '\n' < /proc/5678/environ | sort > /tmp/service.env

diff -u /tmp/app.env /tmp/service.env
Так можно быстро найти переменную, из-за которой одна версия работает, а другая нет. ▪️Почему env может вводить в заблуждение Вы выполняете:
echo "$PATH"
и видите одно значение. Но процесс, запущенный несколько часов назад, мог получить старый PATH. Изменение конфигурации shell не меняет окружение уже работающего процесса. То же касается:
HTTP_PROXY
LD_LIBRARY_PATH
LANG
HOME
JAVA_HOME
▪️Важный момент Чтение /proc/PID/environ требует соответствующих прав доступа. Для чужих процессов доступ может быть ограничен настройками безопасности системы. И главное: вывод нельзя бездумно отправлять в логи. В окружении могут находиться токены, пароли и другие секреты. ▪️Почему это важно Когда сервис работает “вручную”, но ломается под systemd, cron или другим менеджером процессов, часто проверяют конфигурационные файлы, но забывают про окружение. /proc/PID/environ показывает именно то, что получил уже запущенный процесс - а не то, что, как кажется, должно было быть передано ему. BashTex 📱 #bash #linux

/proc/PID/fd: как узнать, что на самом деле держит процесс ps показывает процесс, lsof умеет искать открытые файлы. Но иногда быстрее посмотреть непосредственно в /proc. У каждого процесса есть каталог:
ls -l /proc/1234/fd
Внутри находятся символические ссылки на все открытые файловые дескрипторы процесса. ▪️Что можно увидеть Например:
0 -> /dev/null
1 -> /var/log/app.log
2 -> /var/log/app.err
5 -> /tmp/data.db
0, 1 и 2 — стандартные stdin, stdout и stderr. Остальные дескрипторы приложение открыло самостоятельно. ▪️Найти удалённые файлы Особенно полезен такой поиск:
ls -l /proc/1234/fd | grep deleted
Можно обнаружить:
7 -> /var/log/app.log (deleted)
Файл уже отсутствует в каталоге, но процесс всё ещё держит его открытым. ▪️Посмотреть тип дескриптора
readlink /proc/1234/fd/7
Результат может быть:
socket:[48192]
или:
pipe:[72831]
или:
/run/app.sock
То есть через один каталог можно увидеть не только обычные файлы, но и сокеты, pipes и другие объекты ядра. ▪️Найти все открытые дескрипторы процесса
for fd in /proc/1234/fd/*; do
    printf '%s -> %s\n' "${fd##*/}" "$(readlink "$fd")"
done
Получается компактная карта того, с какими объектами взаимодействует процесс. ▪️Почему это важно Если сервис ведёт себя странно, полезно посмотреть не только его аргументы и память, но и то, что он реально держит открытым. /proc/PID/fd помогает быстро найти удалённые логи, неожиданные файлы, UNIX-сокеты и каналы IPC - причём без отдельного инструмента анализа. BashTex 📱 #bash #utils

PIPESTATUS: как узнать, какая команда в пайпе реально упала В Bash есть неприятная особенность: после пайпа $? показывает статус только последней команды. Например:
false | grep test

echo $?
Здесь grep успешно отработал и вернул 0, поэтому ошибка false потерялась. ▪️На помощь приходит PIPESTATUS Сразу после выполнения пайпа:
false | grep test

echo "${PIPESTATUS[@]}"
Получим:
1 1
Каждое значение соответствует своей команде.
false       → 1
grep test   → 1
▪️Более наглядный пример
cat /missing/file | grep ERROR | sort

echo "${PIPESTATUS[@]}"
Можно получить:
1 1 0
То есть cat завершился с ошибкой, grep ничего не получил, а sort технически выполнился успешно. ▪️Важный нюанс PIPESTATUS очень легко потерять:
false | true

echo "${PIPESTATUS[@]}"
работает. Но если между пайпом и проверкой выполнить другую команду:
false | true

echo "check"
echo "${PIPESTATUS[@]}"
теперь PIPESTATUS относится уже к echo "check". Поэтому сохраняйте его сразу:
false | true

status=("${PIPESTATUS[@]}")

echo "${status[@]}"
▪️А если нужен общий статус пайпа? Есть:
set -o pipefail
Теперь:
false | true

echo $?
вернёт ненулевой код. Но pipefail отвечает только на вопрос «пайп завершился успешно?», а PIPESTATUS позволяет понять «какая именно команда вернула ошибку?». ▪️Почему это важно В сложных Bash-конвейерах последняя команда может успешно завершиться даже тогда, когда предыдущая уже упала. PIPESTATUS позволяет не терять эти ошибки и точно определить проблемный этап. BashTex 📱 #bash #utils