Getting Started with Shell Scripting

Building a script 

  There are many different shells in Linux, but usually we use bash (bourne again shell) for shell programming because bash is free and easy to use. Therefore, the scripts provided by the author in this article use bash (but in most cases, these scripts can also be run in the bourne shell, the older sister of bash). 
  Like other languages, we write our shell programs by using any text editor, such as nedit, kedit, emacs, vi 
  , etc. 
  The program must start with the following line (must be on the first line of the file): 
#!/bin/sh 
  The symbol #! is used to tell the system that the parameters following it are the program used to execute the script. In this example we use /bin/sh to execute the script. 
  When the script is edited, if the script is to be executed, it must also be made executable. 
  To make your script executable: 
chmod +x filename 
  You can then execute your script by typing: ./filename . 
Comments 
  In shell programming, sentences beginning with # indicate comments until the end of the line. We sincerely recommend that you use annotations in your programs. If you use comments, you can understand in a short time what the script does and how it works, even if you haven't used the script for a long time. 
Variables 
  In other programming languages ​​you have to use variables. In shell programming, all variables consist of strings, and you don't need to declare variables. To assign a value to a variable, you can write: 
variable name = value  To
  remove the variable value, add a dollar sign ($) in front of the variable: 

#!/bin/sh
#Assign the variable:
a="hello world"
# Now print the contents of variable a:
echo "A is:"
echo $a

 

 

  Type the above in your editor and save it as a file first. Then execute chmod +x first 
  to make it executable, and finally enter ./first to execute the script. 
  This script will output: 
A is: 
hello world 
  Sometimes variable names can be easily confused with other words, for example: 
num=2 
echo "this is the $numnd" 
  This doesn't print "this is the 2nd", it just prints "this is the 2nd" Prints "this is the" because the shell searches for the value of the variable numnd, but this variable has no value. We can use curly braces to tell the shell that we want to print the num variable: 
num=2 
echo "this is the ${num}nd" 
  This will print: this is the 2nd 
  There are many variables that are automatically set by the system, which will be displayed in The use of these variables is discussed later. 
  If you need to work with mathematical expressions then you need to use a program such as expr (see below). 
  In addition to the usual shell variables that are only valid within a program, there are also environment variables. Variables processed by the export keyword are called environment variables. We do not discuss environment variables because they are typically only used in login scripts. 
Shell commands and process control 
  Three types of commands can be used in shell scripts: 
1) Unix commands: 
  Although any unix commands can be used in shell scripts, there are still some relatively more commonly used commands. These commands are usually used for file and text operations. 
Common command syntax and functions 
  echo "some text": print the text content on the screen 
  ls: file list 
  wc -l filewc -w filewc -c file: Count the number of lines in the file Count the number of words in the file Count the number of characters in the file 
  cp sourcefile destfile: file copy 
  mv oldname newname : rename file or move file 
  rm file: delete file 
  grep 'pattern' file: search string in file for example: grep 'searchstring' file.txt 
  cut -b colnum file: specify to display range of file contents and output them to the standard output device. For example: output the 5th to 9th characters of each line cut -b5-9 file.txt Do not confuse with the cat command, these are two completely different commands 
  cat file.txt: output the contents of the file to the standard output device (screen) 
  file somefile: get the file type 
  read var: prompt the user for input and assign the input to the variable 
  sort file.txt: sort the lines in the file.txt file 
  uniq: delete lines and columns appearing in a text file eg: sort file.txt | uniq 
  expr: do math Example: add 2 and 3expr 2 "+" 3 
  find: search for files eg: search by filename find . -name filename -print 
  tee: output data to the standard output device (screen) and files such as: somecommand | tee outfile 
  basename file: return the file name without the path such as: basename /bin/tux will return tux 
  dirname file: return the path where the file is located such as: dirname /bin/tux will return /bin 
  head file: print the first few lines of a text file 
  tail file: print the last few lines  of a text file
  sed: Sed is a basic find and replace program. You can read text from standard input (such as a command pipe) and output the results to standard output (the screen). This command uses regular expressions (see Reference) to search. Not to be confused with wildcards in the shell. For example: replace linuxfocus with LinuxFocus: cat text.file | sed 's/linuxfocus/LinuxFocus/' > newtext.file 
  awk: awk is used to extract fields from text files. By default, the field delimiter is a space, other delimiters can be specified with -F. cat file.txt | awk -F, '{print $1 "," $3 }' Here we use, as the field separator, to print the first and third fields at the same time. If the content of the file is as follows: Adam Bor, 34, IndiaKerry Miller, 22, USA The output of the command is: Adam Bor, IndiaKerry Miller, USA 
