Skip to content

Instantly share code, notes, and snippets.

Embed
What would you like to do?
Copy data from a Time Machine volume mounted on a Linux box.
#!/bin/bash
#
# Copy data from a Time Machine volume mounted on a Linux box.
#
# Usage: copy-from-time-machine.sh <source> <target>
#
# source: the source directory inside a time machine backup
# target: the target directory in which to copy the reconstructed
# directory trees. Created if it does not exists.
#
# Details:
#
# Time machine implements directory hard links by creating an
# empty file in place of the directory and storing in its
# "number of hard links" metadata attribute a pointer to a
# real directory in "/.HFS Private directory data^M" named
# "dir_$number".
#
# This script reconstructs a plain directory tree from this
# really ugly apple hack. Tested on a 650GB backup from OSX
# 10.6 mounted on a Linux 3.2.0-38 Ubuntu box. YMMV.
#
# MIT License.
#
# - vjt@openssl.it
#
self="$0"
source="$1"
target="$2"
hfsd="$3"
unset LANG
set -e
if [ -z "$source" -o -z "$target" ]; then
echo "Usage: $self <source> <target>"
exit -1
fi
if [ ! -d "$target" ]; then
mkdir -p "$target"
fi
if [ -z "$hfsd" ]; then
# Look for HFS Private directory data
sysname="$(echo -ne '.HFS+ Private Directory Data\r')"
hfsd=$source
while [ "$hfsd" != "/" -a ! -d "$hfsd/$sysname" ]; do
hfsd=`dirname "$hfsd"`;
done
if [ "$hfsd" = '/' ]; then
echo "HFS Private Directory Data not found in $source, is it an HFS filesystem?"
exit -2
else
echo "HFS Private Directory Data found in '$hfsd'"
hfsd="$hfsd/$sysname"
fi
fi
read hlnum type <<<$(stat -c '%h %F' "$source")
if [ "$type" = 'regular empty file' -a -d "$hfsd/dir_$hlnum" ]; then
source="$hfsd/dir_$hlnum"
echo "using source from HFS Private Directory Data"
fi
find "$source" -mindepth 1 -maxdepth 1 -and -not -name . -and -not -name .. | while read entry; do
dest="$target/`basename "$entry"`"
read hlnum type <<<$(stat -c '%h %F' "$entry")
case $type in
'regular file'|'symbolic link')
cp -van "$entry" "$dest"
;;
'directory')
# Recurse
$self "$entry" "$dest" "$hfsd"
;;
'regular empty file')
if [ -d "$hfsd/dir_$hlnum" ]; then
# Recurse
$self "$hfsd/dir_$hlnum" "$dest" "$hfsd"
else
echo "Skipping empty file $entry"
fi
;;
esac
done
@magicoli

This comment has been minimized.

Copy link
Owner Author

@magicoli magicoli commented Aug 9, 2015

Fixed language bug by unsetting language (script stops without doing anything when foreign language locale is set)

@magicoli

This comment has been minimized.

Copy link
Owner Author

@magicoli magicoli commented Aug 9, 2015

New fix to allow selecting a Time Machine hard linked folder as source.

@beelze-b

This comment has been minimized.

Copy link

@beelze-b beelze-b commented Aug 18, 2015

I just want to say that this really saved my data. Thank you very much.

@renato2099

This comment has been minimized.

Copy link

@renato2099 renato2099 commented Aug 26, 2015

I tried this on Ubuntu 14.10 with the following command:

sudo sh copy-from-time-machine.sh /media/marenato/Seagate\ Expansion\ Drive/ ~/Desktop/ /media/marenato/Seagate\ Expansion\ Drive/.HFS+\ Private\ Directory\ Data^M/

And this is what I got:
copy-from-time-machine.sh: 64: copy-from-time-machine.sh: Syntax error: redirection unexpected

any ideas on what might be happening? Thanks again for the script!

@Linuxhombre

This comment has been minimized.

Copy link

@Linuxhombre Linuxhombre commented Oct 30, 2015

Thanks again for this well-modified script! The sudo ./script /media/source/subdirectory /my/local/target subdirectory targeting works briliantly and allows one to restore a particular directory, directories or set of files without having to slam your local storage with all of the 'extra' Mac TimeMachine data that you won't need post-migration of data. Great work, @magicoli

@Jann5s

This comment has been minimized.

Copy link

@Jann5s Jann5s commented Nov 23, 2015

I searched but I don't have the "HFS+ Private Directory Data" anywhere on my time-machine disk. Namely, sudo find /Volume/timemach -iname 'HFS+*' returns nothing. I'm not on Linux, but I'm on OSX (10.11.1), maybe that changes the game.

@pavelgarchenko

This comment has been minimized.

Copy link

@pavelgarchenko pavelgarchenko commented Apr 11, 2016

It works! Thanks a lot!
This is how I made it work

$ chmod +x copy-from-time-machine.sh
$ sudo ./copy-from-time-machine.sh <target path> <source path>

@dustractor

This comment has been minimized.

Copy link

@dustractor dustractor commented Apr 11, 2016

It works!

just do what the commenter above said.

For example, to take out only the latest of the home folder, assuming things are mounted under media:

$ sudo ./copy-from-time-machine.sh /media/<current-username>/<time-machine-drive-name>/Backups.backupdb/<old-mac-system-name>/Latest/<old-mac-internal-hd-name>/Users/<old-mac-user-name> <target...>
@rushedmanpete

This comment has been minimized.

Copy link

@rushedmanpete rushedmanpete commented May 28, 2016

Managed to get a good deal of files off the device, thank you for the script!

@felipecruz

This comment has been minimized.

Copy link

@felipecruz felipecruz commented Jul 6, 2017

Thank you very much!!!!

@amk1969

This comment has been minimized.

Copy link

@amk1969 amk1969 commented Sep 16, 2017

Does not need to limit itself to Time machine, can be used to copy out from any HFS+ drive.
Some changes are needed to process also directories with leading or trailing space:

*** copy-from-time-machine.sh.orig	2017-09-16 23:21:54.456987359 +0200
--- copy-from-time-machine.sh	2017-09-16 23:33:01.449132594 +0200
***************
*** 60,67 ****
--- 60,69 ----
    fi
  fi
  
+ IFS=''
  find "$source" -mindepth 1 -maxdepth 1 -and -not -name . -and -not -name .. | while read entry; do
    dest="$target/`basename "$entry"`"
+   IFS=' '
    read hlnum type <<<$(stat -c '%h %F' "$entry")
  
    case $type in
***************
*** 84,87 ****
--- 86,90 ----
        ;;
    esac
  
+   IFS=''
  done
`
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
You can’t perform that action at this time.