One of the most useful features of shells is the ability to write scripts. It should not come as a surprise to find that there are many commands that only make sense when used with scripts.

[ and test

test(1) and [ are synonymous, apart from the fact that the last argument to [ must be ].

They are programs for evaluating various expressions, usually used in shell flow control statements.

var=foo
if [ "$var" = foo ]; then
    echo bar
fi

It is important to realise that [ is not a syntactical construct, any command may be used in a shell conditional expression.

true and false

Processes, as well as producing output, terminate with an "exit status". Sometimes the exit status is more important than the output. Occasionally the exit status is all you want.

Since the only two exit status codes that people generally care about are zero (ok) and non-zero (error), the true(1) and false(1) commands were written.

One major use for this is flow control in shell scripts.

if complicated and expensive command; then
    ok=true
else
    ok=false
fi
if "$ok"; then
    echo I am ok
fi

Without true(1) or false(1) you would need to come up with your own convention for booleans. You would then need to use test(1) to check them, something like this:

if complicated and expensive command; then
    ok=1
else
    ok=0
fi
if [ "$ok" = 1 ]; then
    echo I am ok
fi

Another is setting a user's shell to /bin/false so they cannot log in.

$ grep false /etc/passwd
messagebus:x:102:104::/var/run/dbus:/bin/false

A third is to replace a command that is no longer required, but other scripts expect to be able to run it.

For example, on old Linux systems, pre-devtmpfs and udev, there was a script called /dev/MAKEDEV. For compatibility, some systems keep the script as a symlink to /bin/true.

A final use, is when you need to have a command in that context.

For example, bash has variable substitution options that will set a variable to a default value. They need to be run as part of another command, or they will be interpreted as a command.

$ true ${var=default}

To make this less confusing, bash introduced : as a synonim for true(1).

$ : ${var=default}

basename and dirname

basename(1) and dirname(1) are used for manipulating file paths.

$ var=/path/to/foo/bar-file.txt
$ #   <--dirname--><-basename->
$ #                 suffix-^--^
$ dirname "$var"
/path/to/foo/
$ basename "$var"
bar-file.txt
$ basename "$var" .txt
bar-file

They are mostly wrappers around the C library's basename(3) and dirname(3) functions, with the exception of basename(1) also taking a suffix parameter, which strips the suffix off the end of the file path if provided.

seq and yes

seq(1) and yes(1) both produce lines of output based on their parameters.

seq(1) produces lines of integers in the provided range.

$ seq 3
1
2
3
$ seq 3 5
3
4
5
$ seq 10 10 30
10
20
30
$ seq 100 -20 60
100
80
60

This can be useful for loop conditions.

$ for i in `seq 1 3`; do echo "hello $i"; done
hello 1
hello 2
hello 3

Though in this case, bash brace expansion is more effective.

$ for i in {1..3}; do echo "hello $i"; done
hello 1
hello 2
hello 3

yes(1) prints the same line endlessly. You could naievely implement it as yes(){ echo "${1-y}"; };

This may not sound useful for anything more than heating the area immediately around your computer, but there are often commands that ask for confirmation over standard input.

This may be an installer script, where the defaults are fine, or you may have a version of cp(1) that doesn't have a --no-clobber or --force option, but does have an --interactive option, which can be simulated with yes(1) as yes n | cp -i "$source" "$dest" and yes | cp -i "$source" "$dest" respectively.

timeout and nohup

timeout(1) and nohup(1) alter a command's termination conditions.

timeout(1) can be used to end a process after a given period of time. For example, you can work out how many lines yes(1) can print in 1 second with:

$ timeout 1s yes | wc -l
8710114

nohup(1) will stop a process being killed when the terminal it runs from is closed. This is not demonstrable in the medium I have chosen to demonstrate commands with, but it is run as nohup COMMAND >output.

If the output of this command is not redirected away from the terminal, then it is written to nohup.out.

This is useful if you have a long-running job that you do not want to end when you disconnect from the server you are running it on.

However, screen(1) is a better choice for this task, as it gives you a terminal you can detach from by pressing ^A-D and reattach later with the screen -x command.

sleep

sleep(1) exits after the specified period. You can use it to implement timeout(1) as:

timeout(){
    time="$1"
    shift
    "$@"& 
    pid="$!"
    sleep "$time"
    kill "$pid"
}

It is also useful in a retry loop to avoid busy-waiting.

while ! ssh some-server; do
    sleep 10
done

tr

tr(1) is used to replace or delete characters in a stream.

For example, this will make all characters upper-case.

$ echo hello world | tr '[:lower:]' '[:upper:]'
HELLO WORLD

The -d flag can be used to remove characters from a stream. This will remove all vowels:

$ echo hello world | tr -d 'aeiou'
hll wrld

printf

printf(1) is mostly a thin wrapper over printf(3). Look to that for most of the details.

One addition worth mentioning is that bash has a built-in version, which also supports %q similarly to %s, but quoted so you can use it to safely generate scripts.

$ printf 'echo %q %q' "foo bar" "baz"
echo foo\ bar baz

tee

tee(1) writes its standard input to its standard output and any files listed on the command line.

$ echo foo bar | tee out
foo bar
$ cat out
foo bar

It is also usable as a way to write the output of a command somewhere you don't usually have permission to write, in conjunction with sudo(1).

$ echo foo bar | sudo tee out >/dev/null

Beware that many of these commands are actually shell builtins (or more precisely, they can be both a shell builtin and also a command in /bin or /usr/bin). When you run true in a script, you're not running true(1) from /bin/true, you're running the builtin true. There's a performance difference there but it's not a big issue. When it comes to other commands like [ and printf, there can be a big difference and you should look at help [ rather than man [ for documentation.

Use type to investigate what a command actually is:

  $ type -a [
  [ is a shell builtin
  [ is /usr/bin/[

  $ type -a ls
  ls is aliased to `ls -FC --color=tty'
  ls is /bin/ls

  $ type -a cd
  cd is a function
  cd () 
  { 
      __load_dir_settings "$@";
      builtin cd "$@"
  }
  cd is a shell builtin

(don't ask about that last one!)

Comment by Stuart Wed Jun 11 12:28:23 2014