Action

rsync Command Generator

Posted by Mike Burke, Last update 3 days ago - Unlisted

This action helps you create properly formatted rsync commands for backing up files and folders on macOS. It guides you through selecting source and destination paths, handles spaces in file names correctly, and explains exactly what the command will do.

Features

  • Prompts for source and destination paths
  • Properly formats paths with spaces
  • Option to include –delete flag for exact synchronization
  • Provides a clear explanation of what the command will do
  • Creates a ready-to-use rsync command

Usage

  1. Run the action
  2. Enter the source path (where your files are)
  3. Enter the destination path (where you want the backup)
  4. Choose whether to use the –delete option
  5. Copy the generated command to Terminal

Tips

  • Use Finder’s “Show Path Bar” (View → Show Path Bar) to easily copy file paths
  • The –delete option makes the destination an exact copy of the source
  • Without –delete, files removed from the source will remain in the backup

Default Options

The command uses these rsync flags:
- -a: Archive mode (preserves file attributes)
- -z: Compress during transfer
- -v: Verbose output
- -h: Human-readable sizes

Steps

  • script

    function getLastFolderName(path) {
        // Remove trailing slash if it exists
        path = path.replace(/\/$/, '');
        // Split on / and get last non-empty segment
        return path.split('/').filter(p => p).pop();
    }
    
    function createRsyncCommand() {
        // Prompt for source path
        let sourcePath = Prompt.create();
        sourcePath.title = "Source Path";
        sourcePath.message = "Enter the path to the files you want to back up";
        sourcePath.addTextField("sourcePath", "Path", "/Users/your-name/Desktop/Source Folder");
        sourcePath.dismissAction = "Cancel";
        sourcePath.addButton("Continue");
        let sourceResult = sourcePath.show();
    
        if (!sourceResult) {
            context.cancel();
        } else {
            let sourceValue = sourcePath.fieldValues["sourcePath"];
            if (!sourceValue) {
                context.fail("Source path is required");
            } else {
                // Prompt for destination path
                let destPath = Prompt.create();
                destPath.title = "Destination Path";
                destPath.message = "Enter the path where files should be backed up";
                destPath.addTextField("destPath", "Path", "/Volumes/External Drive/Destination Folder");
                destPath.dismissAction = "Cancel";
                destPath.addButton("Continue");
                let destResult = destPath.show();
    
                if (!destResult) {
                    context.cancel();
                } else {
                    let destValue = destPath.fieldValues["destPath"];
                    if (!destValue) {
                        context.fail("Destination path is required");
                    } else {
                        // Ask about --delete option
                        let deleteOption = Prompt.create();
                        deleteOption.title = "Include --delete option?";
                        deleteOption.message = "This will remove files from the destination that don't exist in the source";
                        deleteOption.dismissAction = "Cancel";
                        deleteOption.addButton("Yes");
                        deleteOption.addButton("No");
                        let deleteResult = deleteOption.show();
    
                        if (!deleteResult) {
                            context.cancel();
                        } else {
                            // Format paths (add quotes if they contain spaces, remove any existing quotes first)
                            let cleanSource = sourceValue.replace(/['"]/g, '');
                            let cleanDest = destValue.replace(/['"]/g, '');
                            
                            // Ensure paths end with trailing slash before adding quotes
                            if (!cleanSource.endsWith('/')) cleanSource += '/';
                            if (!cleanDest.endsWith('/')) cleanDest += '/';
    
                            // Add quotes if needed
                            let formattedSource = cleanSource.includes(' ') ? 
                                `"${cleanSource}"` : cleanSource;
                            let formattedDest = cleanDest.includes(' ') ? 
                                `"${cleanDest}"` : cleanDest;
    
                            // Get folder names for the message
                            let sourceFolderName = getLastFolderName(cleanSource);
                            let destFolderName = getLastFolderName(cleanDest);
    
                            // Construct the message
                            let explanation;
                            if (deleteResult === 0) {
                                explanation = `This rsync command will synchronize "${sourceFolderName}" to "${destFolderName}". The --delete option is enabled, which means "${destFolderName}" will become an exact copy of "${sourceFolderName}" - any files in "${destFolderName}" that no longer exist in "${sourceFolderName}" will be removed.`;
                            } else {
                                explanation = `This rsync command will copy all files from "${sourceFolderName}" to "${destFolderName}". Since the --delete option is not used, any existing files in "${destFolderName}" will be kept, even if they've been removed from "${sourceFolderName}". This means "${destFolderName}" may accumulate files over time.`;
                            }
                            explanation += '\n\n';
    
                            // Construct the rsync command
                            let command = `rsync -azvh`;
                            if (deleteResult === 0) {
                                command += ` --delete`;
                            }
                            command += ` ${formattedSource} ${formattedDest}`;
    
                            // Create new draft with the explanation and command
                            let d = Draft.create();
                            d.content = explanation + command;
                            d.update();
                            editor.load(d);
                        }
                    }
                }
            }
        }
    }
    
    createRsyncCommand();

Options

  • After Success Default
    Notification Info
    Log Level Info
Items available in the Drafts Directory are uploaded by community members. Use appropriate caution reviewing downloaded items before use.