How to Copy Files and Directories in Bash

How to Copy Files and Directories in Bash

Copying files feels like it should be the simplest thing in the world, and most of the time it is — until you’re trying to preserve permissions during a server migration, or you accidentally overwrite a file you needed, or you’re copying thousands of files and want progress feedback. I’ve run into all of these situations, and the cp command has more depth to it than most people realize on their first pass.

This guide covers everything from basic file copying to preserving metadata, copying directories recursively, handling conflicts safely, and automating copy operations in scripts.

The Basic cp Command

cp source.txt destination.txt

This copies source.txt to a new file called destination.txt in the same directory. If destination.txt already exists, it gets overwritten without warning by default — something to keep in mind.

Copying a File Into a Directory

cp report.txt /home/user/documents/

This copies report.txt into the documents directory, keeping the original filename.

Copying Multiple Files at Once

cp file1.txt file2.txt file3.txt /home/user/backup/

When copying multiple files, the last argument must be a directory, since cp needs somewhere to put all of them.

Copying Directories Recursively

By default, cp refuses to copy directories unless you tell it to do so recursively.

cp -r source_folder/ destination_folder/
  • -r (or -R) — recursive, copies the directory and everything inside it, including subdirectories.

If destination_folder doesn’t exist, it will be created and populated with the contents of source_folder. If destination_folder already exists, source_folder gets copied into it as a subdirectory — this trips people up constantly, so always double-check the resulting structure with ls afterward.

Preserving File Attributes

By default, copying a file can change its timestamps and sometimes its permissions depending on your system’s umask settings. To preserve the original attributes:

cp -p source.txt destination.txt
  • -p — preserves mode (permissions), ownership, and timestamps from the original file.

For a complete, archive-style copy that also preserves symbolic links and directory structure:

cp -a source_folder/ destination_folder/
  • -a — archive mode, equivalent to -dR --preserve=all. This is the option I reach for whenever I need an exact copy, like when migrating a project directory to a new server.

Preventing Accidental Overwrites

cp -i source.txt destination.txt
  • -i — interactive, prompts for confirmation before overwriting an existing file.

I actually alias cp to always include -i (as covered in the aliases article) since it’s such a cheap safety net against accidental data loss.

To never overwrite existing files instead of prompting:

cp -n source.txt destination.txt
  • -n — no-clobber, skips copying if the destination file already exists, without asking.

Verbose Output

cp -v source.txt destination.txt
  • -v — verbose, prints a message for each file copied, which is especially useful when copying many files or directories recursively so you can see progress and confirm exactly what happened.

Copying With Wildcards

cp *.txt /home/user/documents/

This copies every file ending in .txt in the current directory into the documents folder.

cp report_*.csv /home/user/reports/

This copies only files matching the pattern report_*.csv, useful for selectively copying related files without grabbing everything in a directory.

Copying Only If Source Is Newer

cp -u source.txt destination.txt
  • -u — update, only copies if the source file is newer than the destination file (or if the destination doesn’t exist yet). This is handy for incremental copying without overwriting up-to-date files unnecessarily.

Copying Between Remote Machines

While plain cp only works locally, scp (secure copy) uses the same underlying logic for copying files over SSH to remote systems.

scp report.txt user@remote-server.com:/home/user/documents/

To copy a directory recursively over SSH:

scp -r project_folder/ user@remote-server.com:/home/user/projects/

For syncing large directories or making repeated transfers efficient (only copying changed files), rsync is generally the better tool:

rsync -avz project_folder/ user@remote-server.com:/home/user/projects/

Breaking down these flags:

  • -a — archive mode, preserves permissions, timestamps, and symbolic links, similar to cp -a.
  • -v — verbose output.
  • -z — compresses data during transfer, speeding things up over slower network connections.

Copying With Progress Feedback

For large files, plain cp gives no progress indicator, which can be frustrating. One common trick uses rsync instead, even for local copies, because it supports a progress flag:

rsync -ah --progress bigfile.iso /home/user/backup/
  • --progress — shows a live progress bar and transfer speed for each file.

Real-World Use Cases

Server migrations: Using cp -a to preserve exact permissions and ownership when moving a website’s files to a new hosting environment.

Backup before editing: A quick habit of running cp important_config.conf important_config.conf.bak before making risky changes, so you can always roll back.

Deploying static files: Copying built assets (like a compiled frontend) from a build directory into a web server’s public directory as part of a deployment script.

Batch duplication for testing: Copying a database dump multiple times with different names to set up isolated test environments.

