I have a script where it will check if background_logging is true, if it is then I want the rest of the script to run in a new detached screen.
I have tried using this: exec screen -dmS "alt-logging" /bin/bash "$0";. This will sometimes create the screen, etc. but other times nothing will happen at all. When it does create a screen, it doesn't run the rest of the script file and when I try to resume the screen it says it's (Dead??).
Here is the entire script, I have added some comments to explain better what I want to do:
#!/bin/bash
# Configuration files
config='config.cfg'
source "$config"
# If this is true, run the rest of the script in a new screen.
# $background_logging comes from the configuration file declared above (config).
if [ $background_logging == "true" ]; then
exec screen -dmS "alt-logging" /bin/bash "$0";
fi
[ $# -eq 0 ] && { echo -e "\nERROR: You must specify an alt file!"; exit 1; }
# Logging script
y=0
while IFS='' read -r line || [[ -n "$line" ]]; do
cmd="screen -dmS alt$y bash -c 'exec $line;'"
eval $cmd
sleep $logging_speed
y=$(( $y + 1 ))
done < "$1"
Here are the contents of the configuration file:
# This is the speed at which alts will be logged, set to 0 for fast launch.
logging_speed=5
# This is to make a new screen in which the script will run.
background_logging=true
The purpose of this script is to loop through each line in a text file and execute the line as a command. It works perfectly fine when $background_logging is false so there are no issues with the while loop.
As described, it's not entirely possible. Specifically what is going on in your script: when you exec you replace your running script code with that of screen.
What you could do though is to start screen, figure out few details about it and redirect your console scripts in/output to it, but you won't be able to reparent your running script to the screen process as if started there. Something like for instance:
#!/bin/bash
# Use a temp file to pass cat's parent pid out of screen.
tempfile=$(tempfile)
screen -dmS 'alt-logging' /bin/bash -c "echo \$\$ > \"${tempfile}\" && /bin/cat"
# Wait to receive that information on the outside (it may not be available
# immediately).
while [[ -z "${child_cat_pid}" ]] ; do
child_cat_pid=$(cat "${tempfile}")
done
# point stdin/out/err of the current shell (rest of the script) to that cat
# child process
exec 0< /proc/${child_cat_pid}/fd/0
exec 1> /proc/${child_cat_pid}/fd/1
exec 2> /proc/${child_cat_pid}/fd/2
# Rest of the script
i=0
while true ; do
echo $((i++))
sleep 1
done
Far from perfect and rather messy. It could probably be helped by using a 3rd party tool like reptyr to grab console of the script from inside the screen. But cleaner/simpler yet would be to (when desired) start the code that should be executed in that screen session after it has been established.
That said. I'd actually suggest to take a step back and ask, what exactly is it that you're trying to achieve and why exactly would you like to run your script in screen. Are you planning to attach/detach to/from it? Because if running a long term process with a detached console is what you are after, nohup might be a bit simpler route to go.
Related
I'm currently writing a bash script that would create multiple shell instances (with screen command) and execute a subprogram.
The problem is when I try to interrupt the subprogram, it interrupts the screen instance too. I already searched for trap command on internet with SIGINT but I don't really know how to use it in this case.
Here is my code to show you how do I create the screen:
#!/bin/bash
#ALL PATHS ARE DECLARED HERE.
declare -A PATHS; declare -a orders;
PATHS["proxy"]=/home/luna/proxy/HydraProxy; orders+=( "proxy" )
PATHS["bot"]=/home/luna/bot; orders+=( "bot" )
#LAUNCH SERVERS
SERVERS=/home/luna/servers
cd "$SERVERS"
for dir in */; do
d=$(basename "$dir")
PATHS["$d"]="$(realpath $dir)"; orders+=( "$d" )
done
for name in "${orders[#]}"; do
if ! screen -list | grep -q "$name"; then
path="${PATHS[$name]}"
cd "$path"
screen -dmS "$name" ./start.sh
echo "$name CREATED AT $path"
sleep 2
else
echo "SCREEN $name IS ALREADY CREATED"
fi
done
Could you help me more to find a solution please ? Thank you very much for your time.
Each of your screen instances is created to run a single command, start.sh. When this command terminates, for instance when you interrupt it, the screen will have done its job and terminate. The reason for this is that screen runs shell scripts directly in a non-interactive shell, rather than spawning a new interactive shell and running it there.
If you wanted to run start.sh inside an interactive shell in each screen, you'd do something like this:
screen -dmS "$name" /bin/bash -i
screen -S "$name" -X stuff "./start.sh^M"
The ^M is needed as it simulates pressing enter in your shell within screen.
If you use this, then when you interrupt a script within screen, you will still be left with an interactive prompt afterward to deal with as you see fit.
In Windows when I double-click a Batch script, it will automatically open a terminal window and show me what's happening. If I were to double-click a bash script in Linux, a terminal window does not open to show me what is happening; it runs in the background. I have seen that one can use one script to launch another script in a new terminal window with x-terminal-emulator -e "./script.sh", but is there any bash command I can put into the same (one) script.sh so that it will open a terminal and show me what's happening (or if I need to answer y/n questions)?
You can do something similar to what Slax
developers do in their bootinst.sh:
#!/usr/bin/env sh
#
# If you see this file in a text editor instead of getting it executed,
# then it is missing executable permissions (chmod). You can try to set
# exec permissions for this file by using: chmod a+x bootinst.sh
#
# Scrolling down will reveal the actual code of this script.
#
# if we're running this from X, re-run the script in konsole or xterm
if [ "$DISPLAY" != "" ]; then
if [ "$1" != "--rex" -a "$2" != "--rex" ]; then
konsole --nofork -e /bin/sh $0 --rex 2>/dev/null || xterm -e /bin/sh $0 --rex 2>/dev/null || /bin/sh $0 --rex 2>/dev/null
exit
fi
fi
# put contents of your script here
echo hi
# do not close the terminal immediately, let user look at the results
echo "Press Enter..."
read junk
This script would run correctly both when started in graphical
environment and in tty. It tries to restart the script inside
konsole and xterm and but if it doesn't find neither of them it
will simply run in the background.
Background
I have a Bash script that requires user input. It can be run by calling it in a terminal or by pressing a keyboard shortcut registered in the i3 (or Sway) config file as follows:
bindsym --release $mod+Shift+t exec /usr/local/bin/myscript
The Problem
I know I can use read -p to prompt in a terminal, but that obviously won't work when the script is triggered through the keybinding. In that case I can use something like Yad to create a GUI, but I'm unable to detect when it's not "in a terminal". Essentially I want to be able to do something like this:
if [ $isInTerminal ]; then
read -rp "Enter your username: " username
else
username=$(yad --entry --text "Enter your username:")
fi
How can I (automatically) check if my script was called from the keybinding, or is running in a terminal? Ideally this should be without user-specified command-line arguments. Others may use the script and as such, I'd like to avoid introducing any possibility of user error via "forgotten flags".
Attempted "Solution"
This question suggests checking if stdout is going to a terminal, so I created this test script:
#!/usr/bin/env bash
rm -f /tmp/detect.log
logpass() {
echo "$1 IS opened on a terminal" >> /tmp/detect.log
}
logfail() {
echo "$1 IS NOT opened on a terminal" >> /tmp/detect.log
}
if [ -t 0 ]; then logpass stdin; else logfail stdin; fi
if [ -t 1 ]; then logpass stdout; else logfail stdout; fi
if [ -t 2 ]; then logpass stderr; else logfail stderr; fi
However, this doesn't solve my problem. Regardless of whether I run ./detect.sh in a terminal or trigger it via keybind, output is always the same:
$ cat /tmp/detect.log
stdin IS opened on a terminal
stdout IS opened on a terminal
stderr IS opened on a terminal
It seems like the easiest way to solve your actual problem would be to change the i3 bind to
bindsym --release $mod+Shift+t exec /usr/local/bin/myscript fromI3
and do
if [[ -n "$1" ]]; then
echo "this was from a keybind"
else
echo "this wasn't from a keybind"
fi
in your script.
False Positive
As most results on Google would suggest, you could use tty which would normally return "not a tty" when it's not running in a terminal. However, this seems to differ with scripts called via bindsym exec in i3/Sway:
/dev/tty1 # From a keybind
/dev/pts/6 # In a terminal
/dev/tty2 # In a console
While tty | grep pts would go part way to answering the question, it is unable to distinguish between running in a console vs from a keybinding which you won't want if you're trying to show a GUI.
"Sort of" Solution
Triggering via keybinding seems to always have systemd as the parent process. With that in mind, something like this could work:
{
[ "$PPID" = "1" ] && echo "keybind" || echo "terminal"
} > /tmp/detect.log
It is likely a safe assumption that the systemd process will always have 1 as its PID, but there's no guarantee that every system using i3 will also be using systemd so this is probably best avoided.
Better Solution
A more robust way would be using ps. According to PROCESS STATE CODES in the man page:
For BSD formats and when the stat keyword is used, additional characters may be displayed:
< high-priority (not nice to other users)
N low-priority (nice to other users)
L has pages locked into memory (for real-time and custom IO)
s is a session leader
l is multi-threaded (using CLONE_THREAD, like NPTL pthreads do)
+ is in the foreground process group
The key here is + on the last line. To check that in a terminal you can call ps -Sp <PID> which will have a STAT value of Ss when running "interactively", or S+ if it's triggered via keybinding.
Since you only need the STATE column, you can further clean that up with -o stat= which will also remove the headers, then pipe through grep to give you the following:
is_interactive() {
ps -o stat= -p $$ | grep -q '+'
}
if is_interactive; then
read -rp "Enter your username: " username
else
username=$(yad --entry --text "Enter your username:")
fi
This will work not only in a terminal emulator and via an i3/Sway keybinding, but even in a raw console window, making it a much more reliable option than tty above.
I am currently using the following command to run reboot
sudo shutdown -r now
however, I would need to run it for 5 loops before and after executing some other programs. Was wondering if it is possible to do it in MINT environment?
First a disclaimer: I haven't tried this because I don't want to reboot my machine right now...
Anyway, the idea is to make a script that can track it's iteration progress to a file as #david-c-rankin suggested. This bash script could look like this (I did test this):
#!/bin/sh
ITERATIONS="5"
TRACKING_FILE="/path/to/bootloop.txt"
touch "$TRACKING_FILE"
N=$(cat "$TRACKING_FILE" | wc -c)
if [ "$N" -lt "$ITERATIONS" ]; then
printf "." >> "$TRACKING_FILE"
echo "rebooting (iteration $N)"
# TODO: this is where you put the reboot command
# and anything you want to run before rebooting each time
else
rm "$TRACKING_FILE"
# TODO: other commands to resume anything required
fi
Then add a call to this script somewhere where it will be run on boot. eg. cron (#reboot) or systemd. Don't forget to remove it from a startup/boot command when you're finished or next time you reboot, it will reboot N times.
Not sure exactly how you are planning on using it, but the general workflow would look like:
save script to /path/to/reboot_five_times.sh
add script to run on boot (cron, etc.)
do stuff (manually or in a script)
call the script
computer reboots 5 times
anything in the second TODO section of the script is then run
go back to step 3, or if finished remove from cron/systemd so it won't reboot when you don't want it to.
First create a text document wherever you want,I created one on Desktop,
Then use this file as a physical counter and write a daemon file to run things at startup
For example:
#!/bin/sh
var=$(cat a.txt)
echo "$var"
if [ "$var" != 5 ]
then
var=$((var+1))
echo "$var" > a.txt
echo "restart here"
sudo shutdown -r now
else
echo "stop restart"
echo 0 > a.txt
fi
Hope this helps
I found a way to create a file at startup for my reboot script. I incorporated it with the answers provided by swalladge and also shubh. Thank you so much!
#!/bin/bash
#testing making a startup application
echo "
[Desktop Entry]
Type=Application
Exec=notify-send success
Hidden=false
NoDisplay=false
X-GNOME-Autostart-enabled=true
Name[en_CA]=This is a Test
Name=This is a Test
Comment[en_CA]=
Comment=" > ~/.config/autostart/test.desktop
I create a /etc/rc.local file to execute user3089519's script, and this works for my case. (And for bootloop.txt, I put it here: /usr/local/share/bootloop.txt )
First: sudo nano /etc/rc.local
Then edit this:
#!/bin/bash
#TODO: things you want to execute when system boot.
/path/to/reboot_five_times.sh
exit 0
Then it works.
Don't forget edit /etc/rc.local and remove /path/to/reboot_five_times.sh when you done reboot cycling.
I placed a link to my scripts in the rc.local to autostart it on linux debian boot. It starts and then stops at the while loop. It's a netcat script that listens permantently on port 4001.
echo "Start"
while read -r line
do
#some stuff to do
done < <(nc -l -p 4001)
When I start this script as root with command ./myscript it works 100% correctly. Need nc (netcat) root level access or something else?
EDIT:
rc.local
#!/bin/sh -e
#
# rc.local
#
# This script is executed at the end of each multiuser runlevel.
# Make sure that the script will "exit 0" on success or any other
# value on error.
#
# In order to enable or disable this script just change the execution
# bits.
#
# By default this script does nothing.
/etc/samba/SQLScripts
exit 0
rc.local starts my script "SQLScripts"
SQLScripts
#! /bin/sh
# The following part always gets executed.
echo "Starting SQL Scripts" >> /var/log/SQLScriptsStart
/etc/samba/PLCCheck >> /var/log/PLCCheck &
"SQLScripts" starts "PLCCheck" (for example only one)
PLCCheck
#!/bin/bash
echo "before SLEEP" >> /var/log/PLCCheck
sleep 5
echo "after SLEEP" >> /var/log/PLCCheck
echo "vor While" >> /var/log/PLCCheck
while read -r line
do
echo "in While" >> /var/log/PLCCheck
done < <(netcat -u -l -p 6001)
In an rc script you have root level access by default. What does "it stops at the while loop" mean? It quits after a while, or so? I guess you need to run your loop in the background in order to achieve functionality usual in autostart scripts:
echo "Starting"
( while read -r line
do
#some stuff to do
done << (nc -l -p 4001) ) &
echo "Started with pid $( jobs -p )"
I have tested yersterday approximatly the same things, and I have discover that you can bypass the system and execute your netcat script with the following crontask. :
(every minute, but you can ajust that as you want.)
* * * * * /home/kali/script-netcat.sh // working for me
#reboot /home/kali/script-netcat.sh // this is blocked by the system.
According to me, I think that by default debian (and maybe others linux distrib) block every script that try to execute a netcat command.