This is not so much as a scripting guide as it is a guide to the differences between msh and other sh shells, as well as tips and tricks and odd corners of the syntax. If you don't have any scripting knowledge or you just want to learn more the Advanced Bash Scriptingguide is a must.

Builtins and essential commands

This is not meant to be an exhaustive list.

. (source)

. filename [args] Reads and executes commands from filename.

Leaks "IO contexts", eventually crashing msh. If you're going to be sourcing more than a few files, and the files you are sourcing only contains variable definitions (foo=bar), you can use the following loop as a workaround:

exec 4<&0 0<file while read line do eval "$line" done exec 0<&4 4<&-

For small files this one-liner also works. Note that filecan't begin with a comment. It also has the advantage of working with all msh syntax.

eval "`cat file`"

In other sh shells, one can use return outside a loop in a sourced script. Doesn't work in msh.

if condition; then return # stop running sourced file fi


echo in DSLinux doesn't seem to support any command line arguments other than --help.


See . (source) for one example use.

Two more example uses for eval:

foo=0 bar=1 baz=2 for var in foo bar baz; do eval val=\$$var echo $var=$val eval $var= done
args= for arg in "1 2" "3 4"; do args="$args \"$arg\"" # preserve whitespace eval echo "$args" done

See also [#Quoting].


exec can either:

exec memory-hungry-command

* Redirect input/output. There are three standard file descriptors: 0 (stdin), 1 (stdout), 2 (stderr)

exec 4<&0 0</proc/meminfo # redirect input read line set -- $line # split line, see set echo $2 # memtotal exec 0<&4 4<&- # restore console input

In a headless script you might use something like this:

exec 1>/tmp/bar.log 2>&1 echo info message echo error message >&2

See also [#IO_redirection].


exit [n] Exit the process. exittakes one optional arg, called the exit status. The exit status is a integer between 0 and 255. Usually 0 means success and non-zero failure.


export [name[=value]] Variables are local to the current process. If you want a subprocess (i.e. a command that you run) to be able to read a variable you have to export it. Note that any changes made to the variable in the subprocess will not be visible.


If run without arguments it shows exported variables.


read name

Read a line from stdin.

echo -n "Please enter your name: " read name echo "Hello, $name"

read can also be useful for tabular data.

read col1 col2 echo $col1 echo $col2

Give it the input "foo bar", and it will echo foo and then bar. You can also use three variables/columns:

read col1 col2 col3 echo $col1 echo $col2 echo $col3

Try to give it four columns of input. You'll notice that the remainder of the line is stored in the last variable given, col3.

However, note that msh's read command doesn't work properly if there is more than one whitespace character between each column! As an alternative you can use the set builtin for splitting lines.

To use another delimiter than whitespace, you can set IFS (Internal Field Separator). Example:

OLDIFS="$IFS" IFS=: exec 4<&0 0</etc/passwd while read login_name password uid gid name_comment homedirectory shell do echo "Username: $login_name" echo "Name/comment: $name_comment" echo "Home directory: $homedirectory" echo done IFS="$OLDIFS" exec 0<&4 4<&-

See also [#IO_redirection].


set [--] [args]

msh recognizes the following options:

Unrecognized arguments are set as positional arguments.

set -- foo bar baz echo $2 # prints bar

-- is a unix convention that tells a command not to interpret any more arguments as flags/options (e.g. -k).

When called without any arguments, set prints all defined variables.

See . (source) for one example using set to split a line. Here's another.

IFS, or Internal Field Separator, is special variable that tells msh which characters to interpret as whitespace. By using IFS we can pretty much use set as a builtin cut replacement.

csv=col1,col2,col3,col4,,col6 OLDIFS="$IFS" IFS="," set -- $csv # note: if you quote the argument to set it will not be split echo $3 # echoes col3 IFS="$OLDIFS"

In the above example read would probably have worked also. msh bug: $5 contains col6, when it should have been empty. Basically blank columns are ignored.

See also [#read].

In other sh shells, set -eusually tells the shell to exit as soon as a command exits with a non-zero exit status. Doesn't seem to work in msh.

set -e false echo notreached # you should not see this


shift [arg]

set -- 1 2 3 4 5 echo $1 # 1 shift echo $1 # 2 shift 2 echo $1 # 4

The following is a awful hack that is only included here for "fun". It echoes the numbers from 1 to 9.

set -- 1 2 3 4 5 6 7 8 9 10 i=$1 while [ $1 -le 9 ]; do echo $i shift i=$1 done


trap [arg signal]

trap is used for trapping signals. In particular it is useful for cleaning up.

EXIT=0 # normal exit SIGINT=2 # Ctrl-C SIGTERM=15 # kill $pid trap 'echo doing clean-up; exit 2' $EXIT $SIGINT $SIGTERM sleep 10 # with read foo, it doesn't seem to react until enter is pressed

Warning: in the above example the handling code is called twice if killed with a signal! To avoid that the following seems to work:

trap 'trap $EXIT; echo doing clean-up; exit 2' $SIGINT $SIGTERM $EXIT

As can be seen from the above example, if the handler is blank then the signal is simply ignored.

trap without any arguments show defined traps:



umask [mode]

umask is used for getting and setting the file creation mask. If you want all created files to be only readable and writable by you:

umask 0077 touch file ls -l file

umask without any arguments show the current mask.

man umask on a Unix system for information.

Warning: on FAT filesystems umask and chmod are useless.


wait [arg]

wait without any arguments waits for the last started background process.

sleep 10 & wait

To wait for a specific process:

sleep 5 & s5pid=$! # special variable; contains the PID of the last started background process sleep 10 & wait $s5pid

In bash one can use wait %1, wait %2, etc. Doesn't work in msh.

:, true, false

:and true exits with exit status zero. false exits with exit status 1.

That's all they do.


test, or [, is a command used to check conditions.

There is too much to cover here, but I'll show a few common uses.

Testing if a variable is empty:

[ -z "$var" ] && echo true

Testing if a variable is non-empty:

[ -n "$var" ] && echo true # [ "$var" ] also works

Testing if two variables are equal (the variables can also be numbers):

var1=foo var2=bar if [ $var1 = $var2 ]; then echo var1 is equal to var2 fi if [ $var1 != $var2 ]; then echo var1 is not equal to var2 fi

Numerical operators:

x=1 y=2 [ $x -gt $y ] && echo x is greater than y [ $x -le $y ] && echo x is less than y

A few useful file operators:

-o and -a means or and andrespectively.

[ $var -ge 0 -a $var -le 255 ] && echo in range

For more information (some of which may not work in busybox/msh), see http://tldp.org/LDP/abs/html/testconstructs.html. (Click Next for more.)


expr [args]

expr evaluates expressions. It does math, basic "string" (text variable) manipulation and regular expression matching. Do man expron a Unix system, mostly stuff should be the same.

Counting from 1 to 9:

i=1 while [ $i -lt 10 ]; do echo $i i=`expr $i + 1` done

String manipulation:

var=foo expr length $var # 3 var=`expr substr $var 1 2` echo $var # fo



Doesn't work in msh. In other sh shells it negates a test or exit status, e.g.

! false echo $? # 0

However, test/[ supports the ! operator:

if [ ! -d "$dir" ]; then mkdir "$dir" || exit 1 fi

A more general workaround:

if [ -d "$dir" ] then : # do nothing else mkdir "$dir" || exit 1 fi


There's nothing msh specific, but there is one thing that should be noted:

for x in sfsdfsdfdsf[a-zA-Z]?* do echo $x done

As you probably saw, if a glob doesn't return any matches it is simply returned. Thus the above will (probably) echo sfsdfsdfdsf[a-zA-Z]?*.

ksh/bash extended globbing doesn't work.

$, variable substitution

Referencing positional arguments >9:

echo ${10}

Disambiguating variables:

echo ${foo}_bar # echo $foo followed by _bar, not $foo_bar

Things from ksh/bash that don't work in msh:

The variables $* and $@ contain all positional arguments. In ksh/bash, they only differ when between double quotes. The difference is that "$@" preserves the argument list, while "$*" splits the argument list into words.

set -- "foo bar" baz for x in "$@"; do echo "$x"; done echo -- for x in "$*"; do echo "$x"; done

In ksh/bash you'll get the following (correct) output:

foo bar baz -- foo bar baz

In msh:

foo bar baz -- foo bar baz

So in msh, basically they're the same.

See also [#Quoting].

Special variables

Code blocks

Code blocks are for grouping code. In ksh/bash they are often used like this:

{ echo some output } > outfile # redirect all output from code block to outfile
{ while read line; do echo "$line" done } < infile # redirect input to code block from infile

Neither of these uses seem to work in msh. For a workaround for the first, see [#exec]. As a workaround for the latter one can use the following:

exec 4<&0 0<infile while read line; do echo "$line" done exec 0<&4 4<&-

Code blocks can also be used like this:

[ -n "$var" ] && { # or any other condition echo some code here }

The same on one line:

[ -n "$var" ] && { echo some code here ; }

Note that the ;is necessary.

One can also use ( ... ). In msh it is basically equivalent to { }, while in ksh/bash it starts a subshell, i.e. a new process.


In ksh/bash, you can background pretty much anything, such as a loop:

for x in 1 2 3; do echo $x; sleep 5; done &

Most of these uses don't work in msh. As far as I know this is the only usage that works:

sleep 5 & echo pid of command: $!

I/O redirection

While this works:

echo foo > file cat file # foo

This doesn't:

read line < file # Segmentation fault

This works, but msh fails to restore stdin. See [#exec] for a workaround.

cat file | read line echo $line

It also works in ksh. It doesn't behave as you might expect in bash and pdksh. In those shells $line will be blank, because read lineis executed in a subshell.

One sometimes sees this in bash/ksh scripts:

a=`<file` echo $a

Doesn't work in msh. Instead you can use (only recommended for small files):

a=`cat file` echo $a

If you want to blank a file:

> file # : > file doesn't work, redirecting a builtin seems to cause a segfault

Things from bash/ksh that don't work:

See also [#exec] and [#Code_blocks].


When it comes to loops, msh has one fatal bug:

for n in 0 1 do while : do break done echo iteration done

In bash/ksh this will echo iteration twice and exit. In msh it will cause a hang or segfault. The issue is the "break" in the nested loop. Happens with all types of loops.


Used for getting the output of a command.

contents=`cat file`

In standard msh, this operator doesn't set the $? variable. DSLinux contains a patch that fixes that.

contents=`cat file` || { echo "Unable to read file!"; exit 1; }


By default, variable substitution doesn't preserve whitespace.

var="a b c" echo $var

Echoes "a b c". To preserve the whitespace, one have to "quote" the variable:

var="a b c" echo "$var" # a b c

OK, whitespace is a bit simplistic. Variables are always "split" according to $IFS. See [#Special_variables].

There is another quote character, '. The difference is that ' doesn't expand variables:

var=foo echo "$var" # foo echo '$var' # $var

To preserve whitespace when using eval:

var="a b c" eval echo "$var" # a b c eval echo '"$var"' # a b c, leaves it to eval to expand $var eval echo \""$var\"" # a b c


\ does two things. It "escapes" special characters so the shell won't interpret them:

var=foo echo $var # foo echo \$var # $var

It's also a line continuation:

echo foo \ bar \ baz # foo bar baz


This is one notable thing that msh unfortunately lacks.

The closest you can get in msh is using . (source) or something like this:

func=main while :; do case $func in main) get user input; figure out where to go next ;; # pseudocode scan) do something ;; esac done