Running shell command in VIM Script - vim

In my vimrc file, I have an if statement that contains a variable that gets output from a shell command. The idea is if the command output is not Hyper run some few commands and if it is run other commands.
Minor update: I run vim inside tmux sessions so when using $TERM_PROGRAM it produces tmux as my $TERM_PROGRAM
System command:
ps -p $(ps -p $$ -o ppid=) -o args | grep -o Hyper
The issue is that I am unable to escape the shell expansion in vim
" change vim theme depending on the terminal
" need some help here on the term variable
silent! let term = system('ps -p $(ps -p $$ -o ppid=) -o args | grep -o Hyper')
if term != 'Hyper'
set background=dark
" set contrast
let g:everforest_background = 'hard'
colorscheme everforest
else
colorscheme spacecamp
endif

Everything in the provided snippet "works".
I put "Works" in quotes because the syntax of your Vim stuff and the syntax of your shell stuff are correct: everything is passed correctly to the shell and then handled properly. The Vim stuff is fine as-is and the shell stuff is also syntactically fine.
The problem, here, is that the shell command doesn't do what you expect because the process tree is deeper than you anticipated, not that the command needs escaping (which it doesn't).
Here is an ideal view of your shell command's process tree in Vim:
terminal
shell
vim
your shell command
The external command executed via system() ends up being executed as bash -c <your command> so $$ is really the PID of your shell command.
ps -p $$ -o ppid= gives you the PID of the parent of your shell command, which is Vim.
and finally, ps -p $(ps -p $$ -o ppid=) -o args= only ends up giving you the full command that you used to start Vim.
Basically, your shell command is not deep enough to reach your terminal emulator, so you will need to addd more levels.
But your problems don't stop here.
Your first new problem is that the tree gets bigger when you run Vim in Tmux:
terminal
shell
tmux
shell
vim
your shell command
which means that the already heavily nested shell command you wrote to handle the simple case might require even more levels… which leads to another interesting problem: how to decide which version (non-tmux-aware or tmux-aware) of the command to run?
The last problem is the best: Tmux having a client/server architecture, the idealized tree above might actually be completely detached from any terminal emulator:
/sbin/launchd
tmux
shell
vim
your shell command
Good luck finding the name of the current terminal emulator with ps in those conditions.
You may have more luck with pstree but I wouldn't hold my breath.

Related

How to set environment variables of the shell resulting from the vim :sh command

I often use :sh in Vim to get out to a shell and perform some action, then I $ exit to return back to my Vim session. The issue is that sometimes I come back to a tab and I forget if I have exited from a Vim session or not, and typing $ exit will close the tab or SSH connection if I guess wrong and I'm not in a subshell from Vim. I would like to be able to set some special environment variables of the shell when opening a subshell from Vim with :sh. Is there a way to do this?
I was thinking something like :sh ENVVAR="vim is open!" I could then display ENVVAR in my PS1 if the variable is set, but this doesn't seem to work.
You can use let to set an environment variable. Just prepend the name of the variable with $.
:let $ENVVAR = 'vim is open!'
:sh
$ echo $ENVAR
vim is open!
See :help :let-environment. Of course, you can put the let command in your vimrc. In addition, vim sets some environment variables on its own, which you could also query from your shell:
:sh
$ env | grep VIM
VIMRUNTIME=/usr/share/vim/vim81
VIM=/usr/share/vim
MYVIMRC=/home/user/.vimrc
Assuming you're running on Linux or macOS (or something reasonably similar), you can get the parent process name by doing this:
ps -p $PPID -o comm=
So you could add something to your shell configuration like this:
IN_VIM=$(ps -p $PPID -o comm= | grep -qsE '[gm]?vim' && echo 1)
and then do something depending on whether $IN_VIM is non-empty.

Start detached screen with ssh , without killing after the commands execute [duplicate]

