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"
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
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
@ticky

This comment has been minimized.

Copy link

@ticky ticky commented Mar 22, 2014

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

@dragonee

This comment has been minimized.

Copy link

@dragonee 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.
@itsmrgomez

This comment has been minimized.

Copy link

@itsmrgomez itsmrgomez commented Oct 15, 2014

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

sudo bash copy-from-time-machine.sh "source" "target"

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

HFS Private Directory Data found in '/media/Time Machine/'
copy-from-time-machine.sh: line 72: copy-from-time-machine.sh: command not found

Please help, did I do something wrong?

@iimog

This comment has been minimized.

Copy link

@iimog 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 ./copy-from-time-machine.sh
The script recursively calls itself using the $0 variable. In case of your call it is just
copy-from-time-machine.sh which is not in your $PATH (therefore command not found).
If you call it directly as ./copy-from-time-machine.sh the recursive calls use the same and it should work.
Hope that helps!

@yongkangchen

This comment has been minimized.

Copy link

@yongkangchen yongkangchen commented Nov 14, 2014

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
@grandmasterB

This comment has been minimized.

Copy link

@grandmasterB grandmasterB commented Feb 27, 2015

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

Syslnx ~ # ./timemachine.sh /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

This comment has been minimized.

Copy link

@er4z0r er4z0r commented Jun 16, 2015

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

@FlimFlam

This comment has been minimized.

Copy link

@FlimFlam FlimFlam commented Jun 20, 2015

Same here :(

@magicoli

This comment has been minimized.

Copy link

@magicoli magicoli commented Aug 9, 2015

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

I made a fork including this fix:
https://gist.github.com/magicoli/283785bdf21ebafd2202

@magicoli

This comment has been minimized.

Copy link

@magicoli magicoli commented Aug 9, 2015

Scritp does not work either if the source is a Time-Machine hard link. My fork https://gist.github.com/magicoli/283785bdf21ebafd2202 fixes that too.

@lifeofguenter

This comment has been minimized.

Copy link

@lifeofguenter lifeofguenter commented Nov 27, 2015

I am getting a lot of the following errors:

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

Is there any way to fix this?

@BrunoCodeman

This comment has been minimized.

Copy link

@BrunoCodeman BrunoCodeman commented Jun 4, 2016

You saved my life, lol! Thanks!

@shushugah

This comment has been minimized.

Copy link

@shushugah shushugah commented Oct 24, 2016

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

@ekortright

This comment has been minimized.

Copy link

@ekortright ekortright commented May 6, 2018

Thank you very much for making this script available!

@shushugah's comment worried me, but if you read through the script you can see that no files are deleted or modified in any way in the source folder. I can see all the files in my Backups.backupdb folder after extracting everything I needed.

@kadirmalak

This comment has been minimized.

Copy link

@kadirmalak kadirmalak commented Dec 18, 2018

Thank you :)

@alexcthomas

This comment has been minimized.

Copy link

@alexcthomas alexcthomas commented Apr 25, 2019

This was so useful I created a python clone here:
https://gist.github.com/alexcthomas/6df11f8a7b10a40a1dbc6adf7440995f

@sepaepa

This comment has been minimized.

Copy link

@sepaepa sepaepa commented May 9, 2019

Hey Guys ,

I'm a complete noob when it comes to programming. I have written small C programms for school but other than that, I'm useless hahah.
I wanted to ask how exactly to I proceed with this? I copied the code to my atom editor (but the version with the "unset LANG" at the beginning of the code) saved it as tmbackup.sh and compiled with chmod +x.
Then i tried to run the programm by typing ./tmbackup.sh /dev/sdb2/ desktop. This means my source is the /dev/sdb2/ and i wanted to test out if it works, so i used the desktop as my target (was that ok or completely wrong??) .
After executing that last line, i get the following error:*** HFS Private Directory Data not found in /dev/sdb2/, is it an HFS filesystem? ***

I would be very thankful for any advice, and excuse my lacking skills ^^

@dr0i

This comment has been minimized.

Copy link

@dr0i dr0i commented Nov 9, 2019

Copied the script into a repo and updated it with some improvements from others forks. This also should help to better manage issues (thus having a more "clear" snapshot of the status quo in the README).
I do this because @vjt's script is a really good thing, but I had some issues (which I solved myself recognizing belatedly that others already solved them, too.)
@sepaepa try to use the mount point of mounted devices, not devices themselves, i.e. not /dev/sdb2.

@dragonee

This comment has been minimized.

Copy link

@dragonee dragonee commented Nov 9, 2019

@dr0i I've replaced the link in my article to your repo.

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.