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.
Related
I am taking input from user in python code. and as per user's provided input I want to clear previous outputs in terminal.
so is there any function? So I can put it in my code.
I searched on internet but it showing me Shortcut keys from keyboard but I want to clear terminal with help of code.(not want to type terminal )
The commands will be slightly different depending on if you're running Python in Windows or Linux/Mac (or a Unix like terminal while in Windows)
In Windows terminals, the command to clear the screen is cls.
In Max/Linux/Unix, the command is clear.
Windows Powershell appears to
except either cls or clear.
To call the appropriate command from within a script, you need to import the os module and call os.system(cmd). This will return a value of 0 for success, so if you are testing it in the terminal directly you may see an extra 0 get displayed on the screen if you don't put the return value into a variable.
Here's some code to call the correct function based on your os. os.name will return 'nt' for Windows or 'posix' for Mac/Linux
import os
# define our clear function
def clear():
# for Windows
if os.name == 'nt':
_ = os.system('cls')
# Mac or Linux (aka posix)
else:
_ = os.system('clear')
Calling the clear() function in your script should clear the terminal as needed.
You can clear the terminal with:
print('\x1b[H\x1b[2J', end='')
...or...
from sys import stdout
stdout.write('\x1b[H\x1b[2J')
...which is probably more efficient than os.system(...)
The string is actually 2 CSI sequences. The first moves the cursor to the screen origin (1, 1). The second sequence is the clear screen directive
I have written a python program that needs a first command line argument to run from the Terminal. The program can be used to copy a text to the clipboard when it is run with a certain keyword.
~ python3 mclip.py 'agree'
This use case is just an exercise to understand, how I can run a batch file on macOS (or shell script in macOS terminology).
I have created the following shell script and saved it as mclip.command:
#!/usr/bin/env bash
python3 /Users/Andrea_5K/mclip.py
My idea is to execute my shell script from the spotlight input window, passing the argument 'agree'. How can I do that?
On windows the batch file would look like that (mclip.bat):
#py.exe C:\path_to_my_file\mclip.py %*
#pause
I can press WIN-R and type mclip *argument* to run the program. But how can I do the same on a Mac? I cannot type mclip agree in spotlight, that doesn't work like in WIN-R.
#! python3
# mclip.py - A multi-clipboard program.
TEXT = {
'agree': """Yes, I agree. That sounds fine to me.""",
'busy': """Sorry, can we do this later this week or next week?""",
'upsell': """Would you consider making this a monthly donation?""",
}
import sys, pyperclip
if len(sys.argv) < 2:
print('Usage: python mclip.py [keyphrase] - copy phrase text')
sys.exit()
keyphrase = sys.argv[1] # first command line arg is the keyphrase
if keyphrase in TEXT:
pyperclip.copy(TEXT[keyphrase])
print('Text for ' + keyphrase + ' copied to clipboard.')
else:
print('There is no text for ' + keyphrase)
I can get Spotlight to run a script which:
offers you a dialog box with your three options and
then runs your Python script passing the selected option
But I cannot get Spotlight to pass an option to a Python script directly. If that helps, here's how to do it.
Start Script Editor and enter the following code, save it as an app called mclip:
set theArg to choose from list {"Agree", "Busy", "Upsell"} with title "Chooser Dialog" with prompt "Choose option"
tell application "Terminal"
do shell script "/Users/YOURNAME/mclip.py " & theArg
end tell
Note that adding on run argv at the top still doesn't get you any arguments you add within Spotlight - it just plain doesn't seem to want to pass on any arguments you type in the Spotlight dialog.
Now write a Python script called $HOME/mclip.py:
#!/usr/bin/env python3
import os, sys
# Just write the received parameter into a text file on the Desktop to show how it works
file = os.path.expanduser("~/Desktop/result.txt")
with open(file, 'w') as f:
f.write(sys.argv[1])
And make it executable (just necessary one time) with:
chmod +x $HOME/mclip.py
If you now use Spotlight to run mclip, it will pop up a dialog like this:
You may have to answer security questions the first time you run it - depending on your macOS version.
Note that if all your Python script does is copy some text onto the clipboard, you can do that without Python within the Applescript above using:
set the clipboard to "Some funky text"
Just a detail: python is case sensitive. So, if the keys of the dictionary are lower case, the list values in the apple script ought to be lower case to `:D
Assume the shell script (mapIt.command) is:
#!/usr/bin/env bash
python3 /path/to/my/pythonScript.py $#
The $# is interpreted as a list of command line arguments.
I can run the shell script in the MacOS Terminal like that:
sh mapIt.command Streetname Number City
The command line arguments Streetname Number City are forwarded to the python script.
I'm trying to create a program that takes your highlighted text then inverses the case of it using a python script.
The logic is essentially: get highlighted text into clipboard, do the transformation, then return the text to the clipboard and paste it.
I'm trying to execute this by creating a batch file that runs from an AutoHotKey hotkey, but the thing is the batch file has to run without popping up the cmd window because then it changes the window focus and it can't properly get the highlighted text.
The code works when I run it inside of PyCharm, and I can get the file to run from the hotkey, but the window still pops very briefly thus ruining the process.
The course I'm going through said to put #pyw instead of #py at in the batch file to make it run through python windowless, but still the window pops up very briefly. I can't seem to find a solution that works.
I tried to run it through a .vbs file but that didn't work the way I wanted it to. I checked to make sure pythonw.exe is working, and it is. I tried to change the file name to .pyw in the batch code.
What am I doing wrong? Is it with my python code, or my batch code? I don't know.
My Batch Code:
#pyw "C:\Users\offic\PycharmProjects\test\automate the boring stuff\projects\change case of selected.pyw" %*
My Python Code:
#! python 3
import pyperclip
import pyautogui as pya
import time
import sys
#mystery code I tried to make work as a solution
# import ctypes
# import os
# import pywin32_system32
#
# hwnd = ctypes.windll.kernel32.GetConsoleWindow()
# if hwnd != 0:
# ctypes.windll.user32.ShowWindow(hwnd, 0)
# ctypes.windll.kernel32.CloseHandle(hwnd)
# _, pid = pywin32.GetWindowThreadProcessId(hwnd)
# os.system('taskkill /PID ' + str(pid) + ' /f')
def copy_clipboard():
pyperclip.copy("") # <- This prevents last copy replacing current copy of null.
pya.hotkey('ctrl', 'c')
time.sleep(.01) # ctrl-c is usually very fast but your program may execute faster
return pyperclip.paste()
clipboard = copy_clipboard()
try: #check if there is an argument
# convert clipboard to uppercase if arg is 'u'
if sys.argv[1] is 'u':
pyperclip.copy(clipboard.upper())
# convert clipboard to lowercase if arg is 'l'
elif sys.argv[1] is 'l':
pyperclip.copy(clipboard.lower())
#else just swap the case of every character if the arg is not u or l
else:
pyperclip.copy(clipboard.swapcase())
#if there are no args just swap the case of every charater
except:
# put swapped string to clipboard
pyperclip.copy(clipboard.swapcase())
pya.hotkey('ctrl', 'v')
# hELLO
My AutoHotKey Code (if this somehow matters):
^+q::
Run, "C:\Users\offic\Documents\MY BATCH FILES\swapcase.bat"
return
^+q::
Run, "C:\Users\offic\Documents\MY BATCH FILES\swapcase.bat",, Hide
return
AutoHotkey Run Options: Hide.
It would be the running of swapcase.bat causing the console window to show. Doubt it is pyw running pythonw.exe as both being Graphical User Interfaces (GUI) instead of a Console User Interface (CUI) that cmd.exe is, as it interprets swapcase.bat.
You can run your Python script (with .pyw extension) from AHK:
Run script.pyw, c:\mydir
c:\mydir is the location of the Python script.
This will work if Python is associated with .pyw extension of course.
I've made a script (called isPA64.bat) to determine if the executing system is 64-bit (based on this Bear's Log tip):
#echo off
setlocal
set str1=%PROCESSOR_ARCHITECTURE%
set/A sixty4=0
if not x%str1:64=%==x%str1% set/A sixty4=1
endlocal & exit/B %sixty4%
It gets called from another simple batch, named callpa.bat (it could be called directly, too, but this proves that ERRORLEVEL does, indeed, get set appropriately):
#echo off
ver>nul & (call isPA64.bat & if ERRORLEVEL 1 (echo 64-bit & exit/B 1) else (echo not 64-bit & exit/B 0))
Up to this point, this all works fine; however, I must call one of these two from a Python 3.7.2 program. I do this:
import subprocess
print(subprocess.run(["callpa.bat"]))
Simple enough, right? But I haven't been able to figure out how to get a valid return code back, in the python code... Is there a way to assign a variable in the python code to either the "exit"/return code or to the value of ERRORLEVEL, from the cmd.exe shell which executes the outer-level script? ...I can't find a way in the python doc's to do that.
No need of creating other batch files:
import os
arch = os.environ['PROCESSOR_ARCHITECTURE']
if arch == 'x86':
print("x86")
exit(0)
else:
print("x64")
exit(1)
Then import it be errorlevel variable.
WMI can be also used:
import sys
import win32com.client
wmiservice = win32com.client.dispatch("WbemScripting.SWbemLocator")
swbemservice = wmiservice.ConnectServer(., "root/cimv2")
items = swbemservice.ExecQuery("Select * From Win32_OperatingSystem")
for item in items:
if item.OSArchitecture == 'x86':
print("x86")
exit(0)
else:
print("x64")
exit(1)
You need to download python for windows extensions from http://sourceforge.net/projects/pywin32/files/
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.