(?) starting services in "/etc/init.d"

From Benjamin A. Okopnik

Answered By Jay R. Ashworth, Mike "Iron" Orr, John Karns, Jim Dennis

There are a number of services available in "/etc/init.d" that I use only occasionally - "pdnsd" and "ntpdate", for example - and so they're not auto-started in my "/etc/rc*.d". In order to save myself repeatedly typing

su -c '/etc/init.d/pdnsd start'

"stop", etc., I decided to make the command line a bit clearer via the following script:

See attached okopnik.usr-local-bin-start.bash.txt

After creating it, I made a number of symlinks to it:

cd /usr/local/bin
for n in stop reload restart force-reload; do ln -s start $n; done

Now, all I have to do is type an action followed by the service name, like

reload pdnsd
start fetchmail
stop mysql

etc., as root (or invoke it via "su"). More obvious, less typing.

(!) [jra] Except that they're a bit too generic in the global unix namespace, IMHO. I did something similar, with a script called svc, about a release or two before RedHat did something almost identical (though a bit spiffier) called service.

(!) <shrug> It's easy enough to modify for other "rc.d" variants. The important thing here was the idea, and the, erm, "source" is available. :)

(!) [Iron] For a simpler (and more simplistic way), you can throw these shell functions into .zshrc.
function start () { /etc/init.d/$1 start ; }
function stop () { /etc/init.d/$1 stop ; }
function reload () { /etc/init.d/$1 reload ; }
function restart () { /etc/init.d/$1 restart ; }
function ctest () { /etc/init.d/$1 ctest ; }
Also easier to type than 'service start'....
(I assume bash works the same way?)

(!) You need to have semicolons on the ends...

which I added. -- Heather
(!) [Iron] That may be a bashism; it works in zsh without the semicolon. And why would you need a semicolon? You normally only need a semicolon between statements, in "for WORDS ;do", and in the case statement.
(!) [JimD] Actually this was fixed in bash 2.x. The fact that bash 1.x allowed { WORDS } (with no semicolon) is considered a bug in its parser. That's because } is NOT a command separator --- and the command echo } should simply echo a closing brace. That leads to an ambiguity in the following:
{ echo }
... is that a complete command group (in the braces) or is it a fragment including the beginning of a command group (the opening brace) followed by an echo command (which will print out a close brace character, and a newline)?
Alternatively the command line:
{ echo ; }
... is unambiguous.

(!) It's an "sh"-ism, too. You need one because it terminates a group command. From the "bash" man page:

 { list; }
        list  is  simply  executed  in  the current shell environment.
        list must be terminated with a newline or semicolon.  This  is
        known  as a group command.

It doesn't work in .bashrc. Remember that you have to "su" to run them:

Baldur:~$ ztart() { /etc/init.d/$1 start; }
Baldur:~$ su -c 'ztart pdnsd'
bash: ztart: command not found
Baldur:~$ typeset +f | grep -A3 ztart
ztart ()
    /etc/init.d/$1 start
(!) [Iron] I rarely use the "su -c" syntax. By the time you get done typing the convoluted syntax with quotes around the command, you can already be done with an interactive su session.

(!) The point here is that doing it the way you suggest makes it more complex (dependent on whether you did "su" or "su -", for example) and more fragile. I suppose you could always put it in "/etc/profile"... uh, nope, that would break for "csh", "tcsh", etc. users. This is one of those cases where a script is simply better.

No idea what you mean by "convoluted syntax", but the same reasoning applies.

