Last active
December 28, 2015 01:09
-
-
Save colrichie/7418498 to your computer and use it in GitHub Desktop.
GZPIPE - Making a named pipe behave as a gzipping filter
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
#! /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 |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
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)