An open API service indexing awesome lists of open source software.

https://github.com/edtadros/icloud-nosync

A Zsh function to exclude files/folders from iCloud Drive sync on macOS.
https://github.com/edtadros/icloud-nosync

developer-tools file-exclusion icloud icloud-drive icloud-ignore icloud-nosync icloud-sync macos nodejs npm shell-script sync-exclude sync-ignore xattr zsh zshrc zshrc-enhancements

Last synced: 2 months ago
JSON representation

A Zsh function to exclude files/folders from iCloud Drive sync on macOS.

Awesome Lists containing this project

README

          

# iCloud NoSync: Exclude Files/Folders from iCloud Drive Sync on macOS

iCloudNoSync

This repository provides a standalone Zsh script called `nosync` that allows you to easily mark files or folders (e.g., `node_modules`) as excluded from iCloud Drive syncing. It uses an undocumented but reliable `xattr` attribute (`com.apple.fileprovider.ignore#P`) to keep items local to your Mac, preventing unnecessary uploads and saving iCloud storage.

This is particularly useful for developers with projects in iCloud-synced folders like Documents or Desktop, where large temporary folders like `node_modules` can bloat your sync.

**Note**: This works on macOS Ventura, Sonoma, and Sequoia (as of July 2025). It's based on community-tested methods and is non-destructive—items can be unmarked if needed. The xattr method may not work reliably on macOS Sequoia (15.x) and later due to potential changes in iCloud behavior. Consider alternatives like renaming with `.nosync` for newer macOS versions.

## Features
- Marks existing files or folders as non-syncing by adding the com.apple.fileprovider.ignore#P xattr attribute.
- Unmarks items (removes the attribute) to resume syncing with --unset.
- Prompts to create missing items (as a file or directory) before marking in non-recursive mode.
- Supports multiple items at once (e.g., `nosync node_modules dist`).
- Recursive search (--recursive or -r) to mark/unmark all matching items, with --directory (-d) for directories (prunes subdirs after marking unless --no-prune) or --file (-f) for files.
- Verbose mode (--verbose or -v) to list processed paths.
- Shows a spinner indicator in recursive mode to show activity.
- Built-in help: Run `nosync --help` for usage details.
- Safe and reversible: Use --unset to remove the attribute.
- Uses full paths for commands to avoid PATH issues.

## Installation
1. **Clone the Repository**:
```
git clone https://github.com/yourusername/icloud-nosync.git
cd icloud-nosync
```

2. **Make the Script Executable**:
- Ensure the script is executable:
```
chmod +x nosync
```

3. **Add to Your PATH**:
- Add the directory containing `nosync` to your PATH in `~/.zshrc` (or `~/.bash_profile` for Bash):
```
export PATH="/path/to/icloud-nosync:$PATH"
```
- Reload your shell:
```
source ~/.zshrc
```
- Alternatively, move the script to a directory already in your PATH (e.g., `/usr/local/bin`):
```
mv nosync /usr/local/bin/
```

If you're using Bash (not default on macOS), the script is mostly compatible, but test thoroughly.

## Usage
Run the script from the directory containing the items.

### Basic Examples
- Mark a folder:
```
nosync node_modules
```
- Mark a file:
```
nosync temp.cache
```
- Mark multiple items:
```
nosync node_modules dist build.log
```
- Unmark a folder to resume syncing:
```
nosync --unset node_modules
```
- Recursively mark all node_modules directories:
```
nosync -r -d node_modules
```
- Recursively unmark all temp.cache files:
```
nosync -u -r -f temp.cache
```
- Recursively mark with verbose output:
```
nosync -v -r -d node_modules
```
- Recursively mark without pruning subdirs:
```
nosync -r -d --no-prune node_modules
```

### If an Item Doesn't Exist (Non-Recursive Mode Only)
You'll be prompted (only when marking, not unsetting):
```
temp.cache does not exist. Create it as a (f)ile, (d)irectory, or (s)kip?
```
- `f` or `F`: Create as an empty file and mark it.
- `d` or `D`: Create as a directory and mark it.
- `s` or anything else: Skip without creating.

