LinuxQuestions.org
Support LQ: Use code LQ3 and save $3 on Domain Registration
Go Back   LinuxQuestions.org > Forums > Non-*NIX Forums > Programming
User Name
Password
Programming This forum is for all programming questions.
The question does not have to be directly related to Linux and any language is fair game.

Notices

Reply
 
Search this Thread
Old 09-12-2010, 06:09 AM   #1
binbash
LQ Newbie
 
Registered: Sep 2010
Posts: 12

Rep: Reputation: 0
Single Instance of Bash Script


Hi guys,

I have a bash script which is called automatically

I want it so that if when the script is called if a previous instance of it is already running then to delay the running of it until the previous instance has stopped (effectively queue up ./script.sh var1 var2)

I have seen some posts about a 'lockfile' but this just seems to stop the second instance running rather than queueing it up to run next (it also needs to be able to queue up a 3rd/4th calling of the script and run them one at a time)

any suggestions?

p.s. the script is run using cygwin on windows

edit: I am very new to bash scripting so simple explanations please

Last edited by binbash; 09-12-2010 at 06:11 AM.
 
Old 09-12-2010, 06:59 AM   #2
konsolebox
Senior Member
 
Registered: Oct 2005
Distribution: Gentoo, Slackware, LFS
Posts: 2,245
Blog Entries: 15

Rep: Reputation: 233Reputation: 233Reputation: 233
You can try this code. I think the documentation's enough but you can ask me if there's something you don't understand. I hope you'll also refer to the bash manual first (man bash).
Code:
#!/bin/bash

INSTANCEDIR=/path/somewhere

function error {
	# send message to stderr
	echo "$1" >&- >&2
}

