Running a zsh alias with WSL in a ConEmu startup task - zsh

My current WSL2 + ConEmu + bash/zsh setup works as expected.
I have some aliases set up in my .zshrc:
//.zshrc
alias mycommand1="[does some stuff]"
alias mycommand2="[does other stuff]"
What I want to achieve is to have a ConEmu startup Task that would run mycommand1 and mycommand2 in two separate tabs, and then leave these tabs open.
This requires ConEmu to load up WSL+bash+zsh, and then run the command.
As per the docs, my default Task currently runs native wsl.exe:
// {Bash::bash} Task commands
%windir%\system32\wsl.exe -cur_console:pm:/mnt --distribution Ubuntu-20.04
And, after carefully reading all the docs (1, 2, 3) and spending a fair amount of time on fiddling with params, I was only able to produce following attempts:
%windir%\system32\wsl.exe --distribution Ubuntu-20.04 -new_console:pm:/mnt -- ls
// logs my Windows User directory and prompts "Press Enter or Esc to exit..."
%windir%\system32\wsl.exe --distribution Ubuntu-20.04 -new_console:pm:/mnt -- mycommand1
// logs "zsh:1: command not found: mycommand1" and prompts "Press Enter or Esc to exit..."
I would appreciate some pointers:
How can I pass a command from wsl.exe that will be run from within the Ubuntu context, with all my bash/zsh configs?
How can I do that from ConEmu Task, ensuring that after running the command the tabs stay open with a regular zsh shell prompt?
Is there anything else I need to know to solve my problem?

Adding this as a second answer in case it works for you (and it very well may).
My (very long) other answer assumes that you need the alias to run in the same subshell instance that continues to run. If you are okay with the alias running, then exec'ing a new shell, then this answer will be much simpler.
Just create a conemu_start.txt (or whatever you want to call it, wherever you want to place it), with the following:
>%windir%\system32\wsl.exe -cur_console:t:"MyCommand1" -cur_console:pm:/mnt ~ --distribution Ubuntu-20.04 --exec zsh -li -c "mycommand1; zsh -li"
>%windir%\system32\wsl.exe -cur_console:t:"MyCommand2" -cur_console:pm:/mnt ~ --distribution Ubuntu-20.04 --exec zsh -li -c "mycommand2; zsh -li"
As with the other answer, in ComEmu, set Settings -> Startup -> Tasks file to this file.
That's it. The -li will cause the shells to be login (which processes .zprofile) and interactive (which processes .zshrc). That way, the aliases get defined, executed, and then another subshell is run to keep the ConEmu tab open.

