# interminal
`interminal` is a utility for launching a graphical terminal emulator and
running a command. It has the same behaviour as if you had opened the terminal
and typed the command yourself, including history, respecting shell aliases
etc. It is tested on Linux, possibly could work on macOS, and is not
compatible with Windows.
* [Installation](#installation)
* [Introduction](#introduction)
* [Usage](#usage)
* [Notes](#notes)
* [Sublime extension example](#sublime-extension-example)
## Installation
to install `interminal`, run:
```
$ sudo pip3 install interminal
```
or to install from source:
```
$ sudo python3 setup.py install
```
`interminal` requires the `pexpect` package, which pip should automatically
install if it is not already installed.
## Introduction
`interminal` is mostly useful for things like making an external tool for your
text editor, or making a launcher for a terminal program, or things like that.
For example, you might want to run the command `python3 my_script.py` whenever
you press a certain keyboard shortcut in your text editor whilst you've got
the file `my_script.py` open. It's easy to make an extension to do this in,
for example, Sublime Text 3, but the output will be shown in a panel at the
bottom of Sublime's GUI. Sublime's GUI is not a terminal emulator, it does not
give you the power to re-run the command with different arguments, inspect
output files, etc. Sometimes what you really want is for your favourite
terminal emulator to be a keystroke away.
You'd think that would be easy, right? All terminal emulators have command
line options for telling them what command to run — can't we just use those?
Unfortunately it's not that simple. The following command:
```
$ gnome-terminal -x python3 my_script.py
```
has a few problems. One, it closes the terminal as soon as the command exits,
preventing you from seeing its output. Secondly, the script is not necessarily
run from within a shell (whether or not it does seems to vary by terminal
emulator and the current phase of the moon), meaning any environment variables
you've set in your `.bashrc` or aliases etc may not be available. If you want
your commands to be run in a 'normal' environment, you need to run a shell in
the terminal first, and tell the shell to run your command. Then you have to
start another shell to ensure the terminal emulator stays open:
```
$ gnome-terminal -x bash -c "python3 my_script.py; bash"
```
This is almost good enough. It doesn't add the command to your shell history,
but it's basically what we want. But if you need to escape some characters in
your command, you are two-layers deep in "passing commands to other commands"
— the quoting is going to get out of control fast. And for reasons I can't
grasp, I can't get the above to work with some terminal emulators.
Ideally we want something that will work with the minimum quoting, and that
emulates exactly what would happen if you typed something into a terminal
yourself. That's where `interminal` comes in.
## Usage
To run a command such as `echo hello` in a terminal emulator, run the command:
```
$ interminal echo hello
```
This will launch a new terminal emulator, run the command `echo hello`, and
then leave you at an interactive prompt, with the command `echo hello` in your
shell history, exactly as if you had launched the terminal emulator and typed
it yourself. In fact, `interminal` works by injecting the command into the
input of a pseudoterminal connected to your shell, and so as far as that shell
is concerned, you *did* type it in. In this way, `interminal` can respect
whatever crazy hacks you have decided to apply to your shell — by just typing
things into a terminal the same as a human would.
![alt tag](screenshot.png)
If you have something more complex, you can instead do:
```
$ interminal --script 'du -hs $HOME/* | sort -hr; echo hello'
```
This allows you to include things that will be interpreted by the shell in the
launched terminal emulator (but not by the shell, if any, from which you are
executing the above command).
## Notes
Which terminal emulator to launch is guessed based on the `XDG_DESKTOP`
environment variable — the default terminal emulator for the desktop
environment is used. If you want to use a different one, you can call
interminal like this, using the terminology terminal emulator as an example:
```
$ terminology -e inshell --script 'du -hs $HOME/* | sort -hr; echo hello'
```
`interminal` is actually just a very short script that replaces its own
process with the terminal emulator running the `inshell` script, which is also
part of this package. So you can call `inshell` yourself as above to control
which terminal to launch. If this sounds like it defeats the purpose of this
project, it doesn't — inserting the command into a shell that otherwise
behaves like an ordinary interactive shell is the tricky bit.
If you are running the `interminal` command from a shell, then you must make
sure that its arguments, *after being interpreted by that shell* are what you
want to run in the interactive shell that is started in the terminal emulator.
For example, if you run:
```
$ interminal echo $PATH
```
then `$PATH` will be expanded *prior* to `interminal` being called. This might
be undesirable if, for example, the shell you are running `interminal` from is
dash, and your `$SHELL` environment variable (which is what `interminal`
will run) is /bin/bash. The current shell won't have run your `.bashrc` and so
might not have any additions you have made there to your `$PATH` variable. So
as a general rule, in these cases you should do:
```
$ interminal --script 'echo $PATH'
```
Of course if you want to have single quotes in your command, the required
escape sequences can get unwieldy:
```
$ interminal --script 'echo $PATH > '"'"'my file with spaces'"'"''
```
(this will run `echo $PATH > 'my file with spaces'`)
Hopefully, you are using `interminal` from an environment that lets you pass
in command line arguments that are not interpreted by a shell, but instead
are passed directly to the `exec()` system call, in which case the above
quoting is unnecessary. For example, in Python, this is fine:
```python
import subprocess
subprocess.Popen(["interminal", "--script", "echo $PATH > 'my file with spaces'"])
```
In fact, you can have an arbitrarily long multi-line shell script as that
final argument, it will be executed as-is.
The following is fine too, for the case where you are not using any features
of the shell and just want to run a single command:
```python
import subprocess
subprocess.Popen(["interminal", "python3", "/path/to/my script with spaces.py"])
```
No quoting is neccesary at all in this case.
But if you have to pass something to a shell, and you don't know in advance
what the arguments are, you should try to quote them programatically to ensure
you get the quoting right:
```python
import shlex
import os
# If you have a command using shell features:
args = ["interminal", "--script", "echo $PATH > 'my file with spaces'"]
# or, when not using shell features:
# args = ["interminal", "python3", "/path/to/my script with spaces.py"]
# Create a single, appropriately quoted command from the arguments:
command = ' '.join(shlex.quote(arg) for arg in args)
# Then pass the single command to whatever shell will interpret it:
os.system(command)
```
## Sublime extension example
Here's how I use `interminal` to make Sublime Text 3 run Python. This requires
an extension file to be placed in `~/.config/sublime-text-3/Packages/User/`:
```python
# ~/.config/sublime-text-3/Packages/User/python_interminal.py
import os
import subprocess
import sublime_plugin
class WindowCommand(sublime_plugin.WindowCommand):
@property
def current_file(self):
return os.path.abspath(self.window.active_view().file_name())
@property
def current_file_basename(self):
return os.path.basename(self.current_file)
@property
def current_folder(self):
return os.path.dirname(self.current_file)
def save(self):
self.window.active_view().run_command('save')
class PythonInterminalCommand(WindowCommand):
def run(self, index=None):
self.save()
subprocess.Popen(['interminal', 'python3', '-i', self.current_file_basename],
cwd=self.current_folder)
```
And then it requires an addition to the keybindings file:
```
[
...
{ "keys": ["f5"], "command": "python_interminal" },
...
]
```
Then when you press `F5`:
![alt tag](screenshot_sublime.png)
Note that it doesn't matter what the extension file is called, the keybinding
finds the right command to run based on a naming convention for the class:
`foo_bar_baz` means it will look for a class called `FooBarBazCommand`.
I prefer `python3 -i` but you can of course modify this to whatever you like.
In particular with this command, running commands through an actual shell is
useful for because I have an alias such that `python3` actually calls
`~/anaconda3/bin/python`, a newer version of Python than the system Python. If
I change the alias, I won't have to change my Sublime extension - I only have
to make sure the shell does what I want, and then anything launched via
`interminal` will respect that.
Raw data
{
"_id": null,
"home_page": "https://github.com/chrisjbillington/interminal",
"name": "interminal",
"maintainer": null,
"docs_url": null,
"requires_python": null,
"maintainer_email": null,
"keywords": "logging unix",
"author": "Christopher Billington",
"author_email": "chrisjbillington@gmail.com",
"download_url": "https://files.pythonhosted.org/packages/c2/7d/3011edad36493cce77193f9b2a927868566585c277317622c54165c51dff/interminal-0.4.0.tar.gz",
"platform": null,
"description": "# interminal\n\n`interminal` is a utility for launching a graphical terminal emulator and\nrunning a command. It has the same behaviour as if you had opened the terminal\nand typed the command yourself, including history, respecting shell aliases\netc. It is tested on Linux, possibly could work on macOS, and is not\ncompatible with Windows.\n\n\n * [Installation](#installation)\n * [Introduction](#introduction)\n * [Usage](#usage)\n * [Notes](#notes)\n * [Sublime extension example](#sublime-extension-example)\n\n\n## Installation\n\nto install `interminal`, run:\n\n```\n$ sudo pip3 install interminal\n```\n\nor to install from source:\n\n```\n$ sudo python3 setup.py install\n```\n\n`interminal` requires the `pexpect` package, which pip should automatically\ninstall if it is not already installed.\n\n## Introduction\n\n`interminal` is mostly useful for things like making an external tool for your\ntext editor, or making a launcher for a terminal program, or things like that.\nFor example, you might want to run the command `python3 my_script.py` whenever\nyou press a certain keyboard shortcut in your text editor whilst you've got\nthe file `my_script.py` open. It's easy to make an extension to do this in,\nfor example, Sublime Text 3, but the output will be shown in a panel at the\nbottom of Sublime's GUI. Sublime's GUI is not a terminal emulator, it does not\ngive you the power to re-run the command with different arguments, inspect\noutput files, etc. Sometimes what you really want is for your favourite\nterminal emulator to be a keystroke away.\n\nYou'd think that would be easy, right? All terminal emulators have command\nline options for telling them what command to run \u2014 can't we just use those?\nUnfortunately it's not that simple. The following command:\n\n```\n$ gnome-terminal -x python3 my_script.py\n```\n\nhas a few problems. One, it closes the terminal as soon as the command exits,\npreventing you from seeing its output. Secondly, the script is not necessarily\nrun from within a shell (whether or not it does seems to vary by terminal\nemulator and the current phase of the moon), meaning any environment variables\nyou've set in your `.bashrc` or aliases etc may not be available. If you want\nyour commands to be run in a 'normal' environment, you need to run a shell in\nthe terminal first, and tell the shell to run your command. Then you have to\nstart another shell to ensure the terminal emulator stays open:\n\n```\n$ gnome-terminal -x bash -c \"python3 my_script.py; bash\"\n```\n\nThis is almost good enough. It doesn't add the command to your shell history,\nbut it's basically what we want. But if you need to escape some characters in\nyour command, you are two-layers deep in \"passing commands to other commands\"\n\u2014 the quoting is going to get out of control fast. And for reasons I can't\ngrasp, I can't get the above to work with some terminal emulators.\n\nIdeally we want something that will work with the minimum quoting, and that\nemulates exactly what would happen if you typed something into a terminal\nyourself. That's where `interminal` comes in.\n\n\n\n## Usage\n\nTo run a command such as `echo hello` in a terminal emulator, run the command:\n\n```\n$ interminal echo hello\n```\n\nThis will launch a new terminal emulator, run the command `echo hello`, and\nthen leave you at an interactive prompt, with the command `echo hello` in your\nshell history, exactly as if you had launched the terminal emulator and typed\nit yourself. In fact, `interminal` works by injecting the command into the\ninput of a pseudoterminal connected to your shell, and so as far as that shell\nis concerned, you *did* type it in. In this way, `interminal` can respect\nwhatever crazy hacks you have decided to apply to your shell \u2014 by just typing\nthings into a terminal the same as a human would.\n\n![alt tag](screenshot.png)\n\nIf you have something more complex, you can instead do:\n\n```\n$ interminal --script 'du -hs $HOME/* | sort -hr; echo hello'\n```\n\nThis allows you to include things that will be interpreted by the shell in the\nlaunched terminal emulator (but not by the shell, if any, from which you are\nexecuting the above command).\n\n\n\n## Notes\n\nWhich terminal emulator to launch is guessed based on the `XDG_DESKTOP`\nenvironment variable \u2014 the default terminal emulator for the desktop\nenvironment is used. If you want to use a different one, you can call\ninterminal like this, using the terminology terminal emulator as an example:\n\n```\n$ terminology -e inshell --script 'du -hs $HOME/* | sort -hr; echo hello'\n```\n\n`interminal` is actually just a very short script that replaces its own\nprocess with the terminal emulator running the `inshell` script, which is also\npart of this package. So you can call `inshell` yourself as above to control\nwhich terminal to launch. If this sounds like it defeats the purpose of this\nproject, it doesn't \u2014 inserting the command into a shell that otherwise\nbehaves like an ordinary interactive shell is the tricky bit.\n\n\nIf you are running the `interminal` command from a shell, then you must make\nsure that its arguments, *after being interpreted by that shell* are what you\nwant to run in the interactive shell that is started in the terminal emulator.\nFor example, if you run:\n\n```\n$ interminal echo $PATH\n```\n\nthen `$PATH` will be expanded *prior* to `interminal` being called. This might\nbe undesirable if, for example, the shell you are running `interminal` from is\ndash, and your `$SHELL` environment variable (which is what `interminal`\nwill run) is /bin/bash. The current shell won't have run your `.bashrc` and so\nmight not have any additions you have made there to your `$PATH` variable. So\nas a general rule, in these cases you should do:\n\n```\n$ interminal --script 'echo $PATH'\n```\n\nOf course if you want to have single quotes in your command, the required\nescape sequences can get unwieldy:\n\n```\n$ interminal --script 'echo $PATH > '\"'\"'my file with spaces'\"'\"''\n```\n\n(this will run `echo $PATH > 'my file with spaces'`)\n\nHopefully, you are using `interminal` from an environment that lets you pass\nin command line arguments that are not interpreted by a shell, but instead\nare passed directly to the `exec()` system call, in which case the above\nquoting is unnecessary. For example, in Python, this is fine:\n\n```python\nimport subprocess\nsubprocess.Popen([\"interminal\", \"--script\", \"echo $PATH > 'my file with spaces'\"])\n```\n\nIn fact, you can have an arbitrarily long multi-line shell script as that\nfinal argument, it will be executed as-is.\n\nThe following is fine too, for the case where you are not using any features\nof the shell and just want to run a single command:\n\n```python\nimport subprocess\nsubprocess.Popen([\"interminal\", \"python3\", \"/path/to/my script with spaces.py\"])\n```\nNo quoting is neccesary at all in this case.\n\nBut if you have to pass something to a shell, and you don't know in advance\nwhat the arguments are, you should try to quote them programatically to ensure\nyou get the quoting right:\n\n```python\nimport shlex\nimport os\n\n\n# If you have a command using shell features:\nargs = [\"interminal\", \"--script\", \"echo $PATH > 'my file with spaces'\"]\n\n# or, when not using shell features:\n# args = [\"interminal\", \"python3\", \"/path/to/my script with spaces.py\"]\n\n# Create a single, appropriately quoted command from the arguments:\ncommand = ' '.join(shlex.quote(arg) for arg in args)\n\n# Then pass the single command to whatever shell will interpret it:\nos.system(command)\n```\n\n## Sublime extension example\n\nHere's how I use `interminal` to make Sublime Text 3 run Python. This requires\nan extension file to be placed in `~/.config/sublime-text-3/Packages/User/`:\n\n```python\n\n# ~/.config/sublime-text-3/Packages/User/python_interminal.py\n\nimport os\nimport subprocess\nimport sublime_plugin\n\n\nclass WindowCommand(sublime_plugin.WindowCommand):\n\n @property\n def current_file(self):\n return os.path.abspath(self.window.active_view().file_name())\n\n @property\n def current_file_basename(self):\n return os.path.basename(self.current_file)\n\n @property\n def current_folder(self):\n return os.path.dirname(self.current_file)\n\n def save(self):\n self.window.active_view().run_command('save')\n\n\nclass PythonInterminalCommand(WindowCommand):\n def run(self, index=None):\n self.save()\n subprocess.Popen(['interminal', 'python3', '-i', self.current_file_basename],\n cwd=self.current_folder)\n\n```\n\nAnd then it requires an addition to the keybindings file:\n```\n[\n\n ...\n\n { \"keys\": [\"f5\"], \"command\": \"python_interminal\" },\n\n ...\n\n]\n```\n\nThen when you press `F5`:\n\n![alt tag](screenshot_sublime.png)\n\n\nNote that it doesn't matter what the extension file is called, the keybinding\nfinds the right command to run based on a naming convention for the class:\n`foo_bar_baz` means it will look for a class called `FooBarBazCommand`.\n\nI prefer `python3 -i` but you can of course modify this to whatever you like.\n\nIn particular with this command, running commands through an actual shell is\nuseful for because I have an alias such that `python3` actually calls\n`~/anaconda3/bin/python`, a newer version of Python than the system Python. If\nI change the alias, I won't have to change my Sublime extension - I only have\nto make sure the shell does what I want, and then anything launched via\n`interminal` will respect that.\n",
"bugtrack_url": null,
"license": "BSD",
"summary": "Utility for launching commands in a GUI terminal",
"version": "0.4.0",
"project_urls": {
"Download": "https://pypi.org/project/interminal/",
"Homepage": "https://github.com/chrisjbillington/interminal",
"Source Code": "https://github.com/chrisjbillington/interminal",
"Tracker": "https://github.com/chrisjbillington/interminal/issues"
},
"split_keywords": [
"logging",
"unix"
],
"urls": [
{
"comment_text": "",
"digests": {
"blake2b_256": "1522acf5cfbac574df2038a48de2ded13cea51d9b5ad896ff3124cb2d0772cbe",
"md5": "0763afc7e23768d7681a8c3c94b9c3e9",
"sha256": "46066e9fe3af04c1bb798dcf4346fbb6c09f577f4b415cb0ab387096cae7f845"
},
"downloads": -1,
"filename": "interminal-0.4.0-py3-none-any.whl",
"has_sig": false,
"md5_digest": "0763afc7e23768d7681a8c3c94b9c3e9",
"packagetype": "bdist_wheel",
"python_version": "py3",
"requires_python": null,
"size": 8259,
"upload_time": "2024-04-10T05:01:22",
"upload_time_iso_8601": "2024-04-10T05:01:22.195076Z",
"url": "https://files.pythonhosted.org/packages/15/22/acf5cfbac574df2038a48de2ded13cea51d9b5ad896ff3124cb2d0772cbe/interminal-0.4.0-py3-none-any.whl",
"yanked": false,
"yanked_reason": null
},
{
"comment_text": "",
"digests": {
"blake2b_256": "c27d3011edad36493cce77193f9b2a927868566585c277317622c54165c51dff",
"md5": "a38732a999a76c7675f7129d79877339",
"sha256": "25fb58cf35bbdc1ca4b77fe6bc9b04f735bd5028ef07bb6f3c0b3ddef500ed92"
},
"downloads": -1,
"filename": "interminal-0.4.0.tar.gz",
"has_sig": false,
"md5_digest": "a38732a999a76c7675f7129d79877339",
"packagetype": "sdist",
"python_version": "source",
"requires_python": null,
"size": 50756,
"upload_time": "2024-04-10T05:14:59",
"upload_time_iso_8601": "2024-04-10T05:14:59.230006Z",
"url": "https://files.pythonhosted.org/packages/c2/7d/3011edad36493cce77193f9b2a927868566585c277317622c54165c51dff/interminal-0.4.0.tar.gz",
"yanked": false,
"yanked_reason": null
}
],
"upload_time": "2024-04-10 05:14:59",
"github": true,
"gitlab": false,
"bitbucket": false,
"codeberg": false,
"github_user": "chrisjbillington",
"github_project": "interminal",
"travis_ci": false,
"coveralls": false,
"github_actions": false,
"lcname": "interminal"
}