Script to Backup Home Directory to a Compressed Disk Image

Discussion in 'Mac Apps and Mac App Store' started by DarylF2, Oct 19, 2004.

  1. DarylF2 macrumors member

    Joined:
    Apr 15, 2004
    Location:
    Lexington Park, MD, U.S.A.
    #1
    I put together this script for my own use, and thought others here might find it useful:

    Code:
    #!/bin/tcsh -f
    #
    # ABOVE: The "!/bin/tcsh -f" line tells the shell to run this with
    # the tcsh shell interpreter, and the "-f" parameter tells tcsh to
    # ignore the user's "~/.tcshrc" file (so as to start faster). This MUST be
    # the first line in the shell script file!
    #
    
    # 
    # Backup_Daryl_To_Storage_DMG (tcsh script)
    # version 1.0.1 (2004-10-15)
    #
    
    #
    # Embedded copy of /Users/daryl/Scripts/setdatetime.
    #
    
    #
    # These two lines use the "mktemp" command to create two temporary
    # files (usually in /tmp) named "setdatetime-date.XXXXXXXXXX" and
    # "setdatetime-time.XXXXXXXXXX" ("mktemp" replaces the "X" characters
    # with a unique set of semi-random characters). The back-quote characters
    # "`" tell the shell to execute the enclosed commands; the "|| exit 1"
    # clauses at the end tell the shell to exit with error code 1 if either
    # "mktemp" fails; the assignment statements at the beginning puts the
    # return value of the "mktemp" commands (which is the pathname of the
    # created temp file) into the specified local variables.
    #
    set TEMP_DATE = `mktemp -t setdatetime-date.XXXXXXXXXX` || exit 1
    set TEMP_TIME = `mktemp -t setdatetime-time.XXXXXXXXXX` || exit 1
    
    #
    # These two lines call the "date" command with the single-quoted format
    # strings (the characters following the '%' characters tell the "date"
    # command to insert date/time information here. Everything within the
    # single-quote characters following the "date" command comprises the
    # format string; the '+' character tells the "date" command that the
    # following characters comprise a user-defined format string. Type
    # "man date" from a Terminal window for more information...
    # The section at the end of each line after the '>' character tells the
    # shell to send the output of the preceeding command into the following
    # file, here represented by the local variables (the '$' character tells
    # the shell to insert the variable's value here).
    # The purpose is to essentially create two files containing shell scripts
    # which will create (global) shell environment variables holding the
    # current date and time. For example, if run now these temp files might
    # contain the following text: "setenv DATE 2004-10-19" and "10:16:59".
    #
    date '+setenv DATE %Y-%m-%d' > $TEMP_DATE
    date '+setenv TIME %H:%M:%S' > $TEMP_TIME
    
    #
    # These two sets of commands execute the scripts in the temp files
    # (the source command tells the shell to execute the script in THIS
    # shells environment, so any variable assignments are seen by THIS
    # shell too) and then remote (delete) the temp files.
    #
    source $TEMP_DATE ; rm $TEMP_DATE
    source $TEMP_TIME ; rm $TEMP_TIME
    
    #
    # Script code to create the DMG file from user home directory.
    #
    
    #
    # These four lines create another four local variables with the specified
    # values. Again the '$' characters tell the shell that it should substitute
    # the value of the following variable. The '{' and '}' are usually optional;
    # they can be used to bracket a variable name make it easier for the
    # shell to tell it from adjacent text (basically it makes it 100% clear
    # to the shell where the variable name begins and ends).
    #
    # Replace "daryl" in $SRC_USER with your user name; this is used in
    # $SRC_DIR and is used as the disk image volume name.
    # 
    # You can change $SRC_DIR to whatever directory/folder you wish to have
    # backed up.
    #
    # You can change DEST_DMG to whatever DMG file you want to have created.
    # Mine is on my "Storage" volume, in its "-Backup" directory, named (for
    # example, if run today) "2004-10-19 daryl.dmg".
    #
    # $DEST_OUT is the file to hold the status output from the hdiutil program.
    # This is generally CRC values, elapsed time, output file size, compression
    # ratio, etc.). It is named the same as $DEST_DMG but with ".out" appended
    # to it ("2004-10-19 daryl.dmg.out" using my example name above).
    #
    set SRC_USER = "daryl"
    set SRC_DIR  = "/Users/${SRC_USER}"
    
    set DEST_DMG = "/Volumes/Storage/-Backup/${DATE} ${SRC_USER}.dmg"
    set DEST_OUT = "${DEST_DMG}.out"
    
    #
    # The first "if-then" line tells the shell to only execute the following
    # lines (every line until "else") ONLY if the statement between "if" and
    # "then" is true. This particular statement would read, in English, as
    # "if (the file whose name is in the DEST_DMG variable does NOT exist) then".
    # The "-e" is a test for file existence, and the "== 0" works like a "NOT"
    # modifier, because we don't want the following command to overwrite an
    # existing file with the same name.
    #
    # The second "sudo hdiutil..." line is a command to execute the hdiutil
    # program (with super-user permissions) using the parameters that follow
    # it on the line (type "man hdiutil" for more details). Basically, in
    # English, this line would read something like "with super-user permissions,
    # use the "hdiutil" application to create a disk image from the source
    # folder in $SRC_DIR with an HFS+ filesystem using the UDZO format (UDIF
    # zlib-compressed image) with maximum compression (the "-imagekey
    # zlib-level=9" part) with a disk image volume name of $SRC_USER into the
    # output file named $DEST_DMG with all of the command's output status
    # messages redirected to the file $DEST_OUT (where the shell replaces all
    # variables preceeded by a '$' with their respective values, of course).
    # The sudo command is not needed if this script is run as root (i.e. in
    # the system crontab file as I mention below), but may be needed if you run
    # it manually from your regular user account, since some files in your
    # user Library directory may not be normally readable (this is true of some
    # disk utilities, for instance).
    #
    # The third "else" line marks the end of the "if" command lines and the
    # start of the "else" command lines, which are executed only if the
    # $DEST_DMG file DOES exist.
    #
    # The fourth "echo..." line outputs its status message indicating that
    # the $DEST_DMG file already exists and so this script is aborting without
    # creating a new disk image.
    #
    # The fifth (and final) "endif" line marks the end of the "else" command
    # lines and the end of the entire if-then-else block.
    #
    #
    if ((-e "${DEST_DMG}") == 0) then
        sudo hdiutil create -srcfolder $SRC_DIR -fs HFS+ -format UDZO -imagekey zlib-level=9 -volname $SRC_USER "$DEST_DMG" >&! "$DEST_OUT"
    else
        echo "Output file '${DEST_DMG}' already exists; aborting."
    endif
    
    Of course you'll need to change the SRC_USER and DEST_DMG variables to hold values that make sense for your system. A little creative editing can very easily change this script to backup any directory to a compressed disk image.

    I made it owned by "root.system" (chown root.system FILENAME) and set its permissions to "-rwxr-x---" (chmod u+rwx FILENAME; chmod go-rwx FILENAME; chmod g+rx FILENAME). I also added it to my system crontab (Cronnix makes this simple) so that it runs automatically every Saturday morning at 4:00AM ("0 4 * * 6 root FILENAME").

    Be careful to limit ownership and write access if you have it run from the system crontab, as it will run with root (!) permissions (necessary in my case since some of my application's preferences cannot be read with a regular user account, for security reasons). If you don't have these issues, you can run it from your regular user crontab and skip the chown (and possibly chmod) steps. My backup disk image fits on a DVD (barely...) so I back it up to DVD every Saturday as well.

    Let me know if my comments are unclear (or if I made any typos).
     
  2. Doctor Q Administrator

    Doctor Q

    Staff Member

    Joined:
    Sep 19, 2002
    Location:
    Los Angeles
    #2
    It's great that you included so many comments as documentation.

    Two questions:

    1. Instead of setting the DATE and TIME environment variables with a temporary shell script, i.e.,
    Code:
    set TEMP_DATE = `mktemp -t setdatetime-date.XXXXXXXXXX` || exit 1
    set TEMP_TIME = `mktemp -t setdatetime-time.XXXXXXXXXX` || exit 1
    date '+setenv DATE %Y-%m-%d' > $TEMP_DATE
    date '+setenv TIME %H:%M:%S' > $TEMP_TIME
    source $TEMP_DATE ; rm $TEMP_DATE
    source $TEMP_TIME ; rm $TEMP_TIME
    would it be simpler and quicker to set them like this?
    Code:
    setenv DATE `date '+%Y-%m-%d'`
    setenv TIME `date '+%H:%M:%S'`
    2. Do you need TIME at all?
     
  3. DarylF2 thread starter macrumors member

    Joined:
    Apr 15, 2004
    Location:
    Lexington Park, MD, U.S.A.
    #3
    Doctor Q, thanks for the optimization for the two setenvs! This part of the script started out in 1994 or so on the SGI, and so may have been limited by the Irix of that time. That, or I just didn't think of doing it the better way that you showed!

    Time isn't needed by this script, but is part of my original setdatetime script which I just embedded in this one (for security since it might be run as root). I left time in in case I decide to time-stamp the output file in the future. As written, though, it can be safely deleted from the script as you surmised.
     
  4. Doctor Q Administrator

    Doctor Q

    Staff Member

    Joined:
    Sep 19, 2002
    Location:
    Los Angeles
    #4
    I do the same thing, using boilerplate that accumulates over the years. One routine I often include includes a check for anomalies that apply only to SunOS 4 (predating Solaris). I don't really need to keep that code in there any more, but it came along for the ride.

    Too many scripts are written in a quick-and-dirty style, with no documentation, and then survive for years after the author is available to explain or maintain them. Sometimes the techniques and purpose are obvious, but your heavily commented code is the best idea. It's funny how many lines a good script has when the "meat" of the code is really a single line like

    hdiutil create -srcfolder $SRC_DIR -fs HFS+ -format UDZO -imagekey zlib-level=9 -volname $SRC_USER "$DEST_DMG" >&! "$DEST_OUT"

    I never make man pages for the shell script commands I write because I've found that they won't get distributed with the command or that they won't be kept up to date to match the script. Instead, I have the script explain itself when you give no arguments, use invalid syntax, or use a -help switch.

    As long as we're trading tips, here's one. Under Mac OS X, I often want to find the current user's home directory. Instead of assuming $HOME is already correct (it could have been changed before the script was called), I've been using a line like this to be safe:

    setenv HOMEDIR `cd ~;pwd`

    or, depend on which shell:

    HOMEDIR=`cd ~;pwd`
     
  5. bousozoku Moderator emeritus

    Joined:
    Jun 25, 2002
    Location:
    Gone but not forgotten.
    #5
    I'd certainly like to say thanks.

    I've never seen such a well-written backup script for free. The comments are precious. So few people realise that. I've worked on various applications where people comment little or nothing and usually, what they comment is obvious anyway.
     
  6. DarylF2 thread starter macrumors member

    Joined:
    Apr 15, 2004
    Location:
    Lexington Park, MD, U.S.A.
    #6
    You're very wecome!

    I have to admit that I originally posted this in another (incorrect) MacRumors forum and was asked for a more thoroughly commented version. Usually I don't comment as much as I did here, but I thought it might be helpful for shell scripting novices. I think seeing useful working examples is a great way to learn, and so many Mac users have no idea of the power of shell scripting, or of how to learn how to use and leverage that power.
     
  7. bousozoku Moderator emeritus

    Joined:
    Jun 25, 2002
    Location:
    Gone but not forgotten.
    #7
    After all the years AppleScript has been available, most Mac users don't know the first thing about writing a script. I wonder if Tiger will change people's views on scripting.

    I've been writing various scripts for UNIX since the middle 1980s--mostly menus and utilities to keep the operators from changing things and causing a meltdown. The good thing about scripting languages is that they all resemble C and so resemble each other. It's especially important with bc and awk.

    I hope that the users here will appreciate what you've done and implement it or at least, examine it carefully.
     

Share This Page