A completed-file handoff needs stronger safeguards than an ordinary copy because the sender's originals disappear after each successful transfer. A file list frozen before the move can drive both rsync and a SHA-256 manifest, keeping the receiver check bound to the exact names selected for removal.

The --remove-source-files option removes sender-side non-directories that were part of the transfer and were successfully duplicated on the receiver. --files-from confines the transfer to the saved list, while --from0 reads its NUL-delimited names without splitting spaces or other special characters.

Only closed files belong in the selection. Keep unfinished files under a suffix such as *.new and rename them only after writing is complete; names published after the list is created remain at the source for the next move instead of entering the current destructive transfer.

Steps to move files with rsync and remove source files:

  1. Change to the handoff directory so every selected path is relative to its root.
    $ cd /srv/upload-ready
  2. Freeze the completed-file names in a NUL-delimited list outside the source tree.
    $ find . -type f ! -name '*.new' -print0 > /srv/upload-ready.files

    Do not regenerate or edit /srv/upload-ready.files/ before the live transfer. A producer can continue publishing completed names, but any name added after this command waits for the next run.

  3. Create the SHA-256 manifest from that exact file list.
    $ xargs --null --no-run-if-empty --arg-file=/srv/upload-ready.files sha256sum > /srv/upload-ready.sha256

    Storing both control files outside /srv/upload-ready/ prevents them from entering the move. Stop and resolve any checksum error before continuing.

  4. Read the manifest and confirm that it contains every file intended for this move.
    $ cat /srv/upload-ready.sha256
    6f3e50a739de306bbb59ac2fed506535f9799fde75e71fac5239c3b0f7be1aa3  ./invoices/2026-07.csv
    c4a9e7fc4c5597576df0eb5afeee0623be5999b498f33d79579361865e37b90f  ./batch-001.csv

    Stop if the manifest is empty or includes a file that is still being written.

  5. Preview the frozen selection and inspect every itemized path.
    $ rsync -ai --dry-run --from0 --files-from=/srv/upload-ready.files --remove-source-files ./ /srv/archive/
    >f+++++++++ batch-001.csv
    cd+++++++++ invoices/
    >f+++++++++ invoices/2026-07.csv

    The dry run neither copies nor removes files. The source argument ./ refers to /srv/upload-ready/ after the earlier directory change, and files absent from the saved list cannot enter this transfer.

  6. Run the live transfer with the unchanged file list.
    $ rsync -ai --from0 --files-from=/srv/upload-ready.files --remove-source-files ./ /srv/archive/
    >f+++++++++ batch-001.csv
    cd+++++++++ invoices/
    >f+++++++++ invoices/2026-07.csv

    This permanently removes each selected source file after rsync duplicates it on the receiver. Use the option only for closed files, and do not alter the saved list between the dry run and live transfer.

  7. List the remaining source files and confirm that later completions plus unfinished files were not removed.
    $ find /srv/upload-ready -type f -print
    /srv/upload-ready/incoming/batch-002.csv
    /srv/upload-ready/import-002.new

    In this handoff, the producer renamed incoming/batch-002.new after the list was frozen. The completed batch-002.csv remains for the next run because its final name was never added to the current list.

  8. List the destination files and confirm that only the frozen selection arrived.
    $ find /srv/archive -type f -print
    /srv/archive/invoices/2026-07.csv
    /srv/archive/batch-001.csv
  9. Preview the empty source directories eligible for cleanup.
    $ find /srv/upload-ready -mindepth 1 -depth -type d -empty -print
    /srv/upload-ready/invoices
    /srv/upload-ready/old/empty

    --remove-source-files does not remove directories. The depth-first preview leaves the source root and every nonempty directory outside the cleanup set.

  10. Delete only empty subdirectories below the handoff root.
    $ find /srv/upload-ready -mindepth 1 -depth -type d -empty -print -delete
    /srv/upload-ready/invoices
    /srv/upload-ready/old/empty
    /srv/upload-ready/old

    This permanently removes empty folder names from the confined source tree. Keep the starting path and -mindepth 1 unchanged, and omit this cleanup when empty directories carry meaning for the producer.

  11. Inspect the retained source tree after directory cleanup.
    $ find /srv/upload-ready -print
    /srv/upload-ready
    /srv/upload-ready/incoming
    /srv/upload-ready/incoming/batch-002.csv
    /srv/upload-ready/import-002.new
  12. Change to the destination root so the manifest's relative paths resolve against the moved files.
    $ cd /srv/archive
  13. Verify that every file in the frozen move set is readable and still matches its pre-transfer checksum.
    $ sha256sum --check /srv/upload-ready.sha256
    ./invoices/2026-07.csv: OK
    ./batch-001.csv: OK

    Every entry must report OK. A missing, unreadable, or changed destination file makes sha256sum return a nonzero status.