2) Concepts: pipes, redirection and backtick 
  These are not system commands, but they are really important . 
  A pipe (|) takes the output of one command as the input of another. 
grep "hello" file.txt | wc -l 
  searches file.txt for lines containing "hello" and counts the number of lines. 
  Here the output of the grep command is used as the input of the wc command. Of course you can use multiple commands. 
  Redirection: Output the result of a command to a file instead of standard output (screen). 
  > Write file and overwrite old file 
  >> Append to end of file, keep old file contents. 
Backslashes 
 Use backslashes to pass the output of one command as a command-line argument to another command. 
  Command: 
find . -mtime -1 -type f -print 
  is used to find files modified in the past 24 hours (-mtime -2 means the past 48 hours). If you want to pack all found files, you can use this script: 
#!/bin/sh 
# The ticks are backticks (`) not normal quotes ('): 
tar -zcvf lastmod.tar.gz `find . -mtime -1 -type f -print` 
  3) The flow control 
  "if" expression executes the part after then if the condition is true: 
if ....; then 
  .... 
elif ....; then 
  . ... 
else 
  .... 
fi 
  In most cases, conditions can be tested using the test command. For example, you can compare strings, determine whether the file exists and whether it is readable, etc... 
  Usually " [ ] " is used to represent conditional tests. Note that spaces are important here. Be sure to space the square brackets. 
[ -f "somefile" ] : Determine whether it is a file 
[ -x "/bin/ls" ] : Determine whether /bin/ls exists and has executable permissions 
[ -n "$var" ] : Determine whether the $var variable is Has a value 
[ "$a" = "$b" ]: Determine whether $a and $b are equal. 
  Execute man test to view all types of test expressions that can be compared and judged. 
  Execute the following script directly: 
#!/bin/sh 
if [ "$SHELL" = "/bin/bash" ]; then 
 echo "your login shell is the bash (bourne again shell)" 
else 
 echo "your login shell is not bash but $SHELL" 
fi 
  variable $SHELL contains the name of the login shell, which we compared to /bin/bash. 
Shortcut Operators 
  Friends who are familiar with the C language may like the following expression: 
[ -f "/etc/shadow" ] && echo " 
  Here && is a shortcut operator that executes the statement on the right if the expression on the left is true. You can also think of it as the AND operation in logical operations. The above example means that "This computer uses shadow passwors" is printed if the /etc/shadow file exists. The same OR operation (||) is also available in shell programming. Here's an example: 
#!/bin/sh 
mailfolder=/var/spool/mail/james 
[ -r "$mailfolder" ]' '{ echo "Can not read $mailfolder" ; exit 1; } 
echo "$mailfolder has mail from:" 
grep "^From " $mailfolder 
  The script first checks if the mailfolder is readable. Prints the "From" line in the file if it is readable. If it is not readable or the operation is valid, the script exits after printing an error message. The problem here is that we have to have two commands: 
  - print an error message 
  - exit the program 
  We use curly braces to put the two commands together as a single command in the form of an anonymous function. The general functions will be mentioned below. 
  We can do anything with if expressions without the AND and OR operator, but it is much more convenient to use the AND OR operator. 
  case expressions can be used to match a given string, not a number. 
case ... in 
...) do something here ;; 
esac 
  Let's look at an example. The file command can identify the file type of a given file, for example: 
file lf.gz 
  this will return: 
lf.gz: gzip compressed data, deflated, original filename, 
last modified: Mon Aug 27 23:09:18 2001, os: Unix 
 We took advantage of this and wrote a script called smartzip that The script can automatically decompress bzip2, gzip and zip archives: 
#!/bin/sh 
ftype=`file "$1"` 
case "$ftype" in 
"$1: Zip archive"*) 
  unzip "$1" ;; 
"$1 : gzip compressed"*) 
  gunzip "$1" ;; 
"$1: bzip2 compressed"*) 
  bunzip2 "$1" ;; 
*) error "File $1 can not be uncompressed with smartzip";; 
esac 
  You may notice here that we use a special variable $1. This variable contains the value of the first parameter passed to the program. That is, when we run: 
smartzip articles.zip 
$1 is the string articles. 
  zip select expression is an extension of bash, especially good at interactive use. The user can choose from a different set of values. 
select var in ... ; do 
 break 
done 
.... now $var can be used .... 
Here is an example: 
#!/bin/sh 
echo "What is your favourite OS?" 
select var in "Linux" "Gnu Hurd" "Free BSD" "Other"; do 
    break 
done 
echo "You have selected $var" 
  Here is the result of running the script: 
