/Main_Page

::You must have ninja focus to complete your mission::NinjaFocus::

Btc

Views:


btc - the Bit Torrent Console

Back in the day you didn't have fancy bit torrent clients. just some very clever python code written by Bram Cohen. I wanted to run bittorrent as a service that I could control remotely. This monster started out as a simple bit of bash script and grew up to be a full sized beast. It was also the first bit of bash I did

#!/bin/bash
########################
#**********************#
#* BITTORRENT CONSOLE *#
#**********************#
########################
#
# A script to help you use BitTorrent headless.
#
# Never run this script unless you want: serious
# damage to occur to your system, your house to
# burn down, to loose your job, to be attacked by 
# hoards of moomins with chainsaws plus many many
# other really bad things
#
#############
# COPYRIGHT #
#############
#
# Copyright 2005 Kieran Whitbread
#
###########
# LICENSE #
###########
#
# This program is free software; you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
# the Free Software Foundation; either version 2 of the License, or
# (at your option) any later version.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
# GNU General Public License for more details.
#
# Read all about the GNU General Public License version 2 on the Free
# Software Foundation website, http://www.gnu.org/licenses/gpl.html
#
###########
# VERSION #
###########
#
# v0.72 - 8 December 2005
#
################
# REQUIREMENTS #
################
#
# This script depends on the following commands
#
# awk
# basename
# bash
# bittorrent (http://www.bittorrent.com tested with version 4.21)
# cat
# chmod
# cut
# dirname
# echo
# grep
# head
# kill
# mkdir
# mv
# nice
# nohup
# ps
# read
# rm
# sed
# setterm
# tail
#
#############
# FILENAMES #
#############
#
# Each download needs it's own directory to store the log file.
# Directories are created automatically based on the file name of the
# .torrent file (aka the response file).
# The characters in between the first 2 dots in your filename are used.
#
# file.name.blahblah.torrent saves to file/name/<downloadfilename.ext>
# or if you have filename.torrent it will save to 
# filename/<downloadfilename.ext>
#
# <downloadfilename.ext> being the name of the file you are downloading 
# as specified by whoever created the torrent file
#
############
# Settings #
############
#
DOWNLOAD_TREE="/store/da_pit/torrents"   # Where your .torrent files live
MAX_UPLOADS=7   # default 7
MIN_LISTENING_PORT=6871   # default 6881, you need one port for each active job
MAX_LISTENING_PORT=6879   # default 6889
DEFAULT_MAX_UPLOAD_RATE=4   # in kB/s, 0 means unlimited, default 0
LOG_UPDATE_RATE=5   # log (nohup.out) update rate in seconds, default 30
BIT_TORRENT="/usr/local/share/bittorrent"    # Path to BitTorrent Executable Files
NICENESS=10   # -19 to 19, higher means lower priority, default 10
ARCHIVE="TRUE"  # save (TRUE) or delete (FALSE) old response files, default TRUE
#
###############################################################################

ALREADY_TIDY="  WARNING:  Response Files Already Tidy"
#ARCHIVE_DIR after DOWNLOAD_TREE
BIT_TORRENT=`echo $BIT_TORRENT|sed -e 's/\/$//'`
BIT_TORRENT_DOWNLOADER="$BIT_TORRENT/bittorrent-console.py"
BIT_TORRENT_METAINFO="$BIT_TORRENT/torrentinfo-console.py"
DOWNLOAD_TREE=`echo $DOWNLOAD_TREE|sed -e 's/\/$//'`
ARCHIVE_DIR="$DOWNLOAD_TREE/old-response-files"
MKDIR_ERROR="  FAILURE:  Unable to create directory"
NO_COMPLETED_JOBS="  WARNING:  No Completed Jobs"
NO_JOBS="  WARNING:  No Active Jobs"
NO_NEW_RESPONSE_FILES="  WARNING:  No New Response Files"
NO_RESPONSE_FILES="  WARNING:  No Response Files Found"
NUMBER_OF_PORTS=$(($MAX_LISTENING_PORT - $MIN_LISTENING_PORT))
OUT_OF_PORTS="  FAILURE:  Not Enough Ports Available"
PID_ONLY="pidOnly"
prog=`basename $0`
RESPONSE_ONLY="responseOnly"
START_JOB_OK="  Success:  Starting Job"
START_JOB_BAD="    Cannot Start Job"
STOP_JOB_OK="  Success:  Job Stopped"
uploadRate=$DEFAULT_MAX_UPLOAD_RATE

