Create a gist now

Instantly share code, notes, and snippets.

What would you like to do?
Copy data from a Time Machine volume mounted on a Linux box.
# Copy data from a Time Machine volume mounted on a Linux box.
# Usage: <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.
# -
set -e
if [ -z "$source" -o -z "$target" ]; then
echo "Usage: $self <source> <target>"
exit -1
if [ ! -d "$target" ]; then
mkdir -p "$target"
if [ -z "$hfsd" ]; then
# Look for HFS Private directory data
sysname="$(echo -ne '.HFS+ Private Directory Data\r')"
while [ "$hfsd" != "/" -a ! -d "$hfsd/$sysname" ]; do
hfsd=`dirname "$hfsd"`;
if [ "$hfsd" = '/' ]; then
echo "HFS Private Directory Data not found in $source, is it an HFS filesystem?"
exit -2
echo "HFS Private Directory Data found in '$hfsd'"
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"
# Recurse
$self "$entry" "$dest" "$hfsd"
'regular empty file')
if [ -d "$hfsd/dir_$hlnum" ]; then
# Recurse
$self "$hfsd/dir_$hlnum" "$dest" "$hfsd"
echo "Skipping empty file $entry"

ticky commented Mar 22, 2014

Thank you so much for this. This has really saved me a whole bunch of time.

dragonee commented Apr 27, 2014

Thanks! You have saved my life with that. :)

Two suggestions after running this gist:

  1. In case of read failures on disk, set -e should be commented out to recover as many files as much you can.
  2. I've found it necessary to add LANG=C to stat, because it returned localized strings for file types and the case statement did not recognize output.

Hello! I've made sure the scrips is executable then ran:

sudo bash "source" "target"

with my appropriate directories of course, but I keep getting:

HFS Private Directory Data found in '/media/Time Machine/' line 72: command not found

Please help, did I do something wrong?

iimog commented Nov 1, 2014

Thanks for the nice script! I also needed the LANG=C option.
@itsmrgomez: make the script executable and call it as ./
The script recursively calls itself using the $0 variable. In case of your call it is just which is not in your $PATH (therefore command not found).
If you call it directly as ./ the recursive calls use the same and it should work.
Hope that helps!

stat: cannot stat `/mnt/usb_1/.HFS+ Private Directory Data\r/dir_1027471/2014/ImportersPhotoshopImport.xml': No such file or directory

There occur error with stat when path contains \
The right path is:

/mnt/usb_1/.HFS+ Private Directory Data?/dir_1027471/2014/Importers\PhotoshopImport.xml

Hi, when I run this script this is the output:

Syslnx ~ # ./ /media/thomas/MAXELL1 /tmp/osx
HFS Private Directory Data found in '/media/thomas/MAXELL1'

But nothing else happened. (the /tmp/osx dir still empty)
What should I do?

er4z0r commented Jun 16, 2015

Same problem as @grandmasterB. Script finds the private data and then no output is generated

Same here :(

magicoli commented Aug 9, 2015

Nothing happens when your locale is set to something else than english.
unset LANG
export LANG=C
at the beginning of the script and it will fix it.

I made a fork including this fix:

magicoli commented Aug 9, 2015

Scritp does not work either if the source is a Time-Machine hard link. My fork fixes that too.

I am getting a lot of the following errors:

stat: cannot stat ‘/xxxx’: Cannot allocate memory

Is there any way to fix this?

You saved my life, lol! Thanks!

Is it intended behavior that my Backups.backupdb folder is now empty? How can I use this with a mac again?

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment