Использование 'dpkg' в Python вызывает OSError: [Errno 9] Плохой дескриптор файла

1

Я создал скрипт, чтобы найти самую последнюю версию кода Visual Studio, загрузить ее и установить на компьютере с Ubuntu с помощью dpkg. Я не нашел подходящей библиотеки Python для этого и использую subprocess.call() для вызова команды Shell. Это, конечно, не лучший способ сделать это, но это также учебный проект.

Он успешно загружает файл и помещает его в мой каталог ~/Downloads. Когда я пытаюсь вызвать subprocess.call(), он выдает "OSError: [Errno 9] Bad file descriptor"

Я знаю, что моя командная строка верна. Я могу просто вызвать его из CLI. Но он не работает при вызове через подпроцесс.

Любые советы о том, как сделать это более эффективно, приветствуются.

"""
Python 3 script
Downloads the latest .deb package for installing VSCode, and installs it 
"""
import os               # used to direct where to save downloaded file
import subprocess       # used to derive filepath of CLI arg
import requests         # py3 only
import platform         # used to detect the OS
from urllib.request import urlopen, ContentTooShortError, urlretrieve # py3 version of 'import urllib2'

HOME = os.path.expanduser('~')
filePath = HOME + "/Downloads"
fileName = 'vs_code_most_recent_amd64.deb'
outputName = os.path.join(filePath, fileName)
alreadyDownloaded = False

# used in subprocess calls to suppress stdout or stderr
pipeToDevNull = open(os.devnull, 'w')

def IsDownloadable(url):
    """
    Check of the link passed in is a downloadable file. Used to shortcut the 
    processing so that it doesn't attempt to download a URL that isn't 
    downloadable.  Returns True or False.
    """
    h = requests.head(url, allow_redirects=True)
    header = h.headers
    contentType = header.get('content-type')
    if 'text' in contentType.lower():
        return False
    if 'html' in contentType.lower():
        return False
    return True

def DownloadVSCodePkg(url):
    """
    Downloads the file at the specified URL, save it as the above-defined filename
    """
    u = urlopen(url)

    f = open(outputName, 'wb')
    meta = u.info()

    fileSize = int(meta.get_all("Content-Length")[0])    

    fileSizeDL = 0
    #blockSize = 8192
    blockSize = 16384

    while True:
        buffer = u.read(blockSize)
        if not buffer:
            break
        fileSizeDL += len(buffer)
        f.write(buffer)
        status = r"%10d Bytes [%3.2f%%]" % (fileSizeDL, fileSizeDL * 100. / fileSize)
        status = status + chr(8)*(len(status)+1)
        print("Downloading: {0}".format(status), end="\r", flush=True)
    print("Downloading: {0}".format(status))
    print("Downloaded: {0}".format(fileName))
    f.close()
    del f


def CheckDownloadSuccess():
    """
    returns bool value if the file we want is in the dir specified
    """
    try:
        subprocess.check_call("ls " + outputName, stdout=pipeToDevNull, stderr=pipeToDevNull, shell=True)
        return True
    except subprocess.CalledProcessError:
        return False

def UnpackAndInstall():
    """
    Invokes dpkg from the linux shell and installs VSCode.
    """
    #Detect OS
    linuxDistro = platform.linux_distribution()
    OSType = linuxDistro[0]

    if OSType == 'Ubuntu':
        from apt.debfile import DebPackage
        pkg = DebPackage(outputName)
        command = 'sudo dpkg -i ' + outputName

        #The thing that attempts to unpack:
        try:
            subprocess.check_call(command, stdout=subprocess.STDOUT, stderr=subprocess.STDOUT, shell=True)
        except subprocess.CalledProcessError:
            print("Install Failed.")

