Using the Shell - Ubuntu Linux Toolbox: 1000+ Commands for Power Users (2013)

Ubuntu Linux Toolbox: 1000+ Commands for Power Users (2013)

Chapter 3

Using the Shell

IN THIS CHAPTER

· Accessing the shell

· Using command history and completion

· Assigning aliases

· Gaining super user access

· Writing simple shell scripts

The use of a shell command interpreter (usually just called a shell) dates back to the early days of the first UNIX systems. Besides its obvious use of running commands, shells have many built-in features such as environment variables, aliases, and a variety of functions for programming.

Although the shell used most often with Linux systems is called the Bourne Again Shell (bash), other shells are available as well (such as sh, csh, ksh, and tcsh). In many cases, these shells, such as sh, are really symbolic links to other shell programs, such as bash. On Ubuntu Linux, sh is a symbolic link to /bin/dash. The sh shell is important as it is called in most shell scripts as the shell to run scripts. For interactive usage, bash forms the default shell.

This chapter offers information that will help you use Linux shells, in general, and the bash shell, in particular.

Terminal Windows and Shell Access

The most common way to access a shell from a Linux graphical interface is using a Terminal window. From a graphical interface, you can often access virtual terminals to get to a shell. With no graphical interface, with a text-based login you are typically dropped directly to a shell after login.

Using Terminal Windows

To open a Terminal window from Unity (the default Ubuntu desktop), select the Dashboard icon, and then begin typing terms in the search box. Look for a Terminal icon to appear in the dashboard and select it. This opens a gnome-terminal window, displaying a bash shell prompt. Figure 3-1 shows an example of a gnome-terminal window.

Commands shown in Figure 3-1 illustrate that the current shell is the bash shell (/bin/bash), the current useris the desktop user who launched the window (chris), and the current directory is that user’s home directory (/home/chris). The username (chris) and hostname (ubuntutb) appear in the title bar and the prompt.

Figure 3-1: Type shell commands into a gnome-terminal window.

c03f001

The gnome-terminal window not only lets you access a shell, it also has controls for managing your shells. Here are some examples:

· Select File ⇒ Open Tab to open another shell on a different tab.

· Select File ⇒ Open Terminal to open a new Terminal window.

· Select Terminal ⇒ Set Title to set a new title in the title bar.

You can also use control key sequences to work with a Terminal window. Open a shell on a new tab by pressing Shift+Ctrl+t, open a new Terminal window with Shift+Ctrl+n, close a tab with Shift+Ctrl+w, and close a Terminal window with Shift+Ctrl+q. Highlight text and copy it with Shift+Ctrl+c, and then paste it in the same or different window with Shift+Ctrl+v or by clicking the center button on your mouse.

Note In most applications, such as the LibreOffice word processor, Ctrl+c, not Shift+Ctrl+c, invokes the copy function, and Ctrl+v, not Shift+Ctrl+v, invokes the paste function. Because Ctrl+c means something special in a shell window (sending a signal to a program that normally causes it to die), the gnome-terminal window maps the expected graphical desktop functions using the Shift key as a modifier.

Other key sequences for controlling Terminal windows include pressing F11 to toggle the window in and out of full screen mode. Press Ctrl+Shift++ to zoom in (make text larger) or Ctrl+- (that’s Ctrl and a minus sign) to zoom out (make text smaller). Switch among tabs using Ctrl+PageUp and Ctrl+PageDown (previous and next tab), or use Alt+1, Alt+2, Alt+3, and so on, to go to tab one, two, or three (and so on). Type Ctrl+d to exit the shell, which closes the current tab or the entire Terminal window (if it’s the last tab).

The gnome-terminal window also supports profiles (select Edit ⇒ Profiles). Some profile settings are cosmetic (allowing bold text, cursor blinks, terminal bell, colors, images, and transparency). Other settings are functional. For example, by default, the terminal saves 512 scrollback lines. Some people like to be able to scroll back further and are willing to give up more memory to allow that.

If you launch gnome-terminal manually, you can add options. Here are some examples:

$ gnome-terminal -x alsamixer Start with alsamixer (ESC exits)

$ gnome-terminal --tab --tab --tab Start a terminal with 3 open tabs

$ gnome-terminal --geometry 80x20 Start 80 characters by 20 lines

