LinuxQuestions.org
Support LQ: Use code LQ3 and save $3 on Domain Registration
Go Back   LinuxQuestions.org > Forums > Non-*NIX Forums > Programming
User Name
Password
Programming This forum is for all programming questions.
The question does not have to be directly related to Linux and any language is fair game.

Notices

Reply
 
Search this Thread
Old 11-03-2011, 07:20 AM   #1
giantanakim
LQ Newbie
 
Registered: Oct 2011
Posts: 23

Rep: Reputation: Disabled
Linux script Limiting Zip archive size


Hi Everyone:

I am trying to zip a large amount of wav files through a script. However, this zip file needs to be uploaded to a business partners FTP server, which limits files to 2gig max. They have things set up so that it has to be a zip file specifically.

Is there a way to script to take the contents of a folder, zip all the files into one zip file unless it is more than 2 gigs, in which case break it into 2 files, or more if necessary. I cannot use the split archives for this as each file needs to be stand alone.

Thanks everyone
 
Old 11-03-2011, 09:28 AM   #2
MensaWater
Guru
 
Registered: May 2005
Location: Atlanta Georgia USA
Distribution: Redhat (RHEL), CentOS, Fedora, Debian, FreeBSD, HP-UX, Solaris, SCO
Posts: 5,993
Blog Entries: 5

Rep: Reputation: 782Reputation: 782Reputation: 782Reputation: 782Reputation: 782Reputation: 782Reputation: 782
zipsplit splits a zip file into multiple zip files and you can specify maximum size with the -n option.

http://wiki.linuxquestions.org/wiki/Zipsplit
 
Old 11-03-2011, 10:23 AM   #3
giantanakim
LQ Newbie
 
Registered: Oct 2011
Posts: 23

Original Poster
Rep: Reputation: Disabled
I looked into zipsplit. It appears that zipsplit creates files which are dependent on each other (ie. file1.ro1, file2.ro2, etc) where all of the pieces are required to be present before the file can be unzipped.

Am I incorrect on this? The problem with that, if it is true, is that their system unzips a file as soon as it completed uploading. At that point, the next file upload overwrites the prior file as they all are required to be named the same.

If I am confused, please clear me up!
 
Old 11-03-2011, 11:18 AM   #4
MensaWater
Guru
 
Registered: May 2005
Location: Atlanta Georgia USA
Distribution: Redhat (RHEL), CentOS, Fedora, Debian, FreeBSD, HP-UX, Solaris, SCO
Posts: 5,993
Blog Entries: 5

Rep: Reputation: 782Reputation: 782Reputation: 782Reputation: 782Reputation: 782Reputation: 782Reputation: 782
No - the zip files created by zipsplit are independent of each other.

As a test I created a zip file of a directory where I keep various scripts and binaries for personal use:
Code:
zip bin.zip *
That created the file bin.zip. I then ran unzip to a different directory to make sure that had all the files.

I then ran the following to break up the single zip into multiples:
Code:
zipsplit -n 23000 bin.zip
That output:
4 zip files will be made (100% efficiency)
creating: bin1.zip
creating: bin2.zip
creating: bin3.zip
creating: bin4.zip

I then did an scp of bin2.zip to another server and ran unzip there. This extracted the files in that zip file without asking about any of the others. I then did scp of bin4.zip and unzipped it with the same results. I then did it for bin1.zip then for bin3.zip and in all four cases the files unzipped cleanly without asking for any of the others. (As noted when I did bin2.zip there were no others on the system.)
 
Old 11-03-2011, 11:34 AM   #5
Nominal Animal
Senior Member
 
Registered: Dec 2010
Location: Finland
Distribution: Xubuntu, CentOS, LFS
Posts: 1,723
Blog Entries: 3

Rep: Reputation: 942Reputation: 942Reputation: 942Reputation: 942Reputation: 942Reputation: 942Reputation: 942Reputation: 942
The man page for zipsplit says it does not support files over 2G in size.

Quote:
Originally Posted by giantanakim View Post
Is there a way to script to take the contents of a folder, zip all the files into one zip file unless it is more than 2 gigs, in which case break it into 2 files, or more if necessary.
zipsplit seems to do just that to a zip archive, but I'm not sure it works for such large archives.

