February 2008 AAuSE, Take
2 |
Copyright (C) 2008 by Steve Litt. All rights reserved. Materials from guest authors copyrighted by them and licensed for perpetual use to Linux Productivity Magazine. All rights reserved to the copyright holder, except for items specifically marked otherwise (certain free software source code, GNU/GPL, etc.). All material herein provided "As-Is". User assumes all risk and responsibility for any outcome.
Twenty Eight Tales of Troubleshooting Troubleshooting: Just the Facts Manager's Guide to Technical Troubleshooting Troubleshooting Techniques of the Successful Technologist |
[ Troubleshooters.Com
| Back Issues |Troubleshooting
Professional Magazine
]
|
CONTENTS
Author's Note
This article was written when I understood much less about shellscripting. As a result, some of my assertions, such as the article's assertion that finding a process ID isn't quite as easy as it might seem, aren't true. Much of this article is contradicted later in the magazine. That's OK, as I said, this was a journey, and be assured that whatever the article says, the code in this article does work quite nicely. |
forever foreach line in the playlist file timcommand = "timidity " + song_filename system(timcommand)The preceding is wonderful with one exception. The user must interact with the playing. The user might want to go to the next or previous song, start this one over, quit, or jump to a specific song. So the program must continuously read the incoming command FIFO, and do the bidding of the commands coming in through that FIFO. Still no problem:
forever check FIFO and read if necessary if FIFO contains command if command = quit break else adjust playlist subscript according to command endif else increment playlist subscript, or set back to 1 if already on last song endif play song pointed to playlist subscriptThat was easy, wasn't it! Can you see the problem?
childpid=$(cat ./child.pid) timpid=$(ps -aH | grep -A1 ^[[:space:]]*$childpid | tail -n1 | cut -b1-5)The following scripts provide a proof of concept for this way a shellscript can identify the PID used by timidity:
parent.sh | child.sh | |
#!/bin/bash echo $$ > parent.pid ./child.sh & sleep 1 childpid=$(cat ./child.pid) timpid=$(ps -aH | grep -A1 ^[[:space:]]*$childpid | tail -n1 | cut -b1-5) echo child=$childpid echo timidity=$timpid echo ps ax | tail -n8 echo echo -n 'Kill it now? (y or n)==>' read killflag if test "$killflag" = "y"; then kill $timpid kill $childpid fi |
#!/bin/bash echo $$ > child.pid /usr/bin/timidity test.mid echo finished > /tmp/steve.fifo echo FINISHED FINISHED FINISHED |
#!/usr/bin/ruby def play() pid = fork if pid == nil ### THIS IS THE CHILD pid2 = fork if pid2 == nil ### THIS IS THE GRANDCHILD ### REPLACE IT WITH MIDI PLAYER Kernel.exec("timidity", @filename.to_s) else ### THE CHILD ### ITS PURPOSE IS TO ### FORK OFF TIMIDITY AND ### WRITE finished TO FIFO ### WHEN SONG IS FINISHED print "dia pid grandchild=" + pid2.to_s + "\n" pid2file = File.new("/tmp/pid2.pid", "w"); pid2file.puts(pid2.to_s); pid2file.close() @returnstring = "finished" Process.wait(pid2) ### RETURN PROPER STRING THRU FIFO writebackfile = File.new("/d/bats/littmidi.fifo", "w"); writebackfile.puts(returnstring); writebackfile.close() puts "dia returnstring is #{returnstring}" Process.exit(0) end else ### THIS IS THE PARENT pidfile = File.new("/tmp/pid.pid", "w"); pidfile.write(pid.to_s) pidfile.close() Process.detach(pid) puts "dia pid child=#{pid}" end end |
def superkill pidfile = File.new("/tmp/pid.pid", "r"); pid = pidfile.readline().to_i; pidfile.close() Process.kill("HUP", pid) pidfile = File.new("/tmp/pid2.pid", "r"); pid2 = pidfile.readline().to_i; pidfile.close() Process.kill("HUP", pid2) end def kill self.superkill unless @paused end |
def getNextLine(infile) while infile.eof sleep(0.5) end line = infile.gets line.chomp! sleep(0.5) return line end |
Block
diagram of my
timidity front end. Major components are UMENU menu and the frontmidi.rb
program, which handles playlist functions and interprocess
communication between user commands coming through the FIFO, the
playlist, and the processes running timidity. Other significant
components are the filepicker and recordpicker, aumix and timidity. frontmidi.rb's sole connection to the user is the commands coming through the fifo. For debugging purposes the programmer can input such commands directly by redirecting text to the fifo. UMENU is responsible for converting user menu choices to commands useable by frontmidi.rb, and sending them down the FIFO. The song object also writes the FIFO on completion of a song it writes "finished" to the FIFO. |
#!/usr/bin/ruby -w # ======================================================================= # # frontmidi.rb, version 0.1.0, copyright (C) 2008 by Steve Litt # # Redistribution and use in source and binary forms, with or without # modification, are permitted provided that the following conditions are met: # # 1. Redistributions of source code must retain the above copyright notice, # this list of conditions and the following disclaimer. # # 2. Redistributions in binary form must reproduce the above copyright # notice, this list of conditions and the following disclaimer in the # documentation and/or other materials provided with the distribution. # # 3. The name of the author may not be used to endorse or promote products # derived from this software without specific prior written permission. # # THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR IMPLIED # WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF # MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO # EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, # SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, # PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; # OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, # WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR # OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF # ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. # # ======================================================================= # # frontmidi.rb, version 0.1.0 1/30/2008 # This version is somewhat unstable and has been only lightly tested. # BUGS: # * Throws useless error messages on uncaught SIGHUP signal to child process # * Aborts if quickly repeated next commands go past an # unplayable song # * The fifo file is housed in a directory you might not have. Either # change all instances of the fifo file (and change them in UMENU), # or create /d/bats/littmidi.fifo. # # ======================================================================= def fifo() "/d/bats/littmidi.fifo" end class Playlist attr_accessor :paused attr_reader :fname attr_reader :songs attr_reader :numsongs attr_accessor :songnumber attr_reader :songname def initialize(fname) paused = false @fname = fname @songs = [] @numsongs = 0 playlistfile = File.new(fname, "r"); playlistfile.each do |line| line.chomp! skip = 1 if line =~ /\.mid\s*$/ skip = 0 end if line =~ /^\s*$/ skip = 1 end if line =~ /^\s*\#/ skip = 1 end if skip == 0 @songs.push([line]) @numsongs += 1 end end playlistfile.close() @songnumber = 1 @songname = @songs[@songnumber-1] end def set(num) if(num < 1) @songnumber=1 elsif(num > @numsongs) @songnumber = @numsongs else @songnumber = num end @songname = @songs[@songnumber-1] end def next @songnumber += 1 if @songnumber > @numsongs @songnumber = 1 end @songname = @songs[@songnumber-1] end def prev @songnumber -= 1 if @songnumber < 1 @songnumber = @numsongs end @songname = @songs[@songnumber-1] end def write2stdout() puts "There are #{@numsongs.to_s} songs in this playlist:" ss1 = 1 @songs.each do |song| puts "#{ss1.to_s}: #{song}" ss1 += 1 end end end class Midisong attr_reader :returnstring attr_reader :filename attr_accessor :paused def initialize(filename) puts "dia top Midisong init filename=#{filename}" @filename = filename @paused = false end def superkill pidfile = File.new("/tmp/pid.pid", "r"); pid = pidfile.readline().to_i; pidfile.close() Process.kill("HUP", pid) pidfile = File.new("/tmp/pid2.pid", "r"); pid2 = pidfile.readline().to_i; pidfile.close() Process.kill("HUP", pid2) end def kill self.superkill unless @paused end def play pid = fork if pid == nil ### THIS IS THE CHILD pid2 = fork if pid2 == nil ### THIS IS THE GRANDCHILD ### REPLACE IT WITH MIDI PLAYER Kernel.exec("timidity", @filename.to_s) else ### THE CHILD ### ITS PURPOSE IS TO ### FORK OFF TIMIDITY AND ### WRITE finished TO FIFO ### WHEN SONG IS FINISHED print "dia pid grandchild=" + pid2.to_s + "\n" pid2file = File.new("/tmp/pid2.pid", "w"); pid2file.puts(pid2.to_s); pid2file.close() @returnstring = "finished" Process.wait(pid2) ### RETURN PROPER STRING THRU FIFO writebackfile = File.new(fifo, "w"); writebackfile.puts(returnstring); writebackfile.close() puts "dia returnstring is #{returnstring}" Process.exit(0) end else ### THIS IS THE PARENT pidfile = File.new("/tmp/pid.pid", "w"); pidfile.write(pid.to_s) pidfile.close() Process.detach(pid) puts "dia pid child=#{pid}" end end end def getNextLine(infile) while infile.eof sleep(0.5) end line = infile.gets line.chomp! sleep(0.5) return line end puts "Starting" puts "dia pid parent=#{Process.pid()}" playlist=Playlist.new(ARGV[0]) song = Midisong.new(playlist.songname) song.play() commandfile = File.new(fifo, "r") while true command = getNextLine(commandfile) if song.paused puts "Unpausing. Now perform the desired command!" command = "unpause" end puts command case command when 'finished' playlist.next() song = Midisong.new(playlist.songname) song.play when 'next' song.kill() playlist.next() song = Midisong.new(playlist.songname) song.play when 'prev' song.kill() playlist.prev() song = Midisong.new(playlist.songname) song.play when 'firstsong' song.kill() playlist.set(1) song = Midisong.new(playlist.songname) song.play when 'lastsong' song.kill() playlist.set(playlist.numsongs) song = Midisong.new(playlist.songname) song.play when 'beginsong' song.kill() song = Midisong.new(playlist.songname) song.play when 'pause' song.kill() song.paused = true when 'unpause' if song.paused song = Midisong.new(playlist.songname) song.play end when /^playlist\s/ temp=command temp.gsub!(/playlist\s*/){""} temp.gsub!(/\s.*/){""} puts "dia playlist=#{temp}" playlist=Playlist.new(temp) song.kill() song = Midisong.new(playlist.songname) song.play when /^specificsong \d/ temp=command temp.gsub!(/specificsong\s*/){""} temp.gsub!(/\D.*/){""} puts "dia specificsong=#{temp}" song.kill() playlist.set(temp.to_i) song = Midisong.new(playlist.songname) song.play when 'quit' song.kill() break end end print "Goodbye\n\n" |
Ruby program Open source license Bugs The fifo file. Must match the fifo defined in UMENU The playlist object handles all playlist functionality Playlist file loop. This defines which playlist entries are legitimate. To be legitimate, an entry must end in .mid, must not be blank, and must not start with a comment char (#). This definition MUST match UMENU'S definition, or the "jump to song" functionality might play the wrong song. Song handler object superkill method kills the child and grandchild kill method Don't kill descendents if paused play method creates child and grandchild grandchild is replaced with timidity Replace grandchild with timidity Save grandchild pid Child waits for completion or termination of grandchild, which is timidity Tell main process song is finished Child process terminates itself after completion or termination of grandchild Save child pid to file Detach child from main process, meaning main and child proceed independently Function to acquire commands from the fifo. Eof loop waits for input. Second sleep necessary to prevent crashing in the face of rapid, repetitive commands from fifo Initialize playlist object Initialize playlist's first song Start the song Open the fifo for input Command handling loop When player is paused, the next command is disabled other than clearing the pause flag. Normal song repetition. Child and grandchild already completed and terminated. Just incriment playlist and play the song. Next. Kill current child and grandchild (timidity), increment playlist and play. Previous. Kill current child and grandchild (timidity), increment playlist and play. Rewind to playlist's first song. Kill current child and grandchild (timidity), set playlist to first song, and play. Proceed to playlist's last song. Kill current child and grandchild (timidity), set playlist to last song, and play. Replay current song from its beginning. Leave playlist untouched, kill current child and grandchild (timidity), and play. Pause. Kill child and grandchild, and set paused flag. Unpause. Resume play. Code at top of loop already cleared pause flag. Change playlist. Playlist's filename follows the "playlist" keyword. Kill child and grandchild and play new playlist's first song. Choice of the playlist is done externally to this program (typically from UMENU). Jump to song. Song's 1 based number follows keyword "specificsong". Kill child and grandchild, set playlist to new song number, and play playlist's new current song. Actual choice of song is done externally, typically from UMENU. UMENU's definition of legitimate songs on list must match this program's. Quit the program. Kill child and grandchild, terminate the command loop, and fall through to program's end. |
ZZM:::Litt's Text Midi Frontender pAuse param C: echo pause > /d/bats/littmidi.fifo Unpause param C: echo unpause > /d/bats/littmidi.fifo; Beginning of song param C: echo beginsong > /d/bats/littmidi.fifo Next param C: echo next > /d/bats/littmidi.fifo Previous param C: echo prev > /d/bats/littmidi.fifo First song param C: echo firstsong > /d/bats/littmidi.fifo Last song param C: echo lastsong > /d/bats/littmidi.fifo Jump to song param D: /scratch/oldsongs/mid C: echo dia1; C: FN=$(persist playlist=? $HOME/littmidi.state); C: TMPFILE=`mktemp`; C: echo dia2; C: persist action= $HOME/littmidi.state; C: persist recno= $HOME/littmidi.state; C: echo dia3; C: echo "superselect=n" >> $TMPFILE; C: echo "startofdata" >> $TMPFILE; C: echo dia4; C: echo dia4.5 FN=$FN; C: cat $FN >> $TMPFILE; C: echo dia5; C: cat $FN | grep "\.mid\s*$" | grep -v "^\s*$" | C: grep -v "^\s*#" >> $TMPFILE; C: echo dia6; C: /d/at/cpp/filepicker/rpick $TMPFILE; C: echo dia7; C: cat $TMPFILE; C: grep "action=" $TMPFILE >> $HOME/littmidi.state; C: ACTION=$(persist action=? $HOME/littmidi.state); C: persist action= $HOME/littmidi.state; C: if grep -q "action=select" $TMPFILE; then C: grep "recno=" $TMPFILE >> $HOME/littmidi.state; C: RECNO=$(persist recno=? $HOME/littmidi.state); C: persist recno= $HOME/littmidi.state; C: let RECNO=RECNO+1; C: echo specificsong $RECNO > /d/bats/littmidi.fifo; C: fi; C: rm -f $TMPFILE; Run frontmidi.rb param D: /scratch/oldsongs/mid C: TMPFILE=`mktemp`; C: echo "dir=/scratch/oldsongs/mid/lists/" > $TMPFILE; C: /d/at/cpp/filepicker/fpick $TMPFILE; C: if grep -q "action=select" $TMPFILE; then ACTION=SELECT;fi; C: if test "$ACTION" = "SELECT"; then C: FN=`grep "^file=" $TMPFILE | sed -e "s/.*=//"`; C: persist "playlist=$FN" $HOME/littmidi.state; C: /d/at/ruby/frontmidi.rb $FN; C: fi; C: rm -f $TMPFILE; S: 1 Change Playlist param D: /scratch/oldsongs/mid/lists C: TMPFILE=`mktemp`; C: echo "dir=/scratch/oldsongs/mid/lists/" > $TMPFILE; C: /d/at/cpp/filepicker/fpick $TMPFILE; C: if grep -q "action=select" $TMPFILE; then ACTION=SELECT;fi; C: if test "$ACTION" = "SELECT"; then C: FN=`grep "^file=" $TMPFILE | sed -e "s/.*=//"`; C: persist "playlist=$FN" $HOME/littmidi.state; C: echo playlist $FN > /d/bats/littmidi.fifo; C: fi; cHange Playlist param D: /scratch/oldsongs/mid/lists C: TMPFILE=`mktemp`; C: echo "dir=/scratch/oldsongs/mid/lists/" > $TMPFILE; C: /d/at/cpp/filepicker/fpick $TMPFILE; C: if grep -q "action=select" $TMPFILE; then ACTION=SELECT;fi; C: if test "$ACTION" = "SELECT"; then C: FN=`grep "^file=" $TMPFILE | sed -e "s/.*=//"`; C: persist "playlist=$FN" $HOME/littmidi.state; C: echo quit > /d/bats/littmidi.fifo; C: echo Wait two seconds... C: sleep 2; C: /d/at/ruby/frontmidi.rb $FN; C: fi; C: rm -f $TMPFILE Kill Littmidi.rb param C: echo quit > /d/bats/littmidi.fifo Zap zombie midi players param C: zapmidizombies S: 1 Volume ::: Mplayer volume Louder param C: VOLM=$(persist "volume=?" $HOME/littmidi.state); C: if test "$VOLM" = ""; then C: let VOLM=60; C: persist "volume=$VOLM" $HOME/littmidi.state; C: fi; C: if test $VOLM -lt 100; then C: let VOLM=$VOLM+1; C: persist "volume=$VOLM" $HOME/littmidi.state; C: aumix -v $VOLM -w $VOLM; C: fi; C: echo "Volume is $VOLM"; Softer param C: VOLM=$(persist "volume=?" $HOME/littmidi.state); C: if test "$VOLM" = ""; then C: let VOLM=60; C: persist "volume=$VOLM" $HOME/littmidi.state; C: fi; C: if test $VOLM -gt 0; then C: let VOLM=$VOLM-1; C: persist "volume=$VOLM" $HOME/littmidi.state; C: aumix -v $VOLM -w $VOLM; C: fi; C: echo "Volume is $VOLM"; Mute param C: aumix -v 0 -w 0; Unmute param C: VOLM=$(persist "volume=?" $HOME/littmidi.state); C: if test "$VOLM" = ""; then C: let VOLM=60; C: persist "volume=$VOLM" $HOME/littmidi.state; C: fi; C: aumix -v $VOLM -w $VOLM; C: echo "Volume is $VOLM"; Observe Volume param C: VOLM=$(persist "volume=?" $HOME/littmidi.state); C: echo Littmidi volume=$VOLM; C: aumix -q; S: 1 seT volume to specific number (0-100) param C: ORG=$(persist "volume=?" $HOME/littmidi.state); C: VOLM=%1%Volume please (0-100)%%; C: echo "Original volume=$ORG"; C: echo "$VOLM" | grep "[^[:digit:]]"; C: NOTANUMBER=$?; C: test -z $VOLM; C: ZEROLENGTH=$?; C: if test "$NOTANUMBER" = "1" -a "$ZEROLENGTH" = "1"; then C: persist "volume=$VOLM" $HOME/littmidi.state; C: aumix -v $VOLM -w $VOLM; C: echo "New volume is $VOLM"; C: else C: echo "Bad input: $VOLM"; C: echo "Volume unchanged at $ORG"; C: fi; S: 1 ^Quit Special functions ::: Special Functions Menu Drain FIFO param C: cat /d/bats/littmidi.fifo S: 1 Ps ax param C: TEMP=`ps ax | grep frontmidi.rb`; FIFO Tasks ::: FIFO Task Menu Delete current FIFO param C: rm -f /d/bats/littmidi.fifo; C: ls -l /d/bats/littmidi.fifo S: 1 Create FIFO param C: mkfifo /d/bats/littmidi.fifo; C: ls -l /d/bats/littmidi.fifo S: 1 ^Quit What time is it? param C: date +%Y/%m/%d__%H:%M:%S S: 1 Edit Litt's Text Music Player menu param C: gvim /d/bats/zzm.emdl Rebuild Litt's Text Music Player menu param C: /d/bats/rebuild_umenu_zzm.sh ^Quit plaYlist ::: Playlist menu Get Current Playlist param C: FN=$(persist playlist=? $HOME/littmidi.state); C: echo $FN S: 1 Edit Playlist param C: gvim $(persist playlist=? $HOME/littmidi.state); ^Quit ^eXit |
Retrieve current playlist Make tempfile for recordpicker erase former persistent action and record number cannot select whole directories start of data flag to recordpicker Put playlist file lines into record picker, using same criteria as frontmidi.rb. Note lack of semicolon means the line after the cat command is appended to the end of the cat command. Find user action If use SELECTed, get the record number, remember it, and send its one-based equivalent as a specificsong command to frontmidi.rb. Make temp file to communicate with filepicker. Put starting directory into temp file. Run record picker. If user selected, get the chosen filename and start frontmidi.rb with that filename as ARG0. Same as "Run frontmidi.rb", except send the filename down the fifo with a playlist command. Depricated. Change playlists by stopping and restarting frontmidi.rb. The zapmidizombies shellscript does a killall on timidity, and greps ps ax to obtain pids of all frontmidi.rb instances, both parents and children, and issues kill commands for them. Get the single-value volume number from storage. If it's not present, set it to 60 and save it. Increment the volume number if less than 100, and save it. Set aumix master volume AND PCM volume to the single-value number. Crude, but it works well. Just like "Louder", except decrements the volume number if greater than zero. Sets audio volumes to zero, but does not store or change the front end volume number Retrieves the stored front end volume number, and sets the aumix master and pcm volumes to that number. Shows the front end single value number followed by all aumix values. The S: 1 at the end prevents a new menu from overwriting the output until the user presses a key. Sets the front end single-value number to the user's input. Retrieves the current number, queries for and stores the user's input. It checks for non-numerics and for a zero length string, and issues an error message if either is true. Otherwise it changes the single value number, saves it, and sets aumix's pcm and master volumes to that number. Error messages on bad input Stops for user to read messages, until user acknowledges with a keystroke. Edit the EMDL file that is the source for the menu. Compiles the source EMDL into a menu system. |
Here's the main menu. The top choice is to run the ruby program. Pause, unpause, beginning of song, first song, next, previous, and last song send commands to the ruby program through the FIFO. Change playlist and Jump to song first give the user a list to choose from, and then submit the appropriate command through the FIFO. The Kill Littmidi.rb submits a quit command through the FIFO, and in response the Ruby program ends. The Zap zombie choice uses kill commands to strongarm all the ruby midiplayer programs. The choices with elipses in front of them bring up submenus. Exit exits the menu, but does not terminate the Ruby program. |
Here's
the volume
submenu. Louder increases the volume a small amount and can be pressed
repeatedly. Softer does the same thing but decreases the volume. Mute
sets the volume to zero but remembers the former volume. Unmute brings
back the former volume. Observe volume looks at persistent storage to
tell you the current setting. Set volume to specific number queries the
user for a volume number, stores it persistently, and sets the volume
accordingly. All these commands raise and lower volume by calling aumix, and do not submit commands to the Ruby program. |
The Special Functions submenu. CLI Run littmidi.rb runs the Ruby program in the terminal now used by the menu. Drain FIFO does what it says, and is used for maintenance and diagnostics when things go wrong. Ps ax shows the process list. FIFO tasks is a submenu. What time is it shows the current time, Edit Litt's Text Music Player menu lets you edit the EMDL file that created the menu, and Rebuild Litt's Text Music Player menu compiles the EMDL file, and after displaying any errors, enables the user to transfer the newly compiled menu files to UMENU. |
Here's the FIFO task menu. It enables you to delete or create the FIFO. This is used only for troubleshooting when things go very wrong. |
The Playlist submenu. Here you can edit the current playlist (in Vim) or see what the current playlist's filename is. |
This month I proved that concept! |
Parent program | spawner.sh | |
#!/bin/bash ### DEFINE BINARY COMMAND TO SPAWN, AND COMMAND TO RUN ON ITS TERMINATION SPAWNEDCOMMAND='sleep 20' FINISHEDCOMMAND="./test.sh $TMPFILE" ### BUILD THE TEMPORARY FILE TO COMMUNICATE WITH THE CHILD TMPFILE=mktemp echo $SPAWNEDCOMMAND > $TMPFILE echo $FINISHEDCOMMAND >> $TMPFILE ### RUN THE SPAWNER, IN THE BACKGROUND, WITH THE TEMP FILE AS ARG1 ./spawn.sh $TMPFILE & ### DEDUCE CHILD AND GRANDCHILD PIDs MYPID=$$ CHILDPID=$(ps -aH | grep -A1 "^\s*$MYPID" | tail -n1 | cut -b1-5) GRANDCHILDPID=$(ps -aH | grep -A1 "^\s*$CHILDPID" | tail -n1 | cut -b1-5) ### APPLICATION CODE GOES BELOW RESPONSE='n' #echo -n Do you want to kill: $SPAWNEDCOMMAND \(y, n\)? ==\> read RESPONSE if test "$RESPONSE" = "y"; then # kill $CHILDPID # UNCOMMENT TO PREVENT FINISHEDCOMMAND FROM RUNNING kill $GRANDCHILDPID fi echo echo tempfile follows cat $TMPFILE echo =============== sleep 10 echo tempfile follows cat $TMPFILE |
#!/bin/bash TEMPFILE=$1 SPAWNEDCOMMAND=$(head -n1 $TEMPFILE) FINISHEDCOMMAND=$(head -n2 $TEMPFILE | tail -n1) $SPAWNEDCOMMAND $FINISHEDCOMMAND |
#/bin/bash function on_hup () { kill -s SIGHUP $COMMANDPID kill -s SIGUSR2 $PARENTPID exit 1 } PARENTPID=$PPID THISPID=$$ COMMAND2SPAWN=$1 trap on_hup SIGHUP $COMMAND2SPAWN & COMMANDPID=$! echo Parent PID=$PARENTPID echo This PID=$THISPID echo CommandPID=$COMMANDPID wait $COMMANDPID kill -s SIGUSR1 $PARENTPID exit 0 |
Check out spawn.sh at the
left. It traps SIGHUP with function on_hup().
It records the parent PID, which it will use later to contact the
parent as to whether it completed or was HUPped. The first and
only argument is the command to get spawned. It runs that command in
the background, grabs the spawned command's PID, and waits on that PID. If someone HUPs spawn.sh, function on_hup() executes, killing the spawned command, sending a USR2 message to the parent that called spawn.sh, and exits. However, if the spawned command terminates normally, execution falls through the wait, and spawn.sh sends USR1 message to the parent that called spawn.sh, and then the program exits. In summary, it runs the spawned program in the background. If it gets HUPped, it HUPs the spawned background program and sends USR2 to the calling program. Otherwise, the spawned background program completes and USR1 is sent to the calling program. |
#!/bin/bash ### HANDLE THE SONG FINISHING NORMALLY function on_usr1 () { echo "Song ended normally." kill -s SIGHUP $sleeppid } # HANDLE THE SONG GETTING HUPped function on_usr2 () { echo "Process was killed!" kill -s SIGHUP $sleeppid exit 1 } ### PROCESSING STARTS HERE ### SET SIGUSR1 AND SIGUSR2 TRAPS trap on_usr1 SIGUSR1 trap on_usr2 SIGUSR2 ### RUN THE SONG IN THE BACKGROUND ./spawn.sh "timidity ./test.mid" & ### GET THE BACKGROUND PROCESS'S PID spawnpid=$! echo $spawnpid > ./spawnpid.pid ### SLEEP FOREVER, UNLESS A SIGNAL COMES THROUGH sleep 1000000 & ### GRAB THE PID OF SLEEP SO YOU CAN KILL IT TO GO FORWARD sleeppid=$! ### WAIT FOR THE SLEEP TO END wait $sleepid echo "Fell through after the song completed normally" |
Start by creating
functions to handle signals USR1 and USR2, and trap them. Then run spawn.sh to
spawn timidity. It then writes out spawner.sh's PID to file ./spawnpid.pid, and sleeps forever. It will not proceed until that sleep command terminates, which will happen only if the sleep command is killed. The handler functions for USR1 and USR2 both kill that sleep. One more thing: For some reason, if you perform a ps command, spawner.sh will not appear. Instead, it will appear with the same name as the script that called it. |
#!/bin/bash function on_usr1 () { echo "Song ended normally." ./increment_songno.sh persist "spawnpid=999999" $HOME/littmidi_b.state kill -s SIGHUP $sleeppid } function on_usr2 () { echo "Process was killed!" persist "spawnpid=999999" $HOME/littmidi_b.state kill -s SIGHUP $sleeppid exit 1 } trap on_usr1 SIGUSR1 trap on_usr2 SIGUSR2 while true; do songno=$(persist "songno=?" $HOME/littmidi_b.state) songname=$(./songnum2name.sh $songno) echo songname=$songname command="timidity $songname > /dev/null" echo Command=$command ./spawn.sh "$command" & spawnpid=$! persist "spawnpid=$spawnpid" $HOME/littmidi_b.state ### Next line sleeps forever, relies on a signal ### to kill it. sleep 1000000 & sleeppid=$! wait $sleepid done |
As before, USR1 and
USR2 are
trapped and referred to functions. Both functions kill the forever
sleep. USR1 comes in when the song runs its course, while USR2 comes in
when the song (spawner.sh
actually) gets HUPped. On USR1 the playlist is incremented, while on
USR2 it's not. Each iteration of main loop gets the song number, spawn timidity on that song, persistently records the PID for spawn.sh so it can be killed from another process, and sleeps. Because that PID was persistently recorded, the following kill.sh can terminate the current song:
Functionalities such as next, previous, first, last, and set song are implemented by killing the current song, adjusting the song number, and rerunning the script to the left. I made such an animal, which will be discussed later in this TPM issue. |
#!/bin/bash function on_usr1 () { echo "Song ended normally." increment_songno.sh persist "spawnpid=999999" $HOME/littmidi_b.state kill -s SIGHUP $sleeppid } function on_usr2 () { echo "Process was killed!" persist "spawnpid=999999" $HOME/littmidi_b.state kill -s SIGHUP $sleeppid exit 1 } trap on_usr1 SIGUSR1 trap on_usr2 SIGUSR2 while true; do songno=$(persist "songno=?" $HOME/littmidi_b.state) songname=$(songnum2name.sh $songno) echo songname=$songname command="timidity $songname > /dev/null" echo Command=$command spawn.sh "$command" & spawnpid=$! persist "spawnpid=$spawnpid" $HOME/littmidi_b.state ### Next line sleeps forever, relies on a signal ### to kill it. sleep 1000000 & sleeppid=$! wait $sleepid done |
As explained
before, trap USR1 to react to a finished song, USR2 to a killed
process, presumably caused by a user request. Create a forever loop that spawns timidity with the proper song using the spawn.sh discussed in the The Cool Thing About This Program article. Once spawn.sh has run, the PID for spawn.sh is saved, and then it goes into a forever sleep. If you look at the code, the USR1 handler increments the song number and kills the sleep, allowing the loop to repeat with the incremented song number. The USR2 handler does the same thing but without the incrementation -- it's assumed that the user will have done whatever is necessary to set the song number. |
#!/bin/bash play.sh > /dev/null & ### WITHOUT THE FOLLOWING SLEEP, ### SUCCESSIVE NEXTS WILL SPAWN ### MULTITUDES OF PLAY.SH ### AND SCREW UP EVERYTHING sleep 0.1 |
playinbackground.sh | This runs play.sh in the background so it doesn't displace UMENU from the terminal. |
#!/bin/bash songno=$(persist "songno=?" $HOME/littmidi_b.state) numsongs=$(persist "numsongs=?" $HOME/littmidi_b.state) echo Currsongno=$songno of $numsongs if test $songno -lt $numsongs; then let songno=$songno+1 else let songno=1 fi persist "songno=$songno" $HOME/littmidi_b.state |
increment_songno.sh | This takes the song
number out
of persistent storage, increments it, and puts it back in persistent
storage. It handles wraparound. Because all my shellscripts work from persistent storage, merely changing the value in persistent storage is sufficient. |
#!/bin/bash songno=$(persist "songno=?" $HOME/littmidi_b.state) numsongs=$(persist "numsongs=?" $HOME/littmidi_b.state) echo Currsongno=$songno of $numsongs if test $songno -gt 1; then let songno=$songno-1 else let songno=$numsongs fi persist "songno=$songno" $HOME/littmidi_b.state |
decrement_songno.sh | Just like increment_songno.sh, except it decrements. It handles wraparound. |
let songno=$1 let oldsongno=$(persist "songno=?" $HOME/littmidi_b.state) let numsongs=$(persist "numsongs=?" $HOME/littmidi_b.state) echo Currsongno=$songno of $numsongs if test $songno -gt $numsongs; then let songno=1 elif test $songno -lt 1; then let songno=$numsongs fi persist "songno=$songno" $HOME/littmidi_b.state |
set_songno.sh | Just like increment_songno.sh, excepts it sets the song number to Arg1. It does quite a bit of testing Arg1 for a valid number between 1 and the length of the playlist, so that a valid new number is stored. |
#!/bin/bash FN=$(persist "playlist=?" $HOME/littmidi_b.state) cat $FN | grep "\.mid\s*$" | grep -v "^\s*$" | grep -v "^\s*#" |
list_playlist.sh | This is how you derive a playlist from the persistent playlist filename and the file to which it refers. The grep commands simply filter out blank lines, comments, and any files not ending in .mid. |
#!/bin/bash SONGNO=$1 list_playlist.sh | head -n $SONGNO | tail -n 1 |
songnum2name.sh | Uses head and tail on the output of list_playlist.sh to deliver the filename corresponding to the song number, for this playlist. |
#!/bin/bash grep "$1=" | sed -e 's/.*=\s*//' | sed -e 's/\s*$//' |
get_value.sh | Given key=value, this delivers the value based on the key. |
#!/bin/bash spawnpid=$(persist "spawnpid=?" $HOME/littmidi_b.state) kill -s SIGHUP $spawnpid persist "spawnpid=999999" $HOME/littmidi_b.state |
killsong.sh | Retrieves
the PID of spawn.sh
from persistent store, and HUPs it. Remember, upon getting HUPped, spawn.sh sends
USR2 to its caller then exits. It also sets the persistent spawnpid as
999999 (in other words, no spawner process running) NOTE: This shellscript was later fitted with code to check whether the process was running before attempting the kill. |
#!/bin/bash PLAYLIST=$1 persist "playlist=$PLAYLIST" $HOME/littmidi_b.state NUMSONGS=$(list_playlist.sh | wc -l) echo dia $NUMSONGS songs in playlist $PLAYLIST. persist "numsongs=$NUMSONGS" $HOME/littmidi_b.state persist "songno=1" $HOME/littmidi_b.state persist "spawnpid=999999" $HOME/littmidi_b.state |
change_playlist.sh | Arg1 is the filename of the new playlist filename. Stores that, along with the number of songs on the playlist. Stores song number as 1 (start at the beginning). |
#!/bin/bash ps ax | grep 'play.sh' | grep -v grep | cut -b1-5 | xargs kill |
nuke_play.sh | When something goes wrong and processes multiply like rabbits, this is how you take them all down at once. |
#!/bin/bash ps ax | grep 'play.sh' | grep -v grep | cut -b1-5 | xargs kill killall timidity ps ax | grep 'sleep 1000000' | grep -v grep | cut -b1-5 | xargs kill |
nuke_all.sh | This is the ultimate weapon of mass destruction. When you run this puppy, all processes associated with the midi player are killed. |
ZZB:::Litt's Text Midi Frontender (bash version) Run Litt's text midi player param D: /d/at/bash/littmidi C: TMPFILE=`mktemp`; C: echo "dir=/scratch/oldsongs/mid/lists/" > $TMPFILE; C: /d/at/cpp/filepicker/fpick $TMPFILE; C: if grep -q "action=select" $TMPFILE; then ACTION=SELECT;fi; C: if test "$ACTION" = "SELECT"; then C: FN=`grep "^file=" $TMPFILE | sed -e "s/.*=//"`; C: persist "playlist=$FN" $HOME/littmidi_b.state; C: persist "songno=1" $HOME/littmidi_b.state; C: numsongs=$(list_playlist.sh | wc -l); C: persist "numsongs=$numsongs" $HOME/littmidi_b.state; C: killsong.sh; C: playinbackground.sh > /dev/null; C: fi; C: rm -f $TMPFILE; pAuse param D: /d/at/bash/littmidi C: killsong.sh; Unpause param D: /d/at/bash/littmidi C: killsong.sh; C: playinbackground.sh; Beginning of song param D: /d/at/bash/littmidi C: killsong.sh; C: playinbackground.sh; First song param D: /d/at/bash/littmidi C: killsong.sh; C: set_songno.sh 1; C: playinbackground.sh; Next param D: /d/at/bash/littmidi C: killsong.sh; C: increment_songno.sh; C: playinbackground.sh; Previous param D: /d/at/bash/littmidi C: killsong.sh; C: decrement_songno.sh; C: playinbackground.sh; Last song param D: /d/at/bash/littmidi C: killsong.sh; C: numsongs=$(persist "numsongs=?" $HOME/littmidi_b.state); C: set_songno.sh $numsongs; C: playinbackground.sh; Jump to song param D: /d/at/bash/littmidi C: echo dia1; C: FN=$(persist playlist=? $HOME/littmidi_b.state); C: TMPFILE=`mktemp`; C: echo dia2; C: persist action= $HOME/littmidi_b.state; C: persist songno= $HOME/littmidi_b.state; C: echo dia3; C: echo "superselect=n" >> $TMPFILE; C: echo "startofdata" >> $TMPFILE; C: echo dia4; C: echo dia4.5 FN=$FN; C: list_playlist.sh >> $TMPFILE; C: echo dia5; C: echo dia6; C: /d/at/cpp/filepicker/rpick $TMPFILE; C: echo dia7; C: cat $TMPFILE; C: ACTION=$(grep "action=" $TMPFILE | get_value.sh "action"); C: if test "$ACTION" = "select"; then C: RECNO=$(grep "recno=" $TMPFILE | get_value.sh "recno"); C: echo dia recnob4=$RECNO; C: let RECNO=$RECNO+1; C: echo dia recno=$RECNO; C: persist "songno=$RECNO" $HOME/littmidi_b.state; C: killsong.sh; C: playinbackground.sh; C: fi; C: rm -f $TMPFILE; S: 1 Change Playlist param D: /scratch/oldsongs/mid/lists C: TMPFILE=`mktemp`; C: echo "dir=/scratch/oldsongs/mid/lists/" > $TMPFILE; C: /d/at/cpp/filepicker/fpick $TMPFILE; C: if grep -q "action=select" $TMPFILE; then ACTION=SELECT;fi; C: if test "$ACTION" = "SELECT"; then C: FN=`grep "^file=" $TMPFILE | sed -e "s/.*=//"`; C: persist "playlist=$FN" $HOME/littmidi_b.state; C: persist "songno=1" $HOME/littmidi_b.state; C: numsongs=$(list_playlist.sh | wc -l); C: persist "numsongs=$numsongs" $HOME/littmidi_b.state; C: cd /d/at/bash/littmidi; C: killsong.sh; C: playinbackground.sh; C: fi; Kill Litts Midi Bash Player param D: /d/at/bash/littmidi C: killsong.sh Zap zombie midi players param C: zapmidizombies S: 1 Volume ::: Mplayer volume Louder param C: VOLM=$(persist "volume=?" $HOME/littmidi_b.state); C: if test "$VOLM" = ""; then C: let VOLM=60; C: persist "volume=$VOLM" $HOME/littmidi_b.state; C: fi; C: if test $VOLM -lt 100; then C: let VOLM=$VOLM+1; C: persist "volume=$VOLM" $HOME/littmidi_b.state; C: aumix -v $VOLM -w $VOLM; C: fi; C: echo "Volume is $VOLM"; Softer param C: VOLM=$(persist "volume=?" $HOME/littmidi_b.state); C: if test "$VOLM" = ""; then C: let VOLM=60; C: persist "volume=$VOLM" $HOME/littmidi_b.state; C: fi; C: if test $VOLM -gt 0; then C: let VOLM=$VOLM-1; C: persist "volume=$VOLM" $HOME/littmidi_b.state; C: aumix -v $VOLM -w $VOLM; C: fi; C: echo "Volume is $VOLM"; Mute param C: aumix -v 0 -w 0; Unmute param C: VOLM=$(persist "volume=?" $HOME/littmidi_b.state); C: if test "$VOLM" = ""; then C: let VOLM=60; C: persist "volume=$VOLM" $HOME/littmidi_b.state; C: fi; C: aumix -v $VOLM -w $VOLM; C: echo "Volume is $VOLM"; Observe Volume param C: VOLM=$(persist "volume=?" $HOME/littmidi_b.state); C: echo Littmidi volume=$VOLM; C: aumix -q; S: 1 seT volume to specific number (0-100) param C: ORG=$(persist "volume=?" $HOME/littmidi_b.state); C: VOLM=%1%Volume please (0-100)%%; C: echo "Original volume=$ORG"; C: echo "$VOLM" | grep "[^[:digit:]]"; C: NOTANUMBER=$?; C: test -z $VOLM; C: ZEROLENGTH=$?; C: if test "$NOTANUMBER" = "1" -a "$ZEROLENGTH" = "1"; then C: persist "volume=$VOLM" $HOME/littmidi_b.state; C: aumix -v $VOLM -w $VOLM; C: echo "New volume is $VOLM"; C: else C: echo "Bad input: $VOLM"; C: echo "Volume unchanged at $ORG"; C: fi; S: 1 ^Quit Special functions ::: Special Functions Menu Persist display param C: cat $HOME/littmidi_b.state S: 1 show pAth: param C: echo Path=$PATH S: 1 pS ax param C: ps ax | less; What time is it? param C: date +%Y/%m/%d__%H:%M:%S S: 1 Edit Litt's Text Music Player menu param C: gvim /d/at/bash/littmidi/zzb.emdl Rebuild Litt's Text Music Player menu param C: /d/bats/rebuild_umenu_zzb.sh Timidity killall param C: killall -s SIGHUP timidity ^Quit plaYlist ::: Playlist menu Get Current Playlist param C: FN=$(persist playlist=? $HOME/littmidi_b.state); C: echo $FN S: 1 Edit Playlist param C: gvim $(persist playlist=? $HOME/littmidi_b.state); ^Quit ^eXit |
"Run" first runs a filepicker so the
user can choose a playlist file.
If the user chooses to select and not
cancel, it persistently stores the
playlist filename, the song number
(strongarmed to 1), and the number
of songs in the playlist.
Finally it kills any current song,
and runs play.sh in the background.
Note that this is essentially just
like the change directory
functionality.
This is much too big to put in an
EMDL file. It really should have
been a script.
=========================
Pause simply kills the current song,
so nothing's playing.
==========================
Unpause re-kills the song just in
case, and then runs play.sh in the
background.
===========================
Beginning kills the current song
and runs play.sh in the background
to replay the current song from the
beginning.
==========================
First song sets the song number to 1,
kills the current song and plays the
(now) current song.
==========================
Next kills the current song,
increments the persistent song
number, and plays the (now) current
song.
===========================
Previous is just like Next except it
decrements.
===========================
Last song sets the song number to the
number of songs in the current
playlist, then kills and replays.
===========================
Jump to song uses list_playlist.sh
to create a recordpicker from which
the user chooses a song. The
recordpicker delivers a 0 based
record number, which is incremented
to get a 1 based song number, which
is stored persistently, after which
there's a kill and play.
This is much too big to put in an
EMDL file. It really should have
been a script.
==========================
Change Playlist runs a filepicker,
stores the user's choice as the
playlist filename, derives the number
of songs and stores that, stores the
song number as 1, and stores the
spawner PID as 999999, meaning no
spawner.
This is much too big to put in an
EMDL file. It really should have
been a script.
==================================
Kills the current player and does
not replace it.
==================================
This Zap is for the old Ruby based
player, and needs recoding for the
bash based player.
==================================
The volume functions store a single
volume number, and set aumix's
master volume AND pcm to that value.
================================
The special functions are mostly
diagnostic or compilation.
================================
Persist Display displays all
variables in peristent storage.
================================
Show path shows the current path.
================================
ps ax shows that command in a less
pager.
================================
What time is it shows the time.
================================
Edit Litt's Text Music Player lets
you edit the EMDL file.
================================
Rebuild compiles the EMDL file into
a menu, shows any errors or warnings,
and gives you the choice of deploying
(transferring) the new menu or not.
================================
Timidity Killall kills all Timidity
processes.
================================
|
#!/bin/bash TMPFILE=mktemp cd /d/at/bash/littmidi persist "action=cancel" $HOME/littmidi_b.state echo "dir=/scratch/oldsongs/mid/lists/" > $TMPFILE /d/at/cpp/filepicker/fpick $TMPFILE if grep -q "action=select" $TMPFILE; then ACTION=SELECT fi; if test "$ACTION" = "SELECT"; then FN=`grep "^file=" $TMPFILE | sed -e "s/.*=//"` change_playlist.sh $FN persist "action=select" $HOME/littmidi_b.state fi rm -f $TMPFILE |
change_playlist_ui.sh | This runs the
filepicker, stores the action, and if the action was select it runs change_playlist.sh
to store everything else that should be stored. It's up to the UMENU part of the process to actually kill and rerun. |
#!/bin/bash TMPFILE=`mktemp` echo "superselect=n" >> $TMPFILE echo "startofdata" >> $TMPFILE list_playlist.sh >> $TMPFILE /d/at/cpp/filepicker/rpick $TMPFILE ACTION=$(grep "action=" $TMPFILE | get_value.sh "action") persist "action=$ACTION" $HOME/littmidi_b.state if test "$ACTION" = "select"; then RECNO=$(grep "recno=" $TMPFILE | get_value.sh "recno") let RECNO=$RECNO+1 set_songno.sh $RECNO fi rm -f $TMPFILE |
jump2song.sh | This runs the
recordpicker against the playlist's songs, accepts the user's choice,
stores the action (select
or cancel), and if select it runs set_songno.sh
to store relevent info. It's up to the UMENU part of the process to actually kill and rerun. |
Change Playlist param C: change_playlist_ui.sh; C: action=$(persist "action=?" $HOME/littmidi_b.state); C: if test "$action" = "select"; then C: killsong.sh; C: playinbackground.sh; C: fi; S: 1 |
Change Playlist | Here change_playlist_ui does almost all the work, including querying the user with a filepicker. All UMENU does is make sure the user selected rather than cancelled, and if so kill and rerun. |
Jump to song param C: jump2song.sh; C: action=$(persist action=? $HOME/littmidi_b.state); C: if test "$action" = "select"; then C: killsong.sh; C: playinbackground.sh; C: fi S: 1 |
Jump to song | Here jump2song.sh does most of the work, including querying the user with a recordpicker. All UMENU does is test that the user selected instead of cancelled, and if so kills and replays. |
#!/bin/bash while true; do cat /d/bats/littmidi.fifo done |
#!/bin/bash TMPFILE=`mktemp` echo "superselect=n" >> $TMPFILE echo "startofdata" >> $TMPFILE list_playlist.sh >> $TMPFILE /d/at/cpp/filepicker/rpick $TMPFILE ACTION=$(grep "action=" $TMPFILE | get_value.sh "action") persist "action=$ACTION" $HOME/littmidi_b.state exitcode=1 if test "$ACTION" = "select"; then RECNO=$(grep "recno=" $TMPFILE | get_value.sh "recno") let RECNO=$RECNO+1 set_songno.sh $RECNO exitcode=0 fi rm -f $TMPFILE exit $exitcode |
The
code to the right
picks a song and delivers the song name to persistent storage. There's
no way around that. But it also delivers the user's action (select or
cancel) to persistent storage. That's unnecessary because that
information can be delivered back via the exit code (0 for select, 1
for cancel). This way the program that called the code to the left changes the song if this code's exit value is 0, but does nothing if it's nonzero. If the calling program had looked at the "action" variable in persistent disk storage, and if some other process had subsequently modified that value (difficult but not impossible), the calling program would do the wrong thing and display a hard to find bug. Exit codes are by their nature modular because only the process' caller can see them. |
Foreground
Child (songnum2name.sh) |
Parent | |||
#!/bin/bash SONGNO=$1 list_playlist.sh | head -n $SONGNO | tail -n 1 |
songno=$(persist "songno=?" $HOME/littmidi_b.state) songname=$(songnum2name.sh $songno) |
The parent gets the song number, then runs songnum2name.sh and sets $songname to the output of songnum2name. sh.songnum2name.sh lists all songs, and uses head and tail to grab the songname corresponding to $1. |
MODULARITY TIPS | ||
FAVOR
THESE
|
LIMIT
THESE
|
AUTHOR'S NOTE
The output of many of the ps ax commands in this article were altered to make the output less wide. In many cases I did things like substitute "gimp", "dia", "timidity ./test.mid", or "gvim test.sh" for much longer lines in the output. I also reduced the number of spaces between the words "hangup" and "sleep". That being said, in no case did I alter any line containing ./test.sh or sleep, so for the purposes of this article, the ps commands are proof of the existence of nonexistence of the sleep or test.sh processes. |
Representation | What it Represents | |
$$ |
PID of current process | |
$! |
PID of last background process that was run | |
$PPID |
PID of parent of current process | |
$? |
Exit status of last foreground process run |
grep "George Bush" myfile.txt # See whether file contains president's name georgethere=$? # Store grep's return code in $georgethere timidity ./test.mid & # Run timidity in the background timidity_pid=$! # Store timidity's PID in timidity_pidOnce so stored, you can use these values anywhere, including in signal handler routines, even after many other processes have been run, both foreground and background. If the return code of a foreground process is important, always save $? immediately upon termination of the foreground process. If the PID of a process your script has run in the background is important, always save $! immediately after running it in the background.
timidity ./test.mid & # Run timidity in the background timidity_pid=$! # Store timidity's PID in timidity_pid echo $timidity_pid > tim.pid # Store it to diskNow, if any process wants to send, let's say, a USR1 signal to that timidity process, it's as simple as this:
kill -s SIGUSR1 $(cat tim.pid)
NOTE
As mentioned in the Build AAuSE Modularly article, storing data on disk
decreases modularity, so do it only when necessary. Note also that if
you store it to a filename known only to the process receiving the
information (perhaps the filename contains the receiver's PID), and if
the receiving process deletes it immediately after storing it to a
variable, this increases modularity over using a descriptive filename. |
timidity ./test.mid & # Run timidity in the background timidity_pid=$! # Store timidity's PID in timidity_pid # do other stuff ls -ldF /proc/$timidity_pid > /dev/null 2>&1 # Check if the PID has a directory in /proc $stillrunning=$? # If it's still running, $stillrunning is 0 # otherwise $stillrunning will be nonzero
#!/bin/bash mypid=$$ mycommand="kill -s SIGUSR1 $mypid" echo $mycommand > $HOME/danger.sh chmod a+x $HOME/danger.sh sleep 30 echo FELL THROUGH |
[slitt@mydesk ~]$ ./test.sh User defined signal 1 [slitt@mydesk ~]$ ./test.sh FELL THROUGH [slitt@mydesk ~]$ |
First, notice that
the script writes a second script, danger.sh,
which kills the job number corresponding to the running script.
Recording the PID of the running script so others can signal it is a
very common technique. Running danger.sh
sends a USR1 signal to the running script -- a fast way to send the
signal. The first time ./danger.sh was run in order to kill the script. The script terminated immediately and did not print the final message. On the second run, the script was not called, so the script ran its 30 seconds and printed the final message. |
#!/bin/bash function donothing() { echo -n } trap donothing SIGUSR1 mypid=$$ mycommand="kill -s SIGUSR1 $mypid" echo $mycommand > $HOME/danger.sh chmod a+x $HOME/danger.sh sleep 30 echo FELL THROUGH |
[slitt@mydesk ~]$ ./test.sh FELL THROUGH [slitt@mydesk ~]$ |
Here, whether ./danger.sh
was run or not, the script ran all the way to the end, SIGUSR1 signal
executed
function donothing() instead of killing the script. I've seen documentation stating you could disable a signal like this: trap SIGUSR1I can tell you that on my system, that doesn't work, and the script will be terminated by receipt of a SIGUSR1. On my system, you must include either the function name or an empty string: trap "" SIGUSR1The preceding trap completely ignores SIGUSR1, to the extent that a current wait is not breached. |
#!/bin/bash function dosomething() { echo "Hit me with your best shot!" } trap dosomething SIGUSR1 mypid=$$ mycommand="kill -s SIGUSR1 $mypid" echo $mycommand > $HOME/danger.sh chmod a+x $HOME/danger.sh sleep 30 echo FELL THROUGH |
[slitt@mydesk ~]$ ./test.sh Hit me with your best shot! FELL THROUGH [slitt@mydesk ~]$ ./test.sh FELL THROUGH [slitt@mydesk ~]$ |
Here I ran danger.sh six
times, but the message printed only once, and not until the sleep
was over. That's not a good example of "doing useful work". What we
want is that every time we hit it with a USR1, it prints the "hit me"
message. Could it have to do with the fact that sleep was in the foreground when the interrupts hit? |
#!/bin/bash function dosomething() { echo "Hit me with your best shot!" } trap dosomething SIGUSR1 mypid=$$ mycommand="kill -s SIGUSR1 $mypid" echo $mycommand > $HOME/danger.sh chmod a+x $HOME/danger.sh sleep 30 & sleeppid=$! wait $sleeppid echo FELL THROUGH |
[slitt@mydesk ~]$ ./test.sh Hit me with your best shot! FELL THROUGH [slitt@mydesk ~]$ |
Yep -- now the
signal is handled instantly. But there's trouble. The instant you issue the ./danger.sh command, the script terminates but performs the signal handler routine and prints the last statement. A ps command shows that the sleep command is still running, so the problem isn't that the SIGUSR1 got passed on to the sleep command -- the problem is that either the interrupt or the starting of the handler terminated the wait command. Remember, what we want is for the "hit me" message to print immediately after each USR1 signal. |
#!/bin/bash
function dosomething()
{
echo "Hit me with your best shot!"
wait $sleeppid
}
trap dosomething SIGUSR1
mypid=$$
mycommand="kill -s SIGUSR1 $mypid"
echo $mycommand > $HOME/danger.sh
chmod a+x $HOME/danger.sh
sleep 30 &
sleeppid=$!
wait $sleeppid
echo FELL THROUGH
|
[slitt@mydesk ~]$ ./test.sh Hit me with your best shot! Hit me with your best shot! FELL THROUGH [slitt@mydesk ~]$ |
I issued the ./danger.sh
command five times. The first time the "hit me" message printed
instantly. Then the script did nothing more until the sleep
terminated, after which it printed one more "hit me" message and then
fell through and printed the final message. Ugh!!! |
#!/bin/bash function dosomething() { echo "Hit me with your best shot!" } trap dosomething SIGUSR1 mypid=$$ mycommand="kill -s SIGUSR1 $mypid" echo $mycommand > $HOME/danger.sh chmod a+x $HOME/danger.sh sleep 30 & sleeppid=$! loopvar=999999 while test $loopvar -ne 0; do wait $sleeppid loopvar=$? done echo FELL THROUGH |
[slitt@mydesk ~]$ ./test.sh FELL THROUGH [slitt@mydesk ~]$ ./test.sh Hit me with your best shot! Hit me with your best shot! Hit me with your best shot! Hit me with your best shot! Hit me with your best shot! FELL THROUGH [slitt@mydesk ~]$ |
Ah, now that's more
like it. This version prints the "hit me" message immediately upon
running ./danger.sh,
and keeps printing it immediately every time. When the waited for sleep
command finally terminates, the loop ends and we fall through. It turns out that the signal interrupts the wait command, which returns 138 when so interrupted. It returns 0 when the waited-for process times out. So we just loop through waits until one returns 0. Notice this is NOT polling. The loop is iterated only when a SIGUSR1 is received. Otherwise it sits and waits. |
#!/bin/bash function dosomething() { echo "Hit me with your best shot!" } function terminate_loop() { loopvar=0 } trap dosomething SIGUSR1 trap terminate_loop SIGUSR2 mypid=$$ mycommand="kill -s SIGUSR1 $mypid" echo $mycommand > $HOME/danger.sh chmod a+x $HOME/danger.sh mycommand="kill -s SIGUSR2 $mypid" echo $mycommand > $HOME/danger2.sh chmod a+x $HOME/danger2.sh sleep 1000000 & sleeppid=$! loopvar=999999 while test $loopvar -ne 0; do wait $sleeppid done kill -s SIGHUP $sleeppid echo FELL THROUGH ps ax | tail -n 8 |
[slitt@mydesk ~]$ ./test.sh Hit me with your best shot! Hit me with your best shot! FELL THROUGH 7612 pts/10 S+ 0:00 /usr/bin/less -isr 7718 ? S 0:00 kcalc 7766 ? S 0:00 man bash 7986 pts/4 S+ 0:00 gimp 7987 pts/4 S+ 0:01 dia 7996 pts/6 S+ 0:00 /bin/bash ./test.sh 8006 pts/6 R+ 0:00 ps ax 8007 pts/6 R+ 0:00 tail -n 8 ./test.sh: line 32: 7999 Hangup sleep 1000000 [slitt@mydesk ~]$ |
In the preceding, we ran danger.sh twice to hit it with USR1, and then ran danger2.sh to hit it with USR2, which terminated the loop. The final ps shows that the sleep is no longer running, due to the kill just before the script ends. |
#!/bin/bash
function dosomething()
{
echo "Hit me with your best shot!"
}
function terminate_loop()
{
kill -s SIGHUP $sleeppid
loopvar=0
}
trap dosomething SIGUSR1
trap terminate_loop SIGUSR2
mypid=$$
mycommand="kill -s SIGUSR1 $mypid"
echo $mycommand > $HOME/danger.sh
chmod a+x $HOME/danger.sh
mycommand="kill -s SIGUSR2 $mypid"
echo $mycommand > $HOME/danger2.sh
chmod a+x $HOME/danger2.sh
sleep 1000000 &
sleeppid=$!
loopvar=999999
while test $loopvar -ne 0; do
wait $sleeppid
done
echo FELL THROUGH
ps ax | tail -n 8
|
[slitt@mydesk ~]$ ./test.sh Hit me with your best shot! Hit me with your best shot! FELL THROUGH 7602 pts/10 S+ 0:00 man bash 7605 pts/10 S+ 0:00 gimp 7606 pts/10 S+ 0:00 dia 7612 pts/10 S+ 0:00 /usr/bin/less -isr 7718 ? S 0:00 kcalc 7766 ? S 0:00 timidity ./test2.mid 8504 pts/6 S+ 0:00 /bin/bash ./test.sh 8519 pts/6 R+ 0:00 ps ax ./test.sh: line 32: 8507 Hangup sleep 1000000 [slitt@mydesk ~]$ |
This works the same
way, but you know that no matter what kind of break statements are used
in the loop, the sleep
command will terminate. Of course, if an untrapped signal terminates the script, sleep will live on forever (or for 11 days). |
#!/bin/bash function dosomething() { echo "USR1 encountered" } function terminate_loop() { echo "USR2 encountered" kill -s SIGHUP $sleeppid loopvar=0 } function exit_cleanly() { echo "Misc signal encountered" kill -s SIGHUP $sleeppid exit 1 } trap exit_cleanly SIGHUP trap exit_cleanly SIGINT trap exit_cleanly SIGQUIT trap exit_cleanly SIGILL trap exit_cleanly SIGTRAP trap exit_cleanly SIGABRT trap exit_cleanly SIGBUS trap exit_cleanly SIGFPE trap exit_cleanly SIGKILL trap exit_cleanly SIGUSR1 trap exit_cleanly SIGSEGV trap exit_cleanly SIGUSR2 trap exit_cleanly SIGPIPE trap exit_cleanly SIGALRM trap exit_cleanly SIGTERM trap exit_cleanly SIGSTKFLT trap exit_cleanly SIGCHLD trap exit_cleanly SIGCONT trap exit_cleanly SIGSTOP trap exit_cleanly SIGTSTP trap exit_cleanly SIGTTIN trap exit_cleanly SIGTTOU trap exit_cleanly SIGURG trap exit_cleanly SIGXCPU trap exit_cleanly SIGXFSZ trap exit_cleanly SIGVTALRM trap exit_cleanly SIGPROF trap exit_cleanly SIGWINCH trap exit_cleanly SIGIO trap exit_cleanly SIGPWR trap exit_cleanly SIGSYS trap dosomething SIGUSR1 trap terminate_loop SIGUSR2 mypid=$$ mycommand="kill -s SIGUSR1 $mypid" echo $mycommand > $HOME/danger.sh chmod a+x $HOME/danger.sh mycommand="kill -s SIGUSR2 $mypid" echo $mycommand > $HOME/danger2.sh chmod a+x $HOME/danger2.sh sleep 1000000 & sleeppid=$! loopvar=999999 while test $loopvar -ne 0; do wait $sleeppid done echo FELL THROUGH ps ax | tail -n 8 |
[slitt@mydesk ~]$ ./test.sh USR1 encountered USR1 encountered USR2 encountered FELL THROUGH 7612 pts/10 S+ 0:00 /usr/bin/less -isr 7718 ? S 0:00 kcalc 7766 ? S 0:00 timidity ./test.sh 8534 pts/3 Sl 0:01 gimp 8537 pts/3 S 0:00 gvim test.sh 8541 pts/3 S 0:01 dia 8661 pts/6 S+ 0:00 /bin/bash ./test.sh 8669 pts/6 R+ 0:00 ps ax [slitt@mydesk ~]$ ./test.sh Misc signal encountered [slitt@mydesk ~]$ |
After running the
script, from
another terminal I hit it with two USR1 signals and one USR2, which
killed the sleep and caused the loop to terminate. I then ran it again and pressed Ctrl+C, which sent a SIGINT to the script. SIGINT was trapped to exit_cleanly(), which killed sleep and then exited immediately. Notice that USR1 and USR2 were trapped twice, and that the later traps overrode the earlier ones. That's important. Notice also that I could have done all the miscellaneous trapping in a single statement, listing all the interrupts after the handler routine. That's perfectly legal, but it makes for a very long line, and also makes it more difficult when you want to use binary search to find out what signal is hitting you. More on that later. |
trap on_exit EXITIn the preceding, function on_exit() would be performed upon script termination. Here's our script modified so sleep is killed by the exit routine instead of on every exit except USR1 and USR2:
#!/bin/bash function dosomething() { echo "USR1 encountered" } function terminate_loop() { echo "USR2 encountered" kill -s SIGHUP $sleeppid sleeprunning="no" loopvar=0 } function exit_cleanly() { echo "Running exit routine" if test "$sleeprunning" = "yes"; then kill -s SIGHUP $sleeppid echo "Sleep $sleeppid killed." else exit 0 fi } trap exit_cleanly EXIT trap dosomething SIGUSR1 trap terminate_loop SIGUSR2 mypid=$$ mycommand="kill -s SIGUSR1 $mypid" echo $mycommand > $HOME/danger.sh chmod a+x $HOME/danger.sh mycommand="kill -s SIGUSR2 $mypid" echo $mycommand > $HOME/danger2.sh chmod a+x $HOME/danger2.sh sleep 1000000 & sleeppid=$! loopvar=999999 sleeprunning="yes" while test $loopvar -ne 0; do wait $sleeppid done echo FELL THROUGH ps ax | tail -n 8 |
[slitt@mydesk ~]$ ./test.sh |
Function exit_cleanly()
is changed so that it kills $sleeppid
only if $sleeprunning
is "yes". That variable is set to "yes" when sleep is run,
and set to "no" when the USR2 function kills sleep. In this
way, there's never an attempt to kill an already killed sleep.
Remember, in this version exit_cleanly()
runs no matter what causes the program to end. The trap statement now sports a signal spec of EXIT, the reserved word for setting an exit routine (on_exit handler, etc). |
trap "" SIGUSR1This really makes the script immune to the signal, because unlike traps that go to a function, this trap doesn't even go past the current wait or foreground command.
#!/bin/bash function usr1_handler() { echo "Parent received a USR1" } trap usr1_handler SIGUSR1 echo "Starting parent..." mypid=$$ ./child.sh & childpid=$! echo "Parent sees childpid as $childpid" echo "Parent is about to sleep 3" sleep 3 echo "Parent woke up from 3 second sleep" echo "Parent is about to send USR1 to child" kill -s SIGUSR1 $childpid wait $childpid echo "Parent finished" |
The parent traps USR1 into issuing a message saying it got a USR1. It then issues lots of echos to show its progress. It runs child.sh in the background, captures the child's PID, sleeps for 3 seconds, and then sends a USR1 to teh child, and waits on the child for a response. |
#!/bin/bash function usr1_handler() { echo "Child received USR1" echo "Child about to kill sleep" kill -s SIGHUP $sleeppid echo "Child killed sleep" echo "Child about to send a USR1 to parent" kill -s SIGUSR1 $parentpid } trap usr1_handler SIGUSR1 echo "Child starting" mypid=$$ parentpid=$PPID sleep 1000000 & sleeppid=$! wait $sleeppid echo "Child about to sleep 4" sleep 4 echo "Child now terminating after 4 second sleep" |
The child sets a
handler to issue a message, then kill its sleep, issue some
more messages then send a USR1 to its parent. The body of the child issues a start message, captures the parent ID, then sleeps in the background for 11 days, and waits on the sleep. Sooner or later the parent wakes up the child with a USR1. That terminates the wait, so the child issues a message, sleeps for 4 seconds, and then issues a termination message before terminating. |
[slitt@mydesk ~]$ ./parent.sh 2>/dev/null Starting parent... Child starting Parent sees childpid as 10109 Parent is about to sleep 3 Parent woke up from 3 second sleep Parent is about to send USR1 to child Child received USR1 Child about to kill sleep Child killed sleep Child about to send a USR1 to parent Child about to sleep 4 Parent received a USR1 Parent finished [slitt@mydesk ~]$ Child now terminating after 4 second sleep |
The parent starts,
issues a start message, and then starts the child in the background.
The child immediately issues its starting message,
saves its and its parent's PIDs, and goes into an 11 day sleep. Meanwhile, the parent is still working. It issues messages about the child's PID and the fact that it will sleep for 3 seconds, and then runs sleep for 3 seconds. You can't see it in the printout to the left, but after the parent issues the message about sleeping for 3 seconds, nothing happens for 3 seconds. Then the parent wakes up, announces it has awoken and that it's going to send a USR1 to the child, and then sends the USR1 to the child. Immediately after, the parent waits on the child. Meanwhile, due to being hit with the USR1 from the parent, the child's USR1 handler prints that it received the USR1, it's about to kill its sleep. Then it kills the sleep, logic drops through below its wait, and it prints that it's about to send a USR1 to the parent and that it will now sleep for 4 seconds, which it does. Meanwhile, the parent has received the USR1 from the child. That USR1 aborts the wait, so the parent falls through and writes "parent finished" and terminates. But due to its 4 second sleep, the child is still a process. After 4 seconds its sleep is over and it prints its exit message, and then terminates. That's why the child's exit message appears after the parent's exit message. |
#!/bin/bash mypid=$$ pid2signal=$1 sleeptime=$2 message=$3 commfilename=${pid2signal}.incoming commfilename2="$pid2signal$mypid.incoming" sleep $sleeptime echo $message > $commfilename2 echo $mypid > $commfilename kill -s SIGUSR1 $pid2signal |
This is the "child"
process,
although it could have a random relationship with the process it's
calling. It gets all its information on the command line. The PID of
the process to signal is arg1, the time to sleep before signalling is
arg2, and the text message to send is arg3. It constructs two filenames. The first is the PID to signal followed by .incoming. The second is a concatination of the PID to signal and the current process's PID, followed by .incoming. It sleeps the allotted time, then writes the message to the concatted filename, writes its PID to the other filename, and signals USR1 to the PID to signal. Notice that this script writes nothing to the screen. Anything that gets written to the screen is written by the parent, which is the signallee. To summarize, after a sleep period defined by a command line arg, it writes its PID to one file and its message to another, and sends the signal. |
#!/bin/bash function usr1_handler() { echo "Parent received a USR1" fn_incoming=${mypid}.incoming sender=$(cat $fn_incoming) rm -f $fn_incoming echo "The message was sent from $sender" echo -n "The message is: " cat $mypid$sender.incoming rm -f $mypid$sender.incoming } trap usr1_handler SIGUSR1 echo "Starting parent..." mypid=$$ ./child.sh $mypid 5 first & sleep 0.2 ./child.sh $mypid 3 second & sleep 0.2 ./child.sh $mypid 1 third & loopvar=9999 while true; do sleep 100000000 & sleeppid=$! echo "Waiting for interrupt" wait $sleeppid done echo "Parent finished" |
You can think of
this as the
parent, but what it really is is the process that gets signalled by an
anonymous process and has to find the message, even though many
processes might be sending it messages. The signal handler quickly grabs the contents of the file with a name that is current processes PID followed by .incoming. If you recall, this is the file to which the child wrote its PID. Once the parent has the PID of the signaller, it concatinates its own PID with that of the sender to deduce the filename of the message, and reads the message. The main routine spawns child.sh three times without grabbing child PIDs, so it has no immediate way of knowing which child signalled it at any given time. The messages for the three child.sh are in order, but the sleep times go down, meaning that the third child will signal first, then the second child, then the first. So if everything works as expected, the parent will first write the message of the third child, then that of the second, and then that of the first. The loop at the bottom is infinite, so the way to break out of this program is with a Ctrl+C. |
[slitt@mydesk ~]$ ./parent.sh Starting parent... Waiting for interrupt Parent received a USR1 The message was sent from 12216 The message is: third Waiting for interrupt Parent received a USR1 The message was sent from 12213 The message is: second Waiting for interrupt Parent received a USR1 The message was sent from 12210 The message is: first Waiting for interrupt |
Perfect. The
messages come back
in reverse order, as expected. This program is getting messages from
several anonymous processes, and is keeping them straight by
immediately looking in the proper file in order to find the PID of the
signaller. Armed with the signaller's PID, it can concatinate that PID
with its own to read the message sent from the signaller. I'm not saying this is foolproof. If signals are coming in several times per second it's possible that while process A is reading a message from process B, process C signals and then process D overwrites it. I don't know how reentrant this stuff is, but for the purposes of Application Assembly using Small Executables, we don't need to worry about extremely frequent messages. The important thing is to get the information quickly so as to be ready for the next interrupt. |
#!/bin/bash ps ax | grep "$1" | grep -v grep | cut -b1-5 | xargs killThe preceding lists all processes, greps for arg1, eliminates the grep itself, cuts each line down to the first 5 columns, which correspond to the PID, and then sends those PIDs to the kill command. So to kill everything produced by my midi player, I might do something like this:
nuke.sh '/bin/bash.*/littmidi/play\.sh$' nuke.sh 'nuke.sh 'timidity /scratch' nuke.sh 'sleep[[:space:]][[:space:]]*1000000'The first line kills all my play.sh processes. Because play.sh might not be a unique scriptname, I include the subdirectory it's in, and just for fun I include the /bin/bash in the shebang. The dollar sign at the end is a regular expression to avoid something like play.sharon.jones. The one thing you want to avoid is killing the wrong thing. While researching this material I accidentally killed X, blowing off about 5 content-containing programs, including the NVU session in which I was writing this magazine.
ps axTo see all processes and their parent PIDs and other info, do this:
ps ax -fTo see a text-art tree of processes and their descendents (a process tree), do this:
ps axfTo see the process tree with parent PIDs and other info, do this:
ps axjfThese are just a few of the handy ps commands. See the ps man page for more ideas.
kill -lIf you want to format that list nicely so it can easily be turned into trap commands, use this:
kill -l | sed -e 's/[[:digit:]]\+)/ /g' | sed -e 's/[[:space:]]\+/\n/g' | grep -v "^\s*$"I'm sure there's a shorter expression, but that's the best I could do in 10 minutes. Obviously if you want to convert it into traps, you'll need to append at least one more pipe to insert trap "" at the beginning of each line.
list_signals.sh |
#/bin/bash |
make_signaltest.rb |
#!/usr/bin/ruby -w signalarray= STDIN.readlines signalarray.each do |line| line.chomp! line.strip! end signalarray.each do |signal| puts "function #{signal}_handler()" puts "{" puts " echo Received #{signal} signal." puts " kill $sleeppid" if signal == "SIGKILL" puts " echo Terminating on SIGKILL" puts " exit 0" end puts "}" puts end puts signalarray.each do |signal| puts "trap #{signal}_handler #{signal}" end puts "mypid=$$" puts puts "echo This is process number $mypid" puts "echo Hit with signals to diagnose, kill -9 to terminate." puts "while true; do" puts " sleep 100000000 &" puts " sleeppid=$!" puts " echo -n \"Process $mypid waiting for interrupt: \"" puts " wait $sleeppid" puts "done" |
./list_signals.sh | ./make_signaltest.rb > signaltest.shIn the preceding, signaltest.sh is a script that, on receipt of any signal except SIGKILL, issues a message about what message was sent:
[slitt@mydesk ~]$ ./signaltest.sh This is process number 15924 Hit with signals to diagnose, kill -9 to terminate. Process 15924 waiting for interrupt: Received SIGINT signal. Process 15924 waiting for interrupt: Received SIGHUP signal. Process 15924 waiting for interrupt: Received SIGUSR1 signal. Process 15924 waiting for interrupt: Received SIGUSR2 signal. Process 15924 waiting for interrupt: Received SIGALRM signal. Process 15924 waiting for interrupt: Received SIGRTMAX-4 signal. Process 15924 waiting for interrupt: Killed [slitt@mydesk ~]$ |
ls -ldF /proc/$timidity_pid > /dev/null 2>&1 $stillrunning=$?A zero value means it's running, while a nonzero value means it's not.
function usr1_handler() { echo "Child received USR1" } trap usr1_handler SIGUSR1 |
trap "" SIGUSR1 |
function exit_routine() { kill $sleeppid kill $spawnedpid } trap usr1_handler EXIT |
#!/bin/bash function on_usr1 () { echo "Song ended normally." persist "fcn=normal" $HOME/littmidi_b.state kill -s SIGHUP $sleeppid } function on_usr2 () { echo "Process was killed!" } function on_trap () { echo "Process was interrupted by a new function!" kill -s SIGHUP $sleeppid } trap on_usr1 SIGUSR1 #trap on_usr2 SIGUSR2 trap "" SIGUSR2 trap on_trap SIGTRAP playerpid=$$ echo Player PID is $playerpid persist "playerpid=$playerpid" $HOME/littmidi_b.state persist "fcn=normal" $HOME/littmidi_b.state pause="false" while true; do if test "$pause" = "false"; then songno=$(persist "songno=?" $HOME/littmidi_b.state) songname=$(./songnum2name.sh $songno) echo songname=$songname command="timidity $songname > /dev/null" echo Command=$command ./spawn.sh "$command" & spawnpid=$! persist "spawnpid=$spawnpid" $HOME/littmidi_b.state else pause="false" fi ### Next line sleeps forever, relies on a signal ### to kill it. sleep 1000000 & sleeppid=$! wait $sleepid ### Kill the current spawner killsong.sh # BUG: Why does this not always do the job? # kill -s SIGHUP $spawnpid # Do this if killsong.sh doesn't work ### Handle functionalities fcn=$(persist "fcn=?" $HOME/littmidi_b.state) case "$fcn" in ("next") echo "NEXT SONG FORCED" increment_songno.sh ;; ("prev") echo "PREVIOUS SONG FORCED" decrement_songno.sh ;; ("this") ### songno remains the same ;; ("normal") echo "NORMAL SONG INCREMENTATION" increment_songno.sh ;; ("first_song") set_songno.sh 1 ;; ("last_song") set_songno.sh $(persist "numsongs=?" $HOME/littmidi_b.state) ;; ("set_song") ### songno set by signaller process ;; ("set_playlist") ### songno and playlist set by ### signaller process ;; ("pause") pause="true" ;; ("unpause") pause="false" ;; ("quit") break ;; (*) echo "UNEXPECTED FCN=$fcn" break ;; esac done |
It's a bash script SIGUSR1 sent by spawner when a song finishes. Set function to normal Kill the sleep, and proceed from after the wait. SIGUSR2 is ignored by trapping an empty string. This subroutine is not used, but is here only for possible debugging. SIGTRAP is sent by UMENU after UMENU has changed the persistent fcn value and possibly the song number and/or playlist. Kill the sleep and proceed with the algorithm. SIGUSR1 means song has finished SIGUSR2 is ignored. The spawner normally sends this signal if it receives a SIGHUP, but that's not needed. SIGTRAP is sent by UMENU to indicate a new command is ready. Persist the PID so UMENU knows who to SIGTRAP Not paused Loop: Wait for next command or end of song, and act appropriately. Don't spawn if paused. Any other function unpauses. Prevents user confusion. Wait for interrupt. Kill current spawner, and therefore current timidity, in preparation for new command. Get fcn from persistent storage Case: do the right thing. Normal mode means the current song finished and you want to play the next one. Does nothing because UMENU called set_playlist.ui.sh to set playlist and songno. If user wants to pause, set the $pause flag. Redundent, because the if test "$pause" = "false" test resets $pause if it's true. This logic is a hook for if that reset is removed. Break out of the loop if fcn is quit. |
ZZC:::Litt's Text Midi Frontender (Centralized bash version) Run Litt's text midi player param D: /d/at/bash/littmidi C: change_playlist_ui.sh; C: action=$(persist "action=?" $HOME/littmidi_b.state); C: if test "$action" = "select"; then C: playerpid=$(persist "playerpid=?" $HOME/littmidi_b.state); C: persist "fcn=quit" $HOME/littmidi_b.state; C: kill -s SIGTRAP $playerpid; C: persist "fcn=set_playlist" $HOME/littmidi_b.state; C: aterm -e '/d/at/bash/littmidi/superplay.sh' & C: echo Loading player...; C: sleep 1; C: fi; S: 1 pAuse param C: playerpid=$(persist "playerpid=?" $HOME/littmidi_b.state); C: persist "fcn=pause" $HOME/littmidi_b.state; C: kill -s SIGTRAP $playerpid; Unpause param C: playerpid=$(persist "playerpid=?" $HOME/littmidi_b.state); C: persist "fcn=unpause" $HOME/littmidi_b.state; C: kill -s SIGTRAP $playerpid; Beginning of song param C: playerpid=$(persist "playerpid=?" $HOME/littmidi_b.state); C: persist "fcn=this" $HOME/littmidi_b.state; C: kill -s SIGTRAP $playerpid; First song param C: playerpid=$(persist "playerpid=?" $HOME/littmidi_b.state); C: persist "fcn=first_song" $HOME/littmidi_b.state; C: kill -s SIGTRAP $playerpid; Next param C: playerpid=$(persist "playerpid=?" $HOME/littmidi_b.state); C: persist "fcn=next" $HOME/littmidi_b.state; C: kill -s SIGTRAP $playerpid; Previous param C: playerpid=$(persist "playerpid=?" $HOME/littmidi_b.state); C: persist "fcn=prev" $HOME/littmidi_b.state; C: kill -s SIGTRAP $playerpid; Last song param C: playerpid=$(persist "playerpid=?" $HOME/littmidi_b.state); C: persist "fcn=last_song" $HOME/littmidi_b.state; C: kill -s SIGTRAP $playerpid; Jump to song param C: playerpid=$(persist "playerpid=?" $HOME/littmidi_b.state); C: jump2song.sh; C: if test $? -eq 0; then C: persist "fcn=set_song" $HOME/littmidi_b.state; C: kill -s SIGTRAP $playerpid; C: fi; S: 1 Change Playlist param C: playerpid=$(persist "playerpid=?" $HOME/littmidi_b.state); C: change_playlist_ui.sh; C: action=$(persist "action=?" $HOME/littmidi_b.state); C: if test "$action" = "select"; then C: persist "fcn=set_playlist" $HOME/littmidi_b.state; C: kill -s SIGTRAP $playerpid; C: fi; S: 1 Kill Litts Midi Central Bash Player param C: playerpid=$(persist "playerpid=?" $HOME/littmidi_b.state); C: persist "fcn=quit" $HOME/littmidi_b.state; C: kill -s SIGTRAP $playerpid; Zap zombie midi players param C: zapmidizombies; S: 1 Volume ::: Mplayer volume Louder param C: VOLM=$(persist "volume=?" $HOME/littmidi_b.state); C: if test "$VOLM" = ""; then C: let VOLM=60; C: persist "volume=$VOLM" $HOME/littmidi_b.state; C: fi; C: if test $VOLM -lt 100; then C: let VOLM=$VOLM+1; C: persist "volume=$VOLM" $HOME/littmidi_b.state; C: aumix -v $VOLM -w $VOLM; C: fi; C: echo "Volume is $VOLM"; Softer param C: VOLM=$(persist "volume=?" $HOME/littmidi_b.state); C: if test "$VOLM" = ""; then C: let VOLM=60; C: persist "volume=$VOLM" $HOME/littmidi_b.state; C: fi; C: if test $VOLM -gt 0; then C: let VOLM=$VOLM-1; C: persist "volume=$VOLM" $HOME/littmidi_b.state; C: aumix -v $VOLM -w $VOLM; C: fi; C: echo "Volume is $VOLM"; Mute param C: aumix -v 0 -w 0; Unmute param C: VOLM=$(persist "volume=?" $HOME/littmidi_b.state); C: if test "$VOLM" = ""; then C: let VOLM=60; C: persist "volume=$VOLM" $HOME/littmidi_b.state; C: fi; C: aumix -v $VOLM -w $VOLM; C: echo "Volume is $VOLM"; Observe Volume param C: VOLM=$(persist "volume=?" $HOME/littmidi_b.state); C: echo Littmidi volume=$VOLM; C: aumix -q; S: 1 seT volume to specific number (0-100) param C: ORG=$(persist "volume=?" $HOME/littmidi_b.state); C: VOLM=%1%Volume please (0-100)%%; C: echo "Original volume=$ORG"; C: echo "$VOLM" | grep "[^[:digit:]]"; C: NOTANUMBER=$?; C: test -z $VOLM; C: ZEROLENGTH=$?; C: if test "$NOTANUMBER" = "1" -a "$ZEROLENGTH" = "1"; then C: persist "volume=$VOLM" $HOME/littmidi_b.state; C: aumix -v $VOLM -w $VOLM; C: echo "New volume is $VOLM"; C: else C: echo "Bad input: $VOLM"; C: echo "Volume unchanged at $ORG"; C: fi; S: 1 ^Quit Special functions ::: Special Functions Menu Killers ::: Killer functions Relevent processes param C: nuke_all.sh S: 1 Play.sh param C: nuke_play.sh S: 1 Sleep param C: ps ax | grep "sleep 1000000" | grep -v grep | cut -b1-5 | xargs kill; Timidity param C: killall timidity ^Quit cLi Run Litt's text midi player param D: /d/at/bash/littmidi C: change_playlist_ui.sh; C: action=$(persist "action=?" $HOME/littmidi_b.state); C: if test "$action" = "select"; then C: playerpid=$(persist "playerpid=?" $HOME/littmidi_b.state); C: persist "fcn=quit" $HOME/littmidi_b.state; C: kill -s SIGTRAP $playerpid; C: persist "fcn=set_playlist" $HOME/littmidi_b.state; C: /d/at/bash/littmidi/superplay.sh; C: fi; S: 1 Persist display param C: cat $HOME/littmidi_b.state S: 1 show pAth: param C: echo Path=$PATH S: 1 pS ax param C: ps ax | less; Current song param C: songno=$(persist "songno=?" $HOME/littmidi_b.state); C: songname=$(songnum2name.sh $songno); C: echo "Now on song $songno: $songname"; S: 1 What time is it? param C: date +%Y/%m/%d__%H:%M:%S S: 1 Edit Litt's Text Music Player menu param C: gvim /d/at/bash/littmidi/zzc.emdl Rebuild Litt's Text Music Player menu param C: /d/bats/rebuild_umenu_zzc.sh Timidity killall param C: killall -s SIGHUP timidity ^Quit plaYlist ::: Playlist menu Get Current Playlist param C: FN=$(persist playlist=? $HOME/littmidi_b.state); C: echo $FN S: 1 Edit Playlist param C: gvim $(persist playlist=? $HOME/littmidi_b.state); ^Quit ^eXit |
Menu zzc, and main menu title Run superplay.sh. Get desired playlist from user. If user selected instead of cancel kill any existing superplay.sh then run superplay.sh in a X terminal running in the background Get superplay.sh PID. Write "pause" to fcn storage. Send superplay.sh a SIGTRAP Get superplay.sh PID. Write "unpause" to fcn. Send superplay.sh a SIGTRAP Get superplay.sh PID. Write "this" to fcn. Send superplay.sh a SIGTRAP Get superplay.sh PID. Write "first_song" to fcn. Send superplay.sh a SIGTRAP Get superplay.sh PID. Write "next" to fcn. Send superplay.sh a SIGTRAP Get superplay.sh PID. Write "prev" to fcn. Send superplay.sh a SIGTRAP Get superplay.sh PID. Write "last_song" to fcn. Send superplay.sh a SIGTRAP Get superplay.sh PID. Ask user to pick a song. and store the picked song. If user didn't cancel write "set_song" to fcn. send superplay.sh a SIGTRAP Get superplay.sh PID. Ask user to pick a playlist and store that playlist. If user didn't cancel, write "set_playlist" to fcn Send superplay.sh a SIGTRAP Get superplay.sh PID. Write "quit" to fcn. Send superplay.sh a SIGTRAP ALL VOLUME FUNCTIONS ARE THE SAME AS IN OTHER VERSIONS. THEY INTERACT ONLY WITH aumix. Nuke all must be changed to nuke_all_central for this to work. nuke_play must be changed to nuke_play for this to work. Run superplay.sh in UMENU's terminal instead of in an X terminal. Use this in CLI environments. Diagnostic: show all persistent storage. Diagnostic: show $PATH. Diagnostic: Show processes. Show number and name of current song. Show the time. Edit the EMDL file for the menu of this application. Recompile the EMDL for the menu of this application. Show errors, give user the option of replacing current menu. |
Executables | What it does | Builtins | What it does | ||
cat filename |
Writes all lines of filename to stdout. |
& |
Runs the command that precedes it, in the background, as a separate process. | ||
echo string |
Writes the string that follows it to stdout. |
wait pid |
Waits for the process whose PID is pid to complete. Can be interrupted by a trapped or untrapped signal. | ||
head -n numlines |
Writes the first numlines of the referenced file or stdin to stdout. |
trap function signal |
Assigns
a function to a signal, so when that specific signal is received, that
specific function runs. Empty string completely ignores the signal. |
||
tail -n numlines |
Writes
the last numlines
of the referenced file or stdin to stdout. Can be combined
with xhead
to write the 8th line of songfile like this:cat songfile | head -n 8 | tail -n 1 |
exec program |
Replaces the current process with the program, so the program now has the PID of the (former) current process. | ||
grep pattern filename |
Writes all lines of filename that match, via regular expressions, pattern. |
source filename |
Imports shellscript commands from the referenced file at the point of the source builtin command. | ||
sed |
Parses and transforms strings and files | ||||
cut |
Removes parts of strings. | ||||
mktemp |
Makes a temporary file with a guaranteed unique filename. | ||||
kill |
Sends a signal to another process. |
cat `./logfilelist.cgi` | grep -v "\.gif " | grep -v "\.ico " | grep -v "\.png " | grep -v "\.js H" | grep -v "\.css" | grep -v "index.cgi" | grep " 200 " | grep -v "\.jpg " | grep "\"GET " | grep -v "\.class " | grep -v "65\.94\.113\. 101" | ./logeval_worker.cgi |
find /d -type f | grep "Copy of" | xargs -P10 -n1 rmPersonally I get a little spooked by xargs -- do one wrong thing and you've trashed everything. Personally, I'd write a Ruby program to delete every file that comes in through stdin (call it rubydel.rb), and then do this:
find /d -type f | grep "Copy of" | ./rubydel.rbShellscript commands find and grep efficiently screen out most of the files, and then rubydel.rb does the heavy lifting on the remainder. Because rubydel.rb is called once, there's almost no program loading overhead.
[GoLugTech]
NOW I appreciate Linux Date: 02/01/2008 @ 12:13:53 From: Steve Litt <slitt@troubleshooters.com> (Troubleshooters.Com) To: tech@golug.org Reply to: tech@golug.org Hi all, Up until now I viewed Linux as a nice, free, stable and efficient operating system that came bundled with lots of apps. In the three days fooling around with signal based interprocess communication, I understand why the old-time-Unix guys LOVE Linux (and all Unices and workalikes). I came from the DOS world, where assembling different executables was a kludge at best, and probably wouldn't work. So I rewrote everything in Turbo C and thought I was a stud. I thought the limit of using canned programs was the system() command. With Linux I can pretty much assemble anything I want the way you could assemble circuitry with 74xx chips (or whatever they use now) and a protoboard. Now I REALLY appreciate Linux. SteveT |
"GNU/Linux" is probably the most accurate moniker one can give to this operating system. Please be aware that in all of Troubleshooters.Com, when I say "Linux" I really mean "GNU/Linux". I completely believe that without the GNU project, without the GNU Manifesto and the GNU/GPL license it spawned, the operating system the press calls "Linux" never would have happened.
I'm part of the press and there are times when it's easier to say "Linux" than explain to certain audiences that "GNU/Linux" is the same as what the press calls "Linux". So I abbreviate. Additionally, I abbreviate in the same way one might abbreviate the name of a multi-partner law firm. But make no mistake about it. In any article in Troubleshooting Professional Magazine, in the whole of Troubleshooters.Com, and even in the technical books I write, when I say "Linux", I mean "GNU/Linux".
There are those who think FSF is making too big a deal of this. Nothing could be farther from the truth. The GNU General Public License, combined with Richard Stallman's GNU Manifesto and the resulting GNU-GPL License, are the only reason we can enjoy this wonderful alternative to proprietary operating systems, and the only reason proprietary operating systems aren't even more flaky than they are now.Any article submitted to Linux Productivity Magazine must be licensed with the Open Publication License, which you can view at http://opencontent.org/openpub/. At your option you may elect the option to prohibit substantive modifications. However, in order to publish your article in Linux Productivity Magazine, you must decline the option to prohibit commercial use, because Linux Productivity Magazine is a commercial publication.
Obviously, you must be the copyright holder and must be legally able to so license the article. We do not currently pay for articles.
Troubleshooters.Com reserves the right to edit any submission for clarity or brevity, within the scope of the Open Publication License. If you elect to prohibit substantive modifications, we may elect to place editors notes outside of your material, or reject the submission, or send it back for modification. Any published article will include a two sentence description of the author, a hypertext link to his or her email, and a phone number if desired. Upon request, we will include a hypertext link, at the end of the magazine issue, to the author's website, providing that website meets the Troubleshooters.Com criteria for links and that the author's website first links to Troubleshooters.Com. Authors: please understand we can't place hyperlinks inside articles. If we did, only the first article would be read, and we can't place every article first.
Submissions should be emailed to Steve Litt's email address, with subject line Article Submission. The first paragraph of your message should read as follows (unless other arrangements are previously made in writing):
Copyright (c) 2003 by <your name>. This material may be distributed only subject to the terms and conditions set forth in the Open Publication License, version Draft v1.0, 8 June 1999 (Available at http://www.troubleshooters.com/openpub04.txt/ (wordwrapped for readability at http://www.troubleshooters.com/openpub04_wrapped.txt). The latest version is presently available at http://www.opencontent.org/openpub/).
Open Publication License Option A [ is | is not] elected, so this document [may | may not] be modified. Option B is not elected, so this material may be published for commercial purposes.
After that paragraph, write the title, text of the article, and a two sentence description of the author.