$ gnome-terminal --zoom=2 Start terminal with larger font

Besides gnome-terminal, there are many other terminal windows you can use, such as xterm (basic terminal emulator that comes with the X Window System), aterm (terminal emulator modeled after the Afterstep XVT VT102 emulator), and konsole (terminal emulator delivered with the KDE desktop). The Enlightenment desktop project offers the eterm terminal (which includes features such as message logs on the screen background).

Using Virtual Consoles

When Ubuntu boots in multi-user mode (runlevel 2, 3, or 5), six virtual consoles (known as tty1 through tty6) are created with text-based logins. If an X Window System desktop is running, X is probably running in virtual console 7. If X isn’t running, chances are you’re looking at virtual console 1.

From X, you can switch to another virtual console with Ctrl+Alt+F1, Ctrl+Alt+F2, and so on up to F6. From a text virtual console, you can switch using Alt+F1, Alt+F2, and so on. Press Alt+F7 to return to the X GUI. Each console allows you to log in using different user accounts. Switching to look at another console doesn’t affect running processes in any of them. When you switch to virtual console 1 through 6, you see a login prompt similar to the following:

Ubuntu 12.04.2 LTS ubuntutb tty2

ubuntutb login:

Separate getty processes manage each virtual terminal. Type this command to see what getty processes look like before you log in to any virtual terminals:

$ ps awx | grep -v grep | grep getty

4366 tty4 Ss+ 0:00 /sbin/getty -8 38400 tty4

4367 tty5 Ss+ 0:00 /sbin/getty -8 38400 tty5

4372 tty2 Ss+ 0:00 /sbin/getty -8 38400 tty2

4373 tty3 Ss+ 0:00 /sbin/getty -8 38400 tty3

4374 tty1 Ss+ 0:00 /sbin/getty -8 38400 tty1

4375 tty6 Ss+ 0:00 /sbin/getty -8 38400 tty6

After I log in on the first console, getty handles my login, and then fires up a bash shell:

$ ps awx | grep -v grep | grep tty

4366 tty4 Ss+ 0:00 /sbin/getty -8 38400 tty4

4367 tty5 Ss+ 0:00 /sbin/getty -8 38400 tty5

4372 tty2 Ss 0:00 /bin/login --

4373 tty3 Ss+ 0:00 /sbin/getty -8 38400 tty3

4374 tty1 Ss+ 0:00 /sbin/getty -8 38400 tty1

4375 tty6 Ss+ 0:00 /sbin/getty -8 38400 tty6

7214 tty2 S+ 0:00 -bash

1153 tty7 Ss+ 5:59 /usr/bin/X :0 -auth /var/run/lightdm/root/:0

-nolisten tcp vt7 -novtswitch -background none

Virtual consoles are configured in the /etc/init directory. A script appears for each virtual console, such as tty1.conf for the tty1 console, tty2.conf for the tty2 console, and so on.

Note Other versions of Linux configure the consoles in one file, /etc/inittab. The init daemon uses /etc/inittab as its configuration file. Ubuntu Linux, on the other hand, uses a version of init from the upstart package, which uses the /etc/init directory to hold its configuration files.

Using the Shell

After you open a shell (whether from a text-based login or Terminal window), the shell environment is set up based on the user who started the shell. Bash shell settings for all users’ shells are located in several files. You can make your own versions of these files to override the system settings. There are two types of files holding these settings: startup files and initialization files.

Bash runs startup files for any shell that is a login shell. These files define settings that apply across your entire login. Bash runs initialization files for shells run interactively that is, not running a shell script.

Bash uses the system-wide startup file /etc/profile, as well as several dot files in the user’s home directory for personal settings (if they exist): .bash_profile, .bash_login, and .profile. It also includes scripts stored in the /etc/profile.d directory that end in .sh.

Bash includes the system-wide initialization file /etc/bash.bashrc and the .bashrc file in your home directory (for personal settings). Those files are included every time a new bash shell is opened.

Note Other versions of Linux store the system-wide files in /etc/bashrc.

When you exit from a login shell (for example, from a virtual console), any commands in your ~/.bash_logout file are executed. Changing settings in these files permanently changes the user’s shell settings but does not affect shells that are already running. (Other shells use different configuration files.)