(!) [JimD] In the case of zsh it appears to treat the close brace as a special case delimiter. This leads to inconsistencies like this:
  zsh$ echo {}
  zsh$ echo {
  zsh$ echo }
  zsh: parse error near `}'
(or something like that). This is probably a bug in zsh (with regards to Bourne and Korn shell compatibility. As I say, it was considered to be a bug in bash that was noted in a change log for 2.x.
All of that hairsplitting aside I must say that this is one of the most annoying changes in bash 2.x. Like many other shell scripters I'd gotten into the habit of using constructs like { ...; foo } and I still get bitten by it occasionally. (Note that in this example foo is being called with an argument of } (closing brace) and the group is incomplete. We must insert a semicolon or a newline (command separators) for it to parse correctly.

(!) I'd simply learned it as "this is the way it's done"; I guess I came to it fairly late. One thing that I remember annoyed the hell out of me early on: trying to launch two progs and background the first one; seemed like an obvious thing to do

prog1 &; prog2

- right? Wrong! Only later did I realize that '&' was a valid

terminator, just like ';' and newline. Oh, and trying to explain the order in

prog > /dev/log 2>&1 &

to my students involves removing their brains and installing them upside down...

(!) [JimD] Consistency is the hobgoblin of a small mind. Computers have "small minds" indeed!

(!) <snort> Indeed.

(!) [John K.] Interesting, as I did virtually the same thing a while back:

See attached karns.usr-local-sbin-start.sh.txt

but I put it all in /usr/local/sbin, since it has to run as root
lrwxrwxrwx    1 root     root            9 Dec  7  2001
/usr/local/sbin/start -> initScrpt*


# echo "command = /etc/init.d/$1 $0"
CMD=`echo $0 | cut -f5 -d/`


This should chop off 5 fields worth of text, delimited by slashes. Any time you see fixed numbers while doing string handling, you should beware that it won't work for the general case. -- JimD

(?) Yikes! Highly breakable (try invoking it from its own directory, one level above it, etc.) How about just "${0##*/}" instead? That'll work every time.

This offers to chop everything up to the last slash, and leave the last part, whatever it is. Also it's a built-in. But that may be true only for bash... and probably ksh. The rest is left as an exercise in shell debugging, but those of you who prefer working code should just skip to the end. -- JimD


if [ $0 = 'stop' ] ; then


(?) Has this ever worked for you? I'd be very surprised if so; $0 will never equal just "stop" (it'll be "./stop" at the very least.)

(!) [John K.] Yes.
jkInsp8000:~ # stop nscd
Shutting down Name Service Cache Daemon

(?) Doesn't work for me. Obviously, something in your script is tripping off "stop", but I see no way that it can be the above "if" statement.

See attached okopnik.testing-karns.sh-transcript.txt

(!) [John K.] As a test I truncated the script as:
CMD=`echo $0 | cut -f5 -d/`
echo "CMD = $CMD"
exit 0
... then ran the tests
..from root's home dir:
jkInsp8000:~ # stop nscd
CMD = stop
..from the scripts own dir:
jkInsp8000:~ # cd /usr/local/sbin/
jkInsp8000:/usr/local/sbin # stop nscd
CMD = stop
..and from one level above:
jkInsp8000:/usr/local # stop nscd
CMD = stop

(?) Mine acts completely differently :(

Baldur:~$ cat << ! > /usr/local/bin/tst1
> CMD=`echo $0 | cut -f5 -d/`
> echo "CMD = $CMD"
> exit 0
> Baldur:~$ chmod +x /usr/local/bin/tst1
Baldur:~$ tst1
Baldur:~$ cd /usr/local/bin
Baldur:/usr/local/bin$ tst1

Doesn't work for me, John. I can't see how it would work for you. If one of the other Gangsters wants to try it out, cool, but I don't see how it's even possible (unless you have another script, alias, or function called "stop".) Here is a simple test:

echo "./foo" | cut -f5 -d/

If you get "foo" out of that, then your "cut" is doing something magical. Or maybe it's "echo". Of course it could always be gremlins.

Just out of curiosity - you are copying and pasting (NOT retyping) the code, yes?

(!) [John K.] I'm too lazy to type it! :) At this point I'm tempted to say that I personally modified the bash code to suite my misguided purpose (ah, the joy of open source), but I didn't.

(?) I didn't think you had, but I was wondering about your gremlins. You just never know.

jkarns@jkInsp8000:~ > grep gremlin /etc/passwd


(!) [John K.] That's the only script - as I said I modified it for the test, and it responded according to my mod, so it's the one being called. Bash version is:
GNU bash, version 2.04.0(1)-release (i386-suse-linux)
Copyright 1999 Free Software Foundation, Inc.
Running your test:
jkarns@jkInsp8000:~ > echo "./foo" | cut -f5 -d/

jkarns@jkInsp8000:~ >

(!) OK, in that case, here's a guaranteed way to break it:

cd /wherever/the/script/is

I can promise you that it's going to fail. :) Same story if you ever move it into a directory that's "deeper" or "shallower" than the current one (*there's* a hell of a problem to troubleshoot!) If you use "${0##*/}", or even "echo $0|sed 's#.*/##'", that fragility goes away.

Or get really lazy and just use "basename" which is designed for this. -- JimD

See attached debugged.usr-local-sbin-start.sh.txt

Copyright © 2002
Copying license http://www.linuxgazette.net/copying.html
Published in Issue 83 of Linux Gazette, October 2002

[ Table Of Contents ][ Answer Guy Current Index ] greetings   Meet the Gang   1   2   3   4   5   6 [ Index of Past Answers ]