### Help
```
nosync --help
```
Or `nosync -h` for details on syntax and behavior.

### Verify
In Finder, marked items should show a cloud icon with a slash (local-only). Check the attribute:
```
xattr -l node_modules
```
Output: `com.apple.fileprovider.ignore#P: 1`

### Undo
To resume syncing, use the --unset option:
```
nosync --unset node_modules
```
Or manually:
```
xattr -d 'com.apple.fileprovider.ignore#P' node_modules
```

## The Code (`nosync`)
For reference, here's the full script:

```bash
#!/usr/bin/env zsh

# Mark file(s) or folder(s) to be excluded from iCloud sync, with prompt to create missing ones
nosync() {
local recursive=false
local is_directory=false
local is_file=false
local unset=false
local verbose=false
local no_prune=false

# Check for help flag first
if [[ "$1" == "--help" || "$1" == "-h" ]]; then
echo "Usage: nosync [options] [item1] [item2] ..."
echo "Marks files or folders to be excluded from iCloud Drive sync."
echo ""
echo "Options:"
echo " --help, -h Display this help message and exit."
echo " --recursive, -r Recursively search from the current directory for items"
echo " matching the given names (files or folders) and mark/unmark them."
echo " No creation prompt in this mode. Requires -d or -f."
echo " --directory, -d With -r, target directories only (prunes subdirs after marking unless --no-prune)."
echo " --file, -f With -r, target files only."
echo " --unset, -u Remove the exclusion attribute to allow syncing again."
echo " --verbose, -v Enable detailed output, including listing processed paths."
echo " --no-prune With -r -d, disable prune to process subdirs of matched directories."
echo ""
echo "Behavior:"
echo " - Without --unset: Adds com.apple.fileprovider.ignore#P xattr to exclude from sync."
echo " - With --unset: Removes the attribute to resume sync."
echo " - If an item does not exist (non-recursive, without --unset), prompts to create it as a file (f), directory (d), or skip (s)."
echo " - Use 'f' or 'F' to create a file, 'd' or 'D' to create a directory, or 's' (or any other input) to skip."
echo " - In recursive mode, shows a spinner indicator; with --verbose, also lists paths."
echo " - 5-second timeout on xattr calls to prevent hangs (requires coreutils via Homebrew)."
echo ""
echo "Examples:"
echo " nosync node_modules # Mark node_modules folder"
echo " nosync temp.cache # Mark a file"
echo " nosync node_modules dist # Mark multiple items"
echo " nosync -r -d node_modules # Recursively mark all node_modules directories"
echo " nosync -r -f temp.cache # Recursively mark all temp.cache files"
echo " nosync --unset node_modules # Unmark node_modules folder"
echo " nosync -u -r -d node_modules # Recursively unmark all node_modules directories"
echo " nosync -v -r -d node_modules # Recursively mark with verbose output"
echo " nosync -r -d --no-prune node_modules # Recursively mark without pruning subdirs"
return 0
fi

# Parse options
while [[ $# -gt 0 ]]; do
case $1 in
--recursive|-r)
recursive=true
shift
;;
--directory|-d)
is_directory=true
shift
;;
--file|-f)
is_file=true
shift
;;
--unset|-u)
unset=true
shift
;;
--verbose|-v)
verbose=true
shift
;;
--no-prune)
no_prune=true
shift
;;
*)
break
;;
esac
done

if [[ $# -eq 0 ]]; then
echo "Error: No items specified. Use --help for usage."
return 1
fi

if $recursive; then
if ! $is_directory && ! $is_file; then
echo "Error: -r requires -d or -f."
return 1
fi
if $is_directory && $is_file; then
echo "Error: Cannot use -d and -f together."
return 1
fi
if $no_prune && ! $is_directory; then
echo "Error: --no-prune requires -d."
return 1
fi
else
if $is_directory || $is_file; then
echo "Error: -d and -f require -r."
return 1
fi
if $no_prune; then
echo "Error: --no-prune requires -r -d."
return 1
fi
fi

local xattr_cmd
local action_msg
local action_present="Marked"
local action_past="Marked"
if $unset; then
xattr_cmd="/usr/bin/xattr -d 'com.apple.fileprovider.ignore#P' \"%s\" || echo \"Failed to unmark %s (error: \$?)\""
action_msg="Unmarked %s for iCloud sync."
action_present="Unmarking"
action_past="Unmarked"
else
xattr_cmd="/usr/bin/xattr -w 'com.apple.fileprovider.ignore#P' 1 \"%s\" || echo \"Failed to mark %s (error: \$?)\""
action_msg="Marked %s as non-syncing for iCloud."
fi

if $recursive; then
local -a find_args
find_args=( . )
if $is_directory; then
find_args+=( -type d -name )
elif $is_file; then
find_args+=( -type f -name )
fi
local prune_arg=""
if $is_directory && ! $no_prune; then
prune_arg="-prune"
fi
# Start spinner in background
spinner &
local spinner_pid=$!
trap "kill $spinner_pid 2>/dev/null" EXIT

for item in "$@"; do
local found=false
while IFS= read -r -d '' path; do
if $verbose; then
echo "Processing $path"
fi
( gtimeout 5 eval $(printf "$xattr_cmd" "$path" "$path") ) || echo "Timed out processing $path"
printf "$action_msg\n" "$path"
found=true
done < <(/usr/bin/find "${find_args[@]}" "$item" $prune_arg -print0)
if ! $found; then
echo "No matches found for $item recursively."
fi
done
kill $spinner_pid 2>/dev/null
trap - EXIT
echo "" # New line after spinner
else
# Non-recursive mode: process each item directly
for item in "$@"; do
if [ -e "$item" ]; then
( gtimeout 5 eval $(printf "$xattr_cmd" "$item" "$item") ) || echo "Timed out processing $item"
printf "$action_msg\n" "$item"
else
if $unset; then
echo "Skipped $item: Does not exist (nothing to unmark)."
else
read -p "$item does not exist. Create it as a (f)ile, (d)irectory, or (s)kip? " answer
case "$answer" in
[Ff])
if /bin/touch "$item"; then
( gtimeout 5 eval $(printf "$xattr_cmd" "$item" "$item") ) || echo "Timed out processing $item"
printf "Created and $action_past %s as non-syncing for iCloud.\n" "$item"
else
echo "Error: Could not create file $item."
return 1
fi
;;
[Dd])
if /bin/mkdir -p "$item"; then
( gtimeout 5 eval $(printf "$xattr_cmd" "$item" "$item") ) || echo "Timed out processing $item"
printf "Created and $action_past %s as non-syncing for iCloud.\n" "$item"
else
echo "Error: Could not create directory $item."
return 1
fi
;;
[Ss]*|*)
echo "Skipped $item (not created)."
;;
esac
fi
fi
done
fi
}

# Spinner function for recursive mode
spinner() {
local spin='-\|/'
local i=0
while true; do
printf "\rProcessing... %c" "${spin:i++%${#spin}:1}"
sleep 0.1
done
}

nosync "$@"
```

### Installation Notes for gtimeout
After `brew install coreutils`, `gtimeout` will be in `/opt/homebrew/bin/gtimeout` (on Apple Silicon) or `/usr/local/bin/gtimeout` (Intel). If your PATH includes Homebrew bins (common in .zshrc), it should work. Otherwise, add `export PATH="/opt/homebrew/bin:$PATH"` to .zshrc and source it.

Now, `nosync node_modules` should add the attribute and output "Marked node_modules as non-syncing for iCloud." without hanging, thanks to the timeout. If it times out, the attribute won't be added, but the script will continue.

If this still doesn't work, the xattr method is not viable on your system—switch to .nosync or another approach.