Super User is a question and answer site for computer enthusiasts and power users. It's 100% free, no registration required.

Sign up
Here's how it works:
  1. Anybody can ask a question
  2. Anybody can answer
  3. The best answers are voted up and rise to the top

I'm trying to figure out how to write a script to automate creating .cbz files from images in folders on OSX.

My directory tree will look like this:


And what I'd like to do is run a script that will find any folder containing actual files rather than subfolders and zip it, rename it from .zip to .cbz and move the .cbz file to the /cbz folder on the toplevel. Any folder that contains subfolders should not contain any files, so that might be helpful. Also I don't expect there to ever be subfolders deeper than what is shown in the example above.

Where I'm at: This snippet will look through a folder and create the .cbz-files for each subfolder.

for dir in `ls`; do zip $dir $dir/*; mv $dir*zip $dir.cbz; done

I've also found some snippets that will do something similar to every subfolder below the selected folder, but I've had varying results and would be very interested to learn the best way of performing an operation only on folders. Also I have NO clue as to how I should check whether a given folder contains subfolders or files. (I guess ideally I should include checks to see if there are both, but for my current scenario that shouldn't ever be the case.) Any ideas?

share|improve this question
Welcome to SU! We're not a script writing service. What have you got so far? Where are you getting stuck? – Ƭᴇcʜιᴇ007 Sep 28 '13 at 14:49
I'm sorry if that's how I came across. I have managed to put together this snippet that will do the job for a single level of folders: ´for dir in ls; do zip $dir $dir/*; mv $dir*zip $dir.cbz; done´ and I've managed to make it look for subfolders. My biggest hurdle right now is determining if a folder contains subfolders or files. – Martin Sep 28 '13 at 14:59
No worries. Please edit your question and include what you've got so far, and describe where/how you're getting stuck modifying it to do what you want. – Ƭᴇcʜιᴇ007 Sep 28 '13 at 15:00
up vote 4 down vote accepted

Another slightly different solution:

find toplevel/comics -type d -not -empty -print0 | while IFS= read -r -d '' dir; do
  if find "$dir" -maxdepth 1 -type f | read f; then
    zip toplevel/cbz/"$(basename "$dir").cbz" "$dir"/*
  • find … -print0 | while IFS= read -r -d '' file is a recommended pattern to iterate over find results. Doing a simple find … | while read file works fine, but only if the returned file names do not contain a newline. Parsing ls is never recommended.

  • find "$dir" -maxdepth 1 -type f | read f only evaluates true if files are found in the directory. It achieves the same purpose of @terdon's evaluation of the number of lines output by find.

  • Quotes are needed around all variables in order to prevent whitespace in the file names from breaking the arguments.

share|improve this answer
Nice, clever to use read that way. – terdon Sep 28 '13 at 17:03
This seems to work beautifully! I'll accept the answer as soon as the script is done and I can check one of the archives. :) – Martin Sep 28 '13 at 17:35

First of all, never parse the output of ls. Also, you can give zip an arbitrary name for the archive it will create so you don't need to move the files. Try this:

find toplevel/comics/ -mindepth 1 -type d | 
while read dir; do 
    num=$(find "$dir" -mindepth 1 -maxdepth 1 -type f | wc -l )  
    [ "$num" -gt "0" ] && zip toplevel/cbz/"$(basename $dir)".cbd "$dir"/*; 

The 1st find command searches toplevel/comics/ for directories (type -d) that are at least one level below (-mindepth 1) the path given (subdirectories). This ensures that find will not return toplevel/comics itself as a result. The results of that find are piped to the while loop which processes each of them saved in the variable $dir. Each $dir is searched for files (-type f) that are actually in that directory and not in sub directories (-mindepth 1 -maxdepth 1). The number of lines returned by the find (that's what wc does) is saved as $num and if it is greater than 0, the directory is zipped and the archive placed into cbz.

NOTES: The archives will contain the full path to the files (test by unpacking one), you might want to give the -j option to zip if you don't want this. See man zip.

share|improve this answer
Beat me to it. You might want to double-quote toplevel/cbz/$(basename "$dir").cbd in case the base name is expanded with whitespace. For really paranoid file handling, it should be find … -print0 | while IFS= read -r -d '' dir; do …. – slhck Sep 28 '13 at 16:31
@slhck good point about the $(basename "$dir"), answer edited. I didn't include the IFS etc to keep things simple since the OP is just starting with shell scripting and since you have it in your version anyway. – terdon Sep 28 '13 at 17:02
This is looking very promising, but something still snags. Making some adjustments to reflect the actual file structure I ended up with saving this as "" in what would be the "toplevel" dir: #! /bin/bash find Comics -mindepth 1 -type d | while read dir; do num=$(find "$dir" -mindepth 1 -maxdepth 1 -type f | wc -l ) [ "$num" -gt "0" ] && zip CBZs/"$(basename $dir)".cbz "$dir"; done This seems to find all folders and create zips based on their names, but they all seem to be empty. – Martin Sep 28 '13 at 17:28
@Martin sorry, my bad, try with the updated version. – terdon Sep 28 '13 at 17:39

Your Answer


By posting your answer, you agree to the privacy policy and terms of service.

Not the answer you're looking for? Browse other questions tagged or ask your own question.