BashTex | Linux
Ir al canal en Telegram
Авторский канал для тех, кто хочет глубже погрузиться в мир Linux. Подойдет для разработчиков, системных администраторов и DevOps Реклама: @dad_admin
Mostrar más2 529
Suscriptores
Sin datos24 horas
+367 días
+1930 días
Archivo de publicaciones
2 529
/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 #linux2 529
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 #linux2 529
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 #linux2 529
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 #linux2 529
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 #linux2 529
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 #linux2 529
АЙТИШНИКИ БЕСПЛАТНОЕ ОБУЧЕНИЕ
Проект «Terminal» стал крупнейшей библиотекой бесплатного образования. В одном канале собраны курсы, книги, полезные инструменты и практические тренажёры для всех разработчиков:
• Практические курсы и задания
• Книги и статьи известных авторов
• Полезные инструменты и ресурсы
• IT-новости и инсайды
Обучение по всем направлениям: SQL, Python, ML, Frontend, PHP, C++, Go, Git, Linux, QA, Java, Vibe-coding, InfoSec и др.
⌨️ подписаться
2 529
$? после &&, || и 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 #linux2 529
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 #linux2 529
/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 #SSH2 529
📘 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
2 529
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 #SSH2 529
/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 #linux2 529
/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 #utils2 529
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 #utils2 529
Проверка времени отклика сервисов
Когда сервис работает, но пользователи жалуются на медлительность, нужно мерить не аптайм, а отклик. Это легко сделать обычным curl.
▪️ Самый простой замер
time curl -s https://bashtex.com > /dev/null
Показывает общее время выполнения запроса и быстро понять, тормозит или нет.
▪️ Точнее: только сетевое время
curl -s -o /dev/null -w "time_total: %{time_total}\n" https://bashtex.com
Полезные метрики:
time_namelookup
time_connect
time_starttransfer
time_total
Пример:
curl -w "DNS:%{time_namelookup} CONNECT:%{time_connect} TTFB:%{time_starttransfer} TOTAL:%{time_total}\n" \
-o /dev/null -s https://bashtex.com
▪️ Проверка нескольких сервисов
for url in https://a.ru https://b.ru; do
curl -o /dev/null -s -w "$url %{time_total}\n" "$url"
done
▪️ Таймаут обязателен
curl --connect-timeout 3 --max-time 5 https://bashtex.com
Без таймаута любой мониторинг бесполезен.
BashTex 📱 #bash #utils2 529
eval: почему опасен и когда реально нужен
В Bash почти любую строку можно превратить в команду через eval. Он берёт переданный текст и запускает его как новый Bash-код.
Именно поэтому это один из самых спорных инструментов в shell-скриптах.
▪️Как работает eval
Простой пример:
cmd="ls -la"
eval "$cmd"
Bash сначала получает строку:
ls -la
а затем выполняет её как обычную команду.
▪️Где возникает проблема
Если данные приходят от пользователя:
read input
eval "$input"
Теперь введённый текст становится кодом.
Например, вместо ожидаемого аргумента можно передать дополнительные команды, которые Bash выполнит при обработке строки.
Главная проблема eval - повторный разбор данных как команд.
▪️Почему кавычки не всегда спасают
Например:
file="test.txt"
eval "cat $file"
Пока значение простое - всё работает.
Но если внутри появятся специальные символы:
file="file name.txt"
или другие управляющие конструкции, поведение может отличаться от ожидаемого.
▪️Частая замена eval
Вместо хранения команды строкой:
cmd="ls -la /tmp"
eval "$cmd"
лучше использовать массив:
cmd=(ls -la /tmp)
"${cmd[@]}"
Теперь Bash хранит команду и аргументы отдельно.
▪️Когда eval действительно нужен
Иногда без него сложно обойтись. Например, при работе с динамическими именами переменных:
name="USER"
eval "echo \$$name"
Но даже здесь часто есть более безопасные варианты:
declare -n ref="$name"
echo "$ref"
▪️Проверка перед использованием
Если всё же нужен eval, данные должны быть полностью контролируемыми:
case "$action" in
start|stop|restart)
eval "service_$action"
;;
esac
Здесь значение ограничено заранее известным списком.
▪️Почему это важно
eval не является “плохой” командой сам по себе. Проблема появляется, когда данные превращаются в код.
В большинстве случаев массивы, case, функции или declare решают задачу безопаснее. eval стоит оставлять только для ситуаций, где действительно нужен второй проход парсинга Bash.
BashTex 📱 #eval2 529
Проверка доступности локальных Unix-сервисов:
nc -U и curl --unix-socket
Не все сервисы в Linux слушают TCP-порты. Многие приложения работают только через UNIX-сокеты - локальные каналы связи между процессами.
Например:
/run/docker.sock
/run/php/php-fpm.sock
/run/containerd/containerd.sock
Снаружи такие сервисы не видны через обычный nmap, но внутри системы они могут иметь важное значение.
▪️Найти активные UNIX-сокеты
ss -xlПример:
u_str LISTEN 0 128 /run/docker.sockТеперь можно проверить, отвечает ли сервис. ▪️Проверка через
nc
Netcat умеет подключаться к UNIX-сокетам:
nc -U /run/docker.sock
Если соединение установлено - сокет доступен.
Для HTTP-сервисов можно отправить запрос вручную:
printf "GET / HTTP/1.0\r\n\r\n" | nc -U /run/service.sock▪️Проверка HTTP API через
curl
Многие локальные API работают через UNIX-сокеты:
curl --unix-socket /run/docker.sock http://localhost/version
Здесь localhost не используется как сетевой адрес - он нужен только для формирования HTTP-запроса.
▪️Пример с Docker
Вместо:
docker version
можно напрямую обратиться к API:
curl --unix-socket /var/run/docker.sock \
http://localhost/_ping
Ответ:
OKозначает, что Docker daemon доступен. ▪️Проверка прав доступа Даже если сервис работает, доступ может быть ограничен:
ls -l /run/docker.sock
Например:
srw-rw---- root docker docker.sockТолько пользователи группы
docker смогут подключиться.
▪️Почему это важно
UNIX-сокеты часто остаются вне стандартного мониторинга. Администратор проверяет открытые порты, но забывает про локальные API и IPC-каналы.
Проверка через ss, nc -U и curl --unix-socket помогает быстро понять:
жив ли сервис;
кто имеет к нему доступ;
отвечает ли локальное API;
нет ли неожиданного открытого канала между процессами.
BashTex 📱 #sort2 529
Почему
sort | uniq не всегда работает так, как ожидается
Когда нужно убрать дубликаты, многие сразу пишут:
sort file.txt | uniq
Работает. Но только если понимать, что именно делает каждая команда.
▪️Как работает uniq
Распространённое заблуждение - uniq ищет все повторяющиеся строки.
На самом деле он удаляет только соседние дубликаты.
Например:
apple banana apple orangeЕсли выполнить:
uniq file.txtполучим тот же самый список - одинаковые строки не стоят рядом. ▪️Зачем нужен
sort
После сортировки одинаковые строки оказываются соседними:
sort file.txt | uniq
Результат:
apple banana orangeИменно поэтому эти команды почти всегда используются вместе. ▪️Когда сортировка не подходит Если порядок строк важен,
sort изменит его.
Например, при анализе логов или событий по времени это может полностью исказить картину.
В таких случаях лучше использовать:
awk '!seen[$0]++'
Команда удаляет дубликаты, но сохраняет исходный порядок строк.
▪️Найти только повторяющиеся строки
Иногда нужны не уникальные записи, а наоборот - только дубликаты:
sort file.txt | uniq -d
Или вывести количество повторений:
sort file.txt | uniq -c
Получим:
15 nginx 8 sshd 3 cron▪️Почему это важно
uniq кажется простой утилитой, но ошибка в понимании её работы часто приводит к неверным результатам. Перед использованием всегда стоит помнить: она сравнивает только соседние строки, а не весь файл целиком.
BashTex 📱 #sort2 529
mkfifo: обмен данными между процессами через именованные каналы
Обычный pipe:
command1 | command2работает только пока запущены обе команды. Но иногда нужен постоянный канал, к которому разные процессы могут подключаться позже. Для этого в Linux есть FIFO (named pipe). ▪️Создание именованного канала
mkfifo /tmp/my_pipe
Теперь /tmp/my_pipe выглядит как файл:
ls -l /tmp/my_pipe
Вывод:
prw-r--r-- 1 user user 0 Aug 4 12:00 my_pipeБуква
p означает pipe.
▪️Передача данных между двумя процессами
Терминал 1:
cat /tmp/my_pipe
Терминал 2:
echo "hello from process" > /tmp/my_pipe
Первый процесс сразу получит сообщение.
▪️Пример с логированием
Можно отправлять события из одного скрипта в другой:
mkfifo /tmp/events.pipe
tail -f /var/log/app.log > /tmp/events.pipe &
А отдельный обработчик:
while read line; do
echo "EVENT: $line"
done < /tmp/events.pipe
Теперь обработка логов отделена от источника данных.
▪️Важный момент
FIFO блокирует процесс:
echo "data" > /tmp/my_pipe
будет ждать, пока кто-то откроет канал на чтение.
Поэтому в автоматизации часто используют фоновые процессы:
cat /tmp/my_pipe &
echo "start" > /tmp/my_pipe
▪️Чем отличается от обычного файла
Данные в FIFO не сохраняются на диске.
Если никто не читает:
процесс → FIFO → процесс
данные не складываются в очередь, а ждут получателя.
▪️Где полезно
• связь между Bash-скриптами;
• простые IPC-механизмы;
• обработка потоков данных;
• разделение генератора и обработчика.
▪️Почему это важно
mkfifo часто заменяет временные файлы и сложные конструкции с перенаправлениями. Это простой способ построить связь между независимыми процессами прямо средствами Linux без дополнительных сервисов.
BashTex 📱 #mfifo