Contents
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 Scripting guide 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 file can'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
echo in DSLinux doesn't seem to support any command line arguments other than --help.
eval
See . (source) for one example use.
Two more example uses for eval:
- indirect variables
foo=0 bar=1 baz=2 for var in foo bar baz; do eval val=\$$var echo $var=$val eval $var= done
- dynamically building a commandline (useful for dialog)
args= for arg in "1 2" "3 4"; do args="$args \"$arg\"" # preserve whitespace eval echo "$args" done
See also [#Quoting].
exec
exec can either:
- Execute (as opposed to spawn) a command, replacing the current process. Used in DSLinux to save RAM.
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
exit [n] Exit the process. exit takes 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
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.
export VARIABLE
If run without arguments it shows exported variables.
read
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
set [--] [args]
msh recognizes the following options:
-k All assignment arguments are placed in the environment for a command, not just those that precede the command name.
-n Read commands but do not execute them.
-u Treat unset variables as an error when substituting. Seems to cause a segfault if you try to substitute a unset variable.
-v Print shell input lines as they are read.
-x Print commands and their arguments as they are executed.
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 -e usually 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
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
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:
trap
umask
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
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
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:
-eq Equal
-ne Not equal
-gt Greater than
-ge Greater than or equal to
-lt Less than
-le Less than or equal to
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:
-e True if file exists
-r True if file is readable
-w True if file is writable
-x True if file is executable
-d True if file is a directory
-f True if file is a regular file
-o and -a means or and and respectively.
[ $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
expr [args]
expr evaluates expressions. It does math, basic "string" (text variable) manipulation and regular expression matching. Do man expr on 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
Syntax
!
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
Globbing
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_barThings from ksh/bash that don't work in msh:
$(command) (same as `command`, but nests)
- ${var:1:2), ${#var}, ${var%}, ${var##}, etc
- arrays
- maybe more
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
- $IFS Internal Field Separator. See read and set.
- $! PID of last command run in the background
$? Exit status of last command run. In standard busybox msh this doesn't work with `command`, but DSLinux contains a patch.
- $$ Script PID
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 infileNeither 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
< Input
> Output, truncate
>> Output, append
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 line is 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:
&> In bash this redirects both stdout and stderr.
n<> In bash this opens a file descriptor for reading and writing.
See also [#exec] and [#Code_blocks].
Loops
When it comes to loops, msh has one fatal bug:
for n in 0 1
do
while :
do
break
done
echo iteration
doneIn 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; }
Quoting
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
Functions
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