Как скопировать файл с Windows-машины на Linux-сервер с помощью Python?

Программист IT и автоматизация бизнеса
← На главную

Перенос данных между разными операционными системами — классическая задача при автоматизации обмена данными или деплое приложений. Когда нам необходимо передать файл из системы Windows в систему Linux программным путем, мы сталкиваемся с тем, что стандартные средства копирования (например, библиотека shutil) не работают напрямую через сеть, если целевой каталог не «расшарен» через протоколы типа Samba или NFS.

В этой статье мы подробно разберем, как реализовать надежный механизм передачи файлов, используя протоколы SSH и SFTP, которые являются стандартом безопасности для Linux-систем. Мы рассмотрим основной код, разберем типичные ошибки с правами доступа и узнаем, как подготовить окружение даже в условиях отсутствия интернета.

Решение задачи с помощью библиотеки Paramiko

Наиболее гибким и популярным способом взаимодействия с Linux-серверами из Python является библиотека paramiko. Она реализует протокол SSHv2 и предоставляет удобный интерфейс для работы с SFTP (Secure File Transfer Protocol). Рассмотрим пошагово, как настроить соединение и отправить файл.

Для начала проанализируем базовый алгоритм работы скрипта:

  1. Установка SSH-соединения с целевым сервером.
  2. Настройка политики безопасности для работы с ключами хоста.
  3. Инициализация SFTP-сессии.
  4. Загрузка локального файла на удаленный путь.
  5. Закрытие сессии и соединения.

Рассмотрим пример рабочего кода, который позволяет передать файлы баз данных и геоданных (в нашем примере это файлы .dbf, .shp, .shx):


import paramiko

# Инициализируем SSH-клиент
ssh = paramiko.SSHClient()

# Настраиваем политику автоматического добавления ключа сервера 
# (внимание: для промышленных систем лучше использовать проверку ключа)
ssh.set_missing_host_key_policy(paramiko.AutoAddPolicy())

# Устанавливаем соединение с Linux-машиной
ssh.connect('10.255.255.99', username='permyakov', password='password')

# Открываем SFTP-сессию
ftp = ssh.open_sftp()

# Определяем пути и отправляем файлы
# Пример для нескольких файлов
files_to_upload = [
    ('C:\\Users\\svc-app\\AppData\\Local\\Temp\\Paramico\\Work.dbf', '/usr/etc/geofiles/Work.dbf'),
    ('C:\\Users\\svc-app\\AppData\\Local\\Temp\\Paramico\\Work.shp', '/usr/etc/geofiles/Work.shp'),
    ('C:\\Users\\svc-app\\AppData\\Local\\Temp\\Paramico\\Work.shx', '/usr/etc/geofiles/Work.shx')
]

for localpath, remotepath in files_to_upload:
    print(f"Отправка {localpath} в {remotepath}...")
    ftp.put(localpath, remotepath)

# Закрываем ресурсы
ftp.close()
ssh.close()

В этом примере метод ftp.put(localpath, remotepath) берет файл с вашего локального диска на Windows и записывает его по указанному абсолютному пути на Linux-сервере. Важно убедиться, что у пользователя, под которым вы подключаетесь, есть права на запись в целевую папку.

Решение проблемы PermissionError и настройка прав доступа

Часто после успешного выполнения скрипта пользователи сталкиваются с ошибкой PermissionError: [Errno 13] Permission denied. Это происходит, если файл на Linux уже существует, но ваш пользователь не имеет прав на его перезапись, либо у вас нет прав на создание файлов в этой директории.

Выясним причину: в Linux права доступа жестко разграничены. Если вы загружаете файлы в системные папки, такие как /usr/etc/, вам может потребоваться изменить права доступа к целевой папке или самим файлам. Вы можете сделать это прямо из Python, выполнив команду chmod через тот же SSH-объект:


# Пример смены прав доступа на папку перед копированием
ssh.exec_command('chmod 777 /usr/etc/geofiles/')

# Или смена прав на конкретный файл после его загрузки
ssh.exec_command('chmod 644 /usr/etc/geofiles/Work.dbf')

Также в библиотеке paramiko предусмотрен встроенный метод для работы с правами через SFTP-объект: ftp.chmod(remotepath, 0o755). Использование восьмеричной системы счисления (префикс 0o) позволяет задавать права в привычном для Linux формате.

Установка библиотек в закрытом контуре (без интернета)

Разберем ситуацию, когда целевая Windows-машина, с которой должен работать скрипт, не имеет доступа к интернету. Просто скопировать папку site-packages — это рабочий, но не самый надежный метод, так как многие библиотеки (например, cryptography, используемая в paramiko) содержат скомпилированные C-компоненты.

Правильный алгоритм установки в изолированной среде выглядит следующим образом:

  1. На компьютере с доступом в интернет скачайте библиотеку со всеми зависимостями в виде wheel-файлов: pip download paramiko -d C:\packages
  2. Перенесите папку C:\packages на целевую машину.
  3. Выполните установку локально: pip install --no-index --find-links=C:\packages paramiko

Это гарантирует, что все бинарные зависимости будут установлены корректно и скрипт не «упадет» из-за отсутствия какого-либо системного модуля.

Использование надстройки SCP для упрощения кода

Если вам кажется работа с open_sftp() излишне громоздкой, проанализируем библиотеку scp. Это высокоуровневая обертка над paramiko, которая имитирует поведение стандартной консольной утилиты SCP. Она работает быстрее SFTP при передаче больших объемов данных.


from paramiko import SSHClient, AutoAddPolicy
from scp import SCPClient

ssh = SSHClient()
ssh.load_system_host_keys()
ssh.set_missing_host_key_policy(AutoAddPolicy())
ssh.connect('192.168.1.100', username='user', password='password')

# Используем контекстный менеджер для автоматического закрытия
with SCPClient(ssh.get_transport()) as scp:
    scp.put('local_file.txt', '/remote/path/file.txt')

Такой подход делает код чище и избавляет от необходимости вручную закрывать сессии передачи данных.

Важные аспекты: переносы строк и проверка целостности

При переносе текстовых файлов (например, скриптов .sh или конфигураций) из Windows в Linux помните о разнице в кодировках и символах окончания строк. Windows использует CRLF (\r\n), а Linux — LF (\n). Если вы скопируете скрипт «как есть», он может выдать ошибку при запуске в Linux.

Рекомендуется перед отправкой нормализовать строки:


with open('script.sh', 'r') as f:
    content = f.read().replace('\r\n', '\n')
with open('script_linux.sh', 'w', newline='\n') as f:
    f.write(content)
# Теперь отправляем script_linux.sh

Для критически важных файлов обязательно проверяйте целостность данных после передачи. Проанализируем, как сравнить контрольные суммы:


import hashlib

def get_md5(filename):
    hash_md5 = hashlib.md5()
    with open(filename, "rb") as f:
        for chunk in iter(lambda: f.read(4096), b""):
            hash_md5.update(chunk)
    return hash_md5.hexdigest()

# На Linux проверяем через SSH
stdin, stdout, stderr = ssh.exec_command('md5sum /usr/etc/geofiles/Work.dbf')
remote_md5 = stdout.read().decode().split()[0]

if get_md5('Work.dbf') == remote_md5:
    print("Файл передан без ошибок")
else:
    print("Ошибка целостности данных!")

Используя предложенные методы, вы сможете построить отказоустойчивую систему автоматизированного обмена файлами между гетерогенными системами, учитывая вопросы безопасности, прав доступа и специфики сетевых настроек заказчика.

← На главную