由于工作上的需要,花了点时间,研究了一下eval和exec这两个shell内建特殊的命令。因为用的不是很多,所以还是有一点比较隐晦的。。


1.eval                  

    该命令是bashshell中内建的一个命令,相比其他的命令来说还是有一点的难度。该命令后面所跟的内容都认为是参数,但是会两次扫描其参数,第一次扫描会将参数中的变量进行替换,第二
次扫描会将后面的参数当作一个shell中的命令组合来执行命令。其常用的情况是对变量的处理
下面看几个例子:


案例1:直接组合命令
[root@test3 shellscripts]# eval ls -l /home/shellscripts/
total 8
-rwxr-xr-x 1 root root 71 Sep  9 13:29 1.sh
-rwxr-xr-x 1 root root 83 Sep  9 13:17 test.sh
[root@test3 shellscripts]# ls -l /home/shellscripts/
total 8
-rwxr-xr-x 1 root root 71 Sep  9 13:29 1.sh
-rwxr-xr-x 1 root root 83 Sep  9 13:17 test.sh
[root@test3 shellscripts]#
从上面的信息可以看出,当eval后面直接跟原生参数时,直接当作命令来执行,此时的执行结果,跟在shell中直接执行命令没有任何区别

案例2:替换变量
写如下一个脚步:
[root@test3 shellscripts]# cat 1.sh
#!/bin/bash
dirpath=/home/shellscripts
cmd="ls -l $dirpath | awk -F ' ' '{print \$9}'"
eval $cmd
[root@test3 shellscripts]#
[root@test3 shellscripts]# ./1.sh

1.sh
test.sh
[root@test3 shellscripts]# ls -l /home/shellscripts/ | awk -F ' ' '{print $9}'

1.sh
test.sh
[root@test3 shellscripts]#

从上面的信息可以看出最终的执行结果和在命令行直接执行命令的结果是一致的,那也就是说eval执行$cmd的时候,第一次先将变量$cmd用其值替换了,第二次将变量$cmd的值直接在shell中以命令的形式来执行。

注:在上面的脚本中,相信你应该能注意到\$9,为什么要加个转意字符(\),如果不加的话会被当作位置变量进行替换

案例3:可以执行任何值为命令组合的变量
先看例子:
[root@test3 shellscripts]# cat 1.sh
#!/bin/bash
dirpath=/home/shellscripts
cmd="ls -l $dirpath | awk -F ' ' '{print \$9}'"
#eval $cmd     /注释掉该行
$cmd
[root@test3 shellscripts]#

上面的脚本中,注释掉了eval所在行,直接调用$cmd变量,看起来变量cmd的值是一个shell中的命令组合,似乎应该可以正常运行,好运行一下:
[root@test3 shellscripts]# ./1.sh
ls: cannot access |: No such file or directory
ls: cannot access awk: No such file or directory
ls: cannot access ': No such file or directory
ls: cannot access ': No such file or directory
ls: cannot access '{print: No such file or directory
ls: cannot access $9}': No such file or directory
/home/shellscripts:
total 8
-rwxr-xr-x 1 root root 103 Sep  9 13:52 1.sh*
-rwxr-xr-x 1 root root  83 Sep  9 13:17 test.sh*
[root@test3 shellscripts]#

额,看到没,报错了,除了识别了ls -l $dirpath的部分 ,管道符之后的内容一概为识别。但是从案例2中,可知使用eval $cmd的时候,就可以很好的执行。

从案例2和案例3的对比,可以知道,在实际使用中,可以将任意组合的命令赋值给一个变量,然后在需要的位置通过eval $variable来执行这个命令。试想如果你遇到了一个很复杂的命令组合,
可能在多个地方需要执行,此时使用eval的功能就很方便。当然,有人可能会说可以使用函数,但是函数只有在调用的时候才会生效,且每次调用都要到内存中执行一遍,变量一旦赋值,直到变量的生命周期结束都会存在内层中供其调用。


案例4:变量替换赋值
看例子:
创建一个脚步如下:
[root@test3 shellscripts]# cat 2.sh
#!/bin/bash
x=100
y=x
eval echo \$$y
eval $y=50
echo $x
eval echo \$$y
[root@test3 shellscripts]#

执行脚步看结果:
[root@test3 shellscripts]# ./2.sh
100
50
50
[root@test3 shellscripts]#

分析:从上面的结果看,脚步返回了三个值。脚本运行时,先对x变量进行赋值,然后对y变量进行赋值,接着执行eval echo \$$y,返回了一个100,到这里还可以理解。接着执行eval $y=50,然
后尝试着输出$x的值,发现输出了一个50。一开始x变量的值是100,这里却变成了50 why?原因就在eval $y=50这句话,这句话相当于一个变量赋值,$y替换成x而将50赋值给了x变量,因此x
变成了50,而最后一句话再次执行eval \$$y验证其结果,发现确实变成了50。。。