I'm lazy, and I prefer that computers do my work for me. I ssh into several machines on a daily basis, so I created a simple script that launches some xterm windows and places them in positions I want (as you can see, I'm using bash):
#!/bin/bash
xterm -geometry 80x27+1930+0 &
xterm -geometry 80x27+2753+0 &
xterm -geometry 80x27+1930+626 &
xterm -geometry 80x27+2753+626 &
However, the next thing I do is go to the first window and type in
ssh server_a
then in the second
ssh server_b
and so on. What I'd like to do is have my script do the ssh commands in each xterm window, and then leave the windows open for me to do my work. I've seen the -e option for xterm, but the window closes after I execute my command. Is there a way to do this?
I apologize if this is a duplicate question. I've searched around and haven't had any luck with this. Many thanks!
I'd love to see a more elegant answer, but what I came up with does work:
xterm -e bash -c 'echo foo; exec bash'
Replace echo foo with the command of your choice, and you're good to go.
This answer gives one of the best answers I've seen so far to do this. Use the bash --init-file flag either in the shebang or when executing the terminal:
#!/bin/bash --init-file
commands to run
... and execute it as:
xterm -e /path/to/script
# or
gnome-terminal -e /path/to/script
# or
the-terminal -e bash --init-file /path/to/script/with/no/shebang
My only real complaint with the exec option is if the command executed prior to exec bash is long running and the user interrupts it (^C), it doesn't run the shell. With the --init-file option the shell continues running.
Another option is cmdtool from the OpenWin project:
/usr/openwin/bin/cmdtool -I 'commands; here'
# or
/usr/openwin/bin/cmdtool -I 'commands; here' /bin/bash
... where cmdtool injects the commands passed with -I to the slave process as though it was typed by the user. This has the effect of leaving the executed commands in the shell history.
Another option is to use gnome terminator. This creates and positions terminals interactively, and you can set up each terminal to run commands within terminator preferences.
Also does lots of extra tricks using keybindings for things like move, rotate, maximise/minimise of terminals within the containing terminator window
See: https://superuser.com/a/610048
"ClusterSSH controls a number of xterm windows via a single graphical console window to allow commands to be interactively run on multiple servers over an ssh connection"
https://github.com/duncs/clusterssh/wiki
$ cssh server_a server_b
$ command

How to use GNOME Terminal instead of XTerm here?

I have the following variable defined in ~/.vimrc. This works well with XTerm but I can't get it working with GNOME Terminal. Please help.
let g:slimv_client = 'python /home/dumrat/.vim/ftplugin/slimv.py -r "xterm -e sbcl --core /home/dumrat/.sbcl/sbcl.core -s"'
The option -e makes XTerm run the command specified by all of the remaining
command line arguments following -e. Consequently, xterm -e sbcl --core
/home/dumrat/.sbcl/sbcl.core -s opens an XTerm instance running sbcl --core
/home/dumrat/.sbcl/sbcl.core -s command.
GNOME Terminal has the option -x with the same meaning that -e has for
XTerm.1 Thus, change the configuration file, as follows.
let g:slimv_client = 'python /home/dumrat/.vim/ftplugin/slimv.py -r "gnome-terminal -x sbcl --core /home/dumrat/.sbcl/sbcl.core -s"'
1 Note that -e has somewhat different behavior in GNOME
Terminal—the whole command is expected to be in the next argument, while -x
assumes that everything to the end is the command to run.

Avoid gnome-terminal close after script execution?

