#!/usr/bin/perl # 2013 Nilton Castillo Dual licensed: LGPL or Artistic2 (http://dev.perl.org/licenses/artistic.html) # inspired by the self extracting shell script. Added help, and posted on perlmonks in 2015 use strict; use warnings; use Getopt::Long; use File::Basename; use Cwd; # Only used if you use --todir (you can delete all cwd() and chdir() if unused) my $TODIR = "."; my ($help, $verbose, $f, $fn, $list, @add, @replace, @extract, @delete, $run, $unpack, $pack) = !@ARGV; GetOptions ( "help|?" => \$help, "list" => \$list, "add=s@" => \@add, "replace=s@" => \@replace, "x|extract=s@" => \@extract, "delete=s@" => \@delete, "install" => \$run, "unpack" => \$unpack, "pack" => \$pack, "verbose" => \$verbose, "todir=s" => \$TODIR, ); $help && die < Append uuencoded data to script --replace Append/replace update uuencoded data to script --extract Extract a file from the script (alternative: -x ) --delete delete a file from the script --install unpack and autorun --unpack unpack all files (so tarred files are untarred automatically) --pack repack all files/directories into the script. --verbose verbose --todir Change destination directory (default is current directory) You can also use the first letter notation: -l instead of --list. Unpack supports: tar, gz, bz2 and xz. But you need full names: myfile.tar.bz2 Thus, do not use .tbz or .tgz extensions Examples: $0 -e . -t /tmp Extract all files (as-is) to /tmp/ $0 -i -t /tmp unpack all files to /tmp/, then run all files with "install" in the name $0 -d txt Delete all files that contain txt in their name Case sensitive. use this to delete all .xz or .XZ files: '(?i)\\.XZ\$' You can easily modify your files by unpacking them to the local directory. Then use pack to update them into the sfx.pl file. Note that files inside a tar subdirectory are automatically added into the tar. ENDHELP my $MATCH = qr/^begin ([0-7]+) ([^\n ]+)$/s; # used for functions: list, extract, delete $TODIR =~s#/*$#/#; # ensure trailing slash my @SELF; # will hold the entire program in RAM $unpack = 1 if($run); @delete = qw(.) if($pack); # List all attachments: --list | -l # grep ^begin sfx.pl if($list){ while(){ print " $2\n" if(m/$MATCH/); } } # Replace file: --replace | -r # no simple oneliner. for $f (@replace){ push(@delete, @replace); push(@add, @replace); } # Generating a clean and empty self extracting file (then add back what you need) # Delete: --delete . | -d . # perl -pe 'print && exit if(/__END__/)' SFX.pl > clean.pl if(@delete){ open(INPUT,"<", $0) or die "Unable to read $0\n"; @SELF = ; # just in case, we can put it all back. close INPUT; open(OUTPUT,">",$0) or die "Unable to write $0\n"; my $skip = 0; for $f (@SELF){ if($skip){ next unless($f=~/^end$/); $skip = 0; next; } if($f=~m/$MATCH/){ $fn = $2; for $_ (@delete){ $_ = basename($_); $skip = 1 if($fn=~m/$_/); } print OUTPUT $f unless($skip); print "- $fn\n" if($skip && $verbose); push(@add, ";".$fn) if($pack); }else{ print OUTPUT $f; } } close OUTPUT; } # helper function to pipe files sub piper{ my $x = shift; my $fn = shift; my %Z = ( "xz" => [ "|xz -d", "|xz -9" ], "gz" => [ "|gunzip", "|gzip -9" ], "bz2" => [ "|bunzip2", "|bzip2 -9" ], "tar" => [ "|tar xvf -", "|tar cvf -" ], ); my $F=""; my $z; @_ = reverse split(/\.(?=(?:tar|gz|bz2|xz))/, $fn); while (@_){ $z =shift; next unless defined $Z{$z}; #print " z=$z;x=$x"; $F .= ${$Z{$z}}[$x]; } #print "Piper($x,$fn) z=$z F=$F\n"; return $x? ($F? join("|", reverse split(/\|/,$F." ".$z) ) : "$fn") : $F.($fn=~/tar/?'':">$z")||">$fn"; } # Add attachments: --add "./myfile.tar.gz" | -a "./myfile.tar.gz" # cat ./myfile.tar.gz | uuencode myfile.tar.gz >> sfx.pl for $f (@add){ my $fh =$f=~s/^;// ? piper(1,$f):$f; print "+ '$fh'\n" if($verbose); open INPUT, $fh or die "$0: can't read $f: $fh\n"; open OUTPUT, ">>",$0 or die "$0: can't open self\n"; my @stat = stat INPUT; my $mode = @stat? ($stat[2] eq 4096?0644:$stat[2]): 0644; print "mode=$mode\n"; my $omode = sprintf "%03o", $mode; my $pmode = substr $omode, -3; print "begin $pmode ".basename($f)."\n"; print OUTPUT "begin $pmode ".basename($f)."\n"; my ($inbytes, $instring); while ($inbytes = read INPUT, $instring, 45) { print OUTPUT pack "u", $instring; } print OUTPUT " \nend\n"; close(INPUT); close(OUTPUT); } # Extracting all attachments # perl -ne 'print if($GO || /__END__/ && $GO++)' sfx.pl | uudecode @extract = qw(.) if(($run||$unpack) && !@extract); my @RUN; for $f (@extract){ my $mod; my $pwd = cwd(); # only if --todir is used while(){ if(m/$MATCH/ && ($mod=$1) && ($fn=$2) && ($fn=~m/$f/)){ my $FE = piper(0,$fn); if( $unpack && (index($FE, "|")>-1) ){ chdir($TODIR) if $TODIR; open (OUTPUT, $FE) || die "Can not open pipe to $FE for $fn\n"; print "x $fn...\n" if($verbose); print "".(substr($FE,0,1)) ." $fn ...\n" if($verbose && $fn); }else{ $fn = $TODIR . $fn; open(OUTPUT, ">",$fn) or die "$0: Unable to write to $fn ($f)\n"; print "w $fn...\n" if($verbose); } push(@RUN, $fn) if($run && $fn=~/install/); # we will run this later binmode OUTPUT; my $block; while(){ last if /^end$/; $block = unpack ("u", $_); print OUTPUT $block; } close OUTPUT; chmod oct($mod), $fn if(-f $fn); chdir($pwd) if $TODIR; } } } # run if we extracted a program called .*install.* for $f (@RUN){ print "Running $f\n" if($verbose); my $pwd = cwd(); chdir($TODIR) if $TODIR; print `$f`; my $errorcode = $?>>8; print "ERROR: $f failed with errorcode $errorcode\n" if($errorcode); chdir($pwd) if $TODIR; } __END__ begin 644 hello.txt -2&5L;&\@5V]R;&0A"@`` end begin 600 data.tar.bz2 M0EIH.3%!62936>O7O?H``)M[A,*0`P!``?^`(`AG)YY@``(`""``E(2A-*?J M1H>D##48@T"J5-#TAIM1IZ@#0&U*XOEV%^AIB!KMA$0G*1A92E)R:C!F1!8F MR2-1BID/:$`F&,"2#FPP4HT*SG>4Q04)LVZERO;OA4,5BSA*=@Z#S4+@J8;: A&QH5#5&,OI<]-IV9*K%4S\R6JMB(/XNY(IPH2'7KWOT` end begin 740 install.sh M96-H;R`B2&5L;&\@5V]R;&0A('9E