Bash Question: HowTo Exit Script with User Input While Process is Running Mid-Loop?

Hi, I have written a script that allows me to repetitively play a music file $N times, which is specified through user input. However, if I want to exit the script before it has finished looping $N times, if I use CTRL+c, I have to CTRL+c however many times are left in order to complete the loop. For instance, if I specified that the song is to repeat 5 times, if during the first iteration through the loop, if I use CTRL+c to exit the script, I will have to press CTRL+c four more times before the script will terminate.

My objective is to be able to type 'q' or some character in order to quit the process. I have found limited information about this topic online, and what I have found has not worked for me.

Although my script provides more options than what is listed below, here it is in its most basic form (note - I'm using mplayer to play the audio file):

#!/bin/bash

echo -n "Enter the name of the song that you would like to play: "
read fileName
echo ""

echo -n "How many times would you like to repeat the song? "
read num
echo ""

for ((i=1; i<= $num; i++))
    do mplayer $fileName
done

Does anyone have any recommendations on how I can exit from the script using keyboard input in such a way that I'm not stuck in the loop and that the script actually terminates?

Thank you!

I think trap might help you. You can customize how to respond to hardware signals such as Cntrl-c.
Man Page for trap (linux Section 1) - The UNIX and Linux Forums

Thank you chacko193. I read the "trap" manpage and thought that it might work, but perhaps I'm not using it correctly... although it allows CTRL+c to exit the script, it also only allows the audio file to be executed one time; for some reason it won't loop for the specified number of times. Using the same code as in my previous example, I've added the trap signal:

for ((i=1; i<= $num; i++))
    do trap 'mplayer $fileName' 0
done

It seems like there should be a way to code this programatically rather than relying on another application like trap, but I just don't see it. I tried using the solutions that were suggested at the link below but only ended up with an infinite loop. (BTW - I keep trying to wrap this link in URL tags but for some reason vBulletin keeps telling me that I have reached the limit of having 5 URL's in my post so I'm instead wrapping it in CODE tags.)

http://www.unix.com/shell-programming-scripting/84624-nonblocking-i-o-bash-scripts.html

I'm not sure where to go from here.

I'm not familiar with the bash shell but that line

do trap 'mplayer $fileName' 0

just doesn't look right. That is telling the shell to only execute "mplayer" when an interrupt 0 is captured.

The trap should be used to tell the script what to do when it receives certain signals, ie. how to clean up after itself.

trap 'exit 1' 1 2 3 15
for ((i=1; i<= $num; i++))
    mplayer $fileName
done

How can I trap interrupts in my shell script? - IS&T Contributions -�Hermes

Thanks port43! Although this method does not accomplish what I had hoped to do in terms of "listening" for user input (pressing 'q' for quit), exiting the script with CTRL+c instead will work just fine for me and I'll be perfectly happy with that. The script is working very well now thanks to your input and suggestions. Before today, I had never heard of "trap".

As a musician, I will often times use this script when trying to commit a song or a series of songs to memory. Here's the new version:

echo -n "Would you like to repeat one file (1) or many files (M)?"
read quantity
echo ""
echo ""

echo -n "Enter the number of times you would like to play the file(s):"
read num
echo ""
echo ""

if [ $quantity == "1" ]
then
  echo -n "What is the name of the file?"
  read file
  trap 'exit 1' 1 2 3 15
  for ((i=1; i <= $num; i++))
     do mplayer $file
done

elif [ $quantity == "M" ]
then
  echo -n "Enter the file type or extension (mp3 / ogg / *)"
  read ext
  trap 'exit 1' 1 2 3 15
  for ((j=1; j <= $num; j++))
     do mplayer *.$ext
done

else
  echo ""
  echo -n "Invalid entry. Please try again."
  echo ""
fi

Thanks again!

You don't need to repeat the trap command if they're identical; just add one at the top of the script. Don't forget to reset it once it's no more needed.

Yep, as RudiC says, move the trap to the top of your script - one trap for the whole script (unless you want to change the way the script behaves at different times with different signals).

That's good to know, thank you. I looked at the man page and did not see how to reset the trap. After some googling, I think that a trap reset might look something like this?

trap - SIGINT SIGQUIT SIGTSTP

Is that the correct way to reset the trap?

Also, if the script is terminated with CTRL+c (without resetting the trap), the trap settings won't effect other scripts that I run afterwards, will it? Does the trap only apply to the script that it was called from?

Thank you!

Yes, that is how you reset: trap - <value> .

I should point out that in my example I used integer values and yours used the signal name(s); either one is valid. Here are the values I used( kill -l will list other values):

  • SIGHUP 1 death of controlling terminal or process
  • SIGINT 2 <Ctrl><C>
  • SIGQUIT 3 <Ctrl><D>
  • SIGTERM 15 Software termination

If you have properly used the shebang at the top of your script to identify the shell interpreter, ie. #!/bin/ksh then your script is spawned in a subshell and the trap does not need to be reset because when the script execution terminates the trap values are lost.

The point of the reset is if you have something like: trap 'ls -l' 2 (which will cause <Ctrl><C> to do a long listing) for part of your script, you'll might want to put it back with trap - 2 for normalcy.

One last thing, trap is usually used to clean up when a script is aborted so it probably is used more like:

trap 'rm /tmp/mytempfile.txt /path/to/other/tempfile.txt; exit 2' 1 2 3 15
1 Like

Thank you all for your assistance - I really appreciate it. Things seem to be running smoothly at this point and are to my liking. Here is the finished version of the script:

#!/bin/bash

#######################################################################                                                                                      
##                                                                   ##
## This script will play and repeat either one or more audio files   ##
## that are  contained in the current working directory.             ##
## *Script requires that mplayer and figlet are installed.           ##
##      author: Darrin Goodman                                       ##
##      email: hilltopyodeler@gmail.com                              ##
##                                                                   ##
## This program is free software and is available without warranty.  ##
## You are free to redistribute it and/or modify it under the terms  ##
## of the GNU General Public License as published by the             ##
## Free Software Foundation, either version 3 or any later           ##
## version of the license.                                           ##
##                                                                   ##
#######################################################################



## Set up trap so that it will allow you to terminate the script at any time using CTRL+C
## http://www.unix.com/shell-programming-scripting/244861-bash-question-howto-exit-script-user-input-while-process-running-mid-loop.html
trap 'exit 1' 1 2 3 15

###############################################################################
## BEGIN FUNCTIONS SECTION

doWhat()
{
echo -n "Repeat one file (1) or many files (M)? "
read quantity
echo ""
  case $quantity in
    1) oneFile ;;
    m|M) manyFiles ;;
    q|Q) quitski ;;
    *) incorrectEntry ;;
  esac
}

