bash command lines can get to be very long, especially when pipes are used. A script is a text file that contains shell commands that may itself be executed as a command, providing an easy way to reuse complex sequences of commands. In fact, bash provides a complete programming language for use in scripts.
To create a script, simply place commands in a text file. For example, this script will display the ten largest files in the current directory:
ls -lS | tail -n +2 | head -10
Save this file as topten. In order to run the script, you will need to set read and execute permission:
$chmod a+rx
topten
The script can be executed by specifying the directory and filename (or an absolute pathname):
$ ./topten
-rw-r--r-- 1 root root 807103 Jul 12 21:18 termcap
-rw-r--r-- 1 root root 499861 Jul 17 08:08 prelink.cache
-rw-r--r-- 1 root root 362031 Feb 23 08:09 services
-rw-r--r-- 1 root root 97966 Jul 15 11:19 ld.so.cache
-rw-r--r-- 1 root root 92794 Jul 12 12:46 Muttrc
-rw-r--r-- 1 root root 83607 Mar 23 07:23 readahead.files
-rw-r--r-- 1 root root 73946 Jul 13 02:23 sensors.conf
-rw-r--r-- 1 root root 45083 Jul 12 18:33 php.ini
-rw-r--r-- 1 root root 30460 Jul 13 20:36 jwhois.conf
-rw-r--r-- 1 root root 26137 Mar 23 07:23 readahead.early.files
The directory name is required because the current directory (.
) is not in the list of directories normally searched for commands (called the PATH). To make your script accessible to all users, move it to the /usr/local/bin directory, which appears by default in everyone’s PATH:
#mv
topten
/usr/local/bin
bash uses shell variables to keep track of current settings. These shell variables are private to the shell and are not passed to processes started by the shell—but they can be exported, which converts them into environment variables, which are passed to child processes.
You can view all shell and environment variables using the set command:
$ set
BASH=/bin/bash
BASH_ARGC=()
BASH_ARGV=()
BASH_LINENO=()
BASH_SOURCE=()
BASH_VERSINFO=([0]="3" [1]="1" [2]="17" [3]="1" [4]="release" [5]="i686-redhat-linux-gnu")
BASH_VERSION='3.1.17(1)-release'
COLORS=/etc/DIR_COLORS.xterm
COLORTERM=gnome-terminal
COLUMNS=172
CVS_RSH=ssh
DBUS_SESSION_BUS_ADDRESS=unix:abstract=/tmp/dbus-I4CWWfqvE6,guid=e202bd44a31ea8366b20151327662e00
DESKTOP_SESSION=default
DESKTOP_STARTUP_ID=
DIRSTACK=()
DISPLAY=:0.0
EUID=503
GDMSESSION=default
GDM_XSERVER_LOCATION=local
GNOME_DESKTOP_SESSION_ID=Default
GNOME_KEYRING_SOCKET=/tmp/keyring-FJyfaw/socket
GROUPS=()
GTK_RC_FILES=/etc/gtk/gtkrc:/home/hank/.gtkrc-1.2-gnome2
G_BROKEN_FILENAMES=1
HISTFILE=/home/hank/.bash_history
HISTFILESIZE=1000
HISTSIZE=1000
HOME=/home/hank
HOSTNAME=bluesky.fedorabook.com
HOSTTYPE=i686
IFS=$' \t\n'
INPUTRC=/etc/inputrc
KDEDIR=/usr
KDE_IS_PRELINKED=1
LANG=en_US.UTF-8
LESSOPEN='|/usr/bin/lesspipe.sh %s'
LINES=55
LOGNAME=hank
LS_COLORS='no=00:fi=00:di=00;34:ln=00;36:pi=40;33:so=00;35:bd=40;33;01:cd=40;33;01:or=01;05;37;41:mi=01;05;37;41:ex=00;32:*.cmd=00;32:*.exe=00;32:*.com=00;32:*.btm=00;32:*.bat=00;32:*.sh=00;32:*.csh=00;32:*.tar=00;31:*.tgz=00;31:*.arj=00;31:*.taz=00;31:*.lzh=00;31:*.zip=00;31:*.z=00;31:*.Z=00;31:*.gz=00;31:*.bz2=00;31:*.bz=00;31:*.tz=00;31:*.rpm=00;31:*.cpio=00;31:*.jpg=00;35:*.gif=00;35:*.bmp=00;35:*.xbm=00;35:*.xpm=00;35:*.png=00;35:*.tif=00;35:'
MACHTYPE=i686-redhat-linux-gnu
MAIL=/var/spool/mail/hank
MAILCHECK=60
OLDPWD=/usr/share/wallpapers
OPTERR=1
OPTIND=1
OSTYPE=linux-gnu
PATH=/usr/lib/qt-3.3/bin:/usr/kerberos/bin:/usr/local/bin:/usr/bin:/bin:/usr/X11R6/bin:/home/hank/bin
PIPESTATUS=([0]="0" [1]="141" [2]="0")
PPID=3067
PRELINKING=yes
PRELINK_FULL_TIME_INTERVAL=14
PRELINK_NONRPM_CHECK_INTERVAL=7
PRELINK_OPTS=-mR
PROMPT_COMMAND='echo -ne "\033]0;${USER}@${HOSTNAME%%.*}:${PWD/#$HOME/~}"; echo -ne "\007"'
PS1='$ '
PS2='> '
PS4='+ '
PWD=/etc
QTDIR=/usr/lib/qt-3.3
QTINC=/usr/lib/qt-3.3/include
QTLIB=/usr/lib/qt-3.3/lib
SESSION_MANAGER=local/beige.fedorabook.com:/tmp/.ICE-unix/2621
SHELL=/bin/bash
SHELLOPTS=braceexpand:emacs:hashall:histexpand:history:interactive-comments:monitor
SHLVL=2
SSH_AGENT_PID=2659
SSH_ASKPASS=/usr/libexec/openssh/gnome-ssh-askpass
SSH_AUTH_SOCK=/tmp/ssh-dNhrfX2621/agent.2621
TERM=xterm
UID=503
USER=hank
USERNAME=hank
WINDOWID=58721388
XAUTHORITY=/home/hank/.Xauthority
_=
qt_prefix=/usr/lib/qt-3.3
Many of these variables contain settings for particular programs. Some of the common variables used by many programs are shown in Table 4-16.
Table 4-16. Key environment variables
To set a shell variable, type the variable name, an equal sign, and the value you wish to assign (all values are treated as text):
$A=
red
Once a variable has been assigned a value, you can use it in commands, preceded by a dollar sign:
$ls -l
ls: red: No such file or directory $
red
touch $A
$ls -l red
-rw-r--r-- 1 hank hank 0 Jul 18 15:26 red
The echo command can be used to view the value of a variable:
$ echo $A
red
To destroy a variable, use the unset command:
$echo $A
red $unset A
$echo $A
$
Finally, to make a variable accessible to processes started by the current process, use the export command:
$unset A
$TEST=blue
$echo $TEST # variable is known to the shell
blue $bash # start a child shell
[hank@beige foo]$echo $TEST # variable is not known to child
[hank@beige foo]$exit # exit back to parent shell
exit $export
TEST # export the variable
$echo
$TEST # value is still known to the shell
blue $bash # start a new child shell
[hank@beige foo]$echo $TEST # exported value is known to the child
blue
The PATH value is stored in an environment variable of the same name. Its value can be viewed like any other environment variable:
$ echo $PATH
/usr/local/bin:/usr/bin:/bin:/usr/X11R6/bin
To add a directory to the existing directories, use $PATH
on the righthand side of an assignment to insert the current value of the variable into the new value:
$PATH=$PATH:
$
/home/hank/bin
echo $PATH
/usr/local/bin:/usr/bin:/bin:/usr/X11R6/bin:/home/hank/bin
Tip
You don’t need to export PATH
in this case because it has already been exported; assigning a new value does not changes its exported status.
Assuming that the topten script is saved in /home/hank/bin, you can now execute it by just typing its name:
$ topten
-rw-r--r-- 1 root root 807103 Jul 12 21:18 termcap
-rw-r--r-- 1 root root 499861 Jul 17 08:08 prelink.cache
-rw-r--r-- 1 root root 362031 Feb 23 08:09 services
-rw-r--r-- 1 root root 97966 Jul 15 11:19 ld.so.cache
-rw-r--r-- 1 root root 92794 Jul 12 12:46 Muttrc
-rw-r--r-- 1 root root 83607 Mar 23 07:23 readahead.files
-rw-r--r-- 1 root root 73946 Jul 13 02:23 sensors.conf
-rw-r--r-- 1 root root 45083 Jul 12 18:33 php.ini
-rw-r--r-- 1 root root 30460 Jul 13 20:36 jwhois.conf
-rw-r--r-- 1 root root 26137 Mar 23 07:23 readahead.early.files
Within a script, you can prompt the user using the
echo
command, and then use the
read command to read a line from the user and place it in an environment variable:
echo "Please enter your name:" read NAME echo "Hello $NAME!"
Or you can collect the standard output of a command and assign it to a variable using the $()
symbols:
$NOW=$(date)
$echo $NOW
Tue Jul 18 22:25:48 EDT 2006
There are several special parameters, or special variables, that bash sets automatically; Table 4-17 contains a list of the most important ones.
Table 4-17. Important special variables
Like most programming languages, bash features a number of control structures to enable looping and conditional execution. The three most common control structures are listed in Table 4-18; there is also a C-style
for
loop that I’ll discuss in the next section.
Table 4-18. Common bash control structures
The for..in
control structure is great for looping over a range of values. This loop will display the status of the httpd, ftpd, and NetworkManager services:
for SERVICE in httpd ftpd NetworkManager do /sbin/service $SERVICE status done
for...in
is even more useful when the list of values is specified as an ambiguous filename. In this script, the loop is repeated once for each file in the directory /etc/ that ends in .conf:
mkdir backup for FILE in /etc/*.conf do echo "Backing up the file $FILE..." cp $FILE backup/ done
For the if
and while
control structures, a
control-command determines the action taken. The control-command can be any command on the system; an
exit status of zero is considered true
and any other exit status is considered false
.
For example, the
grep command exits with a value of zero if a given pattern is found in the file(s) specified or in the standard input. When combined with an if
structure, you can cause a program to take a particular action if a pattern is found. For example, this code displays the message “Helen is logged in!” if the output of who contains the word helen
:
if who | grep -q helen then echo "Helen is logged in!" fi
Tip
The exit status of the last command is taken as the exit status of a pipeline, which is grep in this case. The -q
argument to grep suppresses the output—otherwise, matching lines are sent to standard output.
The built-in command
test can be used to test conditions; the exit status will be zero if the condition is true
. The most common
conditional expressions are listed in Table 4-19.
Table 4-19. Common bash conditional operators
So if you wanted to print “Too high!” if the value of the variable A
was over 50, you would write:
if test "$A" -gt 50 then echo "Too high!" fi
The variable expression $A
is quoted in case A
has a null value (“”) or doesn’t exist—in which case, if unquoted, a syntax error would occur because there would be nothing to the left of -gt
.
The
square brackets ([]
) are a synonym for test
, so the previous code is more commonly written:
if [ "$A" -gt 50 ] then echo "Too high!" fi
You can also use test with the while control structure. This loop monitors the number of users logged in, checking every 15 seconds until the number of users is equal to or greater than 100, when the loop will exit and the following pipeline will send an email to the email alias alert:
while [ "$(who | wc -l)" -lt 100 ] do sleep 15 done echo "Over 100 users are now logged in!"|mail -s "Overload!" alert
bash provides very limited integer arithmetic capabilities. An expression inside double
parentheses ((
))
is interpreted as a numeric expression; an expression inside double parentheses preceded by a
dollar sign $((
))
is interpreted as a numeric expression that also returns a value.
Tip
Inside double parentheses, you can read a variable’s value without using the dollar sign (use A=B+C
instead of A=$B+$C
).
Here’s an example using a while
loop that counts from 1 to 20 using integer arithmetic:
A=0 while [ "$A" -lt 20 ] do (( A=A+1 )) echo $A done
The C-style increment operators are available, so this code could be rewritten as:
A=0 while [ "$A" -lt 20 ] do echo $(( ++A )) done
The expression $(( ++A ))
returns the value of A
after it is incremented. You could also use $(( A++ ))
, which returns the value of A
before it is incremented:
A=1 while [ "$A" -le 20 ] do echo $(( A++ )) done
Since loops that count through a range of numbers are often needed, bash also supports the C-style
for
loop. Inside double parentheses, specify an initial expression, a conditional expression, and a per-loop expression, separated by semicolons:
# Initial value of A is 1 # Keep looping as long as A<=20 # Each time you loop, increment A by 1 for ((A=1; A<=20; A++)) do echo $A done
Note that the conditional expression uses normal comparison symbols (<=
) instead of the alphabetic options (-le
) used by test
.
Tip
Don’t confuse the C-style for
loop with the for...in
loop!
So far we have been assuming that the user is using the bash shell; if the user of another shell (such as tcsh) tries to execute one of your scripts, it will be interpreted according to the language rules of that shell and will probably fail.
To make your scripts more robust, add a shebang line at the beginning—a pound-sign character followed by an exclamation mark, followed by the full path of the shell to be used to interpret the script (/bin/bash):
#!/bin/bash # script to count from 1 to 20 for ((A=1; A<=20; A++)) do echo $A done
I also added a comment line (starting with #
) after the shebang line to describe the function of the script.
Tip
The shebang line gets its name from sharp and bang, common nicknames for the #!
characters.
Here is an example of a longer script, taking advantage of some of the scripting features in bash:
#!/bin/bash # # number-guessing game # # If the user entered an argument on the command # line, use it as the upper limit of the number # range. if [ "$#" -eq 1 ] then MAX=$1 else MAX=100 fi # Set up other variables SECRET=$(( (RANDOM % MAX) + 1 )) # Random number 1-100 TRIES=0 GUESS=-1 # Display initial messages clear echo "Number-guessing Game" echo "--------------------" echo echo "I have a secret number between 1 and $MAX." # Loop until the user guesses the right number while [ "$GUESS" -ne "$SECRET" ] do # Prompt the user and get her input ((TRIES++)) echo -n "Enter guess #$TRIES: " read GUESS # Display low/high messages if [ "$GUESS" -lt "$SECRET" ] then echo "Too low!" fi if [ "$GUESS" -gt "$SECRET" ] then echo "Too high!" fi done # Display final messages echo echo "You guessed it!" echo "It took you $TRIES tries." echo
This script could be saved as /usr/local/bin/guess-it and then made executable:
#chmod a+rx
/usr/local/bin/guess-it
Here’s a test run of the script:
$guess-it
Number-guessing Game -------------------- I have a secret number between 1 and 100. Enter guess #1:Too low! Enter guess #2:
50
Too low! Enter guess #3:
75
Too low! Enter guess #4:
83
Too high! Enter guess #5:
92
Too high! Enter guess #6:
87
Too low! Enter guess #7:
85
You guessed it! It took you 7 tries.
86
Another test, using an alternate upper limit:
$guess-it 50
Number-guessing Game -------------------- I have a secret number between 1 and 50. Enter guess #1:Too low! Enter guess #2:
25
Too low! Enter guess #3:
37
Too high! Enter guess #4:
44
You guessed it! It took you 4 tries.
40
When a user logs in, the system-wide script /etc/profile and the per-user script ~/.bash_profile are both executed. This is the default /etc/profile:
# /etc/profile # System wide environment and startup programs, for login setup # Functions and aliases go in /etc/bashrc pathmunge () { if ! echo $PATH | /bin/egrep -q "(^|:)$1($|:)" ; then if [ "$2" = "after" ] ; then PATH=$PATH:$1 else PATH=$1:$PATH fi fi } # ksh workaround if [ -z "$EUID" -a -x /usr/bin/id ]; then EUID=\Qid -u\Q UID=\Qid -ru\Q fi # Path manipulation if [ "$EUID" = "0" ]; then pathmunge /sbin pathmunge /usr/sbin pathmunge /usr/local/sbin fi # No core files by default ulimit -S -c 0 > /dev/null 2>&1 if [ -x /usr/bin/id ]; then USER="\Qid -un\Q" LOGNAME=$USER MAIL="/var/spool/mail/$USER" fi HOSTNAME=\Q/bin/hostname\Q HISTSIZE=1000 if [ -z "$INPUTRC" -a ! -f "$HOME/.inputrc" ]; then INPUTRC=/etc/inputrc fi export PATH USER LOGNAME MAIL HOSTNAME HISTSIZE INPUTRC for i in /etc/profile.d/*.sh ; do if [ -r "$i" ]; then . $i fi done unset i unset pathmunge
This script adds /sbin, /usr/sbin, and /usr/local/sbin to the PATH
if the user is the root user. It then creates and exports the USER
, LOGNAME
, MAIL
, HOSTNAME
, and HISTSIZE
variables, and executes any files in /etc/profile.d that end in .sh.
The default ~/.bash_profile looks like this:
# .bash_profile # Get the aliases and functions if [ -f ~/.bashrc ]; then . ~/.bashrc fi # User specific environment and startup programs PATH=$PATH:$HOME/bin export PATH
You can edit /etc/profile to change the login process for all users, or ~/.bash_profile to change just your login process. One useful change that I make to every Fedora system I install is to comment out the if
statements for path manipulation in /etc/profile so that every user has the superuser binary directories in his path:
# Path manipulation#
if [ "$EUID" = "0" ]; then pathmunge /sbin pathmunge /usr/sbin pathmunge /usr/local/sbin#
fi
Tip
bash comments start with #
and are not executed—so commenting out code means adding #
at the start of selected lines to disable them.
Environment variables are inherited by child processes, so any environment variables set up during the login process are accessible to all shells (and other programs) you start. bash also supports the use of aliases, or nicknames, for commands, but since these are not inherited by child processes, they are instead placed in the file ~/.bashrc, which is executed each time a shell starts. If you log in once and then start three shells, ~/.bash_profile is executed once at login and ~/.bashrc is executed three times, once for each shell that starts.
This is the default ~/.bashrc:
# .bashrc # Source global definitions if [ -f /etc/bashrc ]; then . /etc/bashrc fi # User-specific aliases and functions
As you can see, there aren’t any alias definitions in there (but you can add them). The file /etc/bashrc is invoked by this script, and it contains common aliases made available to all users:
# System-wide functions and aliases # Environment stuff goes in /etc/profile # By default, we want this to get set. # Even for noninteractive, nonlogin shells. umask 022 # Are we an interactive shell? if [ "$PS1" ]; then case $TERM in xterm*) if [ -e /etc/sysconfig/bash-prompt-xterm ]; then PROMPT_COMMAND=/etc/sysconfig/bash-prompt-xterm else PROMPT_COMMAND='echo -ne ↵ "\033]0;${USER}@${HOSTNAME%%.*}:${PWD/#$HOME/~}"; echo -ne "\007"' fi ;; screen) if [ -e /etc/sysconfig/bash-prompt-screen ]; then PROMPT_COMMAND=/etc/sysconfig/bash-prompt-screen else PROMPT_COMMAND='echo -ne "\033_${USER}@${HOSTNAME%%.*}:${PWD/#$HOME/~}"; echo -ne "\033\\"' fi ;; *) [ -e /etc/sysconfig/bash-prompt-default ] && PROMPT_COMMAND=/etc/sysconfig/bash-prompt-default ;; esac # Turn on checkwinsize shopt -s checkwinsize [ "$PS1" = "\\s-\\v\\\$ " ] && PS1="[\u@\h \W]\\$ " fi if ! shopt -q login_shell ; then # We're not a login shell # Need to redefine pathmunge, it get's undefined at the end of /etc/profile pathmunge () { if ! echo $PATH | /bin/egrep -q "(^|:)$1($|:)" ; then if [ "$2" = "after" ] ; then PATH=$PATH:$1 else PATH=$1:$PATH fi fi } for i in /etc/profile.d/*.sh; do if [ -r "$i" ]; then . $i fi done unset i unset pathmunge fi # vim:ts=4:sw=4
This script sets up the umask, configures a command that will be executed before the display of each prompt (which sets the terminal-window title to show the user, host, and current directory), and then executes each of the files in /etc/profile.d that end in .sh.
Packages installed on your Fedora system can include files that are placed in /etc/profile.d, providing a simple way for each package to globally add aliases or other shell configuration options. There are a few command aliases defined in these script files, including:
alias l.='ls -d .* --color=tty' alias ll='ls -l --color=tty' alias ls='ls --color=tty' alias vi='vim'
If you type ll
at a command prompt, ls
-l
will be executed, due to the alias highlighted in the preceding listing:
$ ll /
total 138
drwxr-xr-x 2 root root 4096 Jul 17 08:08 bin
drwxr-xr-x 4 root root 1024 Jul 15 11:16 boot
drwxr-xr-x 12 root root 3900 Jul 19 07:56 dev
drwxr-xr-x 102 root root 12288 Jul 18 18:14 etc
drwxr-xr-x 8 root root 4096 Jul 16 22:51 home
drwxr-xr-x 11 root root 4096 Jul 17 07:58 lib
drwx------ 2 root root 16384 Jun 9 19:34 lost+found
drwxr-xr-x 4 root root 4096 Jul 18 18:14 media
drwxr-xr-x 2 root root 0 Jul 18 11:48 misc
drwxr-xr-x 6 root root 4096 Jul 15 11:38 mnt
drwxr-xr-x 2 root root 0 Jul 18 11:48 net
drwxr-xr-x 2 root root 4096 Jul 12 04:48 opt
dr-xr-xr-x 126 root root 0 Jul 18 11:46 proc
drwxr-x--- 9 root root 4096 Jul 18 00:18 root
drwxr-xr-x 2 root root 12288 Jul 17 08:08 sbin
drwxr-xr-x 4 root root 0 Jul 18 11:46 selinux
drwxr-xr-x 2 root root 4096 Jul 12 04:48 srv
drwxr-xr-x 11 root root 0 Jul 18 11:46 sys
drwxrwxrwt 98 root root 4096 Jul 19 11:04 tmp
drwxr-xr-x 14 root root 4096 Jul 14 04:17 usr
drwxr-xr-x 26 root root 4096 Jul 14 04:17 var
Similarly, if you type vi
the shell will execute vim.
You can create your own aliases using the
alias command; for example, I like to use l
for ls
-l
, sometimes use cls to clear the screen, and like to have machine
report the hostname (old habits):
$alias l='ls -l
$alias cls='clear'
$alias machine='hostname'
Adding the same lines to ~/.bashrc will make them available every time you start a new shell; adding them to ~/.bashrc will make them available to all users.
You can see the currently defined aliases by typing alias
alone as a command:
$ alias
alias cls='clear'
alias l='ll'
alias l.='ls -d .* --color=tty'
alias ll='ls -l --color=tty'
alias ls='ls --color=tty'
alias machine='hostname'
alias vi='vim'
To destroy an alias, use the unalias command:
$unalias
$
machine
alias
alias cls='clear' alias l='ll' alias l.='ls -d .* --color=tty' alias ll='ls -l --color=tty' alias ls='ls --color=tty' alias vi='vim'
When the kernel receives a request to execute a file (and that file is executable), it uses magic number codes at the start of the file to determine how to execute it. For example, there are magic numbers for standard Executable and Linking Format (ELF) binaries and historical assembler output (a.out) binaries; the kernel will use them to set up the correct execution environment and then start the program.
If the first two bytes of the file are
#!
, which counts as a magic number, the file is treated as a script: a pathname is read from the file starting at the third byte and continuing to the end of the first line. The shell or interpreter program identified by this pathname is executed, and the script name and all arguments are passed to the interpreter.
If a file has no magic number or shebang line, the kernel will attempt to execute it as though the value of the
SHELL
environment variable were given on the shebang line.
Other scripting languages such as Perl and Python can be used to construct full-scale GUI applications, but the zenity program enables a shell script to interact with a GUI user.
zenity presents a simple dialog or information box to the user. There are a number of dialog types available, including information and error boxes, text entry and editing boxes, and date-selection boxes; the type of dialog as well as the messages that appear in the dialog are configured by zenity options.
Here is the number-guessing script rewritten to use zenity for the user interface:
#!/bin/bash # # number-guessing game - GUI version # # If the user entered an argument on the command # line, use it as the upper limit of the number # range if [ "$#" -eq 1 ] then MAX=$1 else MAX=100 fi # Set up other variables SECRET=$(( (RANDOM % MAX) + 1 )) # Random number 1-100 TRIES=0 GUESS=-1 # Display initial messages zenity --info --text \ "I have a secret number between 1 and $MAX. Try and guess it!" \ --title "Guess-It" # Loop until the user guesses the right number while [ "$GUESS" -ne "$SECRET" ] do # Prompt the user and get her input ((TRIES++)) GUESS=$(zenity --entry --text "Enter guess #$TRIES:" --title "Guess...") # Display low/high messages if [ "$GUESS" -lt "$SECRET" ] then zenity --info --text "Too low!" fi if [ "$GUESS" -gt "$SECRET" ] then zenity --info --text "Too high!" fi done # Display final messages zenity --info --text "You guessed it! It took you $TRIES tries." --title "Congratulations!"
Figure 4-16 shows the zenity dialogs produced by this script. Obviously, this user interface is not as refined as one that could be provided by a full-featured GUI application, but it is perfectly suitable for simple interactions.
Get Fedora Linux now with the O’Reilly learning platform.
O’Reilly members experience books, live events, courses curated by job role, and more from O’Reilly and nearly 200 top publishers.