Thread View: comp.unix.shell
3 messages
3 total messages
Started by S P Arif Sahari Wibowo
Fri, 26 Sep 2008 15:09
code: history auto management (for bash, but should be close to ksh)
Author: S P Arif Sahari Wibowo
Date: Fri, 26 Sep 2008 15:09
Date: Fri, 26 Sep 2008 15:09
126 lines
6360 bytes
6360 bytes
Hi!
Just want to sharing some scripting code, and also RFC for
improving it, if any.
Background: I am lazy and indecisive. :-) I did many long
commands and one-liners that ended up reuse again and again, but
it is difficult for me to decide in the beginning which one need
to be saved and which one not, and I am too lazy too manage huge
collections of small scripts manually.
Bash history keeping have been very helpful, but it has several
limitation, especially since I tend to keep multiple shells open
for weeks:
- When the machine crashes (e.g. kid-induced power failure),
all my history is lost.
- History per shell can be huge, upon saving push out even not
too old history just saved from other shell session.
- Even though I setup generous HISTFILESIZE, one session that
does not start properly can wipe out most of stuff from the
history file.
- Even if the shell support "erasedups" HISTCONTROL,
duplication still occur in the file from multiple session. This
will be loaded back into the shell taking history space.
So I wrote a set of scripts below, generally to automatically
safe the history behind the scene and reduce duplication in
loading. Maybe it can be useful for you too. Let me know if you
have any suggestion. Specifically there are several complexity I
introduced because of some considerations. Let me know if this
considerations don't seem right:
- Instead of just automatically save the history in a fix
cycle, I try to do it in idle time, to reduce duplication of I
work vigorously use the same commands again and again.
- However, the calculation of idle time is such (idle time
required is shorter and shorter) so eventually the history will
be saved anyway.
- In loading history, I put a lot of effort to avoid race
condition where another session open and append to a history
file just before it get overwritten, so: (a) instead of saving
uniquely filtered history file to the original location, it
saves to new file, and (b) in collection all previous history
files, it is first renamed before read and deleted.
- I have several multiple systems with different tools and file
system, so the script start with try to guess which tools /
features available and which are not and define functions to
replace unavailable ones. Obviously not needed in a single or
homogeneous environment.
So here it is:
########### History management ###########
# SPAS 2008IO - http://www.arifsaha.com/
# need license? How about LGPL, ok?
#
# to be incorporated into or called from bash startup script, such as .bashrc
if ! which tac ; then
function tac() {
tail -r "$@"
}
fi
if [[ -d /proc ]] ; then
function timelastactiv() {
date -r "/proc/$1/fd/255" +"%s"
}
elif ( stat --help | grep "%y.*modification" ) >/dev/null 2>&1 ; then
function timelastactiv() {
stat -f "%y" /dev/$(ps -otty -p "$1" | tail -1)
}
else
function timelastactiv() {
stat -f "%m" /dev/$(ps -otty -p "$1" | tail -1)
}
fi
case $- in
*i* ) INTERACTIVE=1 ;;
esac
THISPID="$$"
HISTLOGFILE="/tmp/histmon.$(whoami).log"
HISTFILEBASE="$HOME/.bash_history"
HISTTMPBASE="$HOME/.bash_histtmp"
function routines() {
history -a
#echo -ne \\e[s\\e[1A$(date) history saved!\\e[u
echo $THISPID, $(date) history saved! >> $HISTLOGFILE
}
if [[ ! "$HISTFILESIZE" ]] ; then HISTFILESIZE=4096 ;fi
if [[ "$INTERACTIVE" ]] ; then
trap routines SIGUSR1
WAITINI=$((2 * 3600))
WAITSMALL=600
LASTSAVE=0
echo -n Shell $THISPID signalled by\
( trap "" SIGINT ; while WAITCUR=$WAITINI ; while TOUCHED=$(timelastactiv "$THISPID"); if [[ ! "$TOUCHED" ]] || [[ "$TOUCHED" -le "$LASTSAVE" ]] ; then IDLE=1 ; else IDLE=$(( $(date +"%s") - $TOUCHED )); fi ; echo $THISPID, $(date): $IDLE .. $WAITCUR >> $HISTLOGFILE ; [[ $IDLE -lt $WAITCUR ]] ; do WAITCUR=$(( $WAITCUR * 2 / 3 )) ; sleep $WAITSMALL ; done ; kill -s SIGUSR1 $THISPID ; do LASTSAVE=$(date +'%s'); sleep $WAITSMALL ; done & echo $!)
HISTTMPCOLLECT=$(mktemp "$HISTTMPBASE.XXXXXXXX")
{ ls -rt "$HISTTMPBASE".* | while read -r fn ; do echo got work file "$fn" >&2 ; cat "$fn" ; done
ls -rt "$HISTFILEBASE" "$HISTFILEBASE".* | while read -r fn ; do
HISTTMP=$(mktemp "$HISTTMPCOLLECT.XXXXXXXX");
if mv -f "$fn" "$HISTTMP" ; then echo got hist file "$fn" now "$HISTTMP" >&2 ; cat "$HISTTMP"
else echo cannot mv "$fn" "$HISTTMP" >&2 ; cat "$fn" ; fi
done
} | nl -ba -s: -nrz | tac | sort -t: -u -k2.1 | sort -t: -n -k1,1 | cut -d: -f2- | tail -n $HISTFILESIZE > "$HISTTMPCOLLECT"
history -r "$HISTTMPCOLLECT"
HISTCOLLECT=$(mktemp "$HISTFILEBASE.XXXXXXXX")
if mv -f "$HISTTMPCOLLECT" "$HISTCOLLECT" ; then rm "$HISTTMPCOLLECT".* ; fi
HISTNEW=$(mktemp "$HISTFILEBASE.XXXXXXXX")
if [[ -w "$HISTNEW" ]] ; then HISTFILE="$HISTNEW" ; fi
echo load history from $(wc "$HISTCOLLECT") resulting $(history | wc -l) lines, save to "$HISTFILE"
fi
--
(stephan paul) Arif Sahari Wibowo
_____ _____ _____ _____
/____ /____/ /____/ /____
_____/ / / / _____/ http://www.arifsaha.com/
Re: code: history auto management (for bash, but should be close to ksh)
Author: S P Arif Sahari Wibowo
Date: Sat, 27 Sep 2008 15:58
Date: Sat, 27 Sep 2008 15:58
161 lines
8075 bytes
8075 bytes
On Fri, 26 Sep 2008, Icarus Sparry wrote:
> If you are going to number the lines and then do sorts on them
> then you don't need to reverse the lines.
The numbering and sorting on it is to keep the original
ordering. Reverse the lines it to make sure the last occurence
of each duplication are kept.
> I suggest a
> tac | print_line_first_time_seen | head -n | tac
> pipeline would be better. print_line_first_time_seen is
> usually done as
> awk '!seen[$0]++'
Thanks! I choose the sort method before because I believe it is
more "general" in term of it does not depend on ability to put
the whole file in memory (works for large files). Obviously it
will be hardly the issue with history files. I tested your
method and it seems to be a bit faster. I keep the original line
commented nearby in-case someone one to use 1 GB history files.
:-)
> I find the long variables names bad, It took several reads to
> see that HISTTMPBASE and HISTFILEBASE were different.
Well, it was from mixed influences. I used to long file name in
Pascal, but off course there it is camel-cased. The all capital
influenced by common environment variable naming. I principally
generally favor more explanatory variable name (since data
structure is more important than algorithm in programming). In
any case, I am changing the variable name to more camel case,
hopefully it more understandable.
I don't see any code repetition can be saved by creating
function, but I also trying comment more also to get better
separation between code sections.
> if there are race conditions with multiple shells running at
> the same time, but it certainly looks as if there are issues.
There is definitely one in reading the history file: it is
possible to missed some files. But information is not lost and
next session should got them. Also, there can be an issue with
any process that open a file for extended time, but hard to get
around it without mandatory locking and shell process should not
do that, right?
So here is the improved code:
########### History management ###########
# SPAS 2008IO - http://www.arifsaha.com/
# need license? How about LGPL, ok?
#
# to be incorporated into or sourced from bash startup script, such as .bashrc
case $- in
*i* ) INTERACTIVE=1 ;;
esac
ThisPID="$$"
HISTLogFile="/tmp/histmon.$(whoami).log"
HISTFileBase="$HOME/.bash_history"
HISTTmpBase="$HOME/.bash_histtmp"
if ! which tac ; then
function tac() {
tail -r "$@"
}
fi
# define function to get the last time
# any activity in the tty of a process
if [[ -d /proc ]] ; then
function timelastactiv() {
date -r "/proc/$1/fd/255" +"%s"
}
elif ( stat --help | grep "%y.*modification" ) >/dev/null 2>&1 ; then
function timelastactiv() {
stat -f "%y" /dev/$(ps -otty -p "$1" | tail -1)
}
else
function timelastactiv() {
stat -f "%m" /dev/$(ps -otty -p "$1" | tail -1)
}
fi
function routines() {
history -a
#echo -ne \\e[s\\e[1A$(date) history saved!\\e[u
echo $ThisPID, $(date) history saved! >> $HISTLogFile
}
if [[ ! "$HISTFILESIZE" ]] ; then HISTFILESIZE=4096 ;fi
if [[ "$INTERACTIVE" ]] ; then
trap routines SIGUSR1
WaitIni=$((2 * 3600))
WaitSmall=600
LastSave=0
# Routine below run a background loop that
# will signal the shell to save history after some time.
# Instead of fix periods, it try to do it in idle time,
# to reduce duplication caused by vigorous work
# using the same commands again and again.
# However, the calculation of idle time required is shorter over time,
# so it assure that the history saving signal will be sent after some time.
( trap "" SIGINT ; while WaitCur=$WaitIni ; while TOUCHED=$(timelastactiv "$ThisPID"); if [[ ! "$TOUCHED" ]] || [[ "$TOUCHED" -le "$LastSave" ]] ; then IDLE=1 ; else IDLE=$(( $(date +"%s") - $TOUCHED )); fi ; echo $ThisPID, $(date): $IDLE .. $WaitCur >> $HISTLogFile ; [[ $IDLE -lt $WaitCur ]] ; do WaitCur=$(( $WaitCur * 2 / 3 )) ; sleep $WaitSmall ; done ; kill -s SIGUSR1 $ThisPID ; do LastSave=$(date +'%s'); sleep $WaitSmall ; done & echo $! )
echo -n Shell $ThisPID signalled by" "
HISTTmpCollect=$(mktemp "$HISTTmpBase.XXXXXXXX")
{
# Routine below collects files being worked on
# by other instance of this script:
ls -rt "$HISTTmpBase".* | while read -r afile ; do echo got work file "$afile" >&2 ; cat "$afile" ; done
# Routine below collects history files by previous sessions:
ls -rt "$HISTFileBase" "$HISTFileBase".* | while read -r afile ; do
# To avoid other session open & append the history file
# just after being read and before deleted,
# each file is renamed before read:
HISTTmp=$(mktemp "$HISTTmpCollect.XXXXXXXX");
if mv -f "$afile" "$HISTTmp" ; then echo got hist file "$afile" now "$HISTTmp" >&2 ; cat "$HISTTmp"
else echo cannot mv "$afile" "$HISTTmp" >&2 ; cat "$afile" ; fi
done
} | tac | awk '!seen[$0]++' | head -n $HISTFILESIZE | tac > "$HISTTmpCollect"
# nl -ba -s: -nrz | tac | sort -t: -u -k2.1 | sort -t: -n -k1,1 | cut -d: -f2- | tail -n $HISTFILESIZE > "$HISTTmpCollect"
# The phrase above will filter out duplicate,
# keeping the order, keeping last occurrence of any duplicate.
# The result saved to a new file, not original history file
# to avoid other session open & append the original history file
# just before it being overwritten.
# Note that race condition can still occur in reading the history file:
# it is possible a session started at the same time grab
# a history file and rename it before it can be read
# but just after collecting work file is done.
# So some history may not get loaded.
# But this is less big deal since the information is not
# lost and next session will be able to read it.
history -r "$HISTTmpCollect"
HISTCollect=$(mktemp "$HISTFileBase.XXXXXXXX")
if mv "$HISTTmpCollect" "$HISTCollect" ; then rm "$HISTTmpCollect".* ; fi
# Actual history saving file is another new history file
# instead of the new combined old history
# to keep better ordering of history (i.e. newer history file
# not to be put behind the old one):
HISTNew=$(mktemp "$HISTFileBase.XXXXXXXX")
if [[ -w "$HISTNew" ]] ; then HISTFILE="$HISTNew" ; fi
echo load history from $(wc "$HISTCollect") resulting $(history | wc -l) lines, save to "$HISTFILE"
fi
--
(stephan paul) Arif Sahari Wibowo
_____ _____ _____ _____
/____ /____/ /____/ /____
_____/ / / / _____/ http://www.arifsaha.com/
Disclaimer: IANAL, IANALP, IANAMD, IANAMP, IANAAP
my statements - if any - should be treated as such.
Re: code: history auto management (for bash, but should be close to ksh)
Author: S P Arif Sahari Wibowo
Date: Mon, 29 Sep 2008 00:55
Date: Mon, 29 Sep 2008 00:55
129 lines
6800 bytes
6800 bytes
Updated version, now include sanity check: in case processing
fails, not to remove older history files.
########### History management ###########
# SPAS 2008IO, 2008IR - http://www.arifsaha.com/
# need license? How about LGPL, ok?
#
# to be incorporated into or sourced from bash startup script, such as .bashrc
case $- in
*i* ) INTERACTIVE=1 ;;
esac
ThisPID="$$"
HISTLogFile="/tmp/histmon.$(whoami).log"
HISTFileBase="$HOME/.bash_history"
HISTTmpBase="$HOME/.bash_histtmp"
# In some system 'which' never fails,
# requiring secondary check as below, duh!
if [[ ! -x $(which tac) ]] ; then
echo define tag function >&2
function tac() {
tail -r "$@"
}
fi
# define function to get the last time
# any activity in the tty of a process
if [[ -d /proc ]] ; then
function timelastactiv() {
date -r "/proc/$1/fd/255" +"%s"
}
elif ( stat --help | grep "%y.*modification" ) >/dev/null 2>&1 ; then
function timelastactiv() {
stat -f "%y" /dev/$(ps -otty -p "$1" | tail -1)
}
else
function timelastactiv() {
stat -f "%m" /dev/$(ps -otty -p "$1" | tail -1)
}
fi
function routines() {
history -a
#echo -ne \\e[s\\e[1A$(date) history saved!\\e[u >&2
echo $ThisPID, $(date) history saved! >> $HISTLogFile
}
if [[ ! "$HISTFILESIZE" ]] ; then HISTFILESIZE=4096 ;fi
if [[ "$INTERACTIVE" ]] ; then
trap routines SIGUSR1
WaitIni=$((2 * 3600))
WaitSmall=600
LastSave=0
# Routine below run a background loop that
# will signal the shell to save history after some time.
# Instead of fix periods, it try to do it in idle time,
# to reduce duplication caused by vigorous work
# using the same commands again and again.
# However, the calculation of idle time required is shorter over time,
# so it assure that the history saving signal will be sent after some time.
( trap "" SIGINT ; while WaitCur=$WaitIni ; while TOUCHED=$(timelastactiv "$ThisPID"); if [[ ! "$TOUCHED" ]] || [[ "$TOUCHED" -le "$LastSave" ]] ; then IDLE=1 ; else IDLE=$(( $(date +"%s") - $TOUCHED )); fi ; echo $ThisPID, $(date): $IDLE .. $WaitCur >> $HISTLogFile ; [[ $IDLE -lt $WaitCur ]] ; do WaitCur=$(( $WaitCur * 2 / 3 )) ; sleep $WaitSmall ; done ; kill -s SIGUSR1 $ThisPID ; do LastSave=$(date +'%s'); sleep $WaitSmall ; done & echo -n $! >&2 )
echo " "signalling shell $ThisPID >&2
HISTTmpCollect=$(mktemp "$HISTTmpBase.XXXXXXXX")
{
# Routine below collects files being worked on
# by other instance of this script:
ls -rt "$HISTTmpBase".* 2>/dev/null | while read -r afile ; do echo got work file "$afile" >&2 ; cat "$afile" ; done
# Routine below collects history files by previous sessions:
ls -rt "$HISTFileBase" "$HISTFileBase".* 2>/dev/null | while read -r afile ; do
# To avoid other session open & append the history file
# just after being read and before deleted,
# each file is renamed before read:
HISTTmp=$(mktemp "$HISTTmpCollect.XXXXXXXX");
if mv -f "$afile" "$HISTTmp" ; then echo got hist file "$afile" now "$HISTTmp" >&2 ; cat "$HISTTmp"
else echo cannot mv "$afile" "$HISTTmp" >&2 ; cat "$afile" ; fi
done
} | tac | awk '!seen[$0]++' | head -n $HISTFILESIZE | tac > "$HISTTmpCollect"
# nl -ba -s: -nrz | tac | sort -t: -u -k2.1 | sort -t: -n -k1,1 | cut -d: -f2- | tail -n $HISTFILESIZE > "$HISTTmpCollect"
# The phrase above will filter out duplicate,
# keeping the order, keeping last occurrence of any duplicate.
# The result saved to a new file, not original history file
# to avoid other session open & append the original history file
# just before it being overwritten.
# Note that race condition can still occur in reading the history file:
# it is possible a session started at the same time grab
# a history file and rename it before it can be read
# but just after collecting work file is done.
# So some history may not get loaded.
# But this is less big deal since the information is not
# lost and next session will be able to read it.
# Saity check whether the pipe was actually successful:
# (alternatively bash' PIPESTATUS can be used,
# just it will make it harder to port to other shell)
if [[ -s "$HISTTmpCollect" ]] ; then
history -r "$HISTTmpCollect"
HISTCollect=$(mktemp "$HISTFileBase.XXXXXXXX")
if mv "$HISTTmpCollect" "$HISTCollect" ; then rm "$HISTTmpCollect".* ; fi
# Actual history saving file is another new history file
# instead of the new combined old history
# to keep better ordering of history (i.e. newer history file
# not to be put behind the old one):
HISTNew=$(mktemp "$HISTFileBase.XXXXXXXX")
if [[ -w "$HISTNew" ]] ; then HISTFILE="$HISTNew" ; fi
else
rm "$HISTTmpCollect"
fi
# Move back any work file left over because of some failures:
ls -rt "$HISTTmpCollect".* 2>/dev/null | while read -r afile ; do
HISTTmp=$(mktemp "$HISTFileBase.XXXXXXXX");
mv "$afile" "$HISTTmp"
echo mv back "$afile" "$HISTTmp" >&2
done
echo load history from $(wc "$HISTCollect") resulting $(history | wc -l) lines, save to "$HISTFILE" >&2
fi
--
(stephan paul) Arif Sahari Wibowo
_____ _____ _____ _____
/____ /____/ /____/ /____
_____/ / / / _____/ http://www.arifsaha.com/
Thread Navigation
This is a paginated view of messages in the thread with full content displayed inline.
Messages are displayed in chronological order, with the original post highlighted in green.
Use pagination controls to navigate through all messages in large threads.
Back to All Threads