monamona
monamona

Reputation: 1246

Difference between calling a bash script from the commandline and executing it via python

I'm currently working on automating some processes via Django. For this purpose I have a shell script, that performs certain tasks for me. My webinterface has a "Start Process"-Button and a "Stop Process"-Button.

I came up with the idea to use the process id of the shell script to kill the process when I want to abort it. My start.sh looks like this:

pid=$$; 
source ./stop.sh || true
echo $pid > "./processid.txt"; 
#do the actual stuff

And my stop.sh looks like this:

pid=$(head -n 1 ./processid.txt)
kill $pid

What this does, it takes the process id and temporarily stores it in a variable, then stops the script, if before the script had be running, and then saves the process id to the processid.txt file. My stop.sh just takes this id out of the file and kills the process.

This does work from the commandline... however it doesn't when I execute it from my python code. I call it like this:

def start_bot():
    popencommand = "sh -c \"cd ~/thetargetfolder && ./start.sh\"" 

    Popen(popencommand, shell=True)

def stop_bot():
    popencommand = "sh -c \"cd ~/thetargetfolder && ./stop.sh\""

    Popen(popencommand, shell=True)

For some reason, this doesn't seem to work. I suspect it has something to do with that the process id that I get in the start.sh is not the correct one when calling the python script...

Upvotes: 2

Views: 514

Answers (1)

Charles Duffy
Charles Duffy

Reputation: 295403

When you use subprocess.Popen("sh -c 'cd /somewhere && ./whatever'", shell=True), you're telling Python to kick off a chain of events that runs three processes:

  • First, because shell=True makes Python run sh -c '...yourcode...', it starts a first copy of sh requested by the shell=True
  • That first copy of sh runs the code sh -c 'cd /somewhere && ./whatever', because that's the string you gave Python, so it then starts a second copy of sh.
  • That second copy of sh runs cd /somewhere && ./whatever. If ./whatever is a shell script, that means it starts a third shell.

This is a lot of unnecessary complexity! There's no reason to have more than one shell -- the one that runs ./whatever, invoked by the operating system based on its shebang (#!/bin/sh, or #!/usr/bin/env bash, or so forth).


Some of this you can eliminate by dropping down to only having one shell, the run that runs your scripts. Make sure the scripts have valid shebangs (#!/usr/bin/env bash or similar) and are executable (chmod +x start stop).

def start_bot():
    subprocess.call(
      [os.path.join(os.path.expanduser('~/thetargetfolder'), './start')],
      cwd=os.path.expanduser('~/thetargetfolder')
    )

def stop_bot():
    subprocess.call(
      [os.path.join(os.path.expanduser('~/thetargetfolder'), './stop')],
      cwd=os.path.expanduser('~/thetargetfolder')
    )

Finally, you can do even better by not using a shell for this at all, but just writing your logic directly in Python, or (even better than that!) relying on your operating system's process supervisor (most modern Linux distros ship systemd for the purpose; whereas MacOS ships with launchd). If you define yourprogram.service, then systemctl start yourprogram starts the script running, and systemctl stop yourprogram kills it -- and you can configure it to automatically start on boot, or on a timer, or whenever any program connects to a preopened socket, or otherwise however you like.

Upvotes: 4

Related Questions