function adviseUser()
{
	echo
	echo "  New Job(s) Sent to BitTorrent"
	echo "  Please check the Jobs (J) and Info (I or AI) sections"
	echo
}
function allJobInfo()
{
	activeJobs=`pidAndResponseFile`
	echo
	if [ ! "$activeJobs" = "" ]
	then
		for lines in `pidAndResponseFile $RESPONSE_ONLY|sed -e 's/\ /§/g'`
		do
			cleanLine=`echo "$lines"|sed -e 's/§/\ /g'`
			jobInfo "$cleanLine"
			echo
		done
	else
		echo "$NO_JOBS"
		echo
	fi
}
function allNew()
{
	usedAllNew=0
	currentResponseFile=1
	curJob=""
	findResponseFiles
	if [ ! "${responseFiles[1]}" = "" ]
	then
		echo
		while [ ! $responseFileCount -eq $currentResponseFile ]
		do
			curJob=`basename "${responseFiles[$currentResponseFile]}"`
			if ! jobCompleted "$curJob"
			then
				if ! jobExists `basename "${responseFiles[$currentResponseFile]}"|sed -e 's/.torrent$//'`
				then
					if portAvailable
					then
						choice=`basename "${responseFiles[$currentResponseFile]}"`
						echo -n "$START_JOB_OK - "
						choiceFeedback=`echo $choice|sed -e 's/.torrent$//'`
						echo "$choiceFeedback"
						getTargetFileName
						preExecution
						executeDownload
						permissions `dirname "$targetFile"` 
						usedAllNew=1
					else
						setterm -bold on
						echo "$OUT_OF_PORTS"
						setterm -default
						echo -n "$START_JOB_BAD - "
						echo `basename "${responseFiles[$currentResponseFile]}" .torrent`
					fi
				fi
			fi
			(( currentResponseFile++ ))
		done
		if [ $usedAllNew -eq 1 ]
		then
			adviseUser
		else
			echo "$NO_NEW_RESPONSE_FILES"
			echo
		fi
	else
		echo
		echo "$NO_RESPONSE_FILES"
		echo
	fi
}
function allStop()
{
	process=""
	echo
	for process in `pidAndResponseFile $PID_ONLY`
	do
		stopJob "$process" "no invalid"
	done
	echo
}
function archiveDir()
{
	result=""
	if [ ! "$ARCHIVE" = "FALSE" ]
	then
		result=`mkdir -p "$ARCHIVE_DIR" 2>&1`
		if [ ! "$result" = "" ]
		then
			mkdirError "$result" "$ARCHIVE_DIR"
			return 1
		else
			return 0
		fi
	else
		return 0
	fi
}
function cmdLineHelp()
{
	echo
	echo "BTC, BitTorrent Console for managing headless BitTorrent Downloads"
	echo
	echo "  > $prog      -use console mode"
	echo "  > $prog AI   -list information on all active jobs" 
	echo "  > $prog AN   -start work on all new response files"
	echo "  > $prog AS   -stop all jobs"
	echo "  > $prog J    -list active jobs"
	echo "  > $prog SC   -stop completed jobs"
	echo "  > $prog T    -tidy-up response files"
	echo
} 
function confirmAllStop()
{
	activeJobs=""
	confirm=""
	echo
	activeJobs=`pidAndResponseFile`
	if [ ! "$activeJobs" = "" ]
	then
		echo "Really Stop All Jobs? (y|n)"
		read -a confirm -p "$prog/all stop> "
		case $confirm
		in
			y|Y)	allStop ;;
			*)	continue ;;
		esac
	else
		echo "$NO_JOBS"
		echo
	fi
}
function executeDownload()
{
	cd "$workingDirectory"
	echo -n "       "
	nohup nice -n $NICENESS $BIT_TORRENT_DOWNLOADER\
		--max_uploads $MAX_UPLOADS\
		--minport $MIN_LISTENING_PORT\
		--maxport $MAX_LISTENING_PORT\
		--max_upload_rate $uploadRate\
		--display_interval $LOG_UPDATE_RATE\
		"$DOWNLOAD_TREE"/"$choice"& 
	sleep 1
}
function findResponseFiles()
{
	responseFileCount=1
	unset responsefiles
	for line in `ls -1 ${DOWNLOAD_TREE}/*.torrent 2>/dev/null|sed -e 's/\ /§/g'`
	do
		responseFiles[$responseFileCount]=`echo "$line"|sed -e 's/§/\ /g'`
		(( responseFileCount ++ ))
	done
}
function getResponseFileName()
{
        echo "Choose a Response File"
	local selection=""
	read -a selection -p "$prog/new> "
	if [ ! "${responseFiles[$selection]}" ]
	then
		invalidInput
		return 1
	elif jobExists `basename "${responseFiles[$selection]}" .torrent`
	then
		echo
		echo "  This Job Already Active"
		echo
		return 1
	else
		choice=`basename "${responseFiles[$selection]}"`
		echo
		setterm -bold on
		echo -n "  Response File:  "
		setterm -default
		echo "$choice"
		getTargetFileName
		setterm -bold on
		echo -n "  Target File:  "
		setterm -default
		echo "$targetFile"
		return 0
	fi
}
function getTargetFileName()
{
	targetFile=`$BIT_TORRENT_METAINFO "$DOWNLOAD_TREE/$choice"|\
		head -n 5|tail -n 1|cut -d: -f 2|cut -d\  -f 2-`
}
function heading()
{
	echo
	echo " No	Response File"
	echo
}
function invalid()
{
	echo
	echo "  Not a Valid Menu Item"
	echo "  Enter H for Help"
	echo
}
function invalidInput()
{
	echo
	setterm -bold on
	echo "  FAILURE:  Not a Valid Entry"
	setterm -default
	echo
}
function jobCompleted()
{
	completed=`jobInfo "$1"|sed -n -e '/^Time Left:      seeding/p'`
	if [ ! "$completed" = "" ]
	then
		return 0
	fi
	return 1
}
function jobExists()
{
	for lines in `pidAndResponseFile $RESPONSE_ONLY`
	do
		if [ "$1" = "$lines" ]
		then
			return 0
		fi
	done
	return 1
}
function jobInfo()
{
	upperDirectory=`echo "$1"|cut -d. -f 1`
	lowerDirectory=`echo "$1"|cut -d. -s -f 2`
	if [ "$lowerDirectory" = "torrent" ]
	then
		responseFile="$DOWNLOAD_TREE/$upperDirectory/nohup.out"
	else
		responseFile="$DOWNLOAD_TREE/$upperDirectory/$lowerDirectory/nohup.out"
	fi
	if [ -f "$responseFile" ]
	then
		info_saving=""
		info_file_size=""
		info_percent_done=""
		info_time_left=""
		info_download_to=""
		info_download_rate=""
		info_upload_rate=""
		info_share_rating=""
		info_seed_status=""
		info_peer_status=""
		for lines in `cat -s "$responseFile"|\
				tail -n 20|sed -e '/^[E|\[].*/d' -e '/^$/d' -e 's/\ /§/g'`
		do
			info_property=`echo "$lines"|cut -d: -f 1|sed -e 's/§/\ /g'`
			info_value=`echo "$lines"|cut -d: -f 2-|sed -e 's/§/\ /g'`
			case "$info_property"
			in
				saving)		info_saving="$info_value";;
				file\ size)	info_file_size="$info_value";;
				percent\ done)	info_percent_done="$info_value";;
				time\ left)	info_time_left="$info_value";;
				download\ to)	info_download_to="$info_value";;
				download\ rate)	info_download_rate="$info_value";;
				upload\ rate)	info_upload_rate="$info_value";;
				share\ rating)	info_share_rating="$info_value";;
				seed\ status)	info_seed_status="$info_value";;
				peer\ status)	info_peer_status="$info_value";;
			esac
		done
		echo "Saving:$info_saving"
		echo "File Size:$info_file_size"
		echo "Percent Done:$info_percent_done"
		echo "Time Left:$info_time_left"
		echo "Download To:$info_download_to"
		echo "Download Rate:$info_download_rate"
		echo "Upload Rate:$info_upload_rate"
		echo "Share Rating:$info_share_rating"
		echo "Seed Status:$info_seed_status"
		echo "Peer Status:$info_peer_status"
	fi
}
function jobInfoMenu()
{
	activeJobs=""
	count=1
	selection=""
	activeJobs=`pidAndResponseFile`
	if [ ! "$activeJobs" = "" ]
	then
		heading
		for lines in `pidAndResponseFile $RESPONSE_ONLY|sed -e 's/\ /§/g'`
		do
			activeJobs[$count]=`echo "$lines"|sed -e 's/§/\ /g'`
			echo "$count	${activeJobs[$count]}"
			(( count++ ))
		done
		echo
		echo "Enter Job Number"
		read -a selection -p "$prog/info> "
		if [ "${activeJobs[$selection]}" = "" ]
		then
			invalid
		else
			echo
			jobInfo "${activeJobs[$selection]}"
		fi
	else
		echo
		echo "$NO_JOBS"
	fi
	echo
}
function listCommands()
{
	echo
	echo " AI        - All Information"
	echo " AN        - All New"
	echo " AS        - All Stop"
	echo " H         - Display This Message"
	echo " I         - Display Information" 
	echo " J         - Display Current Jobs"
	echo " N         - New Job"
	echo " S         - Stop Job"
	echo " SC        - Stop Completed Jobs"
	echo " SR        - Set Rate of New Uploads"
	echo " T         - Tidy-up Response Files"
	echo " X or Q    - Exit"
	echo
}
function listJobs()
{
	jobsRunning=`pidAndResponseFile`
	if [ ! "$jobsRunning" = "" ]
	then
		echo
		echo "  Current Jobs:"
		echo 
		echo " PID       Response File "
		echo
		pidAndResponseFile
		echo
	else
		echo
		echo "$NO_JOBS"
		echo
	fi
}
function listResponseFiles()
{
	count=1
	listItem=""
	heading
	while [ ! "${responseFiles[$count]}" = "" ]
	do
		listItem=`basename "${responseFiles[$count]}" .torrent$`
		echo "$count	$listItem"
		 
		(( count++ ))
	done
	echo
}
function mainMenu()
{
	while read -a result -p "$prog> "
	do
		case "$result"
		in
			x|X|q|Q)		setterm -default
						exit 0 ;;
			j|J)			listJobs ;;
			s|S)			stopJobMenu ;;
			h|H|[Hh][Ee][Ll][Pp])	listCommands ;;
			N|n)			newJob ;;
			i|I)			jobInfoMenu ;;
			[aA][sS])		confirmAllStop ;;
			[aA][nN])		allNew ;;
			[aA][iI])		allJobInfo ;;
			t|T)			tidy ;;
			[sS][cC])		stopCompleted ;;
			[sS][rR])		setUploadRate ;;
			*)                      invalid ;;
		esac
	done
}
function mkdirError()
{
	message=`echo $1|cut -d: -f 3`
	setterm -bold on
	echo "$MKDIR_ERROR, $message"
	setterm -default
	echo "     Couldn't create $2"
}
function newJob()
{
	if portAvailable
	then
		findResponseFiles
		if [ "${responseFiles[1]}" = "" ]
		then
			echo
			echo $NO_RESPONSE_FILES
			echo
		else
			listResponseFiles
			if getResponseFileName
			then
				preExecution
				executeDownload
				permissions `dirname "$targetFile"`
				adviseUser
			fi
		fi
	else
		echo
		setterm -bold on
		echo "$OUT_OF_PORTS"
		setterm -default
		echo
	fi
}
function permissions()
{
	chmod o+rwx "$1" -R
}
function pidAndResponseFile()
{
	if [ "$1" = "$RESPONSE_ONLY" ]
	then
		finalCut="2"
	elif [ "$1" = "$PID_ONLY" ]
	then
		finalCut="1"
	else
		finalCut="1,2"
	fi
	field=`echo "$DOWNLOAD_TREE"|awk -F"/" '{ print NF }'`
        (( field++ ))
	ps ax -o pid= -o args=|grep bittorrent-console.py|\
		sed -e 's/^ //' -e 's/^ //'|\
		cut -d\  -f 1,14-|\
		cut -d/ -f 1,5|\
		sed -e '/^[0-9]* *$/d' -e '/:?*:/d' -e '/MATHPATH/d' -e 's/.torrent$//' -e 's/\ /\t/' -e 's/\///'|\
		cut -f $finalCut
}
function portAvailable()
{
	numberOfPortsInUse=0
	for items in `pidAndResponseFile $PID_ONLY`
	do
		(( numberOfPortsInUse++ ))
	done
	if [ $numberOfPortsInUse -lt $NUMBER_OF_PORTS ]
	then
		return 0
	else
		return 1
	fi
}
function preExecution()
{
	upperDirectory=`echo $choice|cut -d. -f 1`
        lowerDirectory=`echo $choice|cut -d. -f 2`
        result=`mkdir -p "$DOWNLOAD_TREE/$upperDirectory" 2>&1`
	if [ $? -eq 0 ]
	then
		if [ "$lowerDirectory" = "torrent" ]
		then
			workingDirectory="$DOWNLOAD_TREE/$upperDirectory"
			targetFile="$DOWNLOAD_TREE/$upperDirectory/$targetFile"
		else
		        mkdir -p "$DOWNLOAD_TREE/$upperDirectory/$lowerDirectory"
			workingDirectory="$DOWNLOAD_TREE/$upperDirectory/$lowerDirectory/"
			targetFile="$DOWNLOAD_TREE/$upperDirectory/$lowerDirectory/$targetFile"
		fi
	else
		mkdirError "$result" "$DOWNLOAD_TREE/$upperDirectory"
	fi
}
function rmTest()
{
	group=""
	groups=""
	groupW=""
	otherW=""
	user=""
	userW=`ls -l $1|sed -n -e '/^..w/p'`
	if [ ! "$userW" = "" ]
	then
		user=`ls -l $1|cut -d\  -f 4 -s`
		if [ "$user" = "$USER" ]
		then
			return 0
		fi
	fi
	groupW=`ls -l $1|sed -n -e '/^.....w/p'`
	if [ ! "$groupW" = "" ]
	then
		group=`ls -l -g $1|cut -d\  -f 4 -s`
		groups=`cat /etc/group|sed -n -e '/'$USER'/p'|cut -d: -f 1`
		for lines in $groups
		do
			if [ "$lines" = "$group" ]
			then
				return 0
			fi
		done
	fi
	otherW=`ls -l $1|sed -n -e '/^........w/p'`
	if [ ! "$otherW" = "" ]
	then
		return 0
	fi
	return 1
}
function setUploadRate()
{
	echo
	echo "Enter new upload rate in KB/s"
	read -a selection -p "$prog/set rate> "
	if [ $selection -lt 2048 -a $selection -gt -1 ] 2>/dev/null
	then
		uploadRate=$selection
	else
		invalidInput
	fi
}
function stopCompleted()
{
	activeJobs=""
	unset activeJobsArray
	count="1"
	done=""
	processes=""
	unset processesArray
	usedStopCompleted=0
	activeJobs=`pidAndResponseFile|sed -e 's/^\([0-9]*\)\t\(.*\)/\1 \2/'`
	processes=`echo "$activeJobs"|cut -d\  -f 1`
	for lines in `echo "$processes"`
	do
		processesArray[$count]="$lines"
		(( count++ ))
	done
	count=1	
	for lines in `echo "$activeJobs"|sed -e 's/^[0-9]* \(.*$\)/\1/' -e 's/ /§/g'`
	do
		activeJobsArray[$count]=`echo "$lines"|sed -e 's/§/ /g'`
		(( count++ ))
	done
	count=1
	echo
	while [ ! "${activeJobsArray[$count]}" = "" ]
	do
		jobCompleted "${activeJobsArray[$count]}"
		if [ $? -eq 0 ]
		then
			stopJob "${processesArray[$count]}" "no invalid"
			usedStopCompleted=1
		fi
		(( count++ ))
	done
	if [ "$usedStopCompleted" -eq 0 ]
	then
		echo "$NO_COMPLETED_JOBS"
	fi
	echo
}
function stopJob()
{
	if [ -d /proc/"$1" ]
	then
		cat /proc/"$1"/cmdline|grep bittorrent-console.py -a -q
		if [ "$?" = 0 ]
		then
			kill -s SIGTERM "$1"
			if [ $? = "0" ]
			then
				echo "$STOP_JOB_OK"
			else
				stopJobBad
			fi
		else
			if [ ! "$2" = "no invalid" ]
			then
				invalid
			fi
		fi
	else
		if [ ! "$2" = "no invalid" ]
		then
			invalid
		fi
	fi
}
function stopJobBad()
{
	setterm -bold on
	echo "  *****************************************"
	echo "  * FAILURE:  Unable to Terminate Process *"
	echo "  *****************************************"
	setterm -default
}
function stopJobMenu()
{
	listJobs
	echo "Enter PID"
	read -a process -p "$prog/stop> "
	if [ $process -gt 0 ] 2>/dev/null
	then
		echo
		stopJob "$process"
		echo
	else
		invalidInput
	fi
}
function tartIntro()
{
	setterm -bold on
	clear
	echo
	echo " KRAFTY  TTTTTTTT  CCCC"
	echo "  BB  BB    TT    CC  CC"
	echo "  BB   BB   TT   CC"
	echo "  BBBBBB    TT   CC"
	echo "  BB   BB   TT   CC    CC"
	echo "  BB   BB   TT    CC  CC"
	echo " BBBBBBB    TT     CUTS"
	setterm -bold off
	echo
	echo "    BitTorrent Console"
	echo "       (H for help)"
	if [ $UID == "0" ]
	then
		setterm -bold on
		echo
		echo
		echo "  *****************************************"
		echo "  * You are running this program as root! *"
		echo "  * It is bad security to run bittorent   *"
		echo "  * as root.                              *"
		echo "  *****************************************"
		setterm -bold off
	fi
}
function testSettingsOK()
{
	if [ ! -f "$BIT_TORRENT_DOWNLOADER" -o ! -f "$BIT_TORRENT_METAINFO" -o \
"$NICENESS" -gt 19 -o "$NICENESS" -lt -19 -o \
"$MAX_UPLOADS" -lt 1 -o "$MIN_LISTENING_PORT" -gt "$MAX_LISTENING_PORT" -o \
"$DEFAULT_MAX_UPLOAD_RATE" -lt 0 -o "$LOG_UPDATE_RATE" -lt 0 ] 2>/dev/null
	then
		return 1
	else
		return 0
	fi
}
function tidy()
{
	count=1
	filesNeededTidying=0
	job=""
	result=""
	findResponseFiles
	echo
	if [ ! "${responseFiles[$count]}" = "" ]
	then
		if archiveDir
		then
			while [ ! "${responseFiles[$count]}" = "" ]
			do
				job=`basename "${responseFiles[$count]}"`
				if jobCompleted "$job"
				then
					filesNeededTidying=1
					if [ "$ARCHIVE" = "FALSE" ]
					then
						rmTest "${responseFiles[$count]}"
						if [ $? -eq 0 ]
						then
							result=`rm -f "${responseFiles[$count]}" 2>&1`
						else
							result=" Permission denied"
						fi
					else
						result=`mv -f "${responseFiles[$count]}" "$ARCHIVE_DIR/$job" 2>&1`
					fi
					if [ ! "$result" = "" ]
					then
						tidyErrorMsg "$result" "$job"
					else
						echo "  Success:  $job Tidied"
					fi
				fi
				(( count++ ))
				result=""
			done
			if [ $filesNeededTidying -eq 0 ]
			then
				echo "$ALREADY_TIDY"
			fi
		fi
	else
		echo "$ALREADY_TIDY"
	fi
	echo
}
function tidyErrorMsg()
{
	message=`echo "$1"|cut -d: -f 3`
	setterm -bold on
	echo "  FAILURE:  Unable to Tidy Response File,$message"
	setterm -default
	echo "    $2"
}
if ! testSettingsOK
then
{
	echo
	echo "BTC, BitTorrent Console"
	echo 
	echo "There seems to be a problem with your settings"
	echo "you might need to edit the settings at the top"
	echo "of this script."
	echo
	echo "Check that paths have been entered corectly &"
	echo "that BitTorrent & python are installed OK."
	echo "All settings must be filled in."
	echo
	echo "$0"
	echo
	exit 1
}
fi
case $1
in
	[aA][sS])	allStop ;;
	[aA][nN])	allNew ;;
	[aA][iI])	allJobInfo ;;
	j|J)		listJobs ;;
	[sS][cC])	stopCompleted ;;
	t|T)		tidy ;;
	*)		if [ "$1" = "" ]
			then
				tartIntro
				listJobs
				mainMenu
			else
				cmdLineHelp
			fi ;;
esac

Main Menu

Personal tools

Toolbox