3

I have attempted to write a shell script that creates another self extracting tar archive that is zipped and encoded in base64. I don't know where to go form here and have little to no experience in shell scripting.

As is this script creates tar archive that is zipped and encoded, but the self extracting does not work when i try to run the ./tarName from the terminal. Any advice is appreciated

#!/bin/sh
tarName=$1;

if [ -e $tarName.tar.gz ] 

    then /bin/echo "$tarName already exists" 
    exit 0
fi

shift;
for files;
do
    tar -czvf tmpTarBall.tar.gz $files;
done

echo "#!/bin/sh" >> $tarName.tar.gz;
echo "base64 -d $tarName.tar.gz"  >> $tarName.tar.gz;
echo "tar -xzvf $tarName.tar.gz" >> $tarName.tar.gz;
chmod +x ./$tarName.tar.gz;

base64 tmpTarBall.tar.gz >> $tarName.tar.gz;
rm tmpTarBall.tar.gz;

----------UPDATE

Did some looking around and this is what I have now, still doesn't work. Can anyone explain to me why?

#!/bin/sh
tarName=$1;

if [ -e $tarName.tar.gz ] 

    then /bin/echo "$tarName already exists" 
    exit 0
fi

shift;
for files;
do
    tar -czvf tmpTarBall.tar.gz $files;
done

cat > extract.sh;
echo "#!/bin/sh" >> extract.sh;
echo "sed '0,/^#TARBALL#$/d' $0 | $tarName.tar.gz | base64 -d | tar -xzv; exit 0" >> extract.sh;
echo "#TARBALL#" >> extract.sh;

cat extract.sh tmpTarBall.tar.gz > $tarName.tar.gz;
chmod +x ./$tarName.tar.gz;

rm extract.sh tmpTarBall.tar.gz;

When I try to run the tarName.tar.gz i get errors: ./tarName.tar.gz: 2: ./tarName.tar.gz: tarName.tar.gz: not found gzip: stdin: unexpected end of file tar: Child returned status 1 tar: Error is not recoverable: exiting now

minusila
  • 41
  • 1
  • 6
  • `exit 1` when something goes wrong — `exit 0` indicates success. You should replace the `for` loop with just `tar -czvf tmpTarBall.tar.gz "$@"` to put all the files into a tarball. The `c` option creates a tar file each time; you have stored only the last file listed, therefore. – Jonathan Leffler Mar 10 '16 at 19:25
  • You need to think carefully about what the generated shell script should look like, because that will determine what your shell script must do to create it. Your current proposed command line is dubious on many counts, not least because you can't pipe data through a gzipped tar file in the ordinary course of events. On the whole, you should probably use the `-b num` option to set a line length on the encoded output from `base64` (that's correct on Mac OS X; there's a chance the GNU version uses a different option). – Jonathan Leffler Mar 11 '16 at 03:20

2 Answers2

2

Desired output

In outline, the script you want to generate should look like:

base64 -d <<'EOF' | tar -xzf -
…base-64 encoded data…
EOF

The base64 command decodes its standard input, which is provided as a here document terminated by a line containing just EOF. The output is written to tar with options to extract gzipped data read from standard input.

Minimal script

So, a minimal generator script looks like:

echo "base64 -d <<'EOF' | tar -czf -"
tar -czf - "$@" | base64 -w 72
echo "EOF"

This echoes the base64 … | tar … line, then uses tar to generate on standard output a zipped tar file containing the files or directories named on the command line, and the output is piped to the GNU coreutils version of base64 with the option to specify that output lines should be 72 characters wide (plus the newline). This is all followed by EOF to mark the end of the here document.

You can add shebang lines (#!/bin/sh) to either or both scripts. There's no need to choose a more specific shell; this uses only core shell scripting constructs that would work back to the days of yore — before POSIX was a gleam in anyone's eye.

Possible complications

Complications that are possible include support for Mac OS X base64 which has a usage message like this:

Usage:  base64 [-dhvD] [-b num] [-i in_file] [-o out_file]
  -h, --help     display this message
  -D, --decode   decodes input
  -b, --break    break encoded string into num character lines
  -i, --input    input file (default: "-" for stdin)
  -o, --output   output file (default: "-" for stdout)

The -v option and the -d option both generate base64: invalid option -- v (for the appropriate letter), plus the usage. There doesn't seem to be a way to get version information from it. However, GNU's base64 does generate a useful message when you request base64 --version. The first line of standard output will contain something like:

base64 (GNU coreutils) 8.22
Copyright (C) 2013 Free Software Foundation, Inc.
License GPLv3+: GNU GPL version 3 or later <http://gnu.org/licenses/gpl.html>.
This is free software: you are free to change and redistribute it.
There is NO WARRANTY, to the extent permitted by law.

Written by Simon Josefsson.

This is written to standard output. So, you could auto-detect whether you have the GNU base64 and adapt accordingly. You'd need one test in the generator script, and a copy of the test in the generated script. That's definitely a more refined program.

Jonathan Leffler
  • 730,956
  • 141
  • 904
  • 1,278
1

Is it necessary to do this yourself? There is an existing tool called makeself that can do this for you. If you do need to write this yourself, here are some thoughts:

Your output file is an archive with a shell script stuck to the front of it. The extract process runs the entire output file through base64 and tar, not just the archive. The base64 call turns the script portion into garbage, which then confuses tar. What you need to do is to add some code that will separate the script from the archive, then run the remaining commands on just the archive portion. One possible way to do this is to tweak your extract script to something like this:

#!/bin/sh
linenum=$(grep -n "__END_OF_SCRIPT_MARKER__" $tarName.tar.gz | tail -1 | sed -e 's/:.*//')
tail -n +$(($linenum + 1)) $tarName.tar.gz | base64 -d | tar -xzv
exit 0
__END_OF_SCRIPT_MARKER__

Make sure there is nothing in the script portion following the marker text except a newline character (which the markup on this website doesn't make visible). With this, you're using grep to find the line number that contains the marker, then stripping off that many lines with tail. What remains will be the archive portion, which is processed normally by the rest of your code. The exit line ensures that the shell doesn't try to execute the marker text or the archive contents as code. You can keep the extract code in a less compressed format if you'd rather, but you'll end up having to create a temporary file for the archive portion and ensure that it gets deleted.

bta
  • 43,959
  • 6
  • 69
  • 99
  • When I try to echo this into my file not everything is included. i get: `#!/bin/sh linenum= tail -n +1 newTarTest.tar.gz | base64 -d | tar -xzv exit 0 __END_OF_SCRIPT_MARKER__` – minusila Mar 11 '16 at 02:42
  • @minusila - when using echo to write the script to the output file, you need to escape special characters like "$()". Otherwise, the shell will evaluate them before the echo happens. – bta Mar 20 '16 at 15:19