Home > other >  Linux shell command isn't executed as expected
Linux shell command isn't executed as expected

Time:10-23

I'm need to launch headless LibreOffice on a web server which processes Calc spreadsheet from template. I tried to do this straight in Python with the command below.

# 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)

However, when I launch Shell script from Python, everything is OK.

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

Shell script:

#!/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

What is wrong with my Python code? Thanks in advance.

CodePudding user response:

TL;DR

Do one of the following:

  1. Drop the shell=True (use shell=False)
  2. Put the entire command line into a single string and use it instead of the array: subprocess.Popen('libreoffice --calc ...', shell=True). Make sure to escape the command properly.
  3. Put the entire command line into the first and the only element of the args. Make sure to escape the command properly.
  4. Modify the first argument so that it accepts the positional arguments, e.g.: 'libreoffice "$@"', where "$@" expands to quoted list of positional arguments.

The docs for subprocess.Popen state:

On POSIX with shell=True, the shell defaults to /bin/sh. If args is a string, the string specifies the command to execute through the shell. This means that the string must be formatted exactly as it would be when typed at the shell prompt. This includes, for example, quoting or backslash escaping filenames with spaces in them. If args is a sequence, the first item specifies the command string, and any additional items will be treated as additional arguments to the shell itself. That is to say, Popen does the equivalent of:

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

Therefore, the following code:

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

is equivalent to the following shell command on POSIX:

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

which actually runs libreoffice script and passes the rest of the arguments (--calc, --headless etc.) to this "script" as $0 (--calc), $1 (--headless) etc. But the positional arguments are never used in the script, so the code actually runs just libreoffice command.

Refer to the man page for sh (man sh):

-c If the -c option is present, then commands are read from the first non-option argument command_string. If there are arguments after the command_string, the first argument is assigned to $0 and any remaining arguments are assigned to the positional parameters. The assignment to $0 sets the name of the shell, which is used in warning and error messages.

CodePudding user response:

Many thanks everyone for help! This did the trick.

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