I created a bash script that opens several gnome-terminals, connect to classroom computers via ssh and run a script.
How can I avoid that the gnome-terminal closes after the script is finished? Note that I also want to be able to enter further commands in the terminal.
Here is an example of my code:
gnome-terminal -e "ssh root#<ip> cd /tmp && ls"
As I understand you want gnome-terminal to open, have it execute some commands, and then drop to the prompt so you can enter some more commands. Gnome-terminal is not designed for this use case, but there are workarounds:
Let gnome-terminal run bash and tell bash to run your commands and then start a new bash
$ gnome-terminal -- bash -c "echo foo; echo bar; exec bash"
or if the commands are in a script
$ gnome-terminal -- bash -c "./scripttorun; exec bash"
The first bash will terminate once all the commands are done. But the last command is a new bash which will then just keep running. And since something is still running gnome-terminal will not close.
Let gnome-terminal run bash with a prepared rcfile which runs your commands
Prepare somercfile:
source ~/.bashrc
echo foo
echo bar
Then run:
$ gnome-terminal -- bash --rcfile somercfile
bash will stay open after running somercfile.
i must admit i do not understand completely why --rcfile has this behaviour but it does.
Let gnome-terminal run a script which runs your commands and then drops to bash
Prepare scripttobash:
#!/bin/sh
echo foo
echo bar
exec bash
Set this file as executable.
Then run:
$ gnome-terminal -- ./scripttobash
for completeness
if you just want to be able read the output of the command and need no interactivity
go to preferences (hamburger button -> preferences)
go to profiles (standard or create a new one)
go to command tab
when command exits -> hold the terminal open
i recommend to create a new profile for just for this use case.
use the profile like this:
gnome-terminal --profile=holdopen -- ./scripttorun
Every method has it's quirks. You must choose, but choose wisely.
I like the first solution. it does not need extra files or profiles. and the command says what it does: run commands then run bash again.
All that said, since you used ssh in your example, you might want to take a look at pssh (parallel ssh). here an article: https://www.cyberciti.biz/cloud-computing/how-to-use-pssh-parallel-ssh-program-on-linux-unix/
Finally this one works for me:
gnome-terminal --working-directory=WORK_DIR -x bash -c "COMMAND; bash"
Stack Overflow answer: the terminal closes when the command run inside it has finished, so you need to write a command that doesn't terminate immediately. For example, to leave the terminal window open until you press Enter in it:
gnome-terminal -e "ssh host 'cd /tmp && ls'; read line"
Super User answer: Create a profile in which the preference “Title and Command/When command exits” is set to “Hold the terminal open”. Invoke gnome-terminal with the --window-with-profile or --tab-with-profile option to specify the terminal name.
Run with -ic instead -i to make terminal close bash proccess when you close your terminal gui:
gnome-terminal -e "bash -ic \"echo foo; echo bar; exec bash\""
As of January 2020, the -e option in gnome-terminal still runs properly but throws out the following warning:
For -e:
# Option “-e” is deprecated and might be removed in a later version
of gnome-terminal.
# Use “-- ” to terminate the options and put the command line to
execute after it.
Based on that information above, I confirmed that you can run the following two commands without receiving any warning messages:
$ gnome-terminal -- "./scripttobash"
$ gnome-terminal -- "./genericscripttobash \"echo foo\" \"echo bar\""
I hope this helps anyone else presently having this issue :)
The ideal solution would be to ask for a user input with echo "Press any key".
But if double-click in Nautis or Nemo and select run in a terminal, it doesn't seem to work.
In case of Ubuntu a shell designed for fast start-up and execution with only standard features is used, named dash I believe.
Because of this the shebang is the very first line to start with to enable proper use of bash features.
Normally this would be: #!/bin/bash or similar.
In Ubuntu I learned this should be: #!/usr/bin/env bash.
Many workarounds exist to keep hold of the screen before the interpreter sees a syntax error in a bash command.
The solution in Ubuntu that worked for me:
#!/usr/bin/env bash
your code
echo Press a key...
read -n1
For a solution applicable to any terminal, there is a script that opens a terminal, runs the command specified and gives you back the prompt in that new terminal:
https://stackoverflow.com/a/60732147/1272994
I really like the bash --rcfile method
I just source ~/.bashrc then add the commands I want to the new startrc.sh
now my automated start.sh work environment is complete... for now 😼
If running a bash script just add gedit afile to the end of the script and that will hold gnome-terminal open. "afile" could be a build log which it was in my case.
Did not try just using gedit alone but, that would properly work too.
Use nohup command.
nohup gnome-terminal -e "ssh root# cd /tmp && ls"
Hope this will help you.