There are a variety of ways in which you can list and change your shell environment. One of the biggest ways is to change which user you are—in particular, to become the super user (see the section “Acquiring Super User Power” later in this chapter).

Using Bash History

The Bourne Again Shell (bash) is the shell used by default by most modern Linux systems and quite a few other operating systems such as Mac OS X. Built into bash, as with other shells, is a history feature that lets you review, change, and reuse commands that you have run in the past. This can prove very helpful as many Linux commands are long and complicated.

When bash starts, it reads the ~/.bash_history file and loads it into memory. This file is set by the value of $HISTFILE.

Note See the section “Using Environment Variables” later in this chapter for more on how to work with shell environment variables such as $HISTFILE.

During a bash session, commands are added to history in memory. When bash exits, history in memory is written back to the .bash_history file. The number of commands held in history during a bash session is set by $HISTSIZE, while the number of commands actually stored in the history file is set by $HISTFILESIZE:

$ echo $HISTFILE $HISTSIZE $HISTFILESIZE

/home/chris/.bash_history 1000 2000

To list the entire history, type history. To list a previous number of history commands, follow history with a number. This lists the previous five commands in your history:

$ history 5

975 mkdir extras

976 mv *doc extras/

977 ls -CF

978 vi house.txt

979 history 5

To move among the commands in your history, use the up arrow and down arrow keys. Once a command displays, you can use the keyboard to edit the current command like any other command: left arrow, right arrow, Delete, Backspace, and so on. Here are some other ways to recall and run commands from your bash history:

$ !! Run the previous command

$ !997 Run command number 997 from history

ls -CF

$ !997 *doc Append *doc to command 997 from history

ls -CF *doc

$ !?CF? Run previous command line containing the CF string

ls -CF *doc

$ !ls Run the previous ls command

ls -CF *doc

$ !ls:s/CF/l Run previous ls command, replacing CF with l

ls -l *doc

Another way to edit the command history is using the fc command. With fc, you open the chosen command from the history using the nano editor. In nano, type your changes, and then type Ctrl+o and Ctrl+x to save your changes. The edited command runs when you exit the editor. Change to a different editor by setting the FCEDIT variable (for example, export FCEDIT=gedit) or on the fc command line. For example:

$ fc 978 Edit command number 978, then run it

$ fc Edit the previous command, then run it

$ fc -e /usr/bin/vim 989 Use vim to edit command 989

Use Ctrl+r to search for a string in history. For example, typing Ctrl+r followed by the string ss resulted in the following:

# <Ctrl+r>

