【问题标题】:Linux shell command isn't executed as expectedLinux shell 命令未按预期执行
【发布时间】:2021-12-07 09:06:44
【问题描述】:

我需要在从模板处理 Calc 电子表格的 Web 服务器上启动无头 LibreOffice。我尝试使用以下命令直接在 Python 中执行此操作。

# Doesn't work as expected, no socket connection, "--accept ..." is ignored.
subprocess.Popen(['libreoffice', '--calc','--headless', 
'--accept="socket,host=localhost,port=2002;urp;"','&'], shell=True)

但是,当我从 Python 启动 Shell 脚本时,一切正常。

subprocess.call('/var/www/scripts/service_report_ods_prepare.sh', shell=True)

Shell 脚本:

#!/bin/bash

app="soffice"

app_pid=`ps -ef | grep "$app" | awk '{print $2}'`

if `ps -p $app_pid > /dev/null`; then
    echo "An instance of LibreOffice is running, pid = "$app_pid
    echo "Not starting another instance."
else
    libreoffice --headless --accept="socket,host=localhost,port=2002;urp;"& > /dev/null 2>&1
fi

我的 Python 代码有什么问题? 提前致谢。

【问题讨论】:

  • 你为什么在这里使用shell=True? (好吧,& 是显而易见的原因,但是您不需要 使用 & 将某些内容与 subprocess.Popen 放在后台;它从不一个隐含的wait(),因此默认情况下所有内容都已隐含在后台,只有当您使用subprocess.call() 或调用wait()communicate() 等时才会更改为前台)
  • & 不是参数;它是 shell 命令的 终止符

标签: python shell subprocess


【解决方案1】:

TL;DR

执行以下一项

  1. 删除shell=True(使用shell=False
  2. 将整个命令行放入单个字符串并使用它代替数组:subprocess.Popen('libreoffice --calc ...', shell=True)。确保正确转义命令。
  3. 将整个命令行放入args 的第一个也是唯一的元素。确保正确转义命令。
  4. 修改第一个参数,使其接受位置参数,例如:'libreoffice "$@"',其中"$@" 扩展为引用的位置参数列表。

The docs for subprocess.Popen 状态:

在带有shell=True 的POSIX 上,shell 默认为/bin/sh。如果args 是字符串,则该字符串指定要通过shell 执行的命令。这意味着字符串的格式必须与在 shell 提示符下键入时的格式完全相同。这包括,例如,引用或反斜杠转义文件名,其中包含空格。 如果args 是一个序列,则第一项指定命令字符串,任何附加项都将被视为shell 本身的附加参数。也就是说,Popen 相当于:

Popen(['/bin/sh', '-c', args[0], args[1], ...])

因此,如下代码:

subprocess.Popen(['libreoffice', '--calc','--headless',
'--accept="socket,host=localhost,port=2002;urp;"','&'], shell=True)

等价于 POSIX 上的以下 shell 命令:

/bin/sh -c libreoffice --calc --headless --accept="socket,host=localhost,port=2002;urp;" &

实际上运行libreoffice 脚本并将其余参数(--calc--headless 等)作为$0--calc)传递给这个“脚本”, $1 (--headless) 等。但位置参数从未在脚本中使用,因此代码实际上只运行 libreoffice 命令。

请参阅sh (man sh) 的手册页:

-c 如果存在 -c 选项,则从第一个非选项参数 command_string 中读取命令。如果在 command_string 之后有参数,则将第一个参数分配给 $0,并将任何剩余的参数分配给位置参数。对 $0 的赋值设置了 shell 的名称,用于警告和错误消息。

【讨论】:

    【解决方案2】:

    非常感谢大家的帮助! 这成功了。

    subprocess.Popen(['libreoffice', '--calc', '--headless', 
        '--norestore', '--nofirststartwizard', '--nologo', 
        '--accept="socket,host=localhost,port=2002;urp;"'], shell=False)
    

    【讨论】: