Search code examples
linuxbashshellgnu-screen

Run script in a new screen if true


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.


Solution

  • 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.