Creating a zip file and recursing through subfolders

Back in the early 1980s I used PKZIP in DOS to archive files to conserve disk space. I am finding that I have this need now. Can anyone assist me with this?

Requirements:
1 – select a folder from Finder
2 – store the folder name to say “x”
2 – in that folder “x”, compress all files and sub-folders with path (use “zip -rjm”??)
3 – rename the zip file to x.zip
4 – delete all the files that created the zip file.

So let’s say the folder structure is:

Vacation
- New York
- Pictures
- Text

In Finder I select New York
I want to create a zip file in the folder New York
This zip file is named New York and does a “zip -rjm”. So I want to recuse the subfolders and move the files to the zip file keeping the subfolder path. Then I can unzip the file and the subfolders would be restored.

This seems like something that would be a popular macro. Any help would be greatly appreciated!

Kurt

You are probably aware that you can:

  • right-click a folder in Finder
  • choose Compress from the contextual menu that pops up

But perhaps its mainly the renaming that you want to automate a bit more ?

Yes. The right-click, Compress doesn’t move files to a zip file, recuse the subfolders, or store the path…I am looking for something a little more than that.

Sounds like a great tool. I could use that as well, so I hope you can get the help you need to build this macro.

Years ago I used WinZip to do this exact process, as well as for other features. If you can't get your macro built, you might check it out.

1 Like

‘recuse’ as in ‘recurse’ ?

You want a flattened file list ?

(The default context-menu ‘compress’ creates a nested archive including sub-folders)

Yes, “Recurse”. I don’t know what a flattened file list is. I want to be able to unzip the file and the subfolders and the files in those subfolders restored.

After further review the “Compress” is almost correct except I need it to “move” the files into the zip file, not “add”…

I need it to "move" the files into the zip file, not "add"....

i.e. compress the folder tree to a zip archive file and then delete the folder tree itself ?

( move = copy + delete )

The deletion stage is a bit tricky, because the only way of testing that the compression worked (and that you really do have a backup) is to decompress somewhere and verify.

Not sure that I would personally want to automate large-scale deletions ...

Good point.

Don't the better tools use a checksum to verify file integrity?

If you moved to Trash rather than a true delete, then you would have a backup, for a while. And then there are always Time Machine backups.

I used PKZip and WinZip for years back in my Windows days (ugg) for the very purpose @Kurt_Kessler stated. I routinely "moved" files from their normal state to a zip file, and I don't recall every having an issue with loss of data/files.

But then we each have our own pain/risk tolerance. :wink:

If you go to terminal and type “zip” you will find a -m parameter.

-m move into zipfile (delete OS files)

This parameter instructs zip to basically delete the OS file after the zip. I remember this parameter from my pkzip days in DOS. We used it for years with no problems.

1 Like

Wow! This is very impressive:

Zip 3.0 (July 5th 2008). Usage:
zip [-options] [-b path] [-t mmddyyyy] [-n suffixes] [zipfile list] [-xi list]
  The default action is to add or replace zipfile entries from list, which
  can include the special name - to compress standard input.
  If zipfile and list are omitted, zip compresses stdin to stdout.
  -f   freshen: only changed files  -u   update: only changed or new files
  -d   delete entries in zipfile    -m   move into zipfile (delete OS files)
  -r   recurse into directories     -j   junk (don't record) directory names
  -0   store only                   -l   convert LF to CR LF (-ll CR LF to LF)
  -1   compress faster              -9   compress better
  -q   quiet operation              -v   verbose operation/print version info
  -c   add one-line comments        -z   add zipfile comment
  -@   read names from stdin        -o   make zipfile as old as latest entry
  -x   exclude the following names  -i   include only the following names
  -F   fix zipfile (-FF try harder) -D   do not add directory entries
  -A   adjust self-extracting exe   -J   junk zipfile prefix (unzipsfx)
  -T   test zipfile integrity       -X   eXclude eXtra file attributes
  -y   store symbolic links as the link instead of the referenced file
  -e   encrypt                      -n   don't compress these suffixes
  -h2  show more help
1 Like

You could experiment with building and executing the zip command line that you want,
using an Execute JavaScript for Automation (or AppleScript or Bash) action in Keyboard Maestro.

The elements of a JS action might look a bit like this:

(function () {
    'use strict';

    // selectedPaths :: () -> [pathString]
    var selectedPaths = function () {
        return Application('Finder')
            .selection()
            .map(function (x) {
                return decodeURI(x.url())
                    .slice(7);
            });
    };

    var strCMD = 'zip -r ~/testArchive3 ' + selectedPaths()
        .map(function (s) {
            return '"' + s + '"';
        }) // paths quoted
        .join(' '); // spaces between quoted paths

    var a = Application.currentApplication(),
        sa = (a.includeStandardAdditions = true, a);

    return strCMD + ' -->\n\n' + sa.doShellScript(strCMD);
})();

Thanks for the script Rob. It looks very interesting.
However, I'm not sure where to "add the zip flags".

Could you please provide an explicit example?

Thanks.

Rob, correct me if I'm wrong, but are you talking about the zip parameters in the following line?

var strCMD = 'zip -r ~/testArchive3 ' + selectedPaths()

Where the "-r" = recurse into directories. You can the parameters in the string to "zip -rm" to recurse and move.

Is there a way to change the "testArchive3" to the folder name? So the the zip will create .zip?

This looks very interesting....

That’s right – I’ve added the -r flag already, and you can add others to construct a command line in strCMD that is tailored to what you need.

You may also want to arrange for an alternative output path, for example.

AppleScript seems so heavy handed for this. Since you're executing a shell script, why not just use bash? Here's a working example I quickly wrote up:

The script:

SAVE_DIR=`dirname "$KMVAR_Path"`
BASE=`basename "$KMVAR_Path"`
BASE_NO_EXT=${BASE%.*}

pushd "$SAVE_DIR"

zip -rm "$BASE_NO_EXT" "$BASE"

popd

If you want to change the name of the zip file, just set a new variable in KM and then change the line to zip -rm "$KMVAR_<thevarname>" "$BASE" where <thevarname> is the name of your variable

2 Likes

Thanks for sharing your macro/script.

I'm very much a Bash novice, so I hope you'll excuse my basic questions.

  1. This looks like it will execute the script for each file.
  • Is that correct?
  • If so, wouldn't it be better to zip all files at once? Or is this not possible?
    .
  1. How do you pass a list of files to the zip command?

To answer your questions:

  1. It does it one at a time. Based on the request, he wanted to compress a single folder into an archive. The bash script doesn’t handle this, the for-loop does within KM

  2. The syntax for compressing multiple files with zip is zip file.zip file1 file2 file3

Here’s a little more context into what’s happening with the script:

SAVE_DIR=`dirname "$KMVAR_Path"`

Gets the directory name for the Path. If the path is /Users/myname/Desktop/My Folder then the dirname is /Users/myname/Desktop

BASE=`basename "$KMVAR_Path"`

This gets the name of the folder without the dirname. From the example above, we’d get “My Folder”

BASE_NO_EXT=${BASE%.*}

This isn’t necessary unless you’ve selected a file with an extension. This will strip off the “.txt” from a file name like “file.txt”

pushd "$SAVE_DIR"

push the save path into the directory stack

zip -rm "$BASE_NO_EXT" "$BASE"

zip up the folder

popd

pop the last path added to the directory stack. In our case here, it’s “$SAVE_DIR”


For compressing all of the selected files into a single archive, that’d take a bit more work. If someone’s looking for this particular solution, I’ll write it up, otherwise I’ll save myself some work :slight_smile:

If that be the case, then it would be easy enough using the KM For Each to build the file list, then pass the entire list to the Bash Script. See anything wrong with that?

Thanks. I would like this, but if what I stated above will work, I think I can modify the macro to do it.

But if it is not too much trouble for you, I'd love to learn from an expert.

BTW, for any other Bash novices like me out there reading this, here is the complete, very detailed, Apple manual on Zip:

zip(1) Mac OS X Manual Page

Although this page states it is for macOS 10.9, the zip version from doing a man zip in Terminal is "ZIP(1L)", the same as in the online manual. I hope that means they are the same version?

Forgot to mention this. Many, many thanks for the detailed step-by-step description. It really helps me.

Very welcome!

Yep exactly. Here's a screenshot of my solution:

Here's the script at the bottom:

# creates an array from the multiple paths variable
IFS=';' read -r -a array <<< "$KMVAR_MultiPaths"

BASE_PATH=`dirname "${array[0]}"`

PATHS_TO_ZIP=''

# Loop through the array, get the base and base name just like before, and concatenate to our PATHS_TO_ZIP variable
# Concatenating with a new line is important. You'll see why below
for element in "${array[@]}"
do
    BASE=`basename "$element"`
    BASE_NO_EXT=${BASE%.*}
    PATHS_TO_ZIP="$BASE_NO_EXT\\n$PATHS_TO_ZIP"
done

pushd $BASE_PATH
# zip didn't like passing the paths as a variable because of spaces. Echoing the paths and piping it to zip solves this
# -@ archives all of the paths passed in through stdout
echo $PATHS_TO_ZIP | zip -r -@ archive.zip
popd

Hope that helps!