What is your favourite OS? 
1) Linux 
2) Gnu Hurd 
3) Free BSD 
4) Other 
#? 1 
You have selected Linux 
  You can also use a loop expression like this in the shell: 
while ...; do 
.... 
done 
  while-loop will run until the expression tests true. will run while the expression that we test for is true. Keyword "break" Used to break out of loops. The keyword "continue" is used to skip to the next loop without executing the rest.
  The for-loop expression looks at a list of strings (strings separated by spaces) and assigns it to a variable: 
for var in ....; do 
 .... 
done 
  In the following example, will print ABC to on the screen: 

Copy the code The code is as follows:

#!/bin/sh 
for var in A B C ; do 
 echo "var is $var" 
done 


  Here's a more useful script showrpm that prints some RPM package statistics:

#!/bin/sh
# list a content summary of a number of RPM packages
# USAGE: showrpm rpmfile1 rpmfile2 ...
# EXAMPLE: showrpm /cdrom/RedHat/RPMS/*.rpm
for rpmpackage in $*; do
 if [ -r "$rpmpackage" ];then
  echo "=============== $rpmpackage =============="
  rpm -qi -p $rpmpackage
 else
  echo "ERROR: cannot read file $rpmpackage"
 be
done

 

 

  A second special variable, $*, appears here, which contains all the entered command-line parameter values. If you run showrpm openssh.rpm w3m.rpm webgrep.rpm 
  $* contains 3 strings, namely openssh.rpm, w3m.rpm and webgrep.rpm. 
Quotes 
  are used to expand wildcards before passing any arguments to the program and variables. The so-called expansion here means that the program will replace the wildcard (such as *) with the appropriate file name, and it will replace the variable with the variable value. To prevent the program from making this substitution, you can use quotation marks: let's look at an example, suppose there are some files in the current directory, two jpg files, mail.jpg and tux.jpg. 

#!/bin/sh 
echo *.jpg 
  This will print the result of "mail.jpg tux.jpg". 
  Quotes (single and double) will prevent this wildcard expansion: 
#!/bin/sh 
echo "*.jpg" 
echo '*.jpg' 
  This will print "*.jpg" twice. 
  Single quotes are more restrictive. It prevents any variable expansion. Double quotes prevent wildcard expansion but allow variable expansion. 
#!/bin/sh 
echo $SHELL 
echo "$SHELL" 
echo '$SHELL' 
  The result is: 
/bin/bash 
/bin/bash 
$SHELL 
  Finally, there is a way to prevent this expansion, and that is to use escape characters - backslashes: 
echo \*.jpg 
echo \$SHELL 
  This will output: 
*.jpg 
$SHELL 
Here 
  documents When passed to a command, here documents is a nice way to go. It's useful to write a helpful text for each script, so if we have the here documents we don't have to use the echo function to output line by line. A "Here document" begins with <<, followed by a string, which must also appear at the end of the here document. Here is an example where we rename multiple files and print help using here documents: 

Copy the code The code is as follows:

