for;shell

http://docs.linuxtone.org/ebooks/C&CPP/c/ch31s02.html

Shell如何执行命令

执行交互式命令

用户在命令行输入命令后,一般情况下Shell会forkexec该命令,但是Shell的内建命令例外,执行内建命令相当于调用Shell进程中的一个函数,并不创建新的进程。以前学过的cdaliasumaskexit等命令即是内建命令,凡是用which命令查不到程序文件所在位置的命令都是内建命令,内建命令没有单独的man手册,要在man手册中查看内建命令,应该

$ man bash-builtins

本节会介绍很多内建命令,如exportshiftifeval[forwhile等等。内建命令虽然不创建新的进程,但也会有Exit Status,通常也用0表示成功非零表示失败,虽然内建命令不创建新的进程,但执行结束后也会有一个状态码,也可以用特殊变量$?读出。

习题

1、在完成“练习:实现简单的Shell”一节时也许有的读者已经试过了,在自己实现的Shell中不能执行cd命令,因为cd是一个内建命令,没有程序文件,不能用exec执行。现在请完善该程序,实现cd命令的功能,用chdir(2)函数可以改变进程的当前工作目录。

2、思考一下,为什么cd命令要实现成内建命令?可不可以实现一个独立的cd程序,例如/bin/cd,就像/bin/ls一样?

执行脚本

首先编写一个简单的脚本,保存为script.sh

例 31.1. 简单的Shell脚本

#! /bin/sh

cd ..
ls

Shell脚本中用#表示注释,相当于C语言的//注释。但如果#位于第一行开头,并且是#!(称为Shebang)则例外,它表示该脚本使用后面指定的解释器/bin/sh解释执行。如果把这个脚本文件加上可执行权限然后执行:

$ chmod +x script.sh
$ ./script.sh

Shell会fork一个子进程并调用exec执行./script.sh这个程序,exec系统调用应该把子进程的代码段替换成./script.sh程序的代码段,并从它的_start开始执行。然而script.sh是个文本文件,根本没有代码段和_start函数,怎么办呢?其实exec还有另外一种机制,如果要执行的是一个文本文件,并且第一行用Shebang指定了解释器,则用解释器程序的代码段替换当前进程,并且从解释器的_start开始执行,而这个文本文件被当作命令行参数传给解释器。因此,执行上述脚本相当于执行程序

$ /bin/sh ./script.sh

以这种方式执行不需要script.sh文件具有可执行权限。再举个例子,比如某个sed脚本的文件名是script,它的开头是

#! /bin/sed -f

执行./script相当于执行程序

$ /bin/sed -f ./script.sh

以上介绍了两种执行Shell脚本的方法:

$ ./script.sh
$ sh ./script.sh

这两种方法本质上是一样的,执行上述脚本的步骤为:

图 31.1. Shell脚本的执行过程

Shell脚本的执行过程

  1. 交互Shell(bashfork/exec一个子Shell(sh)用于执行脚本,父进程bash等待子进程sh终止。

  2. sh读取脚本中的cd ..命令,调用相应的函数执行内建命令,改变当前工作目录为上一级目录。

  3. sh读取脚本中的ls命令,fork/exec这个程序,列出当前工作目录下的文件,sh等待ls终止。

  4. ls终止后,sh继续执行,读到脚本文件末尾,sh终止。

  5. sh终止后,bash继续执行,打印提示符等待用户输入。

如果将命令行下输入的命令用()括号括起来,那么也会fork出一个子Shell执行小括号中的命令,一行中可以输入由分号;隔开的多个命令,比如:

$ (cd ..;ls -l)

和上面两种方法执行Shell脚本的效果是相同的,cd ..命令改变的是子Shell的PWD,而不会影响到交互式Shell。然而命令

$ cd ..;ls -l

则有不同的效果,cd ..命令是直接在交互式Shell下执行的,改变交互式Shell的PWD,然而这种方式相当于这样执行Shell脚本:

$ source ./script.sh

或者

$ . ./script.sh

source或者.命令是Shell的内建命令,这种方式也不会创建子Shell,而是直接在交互式Shell下逐行执行脚本中的命令。

习题 

1、解释如下命令的执行过程:

$ (exit 2)
$ echo $?




 http://qujunorz.blog.51cto.com/6378776/1386340

今晚在《高级bash+脚本编程指南》中看到使用for和while循环语句赋值变量的脚本,觉得很有创意。。

 

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
从循环的输出中产生一个变量
################################Start
Script#######################################
#!/bin/bash
# csubloop.sh: 从循环的输出中产生一个变量.
3
 4 variable1=`for in 1 2 3 4 5
do
echo -n "$i" #  对于这里的命令替换来说
done#+ 这个'echo'命令是非常关键的.
8
 echo "variable1 = $variable1"   # variable1 = 12345
10
11
12 i=0
13 variable2=`while "$i" -lt 10 ]
14 do
15 echo -n "$i" # 再来一个, 'echo'是必须的.
16 let "i += 1" # 递增.
17 done`
18
19 echo "variable2 = $variable2"   # variable2 = 0123456789

 

想起之前想要在shell脚本中去循环获取脚本参数$1、$2、$3、$4。。。

 

方法1:使用shift,shift命令重新分配位置参数,其实就是向左移动一个位置.
$1 <--- $2, $2 <--- $3, $3 <--- $4, 等等.即可以:

1
2
3
4
5
6
for ((i=0;i<=$#;i++))
do
a=$1
shift
echo $a
done

 注意:$0脚本名是不会被移动的。

方法2:$@赋值给一个变量,然后再循环读取,貌似比方法1好点:

1
2
3
4
for in $@
do
echo $i
done

方法3:使用eval,两次变量替换:

1
2
3
4
5
for ((i=1;i<=$#;i++))
do
eval b=\$$i
echo $b
done

 

本文出自 “hiubuntu” 博客,请务必保留此出处http://qujunorz.blog.51cto.com/6378776/1386340

 

 

http://zhidao.baidu.com/question/406777325.html

#!/bin/bash

for((j=1;j<=5;j++))
do
m=80*$j
echo $m
done
运行后出来的结果是80*1
80*2
80*3
80*4
80*5
为什么会这样,怎样改才能让m把具体值算出来,得到输出
80
160
240
320
400



#!/bin/bash
for((j=1;j<=5;j++))
do
m=$[80*$j]
echo $m
done
如此,即可




http://www.cnblogs.com/stephen-liu74/archive/2011/12/26/2272814.html
七、非直接引用变量:

      在Shell中提供了三种为标准(直接)变量赋值的方式:
      1. 直接赋值。
      2. 存储一个命令的输出。
      3. 存储某类型计算的结果。
      然而这三种方式都是给已知变量名的变量赋值,如name=Stephen。但是在有些情况下,变量名本身就是动态的,需要依照运行的结果来构造变量名,之后才是为该变量赋值。这种变量被成为动态变量,或非直接变量。
      /> cat > test7.sh
      #!/bin/sh
      work_dir=`pwd`
      #1. 由于变量名中不能存在反斜杠,因此这里需要将其替换为下划线。
      #2. work_dir和file_count两个变量的变量值用于构建动态变量的变量名。
      work_dir=`echo $work_dir | sed 's/\//_/g'`
      file_count=`ls | wc -l`
      #3. 输出work_dir和file_count两个变量的值,以便确认这里的输出结果和后面构建的命令名一致。
      echo "work_dir = " $work_dir
      echo "file_count = " $file_count
      #4. 通过eval命令进行评估,将变量名展开,如${work_dir}和$file_count,并用其值将其替换,如果不使用eval命令,将不会完成这些展开和替换的操作。最后为动态变量赋值。
      eval BASE${work_dir}_$file_count=$(ls $(pwd) | wc -l)
      #5. 先将echo命令后面用双引号扩住的部分进行展开和替换,由于是在双引号内,仅完成展开和替换操作即可。
      #6. echo命令后面的参数部分,先进行展开和替换,使其成为$BASE_root_test_1动态变量,之后在用该变量的值替换该变量本身作为结果输出。
      eval echo "BASE${work_dir}_$file_count = " '$BASE'${work_dir}_$file_count
      CTRL+D
      /> . ./test7.sh
      work_dir =  _root_test
      file_count =  1
      BASE_root_test_1 = 1
    
八、在循环中使用管道的技巧:

      在Bash Shell中,管道的最后一个命令都是在子Shell中执行的。这意味着在子Shell中赋值的变量对父Shell是无效的。所以当我们将管道输出传送到一个循环结构,填入随后将要使用的变量,那么就会产生很多问题。一旦循环完成,其所依赖的变量就不存在了。
      /> cat > test8_1.sh
      #!/bin/sh
      #1. 先将ls -l命令的结果通过管道传给grep命令作为管道输入。
      #2. grep命令过滤掉包含total的行,之后再通过管道将数据传给while循环。
      #3. while read line命令从grep的输出中读取数据。注意,while是管道的最后一个命令,将在子Shell中运行。
      ls -l | grep -v total | while read line
      do
          #4. all变量是在while块内声明并赋值的。
          all="$all $line"
          echo $line
      done
      #5. 由于上面的all变量在while内声明并初始化,而while内的命令都是在子Shell中运行,包括all变量的赋值,因此该变量的值将不会传递到while块外,因为块外地命令是它的父Shell中执行。
      echo "all = " $all
      CTRL+D
      /> ./test8_1.sh
      -rw-r--r--.  1 root root 193 Nov 24 11:25 outfile
      -rwxr-xr-x. 1 root root 284 Nov 24 10:01 test7.sh
      -rwxr-xr-x. 1 root root 108 Nov 24 12:48 test8_1.sh
      all =

      为了解决该问题,我们可以将while之前的命令结果先输出到一个临时文件,之后再将该临时文件作为while的重定向输入,这样while内部和外部的命令都将在同一个Shell内完成。
      /> cat > test8_2.sh
      #!/bin/sh
      #1. 这里我们已经将命令的结果重定向到一个临时文件中。
      ls -l | grep -v total > outfile
      while read line
      do
          #2. all变量是在while块内声明并赋值的。
          all="$all $line"
          echo $line
          #3. 通过重定向输入的方式,将临时文件中的内容传递给while循环。
      done < outfile
      #4. 删除该临时文件。
      rm -f outfile
      #5. 在while块内声明和赋值的all变量,其值在循环外部仍然有效。
      echo "all = " $all
      CTRL+D
      /> ./test8_2.sh
      -rw-r--r--.  1 root root   0 Nov 24 12:58 outfile
      -rwxr-xr-x. 1 root root 284 Nov 24 10:01 test7.sh
      -rwxr-xr-x. 1 root root 140 Nov 24 12:58 test8_2.sh
      all =  -rwxr-xr-x. 1 root root 284 Nov 24 10:01 test7.sh -rwxr-xr-x. 1 root root 135 Nov 24 13:16 test8_2.sh

      上面的方法只是解决了该问题,然而却带来了一些新问题,比如临时文件的产生容易导致性能问题,以及在脚本异常退出时未能及时删除当前使用的临时文件,从而导致生成过多的垃圾文件等。下面将再介绍一种方法,该方法将同时解决以上两种方法同时存在的问题。该方法是通过HERE-Document的方式来替代之前的临时文件方法。
      /> cat > test8_3.sh
      #!/bin/sh
      #1. 将命令的结果传给一个变量    
      OUTFILE=`ls -l | grep -v total`
      while read line
      do
          all="$all $line"
          echo $line
      done <<EOF
      #2. 将该变量作为该循环的HERE文档输入。
      $OUTFILE
      EOF
      #3. 在循环外部输出循环内声明并初始化的变量all的值。
      echo "all = " $all
      CTRL+D
      /> ./test8_3.sh
      -rwxr-xr-x. 1 root root 284 Nov 24 10:01 test7.sh
      -rwxr-xr-x. 1 root root 135 Nov 24 13:16 test8_3.sh
      all =  -rwxr-xr-x. 1 root root 284 Nov 24 10:01 test7.sh -rwxr-xr-x. 1 root root 135 Nov 24 13:16 test8_3.sh
    
九、自链接脚本:

      通常而言,我们是通过脚本的命令行选项来确定脚本的不同行为,告诉它该如何操作。这里我们将介绍另外一种方式来完成类似的功能,即通过脚本的软连接名来帮助脚本决定其行为。
      /> cat > test9.sh
      #!/bin/sh
      #1. basename命令将剥离脚本的目录信息,只保留脚本名,从而确保在相对路径的模式下执行也没有任何差异。
      #2. 通过sed命令过滤掉脚本的扩展名。
      dowhat=`basename $0 | sed 's/\.sh//'`
      #3. 这里的case语句只是为了演示方便,因此模拟了应用场景,在实际应用中,可以为不同的分支执行不同的操作,或将某些变量初始化为不同的值和状态。
      case $dowhat in
      test9)
          echo "I am test9.sh"
          ;;
      test9_1)
          echo "I am test9_1.sh."
          ;;
      test9_2)
          echo "I am test9_2.sh."
          ;;
      *)
          echo "You are illegal link file."
          ;;
      esac
      CTRL+D
      /> chmod a+x test9.sh
      /> ln -s test9.sh test9_1.sh
      /> ln -s test9.sh test9_2.sh
      /> ls -l
      lrwxrwxrwx. 1 root root   8 Nov 24 14:32 test9_1.sh -> test9.sh
      lrwxrwxrwx. 1 root root   8 Nov 24 14:32 test9_2.sh -> test9.sh
      -rwxr-xr-x. 1 root root 235 Nov 24 14:35 test9.sh
      /> ./test9.sh
      I am test9.sh.
      /> ./test9_1.sh
      I am test9_1.sh.
      /> ./test9_2.sh
      I am test9_2.sh.

十、Here文档的使用技巧:

      在命令行交互模式下,我们通常希望能够直接输入更多的信息,以便当前的命令能够完成一定的自动化任务,特别是对于那些支持自定义脚本的命令来说,我们可以将脚本作为输入的一部分传递给该命令,以使其完成该自动化任务。
      #1. 通过sqlplus以dba的身份登录Oracle数据库服务器。
      #2. 在通过登录后,立即在sqlplus中执行oracle的脚本CreateMyTables和CreateMyViews。
      #3. 最后执行sqlplus的退出命令,退出sqlplus。自动化工作完成。
      /> sqlplus "/as sysdba" <<-SQL
      > @CreateMyTables
      > @CreateMyViews
      > exit
      > SQL
         
十一、获取进程的运行时长(单位: 分钟):

      在进程监控脚本中,我们通常需要根据脚本的参数来确定有哪些性能参数将被收集,当这些性能参数大于最高阈值或小于最低阈值时,监控脚本将根据实际的情况,采取预置的措施,如邮件通知、直接杀死进程等,这里我们给出的例子是收集进程运行时长性能参数。
      ps命令的etime值将给出每个进程的运行时长,其格式主要为以下三种:
      1. minutes:seconds,如20:30
      2. hours:minutes:seconds,如1:20:30
      3. days-hours:minute:seconds,如2-18:20:30
      该脚本将会同时处理这三种格式的时间信息,并最终转换为进程所流经的分钟数。
      /> cat > test11.sh
      #!/bin/sh
      #1. 通过ps命令获取所有进程的pid、etime和comm数据。
      #2. 再通过grep命令过滤,只获取init进程的数据记录,这里我们可以根据需要替换为自己想要监控的进程名。
      #3. 输出结果通常为:1 09:42:09 init
      pid_string=`ps -eo pid,etime,comm | grep "init" | grep -v grep`
      #3. 从这一条记录信息中抽取出etime数据,即第二列的值09:42:09,并赋值给exec_time变量。
      exec_time=`echo $pid_string | awk '{print $2}'`
      #4. 获取exec_time变量的时间组成部分的数量,这里是3个部分,即时:分:秒,是上述格式中的第二种。
      time_field_count=`echo $exec_time | awk -F: '{print NF}'`
      #5. 从exec_time变量中直接提取分钟数,即倒数第二列的数据(42)。
      count_of_minutes=`echo $exec_time | awk -F: '{print $(NF-1)}'`
    
      #6. 判断当前exec_time变量存储的时间数据是属于以上哪种格式。
      #7. 如果是第一种,那么天数和小时数均为0。
      #8. 如果是后两种之一,则需要继续判断到底是第一种还是第二种,如果是第二种,其小时部分将不存在横线(-)分隔符分隔天数和小时数,否则需要将这两个时间字段继续拆分,以获取具体的天数和小时数。对于第二种,天数为0.
      if [ $time_field_count -lt 3 ]; then
          count_of_hours=0
          count_of_days=0
      else
          count_of_hours=`echo $exec_time | awk -F: '{print $(NF-2)}'`
          fields=`echo $count_of_hours | awk -F- '{print NF}'`
          if [ $fields -ne 1 ]; then
              count_of_days=`echo $count_of_hours | awk -F- '{print $1}'`
              count_of_hours=`echo $count_of_hours | awk -F- '{print $2}'`
          else
              count_of_days=0
          fi
      fi
      #9. 通过之前代码获取的各个字段值,计算出该进程实际所流经的分钟数。
      #10. bc命令是计算器命令,可以将echo输出的数学表达式计算为最终的数字值。
      elapsed_minutes=`echo "$count_of_days*1440+$count_of_hours*60+$count_of_minutes" | bc`
      echo "The elapsed minutes of init process is" $elapsed_minutes "minutes."
      CTRL+D
      /> ./test11.sh

      The elapsed minutes of init process is 577 minutes.
    
十二、模拟简单的top命令:
    
      这里用脚本实现了一个极为简单的top命令。为了演示方便,我们在脚本中将很多参数都写成硬代码,你可以根据需要更换这些参数,或者用更为灵活的方式替换现有的实现。
      /> cat > test12.sh
      #!/bin/sh
      #1. 将ps命令的title赋值给一个变量,这样在每次输出时,直接打印该变量即可。
      header=`ps aux | head -n 1`
      #2. 这里是一个无限循环,等价于while true
      #3. 每次循环先清屏,之后打印uptime命令的输出。
      #4. 输出ps的title。
      #5. 这里需要用sed命令删除ps的title行,以避免其参与sort命令的排序。
      #6. sort先基于CPU%倒排,再基于owner排序,最后基于pid排序,最后再将结果输出给head命令,仅显示前20行的数据。
      #7. 每次等待5秒后刷新一次。
     while :
      do
          clear
          uptime
          echo "$header"
          ps aux | sed -e 1d | sort -k3nr -k1,1 -k2n | head -n 20
          sleep 5
      done
      CTRL+D    
      /> ./test12.sh
      21:55:07 up 13:42,  2 users,  load average: 0.00, 0.00, 0.00
      USER       PID %CPU %MEM    VSZ   RSS   TTY      STAT START   TIME   COMMAND
      root      6408     2.0      0.0   4740   932   pts/2    R+    21:45     0:00   ps aux
      root      1755     0.2      2.0  96976 21260   ?        S      08:14     2:08   nautilus
      68        1195     0.0      0.4   6940   4416    ?        Ss    08:13     0:00   hald
      postfix   1399    0.0      0.2  10312  2120    ?        S      08:13     0:00   qmgr -l -t fifo -u
      postfix   6021    0.0      0.2  10244  2080    ?        S      21:33     0:00   pickup -l -t fifo -u
      root         1       0.0      0.1   2828   1364    ?        Ss     08:12    0:02   /sbin/init
      ... ...

 

posted @ 2014-11-12 15:59  陳聽溪  阅读(135)  评论(0)    收藏  举报