3

I have written a shell script run.sh to trigger a few tasks based on the user's input

#!/bin/bash

echo "Please choose mode [1-3]: "
read MODE

case $MODE in

  1)
    echo -n "Enter iteration: "
    read TIME
    echo "Start Updating ..."
    task 1 && task 2 && task 3  
    ;;

  2)
    echo -n "Enter Seed Value: "
    read SEED
    echo "Start Updating ..."
    task 4
    task 5
    ;;

  3)
    echo -n "Enter regression minimum value: "
    read MIN
    echo "Start Updating ..."
    task 6
    ;;

  *)
    echo -n "Unknown option - Exit"
    ;;
esac

The tasks 1,2 ... 6 are php scripts that are run like /usr/bin/php task1.php $TIME with $TIME as an argument for the php script etc...

The script runs fine when I type bash run.sh but since tasks 1-6 takes a long time to complete I would like an option to run the script in background while I disconnect from the terminal. However if I run the script using bash run.sh & I encountered an error like this:

Please choose mode [1-3]: 2
-bash: 2: command not found

[5]+  Stopped                 bash run.sh

It seems like bash interpreted my input 2 as an argument not corresponding to read MODE but instead of bash run.sh 2 which causes an error. I cannot change the script such that tasks 1-6 are run in the background like task 1 & task 2 & etc. because task 2 can only start running after task 1 is completed.

How can I accomplish what I want to do?

1
  • If you put the bash process running your run.sh into the background, how could it ever ask the user for input? Either just put the tasks you are worried about into the background, or better yet, don't ask for input on stdin, but have the MODE_passed as a parameter to the script. Interactively asking for input is rarely a good idea in shell scripting. Commented Nov 2, 2020 at 7:07

2 Answers 2

8

You could run all of those tasks sequentially in a background subshell

( task 1; task 2; task 3 ) &

Try this out with:

( echo "One"; sleep 1; echo "Two"; sleep 2; echo "Three"; sleep 3; echo "Done" ) &

You could also make this more script-looking:

(
echo "One"
sleep 1
echo "Two"
sleep 2
echo "Three"
sleep 3
echo "Done"
) &

Feel free to make use of the useful envar $BASH_SUBSHELL

( echo $BASH_SUBSHELL; ( echo $BASH_SUBSHELL ) )
Sign up to request clarification or add additional context in comments.

2 Comments

I'd also recommend putting some thought into error detection and recovery. What could go wrong with the various tasks? If something goes wrong (or even just happens strangely) during one task, is it safe to proceed with the rest? How would you detect if something went wrong/strange during the tasks?
There are many ways to error detect/respond. In this example functions are being used and you could thus make use of return and $?. Keep in mind a subshell is nothing magic/special, it's really just another bash script (sort of). You could respond to errors in a subshell in quite a similar manner to how you might in a script - Reply to: Gordon Davisson
1

Short answer: running interactive programs (/scripts) in the background doesn't really work; it'll work even less well if you disconnect from the terminal. You should rewrite the script so it doesn't need user input as it runs.

Long answer: when you run the script in the background, something like this happens. Note that the exact order of events may vary, as both the background script and foreground interactive shell are running at the same time, "racing" each other to get things done.

  1. You start the script in the background with bash run.sh &
  2. Your interactive shell is immediately ready for your next command, so it prints your usual command prompt to the terminal.
  3. Your script prints its prompt ("Please choose mode [1-3]: ") to the terminal.
  4. Your interactive shell reads its next command from the terminal. Because the script's prompt printed second, it looks like you're sending input to it, but there's actually no connection between the most recent prompt and which program is receiving your input.
  5. Your interactive shell attempts to run your input ("2") as a command, and it fails.
  6. Your shell script finally gets its chance to read from the terminal... but it's in the background, so it's not allowed to. Instead, it is suspended, and a "Stopped" message is printed. From the bash man page, "Job Control" section:

Background processes which attempt to read from (write to when stty tostop is in effect) the terminal are sent a SIGTTIN (SIGTTOU) signal by the kernel's terminal driver, which, unless caught, suspends the process.

At this point, if you want to continue the script and tell it what to do, you'd need to move it to the foreground (e.g. with the fg) command. Which would kind of negate the point here. Also, its prompt ("Please choose mode [1-3]: ") will not be repeated, as that echo command successfully finished while it was in the background.

The solution: basically, write the script to non-interactively run the tasks in the necessary order. @Lenna has given examples; follow her recommendations.

1 Comment

I believe OP’s question is more along the lines of “Script to launch background task based on user input”. You seem to be answering “Background task script read from stdin” which is not what is being asked here. If you create a new question and answer it, I believe it would add a good reference point for the community.

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.