repeatNum()
{
  echo -n "Enter the number of times you would like to play the file(s): "
  read num
  echo ""
}

oneFile()
{
  echo -n "What is the name of the file? "
  read file
  echo ""
  checkFile
  repeatNum
  echo "-------------------------------------------------------------------------------"
  echo "-------------------------------------------------------------------------------"
  echo ""
  for ((i=1; i <= $num; i++))
     do mplayer $file;
  done
  again
}

checkFile()
{
  ## Check to make sure that the file exists
  if [ ! -f $file ]; then
    echo "   Hmmm... [ $file ] - This file does not exist; did you type it correctly?"
    echo ""
    oneFile
  else
    echo ""
  fi
}

manyFiles()
{
  echo -n "Enter the file type or extension (mp3 / ogg / *) "
  read ext
  echo ""
  repeatNum
  echo "-------------------------------------------------------------------------------"
  echo "-------------------------------------------------------------------------------"
  echo ""
  for ((j=1; j <= $num; j++))
     do mplayer *.$ext;
  done
  again
}

incorrectEntry()
{
  echo "     **************************************"
  echo "     *                                    *"
  echo "     *  INVALID ENTRY! Please try again.  *"
  echo "     *                                    *"
  echo "     **************************************"
  echo ""
  doWhat
}

again()
{
  echo "-------------------------------------------------------------------------------"
  echo "-------------------------------------------------------------------------------"
  figlet "Done."
  figlet "What next?"
  echo "-------------------------------------------------------------------------------"
  echo "-------------------------------------------------------------------------------"
  echo ""
  echo -n "What would you like to do now? ....Start over (s) or Quit (q)? "
  echo ""
  read whatNow
  case $whatNow in
    s|S) doWhat ;;
    q|Q) quitski ;;
    *) echo "Huh?" ; again ;;
  esac
}

quitski()
{
  clear
  echo ""
  figlet Good Bye!
  echo ""
  echo ""
  exit
}

## END FUNCTIONS SECTION
###############################################################################

## Clear the screen
clear

figlet "Music Repeater"
echo "-------------------------------------------------------------------------------"
echo "This program will play one or more audio files, however many times you specify."
echo "Exit this script at any time with CTRL+C."
echo ""

## What do you want to do? Repeat one or many files?
doWhat

## End of program; exit
figlet END

# Reset all traps 
trap - SIGINT SIGQUIT SIGTSTP

exit 0

This script suits my needs well. There may be more efficient ways to code this process, so please feel free make appropriate changes and re-post it if you like, and feel free to use it if it benefits you in some way.

Thanks again to those of you who offered assistance with getting the script to terminate with CTRL+c.