Inconsistent behaviors for coping directory among cp, rsync, aws s3 cp, aws s3 sync, az storage copy and gsutil cp are sometimes confusing. Here is a list of their behaviors and examples for you to better understand them.

Assume we have the following directory structure in current directory and cloud storage before running following commands:

.
├── dir1
│   └── file1
├── dir2
│   └── file2
└── dir3
    └── file3

cp

  • When destination directory does NOT exist,
    • the destination directory is created, and the content of source directory is copied.
  • When destination directory exists,
    • by default, both the directory itself and its content are copied to the destination directory;
    • unless the source directory is . , or is suffixed with /. , by then, only the content of source directory is copied.

Note: Suffix a directory with / is the same as not suffixing it.

Note: The behavior of cp is consistent with mv.

cp Examples

$ # Destination not exists
$ cp -r -v dir1 dir4
'dir1' -> 'dir4'
'dir1/file1' -> 'dir4/file1'
$ # Source dir suffixed with /.
$ # Destination dir not exists
$ cp -r -v dir1/. dir5
'dir1/.' -> 'dir5'
'dir1/./file1' -> 'dir5/file1'
$ # Destination dir exists
$ cp -r -v dir1 dir2
'dir1' -> 'dir2/dir1'
'dir1/file1' -> 'dir2/dir1/file1'
$ # Source dir suffixed with /.
$ # Destination dir exists
$ cp -r -v dir1/. dir3
'dir1/./file1' -> 'dir3/./file1'

rsync

  1. If destination directory does NOT exist, it's created;
    • by default, both the directory itself and its content are copied to the destination directory;
    • unless the source directory is . , or is suffixed with / or /. , by then, only the content of source directory is copied.

Note: Suffix a directory with / is the same as suffixing it with /. .

rsync Examples

$ # Destination not exists
$ rsync -r dir1 dir4
$ tree dir4
dir4
└── dir1
    └── file1

1 directory, 1 file
$ # Source dir suffixed with /.
$ # Destination not exists
$ rsync -r dir1/. dir5
$ tree dir5
dir5
└── file1

0 directories, 1 file
$ # Destination dir exists
$ rsync -r dir1 dir2
$ tree dir2
dir2
├── dir1
│   └── file1
└── file2
$ # Source dir suffixed with /.
$ # Destination dir exists
$ rsync -r dir1/. dir3
$ tree dir3
dir3
└── file1

0 directories, 1 file

aws s3 cp / aws s3 sync

It's always the content of source directory are copied to the destination directory.

Note: Suffix a directory with / is the same as not suffixing it or suffixing it with /. .

aws Examples

$ aws s3 cp --recursive dir1 s3://zzz.buzz/dir2
upload: dir1/file1 to s3://zzz.buzz/dir2/file1

$ aws s3 cp --recursive dir1/. s3://zzz.buzz/dir3
upload: dir1/file1 to s3://zzz.buzz/dir3/file1

$ aws s3 cp --recursive dir1 s3://zzz.buzz/dir4
upload: dir1/file1 to s3://zzz.buzz/dir4/file1

$ aws s3 cp --recursive dir1/. s3://zzz.buzz/dir5
upload: dir1/file1 to s3://zzz.buzz/dir5/file1

$ aws s3 cp --recursive s3://zzz.buzz/dir2 dir1
download: s3://zzz.buzz/dir2/file1 to dir1/file1
$ aws s3 sync dir1 s3://zzz.buzz/dir2
upload: dir1/file1 to s3://zzz.buzz/dir2/file1

$ aws s3 sync dir1/. s3://zzz.buzz/dir3
upload: dir1/file1 to s3://zzz.buzz/dir3/file1

$ aws s3 sync dir1 s3://zzz.buzz/dir4
upload: dir1/file1 to s3://zzz.buzz/dir4/file1

$ aws s3 sync dir1/. s3://zzz.buzz/dir5
upload: dir1/file1 to s3://zzz.buzz/dir5/file1

$ aws s3 sync s3://zzz.buzz/dir2 dir1
download: s3://zzz.buzz/dir2/file1 to dir1/file1

az storage copy

It's always the directory iteself and its content get copied to the destination directory.

Note: Suffix a directory with / is the same as not suffixing it or suffixing it with /. .

az Examples

$ az storage copy -r -s dir1 -d https://zzzbuzz.blob.core.windows.net/container/dir2

$ az storage blob list --account-name zzzbuzz -c container --prefix dir2 --query "[].{name:name}" --output tsv
dir2/dir1/file1
dir2/file2
$ az storage copy -r -s dir1/. -d https://zzzbuzz.blob.core.windows.net/container/dir3

$ az storage blob list --account-name zzzbuzz -c container --prefix dir3 --query "[].{name:name}" --output tsv
dir3/dir1/file1
dir3/file3
$ az storage copy -r -s dir1 -d https://zzzbuzz.blob.core.windows.net/container/dir4

$ az storage blob list --account-name zzzbuzz -c container --prefix dir4 --query "[].{name:name}" --output tsv
dir4/dir1/file1
$ az storage copy -r -s dir1/. -d https://zzzbuzz.blob.core.windows.net/container/dir5

$ az storage blob list --account-name zzzbuzz -c container --prefix dir5 --query "[].{name:name}" --output tsv
dir5/dir1/file1
$ az storage copy -r -s https://zzzbuzz.blob.core.windows.net/container/dir1 -d download

$ tree download
download
└── dir1
    └── file1

1 directory, 1 file

gsutil cp

  • When destination directory does NOT exist,
    • the destination directory is created, and the content of source directory is copied.
  • When destination directory exists,
    • by default, both the directory itself and its content are copied to the destination directory;
    • unless the source directory is ., or is suffixed with /., by then, only the content of source directory is copied.

Note: Suffix a directory with / is the same as not suffixing it.

Note: The behavior of gsutil cp is consistent with cp.

gsutil Examples

$ # Destination not exists
$ gsutil cp -r dir1 gs://zzz.buzz/dir4
$ gsutil ls -r gs://zzz.buzz
gs://zzz.buzz/dir4/:
gs://zzz.buzz/dir4/file1
$ # Source dir suffixed with /.
$ # Destination not exists
$ gsutil cp -r dir1/. gs://zzz.buzz/dir5
$ gsutil ls -r gs://zzz.buzz
gs://zzz.buzz/dir5/:
gs://zzz.buzz/dir5/file1
$ # Destination dir exists
$ gsutil cp -r dir1 gs://zzz.buzz/dir2
$ gsutil ls -r gs://zzz.buzz
gs://zzz.buzz/dir2/:
gs://zzz.buzz/dir2/file2

gs://zzz.buzz/dir2/dir1/:
gs://zzz.buzz/dir2/dir1/file1
$ # Source dir suffixed with /.
$ # Destination dir exists
$ gsutil cp -r dir1/. gs://zzz.buzz/dir3
$ gsutil ls -r gs://zzz.buzz
gs://zzz.buzz/dir3/:
gs://zzz.buzz/dir3/file1
gs://zzz.buzz/dir3/file3