Automation Example: Timestamped Backup Copy

#!/bin/bash

SOURCE_FILE="/etc/nginx/nginx.conf"
BACKUP_DIR="/etc/nginx/backups"
TIMESTAMP=$(date +"%Y%m%d_%H%M%S")

mkdir -p "$BACKUP_DIR"
cp -p "$SOURCE_FILE" "$BACKUP_DIR/nginx.conf.$TIMESTAMP.bak"

echo "Backup saved as $BACKUP_DIR/nginx.conf.$TIMESTAMP.bak"

How this works internally:

  1. date +"%Y%m%d_%H%M%S" generates a unique timestamp so repeated runs don’t overwrite previous backups.
  2. mkdir -p ensures the backup directory exists without erroring if it’s already there.
  3. cp -p copies the config file while preserving its original permissions and timestamps, which matters for configuration files where permission mistakes can break a service.
  4. This pattern is common before editing critical configuration files on production servers, giving you an instant rollback option if something goes wrong.

Best Practices

  • Use -i by default (or alias cp to include it) to avoid accidental overwrites.
  • Use -a for exact, full-fidelity copies, especially when migrating servers or backing up entire directory trees.
  • Always verify the resulting directory structure after a recursive copy — check whether the source folder ended up nested inside the destination unexpectedly.
  • Prefer rsync over cp for large transfers, repeated syncs, or anything crossing a network connection.
  • Include timestamps in backup filenames rather than relying on a single static backup name.

Security Considerations

  • Be careful copying files with sensitive permissions (like private SSH keys) — verify that -p or -a correctly preserved restrictive permissions (600), since a fresh copy without preservation could default to more permissive settings depending on your umask.
  • When copying to shared or network directories, double-check the destination’s permissions don’t inadvertently expose sensitive files to other users.
  • Avoid copying files as root into user-owned directories without adjusting ownership afterward, which can create confusing permission issues later (chown as needed after copying).

Optimization Tips

  • For copying large numbers of small files, rsync or tar piped through ssh often outperforms plain cp or scp, since it reduces per-file overhead.
  • Use cp --reflink=auto on filesystems that support copy-on-write (like Btrfs or XFS with reflink support) for near-instant copies of large files without duplicating actual disk blocks until modified.
  • Avoid unnecessary -v verbosity when scripting copies of thousands of files, since printing that much output can noticeably slow things down.

Troubleshooting Common Issues

“cp: omitting directory” error: You forgot the -r flag when trying to copy a directory.

Copied directory ends up nested one level deeper than expected: This happens when the destination directory already exists — cp -r source/ dest/ copies source into dest rather than merging its contents, if dest already exists.

Permissions look different after copying: Use -p or -a to preserve original permissions; without them, cp applies your current umask to new files.

“No space left on device” during copy: Check available disk space with df -h before large copy operations, especially when duplicating big files or directories.

Frequently Asked Questions

What’s the difference between cp -r and cp -a? -r only makes the copy recursive; -a additionally preserves permissions, timestamps, ownership, and symbolic links, making it a true “archive” copy.

Does cp follow symbolic links by default? Yes, by default cp copies the file a symlink points to, not the symlink itself. Use -P to copy the symlink itself instead of following it, or -a which preserves symlinks appropriately.

How do I copy only specific file types from a directory? Use wildcards, like cp source_dir/*.jpg destination_dir/, or combine with find -exec cp {} destination_dir/ \; for more complex filtering.

Is rsync always better than cp? Not always — for simple, one-off local copies, cp is simpler and perfectly adequate. rsync shines for large transfers, repeated syncs, and remote copying where efficiency matters.

Common Mistakes to Avoid

  • Overwriting important files without realizing it because -i wasn’t used.
  • Forgetting -r when copying directories and getting a confusing error message.
  • Assuming a recursive copy preserves permissions by default — it doesn’t unless you add -p or -a.
  • Copying large files or directories over a network with plain cp/scp instead of rsync, missing out on resumable, incremental transfers.

Summary

Copying files and directories in Bash starts with the simple cp command but has real depth once you factor in recursive copying, permission preservation, conflict handling, and remote transfers. Knowing when to reach for -a versus -r, when to use -i for safety, and when to switch to rsync for larger or repeated transfers will make your file management both safer and more efficient.

References

Total
2
Shares

Leave a Reply

Previous Post
How to Move and Rename Files in Bash

How to Move and Rename Files in Bash

Next Post
How to Find Files in Bash

How to Find Files in Bash

Related Posts