# SFTP連線
###### tags: `python` `ftp` `sftp`
## 準備
#### 安裝pysftp模組
```bash
# pip3 install pysftp
```
## 常用方法
| 方法名稱 | 說明 | 範例 |
| -------- | -------- | ---|
| pwd | 目前路徑 |`print(sftp.pwd)`|
| cwd() | 移動目錄 |`sftp.cwd('/test')`|
| list_dir() | 取得目錄下所有檔案和目錄(僅名稱) |`sftp.list_dir()`|
| listdir_attr() | 取得目錄下所有檔案和目錄(含屬性) |`sftp.listdir_attr()`|
| exists() | 判斷檔案或目錄是否存在 |`sftp.exists('hello')`|
| mkdir() | 建立新的目錄 |`sftp.mkdir('hello')`|
| put() | 上傳檔案到目前路徑 |`sftp.put('a.txt', 'c.txt')`|
| get() | 下載目前路徑下的檔案 |`sftp.get('a.txt', 'b.txt')`|
| isdir() | 判斷檔案是否為目錄 |`sftp.isdir('hello')`|
| isfile() | 判斷檔案是否為檔案 |`sftp.isfile('a.txt')`|
>**補充**
>
>- sftp為已連線的物件
## 說明
SFTP Server連線時會檢查HostKey,如果沒有提供的話會出現錯誤而造成無法連線,
```
AttributeError: 'Connection' object has no attribute '_sftp_live'
```
有幾種解決方式如下:
#### 方式一:在`~/.ssh/known_hosts`加入Public Key
先使用`ssh-keyscan`工具掃描該站台的Public Key
```
$ ssh-keyscan example.com
example.com ssh-rsa AAAAB3NzaC1yc2EAAAADAQABAAAAgQDj4CkA3SdT7TiPqHKMOa1u...
```
然後再將掃描得到的Public Key加入到`~/.ssh/known_hosts`檔案中
```
example.com ssh-rsa AAAAB3NzaC1yc2EAAAADAQABAAAAgQDj4CkA3SdT7TiPqHKMO...
```
在Python內加入下面程式碼:
```python=
cnopts = pysftp.CnOpts(knownhosts='known_hosts')
with pysftp.Connection(host, username, password, cnopts=cnopts) as sftp:
...
```
#### 方式二:直接在程式碼裡面指定Public Key
```python=
keydata = b"""AAAAB3NzaC1yc2EAAAADAQABAAAAgQDj4CkA3SdT7TiPqHKMO..."""
key = paramiko.RSAKey(data=decodebytes(keydata))
cnopts = pysftp.CnOpts()
cnopts.hostkeys.add(host, 'ssh-rsa', key)
with pysftp.Connection(host, username, password, cnopts=cnopts) as sftp:
...
```
#### 方式三:第一次連線忽略Public Key,連線後再將Server傳過來的Key儲存下來
```python=
if cnopts.hostkeys.lookup(host) == None:
print("發現新的SFTP站台,將允許該站台憑證 .....")
# 將新的憑證存到變數裡
hostkeys = cnopts.hostkeys
print(hostkeys.values())
# 將憑證檢查暫時關閉,避免第一次無法連線,
cnopts.hostkeys = None
with pysftp.Connection(host, username=username, password=password, cnopts=cnopts) as sftp:
# 加入新的憑證
if hostkeys != None:
print("連線到該新的SFTP站台並儲存該站台憑證")
hostkeys.add(host, sftp.remote_server_key.get_name(), sftp.remote_server_key)
hostkeys.save(pysftp.helpers.known_hosts())
```
> **補充:**
>
> 如果該server並非使用標準的port 22,可以在`pysftp.Connection()`方法加上`port=21`參數來指定port。
## 範例程式(使用方式三)
```python=
import pysftp
host = '??????????????'
username = '?????????????'
password = '???????????'
cnopts = pysftp.CnOpts()
hostkeys = None
if cnopts.hostkeys.lookup(host) == None:
print("發現新的SFTP站台,將允許該站台憑證 .....")
# 將新的憑證存到變數裡
hostkeys = cnopts.hostkeys
# 將憑證檢查暫時關閉,避免第一次無法連線,
cnopts.hostkeys = None
with pysftp.Connection(host, username=username, password=password, cnopts=cnopts) as sftp:
# 加入新的憑證
if hostkeys != None:
print("連線到該新的SFTP站台並儲存該站台憑證")
hostkeys.add(host, sftp.remote_server_key.get_name(), sftp.remote_server_key)
hostkeys.save(pysftp.helpers.known_hosts())
# 切換路徑
sftp.cwd('/aaron-test')
# 目前所在路徑
print(sftp.pwd)
# 取得目錄詳細內容
directory = sftp.listdir_attr()
# 印出結果
for attr in directory:
print(attr.filename, attr, sftp.isdir(attr.filename))
# 建立目錄
if not sftp.exists('hello'):
sftp.mkdir('hello')
# 上傳檔案
sftp.put('a.txt', 'c.txt')
# 下載檔案
sftp.get('c.txt', 'b.txt')
# 取得目錄所有內容
print(sftp.listdir())
```
## 練習
1. 寫一sftp程式,可以顯示該目錄下的所有目錄和檔案,以及改目錄下的子目錄內所有內容
2. 寫一sftp程式,可以下載該目錄下的所有檔案