From: sherman@unx.sas.com (Chris Sherman)
Subject: Re: how does one tar to stdout? and related?
Date: Sun, 11 Oct 1992 08:47:00 GMT
Organization: SAS Institute Inc.
Keywords: tar,stdout, pipes,redirection

In <1992Oct8.184423.27220@fnbc.com> jja@fnbc.com (Joseph Alotta) writes:

>sorry if this is misposted.  i program in perl, but i like to make
>my programs as small as possible, so i read stdin and write stdout and
>connect them all with pipes.

>i have a problem getting tar to write to stdout.  i can get it to read  
>from stdin, but not the other way around.  i have these large files on 
>tape and i want to read and subset them before i write them on disk  
>because they are really big.  i'd like to do something like:

>tar -xvf /dev/rst0  mybigfile | grep "1000" | filter.pl

>but tar always writes to disk directly.  how can i fake it out?

Don't use tar!  Use something that acts like tar...

A had this problem a while back, and wrote the following which uses a
couple of lines from a program called 'tarfix' by Diomidis Spinellis for
reading the tar header information.  It is probably a trivial task to
modify it for your needs.

---------------------------------------------------------------------------

#!/usr/local/bin/perl 

# This script takes a tar file and extracts one file from it
# and writes that file to STDOUT, without using temporary files.

# Input can come in 3 forms:
#
# star <tar file> <filename>
#   
#      where <tar file> is the name of the tar file and <filename> is 
#      the name of the file you want to extract.

# star <tar file>
# <filename>
#
#      where <filename> is read in from STDIN

# star 
# <tar file>
# <filename>
#
#      where both <tar file> and <filename> are read in from STDIN


&getArgs;
&checkArgs;

open(INFILE,$arg1) || die ("Can't open $arg1!\n");
&copytar;

exit(0);

sub getArgs
{

  # no arguments on the command line, read args from STDIN
  if ($#ARGV == -1)
  {
    $arg1 = <STDIN> || die ("Couldn't read tar archive name from STDIN\n");
    $arg2 = <STDIN> || die ("Couldn't read search pattern from STDIN\n");
  }
  # one argument on the command line, read other arg from STDIN
  elsif ($#ARGV == 0)
  {
    $arg1 = $ARGV[0];
    $arg2 = <STDIN> || die("Couldn't read search pattern from STDIN\n");
  }
  # two arguments on command line, don't read from STDIN
  elsif ($#ARGV == 1)
  {
    $arg1 = $ARGV[0];
    $arg2 = $ARGV[1];
  } 
  else
  {
    die ("Please give less than 2 arguments please.\n");
  }
}


sub checkArgs
{
  # remove blanks and nl's
  $arg1 =~ s/[\n ]*//g;
  $arg2 =~ s/[\n ]*//g;

  # check to see if tar file exists, and it is a regular file
  if (! -r $arg1 )
  {
    die ("Read permission of $arg1 is denied!\n"); 
  } 
  elsif (! -f $arg1)
  {  
    die ("$arg1 is not a regular file\n");
  }
}



# go through the tar file looking for the file we want, and copy it to STDOUT. 

sub copytar {
  
  forloop: for(;;) {

    # read the next header record
    read(INFILE, $header, 512) == 512 || die "$0: Couldn't read header: $!\n";
    if ($header eq "\0" x 512) {
      last forloop;
    }

    # break the header record down into its individual parts
    ($name, $mode, $uid, $gid, $size, $mtime, $checksum, $linkflag, $linkname) 
      = unpack("a100 A7x A7x A7x A12 A12 a8 a1 a100", $header);

    # jump up and down on name to remove blanks and things
    $name =~ s/[\000 ]*//g;

    # jump up and down on $size to get it into useful form
    $sz = $size;
    $sz =~ s/ *//g;
    $sz = oct($sz);   # why?  I have no idea, but it returns the real value

    # Is this the file that we want?
    if ($name eq $arg2)
    {
      # since the tar file tends to pad out the remainder of a 512 block
      # with junk, we need to remove it, so keep a count of how many bytes
      # we have left to write.

      $bytesRemaining = $sz;

      # Copy contents
      for ($i = 0; $i < $sz; $i += 512) 
      {
	read(INFILE, $contents, 512) == 512 || 
	  die "$0: Couldn't read data: $!\n";
#        print STDOUT $i, "\n-------\n",$contents,"\n--\n";

	if ($bytesRemaining < 512) 
	{
	  print STDOUT substr($contents,0,$bytesRemaining);
	}
	else
	{
	  print STDOUT $contents;
	}

        $bytesRemaining -= 512;

      }
      exit(0);
    }
    else
    {
      # jump directory to the next file header record
      seek(INFILE, (int($sz / 512) + 1) * 512, 1) unless $sz == 0;
    }
  }
}

-- 
     ____/     /     /     __  /    _  _/    ____/
    /         /     /     /   /      /     /          Chris Sherman
   /         ___   /        _/      /          /
 _____/   __/   __/   __/ _\    _____/   _____/           sherman@unx.sas.com
Newsgroups: comp.lang.perl