【问题标题】:Using 'dpkg' in Python causes OSError: [Errno 9] Bad file descriptor在 Python 中使用 'dpkg' 会导致 OSError: [Errno 9] Bad file descriptor
【发布时间】:2019-02-21 21:09:38
【问题描述】:

我构建了一个脚本来查找最新版本的 Visual Studio Code,下载并使用 dpkg 将其安装在 Ubuntu 机器上。我还没有找到一个像样的 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

【问题讨论】:

  • 您正在混合正斜杠/反斜杠。
  • 好吧,执行实际执行的 Python 代码看起来没问题。我假设错误来自正确(从 Python 的角度)执行“dpkg”命令(很可能是由于您的文件路径存在问题,正如@David 所说)。我建议做两件事:1)打印'command'的值,这样你就可以准确地看到你正在尝试运行的内容,以及2)尝试在不涉及Python的情况下运行相同的命令。注意:我没有花时间研究您的代码。这些建议正是我遇到这种涉及 subprocess.xxx 的案例时所做的事情

标签: python-3.x visual-studio-code subprocess vscode-extensions dpkg


【解决方案1】:

os.path.expanduser('~') 将返回类似:'C:\\Users\\user.name' 的内容,您将在其中附加 '/Downloads',从而导致错误路径,例如:'C:\\Users\\user.name/Downloads\\'

代替:

filePath = HOME + "/Downloads"

做:

filePath = HOME + "\Downloads"

或者最好:

filePath = os.path.join(HOME, 'Downloads')

【讨论】:

  • 另一种选择(我喜欢在任何人使用os 时推荐)是使用pathlib.PathHOME = pathlib.Path.home()file_path = HOME / "Downloads".
  • 很好的答案 - 我将把我提出的想法插入 cmets 以便 OP 可以受益。如果你打印了你正在执行的内容,你会立即看到这个问题。它是您工具箱中非常强大的工具。
  • 在 Linux 机器上使用正斜杠和反斜杠有区别吗?我不是为 Windows 写的。
  • 在我的特定机器上,os.path.expanduser 在我的 ubuntu 系统上给出“/home/bcuser”。
  • 绝对重要!如果您不是为 Windows 编程,请远离路径中的反斜杠。 - 最好的答案,更乏味但更正确(我应该做更多,但我没有)是使用 path.join() 和其他支持函数,这样你的代码中就没有正斜杠或反斜杠(我看到你已经在做一些了)。这样的代码也可以在 Windows 上运行。 (当然,谁在乎Windoze,哈哈)
【解决方案2】:

在与@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.")  

【讨论】:

    猜你喜欢
    • 2011-12-02
    • 2021-12-05
    • 2018-12-21
    • 2017-07-29
    • 2020-02-25
    • 2016-04-15
    • 1970-01-01
    • 2019-08-19
    • 2023-01-24
    相关资源
    最近更新 更多