Short answer:
Create a new startup directory with a new .zshrc that (a) sources the existing profile, and (b) runs aliases based on an environment variable set before starting the shell.
Start each shell (tab) by setting ZDOTDIR, RUN_ALIAS, and execing zsh -li
Run these through the wsl.exe command
Set a ConEmu startup file to run the appropriate commands
Step 1: Launch zsh, run an alias, and keep the session from exiting
Assumptions: I'm going to assume you really do want these to be aliases. If you want to run other arbitrary scripts/commands, you can still do it, but it will require a slight change to the instructions. I've summarized the changes needed in-line below.
There are several techniques you could use to keep the shell running, but the ones suggested in this question require you to either:
Create a nested shell, which is not ideal.
Or modify your existing ~/.zshrc to "special case" a launch with your command. It's a neat trick, but I'm not a fan of changes to the default startup files when they can be avoided. That said, it's much easier. If you want to go this route, I'll add some info at the bottom of this answer on how to do it.
But the ~/.zshrc solution got me thinking. If you want to run any arbitrary command at startup, and yet keep the shell running, the answer is .zshrc.
You just need to have a different .zshrc that you use for the "special case".
bash has the --rcfile option to select the file to run at startup, but the zsh equivalent is a bit trickier, as it involves setting $ZDOTDIR and changing the location from which zsh reads its startup (from this answer).
So to start, let's create a config directory for your "run an alias" config:
mkdir -p ~/.local/share/zsh/startup/run_alias/`
Or wherever you want, of course. Then, in that directory, create the following:
~/.local/share/zsh/startup/run_alias/.zshrc:
if [ -f $HOME/.zshrc ]; then
. $HOME/.zshrc
fi
if [ -v RUN_ALIAS ] && alias | grep -q "^$RUN_ALIAS="; then
eval "$RUN_ALIAS"
fi
unset RUN_ALIAS
unset ZDOTDIR
And ~/.local/share/zsh/startup/run_alias/.zprofile:
if [ -f $HOME/.zprofile ]; then
. $HOME/.zprofile
fi
The conditionals, of course, are optional if you always know that your $HOME/.zsh and .zprofile exist. But better to be safe.
Repeat for mycommand2.
If it's not obvious, this will:
Call your existing $HOME/.zshrc to define the aliases (and anything else in your startup)
Check to make sure the contents of $RUN_ALIAS really is an alias (some added security)
Call the alias defined in the RUN_ALIAS variable
Unset the RUN_ALIAS variable (cleanup)
Unset the special ZDOTDIR so that future subshell invocations will use the files in $HOME.
Have the new .zprofile source the original as well
You can now test this by calling a subshell with:
ZDOTDIR=~/.local/share/zsh/startup/run_alias RUN_ALIAS=mycommand1 zsh -li
But of course, that leaves an extra subshell running ($SHLVL is 2). So, use exec instead:
ZDOTDIR=~/.local/share/zsh/startup/run_alias RUN_ALIAS=mycommand1 exec zsh -li
$SHLVL is now 1, your command/alias should have been executed in the current (and only) shell, and it is still running.
To get that running in WSL, we do need to start an "outer shell" (that is replaced by execing the proper zsh). I tend to use sh for this, like so:
wsl ~ --distribution Ubuntu-20.04 --exec sh -c "ZDOTDIR=`$HOME/.local/share/zsh/startup/run_alias RUN_ALIAS=mycommand1 exec zsh -li" # PowerShell quoted
wsl ~ --distribution Ubuntu-20.04 --exec sh -c "ZDOTDIR=$HOME/.local/share/zsh/startup/run_alias RUN_ALIAS=mycommand1 exec zsh -li" # CMD (and ConEmu) quoted
Side note: My Windows Terminal profiles for wsl actually used to look very similar to this. I've streamlined them a bit, but I still set an environment variable (the title I want for the tab) before starting my shell (fish) and tmux.
Side note #2: My original answer used two separate ZDOTDIR's and corresponding directories. This would still be useful if you wanted to execute different commands (as opposed to aliases). In that case, create additional directories, and point to them by changing the ZDOTDIR. Just put your commands in the modified .zshrc in the corresponding directory.
Run the commands at ConEmu Startup
The easy part. Create a conemu_start.txt anywhere (and any name), really:
>%windir%\system32\wsl.exe -cur_console:t:"MyCommand1" -cur_console:pm:/mnt ~ --distribution Ubuntu-20.04 --exec sh -c "ZDOTDIR=$HOME/.local/share/zsh/startup/run_alias RUN_ALIAS=mycommand1 exec zsh -li"
>%windir%\system32\wsl.exe -cur_console:t:"MyCommand2" -cur_console:pm:/mnt ~ --distribution Ubuntu-20.04 --exec sh -c "ZDOTDIR=$HOME/.local/share/zsh/startup/run_alias RUN_ALIAS=mycommand2 exec zsh -li"
In ComEmu, set Settings -> Startup -> Tasks file to this file.
That should do it. Starting ConEmu should now open two tabs with zsh, one for each of these aliases/commands.
Alternative: Modify existing .zshrc to run an alias based on an environment variable
Add the following to the bottom of ~/.zshrc:
if [ -v RUN_ALIAS ] && alias | grep -q "^$RUN_ALIAS="; then
eval "$RUN_ALIAS"
fi
Test it with:
RUN_ALIAS=mycommand1 exec zsh -li
And then change the conemu_start.txt to:
>%windir%\system32\wsl.exe -cur_console:t:"MyCommand1" -cur_console:pm:/mnt ~ --distribution Ubuntu-20.04 --exec sh -c "RUN_ALIAS=mycommand1 exec zsh -li"
>%windir%\system32\wsl.exe -cur_console:t:"MyCommand2" -cur_console:pm:/mnt ~ --distribution Ubuntu-20.04 --exec sh -c "RUN_ALIAS=mycommand2 exec zsh -li"