2.exec
    exec也是shell内建的一个命令。类似与eval,source,但是不同的是exec执行后面的命令后会替换当前的shell进程,而前两者不会。。关于exec的用法确实有点特殊,可能编程的人士会对
其比较熟悉,这里介绍三种比较常见的用法;

用法一:用来分离执行脚本,并退出子脚本的shell进程
主脚本:
[root@test3 shellscripts]# cat main.sh
#!/bin/bash
bash /home/shellscripts/3.sh
ls -al
[root@test3 shellscripts]#

子脚本:
[root@test3 shellscripts]# cat 3.sh
#!/bin/bash
ls -l /etc | head -n 5
exec echo "this is a test"
echo "hello world"
[root@test3 shellscripts]#

执行主脚本,看结果:
[root@test3 shellscripts]# ./main.sh
total 1252
-rw-r--r--.  1 root root     44 Sep  5 18:13 adjtime
-rw-r--r--.  1 root root   1512 Jan 12  2010 aliases
-rw-r--r--.  1 root root  12288 Sep  2 09:24 aliases.db
drwxr-xr-x.  2 root root   4096 Sep  2 11:57 alternatives
this is a test
total 44
drwxr-xr-x  2 root root 4096 Sep  9 15:51 .
drwxr-xr-x. 3 root root 4096 Sep  9 14:19 ..
-rwxr-xr-x  1 root root  103 Sep  9 13:52 1.sh
-rwxr-xr-x  1 root root   71 Sep  9 14:02 2.sh
-rwxr-xr-x  1 root root   81 Sep  9 15:51 3.sh
-rwxr-xr-x  1 root root   92 Sep  9 15:43 4.sh
-rwxr-xr-x  1 root root   31 Sep  9 15:16 5.sh
-rwxr-xr-x  1 root root   72 Sep  9 15:43 6.sh
-rw-r--r--  1 root root   41 Sep  9 15:12 file
-rwxr-xr-x  1 root root   49 Sep  9 14:32 main.sh
-rwxr-xr-x  1 root root   83 Sep  9 13:17 test.sh
[root@test3 shellscripts]#

分析:
    从上面的脚本输出信息可以看到成功调用了3.sh脚本,但是你会发现3.sh脚本中的最后一行没有执行,因为没有输出内容。这是因为在输出了exec后面的命令执行结果后,就清除了3.sh脚步
打开的子shell进程,而后回到主shell进程继续执行后面的内容。。

这个特性似乎看起来没多大用处,有人说直接写在一个脚步里好了。但是对于复杂的环境,避免干扰,或许你需要这样的用法。。

用法二:用来设置描述符重定向输入文件内容
脚本如下:
[root@test3 shellscripts]# cat 4.sh
#!/bin/bash
exec 3</home/shellscripts/file
while read -u 3 str
do
echo $str
done
exec 3<&-

创建/home/shellscripts/file文件:
[root@test3 shellscripts]# cat file
hello world haha a b c
liuxueming
liwei
[root@test3 shellscripts]#

执行脚本,看结果:
[root@test3 shellscripts]# ./4.sh
hello world haha a b c
liuxueming
liwei
[root@test3 shellscripts]#

从上面的执行结果看,正常的输出了文本的内容,且按照while循环的次序输出。这里要解释的是文件描述符。exec可以实现文件描述符的重定向,将某个文件描述符关联到某个文件,以后对此
文件描述符的操作就是对该文件的操作,这也符合linux系统文件描述符的定义。例如上面的脚步将3重定向输入到/home/shellscripts/file文件,这样在下面就可以使用read -u 3来指定该描述
符,否则read后面不可以直接添加文件。当调用完成后,还要关闭文件描述符,例如上面的exec 3<&-

当然这里还可以使用另外一种方法:
while read str
do
echo $str
done < /home/shellscripts/file




用法三:用来设置描述符重定向输出内容至文件
看实验:
[root@test3 fd]# pwd
/dev/fd
[root@test3 fd]# ls
0  1  2  255
[root@test3 fd]# exec 4>/home/shellscripts/test
[root@test3 fd]# ls -l /etc | head -n 5 > 4
[root@test3 fd]# exec 4>&-
[root@test3 fd]# cat /home/shellscripts/test
total 1252
-rw-r--r--.  1 root root     44 Sep  5 18:13 adjtime
-rw-r--r--.  1 root root   1512 Jan 12  2010 aliases
-rw-r--r--.  1 root root  12288 Sep  2 09:24 aliases.db
drwxr-xr-x.  2 root root   4096 Sep  2 11:57 alternatives

看到没,重定向输出到文件描述符4的内容,都被输出到文件:/home/shellscripts/test中了。。。


结束!!!

    笨蛋的技术------不怕你不会!!!