Sublimetext integration with Windows Subsystem for Linux (WSL, bash) - sublimetext3

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.

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},
Caveats
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.

Related

How to hide >>> prompt in python3 interactive console? Is there a flag for this?

For copy and paste purposes, I wish to hide the >>> and ... Python prompts whenever I am in the interactive shell console. I have achieved this effect successfully with the command import sys, then sys.ps1 = "" (source). However that only hides >>>. Furthermore, a flag would simplify the work, and since other languages have this type of flag, I wonder if I'm not reinventing the wheel.
In database programming with Db2, for example, there is a +p flag. As it is described negatively, it does the opposite of -p:
The -p option tells the command line processor to display the command line processor prompt when the user is in interactive mode.
Any shortcuts?
python -i scriptname.py runs scriptname.py, then drops you to an interactive shell. If that script clears sys.ps1 and sys.ps2, then the interactive shell will be one that doesn't print prompts.
Even better, you can use the shell feature process substitution to create a temporary filename (something like /dev/fd/10, depending on your OS) that, when read, contains exactly the script you want:
pnp() { python -i <(printf '%s\n' 'import sys' 'sys.ps1=""' 'sys.ps2=""') "$#"; }
...if placed in your .bashrc, will define a command pnp ("python, no prompt") that does the above.

Can substring expansion be used in dash shell or bourne shell?

I'm converting an app to a new image, and the existing commands use substring expansion to set the artifact version like so: mvn clean versions:set -DnewVersion="0.1.$VCSINFO.I${INFO:0:6}.M$OTHER_INFO". I'm using a ubuntu image that defaults to /bin/sh, and I am unable to figure out how to either do something equivalent in bourne shell, or switch shells to run the command. I know bash is installed because I can see it in /etc/shells.
I tried using RUN ['/bin/bash', '-c', '...'] but I can see it is just running that command like so The command '/bin/sh -c ['/bin/bash', '-c',.... What is the best way to convert this functionality over to this new image?
You can run a bash command in two ways, even from sh: Either by passing the string '/bin/bash path/to/your/cmd' to the -c option of sh, or by setting the x-bit in cmd and having as the first line in cmd a #!/bin/bash.
Hence in your setting I would try either a RUN ['/bin/bash /path/to/your/cmd'] or just do a RUN ['/path/to/your/cmd'] and ensure that cmd has the #! line mentioned above, or complicated but fail safe - write a sh wrapper script, which then invokes the bash script in turn. Hence, if this wrappe script is called /path/to/your/cmdwrapper.sh, its content would be
:
/bin/bash /path/to/your/cmd

How to identify which REPL is running in the terminal?

