2

I am creating a self-extracting archive and I have got the basic thing going.

However I want to set the command that extracts the archive contents tail -n+$ARCHIVE_START_LINE $0 into a variable. I can't get this part to work

I have tried many things here - one is shown below for explanation. (Note this is a simplified example and the question is as much about understanding pipes etc as solving the problem.)

#!/bin/bash
export TMPDIR=`mktemp -d /tmp/selfextract.XXXXXX`
ARCHIVE_START_LINE=`awk '/^__ARCHIVE_BELOW__/ {print NR + 1; exit 0; }' $0`

# This works...
tail -n+$ARCHIVE_START_LINE $0 | tar xzv -C $TMPDIR

# .. but this is the kind of thing I want. But it doesn't work...
ARCHIVE_CONTENTS=`tail -n+$ARCHIVE_START_LINE $0`
echo $ARCHIVE_CONTENTS | tar xzv -C $TMPDIR
# I get: gzip: stdin is a multi-part gzip file -- not supported

I am on Ubuntu 12.04

Jeff Schaller
  • 67,283
  • 35
  • 116
  • 255

3 Answers3

2

All of your problems are explained in Why does my shell script choke on whitespace or other special characters?, but I'll repeat the relevant parts here.

First, $var in a shell script does not mean “the value of the variable var”, it means “take the value of the variable var, split it into words and interpret each word as a wildcard pattern”. To avoid these extra steps, put double quotes around variable substitutions: "$var" means “the value of the variable var”. Always put double quotes around variable substitutions and command substitutions unless you know and understand why need to leave them out.

Second, echo doesn't exactly print its argument: it interprets some options, and in some shells, it interprets backslash escape sequences. Furthermore echo prints a trailing newline, which you could remove with the -n option. Gzipped data never looks like an option and bash's echo treats backslashes literally by default, but in general, don't use echo on unknown data, use printf %s instead.

printf %s "$ARCHIVE_CONTENTS" | tar xzv -C "$TMPDIR"

This is guaranteed to feed the value of the variable ARCHIVE_CONTENTS to tar exactly.

The remaining problem, and one you can't solve without using a fundamentally different approach, is that most shells (all the shells I've seen apart from zsh) truncate the value of variables at the first null byte (this is because they use C null-terminated strings under the hood). So the variable ARCHIVE_CONTENTS in fact contains only the beginning of the archive file, up to the first null byte. You can't use a shell to manipulate binary data.

1

why not use a here document like in the old days ?

 #!/bin/bash
 export TMPDIR=`mktemp -d /tmp/selfextract.XXXXXX`

 ## other stuff

 base64 -d <<EOF | tar xvf -C $TMPDIR
 ## base64 encoded
 EOF

you produce base64 encoded this way

 tar cf - my_dir | base64 > /some/place

(including it int self extract archive is left to the reader)

Archemar
  • 31,554
0

You may be inadvertently shell-expanding the contents of your archive. Try quoting your variable.

Replace

echo $ARCHIVE_CONTENTS | tar xzv -C $TMPDIR

with

echo "$ARCHIVE_CONTENTS" | tar xzv -C $TMPDIR

You also may need to avoid appending a newline with echo -n

cpugeniusmv
  • 2,657