I'm trying to run a VBA module that uses a shell script to run an ipython notebook (using runipy) before going on to do other things, but my ipython notebook script requires inputs, which are setup using environment variables.
Here's the VBA I have so far:
rundate = Range("b1").Value
shellScript = "runipy C:\argstest.ipynb"
Dim wsh As Object
Set wsh = VBA.CreateObject("WScript.Shell")
Dim waitOnReturn As Boolean: waitOnReturn = True
Dim windowStyle As Integer: windowStyle = 1
Set wshSystemEnv = wsh.Environment("SYSTEM")
wshSystemEnv("CURRWK") = rundate
If I then run:
MsgBox (wshSystemEnv("CURRWK"))
The result is correct:
2015-04-17
However, if I continue on and exec my shellscript, it isn't recognizing this variable
Set WshShellExec = wsh.exec(shellScript)
Range("a1").Value = WshShellExec.StdErr.ReadAll
shows me that when python runs it gets a key error:
KeyError: 'CURRWK'
If I remove the VBA and run this straight from the command line, I would do it like this:
set CURRWK=2015-04-17
runipy C:\Python27\Programs\ipython\ACT\argstest.ipynb
How do I get the shell created in the VBA to create the new environment variable so the python script can see it when it executes?
Thanks!
Have you tried Process instead of SYSTEM? Your VBA may not have the rights to modify the system-level variables. ("But it can read it back after setting it," you say. True, but your script may have registry virtualization enabled.) Process most closely emulates the command line SET statement in that the change is only for the current process and child processes and is not persisted.
Related
I have file called . /home/test.sh (the space between the first . and / is intentional) which contains some environmental variables. I need to load this file and run the .py. If I run the command manually first on the Linux server and then run python script it generates the required output. However, I want to call . /home/test.sh from within python to load the profile and run rest of the code. If this profile is not loaded python scripts runs and gives 0 as an output.
The call
subprocess.call('. /home/test.sh',shell=True)
runs fine but the profile is not loaded on the Linux terminal to execute python code and give the desired output.
Can someone help?
Environment variables are not inherited directly by the parent process, which is why your simple approach does not work.
If you are trying to pick up environment variables that have been set in your test.sh, then one thing you could do instead is to use env in a sub-shell to write them to stdout after sourcing the script, and then in Python you can parse these and set them locally.
The code below will work provided that test.sh does not write any output itself. (If it does, then what you could do to work around it would be to echo some separator string afterward sourcing it, and before running the env, and then in the Python code, strip off the separator string and everything before it.)
import subprocess
import os
p = subprocess.Popen(". /home/test.sh; env -0", shell=True,
stdout=subprocess.PIPE,
stderr=subprocess.PIPE)
out, _ = p.communicate()
for varspec in out.decode().split("\x00")[:-1]:
pos = varspec.index("=")
name = varspec[:pos]
value = varspec[pos + 1:]
os.environ[name] = value
# just to test whether it works - output of the following should include
# the variables that were set
os.system("env")
It is also worth considering that if all that you want to do is set some environment variables every time before you run any python code, then one option is just to source your test.sh from a shell-script wrapper, and not try to set them inside python at all:
#!/bin/sh
. /home/test.sh
exec "/path/to/your/python/script $#"
Then when you want to run the Python code, you run the wrapper instead.
This question already has answers here:
how to "source" file into python script
(8 answers)
Closed 3 years ago.
I am struggling to execute a shell script from a Python program. The actual issue is the script is a load profile script and runs manually as :
. /path/to/file
The program can't be run as sh script as the calling programs are loading some configuration file and so must need to be run as . /path/to/file
Please do guide how can I integrate the same in my Python script? I am using subprocess.Popen command to run the script and as said the only way it works is to run as . /path/to/file and so not giving the right result.
Without knowledge of the precise reason the script needs to be sourced, this is slightly speculative.
The fundamental problem is this: How do I get a source command to take effect outside the shell script?
Let's say your sourced file does something like
export fnord="value"
This cannot (usefully) be run in a subshell (as a normally executed script would) because the environment variable and its value will be lost when the script terminates. The solution is to source (aka .) this snippet from an already running shell; then the value stays in that shell's environment until that shell terminates.
But Python is not a shell, and there is no general way for Python to execute arbitrary shell script code, short of reimplementing the shell in Python. You can reimplement a small subset of the shell's functionality with something like
with open('/path/to/file') as shell_source:
lines = shell_source.readlines()
for line in lines:
if line.strip().startswith('export '):
var, value = line[7:].strip().split('=', 1)
if value.startswith('"'):
value = value.strip('"')
elif value.startswith("'"):
value = value.strip("'")
os.environ[var] = value
with some very strict restrictions (let's not say naïve assumptions) on the allowable shell script syntax in the file. But what if the file contained something else than a series of variable assignments, or the assignment used something other than trivial quoted strings in the values? (Even the export might or might not be there. Its significance is to make the variable visible to subprocesses of the current shell; maybe that is not wanted or required? Also export variable=value is not portable; proper Bourne shell script syntax would use variable=value; export variable or one of the many variations.)
If you know what exactly your Python script needs from the shell script, maybe do something like
r = subprocess.run('. /path/to/file; printf "%s\n" "$somevariable"',
shell=True, capture_output=True, text=True)
os.environ['somevariable'] = r.stdout.split('\n')[-2]
to source the entire script in a subshell, then print to standard output the part you actually need, and capture that from your Python script (and assign it to an environment variable if that's what you eventually need to accomplish).
EDIT: I made an error when posting below in saying that it worked when running from terminal (I must have tested earlier.) This problem was solved by using the python.exe program in the environment (env) folder pycharm installed instead of the stand alone installation I made originally.
EDIT #2: The problem is persisting again without having changed any of the VBA or python script. (still using the pycharm environment folder python.exe)
I have a VBA sub that creates a WScript.Shell object and then executes a python script which was working fine for the last few weeks. After continuing to build on the code in the python script (salesHist.py) the python script no longer runs correctly and produces an exit code "1". When running the script through pyCharm or console the script fully executes correctly with exit code 0.
I've tried some different variations of the code which are included below. Directories for python.exe and salesHist.py are both correct and do not contain any spaces (which I know is a common error.)
Also references added:
Visual Basic for Applications,
Microsoft Excel 16.0 Object Library,
OLE Automation,
Microsoft Office 16.0 Object Library,
Microsoft HTML Object Library,
Microsoft Scripting Runtime,
Microsoft XML, v3.0,
Windows Script Host Object Model
Sub RunPythonScript(pyScript As String)
'Declare varables
Dim objShell As Object
Dim PythonExe, PythonScript As String
Dim waitOnReturn As Boolean, windowStyle As Integer, retVal As Long
waitOnReturn = True
windowStyle = 0
'Create new object shell
Set objShell = VBA.CreateObject("WScript.Shell")
'Tried Set objShell CreateObject("WScript.Shell")
'Provide the file path to the Python Exe
PythonExe = "C:\Users\steve.levy\AppData\Local\Programs\Python\Python37-32\python.exe"
'PC1: C:\Users\Steven\AppData\Local\Programs\Python\Python37-32\python.exe
'PC2: C:\Users\steve.levy\AppData\Local\Programs\Python\Python37-32\python.exe
'make sure you use triple quotes if there is a space in the file path name. single quotes are ok if not
'Procide the file path to the Python Script
PythonScript = "C:\Users\steve.levy\Documents\elberon\api\" & pyScript
'PC1: C:\Users\Steven\Documents\api\
'PC2: C:\Users\steve.levy\Documents\elberon\api\
Debug.Print (PythonExe)
Debug.Print (PythonScript)
'Run the Python Script
'Tried: Call objShell.Run(PythonExe & " " & PythonScript, 0, True)
'Tried: retVal = objShell.Run(PythonExe & " " & PythonScript, 0, True)
retVal = objShell.Run("C:\Users\steve.levy\AppData\Local\Programs\Python\Python37-32\python.exe C:\Users\steve.levy\Documents\elberon\api\salesHist.py", 0, True)
If retVal = 0 Then
'Do Nothing
Else
MsgBox "Script could not run. Program exited with error code " & retVal & "."
End If
End Sub
Sub pyscr()
Call executePython.RunPythonScript("salesHist.py")
End Sub
Program exists and message box appears:
"Script could not run. Program exited with error code 1."
So I am creating an application that can connect printers with a Python GUI that runs PowerShell scripts in the background. I was wondering if there was a way I could pass a variable inputted from a Python widget into a PowerShell script that is being invoked by Python. This variable would be the name of the printer that I could specify in Python so that I do not have to create separate scripts for each printer.
My code in Python that calls upon the PS script:
def connect():
if self.printerOpts.get() == 'Chosen Printer':
subprocess.call(["C:\\WINDOWS\\system32\\WindowsPowerShell\\v1.0\\powershell.exe",'-ExecutionPolicy','Unrestricted', '.\'./ScriptName\';'])
PS script that connects printer to computer:
Add-Printer -ConnectionName \\server\printer -AsJob
Basically, I am wondering if I can pass a variable from Python into the "printer" part of my PS script so that I do not have to create a different script for each printer that I would like to add.
A better way to do this would be completely in PowerShell or complete in Python.
What you're after is doable. You can pass it in the same way that you have passed -ExecutionPolicy Unrestricted, by ensuring that the PowerShell script is expecting the variable.
My Python is non-existant so please bear with if that part doesn't work.
Python
myPrinter # string variable in Python with printer name
subprocess.call(["C:\\WINDOWS\\system32\\WindowsPowerShell\\v1.0\\powershell.exe",'-ExecutionPolicy','Unrestricted', '.\'./ScriptName\';','-printer',myPrinter])
PowerShell
param(
$printer
)
Add-Printer -ConnectionName \\server\$printer -AsJob
The way that worked for me was first to specify that I was passing a variable as a string in my PS script:
param([string]$path)
Add-Printer -ConnectionName \\server\$path
My PS script was not expecting this variable. In my Python script I had to first define the my variable which named path as a string and then input "path" into the end of my subprocess function.
path = "c"
subprocess.call(["C:\\WINDOWS\\system32\\WindowsPowerShell\\v1.0\\powershell.exe",'-ExecutionPolicy','Unrestricted', 'Script.ps1', path])
I am trying to set a environmental variable permanently. but temporarily it is working.
if i run below program i got the variable path. after close it and open new terminal to find the variable path using the command printenv LD_LIBRARY_PATH nothing will print.
#!/usr/bin/python
import os
import subprocess
def setenv_var():
env_var = "LD_LIBRARY_PATH"
env_path = "/usr/local/lib"`enter code here`
os.environ[env_var] = env_path
process = subprocess.Popen('printenv ' + env_var, stdout=subprocess.PIPE, shell=True)
result = process.communicate()[0]
return result
if __name__ == '__main__':
print setenv_var()
please help me.
Here is what I use to set environment variables:
def setenv_var(env_file, set_this_env=True):
env_var = "LD_LIBRARY_PATH"
env_path = "/usr/local/lib"`enter code here`
# set environments opened later by appending to `source`-d file
with open(env_file, 'a') as f:
f.write(os.linesep + ("%s=%s" % (env_var, env_path)))
if set_this_end:
# set this environment
os.environ[env_var] = env_path
Now you only have to choose where to set it, that is the first argument in the function. I recommend the profile-specific file ~/.profile or if you're using bash which is pretty common ~/.bashrc
You can also set it globally by using a file like /etc/environment but you'll need to have permissions when you run this script (sudo python script.py).
Remember that environments are inherited from the parent process, and you can't have a child set up a parent process' environment.
When you set an environment variable, it only affects the currently running process (and, by extension, any children that are forked after the variable is set). If you are attempting to set an environment variable in your shell and you want that environment variable to always be set for your interactive shells, you need to set it in the startup scripts (eg .login, .bashrc, .profile) for your shell. Commands that you run are (initially) children of the shell from which you run them, so although they inherit the environment of the shell and can change their own environment, they cannot change the environment of your shell.
Whether you do an export from bash or you set your os.environ from Python, these only stay for the session or process's lifetime. If you want to set them permanent you will have to touch and add it to the respective shell's profile file.
For ex. If you are on bash, you could do:
with open("~/.bashrc", "a") as outfile: # 'a' stands for "append"
outfile.write("export LD_LIBRARY_PATH=/usr/local/lib")
Check this out for some insight as to which file to add this depending on the target shell. https://unix.stackexchange.com/questions/117467/how-to-permanently-set-environmental-variables