#!/bin/sh 
# we have less than 3 arguments. Print the help text: 
if [ $# -lt 3 ] ; then 
cat < 
ren -- renames a number of files using sed regular expressions 
USAGE: ren 'regexp' 'replacement' files... 
EXAMPLE: rename all *.HTM files in *.html: 
 ren 'HTM$' 'html' *.HTM 
HELP 
 exit 0 
fi 
OLD="$1" 
NEW="$2" 
# The shift command removes one argument from the list of 
# command line arguments. 
shift 
shift 
# $* contains now all the files: 
for file in $*; do 
  if [ -f "$file" ] ; then 
   newfile=`echo "$file"| sed "s/${OLD}/${NEW}/g"` 
   if [ -f "$newfile" ]; then 
    echo "ERROR: $newfile exists already" 
   else 
    echo "renaming $file to $newfile ..." 
    mv "$file" "$newfile" 
   fi 
  fi 
done 


  This is a slightly more complicated example. Let's discuss it in detail. The first if expression determines whether the input command line parameters are less than 3 (the special variable $# indicates the number of parameters included). If there are less than 3 input arguments, the help text is passed to the cat command, which then prints it on the screen. The program exits after printing the help text. If the input parameters are equal to or greater than 3, we assign the first parameter to the variable OLD and the second parameter to the variable NEW. Next, we use the shift command to remove the first and second parameters from the parameter list, so that the original third parameter becomes the first parameter of the parameter list $*. Then we start the loop and the list of command line arguments is assigned to the variable $file one by one. Then we judge whether the file exists, and if so, we use the sed command to search and replace to generate a new file name. Then assign the result of the command within the backslash to newfile. So we achieve our goal: get the old file name and the new file name. Then use the mv command to rename. 
Functions 
  If you have written some slightly more complex programs, you will find that the same code is probably used in several places in the program, and you will also find that it is much more convenient if we use functions. A function looks like this: 

Copy the code The code is as follows:

functionname() 

# inside the body $1 is the first argument given to the function 
# $2 the second ... 
body 


  您需要在每个程序的开始对函数进行声明。 

  下面是一个叫做xtitlebar的脚本,使用这个脚本您可以改变终端窗口的名称。这里使用了一个叫做help的函数。正如您可以看到的那样,这个定义的函数被使用了两次。 

复制代码代码如下:

#!/bin/sh 
# vim: set sw=4 ts=4 et: 
help() 

  cat < 
xtitlebar -- change the name of an xterm, gnome-terminal or kde konsole 
USAGE: xtitlebar [-h] "string_for_titelbar" 
OPTIONS: -h help text 
EXAMPLE: xtitlebar "cvs" 
HELP 
  exit 0 

# in case of error or if -h is given we call the function help: 
[ -z "$1" ] && help 
[ "$1" = "-h" ] && help 
# send the escape sequence to change the xterm titelbar: 
echo -e "33]0;$107" 



  在脚本中提供帮助是一种很好的编程习惯,这样方便其他用户(和您)使用和理解脚本。 
命令行参数 
  我们已经见过$* 和 $1, $2 ... $9 等特殊变量,这些特殊变量包含了用户从命令行输入的参数。迄今为止,我们仅仅了解了一些简单的命令行语法(比如一些强制性的参数和查看帮助的-h选项)。但是在编写更复杂的程序时,您可能会发现您需要更多的自定义的选项。通常的惯例是在所有可选的参数之前加一个减号,后面再加上参数值 (比如文件名)。 
  有好多方法可以实现对输入参数的分析,但是下面的使用case表达式的例子无遗是一个不错的方法。 

复制代码代码如下:

#!/bin/sh 
help() 

 cat < 
This is a generic command line parser demo. 
USAGE EXAMPLE: cmdparser -l hello -f -- -somefile1 somefile2 
HELP 
 exit 0 

while [ -n "$1" ]; do 
case $1 in 
  -h) help;shift 1;; # function help is called 
  -f) opt_f=1;shift 1;; # variable opt_f is set 
  -l) opt_l=$2;shift 2;; # -l takes an argument -> shift by 2 
  --) shift;break;; # end of options 
  -*) echo "error: no such option $1. -h for help";exit 1;; 
  *) break;; 
esac 
done 

echo "opt_f is $opt_f" 
echo "opt_l is $opt_l" 
echo "first arg is $1" 
echo "2nd arg is $2" 


  您可以这样运行该脚本: 
cmdparser -l hello -f -- -somefile1 somefile2 
  返回的结果是: 
opt_f is 1 
opt_l is hello 
first arg is -somefile1 
2nd arg is somefile2 
  这个脚本是如何工作的呢?脚本首先在所有输入命令行参数中进行循环,将输入参数与case表达式进行比较,如果匹配则设置一个变量并且移除该参数。根据unix系统的惯例,首先输入的应该是包含减号的参数。 
实例 
  一般编程步骤 
  现在我们来讨论编写一个脚本的一般步骤。任何优秀的脚本都应该具有帮助和输入参数。并且写一个伪脚本(framework.sh),该脚本包含了大多数脚本都需要的框架结构,是一个非常不错的主意。这时候,在写一个新的脚本时我们只需要执行一下copy命令: 
cp framework.sh myscript 
 然后再插入自己的函数。 
  让我们再看两个例子: 
  二进制到十进制的转换 
  脚本 b2d 将二进制数 (比如 1101) 转换为相应的十进制数。这也是一个用expr命令进行数学运算的例子: 

复制代码代码如下:

#!/bin/sh 
# vim: set sw=4 ts=4 et: 
help() 

 cat < 
b2h -- convert binary to decimal 
USAGE: b2h [-h] binarynum 
OPTIONS: -h help text 
EXAMPLE: b2h 111010 
will return 58 
HELP 
 exit 0 

error() 

  # print an error and exit 
  echo "$1" 
  exit 1 

lastchar() 

  # return the last character of a string in $rval 
  if [ -z "$1" ]; then 
    # empty string 
    rval="" 
    return 
  fi 
  # wc puts some space behind the output this is why we need sed: 
  numofchar=`echo -n "$1" | wc -c | sed 's/ //g' ` 
  # now cut out the last char 
  rval=`echo -n "$1" | cut -b $numofchar` 


