2056
command , shell script ,tool , crontab / Re: คำสั่ง find,awk,sed,grep ที่อาจจะเป็นประโยชน์ ไว้ใช้ในการ track ปัญหา
« on: มกราคม 23, 2011, 10:14:17 pm »
An Introduction to Shell Scripting with bash (continued)
In the last article, we dealt with variables and positional parameters. This month, it's time to
examine the more advanced features that make scripts much more powerful, especially bash's flow
control capabilities.
Functions
The bash shell allows us to define functions, which are effectively subroutines. This makes complex
scripts significantly simpler, as well as making them faster. You can also define functions in your
login scripts, rather like aliases, only more sophisticated. You can define a function like this:
funcname ()
{
shell statements
}
You can do this interactively - for example:
[ les@sleipnir les]$ beep ()
> {
> echo -e -n \\a
> }
[ les@sleipnir les]$ beep
[ les@sleipnir les]$
You can see how, after the parentheses, the shell prompt changes to PS2 (>) to indicate the shell
will accept more input without immediately processing it, until the closing curly brace. Once this
has been done, you can execute a beep command at any time. (In case you're wondering, aliases
take precedence over keywords like if, which take precedence over functions, which take precedence
over builtins (like cd) which take precedence over executable files).
Flow Control
A shell script that simply runs some commands is all very well, but it would be a lot better if it
could deal with errors rather than plowing on regardless. And looping would be useful - to process
lots of files or directories.
if ... else
Almost all programs that run from the command line terminate through an exit() system call which
returns an exit status. In general, an exit status value of 0 indicates successful execution, while
increasingly large integers indicate increasingly serious errors.
Immediately after a program returns, its exit status can be accessed through the $? shell variable.
For example:
[ les@sleipnir scripts]$ ls
stat
[ les@sleipnir scripts]$ echo $?
0
[ les@sleipnir scripts]$ ls nonexistent
ls: nonexistent: No such file or directory
[ les@sleipnir scripts]$ echo $?
1
[ les@sleipnir scripts]$
As you can see, if the ls command matches some files, its exit status is zero, while if no files are
found, the exit status is one. The exit status can be used in scripts - for example, here is an
excerpt from a shell script that I commonly use to rebuild kernels:
make bzImage
if [ $? -gt 0 ] ; then
echo make bzImage failed
exit
fi
This introduces the if statement, as well as the -gt comparison operator. The syntax of if is:
if COMMANDS ; then
COMMANDS;
[ elif COMMANDS; then COMMANDS ; ]...
[ else COMMANDS; ]
fi
The square brackets in the syntax above indicate optional components of the statement. However,
square brackets, to the bash shell - as in the preceding example - indicate a test (in fact,
/usr/bin/[ is a symbolic link to the test binary on many systems). The expression in the square
brackets will be evaluated and true or false returned. So in the example above, if the make bzImage
step fails, it will return an exit status of 1 or higher. The test says that if the exit status is
greater than zero, then the script will print an error message and exit.
You can also test files for various attributes: does the file exist, is it a directory, is it
executable, do you have read permission, etc. This is particularly useful in scripts that install
software - you can check whether a configuration file already exists, for example, and skip any
steps that would over-write it.
Table 1: File operators
Operator Returns true if:
-d file file exists and is a directory
-e file file exists
-f file file exists and is a regular file
-r file file exists and you have read permission on it
-s file file exists and is more than 0 bytes in size
-w file You have write permission on file
-x file You have execute permission on file, or search permission if it is a directory
-O file You are the owner of file
-G file You are a member of the group that owns the file
file1 -nt file2 file1 is newer than file2
file1 -ot file2 file1 is older than file2
Remember that many of these commands can be used interactively, at the command prompt, as well as in
scripts. For example, here is a command line that will show the disk space used by every
subdirectory of the current directory:
for n in * ; do if [ -d $n ]; then du -hs $n ; fi; done
You can also perform string comparisons, as shown in Table 2.
Table 2: String operators
Operator Returns true if:
str1 = str2 str1 matches str2
str1 != str2 str1 does not match str2
str1 < str2 str1 is less than str2
str1 > str2 str1 is greater than str2
-n str1 str1 is non-null (i.e. is not an empty string)
-z str1 str1 is null (i.e. has length zero)
And finally, you can make comparisons between integer variables, as shown in Table 3. You'll see
a little later how to perform arithmetic.
Table 3: Integer operators
Operator Returns true if:
var1 -lt var2 var1 is less than var2
var1 -le var2 var1 is less than or equal to var2
var1 -eq var2 var1 is equal to var2
var1 -ne var2 var1 is not equal to var2
var1 -gt var2 var1 is greater than var2
var1 -ge var2 var1 is greater than or equal to var2
The for Statement
The for statement provides looping within scripts. However, bash's for statement is quite
different from that found in C, Perl and similar languages, which surprises novice shell scripters.
The syntax of the for statement is:
for varname [in list]
do
statement
...
done
The statements in the loop will probably use $varname in some way. The effect of this statement is
to substitute each value in list in turn, into the variable varname, and perform the body of the
loop. The list can actually be a wildcard, which the shell will automatically expand into a list of
matching filenames, and you can see an example of this in the directory size listing command above.
case
The case statement is essentially a multi-way branch, rather like an if . . ifelif . . . else . . .
fi chain. It is particularly common in the SysVInit scripts which are used to start, stop, restart
and reload daemons on distributions like Red Hat and Mandrake. The basic syntax of the case
statement is:
case varname in
value1)
statements
;;
value2)
statements
;;
*)
statements
;;
esac
You can find lots of examples in the / etc/rc.d/init.d or /etc/init.d directory on your system, and
the example at the end of this article.
while & until
More conventional are the while and until looping constructs. The syntax for while is:
while COMMANDS ; do
COMMANDS
done
Here is an example - a simple script that adds up the values of all integers between 1 and 100:
Listing 1: A shell script to sum the integers between 1 and 100:
#!/bin/bash
# Simple script to demonstrate while and arithmetic
count=0
sum=0
while [ $count -lt 101 ] ; do
sum=$(( $sum + $count ))
count=$(( $count + 1 ))
done
echo "Sum = $sum"
This example also shows one way of doing arithmetic in shell scripts, with the $(( expression ))
construct.
The until statement is similar, except that it continues looping as long as the test condition
fails. I find this quite useful at the command line, as a way of waiting for certain events to occur
- for example, waiting for a network link to come up:
until ping -c 1 192.168.170.1; do sleep 60; done; ssh 192.168.170.1
shift
Another statement that is useful in connection with loops is shift. This works on Linux pretty much
like it does on DOS/Windows - it shifts the positional parameters (except $0) down by one, so that
the old value of $1 is lost and its new value is $2, the new value of $2 is $3, and so on. This
allows one-by-one processing of an arbitrarily long list of arguments. Here is an example that
demonstrates shift, along with the until statement:
Listing 2: A script to demonstrate shift.
#!/bin/bash
until [ -z $1 ] ; do
echo $1
shift
done
This simple script just echoes its arguments, each on a separate line.
Let's finish up with an example. This short script works to remind you to do things at some time
(between 1 and 999 minutes) in the future, and illustrates many of the techniques previously
discussed:
* Integer operator ( -lt )
* Definition and calling of a function
* A for loop, if statement and more integer operators ( -eq)
* A case statement
Listing 3: A short script to set a reminder timer
#!/bin/bash
# Simple reminder shell script
if [ $# -lt 2 ] ; then
echo "usage: $0 minutes message"
exit 1
fi
alarm()
{
sleep ${delay}m
echo -n $msg
for i in 1 2 3 4 5 6 7 8 9 ; do
if [ $i -eq 2 -o $i -eq 4 -o $i -eq 6 -o $i -eq 8 ] ; then
sleep 1
else
echo -n -e \\a
fi
done
}
delay=$1
msg=$2
case $1 in
[0-9] | [0-9][0-9] | [0-9][0-9][0-9] )
alarm &
;;
*)
echo 'usage: minutes value must be in range 0 to 999'
;;
esac
In the last article, we dealt with variables and positional parameters. This month, it's time to
examine the more advanced features that make scripts much more powerful, especially bash's flow
control capabilities.
Functions
The bash shell allows us to define functions, which are effectively subroutines. This makes complex
scripts significantly simpler, as well as making them faster. You can also define functions in your
login scripts, rather like aliases, only more sophisticated. You can define a function like this:
funcname ()
{
shell statements
}
You can do this interactively - for example:
[ les@sleipnir les]$ beep ()
> {
> echo -e -n \\a
> }
[ les@sleipnir les]$ beep
[ les@sleipnir les]$
You can see how, after the parentheses, the shell prompt changes to PS2 (>) to indicate the shell
will accept more input without immediately processing it, until the closing curly brace. Once this
has been done, you can execute a beep command at any time. (In case you're wondering, aliases
take precedence over keywords like if, which take precedence over functions, which take precedence
over builtins (like cd) which take precedence over executable files).
Flow Control
A shell script that simply runs some commands is all very well, but it would be a lot better if it
could deal with errors rather than plowing on regardless. And looping would be useful - to process
lots of files or directories.
if ... else
Almost all programs that run from the command line terminate through an exit() system call which
returns an exit status. In general, an exit status value of 0 indicates successful execution, while
increasingly large integers indicate increasingly serious errors.
Immediately after a program returns, its exit status can be accessed through the $? shell variable.
For example:
[ les@sleipnir scripts]$ ls
stat
[ les@sleipnir scripts]$ echo $?
0
[ les@sleipnir scripts]$ ls nonexistent
ls: nonexistent: No such file or directory
[ les@sleipnir scripts]$ echo $?
1
[ les@sleipnir scripts]$
As you can see, if the ls command matches some files, its exit status is zero, while if no files are
found, the exit status is one. The exit status can be used in scripts - for example, here is an
excerpt from a shell script that I commonly use to rebuild kernels:
make bzImage
if [ $? -gt 0 ] ; then
echo make bzImage failed
exit
fi
This introduces the if statement, as well as the -gt comparison operator. The syntax of if is:
if COMMANDS ; then
COMMANDS;
[ elif COMMANDS; then COMMANDS ; ]...
[ else COMMANDS; ]
fi
The square brackets in the syntax above indicate optional components of the statement. However,
square brackets, to the bash shell - as in the preceding example - indicate a test (in fact,
/usr/bin/[ is a symbolic link to the test binary on many systems). The expression in the square
brackets will be evaluated and true or false returned. So in the example above, if the make bzImage
step fails, it will return an exit status of 1 or higher. The test says that if the exit status is
greater than zero, then the script will print an error message and exit.
You can also test files for various attributes: does the file exist, is it a directory, is it
executable, do you have read permission, etc. This is particularly useful in scripts that install
software - you can check whether a configuration file already exists, for example, and skip any
steps that would over-write it.
Table 1: File operators
Operator Returns true if:
-d file file exists and is a directory
-e file file exists
-f file file exists and is a regular file
-r file file exists and you have read permission on it
-s file file exists and is more than 0 bytes in size
-w file You have write permission on file
-x file You have execute permission on file, or search permission if it is a directory
-O file You are the owner of file
-G file You are a member of the group that owns the file
file1 -nt file2 file1 is newer than file2
file1 -ot file2 file1 is older than file2
Remember that many of these commands can be used interactively, at the command prompt, as well as in
scripts. For example, here is a command line that will show the disk space used by every
subdirectory of the current directory:
for n in * ; do if [ -d $n ]; then du -hs $n ; fi; done
You can also perform string comparisons, as shown in Table 2.
Table 2: String operators
Operator Returns true if:
str1 = str2 str1 matches str2
str1 != str2 str1 does not match str2
str1 < str2 str1 is less than str2
str1 > str2 str1 is greater than str2
-n str1 str1 is non-null (i.e. is not an empty string)
-z str1 str1 is null (i.e. has length zero)
And finally, you can make comparisons between integer variables, as shown in Table 3. You'll see
a little later how to perform arithmetic.
Table 3: Integer operators
Operator Returns true if:
var1 -lt var2 var1 is less than var2
var1 -le var2 var1 is less than or equal to var2
var1 -eq var2 var1 is equal to var2
var1 -ne var2 var1 is not equal to var2
var1 -gt var2 var1 is greater than var2
var1 -ge var2 var1 is greater than or equal to var2
The for Statement
The for statement provides looping within scripts. However, bash's for statement is quite
different from that found in C, Perl and similar languages, which surprises novice shell scripters.
The syntax of the for statement is:
for varname [in list]
do
statement
...
done
The statements in the loop will probably use $varname in some way. The effect of this statement is
to substitute each value in list in turn, into the variable varname, and perform the body of the
loop. The list can actually be a wildcard, which the shell will automatically expand into a list of
matching filenames, and you can see an example of this in the directory size listing command above.
case
The case statement is essentially a multi-way branch, rather like an if . . ifelif . . . else . . .
fi chain. It is particularly common in the SysVInit scripts which are used to start, stop, restart
and reload daemons on distributions like Red Hat and Mandrake. The basic syntax of the case
statement is:
case varname in
value1)
statements
;;
value2)
statements
;;
*)
statements
;;
esac
You can find lots of examples in the / etc/rc.d/init.d or /etc/init.d directory on your system, and
the example at the end of this article.
while & until
More conventional are the while and until looping constructs. The syntax for while is:
while COMMANDS ; do
COMMANDS
done
Here is an example - a simple script that adds up the values of all integers between 1 and 100:
Listing 1: A shell script to sum the integers between 1 and 100:
#!/bin/bash
# Simple script to demonstrate while and arithmetic
count=0
sum=0
while [ $count -lt 101 ] ; do
sum=$(( $sum + $count ))
count=$(( $count + 1 ))
done
echo "Sum = $sum"
This example also shows one way of doing arithmetic in shell scripts, with the $(( expression ))
construct.
The until statement is similar, except that it continues looping as long as the test condition
fails. I find this quite useful at the command line, as a way of waiting for certain events to occur
- for example, waiting for a network link to come up:
until ping -c 1 192.168.170.1; do sleep 60; done; ssh 192.168.170.1
shift
Another statement that is useful in connection with loops is shift. This works on Linux pretty much
like it does on DOS/Windows - it shifts the positional parameters (except $0) down by one, so that
the old value of $1 is lost and its new value is $2, the new value of $2 is $3, and so on. This
allows one-by-one processing of an arbitrarily long list of arguments. Here is an example that
demonstrates shift, along with the until statement:
Listing 2: A script to demonstrate shift.
#!/bin/bash
until [ -z $1 ] ; do
echo $1
shift
done
This simple script just echoes its arguments, each on a separate line.
Let's finish up with an example. This short script works to remind you to do things at some time
(between 1 and 999 minutes) in the future, and illustrates many of the techniques previously
discussed:
* Integer operator ( -lt )
* Definition and calling of a function
* A for loop, if statement and more integer operators ( -eq)
* A case statement
Listing 3: A short script to set a reminder timer
#!/bin/bash
# Simple reminder shell script
if [ $# -lt 2 ] ; then
echo "usage: $0 minutes message"
exit 1
fi
alarm()
{
sleep ${delay}m
echo -n $msg
for i in 1 2 3 4 5 6 7 8 9 ; do
if [ $i -eq 2 -o $i -eq 4 -o $i -eq 6 -o $i -eq 8 ] ; then
sleep 1
else
echo -n -e \\a
fi
done
}
delay=$1
msg=$2
case $1 in
[0-9] | [0-9][0-9] | [0-9][0-9][0-9] )
alarm &
;;
*)
echo 'usage: minutes value must be in range 0 to 999'
;;
esac