Skip to content

Instantly share code, notes, and snippets.

@magicoli
Forked from vjt/copy-from-time-machine.sh
Last active April 18, 2023 19:31
Show Gist options
  • Save magicoli/283785bdf21ebafd2202 to your computer and use it in GitHub Desktop.
Save magicoli/283785bdf21ebafd2202 to your computer and use it in GitHub Desktop.
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
@dustractor
Copy link

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
Copy link

rushedmanpete commented May 28, 2016

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

@felipecruz
Copy link

Thank you very much!!!!

@amk1969
Copy link

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
`

@stanfrbd
Copy link

Wow, thank you! This just saved my life ahah. As my Ubuntu is in French, the original script didn't work properly

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