I am trying to have my terminal title change depending on what that specific window is doing. So far I have the following which will update based on directory and server.
function settitle() {
if [[ -z "$ORIG" ]]; then
ORIG=$PS1
fi
TITLE="\[\e]2;$*:$(dirs -0)\a\]" #dirs -0 is like pwd but with ~/ instead of /home/user/
PS1=${ORIG}${TITLE}
}
PROMPT_COMMAND="settitle local" # local is the server name in this case
Now, sometimes I'm in the PHP (php -a) or MySQL (mysql -u user -ppass) REPL, and I'd like the title to reflect that instead of just being whatever directory I launched the REPL from.
The best I can figure is getting the last command somehow, then figuring out what the first word is, and running an if check in settitle(). I've tried everything from here and here among other places, and while I can usually get part of it to work in the command line, non of it works in settitle(). For example.
local:~$ echo 'foobar'
foobar
local:~$ !:0
echo
# I add echo !:0 to settitle()
local:~$ source .bashrc
!:0
local:~$
A note: It should be "source", or at least "echo" from before. !:0 does not recognize itself as a command so it will repeat the last real command over and over. The "!:0" being echoed is a literal string, not the results of the command. Additionally, saving to a var does not work, and just putting the command without trying to echo/save the result gives !:0: command not found.
I don't want to make this an XY problem, so if I am barking up the wrong tree here at any step of the process, please let me know. The goal is to be able to change the title of my terminal window if I enter an REPL. How can I identify when a command will enter me into one?
Note that PROMPT_COMMAND and similar shell features are not relevant when you're in a different REPL; from the point of view of the shell, the entire REPL session is one single command. The shell prompt doesn't show up again until you exit that REPL, and that's the point at which PROMPT_COMMAND and friends are activated.
One thing you can do is alias the command you use to start the REPL so that it sets the title of the window first:
alias phpa='setttitle PHP; php -a'
alias mysqli=`settitle MySQL; mysql -u "$USER"'
or something like that.
The sequence goes like this:
PROMPT_COMMAND runs.
The shell prints its prompt.
You type the command to start a REPL
You are in the REPL. The prompt you see is printed by the REPL, not the shell, which is not involved at this point. The shell is just hanging out waiting for you to exit the REPL; it's not printing any prompts, so it's not ever running PROMPT_COMMAND.
You type commands in the REPL. No matter how many you run, it's part of a single session that the shell sees as a single command.
You exit the REPL.
PROMPT_COMMAND runs.
The shell prints its prompt.

How to use xdotool to open a new tab, switch to it and run commands in it

I am trying to write a bash script to automate running some commands. However some of these commands should be running in their own terminal tab.
So I use the following in my bash script to open a new tab:
xdotool key ctrl+shift+t
this does the job, but the next commands in my bash script are still executed in the previous terminal tab.
How can I make the new opened terminal tab active and run the next commands in this tab?
What Terminal Emulator are you using? It strongly depends on this.
In general, you could write the commands you want to execute in a shell script and tell your terminal emulator to execute the script once it has started.
Example with xterm:
echo '#!/bin/bash' > /tmp/thescript
echo 'ls -la' >> /tmp/thescript
chmod +x /tmp/thescript
xterm -hold -e /tmp/thescript
EDIT: I just saw that u asked for a way to achieve this with xdotool. So this answer might be invalid. Please tell me if so - then i'll delete it.
How are you using xdotool? It can be done with a chain, for example:
$ xdotool key "ctrl+shift+t"; xdotool type "ls"; xdotool key Return
If all you want is to run the commands in the background / in parallel, without synchronously waiting for each command to complete before the next begins, terminate them with an ampersand & to instruct the shell to do so.
Alternatively, you can execute the commands in their own subshells by surrounding each with parentheses ( ). If they are long running processes or you do not wish to pollute the original shell with their output, you can fork them off and capture their output to file with something like (setsid command 1>/path/to/log &).
If separate tabs is necessary requirement, you can use xdotool to key the switch-to-the-next-tab binding or similar, and then key the commands you must run in that tab.
Instead of sorting out that mess yourself, you could use a script from this answer by Jacob Vlijm, which wraps a windowed approach that uses xdotool and wmctrl to 'send' commands to different terminal windows. The script is written in python 3 but it can easily be rewritten for a shell environment of choice.
A more direct approach involves use of a TIOCSTI ioctl to inject characters into another terminal. According to the tty_ioctl manual page:
NAME
ioctl_tty - ioctls for terminals and serial lines
...
DESCRIPTION
The ioctl(2) call for terminals and serial ports accepts many possible
command arguments.
...
Faking input
TIOCSTI const char *argp
Insert the given byte in the input queue
...
Here are c and perl wrappers, and an example in python as referenced by this answer.

bash + Linux + how to ignore the character "!"

I want to send little script to remote machine by ssh
the script is
#!/bin/bash
sleep 1
reboot
but I get event not found - because the "!"
ssh 183.34.4.9 "echo -e '#!/bin/bash\nsleep 1\reboot>'/tmp/file"
-bash: !/bin/bash\nsleep: event not found
how to ignore the "!" char so script will so send successfully by ssh?
remark I cant use "\" before the "!" because I get
more /tmp/file
#\!/bin/bash
sleep 1
Use set +H before your command to disable ! style history substitution:
set +H
ssh 183.34.4.9 "echo -e '#!/bin/bash\nsleep 1\reboot>'/tmp/file"
# enable hostory expnsion again
set -H
I think your command line is not well formated. You can send this:
ssh 183.34.4.9 'echo -e "#!/bin/bash\nsleep 1\nreboot">/tmp/file'
When I say "not well formated" I mean you put ">" inside the "echo" and you forgot to add "n" before "reboot", and you put "\reboot", wich will be interpreted as "CR" (carriage return) followed by "eboot" command (which I don't think that exists).
But what did the trick here is to invert the comas changing (') with (") and viceversa.
Bash is running interactively (which means that you are feeding commands to it from the standard input and not exec(2)ing a command from a shell script) so you don't need to include the line #!/bin/bash in that case (even more, bash should just ignore it, but not the included bang, as it is part of the active history mechanism)
But why? the first two characters in an executable file (any file capable of being exec(2)ed from secondary storage, not your case) have a special meaning (for the kernel and for the shell): they are the magic number that identifies the kind of executable file the kernel is loading. This allows the kernel to select the proper executable loading routines depending on the binary executable format (and what allows you for example to execute BSD programs in linux kernels, and viceversa)
A special value for this magic numbers is composed by the two characters # and ! (in that order) that forces the kernel to read the complete first line of that file and load the executable file specified in that line instead, allowing you to execute shell scripts for different interpreters directly from the command line. And it is done on purpose, as the # character is commonly in shell script parlance a comment character. This only happens when the shell that is interpreting the commands is not an interactive shell. When the shell loads a script with those characters, it normally reads the first line also to check if it has the #! mark and load the proper interpreter, by replicating the kernel function that does this. Despite of being a comment for the shell, it does this to allow to treat as executables files that are not stored on secondary storage (the only ones the exec(2) system call can deal with), but coming from stdin (as happens to yours).
As your shell is running interactively and you do want to execute its commands without a shell change, you don't need that line and can completely eliminate it without having to disable the bang character.
Sorry, but the solution given about executing the shell with -H option will probably not be viable, as the shell executing the commands is the login shell in the target machine, so you cannot provide specific parameters to it (parameters are selected by the login(8) program and normally don't include arbitrary parameters like -H).
The best solution is to fully eliminate the #!/bin/bash line, as you are not going to exec(2) that program in the target. In case you want to select the shell from the input line (case the user has a different shell installed as login shell), it is better to invoke the wanted shell in the command line and pass it (through stdin, or making it read the shell script as a file) the shell commands you wan to execute (but again, without the #! line).
NOTE
Its important to ensure you'll execute the whole thing, so it's best to pass all the script contents in the destination target, and once assured you have passed the whole thing to execute it as a whole. Then your #! first line will be properly processed, as the executable will be run by means of an exec(2) made from the kernel.
Example:
DIRECTORY=/bla/bla
FILE=/path/to/file
OUTPUT=/path/to/output
# this is the command we want to pass through the line
cat <<EOF | ssh user#target "cat >>/tmp/shell.sh"
cd $DIRECTORY
foo $FILE >$OUTPUT
exit 0
EOF
# we have copied the script file in a remote /tmp/shell.sh
# and we are sure it has passed correctly, so it's ready
# for local execution there.
# now, execute it.
# the remote shell won't be interactive, and you'll ensure that it is /bin/bash
ssh user#target "/bin/bash /tmp/shell.sh" >remote_shell.out
A more sophisticate system is one that allows to to sign the shell script before sending, and verify the script signature before executing it, so you are protected against possible trojan horse attacks. But this is out of scope on this explanation.
Another alternative is to use the batch(2) command remotely and pass it all the commands you want executed. you'll get a sessionless executing environment, more suitable to the task you are demanding (despite the fact that you'll get the script output by email to the target user running the script)
Interactively, beware that ! triggers history expansion inside double quotes
from here: https://riptutorial.com/bash/example/2465/quoting-literal-text
my recommended solution is to use single quotes to define the string (and either escape single quotes \' or use double quotes " within the string):
ssh 183.34.4.9 'echo -e "#!/bin/bash\nsleep 1\reboot>"/tmp/file'

Resources