def main():
    url = 'https://go.microsoft.com/fwlink/?LinkID=760868'

    alreadyDownloaded = CheckDownloadSuccess()

    if alreadyDownloaded is False:
        if IsDownloadable(url):
            DownloadVSCodePkg(url)            
            # check if the download succeeded, if file doesn't already exist.
            if CheckDownloadSuccess():
                print("Download Successful!\nFile location => " + outputName)
            else:
                print("Download Failed...")

        else:
            print('Link broken: need to update the package resource link.')
    else:
        print("File already exists.")

    UnpackAndInstall()

if __name__ == "__main__":
    main()

Вот трассировка и ошибка от CLI:

$ python3 setupVSCode.py 

Traceback (most recent call last):
  File "setupVSCode.py", line 192, in <module>
    main()
  File "setupVSCode.py", line 189, in main
    UnpackAndInstall()
  File "setupVSCode.py", line 95, in UnpackAndInstall
    subprocess.call(command, stdout=subprocess.STDOUT, stderr=subprocess.STDOUT, shell=True)
  File "/usr/lib/python3.6/subprocess.py", line 267, in call
    with Popen(*popenargs, **kwargs) as p:
  File "/usr/lib/python3.6/subprocess.py", line 709, in __init__
    restore_signals, start_new_session)
  File "/usr/lib/python3.6/subprocess.py", line 1344, in _execute_child
    raise child_exception_type(errno_num, err_msg, err_filename)
OSError: [Errno 9] Bad file descriptor
  • 1
    Вы смешиваете прямые / обратные косые черты.
  • 0
    Ну, ваш код Python, который выполняет фактическое исполнение, выглядит нормально. Я предполагаю, что ошибка происходит из-за правильного (с точки зрения Python) выполнения команды 'dpkg' (вполне возможно, из-за проблем с вашим путем к файлу, как говорит @David). Я бы предложил сделать две вещи: 1) вывести значение 'command', чтобы вы точно видели, что вы пытаетесь запустить, и 2) попробовать выполнить ту же команду без участия Python. ПРИМЕЧАНИЕ: я не тратил время на изучение вашего кода. Эти предложения - то, что я делаю всякий раз, когда у меня есть такой случай, связанный с subprocess.xxx
Теги:
visual-studio-code
subprocess
dpkg

2 ответа

2

os.path.expanduser('~') собирается вернуть что-то вроде: 'C:\\Users\\user.name' которое вы 'C:\\Users\\user.name' '/Downloads', что приведет к неправильному пути, например: 'C:\\Users\\user.name/Downloads\\'

Вместо:

filePath = HOME + "/Downloads"

Делать:

filePath = HOME + "\Downloads"

Или предпочтительно:

filePath = os.path.join(HOME, 'Downloads')
  • 0
    Альтернатива (которую я рекомендую, когда кто-либо использует os ) - это использовать pathlib.Path : HOME = pathlib.Path.home() ; file_path = HOME / "Downloads" .
  • 0
    Хороший ответ (и) - я просто добавлю идею, которую я предлагаю, в комментарии, чтобы ФП мог получить пользу. Если вы напечатали то, что выполняете, вы сразу увидите эту проблему. Это очень мощный инструмент в вашем наборе инструментов.
Показать ещё 6 комментариев
1

Поговорив с @Steve, я попытался удалить перенаправители вывода в subprocess.call().

Рекомендации по удалению всех слешей при построении пути и использовании "os.path.join()" были реализованы, и с этого момента мы будем следовать в качестве лучшей практики.

Поскольку созданная команда работала нормально из CLI, нужно было подумать о том, что subprocess.call() делало иначе. Он перенаправил вывод. С этим убираются вещи работают нормально

Теперь это выглядит так:

 HOME = os.path.expanduser('~')
 filePath = os.path.join(HOME, "Downloads")
 fileName = 'vs_code_most_recent_amd64.deb'
 outputName = os.path.join(filePath, fileName)
 alreadyDownloaded = False
 ...
 command = 'sudo dpkg -i ' + outputName

 try:
     subprocess.check_call(command, shell=True)
 except subprocess.CalledProcessError:
     print("Install Failed.")  

Ещё вопросы

Сообщество Overcoder
Наверх
Меню