Skip to content

Instantly share code, notes, and snippets.

@colrichie
Last active December 28, 2015 01:09
Show Gist options
  • Save colrichie/7418498 to your computer and use it in GitHub Desktop.
Save colrichie/7418498 to your computer and use it in GitHub Desktop.
GZPIPE - Making a named pipe behave as a gzipping filter
#! /bin/sh
######################################################################
#
# GZPIPE - Making a named pipe behave as a gzipping filter
#
# USAGE: gzpipe [-t timeout] <named_pipe_to_use> [output_file]
#
# 1) create the named pipe <named_pipe_to_use> when unexists
# 2) read stream data from the named pipe
# 3) compress it with gzip
# 4) output to <output_file> (default: STDOUT)
# 5) remove the named pipe if the pipe is made by me
# However, the pipe will be disconnected by itself when the time will
# have been to <timeout>(default:600sec). That is not to forget to disconnect.
#
# Return: ==0 when succeed
# !=0 when failed
#
# HOW TO APPLY THIS:
# * e.g., if you want to write the executing log of a shellscript into
# a file which is gzipped, you can use commands as follow.
# > touch /PATH/TO/LOG/DIR/logfile.gz
# > chmod 600 /PATH/TO/LOG/DIR/logfile.gz
# > gzpipe /tmp/named_pipe.$$ /PATH/TO/LOG/DIR/logfile.gz
# > [ $? -eq 0 ] || { echo 'error!' 1>&2; exit 1; }
# > exec 2>/tmp/named_pipe.$$
# > set -xv
#
# NOTICE:
# * The pipe and my process will be erased when the caller process will
# die.
# * If you want to set permission to the <output_file>, you should
# make the <output_file> beforehand with the demanded permission.
# * The following command, which use redirection instead of the 2nd
# argument works correctly at almost all host.
# > gzpipe "named_pipe_to_use" > "output_file"
# But it should not be written. Beause the redirection from a
# background process will not probably be assumed. And performance
# also will probably worse than "output_file" is as an argument.
# However, the method has GREAT POSSIBILITIES. e.g., you can write
# commands as follow.
# > gzpipe "namedpipe" | zcat | tr 'A-Z' 'a-z' | grep error >error.log &
# > while [ ! -p "namedpipe" ];do sleep 0; done # wait for creating the pipe
# > exec 2>"namedpipe"
# > (various commands which write into stderr)
# That means, you can join various filters to fd=2 (stderr). Although
# it is very tricky. ;-)
#
# Written by Rich Mikan (richmikan[at]richlab.org) at 2014/06/29
#
######################################################################
DEFAULT_TIMEOUT=600
# ===== FUNCTION =====================================================
# --- define a function when invalid arguments were set --------------
print_usage_and_exit () {
cat <<-__USAGE 1>&2
Usage : ${0##*/} [-t timeout] <named_pipe_to_use> [output_file]
Version : Sun Jun 29 15:01:50 JST 2014
__USAGE
exit 1
}
# ===== PREPARATION ==================================================
# --- set the command path -------------------------------------------
PATH='/usr/bin:/bin'
# --- judge which I have started as a parent or a child --------------
while :; do
[ $# -eq 6 ] || { check='p'; break; }
pid=$$
pid=$(ps -Ao pid,ppid | awk '$1=='$pid'{print $2*1}')
[ -n "$pid" ] || { echo '*** Unexpected error (ps command)' 1>&3;break; }
pid=$(ps -Ao pid,ppid | awk '$1=='$pid'{print $2*1}')
[ "$pid" = "$5" ] || { check='p'; break; }
check='c';break;
done
# ===== ROUTINE AS A PARENT ==========================================
case $check in 'p')
# --- exit trap (for parent) ---------------------------------------
exit_trap() {
trap EXIT HUP INT QUIT PIPE ALRM TERM
rm -f "${namedpipe_to_del_by_myself:-}"
exit ${1:-0}
}
# --- parse the arguments ------------------------------------------
timeout=$DEFAULT_TIMEOUT
[ $# -ne 0 ] || print_usage_and_exit
if [ "_$1" = '_-t' ]; then
shift
[ $# -ne 0 ] || print_usage_and_exit
printf '%s' "$1" | grep '[^0-9]' >/dev/null
[ $? -ne 0 ] || print_usage_and_exit
timeout=$1
shift
fi
[ \( $# -eq 1 \) -o \( $# -eq 2 \) ] || print_usage_and_exit
[ -n "$1" ] || print_usage_and_exit
pipe=$1
outfile=''
[ \( $# -eq 2 \) -a \( -n "$2" \) ] && outfile=$2
[ "_$pipe" != "_$outfile" ] || print_usage_and_exit
# --- enable the exit trap -----------------------------------------
trap 'exit_trap' EXIT HUP INT QUIT PIPE ALRM TERM
# --- investigate the caller process ID ----------------------------
pid=$$
pid=$(ps -Ao pid,ppid | awk '$1=='$pid'{print $2}')
# --- make the named pipe if required ------------------------------
check=$(ls -l "$pipe" 2>/dev/null)
ret=$?
if [ \( $ret -ne 0 \) -a \( -z "$check" \) ]; then
mkfifo -m 600 "$pipe"
if [ $? -ne 0 ]; then
echo "${0##*/}: Can't make the named pipe" 1>&2
exit_trap 2
fi
namedpipe_to_del_by_myself=$pipe
elif [ \( $ret -eq 0 \) -a \( -n "$check" \) ]; then
case "$check" in
p*) :;;
*) echo "${0##*/}: \"$pipe\" exists as another attribute" 1>&2
exit_trap 3
;;
esac
else
echo "${0##*/}: An error occured at executing ls command" 1>&2
exit_trap 4
fi
# --- make stderr silent -------------------------------------------
exec 2>&- >/dev/null 3>/dev/null # fd3 is only available for debugging
# --- prepare for locking ------------------------------------------
sleep 100 & # This is to wait that the child process is ready
sleepjob_pid=$!
# --- exec the gzip piping script ----------------------------------
if [ -n "${namedpipe_to_del_by_myself:-}" ]; then
check=1
namedpipe_to_del_by_myself=''
else
check=0
fi
"$0" "$pipe" "$outfile" "$check" "$sleepjob_pid" "$pid" "$timeout" &
# --- wait for starting of the gzip piping script ------------------
wait "$sleepjob_pid" 2>/dev/null
# --- exit normally ------------------------------------------------
exit_trap 0
;;
# ===== ROUTINE AS A CHILD ===========================================
'c')
# This child process must be called with the arguments
# $1:namedpipe to read
# $2:file to write into
# $3:1 is set if the parent wants the child to delete $1 after using,
# otherwise 0
# $4:process ID to kill for resuming the parent process
# $5:process ID which this script waits for termination (caller PID)
# $6:timeout (seconds)
# --- validate the arguments ---------------------------------------
[ $# -eq 6 ] || { echo '*** Invalid arguments' 1>&3; exit 10; }
[ -p "$1" ] || { echo '*** Invalid argument #1' 1>&3; exit 11; }
pipe=$1
if [ \( -n "$2" \) -a \( \( ! -f "$2" \) -o \( ! -w "$2" \) ]; then
echo '*** Invalid argument #2' 1>&3
exit 12
fi
file=$2
if [ \( "_$3" != '_0' \) -a \( "_$3" != '_1' \) ]; then
echo '*** Invalid argument #3' 1>&3
exit 13
fi
del_the_pipe=$3
echo "_$4" | grep '^_[0-9]\+$' >/dev/null
[ $? -eq 0 ] || { echo '*** Invalid argument #4' 1>&3; exit 14; }
ppid=$4
echo "_$5" | grep '^_[0-9]\+$' >/dev/null
[ $? -eq 0 ] || { echo '*** Invalid argument #5' 1>&3; exit 15; }
cpid=$5
echo "_$6" | grep '^_[0-9]\+$' >/dev/null
[ $? -eq 0 ] || { echo '*** Invalid argument #6' 1>&3; exit 16; }
timeout=$6
# --- set exiting trap ---------------------------------------------
exit_trap() {
trap EXIT HUP INT QUIT PIPE ALRM TERM
ret=${1:-0}
# --- the gzipped pipe has been used or not? ---
while [ -f "${gzipret_file:-}" ]; do
check=$(cat "$gzipret_file")
[ -n "$check" ] || break
ret=$check
break
done
# --- terminate the remained processes ---
kill -s TERM "${timerjob_pid:-}" "${timersleep_pid:-}" \
"${gzippedpipingjob_pid:-}" "${gzip_pid:-}" \
"${pollingsleep_pid:-}"
# --- delete the unnecessary files ---
rm -f "${gzipret_file:-}"
[ $del_the_pipe -eq 1 ] && rm -f "$pipe"
# --- finish ---
exit $ret
}
trap 'exit_trap' EXIT HUP INT QUIT PIPE ALRM TERM
# --- tell the parent that I have started up -----------------------
kill -s TERM "$ppid"
# --- make a temporary file ----------------------------------------
if which mktemp >/dev/null 2>&1; then
gzipret_file=$(mktemp -t "${0##*/}.XXXXXXXX")
else
gzipret_file="/tmp/${0##*/}.$$"
touch "$gzipret_file"
chmod 600 "$gzipret_file"
fi
if [ $? -ne 0 ]; then
echo "${0##*/}: Can't make a tempfile" 1>&3
exit_trap 18
fi
# --- set my life time ---------------------------------------------
if [ $timeout -gt 0 ]; then
pid=$$
{ sleep $timeout; kill -s ALRM $pid; } &
timerjob_pid=$!
timersleep_pid=$(ps -Ao pid,ppid | awk '$2=='$timerjob_pid'{print $1}')
fi
# --- do the gzipped piping (hehave as a background job) -----------
pid=$$
if [ -z "$file" ]; then
{ gzip <"$pipe" ; echo $? >"$gzipret_file"; kill -s ALRM $pid; } &
else
{ gzip <"$pipe" >"$file"; echo $? >"$gzipret_file"; kill -s ALRM $pid; } &
fi
gzippedpipingjob_pid=$!
gzip_pid=$(ps -Ao pid,ppid | awk '$2=='${gzippedpipingjob_pid}'{print $1}')
# TIPS: { gzip < namedpipe_which_is_received_nothing_yet; } &
# You cannot find the process ID of gzip yet. The reason is
# that the namedpipe which is received nothing yet prevent from
# starting the gzip command until the namedpipe get any data.
# --- wait for termination of the caller (is not the parent) process
while :; do
kill -0 $cpid >/dev/null 2>&1
[ $? -ne 0 ] && break
sleep 1 &
pollingsleep_pid=$!
wait $pollingsleep_pid
done
exit_trap 0
;; esac
@colrichie
Copy link
Author

2013/11/12: fisrt release

2013/11/26: improve waiting code and add the timeout option (-t)

2013/12/24: fix a bug on FreeBSD

2013/12/25: make ps command more compatible

2013/12/28: remodel the following point
(1)check the dead of alive of the caller process
(2)stop using a executable tempfile

2013/12/29: fix signal number (12->13)

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment