#!/bin/sh
# 2018-12-19 w jch - modified for better posix compliance
#-------------------------------------------------------------------------------
# exex - create the SXE (mode=c), or (ex)tract & (ex)ecute the SXE (mode=x)
# ---- ------ --- ------ -- -- --- ------
# SXE = (S)elf-e(X)tracting-(E)xecutable script file
Create() { #--- Create the whole self-extracting archive and executable script.
#--- get the file size of this file, which will be the first file in the sxe,
size1=`ls -l $0 |awk 'BEGIN{FS=" "}{printf("%s",$5)}'`
#-- and the size of the user-provided script file, which will be the 2nd file,
size2=`ls -l $script |awk 'BEGIN{FS=" "}{printf("%s",$5)}'`
#--- and the size of the user-provided archive, which will be the 3rd file
# size3=`ls -l $archive |awk 'BEGIN{FS=" "}{printf("%s",$5)}'`
#--- NOTE: we don't actually need to know size3, hah, so I commented it out.
#--- Build a candidate line 2, which will be interpreted as a comment when
#--- the combined script files run. The two scripts will execute one after the
#--- other, like a single script. The size info line 2 comes right after the
#--- shebang line, i.e., the line that says: "#!/bin/sh" ... The magical
#--- importance of the inserted 2nd line is that it provides info to the
#--- Extract() function, as to exactly where the user-provided archive file
#--- begins, so it can be extracted from the sxe, and made into a usable
#--- copy of the original archive. Thus, it must also contain the name of that
#--- original user-provided archive. This Create() function will also insert
#--- an "exit 0" at the end of the user provided script file, so the shell
#--- won't try to execute the archive, which will be tucked in this file when
#--- the exex script and the user provided script are executed together, as
#--- one. The shell will interpret the two scripts together in one pass, like
#--- they were a single script, and that's what makes it "self-executing."
exitcommand="exit 0" #--- The actual length of this line will be 7,
# exitcommandlength=7 #--- 6 for the characters, plus 1 for the "\n" char.
#--- Now begins a series of approximations of the exact offset of the archive.
offset=$(($size1+$size2+7+4+${#archive})); # We know where the 7
#--- in this offset sum came from, and the 4 in the same calculation is for
#--- the length of the 3 # chars and the \n in the line 2 info comment. Then
#--- we add the length of the NAME of the archive file, NOT the length, or
#--- the size, of the actual archive file, because the name of the file will
#--- be the 2nd item of info in the info comment line.
#--- Now the 2nd, closer approximation: The problem is the decimal string len.
offset2=$(($offset+${#offset})) #--- Add string length of offset.
offslen=${#offset} #--- The approx. length of the decimal string.
offslen2=${#offset2} #--- offslen is 1 estimate, offslen2, closer
#- Make offslen2 the EXACT offset, after 1st seeing if the length "rolls over"
#- (like speedometer mileage), for example, 999 is 3 digits, 1000 is 4 digits.
if [ $offslen2 -gt $offslen ]; then $offset2=$(($offset2+1)); fi
line2='#'$offset2'#'$archive"#" #--- Now, finally, offset2 = the exact offset
# Oh I know, I could've just used a few # chars for padding, but it was fun:)!
#--- Create a temporary filename for the tmp directory,
tmpf=$HOME/grip/tmp/exexarchive.`wasat.who` #( wasat.who = pts.0, tty1, etc.,)
#( something unique to this user.)
#--- add the first line of this file (the shebang line) to the temporary file,
line1=`awk 'NR==1{print $0}' $0` #(Select NR (Number of Record)=1,which means)
#(the 1st line. (R)ecord=row, (F)ield=column)
echo $line1 > $tmpf
#--- followed by the 2nd info or comment line, which we need to insert, so the
#--- Extract() function can use that info, and will know where to find it,
echo $line2 >> $tmpf
#--- Now, add lines 2 through the end of file, of the exex file, after line 2,
tail -n +2 $0 >> $tmpf
#--- and then add all the lines of the user supplied script file after that,
cat $script >> $tmpf
#--- and add the exit 0 command after the user supplied script, just in case,
#--- because we wouldn't want the shell interpreter interpreting the archive.
echo $exitcommand >> $tmpf
#--- and add the archive file,
cat $archive >> $tmpf
#--- and rename it, at the same time moving to the path specified in the name,
mv $tmpf $sxe
chmod +x $sxe #--- and then, finally, make the whole file executable.
echo 'Finished creating "'$sxe'"'
}
Extract() { #--- Extract the user-provided archive file, and then the 2nd part
#--- of this file will execute, right after this function executes.
#--- The shell interpreter's timing considerations will make the 2nd script
#--- wait for the archive to be extracted, just like we have to wait for a
#--- command to finish before the shell shows us the next command prompt.
line2=`head -n 2 $0 |awk 'NR==2{print $0}'`
offset=`echo $line2 |awk 'BEGIN{FS="#"}{printf("%s",$2)}'`
archive=`echo $line2 |awk 'BEGIN{FS="#"}{printf("%s",$3)}'`
if [ ! -f $archive ]; then
tail -c +$(($offset+1)) $0 > $archive
fi
}
Syntax() { #--- Complain about syntax errors and/or any type of errors, & exit.
if [ "$mes" != "" ]; then
echo
echo "Error: *** "$mes" ***"
fi
echo
echo "Syntax:"
echo " exex scriptFile archiveFile nameForNewSelfExtractingExecutable"
echo
exit 1
}
#--- ma(in lo)gic
#--- Determine if we're running in create mode, or in extract and execute mode.
mode= #--- if filename is exex, run in create mode, otherwise, use ex & ex mode
mes= #--- error message for the Syntax() function
name=`echo $0 |awk 'BEGIN{FS="/"}{printf("%s",$NF)}'` #--- this filename
if [ "$name" = "exex" ]; then mode=c; else mode=x; fi
case "$mode" in
("c") #--- Create the archive
#--- First check the command line arguments.
exex=$0
script=$1
archive=$2
sxe=$3 #--- S.X.E. is bad language for (S)elf e(X)ecuting (E)xecutable.
if [ "$sxe" = "" ]; then Syntax; #--- If last one is empty all 3 are empty.
elif [ ! -f $script ]; then
mes='Script file: "'$script'" not found.'; Syntax
elif [ ! -f $archive ]; then
mes='Archive file: "'$archive'" not found.'; Syntax
elif [ -f $sxe ]; then
mes='Target file: "'$sxe'" already exists.'; Syntax
fi
Create
;;
("x") #--- Extract the user-provided archive file, which is file section 3 in
#--- this weird file, and then let the shell interpreter start
#--- interpretting the user-provided script, which is file section 2.
#--- We are now close to the end of file section 1. Thanks for visiting.
Extract
;;
(*) ;;
esac
#--- PROGRAMMER(S): THIS IS IMPORTANT! Do NOT add an exit command, and PLEASE,
# exit 0 #--- PLEASE, DON"T uncomment THIS line, or the user's script file
# won't be executed! Which would thwart the whole purpose of this file.
#--- BECAUSE: we want the two scripts to run together, like a single script.
#-------------------------------------------------------------------------------