Related

Unable to export env variable from script

I'm currently struggling with running a .sh script I'm trying to trigger from Jenkins.
Within the Jenkins "execute shell" section, I'm connecting to a remote server (The Jenkins agent does not have right OS to build what I need.), using:
cp -r . /to/shared/drive/to/have/access/on/remote
ssh -t -t username#servername << EOF
cd /to/shared/drive/to/have/access/on/remote
source build.sh dev
exit
EOF
Inside build.sh, I'm exporting R_LIBS to build a package for different R versions.
...
for path in "${!rVersionPaths[#]}"; do
export R_LIBS="${path}"
Rscript -e 'install.packages(c("someDependency", "someOtherDependency"), repos="http://cran.r-project.org");'
...
Setting R_LIBS should functions here like setting lib within install.packages(...). For some reason the R_LIBS export doesn't get picked up. Also setting other env variables like http_proxy are ignored. This causes any requests outside the network to fail.
Is there any particular way of achieving this?
Maybe pass those variables with env, like
env R_LIBS="${path}" Rscript -e 'install.packages(c("someDependency", .....
Well i'm not able to comment on the question, so posting it as answer.
I had similar problem when calling remote shell script from Jenkins, the problem was somehow bash_profile variables were not loaded when called the script from Jenkins but locally it worked. Loading the bash profile in ssh connection solved it for me.
Add source to bash_profile in build.sh
. ~/.bash_profile OR source ~/.bash_profile
Or
Reload bash_profile in ssh connection
`ssh -t -t username#servername << EOF
. ~/.bash_profile
your commands here
exit
EOF
You can set that variable in the same command line like this:
R_LIBS="${path}" Rscript -e \
'install.packages(c("someDependency", "someOtherDependency"), repos="http://cran.r-project.org");'
It's possible to append more variables in this way. Note that this will set those environment variables only for the command being called after them (and its children processes as well).
You said that "R_LIBS export doesn't get picked up". Question Is the value UNSET? Or is it set to some other value & you are trying to override it?
It is possible that SSH may be invoking "/bin/sh -c". Based on the second answer to: Why does 'cd' command not work via SSH?, you can simplify the SSH command and explicitly invoke the build.sh script in Bash:
cp -r . /to/shared/drive/to/have/access/on/remote
ssh -t -t username#servername "cd /to/shared/drive/to/have/access/on/remote && bash -f build.sh dev"
This makes the SSH invocation more similar to invoking the command within a remote interactive shell. (You can avoid sourcing scripts and exporting variables.)
You don't need to export R_LIBSor env R_LIBS when it is possible to prefix any command with local environment variable overrides (agrees with Luis' answer):
...
for path in "${!rVersionPaths[#]}"; do
R_LIBS="${path}" Rscript -e 'install.packages(c("someDependency", "someOtherDependency"), repos="http://cran.r-project.org");'
...
The Rscript may be doing a lot with env vars. You can verify that you are setting the R_LIBS env var by replacing Rscript with the env command and observe the output:
...
for path in "${!rVersionPaths[#]}"; do
R_LIBS="${path}" env
...
According to this manual "Initialization at Start of an R Session", Rscript looks in several places to load "site and user files":
$R_PROFILE
$R_HOME/etc/Renviron
$R_HOME/etc/Renviron.site
$R_ENVIRON_USER
$R_PROFILE_USER
./.Rprofile
$HOME/.Rprofile
./.RData
The "Examples" section of that manual shows this:
## Not run:
## Example ~/.Renviron on Unix
R_LIBS=~/R/library
PAGER=/usr/local/bin/less
If you add the --vanilla command-line option to ignore all of these files, then you may get different results and know something in the site/init/environ files is affecting your R_LIBS! I cannot run this system myself. Hopefully we have given you some areas to investigate.
You probably don't want to source build.sh, just invoke it directly (i.e. remove the source command).
By source-ing the file your script is executed in the SSH shell (likely sh) rather than by bash, which it sounds like is what you intended.

Zsh inherit xtrace option

Similar to this question: bash recursive xtrace, but for Zsh.
How can I make all the Zsh subshells inherit the xtrace option?
For example, if script1.sh is calling ./script2.sh and I run zsh -x script1.sh, I want script2.sh to also have the xtrace mode enabled.
For Bash, the answer is to export the SHELLOPTS variable.
Is there a solution for Zsh?
You can take advantage of startup/shutdown files in Zsh (see man zshall): Zsh will always read (and execute) the file $ZDOTDIR/.zshenv at startup (if ZDOTDIR is unset, HOME is used instead). So you can put set -x in $ZDOTDIR/.zshenv and every Zsh script will run with xtrace mode enabled.
This is how I used it in a script:
env_dir="$(mktemp -d)"
echo "set -x" > "${env_dir}/.zshenv"
export ZDOTDIR="${env_dir}"
zsh "$#"
unset ZDOTDIR
rm -rf "${env_dir}"
In fact, this solution can be used for Bash as well, using the BASH_ENV variable, which points to a file that will, similarly, be executed when Bash starts.

urxvt -cd "/abs/path" not loading user zsh config

When I run urxvt -cd "/absolute/path" to start a terminal in a directory, it doesn't load my user zsh settings, it only loads the global ones in /etc.
Here's some context: Running latest stable versions of rxvt-unicode and zsh (on Arch Linux). I've got ZDOTDIR=~/.zsh in case that makes a difference (but I doubt it, since I tried symlinking ~/.zshrc to ~/.zsh/.zshrc.) If I just run urxvt then it works fine, but it's with the -cd flag that it messes up.
The reason I'm trying to do this is to start a terminal in the current location from Thunar AND have it read my user zsh configuration file. So if you know another way of doing this then that will work too.
Try adding -ls to its options to run it as login shell, like:
urxvt -ls -cd "/absolute/path"
Otherwise it will spawn a subshell. If that doesn't work for you, it still possible to use:
urxvt -e /where/is/your/zsh -i -l -c "cd /where/you/want/it"
Or (regarding the Thunar custom action):
urxvt -cd %f -e /where/is/your/zsh -i -l

How to always have the same current directory in VIm and in Terminal?

I would like to my terminal current directory follows my VIM one.
Example:
In TERMINAL:
> pwd
=> /Users/rege
> vim
Then in VIM
:cd /Users/rege/project
<Ctrl-z>(for suspend)
In terminal
> pwd
=> /Users/rege/project
I`m using MacOS, zsh, tmux.
I need this because when Im trying to use tags in VIM, tags are check in project from my terminal directory not vim one.
So I need to change terminal current directory always when I change VIM current directory.
What kind of command do you issue in your shell after you suspend Vim? Isn't Vim's :!command enough?
With set autochdir, Vim's current directory follows you as you jump from file to file. With this setting, a simple :!ctags -R . will always create a tags file in the directory of the current file.
Another useful setting is set tags=./tags,tags;$HOME which tells Vim to look for a tags file in the directory of the current file, then in the "current directory" and up and up until it reaches your ~/. You might modify the endpoint to suit your needs. This allows you to use a tags at the root of your project while editing any file belonging to the project.
So, basically, you can go a long way without leaving Vim at all.
If you really need to go back to the shell to issue your commands, :shell (or :sh) launchs a new shell with Vim's current directory. When you are done, you only have to $ exit to go back to Vim:
$ pwd
/home/romainl
$ vim
:cd Projects
:sh
$ pwd
/home/romainl/Projects
$ exit
In bash or zsh and on Unix you can do this: current working directory of the process is represented in /proc/{PID}/cwd as a symlink to a real directory. Speaking about zsh the following code will do the job:
function precmd()
{
emulate -L zsh
(( $#jobstates == 1 )) || return
local -i PID=${${${(s.:.)${(v)jobstates[1]}}[3]}%\=*}
cd $(readlink /proc/$PID/cwd)
}
. Note: with this code you won’t be able to pernamently switch directories in terminal anymore, only in vim or for duration of one command (using cd other-dir && some command).
Note 2: I have no idea how to express this in bash. The straightforward way is to get PIDs of all children of the shell (using ps --ppid $$ -o CMD), filter out the ps process (it will be shown as a child as well), check that there is only one other child and use its PID like in the last line above. But I am pretty sure there is a better way using some shell builtins like I did with zsh’s $jobstates associative array. I also don’t remember what is the analogue of precmd in bash.
Another idea would be making vim save its current directory into some file when you do <C-z> and make shell read this in precmd:
" In .vimrc:
function s:CtrlZ()
call writefile([fnamemodify('.', ':p')], $CWDFILE, 'b')
return "\<C-z>"
endfunction
nnoremap <expr> <C-z> <SID>CtrlZ()
# In .zshrc
function vim()
{
local -x CWDFILE=~/.workdirs/$$
test -d $CWDFILE:h || mkdir $CWDFILE:h
vim $#
}
function precmd()
{
local CWDFILE=~/.workdirs/$$
test -e $CWDFILE && cd "$(cat $CWDFILE)"
}
. It should be easier to port above code to bash.
you can open a new terminal like this
:!xterm -e bash -c "cd %:p:h;bash" &
actually I write this in my .vimrc
nmap <F3> :!xterm -e bash -c "cd %:p:h;bash" &<CR> | :redraw!
For bash users coming by:
Vim: Save pwd at <c-z> (with map and getpwd()).
Bash: Before prompt command, goto directory indicated by vim with PROMPT_COMMAND.
.bashrc
PROMPT_COMMAND='read -r line 2>/dev/null </tmp/cd_vim'\
'&& > /tmp/cd_vim && cd ${line##\r};'$PROMPT_COMMAND
vimrc
function! s:CtrlZ() call writefile([getcwd(),''], '/tmp/cd_vim', 'b')
return "\<C-z>"
endfunction
nnoremap <expr> <C-z> <SID>CtrlZ()
This is ZyX answer edited for bash https://stackoverflow.com/a/12241861/2544873

Whats the difference between running a shell script as ./script.sh and sh script.sh

I have a script that looks like this
#!/bin/bash
function something() {
echo "hello world!!"
}
something | tee logfile
I have set the execute permission on this file and when I try running the file like this
$./script.sh
it runs perfectly fine, but when I run it on the command line like this
$sh script.sh
It throws up an error. Why does this happen and what are the ways in which I can fix this.
Running it as ./script.sh will make the kernel read the first line (the shebang), and then invoke bash to interpret the script. Running it as sh script.sh uses whatever shell your system defaults sh to (on Ubuntu this is Dash, which is sh-compatible, but doesn't support some of the extra features of Bash).
You can fix it by invoking it as bash script.sh, or if it's your machine you can change /bin/sh to be bash and not whatever it is currently (usually just by symlinking it - rm /bin/sh && ln -s /bin/bash /bin/sh). Or you can just use ./script.sh instead if that's already working ;)
If your shell is indeed dash and you want to modify the script to be compatible, https://wiki.ubuntu.com/DashAsBinSh has a helpful guide to the differences. In your sample it looks like you'd just have to remove the function keyword.
if your script is at your present working directory and you issue ./script.sh, the kernel will read the shebang (first line) and execute the shell interpreter that is defined. you can also call your script.sh by specifying the path of the interpreter eg
/bin/bash myscript.sh
/bin/sh myscript.sh
/bin/ksh myscript.sh etc
By the way, you can also put your shebang like this (if you don't want to specify full path)
#!/usr/bin/env sh
sh script.sh forces the script to be executed within the sh - shell.
while simply starting it from command line uses the shell-environemnt you're in.
Please post the error message for further answers.
Random though on what the error may be:
path specified in first line /bin/bash is wrong -- maybe bash is not installed?

Resources