Skip to main content

Ralsina.Me — Roberto Alsina's website

Going Higher Level, Leaving the Shell (Part I)


Why are we us­ing shell­s?

We are us­ing them for in­ter­ac­tive pur­pos­es. I sup­pose that's ok. I do it, at least.

But why are we us­ing them for sys­tem script­s? It's not that they are fast, or small (at least not bash, which most lin­ux dis­tros use)...

It's not that it's sim­ple, at least not if you try to make scripts that are not very bug­gy be­cause of the com­plex pars­ing...

It must be be­cause we ex­pect ev­ery sys­tem to have a Bourne shell some­where. Many Lin­ux users even ex­pect bash to be there, and blithe­ly adorn scripts full of bashisms with #!/bin/sh on top.

This has been bug­ging me for a long time, and then it hit me... what would it take to switch a dis­tro so it can boot with­out a shell?

And be­cause I am a mo­ron... I de­cid­ed to try.

I will do it with a ba­sic Arch Lin­ux 0.7.2 in­stal­la­tion. I will change all its init scripts to work on Lu­a.

Why? Well, for starter­s, Lua is small and fea­ture­ful. Then again, I don't ac­tu­al­ly know Lu­a. So this is prob­a­bly go­ing to be in­ter­est­ing.

I chose Arch be­cause it has a very ba­sic start­up script set. Yes, I have blogged a few days ago that I thought them sim­plis­tic. So what? They are still sim­ple, which means they are eas­i­er to re­write.

Step 1: /etc/rc.conf

Pret­ty much ev­ery­thing in Arch's sys­tem us­es the rc.­conf file. I sup­pose some users don't even un­der­stand the im­pli­ca­tions of that: you screw this file (which, re­mem­ber, is writ­ten in a pret­ty evil lan­guage) and you break most of the sys­tem.

Be­cause, yes, this is a shell script frag­men­t, used as a con­fig­u­ra­tion file. Which is more com­mon than you think. How­ev­er, as long as you on­ly touch what's there and don't write ex­tra stuff, you will be ok.

Converting to Lua was trivial, X=(a,b,c) converts to X={"a","b","c"} Comments are "--" instead of "#". Then your scripts do dofile ("/etc/r­c.­con­­a") instead of . /etc/rc.conf.

So, no prob­lem at all so far.

Step 2: /etc/rc.d/functions

This is trick­i­er. This file con­tains func­tions to dis­play mes­sages, like this:

stat_fail() {
        /bin/echo -e "   $C_OTHER[${C_FAIL}FAIL$C_OTHER]$C_CLEAR "

Where C_OTHER and com­pa­ny are stuff like this:

C_OTHER="\033[1;34m"     # prefix & brackets

The on­ly dif­fer­ence is that Lua does­n't use oc­tal:

C_OTHER="\27[1;34m"      -- prefix & brackets

Now look like this (e­cho is ba­si­cal­ly print­):

function stat_fail()
  echo ("   "..C_OTHER.."["..C_FAIL.."FAIL"..C_OTHER.."]"..C_CLEAR.." ")

Now, the first re­al hur­dle comes up. I am not go­ing to repli­cate the whole lin­ux shell utils in Lu­a. So, we need a way to do back­quot­ing.

Back­quot­ing is one of those "nifty" shell fea­tures. You put a com­mand in back­quotes, and you get that re­placed, at run­time, with the out­put of the com­mand.

It's not hard, though. All I need is a way to open a pipe to the com­mand, and read the out­put. And it's in the docs ;-)

function shell(c)
  local o, h
  h = assert(io.popen( c,"r" ))
  o = h:read("*all")
  return o

So, let's add that to func­tion­s.lua first of al­l.

Then, we turn things like this:

STAT_COL=$[`stty size | awk 'BEGIN { RS=" " }; END { print $1 }'` - 13]

In­to this:

STAT_COL=tonumber(shell("stty size | awk 'BEGIN { RS=" " }; END { print $1 }'")) - 13

Which is not much bet­ter, but hey, the re­al­ly ug­ly part is still shell script (and AWK!) ;-)

So, how can we make that nicer? How about this (s­plit is from Lu­a's wik­i):

STAT_COL=tonumber(split(shell("stty size")," ")[2]-13)

Nicer, short­er... less lan­gua­jes per line... I think I am en­joy­ing this!

But any­way, here's a bet­ter idea:

STAT_COL=tonumber(shell("tput cols"))-13

An­oth­er hur­dle:

[ -d /var/run/daemons ]

I just can't find a way to see if a fold­er ex­ists in Lu­a's stan­dard li­brary. Hel­l, there is no way even to cre­ate a fold­er!

But no prob­lem, there is an ad­d-on called lu­afilesys­tem, which has all that so we are still ok.

Not triv­ial, but very sim­ple, so far. I had to write a few helper func­tions for string ma­nip­u­la­tion, and some "shel­l-­like" fea­tures, but not hard at al­l.

Hav­ing to go out­side the lan­guage al­ready is a lit­tle wor­ry­ing, though.

Step 3: rc.sysinit

This 267 line shell script runs at the very be­gin­ning of the boot­ing pro­cess, so it's our next step.

It's not im­pos­si­ble ei­ther, but it's start­ing to get hard­er.

The main thing is the ex­treme­ly lim­it­ed stan­dard li­brary that comes with Lu­a.

There is no way to:

  • Check per­mis­­sions

  • Change per­mis­­sions

  • See if a file ex­ists

And many oth­er things a shell has to be able to han­dle eas­i­ly.

One ex­tra an­noy­ance are Lu­a's com­ments. Since they are dif­fer­ent from ev­ery oth­er script­ing lan­guage, you can't use even this triv­ial shell file:

# Set your NIS domain name here

So you have to cre­ate this one:

-- Set your NIS domain name here

That's painful, in part be­cause it's so sim­ple.

Some pieces of the script are con­vert­ed in the bad way, us­ing os­.ex­e­cute() which means they are more ex­pen­sive than in shel­l, be­cause it spawns a shell for that com­mand alone.

Num­ber of things run­ning like that: 35. I need to find a way to run ex­ter­nal com­mands witout in­vok­ing a shel­l. Yes, a fork/ex­ec. Which is not there, it seems ;-)

So, the script works as a Lua scrip­t, but it's cheat­ing a bit.

Status after an afternoon of hacking:

I changed func­tions and rc.­con­f, and have a hack­ish rc.sysinit. That work­s, which means right now the sys­tem is boot­ing in Lu­a, ex­cept for rc.­mul­ti (which is quite short)

To­mor­row's job: write a de­cent shelly li­brary for Lu­a.

Baris Metin / 2006-09-17 12:47:

We thought the same as you and changed our init system to Python in our distribution (Pardus).

I encourage you to take a look at our new init system: Mudur.


employment background check / 2011-12-27 23:22:

Hi very nice article

Contents © 2000-2021 Roberto Alsina