Shell Scripting part2
This code will keep asking for input until you hit RETURN (X is zero length). Thanks to Justin Heath for pointing out that the script didn't work - I'd missed out the quotes around $X in the
while [ -n "$X" ]
. Without those quotes, there is nothing to test when $X is empty.
$ ./test2.sh
Enter some text (RETURN to quit)
fred
You said: fred
Enter some text (RETURN to quit)
wilma
You said: wilma
Enter some text (RETURN to quit)
quit
You said: quit
Enter some text (RETURN to quit)
You said:
$
$ cat test2.sh
#!/bin/sh
X=0
while [ -n "$X" ]
do
echo "Enter some text (RETURN to quit)"
read X
echo "You said: $X"
done
$
9. Case
The
case
statement saves going through a whole set ofif .. then .. else
statements. Its syntax is really quite simple:
$
$ vim talk.sh
$ chmod 775 talk.sh
$
$ ./talk.sh
Please talk to me ...
hello
Hello yourself!
What do you think of politics?
Sorry, I don't understand
bye
See you again!
That's all folks!
$
$ cat talk.sh
#!/bin/sh
echo "Please talk to me ..."
while :
do
read INPUT_STRING
case $INPUT_STRING in
hello)
echo "Hello yourself!"
;;
bye)
echo "See you again!"
break
;;
*)
echo "Sorry, I don't understand"
;;
esac
done
echo
echo "That's all folks!"
$
The syntax is quite simple:
Thecase
line itself is always of the same format, and it means that we are testing the value of the variableINPUT_STRING
.
The options we understand are then listed and followed by a right bracket, ashello)
andbye)
.
This means that ifINPUT_STRING
matcheshello
then that section of code is executed, up to the double semicolon.
IfINPUT_STRING
matchesbye
then the goodbye message is printed and the loop exits. Note that if we wanted to exit the script completely then we would use the commandexit
instead ofbreak
.
The third option here, the*)
, is the default catch-all condition; it is not required, but is often useful for debugging purposes even if we think we know what values the test variable will have.The whole case statement is ended with
esac
(case backwards!) then we end the while loop with adone
.
10. Variables - Part II
There are a set of variables which are set for you already, and most of these cannot have values assigned to them.
These can contain useful information, which can be used by the script to know about the environment in which it is running.The first set of variables we will look at are
$0 .. $9
and$#
.
The variable$0
is the basename of the program as it was called.$1 .. $9
are the first 9 additional parameters the script was called with.
The variable$@
is all parameters$1 .. whatever
. The variable$*
, is similar, but does not preserve any whitespace, and quoting, so "File with spaces" becomes "File" "with" "spaces". This is similar to theecho
stuff .As a general rule, use$@
and avoid$*
.$#
is the number of parameters the script was called with.
var3.sh
$
$ cat var3.sh
#!/bin/sh
echo "I was called with $# parameters"
echo "My name is $0"
echo "My first parameter is $1"
echo "My second parameter is $2"
echo "All parameter are $@"
$
$
$
Let's look at running this code and see the output:
$
$ vim var3.sh
$ chmod 775 var3.sh
$
$ pwd
/home/maxwell/projects/shell_20230320
$ /home/maxwell/projects/shell_20230320/var3.sh
I was called with 0 parameters
My name is /home/maxwell/projects/shell_20230320/var3.sh
My first parameter is
My second parameter is
All parameter are
$
$ ./var3.sh hello world earth
I was called with 3 parameters
My name is ./var3.sh
My first parameter is hello
My second parameter is world
All parameter are hello world earth
$
Note that the value of
$0
changes depending on how the script was called. The external utilitybasename
can help tidy this up:
echo "My name is `basename $0`"
$#
and$1 .. $9
are set automatically by the shell.
We can take more than 9 parameters by using the
shift
command; look at the script below:
$
$ vim var4.sh
$
$ chmod 775 var4.sh
$
$ cat var4.sh
#!/bin/sh
while [ "$#" -gt "0" ]
do
echo "\$1 is $1"
shift
done
$
This script keeps on using
shift
until$#
is down to zero, at which point the list is empty.
Another special variable is $?
. This contains the exit value of the last run command. So the code:
#!/bin/sh
/usr/local/bin/my-command
if [ "$?" -ne "0" ]; then
echo "Sorry, we had a problem there!"
fi
will attempt to run
/usr/local/bin/my-command
which should exit with a value of zero if all went well, or a nonzero value on failure. We can then handle this by checking the value of$?
after calling the command. This helps make scripts robust and more intelligent.
在shell中,"$?"是一个特殊的shell变量,它存储了上一个执行的命令的退出状态。
当一个命令被执行时,它会返回一个退出状态码,用于指示该命令是否成功执行。"$?"的值被设置为该退出状态码。
"$?"的值为0表示上一个命令成功执行,而非零值表示有错误发生。
您可以在shell脚本中使用"$?"来检查前一个命令的退出状态,并根据该状态采取适当的操作。例如,您可以使用“if”语句检查"$?"是否等于0,如果是则执行一些代码,否则执行不同的代码。
The other two main variables set for you by the environment are
$$
and$!
. These are both process numbers.
The$$
variable is the PID (Process IDentifier) of the currently running shell. This can be useful for creating temporary files, such as/tmp/my-script.$$
which is useful if many instances of the script could be run at the same time, and they all need their own temporary files.
The
$!
variable is the PID of the last run background process. This is useful to keep track of the process as it gets on with its job.Another interesting variable is
IFS
. This is the Internal Field Separator. The default value isSPACE TAB NEWLINE
, but if you are changing it, it's easier to take a copy, as shown:
在shell中,"$$"和"$!"都是特殊的变量,分别用于获取当前进程的进程ID(PID)和上一个异步进程的PID。
"$$" 变量返回当前进程的PID,它是一个唯一的数字标识符,用于标识该进程在操作系统中的位置。您可以使用它来识别当前正在运行的进程,或将其作为锁文件名称的一部分,以确保在同一时间只有一个实例运行。
"$!" 变量返回上一个异步进程的PID,当您在后台运行一个命令时,该命令将在后台运行,并且"$!"变量将返回该命令的PID。您可以使用它来等待该命令的完成,或使用它来检查进程的状态。
例如,假设您在后台运行了一个长时间运行的脚本:
./long_script.sh &
在这种情况下,"$!"将返回该脚本的PID。您可以使用它来等待该脚本的完成:
wait $!
或者您可以使用它来检查该脚本的状态:
kill -0 $!
if [ $? -eq 0 ]; then
echo "The script is still running."
else
echo "The script has finished."
fi
$
$ vim var5.sh
$
$ chmod 775 var5.sh
$
$ ./var5.sh
Please input some data separated by colons ...
hello:how are you:today
x is hello y is how are you z is today
$
$
$ ./var5.sh
Please input some data separated by colons ...
hello:how are you:today:my:friend
x is hello y is how are you z is today:my:friend
$
$
$ cat var5.sh
#!/bin/sh
old_IFS="$IFS"
IFS=:
echo "Please input some data separated by colons ..."
read x y z
IFS=$old_IFS
echo "x is $x y is $y z is $z"
$
$
11. Variables - Part III
curly brackets around a variable avoid confusion:
$
$ foo=sun
$ echo $fooshine # $ fooshine is undefined
$ echo ${foo}shine # displays the word "sunshine"
sunshine
$
$
Using Default Values
Consider the following code snippet which prompts the user for input, but accepts defaults:
$
$ vim name.sh
$
$ chmod 775 name.sh
$
$ ./name.sh
What is your name [ maxwell ]
Your name is : maxwell
$
$
$ cat name.sh
#!/bin/sh
echo -en "What is your name [ `whoami` ] "
read myname
if [ -z "$myname" ]; then
myname=`whoami`
fi
echo "Your name is : $myname"
$
Passing the "
-en
" to echo tells it not to add a linebreak (for bash and csh). For Dash, Bourne and other compliant shells, you use a "\c
" at the end of the line, instead. Ksh understands both form
This script runs like this if you accept the default by pressing "RETURN":
$
$
$ ./name.sh
What is your name [ maxwell ]
Your name is : maxwell
$
$ ./name.sh
What is your name [ maxwell ] foo
Your name is : foo
$
$
12. External Programs
External programs are often used within shell scripts; there are a few builtin commands (
echo
,which
, andtest
are commonly builtin), but many useful commands are actually Unix utilities, such astr
,grep
,expr
andcut
.
The backtick (`)is also often associated with external commands. Because of this, we will discuss the backtick first.
The backtick is used to indicate that the enclosed text is to be executed as a command. This is quite simple to understand. First, use an interactive shell to read your full name from/etc/passwd
:
$
$ grep "^${USER}:" /etc/passwd | cut -d: -f5
maxwell
$
$
Now we will grab this output into a variable which we can manipulate more easily:
$
$ MYNAME=`grep "^${USER}:" /etc/passwd | cut -d: -f5`
$ echo $MYNAME
maxwell
$
So we see that the backtick simply catches the standard output from any command or set of commands we choose to run. It can also improve performance if you want to run a slow command or set of commands and parse various bits of its output:
#!/bin/sh
find / -name "*.html" -print | grep "/index.html$"
find / -name "*.html" -print | grep "/contents.html$"
this code could take a long time to run, and we are doing it twice!
A better solution is:
#!/bin/sh
HTML_FILES=`find / -name "*.html" -print`
echo "$HTML_FILES" | grep "/index.html$"
echo "$HTML_FILES" | grep "/contents.html$"
Note: the quotes around
$HTML_FILES
are essential to preserve the newlines between each file listed. Otherwise,grep
will see one huge long line of text, and not one line per file.
This way, we are only running the slow find
once, roughly halving the execution time of the script.