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.
Related: How to install rsync on Ubuntu
Related: How to preview rsync changes before syncing
Related: How to resume an interrupted rsync transfer
$ cd /srv/upload-ready
$ 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.
$ 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.
$ 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.
$ 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.
Related: How to read rsync itemized changes
$ 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.
$ 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.
$ find /srv/archive -type f -print /srv/archive/invoices/2026-07.csv /srv/archive/batch-001.csv
$ 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.
$ 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.
$ 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
$ cd /srv/archive
$ 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.