(reverse-i-search)`ss': sudo /usr/bin/less /var/log/messages

Press Ctrl+r repeatedly to search backward through your history list for other occurrences of the ss string.

Note By default, bash command history editing uses emacs-style commands. If you prefer the vi editor, you can use vi-style editing of your history by using the set command to set your editor to vi. To do that, type the following: set -o vi. Add that line to your .bashrc file, as described later, to have vi set as your history editor every time you open a shell.

Using Command Line Completion

You can use the Tab key to complete different types of information on the command line. Here are some examples where you type a partial name, followed by the Tab key, to have bash try to complete the information you want on your command line:

$ ifc<Tab> Command completion: Completes to ifconfig command

$ cd /home/ch<Tab> File completion: Completes to /home/chris directory

$ cd ~jo<Tab> User homedir completion: Completes to /home/john

$ echo $PA<Tab> Env variable completion: Completes to $PATH

$ ping @<Tab><Tab> Host completion: Show hosts from /etc/hosts

@davinci.example.com @ritchie.example.com @thompson.example.com

@localhost @zooey

Redirecting stdin and stdout

Typing a command in a shell makes it run interactively. The resulting process has two output streams: stdout for normal command output and stderr for error output. In the following example, when /tmpp isn’t found, an error message goes to stderr but output from listing /tmp (which is found) goes to stdout:

$ ls /tmp /tmpp

ls: /tmpp: No such file or directory

/tmp/:

gconfd-chris keyring-b41WuB keyring-ItEWbz mapping-chris orbit-chris

By default, all output is directed to the screen. Use the greater-than sign (>) to direct output to a file. More specifically, you can direct thestandard output stream (using >) or standard error stream (using 2>) to a file. Here are examples:

$ ls /tmp /tmmp > output.txt

ls: /tmpp: No such file or directory

$ ls /tmp /tmmp 2> errors.txt

/tmp/:

gconfd-chris keyring-b41WuB keyring-ItEWbz mapping-chris orbit-chris

$ ls /tmp /tmmp 2> errors.txt > output.txt

$ ls /tmp /tmmp > everything.txt 2>&1

In the first example, stdout is redirected to the file output.txt, while stderr is still directed to the screen. In the second example, stderr (stream 2) is directed to errors.txt whereas stdout goes to the screen. In the third example, the first two examples are combined. The last example directs both streams to the everything.txt file. To append to a file instead of overwriting it, use two greater-than signs:

$ ls /tmp >> output.txt

If you don’t ever want to see an output stream, you can simply direct the output stream to a special bit bucket file (/dev/null). In this case, stderr is discarded and stdout is displayed:

$ ls /tmp 2> /dev/null

Note Another time that you may want to redirect stderr is when you run jobs with crontab. You could redirect stderr to a mail message that goes to the crontab’s owner. That way, any error messages can be sent to the person running the job.

Just as you can direct standard output from a command, you can also direct standard input to a command. For example, the following command e-mails the /etc/hosts file to the user named chris on the local system:

$ mail chris < /etc/hosts

Using pipes, you can redirect output from one process to another process rather than just files. Here is an example where the output of the ls command is piped to the sort -r command to have the output sorted in reverse order:

$ ls /tmp | sort -r

In the next example, a pipe and redirection are combined (the stdout of the ls command is sorted and stderr is dumped to the bit bucket):

$ ls /tmp/ /tmmp 2> /dev/null | sort -r

Pipes can be used for tons of things:

$ dpkg-query -l | grep -i sql | wc -l

$ ps auwx | grep firefox

$ ps auwx | less

$ whereis -m bash | awk '{print $2}'

The first command line in the preceding examples lists all installed packages, grabs those packages that have sql in them (regardless of case), and does a count of how many lines are left (effectively counting packages with sql in the name). The second command line displays Firefox processes taken from the long process list (assuming the Firefox web browser is running), as well as any process whose command line references firefox, such as the command issued in this example. The third command line lets you page through the process list. The last line displays the word bash: followed by the path to the bash man page, and then displays only the path to the man page (the second element on the line).

Using backticks, you can execute one section of a command line first and feed the output of that command to the rest of the command line. Here are examples:

$ dpkg-query -S `which ps`

$ ls -l `which bash`

The first command line in the preceding example finds the full path of the ps command and finds the package that contains that ps command. The second command line finds the full path to the bash command and does a long list (ls -l) of that command.

A more advanced and powerful way to take the output of one command and pass it as parameters to another is with the xargs command. For example:

$ ls /bin/b* | xargs /usr/bin/dpkg-query -S

You can use the -t option to xargs to produce a verbose output of the command line before the command is executed. You can also have xargs pass each output string from ls as input to individual dpkg-query commands. You define {} as the placeholder for the string:

$ ls /bin/b* | xargs -t -I{} dpkg-query -S {}

dpkg-query -S /bin/bash

bash: /bin/bash

dpkg-query -S /bin/bunzip2

bzip2: /bin/bunzip2

dpkg-query -S /bin/bzcat

bzip2: /bin/bzcat

dpkg-query -S /bin/bzcmp

bzip2: /bin/bzcmp

dpkg-query -S /bin/bzdiff

bzip2: /bin/bzdiff

dpkg-query -S /bin/bzegrep

bzip2: /bin/bzegrep

dpkg-query -S /bin/bzexe

bzip2: /bin/bzexe

dpkg-query -S /bin/bzfgrep

bzip2: /bin/bzfgrep

dpkg-query -S /bin/bzgrep

bzip2: /bin/bzgrep

dpkg-query -S /bin/bzip2

bzip2: /bin/bzip2

dpkg-query -S /bin/bzip2recover

bzip2: /bin/bzip2recover

dpkg-query -S /bin/bzless

bzip2: /bin/bzless

dpkg-query -S /bin/bzmore

bzip2: /bin/bzmore

As you can see from the output, separate dpkg-query -S commands are run for each option passed by ls.

Using alias

You use the alias command to set and list aliases. Some aliases are already set in the system-wide or user-specific shell initialization files discussed previously. Here’s how to list the aliases that are currently set:

$ alias

alias egrep='egrep --color=auto'

alias fgrep='fgrep --color=auto'

alias grep='grep --color=auto'

alias l='ls -CF'

alias la='ls -A'

alias ll='ls -alF'

alias ls='ls --color=auto'

Notice that some aliases are set simply as a way of adding options to the default behavior of a command (such as ls --color=auto, so that the output of the ls command is always shown in color).

You can define your own aliases for the current bash session as follows:

$ alias lala='ls -la'

Add that line to your ~/.bashrc file for the definition to occur for each new bash session. Remove an alias from the current bash session using the unalias command, as follows:

$ unalias lala Unalias the previously aliased lala command

$ unalias -a Unalias all aliased commands

Watching Commands

If you need to keep an eye on a command whose output is changing, use the watch command. For example, to keep an eye on your load average, you can use the following:

$ watch `cat /proc/loadavg`

Every two seconds, watch runs the cat command again. Use Ctrl+c to quit. To change the refresh rate to 10 seconds, type the following:

$ watch -n 10 `ls -l`

To highlight the difference between screen updates, type:

$ watch -d `ls -l`

Press Ctrl+c to exit the watch command. Note that files need to change so that differences can be highlighted.

Watching Files

You can use the watch command to watch the size of a file. For example, to watch a large ISO file named mydownload.iso as it downloads, use the following command:

$ watch 'ls -l mydownload.iso'

To watch the contents of a plain text file grow over time, you can use the tail command. For example, you can watch as messages are added to the /var/log/messages file and see those messages displayed on your screen as follows:

$ sudo tail -f /var/log/messages

Pressing Ctrl+c will exit from the tail command.

Acquiring Super User Power

When you open a shell, you are able to run commands and access files and directories based on your user/group ID and the permissions set for those components. Many system features are restricted to the root user, also referred to as the super user.

There are three main ways to acquire super user power:

· Run one command with root user privileges with the sudo command.

· Log in as the root user.

· Temporarily become the root user with the su command.

Ubuntu Linux is set up for users to run the sudo command. In fact, a password is not even set for the root user by default. So, in most cases, to run an administrative command (such as useradd to add a new user), you would precede that command with the sudo command. For example:

$ sudo useradd -m joe As root user, add a new user named joe

Because Ubuntu restricts the system such that the root user cannot log in, Ubuntu is also not set up to use the su command, which is normally used on other Linux systems, to change to the root user.

If you find that you need to run a series of commands as root user, you could type the following command to open a shell as the root user:

$ sudo bash Open a shell as root user

[sudo] password for chris:

********

#

If you decide that you want to add a password for the root user (allowing you to log in as the root user or use the su command to temporarily become root), you can also do that using the sudo command:

$ sudo passwd root Set the root user's password

Most Ubuntu desktop users, however, simply use sudo and never set a root password.

Delegating Power with sudo

The sudo command allows very granular delegation of power to users other than the root user. The sudo facility is a great tool for granting specific escalated privileges when you have multiple users and logging everything the users do with those privileges. Unless otherwise specified, sudo runs as root. Ubuntu Linux uses the sudo command to execute privileged commands, rather than the su command.

The sudo command is configured in /etc/sudoers.

Warning Never edit this file with your normal text editor. Instead, always use the visudo command. This opens the sudoers file in an editor, temporarily locks it from use by others, and does some error checking before you leave.

The file /etc/sudoers is restricted, so you need to use the sudo command to edit the file. For example:

$ sudo visudo

The visudo command launches an editor, by default the nano editor, discussed previously.

If you look at the sudoers file that shipped with your distribution, you’ll see different empty sections delimited by comments and one active statement:

root ALL=(ALL:ALL) ALL

This means that the user root is allowed on any hosts to run any command as any user.

Ubuntu Linux adds the following so that all users who are part of the admin or sudo group (as set in the /etc/group file) can acquire root privileges:

# Members of the admin group may gain root privileges

%admin ALL=(ALL) ALL

# Allow members of group sudo to execute any command

%sudo ALL=(ALL:ALL) NOPASSWD: ALL

When a user in the sudo group requests root privilege, that user gains access to that privilege without entering a password. You can modify that sudoers line so a privileged user does have to provide a password (their own user password) when running sudo as follows:

%sudo ALL=(ALL:ALL) ALL

When you installed Ubuntu Linux, the user account you created was automatically added to the sudo group. To allow additional users to acquire root privileges, you can simply add them to the sudo group in the /etc/group file. For example, to add sudo privilege for an existing user named joe, you could modify the line in /etc/group so it appears as follows:

sudo:x:27:chris,joe

You also have the choice of giving a user limited root privilege instead in the /etc/sudoers file. For example, you can add the following line, setting the first field to a user account on your system:

joe ALL= /usr/bin/less /var/log/messages

Note The preceding setting allows this user to run the less command with root privileges. This introduces a security issue, as the less command can allow this user to gain more information on the system by examining other system files.

Now joe (or whichever user you’ve added) can do the following:

$ sudo /usr/bin/less /var/log/messages

Password:

After joe types his own password, he can page through the /var/log/messages file. A timestamp is set at that time as well. For the next five minutes (by default), that user can type the preceding command and have it work without being prompted for the password.

Normally, though, you should add such users to the sudoers or admin group and not create individual entries in the /etc/sudoers file.

Every use of sudo gets logged in /var/log/secure:

Feb 24 21:58:57 localhost sudo: joe : TTY=pts/3 ; PWD=/home/joe ;

USER=root; COMMAND=/usr/bin/less /var/log/messages

Next add this line to /etc/sudoers:

joe server1=(chris) /bin/ls /home/chris

Now joe can do the following:

$ sudo -u chris /bin/ls /home/chris

The sudo command just shown runs as chris and will work only on the host server1. In some organizations, the /etc/sudoers file is centrally managed and deployed to all the hosts, so it can be useful to specify sudo permissions on specific hosts.

The sudo command also allows the definition of aliases, or predefined groups of users, commands, and hosts. Check the /etc/sudoers file on your Linux system for examples of those features.

Using the su Command

If you did decide at some point to add a password to your root user account, with a shell open as a regular user, you can use the su (super user) command to become the root user. You can also use the su command to switch to a different, non-root user. The following sections describe how the su command works.

Simply using su, as in the following code, doesn’t give you a login shell with root’s environment:

$ su

Password:*****

# echo $PATH

/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin:/usr/games

/usr/kerberos/sbin:/usr/kerberos/bin:/usr/local/bin:

/usr/bin:/bin:/usr/X11R6/bin:/home/joe/bin

After running su, the user still has the user joe’s PATH. To enable the root user’s environment, use the su command with the dash option (-), as follows:

# exit

$ su -

Password: *****

# echo $PATH

/usr/kerberos/sbin:/usr/kerberos/bin:/usr/local/sbin:

/usr/local/bin:/sbin:/bin:/usr/sbin:/usr/bin:/root/bin

In most cases, use su -, unless you have a very specific reason not to. If no user is specified, su defaults to the root user. However, su can also be used to become other users:

$ su - chris

The su command can also be used to execute a single command as a particular user:

$ su -c whoami

Password: ******

root

# su -c 'less /var/log/messages'

Although in the second example you are logged in as a regular user, when you run whoami with su -c, it shows that you are the root user. In the directly preceding example, the quotes are required around the less command line to identify /var/log/messages as an option to less. As shown above, whoami can be useful in determining which user you’re currently running a command as:

$ whoami

chris

Using Environment Variables

Small chunks of information that are useful to your shell environment are stored in what are referred to as variables. By convention, shell variable names are all uppercase (although that convention is not enforced). If you use the bash shell, some variables are set for you from various bash start scripts covered previously.

How a variable is set determines whether it is a local variable (available only to the current shell) or an environment variable (which is inherited by other shells or applications run from that shell). To become an environment variable, the variable must be exported.

To display, in alphabetical order, all of the current shell variables that are already set for your shell, type the following:

$ set | less

BASH=/bin/bash

COLORS=/etc/DIR_COLORS.xterm

COLUMNS=118

DISPLAY=:0.0

HOME=/home/chris

HOSTNAME=ubuntutb

...

The output just shown contains only a few examples of the environment variables you will see. The set command lists local variables, environment variables, and functions as well. The env command just lists environment variables.

You can also set, or reset, any variables yourself. For example, to assign the value 123 to the variable ABC (then display the contents of ABC), type the following:

$ ABC=123

$ echo $ABC

123

The variable ABC exists only in the shell it was created in. If you launch a command from that shell (ls, cat, firefox, and so on), that new process will not see the variable. Start a new bash process and test this:

$ bash

$ echo $ABC

$

You can make variables part of the environment and inheritable by child processes by exporting them:

$ export ABC=123

$ bash

$ echo $ABC

123

Also, you can concatenate a string to an existing variable:

$ export PATH=$PATH:/home/chris/bin

Type the following to list your bash’s environment variables:

$ env

When you go to create your own environment variables, avoid using names that are already commonly used by the system for environment variables. See Appendix B for a list of shell environment variables.

Creating Simple Shell Scripts

Shell scripts are good for automating repetitive shell tasks. Bash and other shells include the basic constructs found in various programming languages, such as loops, tests, case statements, and so on. The main difference is that there is only one type of variable: strings.

Editing and Running a Script

Shell scripts are simple text files that contain commands, variables, functions, aliases, or any other component you can run directly from a shell. The advantage of a shell script is that you can use it to gather a whole set of commands together so they can be easily run again (without retyping) or even run unattended (such as in a cron job).

You can create shell scripts using your favorite text editor (such as vi). To run, the shell script file must be executable. For example, if you created a shell script with a filename of myscript.sh, you could make it executable as follows:

$ chmod u+x myscript.sh

Also, the first line of your bash scripts should always be the following:

#!/bin/bash

The # in this case starts a comment. The #! syntax acts as a comment for shells that don’t understand this special syntax. The /bin/bash part tells any running shell, be it bash or another shell, which program should be used to run the script. (Since historically not all systems came with bash, you will often see /bin/sh as the command to run a script.)

As with any command, in addition to being executable, the shell script you create must also either be in your PATH or be identified by its full or relative path when you run it. In other words, if you just try to run your script, you may get the following result:

$ myscript.sh

bash: myscript.sh: command not found

In this example, the directory containing myscript.sh is not included in your PATH. To correct this problem, you can edit your PATH, copy the script to a directory in your PATH, or enter the full or relative PATH to your script. The four PATH examples just described, respectively, are shown here:

$ mkdir ~/bin ; cp myscript.sh ~/bin/ ; PATH=$PATH:~/bin

$ cp myscript.sh /usr/local/bin

$ ./myscript.sh

$ /tmp/myscript.sh

Avoid putting a dot (.) into the PATH environment variable to indicate that commands can be run from the current directory. This is a technique that could result in commands with the same filename as important, well-known commands (such as ls or cat), which could be overridden if a command of the same name exists in the current directory. This can become a major security issue.

Adding Content to Your Script

Although a shell script can be a simple sequence of commands, shell scripts can also be used as you would any programming language. For example, a script can produce different results based on giving it different input. This section describes how to use compound commands, such as if/then statements, case statements, and for/while loops in your shell scripts.

The following example code assigns the string abc to the variable MYSTRING. It then tests the input to see if it equals abc and acts based on the outcome of the test. The test is between the brackets ([ ]):

MYSTRING=abc

if [ $MYSTRING = abc ] ; then

echo "The variable is abc"

fi

To negate the test, use != instead of =, as shown in the following:

if [ $MYSTRING != abc ] ; then

echo "$MYSTRING is not abc";

fi

The following are examples of testing for numbers:

MYNUMBER=1

if [ $MYNUMBER -eq 1 ] ; then echo "MYNUMBER equals 1"; fi

if [ $MYNUMBER -lt 2 ] ; then echo "MYNUMBER <2"; fi

if [ $MYNUMBER -le 1 ] ; then echo "MYNUMBER <=1"; fi

if [ $MYNUMBER -gt 0 ] ; then echo "MYNUMBER >0"; fi

if [ $MYNUMBER -ge 1 ] ; then echo "MYNUMBER >=1"; fi

Let’s look at some tests on filenames. In this example, you can check if a file exists (-e), if it’s a regular file (-f), or if it is a directory (-d). These checks are done with if/then statements. If there is no match, then the elsestatement is used to produce the result.

filename="$HOME"

if [ -e $filename ] ; then echo "$filename exists"; fi

if [ -f "$filename" ] ; then

echo "$filename is a regular file"

elif [ -d "$filename" ] ; then

echo "$filename is a directory"

else

echo "I have no idea what $filename is"

fi

Table 3-1 shows examples of tests that you can perform on files, strings, and variables.

Table 3-1: Operators for Test Expressions

Operator

Test Being Performed

-a file

Check that the file exists (same as -e)

-b file

Check whether the file is a special block device

-c file

Check whether the file is a character special device (such as a serial device)

-d file

Check whether the file is a directory

-e file

Check whether the file exists (same as -a)

-f file

Check whether the file exists and is a regular file (for example, not a directory, socket, pipe, link, or device file)

-g file

Check whether the file has the set-group-id bit set

-h file

Check whether the file is a symbolic link (same as -L)

-k file

Check whether the file has the sticky bit set

-L file

Check whether the file is a symbolic link (same as -h)

-n string

Check whether the string length is greater than 0 bytes

-O file

Check whether you own the file

-p file

Check whether the file is a named pipe

-r file

Check whether the file is readable by you

-s file

Check whether the file exists and is larger than 0 bytes

-S file

Check whether the file exists and is a socket

-t fd

Check whether the file descriptor is connected to a terminal

-u file

Check whether the file has the set-user-id bit set

-w file

Check whether the file is writable by you

-x file

Check whether the file is executable by you

-z string

Check whether the length of the string is 0 (zero) bytes

expr1 -a expr2

Check whether both the first and the second expressions are true

expr1 -o expr2

Check whether either of the two expressions is true

file1 -nt file2

Check whether the first file is newer than the second file (using the modification timestamp)

file1 -ot file2

Check whether the first file is older than the second file (using the modification timestamp)

file1 -ef file2

Check whether the two files are associated by a link (a hard link or a symbolic link)

var1 = var2

Check whether the first variable is equal to the second variable

var1 -eq var2

Check whether the first variable is equal to the second variable

var1 -ge var2

Check whether the first variable is greater than or equal to the second variable

var1 -gt var2

Check whether the first variable is greater than the second variable

var1 -le var2

Check whether the first variable is less than or equal to the second variable

var1 -lt var2

Check whether the first variable is less than the second variable

var1 != var2
var1 -ne var2

Check whether the first variable is not equal to the second variable

Another frequently used construct is the case command. Using the case statement, you can test for different cases and take an action based on the result. Similar to a switch statement in programming languages, case statements can take the place of several nested if statements.

case "$VAR" in

string1)

{ action1 };;

string2)

{ action2 };;

*)

{ default action } ;;

esac

You can find examples of case usage in the system start-up scripts (initscripts) found in the /etc/init.d/ directory. Each init script takes actions based on what parameter was passed to it (start, stop, and so on) and the selection is done via a large case construct.

Note The init scripts once stored in the /etc/init.d directory and related /etc/rc?.d directories are now stored in the /etc/init directory in Ubuntu.

The bash shell also offers standard loop constructs, illustrated by a few examples that follow. In the first example, all the values of the NUMBER variable (0 through 9) appear on the for line:

for NUMBER in 0 1 2 3 4 5 6 7 8 9

do

echo The number is $NUMBER

done

In the following examples, the output from the ls command (a list of files) provides the variables that the for statement acts on:

for FILE in `/bin/ls`; do echo $FILE; done

Instead of feeding the whole list of values to a for statement, you can increment a value and continue through a while loop until a condition is met. In the following example, VAR begins as 0 and the while loop continues to increment until the value of VAR becomes 3:

"VAR=0"

while [ $VAR -lt 3 ]; do

echo $VAR

VAR=$[$VAR+1]

done

Another way to get the same result as the while statement just shown is to use the until statement, as shown in the following example:

VAR=0

until [ $VAR -eq 3 ]; do echo $VAR; VAR=$[$VAR+1]; done

If you are just starting with shell programming, refer to the Bash Guide for Beginners (http://tldp.org/LDP/Bash-Beginners-Guide/html/index.html). Use that guide, along with reference material such as the bash man page, to step through many examples of good shell scripting techniques.

Summary

Despite improvements in graphical user interfaces, the shell is still the most common method for power users to work with Linux systems. The Bourne Again Shell (bash) is the most common shell used with Linux. It includes many helpful features for recalling commands (history), completing commands, assigning aliases, and redirecting output from and input to commands. You can make powerful commands of your own using simple shell scripting techniques.