Skip to content

Instantly share code, notes, and snippets.

@inoperable
Last active March 11, 2024 11:12
Show Gist options
  • Star 10 You must be signed in to star a gist
  • Fork 1 You must be signed in to fork a gist
  • Save inoperable/6041a6cc9426043bb830f4f4ed658449 to your computer and use it in GitHub Desktop.
Save inoperable/6041a6cc9426043bb830f4f4ed658449 to your computer and use it in GitHub Desktop.
export defaults for all domains in macOS into separate .plist files for editing and importing, files are written into $HOME/defaults
#!/usr/bin/env zsh
function exportDefaults {
local outdir="$HOME/defaults"
local outdirApple="$outdir/apple"
local outdirUser="$outdir/user"
local outdirGlobal="$outdir/global"
local filesdone=0
local filecount=0
local filestotal=0
local globals=0
function cleanOutdirs {
[[ ! -d $HOME/defaults ]] && mkdir "$HOME/defaults"
if [[ -d $outdirApple ]]; then
echo "removing all files in $outdirApple"
rm -rf "${outdirApple/*}"
else
mkdir "$outdirApple"
fi
if [[ -d $outdirUser ]]; then
echo "removing all files in $outdirUser"
rm -rf "${outdirUser/*}"
else
mkdir "$outdirUser"
fi
if [[ -d $outdirGlobal ]]; then
echo "removing all files in $outdirGlobal"
rm -rf "${outdirGlobal/*}"
else
mkdir "$outdirGlobal"
fi
}
function exportDomains {
filesdone=0
filecount=0
for domain in "${domains[@]}"; do
plist="${domain//,/}.plist"
if [[ $globals == 0 ]]; then
if [[ $domain =~ com.apple ]]; then
defaults export "$domain" "$outdirApple/$plist"
#echo "writing $plist to $outdirApple"
filecount=$((filecount+1))
else
defaults export "$domain" "$outdirUser/$plist"
#echo "writing $plist to $outdirUser"
filecount=$((filecount+1))
fi
else
sudo defaults export "$domain" "$outdirGlobal/$plist"
#echo "writing $plist to $outdirGlobal"
filecount=$((filecount+1))
fi
filesleft=$((filesleft-1))
filesdone=$((filesdone+1))
echo -ne "[ $filesdone/$filesleft ] \r"
done
}
cleanOutdirs
# -------------------------------------------------
local domains=($(defaults domains))
local filesleft=${#domains[@]}
echo "USER namespace has ${#domains[@]} domains, exportig..."
exportDomains
echo "written $filecount files in $outdir"
local filestotal=$((filestotal+filecount))
# -------------------------------------------------
globals=1
# -------------------------------------------------
local domains=($(sudo defaults domains))
local filesleft=${#domains[@]}
echo "GLOBAL namespace has ${#domains[@]} domains, exportig..."
exportDomains
echo "written $filecount files in $outdir"
local filestotal=$((filestotal+filecount))
# -------------------------------------------------
sudo chown -R "$(whoami)":staff "$HOME/defaults"
local timed="$((SECONDS / 3600))hrs $(((SECONDS / 60) % 60))min $((SECONDS % 60))sec"
echo "exported $filestotal files in $timed"
}
exportDefaults
@Hologos
Copy link

Hologos commented Mar 19, 2022

Hi, this is exactly what I was looking for! How do you import the settings? Thank you

Edit: I guess it's default import <domain> <path to plist>, right? :-)

@mamiu
Copy link

mamiu commented Jul 4, 2023

I've added a line to create a zip file of the defaults directory, so that it's easy to move it between macOS systems:

#!/usr/bin/env bash

function exportDefaults {
    local outdir="$HOME/defaults"
    local outdirApple="$outdir/apple"
    local outdirUser="$outdir/user"
    local outdirGlobal="$outdir/global"
    local filesdone=0
    local filecount=0
    local filestotal=0
    local globals=0


    function cleanOutdirs {
        [[ ! -d $outdir ]] && mkdir "$outdir"
        if [[ -d $outdirApple ]]; then
            echo "removing all files in $outdirApple"
            rm -rf "${outdirApple/*}"
        else
            mkdir "$outdirApple"
        fi

        if [[ -d $outdirUser ]]; then
            echo "removing all files in $outdirUser"
            rm -rf "${outdirUser/*}"
        else
            mkdir "$outdirUser"
        fi

        if [[ -d $outdirGlobal ]]; then
            echo "removing all files in $outdirGlobal"
            rm -rf "${outdirGlobal/*}"
        else
            mkdir "$outdirGlobal"
        fi
    }

    function exportDomains {
        filesdone=0
        filecount=0
        for domain in "${domains[@]}"; do
            plist="${domain}.plist"
            if [[ $globals == 0 ]]; then
                if [[ $domain =~ com.apple ]]; then
                    defaults export "$domain" "$outdirApple/$plist"
                    #echo "writing $plist to $outdirApple"
                    filecount=$((filecount+1))
                else
                    defaults export "$domain" "$outdirUser/$plist"
                    #echo "writing $plist to $outdirUser"
                    filecount=$((filecount+1))
                fi
            else
                sudo defaults export "$domain" "$outdirGlobal/$plist"
                #echo "writing $plist to $outdirGlobal"
                filecount=$((filecount+1))
            fi
            filesleft=$((filesleft-1))
            filesdone=$((filesdone+1))
            echo -ne "[ $filesdone/$filesleft ] \r"
        done
    }

    cleanOutdirs

    # -------------------------------------------------
    local domainsWithSeparator="$(defaults domains)"
    local domains=(${domainsWithSeparator//,/})
    local filesleft=${#domains[@]}
    echo "USER namespace has ${#domains[@]} domains, exportig..."
    exportDomains
    echo "written $filecount files in $outdir"
    local filestotal=$((filestotal+filecount))
    # -------------------------------------------------
    globals=1
    # -------------------------------------------------
    local domainsWithSeparator="$(sudo defaults domains)"
    local domains=(${domainsWithSeparator//,/})
    local filesleft=${#domains[@]}
    echo "GLOBAL namespace has ${#domains[@]} domains, exportig..."
    exportDomains
    echo "written $filecount files in $outdir"
    local filestotal=$((filestotal+filecount))
    # -------------------------------------------------

    sudo chown -R "$(whoami)":staff "$outdir"
    cd "$outdir"
    zip -q -r defaults.zip .

    local timed="$((SECONDS / 3600))hrs $(((SECONDS / 60) % 60))min $((SECONDS % 60))sec"

    echo "exported $filestotal files in $timed"
}

exportDefaults

And here's the corresponding defaults-import.sh:

#!/usr/bin/env bash

function importDefaults {
    local zipPath=$1
    local extractDir="$HOME/defaults"
    local appleDir="$extractDir/apple"
    local userDir="$extractDir/user"
    local globalDir="$extractDir/global"
    local filesTotal=0

    function importDomains {
        local dir=$1
        local filesImported=0
        local filesCount=$(ls -1 "$dir"/*.plist 2>/dev/null | wc -l)
        local filesRemaining=$filesCount
        local dirName=$(basename "$dir")
        echo "$dirName namespace has $filesCount files, importing..."

        for plist in "$dir"/*.plist; do
            domain=$(basename "$plist" .plist)
            if [[ $dir == "$globalDir" ]]; then
                sudo defaults import "$domain" "$plist"
            else
                defaults import "$domain" "$plist"
            fi
            filesRemaining=$((filesRemaining-1))
            filesImported=$((filesImported+1))
            echo -ne "[ $filesImported/$filesRemaining ] \r"
        done

        filesTotal=$((filesTotal+filesCount))
        echo -e "Imported $filesCount files from the $dirName namespace.\n"
    }

    if [[ -d "$extractDir" && -n "$(ls -A "$extractDir")" ]]; then
        echo "The extraction directory '$extractDir' is not empty."
        read -p "Do you want to delete the directory and continue? (y/N): " confirmDelete
        if [[ $confirmDelete == [Yy] ]]; then
            rm -rf "$extractDir"
            echo -e "Deleted the existing directory '$extractDir'.\n"
        else
            echo "Script canceled. Exiting..."
            exit 0
        fi
    fi

    # Extract the zip file
    unzip -q "$zipPath" -d "$extractDir"

    # Import plist files in the apple directory
    importDomains "$appleDir"

    # Import plist files in the user directory
    importDomains "$userDir"

    # Import plist files in the global directory
    importDomains "$globalDir"

    echo "Defaults import completed. Imported a total of $filesTotal files."
}

if [ -z "$1" ]; then
    echo "Please provide the path to the zip file."
else
    importDefaults "$1"
fi

The import script requires the path to the zip file as first parameter.
E.g. defaults-import.sh ~/Downloads/defaults.zip

@inoperable
Copy link
Author

Excellent! Thanks for updating this

@mamiu
Copy link

mamiu commented Jul 5, 2023

@inoperable My pleasure! Thank you for providing this script in the first place.

One thing I didnt seen before /usr/env/bash could be trouble since Apple dropped bash in favour of zsh (unless that changed back, I dropped macOS since Apple Silicon showed up - main reason being economics)

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