1. Overview

When collecting and compressing files on Linux, we have many options: tar, gzipxz and others.

But when sharing files with Windows or macOS users, the ZIP format remains the easiest. We want to keep it easy for them.

In this tutorial, we’ll look into how to create archives that don’t include confusing extra directories. We’ll practice good path hygiene. This may be simple when done by hand, but can be trickier in a script that could be called from any location.

To work around that, we’ll use the bash shell’s built-in pushd and popd features.

2. Zip All the Things with -r

When our friends or clients extract the ZIP file we sent them, we usually want to preserve the basic directory structure. We can do that with the zip command’s -r option.

Say we want to send everything in our twinejs/src/vue directory, and everything beneath it:

~$ ls -R /home/allan/Source/twinejs/src/vue/
/home/a/Source/twinejs/src/vue/:
codemirror-theme.less  directives  index.less  mixins  transitions.less

/home/allan/Source/twinejs/src/vue/directives:
mouse-scrolling.js  mouse-scrolling.less

/home/allan/Source/twinejs/src/vue/mixins:
dom-events.spec.js  mount-to.js

That’s a bunch of files, in three different directories. If we use -r to recurse into each directory (zipping each directory in the tree), we get this output:

~$ zip -r ~/twine-vue.zip /home/allan/Source/twinejs/src/vue
updating: home/allan/Source/twinejs/src/vue/ (stored 0%)
  adding: home/allan/Source/twinejs/src/vue/codemirror-theme.less (deflated 73%)
  adding: home/allan/Source/twinejs/src/vue/directives/ (stored 0%)
  adding: home/allan/Source/twinejs/src/vue/directives/mouse-scrolling.less (deflated 37%)
  adding: home/allan/Source/twinejs/src/vue/directives/mouse-scrolling.js (deflated 68%)
  adding: home/allan/Source/twinejs/src/vue/index.less (stored 0%)
  adding: home/allan/Source/twinejs/src/vue/mixins/ (stored 0%)
  adding: home/allan/Source/twinejs/src/vue/mixins/mount-to.js (deflated 31%)
  adding: home/allan/Source/twinejs/src/vue/mixins/dom-events.spec.js (deflated 69%)
  adding: home/allan/Source/twinejs/src/vue/transitions.less (deflated 63%)

But we don’t want to send them the whole path. Then our friend would have to go down several levels to get to the files.

If we archived our files by hand, we could cd to and from the directory we want to capture (vue). But that’s clunky in a script. If only there were some option or command to avoid that!

3. Junk All the Directories with -j

The -j option “junks” the directories of every file added to the ZIP file. It’s like the basename tool: It just keeps the name of the file, dropping all the directories in the path to it.

Let’s try it, and see if that’s any closer to what we want:

~$ zip -rj ~/twine-vue.zip /home/a/Source/twinejs/src/vue
  adding: codemirror-theme.less (deflated 73%)
  adding: mouse-scrolling.less (deflated 37%)
  adding: mouse-scrolling.js (deflated 68%)
  adding: index.less (stored 0%)
  adding: mount-to.js (deflated 31%)
  adding: dom-events.spec.js (deflated 69%)
  adding: transitions.less (deflated 63%)

Not quite right, either. Unzipping this would spill files all over our friend’s Downloads folder. I guess we’re back to cd — unless there’s another way?

4. pushd and popd as a Better Kind of cd

We can make our scripts less brittle by using a handy feature of our shell: the pushd command, which sends us to the directory we want to be in and also keeps track of where we started.

4.1. pushd Our Way In

Let’s take a look at how we might use pushd when running a script from /usr/local/bin:

/usr/local/bin$ pushd /home/a/Source/twinejs/src/
~/Source/twinejs/src /usr/local

pushd returns two pathnames: first, where we asked it to go, and next, where we were. Using it, we move to the directory we specified.

Now we’re in the directory right above vue and can zip it up with the -r option:

~/Source/twinejs/src$ zip -r ~/twine-vue.zip vue/
  adding: vue/ (stored 0%)
  adding: vue/codemirror-theme.less (deflated 73%)
  adding: vue/directives/ (stored 0%)
  adding: vue/directives/mouse-scrolling.less (deflated 37%)
  adding: vue/directives/mouse-scrolling.js (deflated 68%)
  adding: vue/index.less (stored 0%)
  adding: vue/mixins/ (stored 0%)
  adding: vue/mixins/mount-to.js (deflated 31%)
  adding: vue/mixins/dom-events.spec.js (deflated 69%)
  adding: vue/transitions.less (deflated 63%)

When our ZIP file recipient extracts our gift to them, they get everything neatly in a single directory.

4.2. popd out Again

And when our script wants to pick up from where it was, we run popd and are returned right where we began. This can happen now or later — no matter where we are, we’ll be returned to the directory where we first ran pushd. (We can even do this multiple nested times if we want!)

~/Source/twinejs/src$ popd
/usr/local/bin
/usr/local/bin$

And we’re back again to /usr/local/bin, leaving a neatly organized ZIP file where we put it in our home directory. Our script can proceed.

5. Conclusion

In this article, we take advantage of a useful shell feature to avoid unwanted additional directories in our ZIP archives.

It’s a cleaner way to automate this step. And an organized ZIP file makes life easier for the people who receive it.

Comments are closed on this article!