Creating a zip file and recursing through subfolders

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!

@rjames86, many thanks again for your macro/script.

I've enhanced it a bit, to

  • add some documentation
  • Ask User to Confirm
  • Make it clear that the original source folder will be DELETED
  • Provide a nice report, copied to clipboard

Example Results


##Macro Library   @ZIP Folder (& all Sub-Folders) & DELETE


####DOWNLOAD:
<a class="attachment" href="/uploads/default/original/2X/7/77efe4d6b43590ae28a8072e31feaa223d3ffabb.kmmacros">@ZIP Folder (& all Sub-Folders) & DELETE.kmmacros</a> (11 KB)

---

###ReleaseNotes

HOW TO USE:

1. Select one or more FOLDERs in the Finder
   • Each Folder wll be put in a separate Zip file
2. Trigger this Macro

Author:	 @rjames86 -- Original Macro/Script Posted below 
                @JMichaelTX -- this macro based on the one by @rjames86

WARNING!

The Folder selected in the Finder will be PERMINATELY DELETED.
(not just moved to Trash)

If the Zip File already exists, it will be OVERWRITTEN.


REFERENCE

Topic:		Creating a zip file and recursing through subfolders
Forum:		Keyboard Maestro Discourse, general

Post by:		rjames86
Post Date:	2016-12-22
Script Lang:	Bash
Post URL:	https://forum.keyboardmaestro.com/t/creating-a-zip-file-and-recursing-through-subfolders/5811/15?u=jmichaeltx

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

For a detailed, step-by-step description of the script, see:
[The above post by rjames86](https://forum.keyboardmaestro.com/t/creating-a-zip-file-and-recursing-through-subfolders/5811/17?u=jmichaeltx)

---

<img src="/uploads/default/original/2X/d/da09f3e2b3fe7f3b46c0a6260dc5304ee2e6d27d.png" width="595" height="895">

BTW, to address concerns about deleting the folder/files without first expanding the zip, I did expand the zip files a number of times in the process of testing the above macro, and it worked flawlessly.

Having said that, if the files were very important, or hard to replace, then I might just zip them normally (no deletion) and test the zip integrity before i deleted the original source files.

Again, this is an individual decision.

Great writeup! I personally don’t like the idea of deleting the original folder. Seems too dangerous.

I’d personally change the line zip -rm "$BASE_NO_EXT" "$BASE" to be:

zip -r "$BASE_NO_EXT" "$BASE"

It would be easy to add another confirmation if you wanted to delete or not. A way to do this could be to ask, and if they do, set a variable DELETE_CHECK to “true”. The code would then look like this:


if [[ $KMVAR_DELETE_CHECK == 'true' ]]; 
then 
    ZIP_ARGS="rm";
else
    ZIP_ARGS="m";
fi

zip -$ZIP_ARGS "$BASE_NO_EXT" "$BASE"

OK, I'm open to that.
How would you verify the integrity of the zip, before deleting the original folder? Or maybe just move the folder to Trash, rather than delete? Could this all be done in one macro?

oops. See above

OK, here's a version that moves the folder to trash, AFTER the zip has completed.

Still would like a way to verify the zip integrity. Any ideas?


Here are the main changes:


##Macro Library   @ZIP T -- Zip Folder (& all Sub-Folders) & Move to TRASH


####DOWNLOAD:
<a class="attachment" href="/uploads/default/original/2X/4/467bd257d4dda460190f94134e6a4b8f745af4d4.kmmacros">@ZIP T -- Zip Folder (& all Sub-Folders) & Move to TRASH.kmmacros</a> (12 KB)

---

###ReleaseNotes

HOW TO USE:

1. Select one or more FOLDERs in the Finder
   • Each Folder wll be put in a separate Zip file
2. Trigger this Macro

Author:	 @rjames86 -- Original Macro/Script Posted below 
                @JMichaelTX -- this macro based on the one by @rjames86

WARNING!

The Folder selected in the Finder will be MOVED TO TRASH.

If the Zip File already exists, it will be OVERWRITTEN.


REFERENCE

Topic:		Creating a zip file and recursing through subfolders
Forum:		Keyboard Maestro Discourse, general

Post by:		rjames86
Post Date:	2016-12-22
Script Lang:	Bash
Post URL:	https://forum.keyboardmaestro.com/t/creating-a-zip-file-and-recursing-through-subfolders/5811/15?u=jmichaeltx

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

For a detailed, step-by-step description of the script, see:
https://forum.keyboardmaestro.com/t/creating-a-zip-file-and-recursing-through-subfolders/5811/17?u=jmichaeltx

---

<img src="/uploads/default/original/2X/4/436a24f734c8243e2ad494ff783cd83898e0cebc.png" width="592" height="1590">

Add the T flag to zip.

zip -rT '$BASE_NO_EXT" "$BASE"

-T test zipfile integrity

@rjames86, et al:

How can we detect if either:

  • The zip fails
  • The zip lacks integrity

and thus NOT do the Trash?

You can add the following command after the zip, but before popd

if [[ `unzip -tq "${BASE_NO_EXT}.zip"` =~ "No errors detected" ]]; 
then
    osascript -e 'tell application "Keyboard Maestro Engine" to setvariable "DeleteAfterZip" to "true"';
else
    osascript -e 'tell application "Keyboard Maestro Engine" to setvariable "DeleteAfterZip" to "false"';
fi

you should then have a variable available to you in KM called DeleteAfterZip. If its set to the string “true”, you can delete it

1 Like

@rjames86, thanks, this looks perfect.

Just to make sure I understand correctly, this test will fail if the zip does not have integrity, correct? I doing both:

zip -rT "$BASE_NO_EXT" "$BASE"

if [[ `unzip -tq "${BASE_NO_EXT}.zip"` =~ "No errors detected" ]]; 
then
    osascript -e 'tell application "Keyboard Maestro Engine" to setvariable "ZIP__NoErrors" to "true"';
else
    osascript -e 'tell application "Keyboard Maestro Engine" to setvariable "ZIP__NoErrors" to "false"';
fi

Note I changed you KM Var name to "ZIP__NoErrors". :smile:

Thanks. I really appreciate your expertise and patience with me.

Correct. If it fails (i.e. doesn't contain the string "No errors detected") it'll set the NoErrors to false.

Absolutely! Happy to help out

Thanks to the original macro/script by @rjames86, and his help in improving my macro (based on his), I think we now have a decent macro good for general consumption. I also hope that @Kurt_Kessler finds it useful.

I have posted this maro here:

If you have any comments on this macro, please post them in the above thread.

Thanks again, @rjames86.

2 Likes

I think it’s perfect! Thanks to everyone!!!

1 Like