I want to mimic the behavior of the famous Linux rsync command where it copies the entire directory from "test" if the "/" is not specified at the end of a directory and copies everything inside the "test/" when the "/" is present. My local "test" folder is structure like so:
test
.
├── fileA.txt
├── fileB.txt
├── test1
│ └── test3
│ └── file3.txt
└── test2
└── file2.txt
To copy the entire local test folder to remote server in rsync:
rsync -avzP test username@remotehost:/home/
Inside the remotehost's home directory would be
home
.
|__ test
├── fileA.txt
├── fileB.txt
├── test1
│ └── test3
│ └── file3.txt
└── test2
└── file2.txt
Example A
To copy everything inside the local "test" folder excluding itself:
rsync -avzP test/ username@remotehost:/home/
The structure for home directory would be:
home/
.
├── fileA.txt
├── fileB.txt
├── test1
│ └── test3
│ └── file3.txt
└── test2
└── file2.txt
Example B
The code I have isn't working for Example B. I thought about splitting the paths and get rid of the "test" then copy everything inside of it but it only leads me to an endless, nested, for loops. Another idea is to use os.listdir. If it's a directory, list the directory again and copy the contents inside that directory. This is still another endless for loop. The tree structure above is an over simplified example but in real life, we all know that the directory could be 5 levels deep. How can I implement Example B?
def put (self, localpath, remotepath):
sftp = self.ssh.open_sftp ()
# Create remote directory if it doesn't exist
try:
sftp.stat (remotepath)
except FileNotFoundError:
sftp.mkdir (remotepath)
if os.path.isfile (localpath):
# Obtain file name from local path & append to remote path
path = os.path.split (localpath) # Returns a tuple (directory, filename)
remote_filename = os.path.join (remotepath, path [1])
print (' Copying %s' % remote_filename)
sftp.put (localpath, remote_filename)
elif os.path.isdir (localpath):
p = os.path.join (remotepath, localpath)
try:
sftp.stat (p)
except FileNotFoundError:
sftp.mkdir (p)
for dirpath, dirnames, filenames in os.walk (localpath):
# Traverse into each child directory and create sub directory if it doesn't exist
if dirnames:
for dirname in dirnames:
subdir = os.path.join (dirpath, dirname)
try:
sftp.stat (subdir)
except FileNotFoundError:
sftp.mkdir (subdir)
for filename in filenames:
local_filename = os.path.join (dirpath, filename)
remote_filename = os.path.join (remotepath, local_filename)
sftp.put (local_filename, remote_filename)
I figured it out. It's not the prettiest but functional. If anyone has better, cleaner, and more pythonic way of doing so, please share.
def put (self, localpath, remotepath):
sftp = self.ssh.open_sftp ()
# Create remote directory if it doesn't exist
try:
sftp.stat (remotepath)
except FileNotFoundError:
sftp.mkdir (remotepath)
if os.path.isfile (localpath):
# Obtain file name from local path & append to remote path
path = os.path.split (localpath) # Returns a tuple (directory, filename)
remote_filename = os.path.join (remotepath, path [1])
print (' Copying %s' % remote_filename)
sftp.put (localpath, remote_filename)
elif os.path.isdir (localpath):
if localpath.endswith ('/'):
for dirpath, dirnames, filenames in os.walk (localpath):
# Change local dirpath to match remote path. Ex: local/dir/.. to remote/dir/...
remotedir = dirpath.split ('/') # remotedir = [local, dir1, dir2, ...]
remotedir [0] = remotepath.rstrip ('/') # remotedir = [/remote, dir1, dir2, ...]
remotedir = '/'.join (remotedir)
# Traverse into each child directory and create sub directory if it doesn't exist on remote host
if dirnames:
for dirname in dirnames:
subdir = os.path.join (remotedir, dirname)
try:
sftp.stat (subdir)
except FileNotFoundError:
sftp.mkdir (subdir)
for filename in filenames:
localdir = os.path.join (dirpath, filename)
remotefile = os.path.join (remotedir, filename)
print (' Copying %s' % localdir)
sftp.put (localdir, remotefile)
else:
# Create path /remote/local/dir1...
p = os.path.join (remotepath, localpath)
try:
sftp.stat (p)
except FileNotFoundError:
sftp.mkdir (p)
for dirpath, dirnames, filenames in os.walk (localpath):
if dirnames:
for dirname in dirnames:
subdir = os.path.join (dirpath, dirname)
remotedir = os.path.join (remotepath, subdir)
try:
sftp.stat (remotedir)
except FileNotFoundError:
sftp.mkdir (remotedir)
for filename in filenames:
local_filename = os.path.join (dirpath, filename)
remote_filename = os.path.join (remotepath, local_filename)
print (' Copying %s' % local_filename)
sftp.put (local_filename, remote_filename)
else:
print ('File or directory not found.')
End results:
Example A:
>>> ssh.put ('test', '/home/user/')
Copying test/fileA.txt
Copying test/fileB.txt
Copying test/test1/test3/file3.txt
Copying test/test2/file2.txt
Completed!
-sh-4.1$ ls -lh test/*
-rw-r----- 1 user users 0 Sep 1 23:43 test/fileA.txt
-rw-r----- 1 user users 0 Sep 1 23:43 test/fileB.txt
test/test1:
total 4.0K
drwxr-x--- 2 user users 4.0K Sep 1 23:43 test3
test/test2:
total 0
-rw-r----- 1 user users 0 Sep 1 23:43 file2.txt
-sh-4.1$ ls -lh test/*/*
-rw-r----- 1 user users 0 Sep 1 23:43 test/test2/file2.txt
test/test1/test3:
total 0
-rw-r----- 1 user users 0 Sep 1 23:43 file3.txt
-sh-4.1$
Example B:
>>> ssh.put ('test/', '/home/user/')
Copying test/fileA.txt
Copying test/fileB.txt
Copying test/test1/test3/file3.txt
Copying test/test2/file2.txt
Completed!
-sh-4.1$ pwd
/home/user
-sh-4.1$ ls -lh
total 108K
-rw-r----- 1 user users 0 Sep 1 23:43 fileA.txt
-rw-r----- 1 user users 0 Sep 1 23:43 fileB.txt
drwxr-x--- 3 user users 4.0K Sep 1 23:43 test1
drwxr-x--- 2 user users 4.0K Sep 1 23:43 test2
-sh-4.1$ ls -lh test1 test2
test1:
total 4.0K
drwxr-x--- 2 user users 4.0K Sep 1 23:43 test3
test2:
total 0
-rw-r----- 1 user users 0 Sep 1 23:43 file2.txt
-sh-4.1$ ls -lh test1/test3/
total 0
-rw-r----- 1 user users 0 Sep 1 23:43 file3.txt
-sh-4.1$