Что случилось
Чтобы объяснить, что здесь происходит:
- Ctrl + C отправляет SIGINT на вся группа процессов .Это означает, что он не только завершает
tcpdump
, но также завершает gzip
.(Временные решения, которые вы пытались избежать, пытаются избежать этого путем перемещения содержимого в фоновые процессы и, следовательно, из одной и той же группы процессов). - stdout по умолчанию буферизуется в строке только в том случае, если вывод выполняется в TTY;когда выходные данные передаются в FIFO, они буферизуются в блоке, что позволяет повысить эффективность путем записи данных из левого процесса только при наличии достаточно большого фрагмента.В многих ситуациях вы можете просто использовать
stdbuf -oL
или подобное, чтобы отключить это.Однако ... gzip
по своей природе не может работать полностью без буферизации.Это потому, что алгоритмы сжатия на основе блоков должны собирать данные, в общем, блоки ;анализировать этот контент навалом;& c.
Итак, если gzip
и tcpdump
завершены одновременно, это означает, что нет никакой гарантии, что tcpdump
действительно сможет очистить свой выходной буфер, а затем gzip
прочитайте, сожмите и запишите эти очищенные данные , прежде чем gzip
сам выйдет из сигнала, который он получил одновременно.
Исправление проблемы
Обратите внимание, что фрагменты кода под заголовками, содержащими слово "Интерактивный", предназначены для интерактивного использования .
Надежный интерактивный обходной путь (для Bash)
Как верное решение, переместите gzip
полностью вне диапазона, чтобы он не был склонен к отправке SIGINT при нажатии ctrl + c на команде tcpdump
:
exec 3> >(gzip -c >test.gz) # Make FD 3 point to gzip
tcpdump -l -i eth0 >&3 # run tcpdump **AS A SEPARATE COMMAND** writing to that fd
exec 3>&- # later, after you cancelled tcpdump, close the FD.
Надежный интерактивный обходной путь (для любой оболочки POSIX)
То же самое, но немного дольше и не зависит от замены процесса:
mkfifo test.fifo # create a named FIFO
gzip -c <test.fifo >test.gz & gzip_pid="$!" # start gzip, reading from that named FIFO
tcpdump -l -i eth0 >test.fifo # start tcpdump, writing to that named FIFO
rm test.fifo # delete the FIFO when done
wait "$gzip_pid" # ...and wait for gzip to exit
Обратите внимание, что wait
будет иметьвыход из состояния процесса gzip, так что вы можете определить, чтоВ эфире обнаружена ошибка.
Надежный сценарий обходного решения (для любой оболочки POSIX)
Если мы запускаем скрипт, то целесообразно настроить обработчик сигнала, чтобы мыможет обрабатывать SIGINT (убивая только tcpdump) явно:
#!/bin/sh
[ "$#" -gt 0 ] || {
echo "Usage: ${0##*/} file.tcpdump.gz [tcpdump-args]" >&2
echo " Example: ${0##*/} foo.tcpdump.gz -l -i eth0" >&2
exit 1
}
outfile=$1; shift
fifo=test-$$.fifo # for real code, put this in a unique temporary directory
trap '[ -n "$tcpdump_pid" ] && kill "$tcpdump_pid"' INT
trap 'rm -f -- "$fifo"' EXIT
rm -f -- "$fifo"; mkfifo "$fifo" || exit
gzip -c >"$outfile" <"$fifo" & gzip_pid=$!
# avoid trying to run tcpdump if gzip obviously failed to start
{ [ -n "$gzip_pid" ] && [ "$gzip_pid" -gt 0 ] && kill -0 "$gzip_pid"; } || exit 1
tcpdump "$@" >"$fifo" & tcpdump_pid=$!
# return exit status of tcpdump if it fails, or gzip if tcpdump succeeds
wait "$tcpdump_pid" || wait "$gzip_pid"