You can pre-group your wav files beforehand, and then zip them separately. They are unlikely to compress much, so grouping each 1800 M to 2 G of wav files to be zipped in one archive should work.

Here is a simple Bash script you might start with:
Code:
#!/bin/bash
if [ $# -lt 3 -o "$1" == "-h" -o "$1" == "--help" ]; then
    exec >&2
    echo ""
    echo "Usage: $0 [ -h | --help ]"
    echo "       $0 name maxsize folder(s)..."
    echo ""
    echo "This program creates name.zip containing all files in the"
    echo "specified folders, unless their combined size exceeds maxsize."
    echo ""
    echo "When the combined file sizes exceed maxsize, this program"
    echo "will create name-1.zip, name-2.zip, and so on, with each"
    echo "archive containing at least one file, but archive contents"
    echo "not exceeding the maxsize. Each archive will contain full files."
    echo ""
    echo "Files are not reordered or sorted, so archive sizes may"
    echo "fluctuate wildly."
    echo ""
    exit 0
fi

# Base name of the zip archive to create
BASENAME="$1"
if [ -z "$BASENAME" ]; then
    echo "Empty zip archive name!" >&2
    exit 1
fi

# Maximum size for input files for each archive
case "$2" in
    *k|*K)           MAXTOTAL=$[ (${2//[^0-9]/} -0) * 1000 ] || exit $? ;;
    *kb|*kB|*Kb|*KB) MAXTOTAL=$[ (${2//[^0-9]/} -0) * 1024 ] || exit $? ;;
    *m|*M)           MAXTOTAL=$[ (${2//[^0-9]/} -0) * 1000000 ] || exit $? ;;
    *mb|*mB|*Mb|*MB) MAXTOTAL=$[ (${2//[^0-9]/} -0) * 1048576 ] || exit $? ;;
    *g|*G)           MAXTOTAL=$[ (${2//[^0-9]/} -0) * 1000000000 ] || exit $? ;;
    *gb|*gB|*Gb|*GB) MAXTOTAL=$[ (${2//[^0-9]/} -0) * 1073741824 ] || exit $? ;;
    *[^0-9]*)        echo "$2: Invalid maximum size." >&2
                     exit 1 ;;
    *)               MAXTOTAL=$[ $2 ] || exit $? ;;
esac
shift 2

find "$@" -type f -print0 | (

    # Current index, list of files, and total size of files
    INDEX=0
    FILES=()
    TOTAL=0

    while read -d "" FILE ; do
        SIZE=`stat -c %s "$FILE"` || continue

        NEWTOTAL=$[ SIZE + TOTAL ]
        if [ ${#FILES[@]} -lt 1 ] || [ $NEWTOTAL -le $MAXTOTAL ]; then
            FILES=("${FILES[@]}" "$FILE")
            TOTAL=$NEWTOTAL
            continue
        fi

        INDEX=$[ INDEX + 1 ]
        zip "$BASENAME-$INDEX.zip" "${FILES[@]}" || exit $?

        FILES=("$FILE")
        TOTAL=$SIZE
    done

    if [ ${#FILES[@]} -gt 0 ]; then
        if [ $INDEX -gt 0 ]; then
            INDEX=$[ INDEX + 1 ]
            zip "$BASENAME-$INDEX.zip" "${FILES[@]}" || exit $?
        else
            zip "$BASENAME.zip" "${FILES[@]}" || exit $?
        fi
    elif [ $INDEX -eq 0 ]; then
        echo "No files to zip specified." >&2
        exit 0
    fi

    echo "" >&2
    if [ $INDEX -gt 0 ]; then
        echo "Created $INDEX files:" >&2
        for I in `seq 1 $INDEX` ; do
            echo "    $BASENAME-$I.zip ($(stat -c %s "$BASENAME-$I.zip") bytes)" >&2
        done
    else
        echo "Created 1 file:" >&2
        echo "    $BASENAME.zip ($(stat -c %s "$BASENAME.zip") bytes)" >&2
    fi
    echo "" >&2
    exit 0
)
exit $?
Run it without arguments to get usage. You can use k/M/G suffixes for the maximum size (for kilo, mega, and giga respectively), but the number must be integer; no decimals!

This script will also never use the Zip64 extensions for large archives, even when the directory contents are several gigabytes (at least if the size limit is < 2G). The resulting archives should be unpackable with even old PKZIP programs. (If I remember correctly, the old ones may choke with zipsplit archives.)

If you have wildly different file sizes, you should consider writing an awk script, which uses one of the greedy algorithms for solving the bin packing problem (to efficiently decide which file should go in which zip archive). Similar problem is encountered when backing up large directories to write-once media (DVD-R discs, for example).

If you need each zip file to be as close to the limit as possible, you could use the zip -O option to add each file to the archive without overwriting the old one; if the result is smaller than the limit, then try adding the next file to it. However, since in the worst case you'd copy almost 2G (the archive size before adding the current file -- remember, it must keep the old archive intact in case the limit is exceeded) each time you add one file, it would be quite slow at times.

I hope this gets you started,
 
1 members found this post helpful.
Old 11-03-2011, 12:31 PM   #6
MensaWater
Guru
 
Registered: May 2005
Location: Atlanta Georgia USA
Distribution: Redhat (RHEL), CentOS, Fedora, Debian, FreeBSD, HP-UX, Solaris, SCO
Posts: 5,993
Blog Entries: 5

Rep: Reputation: 782Reputation: 782Reputation: 782Reputation: 782Reputation: 782Reputation: 782Reputation: 782
The man page is cryptic on this. The project site's FAQ seems to suggest the limitation is on files within the archive. That is to say I'm not sure if its limitation is on zip files larger than 2 GB or files contained with in the zip larger than 2 GB or both.

It can't hurt to try it if you don't have any files larger than 2 GB you are zipping up or if the zip is less than 2 GB anyway.

I'll have to admit I'm surprised by such a limitation on the zip file size itself. It seems the most likely users of zipsplit would be those with large zip files and these days 2 GB is nothing.
 
Old 11-03-2011, 03:12 PM   #7
giantanakim
LQ Newbie
 
Registered: Oct 2011
Posts: 23

Original Poster
Rep: Reputation: Disabled
Ok, so I am attempting to use zipsplit. However, every time I try this command:

Quote:
zipsplit -n 23000 bin.zip
It gives me the following response:

zipsplit warning: Entry is larger than max split size of :
zipsplit warning: use -n to set split size
zipsplit error: Entry too big to split, read, or write

I have entered in multiple sizes, and nothing has work. In addition, I tried to remove the -n 23000 from the command, and still received the same error. This is a 43MB test file.

Any suggestions?
 
Old 11-03-2011, 04:05 PM   #8
Cedrik
Senior Member
 
Registered: Jul 2004
Distribution: Slackware
Posts: 2,140

Rep: Reputation: 242Reputation: 242Reputation: 242
Why not use the nice looking script posted above by Nominal Animal ?
 
Old 11-03-2011, 04:50 PM   #9
MensaWater
Guru
 
Registered: May 2005
Location: Atlanta Georgia USA
Distribution: Redhat (RHEL), CentOS, Fedora, Debian, FreeBSD, HP-UX, Solaris, SCO
Posts: 5,993
Blog Entries: 5

Rep: Reputation: 782Reputation: 782Reputation: 782Reputation: 782Reputation: 782Reputation: 782Reputation: 782
bin.zip was an arbitrary name I chose for my file because it was zip of a bin directory.

The errors are essentially blank presumably because it can not find a file named bin.zip. Odd though - I'd have thought they'd tell you file not found rather than do all that.

You need to run the command on the zip filename YOU created. Since you seem to have created it frequently prior to posting I'm assuming it is NOT named bin.zip (or it is one hell of a coincidence if it is).

The generic syntax for what I did is:

zipsplit -n <size> <filename>

Where you substitute the size in bites for <size> and filename of YOUR zip file for <filename>.
 
Old 11-04-2011, 04:54 AM   #10
Nominal Animal
Senior Member
 
Registered: Dec 2010
Location: Finland
Distribution: Xubuntu, CentOS, LFS
Posts: 1,723
Blog Entries: 3

Rep: Reputation: 942Reputation: 942Reputation: 942Reputation: 942Reputation: 942Reputation: 942Reputation: 942Reputation: 942
Quote:
Originally Posted by giantanakim View Post
zipsplit warning: use -n to set split size
zipsplit error: Entry too big to split, read, or write
If you do not specify -n size it uses a default. You can see the default if you run zipsplit without arguments; in my case, it is 36000 bytes.
Quote:
Originally Posted by giantanakim View Post
zipsplit error: Entry too big to split, read, or write
This means that either one of the split zip files would have to be larger than the maximum size specified (typically because there is at least one file that when compressed alone, is larger than the maximum size specified), or that the archive (or a single file) is larger than the 2G limit for zipsplit.

The limitations for zipsplit are due to the fact that it is a very old 32-bit format, and you can only describe lengths of up to 2147483647 bytes exactly using a signed 32-bit*integer. (Where unsigned integers are used, the limit is of course 4G, or 4294967295 bytes).

To overcome the limitations, Zip64 extensions were developed, using 64 bit integers (theoretically, 9223372036854775807 byte limit for signed 64-bit integers). However, zipsplit does not support those extensions. (I suspect there may be a technical reason, since the man page indicates zipsplit uses different extensions for splitting the zip archive; if those extensions are not 64-bit, then you cannot support 64-bit archives. It seems that PKZIP has only relatively recently grown support for zipsplit extensions, too.)

Quote:
Originally Posted by giantanakim View Post
Any suggestions?
Try my script, dangit, or at least tell me why it does not suit your needs. It works for me.
 
Old 11-07-2011, 12:28 PM   #11
giantanakim
LQ Newbie
 
Registered: Oct 2011
Posts: 23

Original Poster
Rep: Reputation: Disabled
@Mensawater:

I was not actually using the bin.zip in the actually command, but did not want to specifically post the name of the file. It was still giving the error. I have done some reading and it appears that the newer versions of Ubuntu have a bug regarding zipsplit.

@Nominal Animal

I would love to use your script. I am trying to understand it, as it will be connecting to other scripts in sequence. It is a bit more complex than I am used to at this point, so any help that you could provide in understanding how it works would be great.
 
Old 11-07-2011, 07:12 PM   #12
Nominal Animal
Senior Member
 
Registered: Dec 2010
Location: Finland
Distribution: Xubuntu, CentOS, LFS
Posts: 1,723
Blog Entries: 3

Rep: Reputation: 942Reputation: 942Reputation: 942Reputation: 942Reputation: 942Reputation: 942Reputation: 942Reputation: 942
Here's the script I listed above explained part by part.

If the script is called with less than three arguments, or the first argument is -h or --help, the script outputs some usage information to standard error:
Code:
#!/bin/bash
if [ $# -lt 3 -o "$1" == "-h" -o "$1" == "--help" ]; then
    exec >&2
    echo ""
    echo "Usage: $0 [ -h | --help ]"
    echo "       $0 name maxsize folder(s)..."
    echo ""
    echo "This program creates name.zip containing all files in the"
    echo "specified folders, unless their combined size exceeds maxsize."
    echo ""
    echo "When the combined file sizes exceed maxsize, this program"
    echo "will create name-1.zip, name-2.zip, and so on, with each"
    echo "archive containing at least one file, but archive contents"
    echo "not exceeding the maxsize. Each archive will contain full files."
    echo ""
    echo "Files are not reordered or sorted, so archive sizes may"
    echo "fluctuate wildly."
    echo ""
    exit 0
fi
First parameter specifies the name of the ZIP archive to create (sans .zip suffix).
Code:
# Base name of the zip archive to create
BASENAME="$1"
if [ -z "$BASENAME" ]; then
    echo "Empty zip archive name!" >&2
    exit 1
fi
Second command line parameter specifies the maximum total size for files in a single archive. ${2//[^0-9]/} evaluates to only the digits in the second parameter. If the second parameter is foo.2-bar/4 then ${2//[^0-9]/} evaluates to 24 . A case statement is used to catch the known multipliers, so that MAXTOTAL will end up being the actual limit in bytes. If the conversion calculation fails, the script aborts. (Bash will issue an error message describing the problem.)
Code:
# Maximum size for input files for each archive
case "$2" in
    *k|*K)           MAXTOTAL=$[ (${2//[^0-9]/} -0) * 1000 ] || exit $? ;;
    *kb|*kB|*Kb|*KB) MAXTOTAL=$[ (${2//[^0-9]/} -0) * 1024 ] || exit $? ;;
    *m|*M)           MAXTOTAL=$[ (${2//[^0-9]/} -0) * 1000000 ] || exit $? ;;
    *mb|*mB|*Mb|*MB) MAXTOTAL=$[ (${2//[^0-9]/} -0) * 1048576 ] || exit $? ;;
    *g|*G)           MAXTOTAL=$[ (${2//[^0-9]/} -0) * 1000000000 ] || exit $? ;;
    *gb|*gB|*Gb|*GB) MAXTOTAL=$[ (${2//[^0-9]/} -0) * 1073741824 ] || exit $? ;;
    *[^0-9]*)        echo "$2: Invalid maximum size." >&2
                     exit 1 ;;
    *)               MAXTOTAL=$[ $2 ] || exit $? ;;
esac
Since the two first parameters have been taken care of, we remove them. Third command line parameter will become first, fourth the second, and so on.
Code:
shift 2
To find all the files to archive, we use a find command, using the command line parameters as arguments to it (except the two parameters we handled above already). File names will be separated by ASCII NULs. The list is supplied as input to a subshell. (You can just think of it as a separate scope. Changes to e.g. variables are not propagated outside the subshell.)
Code:
find "$@" -type f -print0 | (

    # Current index, list of files, and total size of files
    INDEX=0
    FILES=()
    TOTAL=0
Each file name is read in a loop, and the size of that file put into variable SIZE. If the file does not exist or is otherwise unreadable, it is skipped.
Code:
    while read -d "" FILE ; do
        SIZE=`stat -c %s "$FILE"` || continue
TOTAL is the current running total. Calculate the new total, if the file were to be included into the current zip archive.
Code:
        NEWTOTAL=$[ SIZE + TOTAL ]
If the current archive is empty, or the new total does not exceed the maximum size limit, add the current file to the list of files to be archived in the current zip archive, and continue on to the next file.
Code:
        if [ ${#FILES[@]} -lt 1 ] || [ $NEWTOTAL -le $MAXTOTAL ]; then
            FILES=("${FILES[@]}" "$FILE")
            TOTAL=$NEWTOTAL
            continue
        fi
At this point we know that the current file cannot be added to the existing zip archive. Since we already know the files in the current zip archive, lets create the zip archive now. Note that INDEX was initially zero. We increase it first, so that we get -1 for the first archive, -2 for the second, and so on. If the zip command fails, abort the script.
Code:
        INDEX=$[ INDEX + 1 ]
        zip "$BASENAME-$INDEX.zip" "${FILES[@]}" || exit $?
We have taken care of all the previous files. The current file is the first file in a new archive; start a new list (and running total size) for the new archive.
Code:
        FILES=("$FILE")
        TOTAL=$SIZE
The file handling loop is complete:
Code:
    done
If there are files yet to be archived, create a new archive name. If we have not created any archives at all yet, there is no need to use the -1 in the name. Use the same zip command as above to create the archive in either case.
Code:
    if [ ${#FILES[@]} -gt 0 ]; then
        if [ $INDEX -gt 0 ]; then
            INDEX=$[ INDEX + 1 ]
            zip "$BASENAME-$INDEX.zip" "${FILES[@]}" || exit $?
        else
            zip "$BASENAME.zip" "${FILES[@]}" || exit $?
        fi
Otherwise, if INDEX is still zero, there were no files to archive at all. Abort the script if no archive was created.
Code:
    elif [ $INDEX -eq 0 ]; then
        echo "No files to zip specified." >&2
        exit 0
    fi
I like to tell the user explicitly what the script does. If INDEX is nonzero, we have created archives 1 through INDEX:
Code:
    echo "" >&2
    if [ $INDEX -gt 0 ]; then
        echo "Created $INDEX files:" >&2
        for I in `seq 1 $INDEX` ; do
            echo "    $BASENAME-$I.zip ($(stat -c %s "$BASENAME-$I.zip") bytes)" >&2
        done
otherwise we have created just the one (versionless) archive.
Code:
    else
        echo "Created 1 file:" >&2
        echo "    $BASENAME.zip ($(stat -c %s "$BASENAME.zip") bytes)" >&2
    fi
    echo "" >&2
    exit 0
Note that above, stat -c %s filename outputs the size of filename in bytes. Because we want to insert the output of that command, we enclose it in $(...). The >&2 just redirects the output of that command to standard error instead of standard output. It is not important, just something I like to do: it is nice to be able to pipe the output to another command, but see the summary on-screen when running the script (since standard error is usually directed to the terminal, even if standard output is piped to another command).

Use the subshell exit status for the entire script. If the subshell succeeded, the script will also return success.
Code:
)
exit $?
If there are some specific things you wish me to clarify, just let me know.
 
1 members found this post helpful.
Old 11-08-2011, 01:16 PM   #13
giantanakim
LQ Newbie
 
Registered: Oct 2011
Posts: 23

Original Poster
Rep: Reputation: Disabled
Wow! I really appreciate the clarification on that Nominal Animal! The script works flawlessly. I have saved it into a script file. Can I just call it from another script with the line ./nominalanimal.sh Recordings 2G ./ ? That line should provide all needed properties correct?

Don't answer that, I will figure it out myself! Thanks again for all of your help.
 
Old 11-09-2011, 08:06 AM   #14
giantanakim
LQ Newbie
 
Registered: Oct 2011
Posts: 23

Original Poster
Rep: Reputation: Disabled
Ok, a couple things just came up.

1. I ran this script on 5g worth of files. It split this up into three different zip files. -1 was 1.3G, -2 was 800MB, and -3 was 2.39GB. I am not sure why it did that. Any thoughts?

2. Can I run this for another directory? For instance, if I put in ./nominalanimal.sh /home/foo/bar/recordings will it create the zip files in the specified directory? And will it zip the files contained within that directory?

3. Can this script be modified so that once a file is added to the zip, the original can be moved to a different directory?
 
Old 11-09-2011, 08:27 AM   #15
michaelk
Moderator
 
Registered: Aug 2002
Posts: 11,856

Rep: Reputation: 738Reputation: 738Reputation: 738Reputation: 738Reputation: 738Reputation: 738Reputation: 738
It depends on the type of files. As stated binary files like audio will not compress much. Since zip files contain additional information by compressing many binary files the actual result might be bigger then the original source. And since the final zip file size is a bit of an unknown you will need to adjust max size parameter.

Last edited by michaelk; 11-09-2011 at 08:51 AM.
 
  


Reply

Tags
archive, zip


Thread Tools Search this Thread
Search this Thread:

Advanced Search

Posting Rules
You may not post new threads
You may not post replies
You may not post attachments
You may not edit your posts

BB code is On
Smilies are On
[IMG] code is Off
HTML code is Off


Similar Threads
Thread Thread Starter Forum Replies Last Post
Linux zip program's -d -tt option deletes all files from zip archive Arun Gupta Linux - Software 4 04-27-2011 07:06 PM
Shell Script how to remove absolute paths from zip archive hi_irf Linux - Newbie 1 10-03-2009 05:59 AM
browse zip archive louisJ Linux - Newbie 3 10-18-2008 03:47 AM
Script to Add files to a Zip archive with out unzipping it !! prashix Programming 12 10-23-2007 09:19 AM
What Are archive.zip files? kajensen Linux - General 1 09-21-2003 04:00 AM


All times are GMT -5. The time now is 06:50 PM.

Main Menu
My LQ
Write for LQ
LinuxQuestions.org is looking for people interested in writing Editorials, Articles, Reviews, and more. If you'd like to contribute content, let us know.
Main Menu
Syndicate
RSS1  Latest Threads
RSS1  LQ News
Twitter: @linuxquestions
identi.ca: @linuxquestions
Facebook: linuxquestions Google+: linuxquestions
Open Source Consulting | Domain Registration