chop() 

  # remove the last character in string and return it in $rval 
  if [ -z "$1" ]; then 
    # empty string 
    rval="" 
    return 
  fi 
  # wc puts some space behind the output this is why we need sed: 
  numofchar=`echo -n "$1" | wc -c | sed 's/ //g' ` 
  if [ "$numofchar" = "1" ]; then 
    # only one char in string 
    rval="" 
    return 
  fi 
  numofcharminus1=`expr $numofchar "-" 1` 
  # now cut all but the last char: 
  rval=`echo -n "$1" | cut -b 0-${numofcharminus1}` 

while [ -n "$1" ]; do 
case $1 in 
  -h) help;shift 1;; # function help is called 
  --) shift;break;; # end of options 
  -*) error "error: no such option $1. -h for help";; 
  *) break;; 
esac 
done 
# The main program 
sum=0 
weight=1 
# one arg must be given: 
[ -z "$1" ] && help 
binnum="$1" 
binnumorig="$1" 

while [ -n "$binnum" ]; do 
  lastchar "$binnum" 
  if [ "$rval" = "1" ]; then 
    sum=`expr "$weight" "+" "$sum"` 
  fi 
  # remove the last position in $binnum 
  chop "$binnum" 
  binnum="$rval" 
  weight=`expr "$weight" "*" 2` 
done 
echo "binary $binnumorig is decimal $sum" 


  该脚本使用的算法是利用十进制和二进制数权值 (1,2,4,8,16,..),比如二进制"10"可以这样转换成十进制: 
0 * 1 + 1 * 2 = 2 
  为了得到单个的二进制数我们是用了lastchar 函数。该函数使用wc –c计算字符个数,然后使用cut命令取出末尾一个字符。Chop函数的功能则是移除最后一个字符。 
  文件循环程序 
  或许您是想将所有发出的邮件保存到一个文件中的人们中的一员,但是在过了几个月以后,这个文件可能会变得很大以至于使对该文件的访问速度变慢。下面的脚本rotatefile 可以解决这个问题。这个脚本可以重命名邮件保存文件(假设为outmail)为outmail.1,而对于outmail.1就变成了outmail.2 等等等等... 

复制代码代码如下:

#!/bin/sh 
# vim: set sw=4 ts=4 et: 
ver="0.1" 
help() 

  cat < 
rotatefile -- rotate the file name 

USAGE: rotatefile [-h] filename 

OPTIONS: -h help text 
EXAMPLE: rotatefile out 
This will e.g rename out.2 to out.3, out.1 to out.2, out to out.1 
and create an empty out-file 
The max number is 10 
version $ver 
HELP 
  exit 0 


error() 

  echo "$1" 
  exit 1 

while [ -n "$1" ]; do 
case $1 in 
  -h) help;shift 1;; 
  --) break;; 
  -*) echo "error: no such option $1. -h for help";exit 1;; 
  *) break;; 
esac 
done 
# input check: 
if [ -z "$1" ] ; then 
error "ERROR: you must specify a file, use -h for help" 
fi 
filen="$1" 
# rename any .1 , .2 etc file: 
for n in 9 8 7 6 5 4 3 2 1; do 
  if [ -f "$filen.$n" ]; then 
    p=`expr $n + 1` 
    echo "mv $filen.$n $filen.$p" 
    mv $filen.$n $filen.$p 
  fi 
done 
# rename the original file: 
if [ -f "$filen" ]; then 
  echo "mv $filen $filen.1" 
  mv $filen $filen.1 
fi 
echo touch $filen 
touch $filen 


  这个脚本是如何工作的呢?在检测用户提供了一个文件名以后,我们进行一个9到1的循环。文件9被命名为10,文件8重命名为9等等。循环完成之后,我们将原始文件命名为文件1同时建立一个与原始文件同名的空文件。 
调试 
  最简单的调试命令当然是使用echo命令。您可以使用echo在任何怀疑出错的地方打印任何变量值。这也是绝大多数的shell程序员要花费80%的时间来调试程序的原因。Shell程序的好处在于不需要重新编译,插入一个echo命令也不需要多少时间。 
  shell也有一个真实的调试模式。如果在脚本"strangescript" 中有错误,您可以这样来进行调试: 
sh -x strangescript 
  这将执行该脚本并显示所有变量的值。 
  shell还有一个不需要执行脚本只是检查语法的模式。可以这样使用: 
sh -n your_script 
  这将返回所有语法错误。 
  我希望您现在可以开始写您自己的shell脚本,希望您玩得开心。

Guess you like

Origin http://10.200.1.11:23101/article/api/json?id=326775417&siteId=291194637