process = subprocess.run(cmd, stdout=subprocess.PIPE, stderr=subprocess.PIPE)
For the above subprocess the cmd is:
cmd = ['/usr/bin/mkvmerge', '-o', 'temp', '--audio-tracks', '4', '/home/test/Wingwommen (2023)/Wingwomen (2023).mkv', '--default-track', '4:1']
The subprocess runs with the expected output.
Im my Tkinter code I first have the cmd displayed in a Tkinter entry widget. If I get the cmd displayed in the entry widget and use that for the subprocess command I get the following error:
Exception in Tkinter callback
Traceback (most recent call last):
File "/usr/lib/python3.10/tkinter/__init__.py", line 1921, in __call__
return self.func(*args)
File "/home/pi/scripts/tkinter/AUDIO/REMOVE_AUDIO.py", line 258, in rem_audio
process = subprocess.run(cmd, stdout=subprocess.PIPE, stderr=subprocess.PIPE)
File "/usr/lib/python3.10/subprocess.py", line 503, in run
with Popen(*popenargs, **kwargs) as process:
File "/usr/lib/python3.10/subprocess.py", line 971, in __init__
self._execute_child(args, executable, preexec_fn, close_fds,
File "/usr/lib/python3.10/subprocess.py", line 1863, in _execute_child
raise child_exception_type(errno_num, err_msg, err_filename)
FileNotFoundError: [Errno 2] No such file or directory: "['/usr/bin/mkvmerge', '-o', 'temp', '--audio-tracks', '4', '/home/pi/Torrents/test/Wingwommen (2023)/Wingwomen (2023).mkv', '--default-track', '4:1']"
I can't figure out why the first cmd runs but the one that the code gets from the entry box fails even though the two commands appear to be exactly the same.
Tkinter .get
is retrieving the value you see as a string, not as a list of strings. That can be seen by looking at the printed error message you pasted there:
FileNotFoundError: [Errno 2] No such file or directory: "['/usr/bin/mkvmerge', '-o', 'temp', '--audio-tracks', '4', '/home/pi/Torrents/test/Wingwommen (2023)/Wingwomen (2023).mkv', '--default-track', '4:1']"
See the "
around whatever is not found as a "file or directory"? As it is a string, subprocess.popen will try to run an executable file by that name in the file system. not /usr/bin/mkvmerge
.
If you really want to edit the list form in the tkinter application, you have to parse that string into a Python list - a way to do that which won't trigger in-Python code execution is to use ast.literal_eval
import ast
...
def ____():
cmd_str = widget.get()
cmd=ast.literal_eval(cmd_str)
process = subprocess.run(cmd, stdout=subprocess.PIPE, stderr=subprocess.PIPE)
...
However - I don't see the point of having a GUI to edit a command line and have to take care of the extra quotes, commas and brackets can be non intuitive maybe you should really just allow the "shell" one-string form to be edited in the GUI for oncce.
I.E., instead of presenting the list-form of the subcommand in the tkinter GUI, just present it as: "/usr/bin/mkvmerge -o temp --audio-tracks 4 '/home/test/Wingwommen (2023)/Wingwomen (2023).mkv' --default-track 4:1"
- and let the shell process the single-quotes around the filepath. (over time you may want to imporve your GUI so that the parameters get each their own control - and you could use a tkinter.filedialog.askopenfilename
call (or similar) to allow the user to pick a file instead
This answer assumes you are creating a small app for personal use which you will be using in a computer under your control: Since what you are doing is literally executing an arbitrary command on the computer, with the permissions given to the running app - so, if this would ever receive remote input from anonymous users (via remote app running, or publishing it to the web, for example) - the server (Where the app is executed) would already be compromised.
This note may feel strange, but the more time one keeps in the field, the more natural should these concerns arise: always keep in mind any security concerns.