WSL run linux from windows without spawning a cmd-window - windows

I have WSL bash running in a cmd. I don't use it for anything, it just hangs there to keep the WSL system alive.
When I start X applications:
bash -c "DISPLAY=:0 xmessage hello &"
I get this result:
I can close down the command window without any problems, but it's rather annoying.
How can run commands without getting this cmd window every time?

Here's a simpler solution, which, however, requires a WSH-based helper script, runHidden.vbs (see bottom section):
wscript .\runHidden.vbs bash -c "DISPLAY=:0 xmessage 'hello, world'"
To apply #davv's own launch-in-background technique to avoid creating a new bash instance every time:
One-time action (e.g., at boot time): launch a hidden, stay-open bash window. This spawns 2 bash processes: the Windows bash.exe process that owns the console window, and the WSL bash process (owned by the WSL init singleton), which is then available for servicing background commands.
wscript .\runHidden.vbs bash # hidden helper instance for servicing background commands
For every X Window-launching command: Terminate each command with & to have it be run by the hidden WSL bash instance asynchronously, without keeping the invoking bash instance alive:
wscript .\runHidden.vbs bash -c "DISPLAY=:0 xmessage 'hello, world' &"
runHidden.vbs source code:
' Simple command-line help.
select case WScript.Arguments(0)
case "-?", "/?", "-h", "--help"
WScript.echo "Usage: runHidden executable [...]" & vbNewLine & vbNewLine & "Runs the specified command hidden (without a visible window)."
WScript.Quit(0)
end select
' Separate the arguments into the executable name
' and a single string containing all arguments.
exe = WScript.Arguments(0)
sep = ""
for i = 1 to WScript.Arguments.Count -1
' Enclose arguments in "..." to preserve their original partitioning, if necessary.
if Instr(WScript.Arguments(i), " ") > 0 then
args = args & sep & """" & WScript.Arguments(i) & """"
else
args = args & sep & WScript.Arguments(i)
end if
sep = " "
next
' Execute the command with its window *hidden* (0)
WScript.CreateObject("Shell.Application").ShellExecute exe, args, "", "open", 0
Even when launched from a GUI app (such as via the Run dialog invoked with Win+R), this will not show a console window.
If your system is configured to execute .vbs scripts with wscript.exe by default (wscript //h:wscript /s, which, I think, is the default configuration), you can invoke runHidden.vbs directly, and if you put it in your %PATH%, by filename (root) only: runHidden ....
Note that use of the script is not limited to console applications: even GUI applications can be run hidden with it.

There's another simple solution, it requires an external executable though. It has no dependencies and was recommended by aseering on GitHub.
you can launch bash via run.exe: run.exe bash.exe -c "<whatever Linux command>". (run.exe is available here: http://www.straightrunning.com/projectrun/ , make sure you download the 64-bit version, the 32-bit version will not be able to find or run bash).
With run on the search PATH, you can just call
run bash -c "DISPLAY=:0 xmessage hello"

So I just made this workaround for now. I really hope that there's a better way than this, but here it goes:
In the command prompt that lives purely to keep WSL alive, I have this script running:
wsl_run_server
#!/bin/bash
set -e
nc -kl 127.0.0.1 15150 | sh
And then I have this command to execute commands in background:
wsl_run_command
if ! pidof -x bin/wsl_run_server; then
echo wsl_run_server isnt running!
exit 1
fi
echo \($#\) \& | nc localhost 15150
from windows I then call:
bash -c "DISPLAY=:0 ~/bin/wsl_run_command xmessage hello"

There no longer is a need to have that command window pop up anymore with WSLg recently added to the mix. You just can call bash using WSLg, like so (I use Ubuntu currently in WSL):
wslg ~ -d Ubuntu bash
This will create a BASH session that will just sit there without being seen. Alternatively, you can do what I do and run a few services that stay running. I created a script that checks for running services, and if it doesn't find them running, will run them. Create the file in /usr/bin:
sudo touch /usr/bin/start-wsl-services
sudo nano /usr/bin/start-wsl-services
Past the following into the file:
#!/bin/bash
# Check for and run System-wide DBus service.
SERVICE="dbus-daemon"
if pgrep -x "$SERVICE" >/dev/null
then
pgrep -a "$SERVICE"
else
sudo /etc/init.d/dbus start
pgrep -a "$SERVICE"
fi
# Check for and run CUPS Printing Service.
SERVICE="cupsd"
if pgrep -x "$SERVICE" >/dev/null
then
pgrep -a "$SERVICE"
else
sudo /etc/init.d/cups start
pgrep -a "$SERVICE"
fi
# Check for and start Freshclam CLAMAV Update service.
SERVICE="freshclam"
if pgrep -x "$SERVICE" >/dev/null
then
pgrep -a "$SERVICE"
else
sudo /etc/init.d/clamav-freshclam start
pgrep -a "$SERVICE"
fi
# Check for and start SANED Scanner service.
SERVICE="saned"
if pgrep -x "$SERVICE" >/dev/null
then
pgrep -a "$SERVICE"
else
sudo /etc/init.d/saned start
pgrep -a "$SERVICE"
fi
# Check for and start screen-cleanup service.
SERVICE="screen-cleanup"
if pgrep -x "$SERVICE" >/dev/null
then
pgrep -a "$SERVICE"
else
sudo /etc/init.d/screen-cleanup start
pgrep -a "$SERVICE"
fi
# Check for and start Preload service.
SERVICE="preload"
if pgrep -x "$SERVICE" >/dev/null
then
pgrep -a "$SERVICE"
else
sudo /etc/init.d/preload start
pgrep -a "$SERVICE"
fi
# Prestart LibreOffice twice for faster loading.
#/usr/bin/libreoffice --terminate_after_init
#sleep 5
#/usr/bin/libreoffice --terminate_after_init
# Check for error, make sure all functions called and run, and pass the result on to calling process.
if [[ $? -ne 0 ]] ; then
exit 1
else
exit 0
fi
Save and exit the file, and then make it executable:
sudo chmod +x /usr/bin/start-wsl-services
I then call this using a shortcut that runs a startup script at startup. Or you can just run it manually. The command I use in the startup script is:
C:\Windows\System32\wslg.exe -d Ubuntu -- /usr/bin/start-wsl-services
The startup command script I use (named StartWSL.cmd) is as follows:
#echo off
echo Starting WSL Linux...
:RETRY
C:\Windows\System32\wslg.exe -d Ubuntu -- /usr/bin/start-wsl-services
REM - C:\Windows\System32\bash.exe -c '/usr/bin/start-wsl-services'
IF %ERRORLEVEL% NEQ 0 (GOTO RETRY)
REM - Allow time to see all results.
timeout /t 5 /nobreak >NUL
REM - Uncomment below line for troubleshooting.
REM - pause
exit 0
And that's how I now keep WSL running in the background on Windows 11, and similar to how I did it on Windows 10.

run command background
screen -dmS [name] [command]
example
screen -dmS gui bash -c "DISPLAY=:0 xmessage hello"
create a shortcut on windows desktop(run in wsl)
wslusc screen -dmS gui bash -c "DISPLAY=:0 xmessage hello"

Related

Execute a script through ssh and store its pid in a file on the remote machine [duplicate]

This question already has answers here:
How to pass argument with exclamation mark on Linux?
(3 answers)
Closed 3 years ago.
I am not able to store any PID in a file on the remote machine when running a script in background through ssh.
I need to store the PID of the script process in a file in purpose to kill it whenever needed. When running the exact command on the remote machine it is working, why through ssh it is not working so ?
What is wrong with the following command:
ssh user#remote_machine "nohup ./script.sh > /dev/null 2>&1 & echo $! > ./pid.log"
Result: The file pid.log is created but empty.
Expected: The file pid.log should contain the PID of the running script.
Use
ssh user#remote_machine 'nohup ./script.sh > /dev/null 2>&1 & echo $! > ./pid.log'
OR
ssh user#remote_machine "nohup ./script.sh > /dev/null 2>&1 & echo \$! > ./pid.log"
Issue:
Your $! was getting expanded locally, before calling ssh at all.
Worse, before calling the ssh command, if there was a process stared in the background, then $! would have expanded to that and complete ssh command would have got expanded to contain that PID as argument to echo.
e.g.
$ ls &
[12342] <~~~~ This is the PID of ls
$ <~~~~ Prompt returns immediately because ls was stared in background.
myfile1 myfile2 <~~~~ Output of ls.
[1]+ Done ls
#### At this point, $! contains 12342
$ ssh user#remote "command & echo $! > pidfile"
# before even calling ssh, shell internally expands it to:
$ ssh user#remote "command & echo 12342 > pidfile"
And it will put the wrong PID in the pidfile.

Script stuck during read line when script is executed remotely

I want to have one script which starts a services in another server.
I have tested that the script works as expected in the server where the server is going to run.
This is the code which starts the service and monitors the log until it is in the startup process:
pkill -f "$1"
nohup java -jar -Dspring.profiles.active=$PROFILE $1 &
tail -n 0 -f nohup.out | while read LOGLINE
do
echo $LOGLINE
[[ "${LOGLINE}" == *"$L_LOG_STRING"* ]] && pkill -P $$ tail
done
This works fine as long as I execute that from that machine.
Now I want to call that script from another server:
#!/usr/bin/env bash
DESTINATION_SERVER=$1
ssh root#$DESTINATION_SERVER /bin/bash << EOF
echo "Restarting first service..."
/usr/local/starter.sh -s parameter
echo "Restarting second service..."
/usr/local/starter.sh -s parameter2
EOF
Well, everytime I try that the script of the remote server gets stuck in the "while READ" loop. But as I said, when I execute it locally from the server works fine, and in my "not simplified script" I´m not using any system variable or similar.
Update: I just tried to simplify the code even more with the following lines in the first scenario:
pkill -f "$1"
nohup java -jar -Dspring.profiles.active=$PROFILE $1 &
tail -n 0 -f nohup.out | sed "/$L_LOG_STRING/ q"
I'd say the problem is some how in the "|" through ssh, but I still can find why.
it seems that the problem comes from not having an interactive console when you execute the ssh command, therefore the nohup command behaves strangly.
I could solve it in two ways, outputing the code to the file explicitly:
"nohup java -jar -Dspring.profiles.active=test &1 >> nohup.out &"
instead of:
"nohup java -jar -Dspring.profiles.active=test &1&"
Or changing the way I access via ssh adding the tt option (just one did not work):
ssh -tt root#$DESTINATION_SERVER /bin/bash << EOF
But this last solution could lead to other problems with some character, so unless someone suggests another solution that is my patch which makes it work.

shell forcing demonized program to start without output

I'm trying to run a script that is required to have an exit code of 0. Unfortunalty I cannot use an init.d or other startup script to control this this, so I must make this work.
Basically if I understand AWS's docs correctly (elastic beanstalk), I need be able to run the following two commands and exit with a 0 and provide no other output to stdout.
As the root user I need to cd to a particular dir and run these two commands:
pkill -f que
bundle exec que
In my actually script I have:
#!/usr/bin/env bash
su -s /bin/bash -c "cd /some/dir && nohup pkill -f que &>/dev/null &"
sleep 10
su -s /bin/bash -c "cd /some/dir && nohup bundle exec que &"
Which still causes this error to be raised:
returned non-zero exit status 1 (Executor::NonZeroExitStatus)
Any tips for how to silently run those commands correctly?
I'm also looking at these for ideas:
https://blog.eq8.eu/article/aws-elasticbeanstalk-hooks.html
http://www.dannemanne.com/posts/post-deployment_script_on_elastic_beanstalk_restart_delayed_job
But its still not clear to me how this is supposed to exit successfully
Perhaps I'm missing something, but wouldn't this be easily solved by using two shell scripts? One with cd, pkill, and bundle. Call this script (foo.sh) something like:
#!/usr/bin/env bash
su -c ./foo.sh > /dev/null 2>&1 < /dev/null
exit 0

AppleScript won't run shell script as is in Terminal

I have a .sh script that works fine if I run it in Terminal using "/Volumes/MEDIA/SERVER/SYNC.sh"
But I can not get it to run the same in AppleScript Editor using:
do shell script "/Volumes/MEDIA/SERVER/SYNC.sh"
Also tried the above with bash in front, sh in front.
The shell script (SYNC.sh)
#!/bin/bash
login="uhh"
pass="uhh"
host="uhh.com"
remote_dir='~/private/sync'
local_dir="/Volumes/MEDIA/_/SYNCING"
base_name="$(basename "$0")"
lock_file="/tmp/$base_name.lock"
trap "rm -f $lock_file" SIGINT SIGTERM
if [ -e "$lock_file" ]
then
echo "$base_name is running already."
exit
else
touch "$lock_file"
lftp -p 22 -u "$login","$pass" sftp://"$host" << EOF
set sftp:auto-confirm yes
set mirror:use-pget-n 5
mirror -c -P5 --Remove-source-files --log="/Volumes/MEDIA/SERVER/LOGS/$base_name.log" "$remote_dir" "$local_dir"
quit
EOF
# MOVE FINISHED FILES INTO DIRECTORY FOR CONVERSION
mv /Volumes/MEDIA/_/SYNCING/movies/* /Volumes/MEDIA/SEEDBOX/MOVIES
mv /Volumes/MEDIA/_/SYNCING/tvshows/* /Volumes/MEDIA/SEEDBOX/TVSHOWS
mv /Volumes/MEDIA/_/SYNCING/books/* /Volumes/MEDIA/SEEDBOX/BOOKS
mv /Volumes/MEDIA/_/SYNCING/music/* /Volumes/MEDIA/SEEDBOX/MOVIES
# SHOW COMPLETED NOTIFICIATION
osascript -e 'display notification "Sync completed" with title "SEEDB0X"'
rm -f "$lock_file"
trap - SIGINT SIGTERM
exit
fi
By not 'the same' what happens is only the
osascript -e 'display notification "Sync completed" with title "SEEDB0X"'
is run. With the script running through Terminal that only appears once syncing is done.
Thanks for any help!
Did you install lftp yourself? I don't have a Mac handy to check if it's in Mac OS X by default or not. If you installed it, then it probably isn't in the PATH of the AppleScript environment and the bash script can't find it when run from there.
If this is the case, then you'll have to either:
Fully qualify the path to 'lftp' (eg, "/usr/local/bin/lftp" or where ever it actually is)
or
Append to the PATH environment variable as used by AppleScript (or early in your bash script).
I think I'd go for option 1. Option 2 is overkill and more likely to adversely affect other things at other times.
PS. If you don't know where 'lftp' is installed, type 'which lftp' in the terminal.

nohup doesn't work when used with double-ampersand (&&) instead of semicolon (;)

I have a script that uses ssh to login to a remote machine, cd to a particular directory, and then start a daemon. The original script looks like this:
ssh server "cd /tmp/path ; nohup java server 0</dev/null 1>server_stdout 2>server_stderr &"
This script appears to work fine. However, it is not robust to the case when the user enters the wrong path so the cd fails. Because of the ;, this command will try to run the nohup command even if the cd fails.
The obvious fix doesn't work:
ssh server "cd /tmp/path && nohup java server 0</dev/null 1>server_stdout 2>server_stderr &"
that is, the SSH command does not return until the server is stopped. Putting nohup in front of the cd instead of in front of the java didn't work.
Can anyone help me fix this? Can you explain why this solution doesn't work? Thanks!
Edit: cbuckley suggests using sh -c, from which I derived:
ssh server "nohup sh -c 'cd /tmp/path && java server 0</dev/null 1>master_stdout 2>master_stderr' 2>/dev/null 1>/dev/null &"
However, now the exit code is always 0 when the cd fails; whereas if I do ssh server cd /failed/path then I get a real exit code. Suggestions?
See Bash's Operator Precedence.
The & is being attached to the whole statement because it has a higher precedence than &&. You don't need ssh to verify this. Just run this in your shell:
$ sleep 100 && echo yay &
[1] 19934
If the & were only attached to the echo yay, then your shell would sleep for 100 seconds and then report the background job. However, the entire sleep 100 && echo yay is backgrounded and you're given the job notification immediately. Running jobs will show it hanging out:
$ sleep 100 && echo yay &
[1] 20124
$ jobs
[1]+ Running sleep 100 && echo yay &
You can use parenthesis to create a subshell around echo yay &, giving you what you'd expect:
sleep 100 && ( echo yay & )
This would be similar to using bash -c to run echo yay &:
sleep 100 && bash -c "echo yay &"
Tossing these into an ssh, and we get:
# using parenthesis...
$ ssh localhost "cd / && (nohup sleep 100 >/dev/null </dev/null &)"
$ ps -ef | grep sleep
me 20136 1 0 16:48 ? 00:00:00 sleep 100
# and using `bash -c`
$ ssh localhost "cd / && bash -c 'nohup sleep 100 >/dev/null </dev/null &'"
$ ps -ef | grep sleep
me 20145 1 0 16:48 ? 00:00:00 sleep 100
Applying this to your command, and we get
ssh server "cd /tmp/path && (nohup java server 0</dev/null 1>server_stdout 2>server_stderr &)"
or:
ssh server "cd /tmp/path && bash -c 'nohup java server 0</dev/null 1>server_stdout 2>server_stderr &'"
Also, with regard to your comment on the post,
Right, sh -c always returns 0. E.g., sh -c exit 1 has error code
0"
this is incorrect. Directly from the manpage:
Bash's exit status is the exit status of the last command executed in
the script. If no commands are executed, the exit status is 0.
Indeed:
$ bash -c "true ; exit 1"
$ echo $?
1
$ bash -c "false ; exit 22"
$ echo $?
22
ssh server "test -d /tmp/path" && ssh server "nohup ... &"
Answer roundup:
Bad: Using sh -c to wrap the entire nohup command doesn't work for my purposes because it doesn't return error codes. (#cbuckley)
Okay: ssh <server> <cmd1> && ssh <server> <cmd2> works but is much slower (#joachim-nilsson)
Good: Create a shell script on <server> that runs the commands in succession and returns the correct error code.
The last is what I ended up using. I'd still be interested in learning why the original use-case doesn't work, if someone who understands shell internals can explain it to me!

Resources