How do I launch an editor from a shell script?

I would like my tcsh script to launch an editor (e.g., vi, emacs):
#!/bin/tcsh
vi my_file
This starts up vi with my_file but first displays a warning "Vim: Warning: Output is not to a terminal" and my keystrokes don't appear on the screen. After I kill vi, my terminal window is messed up (no newlines), requiring a "reset". I tried "emacs -nw", "xemacs -nw", and pico with similar results. "xemacs" works but launches a separate window. I want to reuse the same terminal window.
Is there a way to launch an editor from a script so that it reuses the same terminal window?
I answered my own question! You have to redirect terminal input and output:
#!/bin/tcsh
vi my_file < `tty` > `tty`
The reason you're getting the error is that when you start a shell in your environment, it's starting in a subshell that has STDIN and STDOUT not connected to a TTY — probably because this is in something like a pipeline. When you redirect, you're opening a new connection directly to the device. So, for example, your command line turns
$ vi < `tty` > `tty`
into
$ vi < /dev/ttys000 > /dev/ttys000
So you're not really using your old STDIN/STDOUT, you're creating two new files and mapping them to your vi process's STDIN/STDOUT.
Now, tell us what you're doing with this and we'll tell you how to avoid this kludge.
I wanted to do something similar. I wanted an alias that would find the last file I was working on, and open it in vi(1) for editing. Anyway, I couldn't figure out how to do it as a readable alias (in tcsh) so I just created an ugly shell script (csh because I'm old) instead:
#!/bin/csh
set DIR = "~/www/TooMuchRock/shows/"
set file = $DIR`ls -t $DIR | head -1`
set tty = `tty`
vi $file <$tty >$tty
(1) kraftwerk:bin> which vi
vi: aliased to /usr/local/bin/vim -u ~/.exrc
Absolutely. :-)
Write your script and have it call the EDITOR environment variable, which you will have set to "emacsclient". Then start up Emacs, execute M-x server-start, switch to a shell buffer (M-x shell) and execute your script. Emacsclient will pop up the thing to be edited and C-x # will act as a "done" command and take you back to your script with edits completed or aborted, as you choose.
Enjoy.
Edit: I meant to add that these days Emacs IS my terminal program. I have dozens of shell buffers and never have to worry about losing output and can use all the power of Emacs to manipulate and analyse the terminal output. And have Emacs scripts generate input to the shells. Awesome actually. For example, watching Tomcat output scroll by in a shell buffer while editing sources or processing mail or doing most any Emacs thing is very convenient. When a Tomcat stack trace appears I can quickly respond to it.
Had the same trouble with 'pinfo' in a shell script 'while' loop. The line can be used in the script, it uses 'ps' to find the tty of the current process number, "$$", and stores that tty in $KEY_TTY:
KEY_TTY=/dev/`ps | grep $$ | tr -s '[:blank:]' | cut -d " " -f 3`
Later in the script, just call the tty-only proggie, with $KEY_TTY as input, in my case it was:
pinfo -m $s $page < $KEY_TTY
For 'vi' it'd be:
vi $a < $KEY_TTY > $KEY_TTY
The advantage is that the script as a whole can still accept STDIN input, and 'vi' (or whatever) should work fine -- without having to remember to set any environmental variables before running the script.
Set your terminal tty to a variable, and then redirect the editor i/o through that variable.
In your script:
#!/bin/sh
ls | while read a; do vi $a < $MYTTY >$MYTTY; done
And then execute the script with:
$ MYTTY=`tty` ./myscript >/tmp/log
I was able to get the desired behavior under bash+Cygwin+Terminator:
#!/bin/bash
vim foo
Run the script, vim loads, no error messages, behaves as normal. There are undoubtedly dozens of variations between our setups, however, so I can't hazard a guess as to what makes the difference. I'm curious what it is, but you got it working, which is the important part.

Resources