function main {
	local MYPID=$$ FILE PID
	local -a INSTANCEFILES

	echo "$MYPID" >> "$INSTANCEDIR/$MYPID" || {
		error "failed to record instance to $INSTANCEDIR/$MYPID"
		return 1
	}

	for (( ;; )); do
		INSTANCEFILES=("$INSTANCEDIR"/*)

		case "${#INSTANCEFILES[@]}" in
		1)
			break
			;;
		0)
			error "our instance file has been deleted."
			return 1
			;;
		esac

		for FILE in "${INSTANCEFILES[@]}"; do
			# read PID from instance file
			PID=$(<"$FILE")

			# delete the instance file if read data in it is not a digitt
			if [[ $PID != +([[:digit:]]) ]]; then
				rm "$FILE" || {
					error "failed to delete unowned instance file $FILE."
					return 1
				}
			fi

			# skip the file if it's from us
			[[ PID -eq MYPID ]] && continue

			# Delete the instance file if the process presented by the PID no longer exists
			# There might also be a better way to check the process but I'm not sure.
			if ! kill -s 0 "$PID"; then
				rm "$FILE" || {
					error "failed to delete unowned instance file $FILE."
					return 1
				}
			fi
		done
	done

	# continue the operations from here
}
---- EDIT ----

Other than this there should also be other methods like locking files with exec and using fuser to check if a process is still locked with the file; and also repeatedly using rm -f to locked files until the files count turn 1..... Although I still can't see cleaner implementation in those ways and I also find some of it as quirky.

You may also just use a single instance file to reference but I didn't choose it since I see a very tiny possibility that a race condition could occur. I get a feeling that other optimistic (ones who'll tend to say "that's ok") programmers may argue with this.

You might also want to add a code that automatically checks and/or creates INSTANCEDIR.

Last edited by konsolebox; 09-12-2010 at 07:18 AM.
 
Old 09-12-2010, 09:54 AM   #3
crts
Senior Member
 
Registered: Jan 2010
Posts: 1,604

Rep: Reputation: 446Reputation: 446Reputation: 446Reputation: 446Reputation: 446
Hi,

@konsolebox
not sure if I executed your script as it was intended to. This is what I did:
Code:
#!/bin/bash
# script.sh
error {
...
}
main {
...
}

main

# just do something unspectacular
for i in $(seq 1 10);do
	echo loop $i of script $1
	sleep 1
done

exit
Then I created another script to call it several times:
Code:
./script.sh 1
sleep 1
./script.sh 2
sleep 1
./script.sh 3
sleep 1
./script.sh 4
Some results:
Code:
$ ./caller.sh 
loop 1 of script 1
loop 2 of script 1
loop 3 of script 1
$ loop 4 of script 1
loop 5 of script 1
loop 6 of script 1
loop 7 of script 1
loop 8 of script 1
loop 9 of script 1
loop 10 of script 1
./script.sh: line 50: kill: (1041) - No such process
./script.sh: line 50: kill: (1041) - No such process
rm: cannot remove `./lock/1041': No such file or directory
failed to delete unowned instance file ./lock/1041.
loop 1 of script 3
loop 2 of script 3
loop 3 of script 3
loop 4 of script 3
loop 5 of script 3
loop 6 of script 3
loop 7 of script 3
loop 8 of script 3
loop 9 of script 3
loop 10 of script 3
./script.sh: line 50: kill: (2296) - No such process
./script.sh: line 35: ./lock/2296: No such file or directory
rm: cannot remove `./lock/2296': No such file or directory
failed to delete unowned instance file ./lock/2296.
loop 1 of script 2
loop 2 of script 2
loop 3 of script 2
loop 4 of script 2
loop 5 of script 2
loop 6 of script 2
loop 7 of script 2
loop 8 of script 2
loop 9 of script 2
loop 10 of script 2
./script.sh: line 50: kill: (1047) - No such process
loop 1 of script 4
loop 2 of script 4
loop 3 of script 4
loop 4 of script 4
loop 5 of script 4
loop 6 of script 4
loop 7 of script 4
loop 8 of script 4
loop 9 of script 4
loop 10 of script 4

$ ./caller.sh 
loop 1 of script 1
loop 2 of script 1
loop 3 of script 1
$ loop 4 of script 1
loop 5 of script 1
loop 6 of script 1
loop 7 of script 1
loop 8 of script 1
loop 9 of script 1
loop 10 of script 1
./script.sh: line 50: kill: (3258) - No such process
./script.sh: line 50: kill: (3258) - No such process
./script.sh: line 50: kill: (3258) - No such process
rm: cannot remove `./lock/3258': No such file or directory
failed to delete unowned instance file ./lock/3258.
rm: cannot remove `./lock/3258': No such file or directory
failed to delete unowned instance file ./lock/3258.
loop 1 of script 3
loop 1 of script 2
loop 2 of script 3
loop 2 of script 2
loop 3 of script 3
loop 3 of script 2
loop 4 of script 3
loop 4 of script 2
loop 5 of script 3
loop 5 of script 2
loop 6 of script 3
loop 6 of script 2
loop 7 of script 3
loop 7 of script 2
loop 8 of script 3
loop 8 of script 2
loop 9 of script 3
loop 9 of script 2
loop 10 of script 3
loop 10 of script 2
./script.sh: line 50: kill: (4380) - No such process
./script.sh: line 50: kill: (3268) - No such process
loop 1 of script 4
loop 2 of script 4
loop 3 of script 4
loop 4 of script 4
loop 5 of script 4
loop 6 of script 4
loop 7 of script 4
loop 8 of script 4
loop 9 of script 4
loop 10 of script 4
The first time the scripts were executed in the wrong order. The second time apparently the lock did not work 100% and the ouputs of script 2 and 3 interleaved.
But as I said, I am not sure if I used it right.

<removed: fawlty script>

Last edited by crts; 09-12-2010 at 10:00 AM. Reason: removed script
 
Old 09-12-2010, 11:07 AM   #4
konsolebox
Senior Member
 
Registered: Oct 2005
Distribution: Gentoo, Slackware, LFS
Posts: 2,245
Blog Entries: 15

Rep: Reputation: 233Reputation: 233Reputation: 233
sorry my mistake. i forgot to add shopt -s extglob
Code:
#!/bin/bash

shopt -s extglob

...
 
Old 09-12-2010, 11:11 AM   #5
konsolebox
Senior Member
 
Registered: Oct 2005
Distribution: Gentoo, Slackware, LFS
Posts: 2,245
Blog Entries: 15

Rep: Reputation: 233Reputation: 233Reputation: 233
edit: mistakenly pressed quote button for edit.. guess i'll better just post a new complete code. The error messages of kill -s 0 "$PID" is now sent to null.
Code:
#!/bin/bash

shopt -s extglob

INSTANCEDIR=/path/somewhere

function error {
	# send message to stderr
	echo "$1" >&- >&2
}

function main {
	local MYPID=$$ FILE PID
	local -a INSTANCEFILES

	echo "$MYPID" >> "$INSTANCEDIR/$MYPID" || {
		error "failed to record instance to $INSTANCEDIR/$MYPID"
		return 1
	}

	for (( ;; )); do
		INSTANCEFILES=("$INSTANCEDIR"/*)

		case "${#INSTANCEFILES[@]}" in
		1)
			break
			;;
		0)
			error "our instance file has been deleted."
			return 1
			;;
		esac

		for FILE in "${INSTANCEFILES[@]}"; do
			# read PID from instance file
			PID=$(<"$FILE")

			# delete the instance file if read data in it is not a digitt
			if [[ $PID != +([[:digit:]]) ]]; then
				rm "$FILE" || {
					error "failed to delete unowned instance file $FILE."
					return 1
				}
			fi

			# skip the file if it's from us
			[[ PID -eq MYPID ]] && continue

			# Delete the instance file if the process presented by the PID no longer exists
			# There might also be a better way to check the process but I'm not sure.
			if ! kill -s 0 "$PID" 2>/dev/null; then
				rm "$FILE" || {
					error "failed to delete unowned instance file $FILE."
					return 1
				}
			fi
		done

		read -t 1 -n 1
	done

	# continue the operations from here
}

main
update: added 'read -t 1 -n 1'

Last edited by konsolebox; 09-12-2010 at 11:20 AM.
 
Old 09-15-2010, 04:04 PM   #6
binbash
LQ Newbie
 
Registered: Sep 2010
Posts: 12

Original Poster
Rep: Reputation: 0
thank you konsolebox

where it says # continue the operations from here

I decided to put /path/to/script.sh $1 $2 and call singleinstance.sh $1 $2 in place of the original call (I find it easier to keep the 2 pieces of code separate) but script.sh is saying var 1 is null so it doesn't seem to be passing them

any ideas?
 
Old 09-15-2010, 08:12 PM   #7
konsolebox
Senior Member
 
Registered: Oct 2005
Distribution: Gentoo, Slackware, LFS
Posts: 2,245
Blog Entries: 15

Rep: Reputation: 233Reputation: 233Reputation: 233
I didn't know that you'd also need to pass the arguments.
Did you already add "$@" to the call to main?
Code:
 ...

main "$@"
Also you should place the variables inside quotes:
Code:
/path/to/script.sh "$1" "$2"
 
Old 09-16-2010, 03:29 PM   #8
binbash
LQ Newbie
 
Registered: Sep 2010
Posts: 12

Original Poster
Rep: Reputation: 0
seems to be working good now - thanks konsolebox
 
Old 09-16-2010, 06:57 PM   #9
konsolebox
Senior Member
 
Registered: Oct 2005
Distribution: Gentoo, Slackware, LFS
Posts: 2,245
Blog Entries: 15

Rep: Reputation: 233Reputation: 233Reputation: 233
No prob. You may now mark the thread as solved then.
 
Old 09-17-2010, 12:43 PM   #10
binbash
LQ Newbie
 
Registered: Sep 2010
Posts: 12

Original Poster
Rep: Reputation: 0
sorry but how to mark it?
 
Old 09-17-2010, 01:25 PM   #11
MTK358
LQ 5k Club
 
Registered: Sep 2009
Posts: 6,443
Blog Entries: 3

Rep: Reputation: 713Reputation: 713Reputation: 713Reputation: 713Reputation: 713Reputation: 713Reputation: 713
Thread Tools.
 
Old 09-29-2010, 05:36 AM   #12
binbash
LQ Newbie
 
Registered: Sep 2010
Posts: 12

Original Poster
Rep: Reputation: 0
the script has been running good for a number of days

but lately not sure why sometimes the script hangs not sure if it is the single instance one or the script i am running through it (need to look into it more - have been away for a few days)

and then any other instances get queued up and don't run as the first one never exits

could there be an issue if it is called twice very quickly i.e. within 1 second or less?

also could it be made so that if the instance has been running for 5 minutes it terminates (therefore allowing any other queued instances to start)?

thanks in advance

edit: could it be to do with this? http://inamidst.com/eph/cygwin

Quote:
Process ID Shortage

Cygwin does some weird stuff sometimes. For example I keep running into this problem and not being able to find the solution easily:

676102803 [main] zsh 840377 fork_copy: user/cygwin data
pass 2 failed, 0x970000..0xAF3000, done 0, windows
pid 4294640815, Win32 error 8
withcd: fork failed: resource temporarily unavailable

This is due to it running out of PIDs. I noted the problem and solution on Swhack; basically you have to run "rebaseall", but that means closing down all of your current processes and actually running it in bash which is somewhat irritating. If anyone has a better solution, I'd be glad if they could let me know. Thanks.
also stackdump:
Quote:
Exception: STATUS_ACCESS_VIOLATION at eip=61020137
eax=00E0D008 ebx=6123A614 ecx=764C0D06 edx=003441B8 esi=00000000 edi=0028F9E8
ebp=61020890 esp=0028C7B8 program=C:\cygwin\bin\sh.exe, pid 6656, thread main
cs=0023 ds=002B es=002B fs=0053 gs=002B ss=002B
Stack trace:
Frame Function Args
End of stack trace

Last edited by binbash; 09-29-2010 at 05:54 AM.
 
Old 09-30-2010, 04:55 AM   #13
konsolebox
Senior Member
 
Registered: Oct 2005
Distribution: Gentoo, Slackware, LFS
Posts: 2,245
Blog Entries: 15

Rep: Reputation: 233Reputation: 233Reputation: 233
Quite very indirect. I wonder, could the script be sometimes called within an sh.exe environment instead of bash.exe?
Quote:
Originally Posted by binbash View Post
ebp=61020890 esp=0028C7B8 program=C:\cygwin\bin\sh.exe, pid 6656, thread main
 
Old 09-30-2010, 11:34 AM   #14
binbash
LQ Newbie
 
Registered: Sep 2010
Posts: 12

Original Poster
Rep: Reputation: 0
do you mean sometimes run it in bash and sometimes in sh?

i think it is a cygwin error though as it has been happening all the time today... didn't happen at all last week

rebaseall helped for a while doesn't anymore
 
Old 10-04-2010, 07:57 AM   #15
konsolebox
Senior Member
 
Registered: Oct 2005
Distribution: Gentoo, Slackware, LFS
Posts: 2,245
Blog Entries: 15

Rep: Reputation: 233Reputation: 233Reputation: 233
The script's only meant to run with bash. If sh.exe is your default shell, that might give a difference with operations.
 
  


Reply

Tags
bash, cygwin


Thread Tools Search this Thread
Search this Thread:

Advanced Search

Posting Rules
You may not post new threads
You may not post replies
You may not post attachments
You may not edit your posts

BB code is On
Smilies are On
[IMG] code is Off
HTML code is Off


Similar Threads
Thread Thread Starter Forum Replies Last Post
[SOLVED] escaping a single quote from a bash script atbrew Programming 8 07-21-2010 09:21 AM
[SOLVED] Using a long Bash command including single quotes and pipes in a Bash script antcore Linux - General 9 07-22-2009 11:10 AM
Bash script to put log files into single file and email DragonM15 Programming 13 11-08-2007 03:27 AM
Creating Single Instance linux application praj_linux Programming 2 11-19-2004 04:06 AM
Only one instance of bash script... cmfarley19 Programming 7 05-06-2003 01:42 PM


All times are GMT -5. The time now is 09:26 AM.

Main Menu
My LQ
Write for LQ
LinuxQuestions.org is looking for people interested in writing Editorials, Articles, Reviews, and more. If you'd like to contribute content, let us know.
Main Menu
Syndicate
RSS1  Latest Threads
RSS1  LQ News
Twitter: @linuxquestions
identi.ca: @linuxquestions
Facebook: linuxquestions Google+: linuxquestions
Open Source Consulting | Domain Registration