DEV Community

Yoshi Nakamoto
Yoshi Nakamoto

Posted on

rcp & rmv: copying & moving using rsync with the simplicity of cp & mv

These two commands rcp and rmv acts a direct replacement for cp and mv for copying or moving files using rsync.

Instead of doing:

rsync -avh --partial --info=progress2 source dest
rsync -avh --partial --info=progress2 --remove-source-files source dest
Enter fullscreen mode Exit fullscreen mode

Now you can simply do:

rcp source dest
rmv source dest
Enter fullscreen mode Exit fullscreen mode

You can also include additional rsync options, here are two examples with -z for compression (great for faster network transfers) and --dry-run to see what gets transferred without actually transferring (yet).

rcp -z source dest
rmv --dry-run source dest
Enter fullscreen mode Exit fullscreen mode

The rcp() and rmv() shell functions

You can copy and paste the following lines in your .zshrc or .bashrc file.

# Function to copy files using rsync
# Usage: rcp [rsync_options] source [source2...] destination
rcp() {
  rsync -avh --partial --info=progress2 "$@"
}

# Function to move files using rsync (and delete empty directories after)
# Usage: rmv [rsync_options] source [source2...] destination
rmv() {
  local default_options=(-avh --partial --info=progress2 --remove-source-files)
  local rsync_options=()
  local is_dry_run=0

  # Separate options from paths
  while [[ "$#" -gt 0 ]]; do
    if [[ "$1" == "--dry-run" ]] || [[ "$1" == "-n" ]]; then
      is_dry_run=1
      rsync_options+=("$1")
    elif [[ "$1" == "--" ]]; then
      shift
      break
    elif [[ "$1" == "-"* ]]; then
      rsync_options+=("$1")
    else
      break
    fi
    shift
  done

  # Check if at least two arguments remain (sources and destination)
  if [[ "$#" -lt 2 ]]; then
    echo "Usage: rmv [rsync_options] source [source2...] destination"
    return 1
  fi

  local all_paths=("${@:1:$#}")
  local source_paths=("${all_paths[@]:0:${#all_paths[@]}-1}")
  local dest_path="${all_paths[-1]}"

  # Execute rsync by passing options from the arrays
  rsync "${default_options[@]}" "${rsync_options[@]}" "${source_paths[@]}" "$dest_path" && {
    for source_dir in "${source_paths[@]}"; do
      if [[ -d "$source_dir" ]]; then
        if [[ $is_dry_run -eq 0 ]]; then
          echo "Removing empty directory '$source_dir'..."
          find "$source_dir" -type d -empty -delete
        else
          echo "Dry run: would remove empty directory '$source_dir'..."
          find "$source_dir" -type d -empty -print
        fi
      fi
    done
  }
}
Enter fullscreen mode Exit fullscreen mode

macOS users: Upgrade your rsync

The default rsync tool that comes bundled with macOS is v2.6.9 does not support the --info=progress2 command, so run brew install rsync to get the latest rsync.

$ /usr/bin/rsync --version
openrsync: protocol version 29
rsync version 2.6.9 compatible

$ /opt/homebrew/bin/rsync --version
rsync  version 3.4.1  protocol version 32
Enter fullscreen mode Exit fullscreen mode

Once installed, add this to your .zshrc or .bashrc file to replace the default rsync with homebrew's rsync.

alias rsync="/opt/homebrew/bin/rsync"
Enter fullscreen mode Exit fullscreen mode

Handling the removal of empty directories

The --remove-source-files option in rsync does not remove empty directories after all the files has been moved. Therefore, in the rmv() shell function above, you will notice that it will run additional commands to remove the empty directories using find after rsync has finished running.


So there you have it! These two little shell functions, rcp and rmv, wrapping up the power of rsync into something as easy to use as cp and mv.

No more remembering long strings of options, just a simple command that gets the job done, with a nice progress update to boot.

Top comments (0)