Reputation: 929
Is there a way of executing bash commands from sublimetext?
In many projects it's just better developing with linux tools via the Windows Subsystem for Linux (WSL, bash) as most tools are built for linux.
In windows, you can run bash commands in a windows-console like this:
bash -c "echo \"my bash command here\""
Let's say I want to run a very specific build script like this:
bash -c "prettier mypath/ && eslint mypath/ --fix"
or like this:
bash -c "my_very_specific_build_script.sh"
or even better, having a hook executing a linter via a hook with: https://packagecontrol.io/packages/Hooks
bash -c "my_linting_script.sh"
and then call the script on every "save" like this:
"on_post_save_user":
[
{
"command": "bash_execute",
"args": {
"command": "my_linting_script.sh",
}
}
],
This would be a game changer for all of us developers without a mac
MY ADVANCE SO FAR
In Keybindings this works (creates a log when ctrl+alt+b pressed)
{
"keys": ["ctrl+alt+b"],
"command": "exec",
"args": {
"cmd": "bash -c \"cd ~ && echo ok >> log.txt\"",
}
}
And this works in preferences triggered on every "save" using the "hooks" plugin:
"on_post_save_user": [
{
"command": "exec",
"args": {
"cmd": "bash -c \"cd ~ && echo ok >> log.txt\"",
},
"scope": "window"
}
],
Is this the best way?
An attempt at creating a plugin
I made a simple plugin that successfully runs "top" with bash in windows with WSL.
https://github.com/eduardoarandah/sublime-bash-execute
Just copy BashCommand.py in Packages/User and run in console:
view.run_command('bash_exec')
I've never made a sublime plugin, any help would be credited, appreciated and so on.
Upvotes: 4
Views: 4534
Reputation: 22791
The internal exec
command can run any arbitrary command that you choose to throw at it, so it's possible to use it directly to do this using that command if so desired.
Something to note is that the exec
command can be given either cmd
as a ["list", "of", "strings"]
or shell_cmd
as "a single string"
. When using cmd
, the first item in the list is invoked directly and everything else is passed as arguments, while in the second case the string is passed directly to the shell as is.
Generally for items such as you're mentioning here, you want to use shell_cmd
; since it's passed directly to the shell to execute, it can contain things such as redirection and chained commands.
As noted in your examples above, you can use shell_cmd
and prefix any command that you want to execute with bash -c
, wrapping the rest of the command in double quotes, and the exec
command will run bash
and pass it the command that you provided.
It's also possible to create a simple command in Sublime that behaves like exec
does, while at the same time automatically injecting the appropriate item in there to get bash to execute the command for you.
Such a plugin might look like the following:
import sublime
import sublime_plugin
from Default.exec import ExecCommand
class BashExecCommand(ExecCommand):
"""
A drop in replacement for the internal exec command that will run the
given command directly in bash. Use of "shell_cmd" is recommended, but
some care is taken to convert "cmd" to the appropriate "shell_cmd"
equivalent.
For use in key bindings, replace `exec` with `bash_exec` and you're good
to go. For use in a build system, include the following lines to your
build to tell Sublime to use this command to execute the build as well as
how to kill it:
"target": "bash_exec",
"cancel": {"kill": True}
"""
def run(self, **kwargs):
# If we're being told to kill a running build, short circuit everything
# and just do it directly. Not really needed, but it's a nice reminder
# that custom builds need to handle this sort of thing in general.
if kwargs.get("kill", False):
return super().run(kill=True)
# If there is a "cmd" argument, grab it out and convert it into a
# string. Items in cmd that contain spaces are double quoted so that
# they're considered a single argument, and all items are joined
# together.
cmd = kwargs.pop("cmd", None)
if cmd is not None:
cmd = " ".join(["\"%s\"" % s if " " in s else s for s in cmd])
# If there is a "shell_cmd" argument, then modify it to include the
# correct prefix to run the command via bash as well as to quote all
# double quote characters so the shell sees them.
#
# This will fall back to the string we just gathered for "cmd" if no
# shell_cmd is given.
shell_cmd = kwargs.pop("shell_cmd", cmd)
if shell_cmd is not None:
kwargs["shell_cmd"] = "bash -c \"{0}\"".format(
shell_cmd.replace("\"", "\\\""))
# Defer to the built in exec command to run the command
super().run(**kwargs)
If you redact away the comments that explain what this is doing, the change is ~20 lines or so. What this is doing is leveraging the internal exec
command to do all of the heavy lifting for us. Thus we can just modify the arguments to the command automatically and let Sublime take care of the details.
As seen in the code, if a cmd
argument is provided, it's converted into a string by joining all of the arguments together with space characters. Along the way any argument that contains a space is automatically double quoted so that it conveys properly to the shell.
If shell_cmd
is provided, it's similarly extracted and converted into a string with a bash -c
prefix; along the way any double quotes that appear in the command are quoted so that they convey properly to the shell.
Additionally, the converted value of cmd
is used as the default if no shell_cmd
is provided; thus if you specify shell_cmd
that's always what is executed, and if you use cmd
it's converted into the appropriate shell_cmd
argument directly.
In use the command that this implements is bash_exec
, and should be a drop in replacement for uses of exec
in places where you're calling it directly, such as in key bindings, menu entries, command palette entries, via other commands, etc.
In addition, you can add the following two lines directly to a sublime-build
file to use this command to execute builds undertaken that way; the first line tells Sublime to use this command instead of exec
to execute the build, while the second tells it what arguments to pass to the command to get it to cancel a running build.
"target": "bash_exec",
"cancel": {"kill": True},
This is really only of use on Windows; on Linux and MacOS Sublime already uses bash
to execute shell_cmd
without your having to do anything special. Using sublime.platform()
, the command could determine if it's being executed on Windows and only rewrite commands on that OS and otherwise leave things alone, if desired.
Note also that I don't personally use WSL on my Windows machines, so this is untested in that environment (but it performs as expected on my Linux box); as such there may still be some tweaks to be made here, such as if bash
isn't directly on the path or you want it to be configurable.
The last thing to mention is that the code to convert shell_cmd
and cmd
into a bash command line may require some more intelligence or subtle tweaks depending on what you're trying to pull off. If things don't seem to do what you expect, the Sublime console displays what command exec
is running, which may shed some light on things.
Upvotes: 1