http://www.perlmonks.org?node_id=1085940

desertrat has asked for the wisdom of the Perl Monks concerning the following question:

I have a perl CGI script that munges a text input and returns one or more output files.

Because these files are being delivered from a web server, with a specific directory set for CGI scripts to write to, the resulting zip file also contains that hierarchy eg:

#/usr/bin/perl use strict; use use IO::Compress::Zip qw(zip $ZipError); my $tmpfiles = '/home/webfiles/tmpfiles'; open ($FH1,'>, $tmpfiles.'/file1.txt'); open ($FH2,'>, $tmpfiles.'/file2.txt'); # do stuff here to write to $FH1 and $FH2 close $FH1; close $FH2; zip [$tmpfiles.'/file1.txt',$tmpfiles.'file2.txt'] =>$tmpfiles.'/outpu +t.zip'; exit;

When 'output.zip' is uncompressed you end up with:

home/ webfiles/ tmpfiles/ file1.txt file2.txt
Is there any way to create the zip file without the enclosing directory hierarchy?

Replies are listed 'Best First'.
Re: Zip only files, not directory hierarchy.
by MidLifeXis (Monsignor) on May 13, 2014 at 20:43 UTC

    chdir and Cwd might be two options. Some zip clients also allow you to specify the root directory to use.

    --MidLifeXis

      Thank you! chdir worked.
Re: Zip only files, not directory hierarchy.
by GotToBTru (Prior) on May 13, 2014 at 21:01 UTC

    The command you use to expand the zip file can suppress the directory structure.

    1 Peter 4:10

      Yes, but this is for the use of naive end-users in WIndows, who will be double-clicking on the zip files.

      doing a "chdir $tmppath;" prior to the zip command does exactly what I needed it to do.

Re: Zip only files, not directory hierarchy.
by mhearse (Chaplain) on May 13, 2014 at 23:39 UTC
    find /home/webfiles/tmpfiles/ -type f | while read file do DIR=$(dirname $file) cd $DIR FILE=$(basename $file) tar -uvf /tmp/matt.tar $FILE cd $OLDPWD done gzip -9 /tmp/matt.tar # forever zorked
Re: Zip only files, not directory hierarchy.
by admiral_grinder (Pilgrim) on May 14, 2014 at 14:22 UTC

    Any reason for using IO::Compress::Zip instead of Archive::Zip? It allows you to specify the internal path and file name (Unix format) when you add a member to the archive. Just be sure that your path and filename are unique though, it does allow adding multiple files with identical filenames.

    I have done this before using Archive::Zip. You can create the zip file in memory, read in your files, modify them in memory, add them to the zip from memory, and then output via HTTP without having to write anything back to disk (assuming it all fits).

Re: Zip only files, not directory hierarchy.
by pmqs (Friar) on May 14, 2014 at 18:48 UTC

    No need to chdir. The FilterName option was written primarily to handle this use case. The code below assumes Unix/Linux path delimeters.

    zip [$tmpfiles.'/file1.txt',$tmpfiles.'file2.txt'] =>$tmpfiles.'/output.zip', FilterName => sub { s[^.*/][] };

    Alternatively, you could do away with the temporary files completely and write directly to the zip file

    use Archive::Zip::SimpleZip qw($SimpleZipError) ; my $z = new Archive::Zip::SimpleZip $tmpfiles.'/output.zip' or die "Cannot create zip file: $SimpleZipError\n" ; # If the data is in a string $z->addString("some text", Name => "file1.txt"); # or if you need carry out more complex processing $fh = $z->openMember(Name => "file3.txt"); print $fh "some data" ; print $fh "some more data" ; close $fh; $z->close();
Re: Zip only files, not directory hierarchy.
by linuxer (Curate) on May 14, 2014 at 18:39 UTC

    Have a look at the perldoc of IO::Compress::Zip. It mentions an option FilterName, which could be used.

    Quoting:

    ...

    Filtername => sub { ... }

    ... For example, the code below shows how FilterName can be used to remove the path component from a series of filenames before they are stored in $zipfile.

    sub compressTxtFiles { my $zipfile = shift ; my $dir = shift ; zip [ <$dir/*.txt> ] => $zipfile, FilterName => sub { s[^$dir/][] } ; }
    Update

    Did a test; it works ;-)

    #! /usr/bin/env perl use strict; use warnings; use File::Basename qw( basename ); use IO::Compress::Zip qw( zip $ZipError ); my @files = glob( 'data/*/*.dat' ); my $zipfile = './zipped.zip'; print "zipping $_\n" for @files; zip [ @files ] => $zipfile, FilterName => sub { $_ = basename($_); }; __END__ # source files data/foo/FOO.dat data/bar/BAR.dat # files in zip (according to 'unzip -l') FOO.dat BAR.dat