Mac OS has integrated Archive Utility for packing files and folders into archives. It works fine, but what I don’t like about it, is that it packs not only files and folders I want to pack, but also hidden system stuff like __MACOSX folders and .DS_Store files. You can use some 3rd-party archiver instead (Keka is a very good one) that provides the possibility to exclude such files, but this is also possible to achieve with the integrated ZIP utility.

ZIP utility in Terminal

First, let me show you those system files in an actual archive.

So, let’s take some folder with files in it:

user@MacBook-Pro:~/Desktop$ ls -la somefolder/
total 80
drwxr-xr-x  6 user  staff   204B Jul 31 09:39 ./
drwx------+ 7 user  staff   238B Jul 31 09:48 ../
-rw-r--r--@ 1 user  staff   6.0K Jul 31 09:38 .DS_Store
-rw-r--r--  1 user  staff   9.7K Jul 30 20:29 and-one-more.markdown
-rw-r--r--  1 user  staff    13K Jul 21 17:02 another-text.markdown
-rw-r--r--  1 user  staff   1.1K Jul 12 11:21 some-text.markdown

Now we right-click on this folder in Finder and press standard Compress "somefolder". Archive somefolder.zip will be created. Let’s inspect it:

user@MacBook-Pro:~/Desktop$ zipinfo somefolder.zip 
Archive:  somefolder.zip
Zip file size: 11704 bytes, number of entries: 8
drwxr-xr-x  2.1 unx        0 bx stor 17-Jul-31 09:39 somefolder/
-rw-r--r--  2.1 unx     6148 bX defN 17-Jul-31 09:38 somefolder/.DS_Store
drwxrwxr-x  2.1 unx        0 bx stor 17-Jul-31 10:05 __MACOSX/
drwxrwxr-x  2.1 unx        0 bx stor 17-Jul-31 10:05 __MACOSX/somefolder/
-rw-r--r--  2.1 unx      120 bX defN 17-Jul-31 09:38 __MACOSX/somefolder/._.DS_Store
-rw-r--r--  2.1 unx     9972 bX defN 17-Jul-30 20:29 somefolder/and-one-more.markdown
-rw-r--r--  2.1 unx    12989 bX defN 17-Jul-21 17:02 somefolder/another-text.markdown
-rw-r--r--  2.1 unx     1130 bX defN 17-Jul-12 11:21 somefolder/some-text.markdown
8 files, 30359 bytes uncompressed, 10402 bytes compressed:  65.7%

As you can see, beside the actual files there is some system stuff inside the archive.

Okay, now here’s how to zip a folder in Terminal, excluding system files:

zip -r9 folderToArchive.zip folderToArchive -x "*.DS_Store"

Let’s inspect the archive now:

user@MacBook-Pro:~/Desktop$ zipinfo somefolder.zip 
Archive:  somefolder.zip
Zip file size: 10912 bytes, number of entries: 4
drwxr-xr-x  3.0 unx        0 bx stor 17-Jul-31 09:39 somefolder/
-rw-r--r--  3.0 unx     9972 tx defX 17-Jul-30 20:29 somefolder/and-one-more.markdown
-rw-r--r--  3.0 unx    12989 tx defX 17-Jul-21 17:02 somefolder/another-text.markdown
-rw-r--r--  3.0 unx     1130 tx defX 17-Jul-12 11:21 somefolder/some-text.markdown
4 files, 24091 bytes uncompressed, 10170 bytes compressed:  57.8%

As you can see, now our archive contains only files we wanted to pack there.

Dataforks

From the man zip you can discover this section:

-df
    --datafork
        [MacOS] Include only data-fork of files zipped into the archive.   Good  for
        exporting  files  to  foreign  operating-systems.   Resource-forks  will  be
        ignored at all.

Which implies that a more correct way to exclude system files from packing is to use this option instead of a hardcoded -x. However, trying to use -df option gives me this error:

zip error: Invalid command arguments (specify just one action)

or, in case of a long option --datafork:

zip error: Invalid command arguments (long option 'datafork' not supported)

Some *nux’es legacy, huh? Giving the documentation for something that is not there.

But anyway, the point here - I have to use -x "*.DS_Store".

Mac OS service

Now we will create a Mac OS service around this, so we could perform this command by right-clicking on folders in Finder.

Here’s the workflow:

Mac OS service workflow to ZIP a folder

And here’s the main script:

on run {input, parameters}
    
    set logScript to load script "/path/to/your/scripts/write2log.scpt"
    set fnameScript to load script "/path/to/your/scripts/getFname.scpt"
    
    #write2log("/path/to/your/logs/some.log", input) of logScript
    
    if (count of input) is not 1 then # it was easier to implement it with just one folder
        display dialog "You can archive only one folder." with icon stop with title "Error" buttons {"Okay, jeez"} default button {"Okay, jeez"}
        return
    else
        set filePath to POSIX path of input
        set fname to getFolderName(filePath) of fnameScript # gets "folder" from "/some/path/to/folder/"
        set archName to text 1 thru -2 of filePath & ".zip" # replace last "/" with ".zip"
        #write2log("/path/to/your/logs/some.log", archName) of logScript
        # prepare the command: set the context to the same directory and zip the folder
        set cmd to "cd " & quoted form of filePath & "..; zip -r9 " & fname & ".zip " & fname & " -x \"*.DS_Store\""
        #write2log("/path/to/your/logs/some.log", cmd) of logScript
        # execute command
        do shell script cmd
        # preparation for the result window
        set rez to do shell script "du -h " & quoted form of archName & " | awk '{print $1}'"
        display dialog "Archive was successfully created: " & archName & " (" & rez & ")" with title "Job's done" buttons {"OK"} default button "OK"
    end if
end run

Function getFolderName(filePath) is available from this script.

I want to give you some explanation about cmd. After all substitutes it looks like this:

cd /path/to/the/folderToArchive/..; zip -r9 folderToArchive.zip folderToArchive -x "*.DS_Store"

First half (the part before ;) of the command is an environment (working directory) setting - we go the target directory and then we go one level up (..). That way zip utility will “know” the path where to operate. Otherwise it will try to create archive somewhere in the system path and will fail with an access error. Of course, you can specify the full path where to create archive, but then inside the archive you will get the full hierarchy of folders. And also there is no need to provide the full path to folder that needs to be archived as we are already inside that directory.

Currently this service works only with a single folder, because in was easier that way and also because I only need that functionality for a single folder. I usually don’t pack several folders into one archive.

As you might have noticed, I added a dialog to the end of workflow, showing the result of packing. Here’s how it looks like in action:

Mac OS service to ZIP a folder