Shell(十一):I/O重定向

  I/O重定向最常用的方法:管道,结合sed和awk等命令的用法;

  面向文件的I/O重定向,涉及文件标识符、I/O重定向符号及其用法、exec命令和代码块重定向。

1、管道

1.1、管道简介

  管道技术是Linux的一种基本的进程间通信技术,利用先进先出(First In First Out,FIFO)排队模型来指挥进程间的通信。对于管道,可以将它们当做是连接两个实体的一个单向连接器。

  Linux管道可用于应用程序之间、Linux命令之间,以及应用程序和Linux命令之间的通信,Shell编程主要是利用管道进行Linux命令之间的通信。

  Shell编程中管道符号是竖杠符号"|",命令之间利用管道进行通信的一般格式为:

command1 | command2 | ... | commandn 

  command1到commandn表示Linux的n个命令,这n个命令利用管道管道进行通信。command1的输出结果将直接显示在Shell上,当Shell遇到管道符"|"后,就将  command1的输出发送给command2,作为command2的输入。

  案例演示详情如下:

0

  列出/etc目录的文件列表,在文件列表中用grep命令查询与字符串vi匹配的行。

  管道也可用于多个命令进行通信,详情如下:

0

  统计/etc目录的文件列表中vi匹配的行数。命令执行流程如下:

0

  ls -l | grep vi | wc -l 命令实际上是在三个命令之间建立两根管道,第一个命令ls -l执行后产生的输出作为第二个命令grep vi的输入,第二个命令在管道输入下执行后产生的输出又作为第三个命令wc -l的输入,最后,第三个命令wc -l在管道输入下执行命令,并将产生的结果输出到Shell。

  整个过程是半双工通信,通信是单向的,两个命令之间的连接具体工作是由Linux内核来完成的。

1.2、cat和more命令

  cat和more命令是用来显示文件内容,基本格式如下:

cat [option] fileName
more [option] [+linenum] fileName

  cat命令的选项如下:

选项

含义

-a

显示文件的所有内容

-b

由1开始对所有输出的行数编号,但对空白行不编号

-E

每一行末显示"$"

-n

由 1 开始对所有输出行数编号

-s

当遇到有连续两行以上的空白行,就替换为一行空白行

-v

不可显示的字符(制表符、新行符合换页符除外)以可见的形式显示

  more命令的选项如下:

选项

含义

-num

一次显示的行数

-f

计算行数时,以实际的行数,而非自动换行过后的行数

-p

不以卷动的方式显示每一页,而是先清除屏幕后在显示内容那个

-c

先显示内容,再清除其他旧资料

-s

当遇到有连续两行以上的空白行,就替换为一行的空白行

-u

禁止显示强调符,即不显示下画线

+/

在每个文件显示前搜寻该字串(pattern),然后从该字串之后开始显示

+num

从第num行开始显示

  两个命令最大的区别在于:cat命令在显示文件时不提供分页功能,而more命令在显示超过一页的文件时提供了分页功能。

1.3、sed命令与管道

  sed和awk命令都是从文件读取输入数据,sed和awk命令都支持从管道获得输入数据。

  sed命令的标准格式是:

sed [选项] 'sed命令' 输入文件

  sed是对输入文件进行处理,当sed从管道读取输入数据时,命令中就没必要出现输入文件了,sed命令的格式就变为:

| sed [选项] 'sed命令'

  sed在管道符"|"之后出现,表示sed命令对管道输入的数据进行处理。

  sed用于处理Shell命令的输出,利用sed处理ls -l命令的输出结果,即ls -l命令的输出结果通过管道传给sed命令作为输入,sed -n '1,5p'表示打印ls -l命令结果的第1~5行。

0

  利用管道符同样可以对文件用sed命令进行编辑。

0

  第1条命令,用cat显示/etc/passwd文件,通过管道传给sed命令,实际上,sed从管道接收到的输入数据就是/etc/passwd文件的全部内容,sed查找并打印输入数据中包含root关键字的所有行,结果显示两条包含root的行。

  第1条命令等价于:

sed -n '/root/p' /etc/passwd

  第2条命令在第1条命令的基础上添加一个管道,对第1条命令所产生的结果继续用sed命令处理,查找并打印第1条命令结果中包含login关键字的行,结果变成一行,该行既包含root关键字,又包含login关键字。

1.4、awk命令与管道

  awk可以将管道输入作为输入数,并对其进行处理,当awk将管道作为输入时,awk命令同样要将输入文件去掉,命令格式变为:

| awk [-F 域分隔符] 'awk程序段'

  演示awk和管道结合处理字符串:

0

  第1条命令,利用awk内置字符串函数length计算str的字符串的长度,该命令的length是$0,因为str从管道赋给awk时,str字符串是awk的输入数据,str此时就相当于原awk命令中的输入文件,因此,$0表示输入数据的全域,即整个str字符串。

  第2条命令,仅将第1条命令的$0改为$1,表示计算str第1域的长度,由于awk默认以空格为域分隔符,所以,str的第1域为Speeding,长度为8。

  第3条命令,利用awk内置字符串函数substr抽取str字符串中第1~8个字符作为子串输出,相当于expr substr命令,该命令仍然以全域$0代表整个str字符串。

  第4条命令将抽取str字符串中第25个字符开始到最后的子串。并将$0改为$string。从结果可以看出,awk同样可以解析string变量名,实现substr抽取子串功能。

  将/etc/passwd第1域按字母排序后打印:

0

  该例将/etc/passwd第1域按字母排序后打印,awk命令先将域分隔符指定为冒号,然后打印第1域,将打印结果通过管道传输给sort命令,sort命令对第1域结果进行排序,然后输出,结果将/etc/passwd第1域,即用户按照字母排序后打印。

0

  getline函数将Shell命令的输出保存到变量中,awk再对该变量进行处理。本例中 ls /usr 命令的输出传给awk,awk再将awk打印出来。在awk的BEGIN字段中使用了while循环,循环内将ls /usr命令的结果逐个通过管道传给getline d命令,打印d变量,直到ls /usr命令的结果全部处理结束。

  在awk中,若要对Shell命令结果进行处理,必须将结果通过管道传给getline函数,若结果较多,则需要使用循环。

  查看文件系统剩余的空间容量,输出可用空间大于一个触发值的文件系统,可以将df -k命令的结果通过管道传给awk,awk通过判断第4域是否大于这一触发值而决定是否输出该文件空间,df -k命令可以列出Linux系统文件空间的详细信息,第4域是剩余空间量。

0

  该例输出可用空间大于1GB的文件系统,结果显示只有文件系统/dev/sda3满足条件,挂载点是Linux的 / 目录。

2、I/O重定向

  I/O重定向是一个过程,这个过程捕捉一个文件、命令、程序或脚本,甚至代码块(code block)的输出,然后把捕捉到的输出作为输入发送给另外一个文件、命令、程序或脚本。

2.1、文件标识符

  文件标识符(File Descriptor,FD)是从0开始到9结束的整数,指明了与进程相关的特定数据流的源。当Linux系统启动一个进程(该进程可能用于执行Shell命令)时,将自动为该进程打开三个文件:标准输入、标准输出和标准错误输出,分别由文件标识符0、1、2标识,该进程如果要打开其他的输入或输出文件,则从整数3开始标识。

  stdin、stdout、stderr和Shell命令的关系,Shell命令从标准输入读取输入数据,将输出送到标准输出,若该命令在执行过程中发生错误,则将错误信息输出到标准错误输出。

0

  将Shell命令的输出从标准输出复制一份到某个文件中,可以使用tee命令。

0

  who | tee output.txt 先将who命令的执行结果从管道传给tee命令,tee命令将who命令的标准输出复制到output.txt文件,因此,结果仍然输出who命令的标准输出,查看output.txt文件的内容后,证实output.txt文件确实保存了who命令的标准输出。

  who | tee output.txt命令的数据流向,tee命令产生的数据流向像英文字母T,将一个输出分为两个支流,一个到标准输出,另一个到某输出文件。

0

  tee命令还有一个选项-a,表示将Shell命令的输出追加到某个文件的末尾,tee -a命令的用法,命令 ps | tee -a output.txt将ps命令的标准输出追加到output.txt文件末尾,从结果可以看出,output首行是由who命令产生的输出,后面四行是ps命令产生的输出。

0

2.2、I/O重定向符号及其用法

  I/O重定向符号分为两类:基本I/O重定向符号和I/O重定向符号。

符号

含义

cmd1 | cmd2

管道符,将cmd1的标准输出作为cmd2的标准输入

> filename

将标准输出写到文件filename之中

< filename

将文件filename的内容读入到标准输入之中

>> filename

将标准输出写到文件filename中,若filename文件已存在,则将标准输出追加到filename已有内容之后

>|filename

强制将标准输出写到文件filename之中,即将filename文件覆盖掉

n>|filename

强制将FD为n的输出写到文件filename之中,即将filename文件覆盖掉

n>filename

将FD为n的输出写到文件filename之中

n<filename< span="">

将文件filename的内容读入到FD n 之中

n>>filename

将FD为n的输出写到文件filename之中,若filename文件已存在,则将FD为n的输出追加到filename已有内容之后

  管道符也是一种I/O重定向符号,newfile.txt被创建后,再用">>"符号在newfile后追加一些文本,将 /etc 目录中包含rc字符的文件名追加到newfile.txt之中,命令先列出 /etc 的所有文件名,将结果传给grep命令,grep查找与rc字符串匹配的文件名,将输出结果追加到newfile.txt。

0

  >> 符号改为 > 符号,执行它并观察 newfile.txt 的文本内容,从结果可以看到,newfile.txt中原来的文件被覆盖,换成 ls /etc | grep "rc" 命令的输出结果。

  由此可以总结出,>> 符号是将标准输出追加到已有文件的内容之后,而 > 符号则将文件的原有文本覆盖,然后写入标准输出的内容。

0

  >| 符号是强制覆盖文件的符号,若noclobber选项开启,表示不允许覆盖任何文件,而>|符号则可以不管noclobber选项的作用,强制将文件覆盖。

0

  第1条命令用set -o命令开启noclobber选项;

  第2条命令用>符号将 ls /etc | grep "rc.d" 命令的输出结果写入newfile,因>符号需要覆盖文件,因此,Shell报错"不能覆盖已存在的文件",说明noclobber选项确实起到了作用;

  第3条命令将第2条命令的>符号改为>|符号,执行成功,说明>|强制将newfile覆盖;

  第4条命令查看newfile文件的内容,表明newfile文件确实已被覆盖。

  n>>filename、n>|filename与n>filename都是将FD为n的文件重定向到filename文件之中,三者的区别实际上是 >>、>| 和 > 这三个符号的区别。

  < 是I/O重定向的输入符号,可将文件内容写到标准输入之中。

0

  第1条命令中wc开始执行时,将-l和 newfile.txt 作为两个输入参数,-l参数表示对行进行计数,newfile指明了需要行计数的文件名,wc打开 newfile.txt 文件,统计行数,并在Shell上打印出统计的行数和相应的文件名。

  第2条命令开始执行时,Shell识别出输入重定向符号<,并判断<符号后面的字符串表示重定向数据源文件的名称,Shell从命令行"去"掉 <newfile.txt,启动wc命令,将它的标准输入重定向到newfile.txt文件,并给wc命令传递一个参数-l,wc命令并不知道newfile.txt的存在,wc命令直观从标准输入中读取数据,结果只打印行数26,并不打印文件名称。< div="">

  第3条命令显示newfile文件的内容。

   <<delimiter 符号称为此处文档(Here-document),delimiter称为分界符,该符号表明:Shell将分界符delimiter之后直至下一个delimiter之前的所有内容作为输入,<<delimiter还有另一种形式:-<<delimiter,在 <<="" 符号加上一个符号,输入1文本行所有开头的"tab"键都会被删除,但是,开头的空格键不会被删除。<="" div="">

0

2.3、exec命令的用法

  exec命令可以通过文件标识符打开或关闭文件,也可将文件重定向到标准输入,及将标准输出重定向到文件。exec将标准输入重定向到文件,新建 execwap.sh 脚本,详情如下:

#!/bin/bash
# 使用exec将stdin重定向到文件案例

# FD 8是FD 0(标准输入)的副本,用于恢复 FD 0
exec 8<&0

# 将标准输入重定向到hfile
exec < hfile
# 读取hfile文件的第1行
read a
# 读取hfile文件的第2行
read b

echo "--------------"
echo $a
echo $b

echo "Close FD 8:"
# 0<&8:将FD 8复制到FD 0,FD 8是原来的标准输入,FD 0从FD 8中恢复原状
# 8<&-:关闭 FD 8,其他进程可以重复使用 FD 8
exec 0<&8 8<&-

# 在执行read命令时,read命令从键盘输入读取数据
echo -n "Pls.Enter Data:"
# 标准输入恢复成键盘输入
read c
echo $c

  exec.sh脚本演示如何将标准输入重定向到文件,以及如何通过其他FD的文件恢复标准输入。exec.sh脚本首先用 exec 8<& 0复制到FD 8,此时,exec命令同时打开FD 8 文件。然后,exec.sh 脚本用 exec <hfile将hfile赋值到标准输入,<符号是等价于0<符号的。< div="">

  read命令是用于接收用户输入的,即read命令原本从标准输入读取数据。当exec.sh脚本执行read时,每次读入hfile的一行,a和b变量分别保存了hfile的第1行和第2行。接着,exec.sh脚本将FD 8文件复制到FD 0,由于 FD 8保存了原来的 FD 0,因此,将FD 8赋值给FD 0 后,FD 0恢复原状了,并且 exec.sh脚本关闭FD 8文件,便于其他进程可以重复使用FD 8。最后,exec.sh脚本再次执行read命令以测试标准输入是否恢复原状。

  exec.sh脚本执行结果如下:

0

  exec将标准输出重定向到文件,新建名为 execout.sh 的脚本,详情如下:

 

#!/bin/bash
# stdout重定向到文件

# FD 8 是 FD 1(标准输入)的副本,用于恢复 FD 1
exec 8>&1

# 将标准输出重定向到out
exec > out

# 执行 data 和 df 命令,测试输出是否写入 temp 文件
echo "Output of data command"
date

echo "Output of df command"
df

# 1<&8: 将 FD 8 复制到 FD 1,FD 8是原来的标准输出,FD 1从FD 8中恢复原状
# 8<&-; 关闭FD 8,其他进程可以重复使用FD 8
exec 1>&8 8>&-

# 再次执行date和df命令,测试标准输出是否恢复原状
echo "--------------------------------"
echo "Output of date command"
date
echo "Output of df command"
df

  执行结果如下:

0

  查看输出的out文件详情:

0

  高级I/O重定向符号机及其意义:

符号

含义

n>&m

将FD为m的输出复制到FD为n的文件

n<&m

将FD为m的输入复制到FD为n的文件

n>&-

关闭FD为n的输出

n<&-

关闭FD为n的输入

&>file

将标准输出和标准错误输出重定向到文件

  &>可以同时将标准输出和标准错误输出重定向到文件,新建 execerr.sh 脚本,详情如下:

#!/bin/bash
# stdout和stderr重定向到文件

# 将 FD 1(即标准输出)复制到FD 8,FD 2(即标准错误输出)复制到 FD 9
exec 8>&1 9>&2

# 将标准输出和标准错误输出重定向到log文件
exec &> log

# 执行 ls xxx* 和 date 命令,测试输出和错误输出是否写入log文件
ls xxx*     # 将会出现错误输出
date

# 恢复标准输出和标准错误输出,并关闭FD 8和 FD 9
exec 1>&8 2>&9 8<&- 9<&-

# 再次执行 ls xxx* 和 date 命令,测试输出和错误输出是否恢复原状
echo "---------------------------"
echo "Close FD 8 and 9: "
ls xxx*
date

  执行结果如下:

0

2.4、代码块重定向

  代码重定向是指在代码块内将标准输入或标准输出重定向到文件,而在代码块之外还是保留默认状态,即 代码块重定向是指对标准输入或标准输出的重定向且只在代码块内有效。

  代码块输入重定向符号是<,输出重定向符号是>。

  while循环的重定向,新建 rewhile.sh 脚本,详情如下:

#!/bin/bash
# while循环的重定向

# 将 ls /etc 的结果写到logg文件中
ls /etc > logg

# 搜索logg文件中与rc.d所匹配的行,输出匹配行的行数
while [ "$filename" != "rc.d" ]
do
    # 按行读取logg内容
    read filename
    
    let "count+=1"
done < logg

echo "$count times read"

# 测试循环体外面的标准输入是否被重定向
echo -n "------ Pls. Input Data: ------"
read test
echo $test

  rewhile.sh脚本首先将 ls /etc的结果写到logg文件,logg文件中按行保存了/etc目录下所有的文件名,rewhile.sh脚本需要查询logg文件中与rc.d所匹配的行,并输出匹配行的行数,因此,rewhile.sh使用了while循环,该循环在结尾处done关键字的后面利用<符号将标准输入重定向到logg文件,然后用read命令按行读取logg内容,保存在filename变量中,while循环测试条件判断filename变量的值是否等于关键字rc.d,若该测试条件不满足,则继续读取下一行,计数器count增加1,一旦测试条件满足,则跳出while循环,输出count变量值。

  rewhile.sh 执行结果如下:

0

  for循环重定向对logg文件rc.d关键字的查找,新建 refor.sh 脚本,详情如下:

#!/bin/bash

# ls /etc的结果保存到 logg
ls /etc > logg

# 计算logg文件最大的行数,并赋给maxline
maxline=$(wc -l < logg)

# 搜索logg文件中与rc.d所匹配的行,输出匹配行的行数
# 利用 seq 命令产生循环参数,相当于 for filename in 1,2,3,...,maxline
for filename in `seq $maxline`        
do
    read filename    # 按行读取logg中的数据
    
    # for循环,if语句指定跳出循环的条件
    if [ "$filename" = "rc.d" ]
    then
        break
    else
        let "count += 1"
    fi
done < logg    # 将标准输入重定向到 logg 文件

echo "$count times read"

# 测试循环体外面的标准输入是否被重定向
echo -n "--- Pls. Input Data:  ---"
read test
echo $test

  执行结果如下:

0

  代码块输出重定向与输入重定向类似,只是将该代码块内的输出写入文件中,符号改成输出重定向符号>。

  使用if/then结构的输出重定向,新建 reif.sh 脚本,详情如下:

#!/bin/bash
# if/then结构的输出重定向

if [ -z "$1" ]
then
    echo "Positional Parameter is NULL"
fi > logg

# 测试if/then结构之外的标准输出是否被重定向
echo "------------ normal stdout -------------"

  reif.sh脚本将if/then结构的输出重定向到logg文件,if/then结构的测试条件是位置参数$1是否为空,如果$1为空,打印"Positional Parameter is NULL",打印的内容写入logg文件。执行结果如下:

0

3、命令行处理

  Shell如何读取输入数据,如何处理单引号、双引号、反引号等符号,以及如何根据环境变量IFS将命令行分割成字符,这些操作由Shell自动完成,称之为命令行处理。

3.1、命令行处理流程

  Shell从标准输入或脚本读取的每一行称为管道(pipeline),每一行包含一个或多个命令,这些命令用管道符隔开,Shell对每一个读取的管道都按照如下的流程进行处理:

 命令行处理流程共有12个步骤:

  1) 将命令分割成令牌 (token),令牌之间以元字符分隔,Shell的元字符集合是固定不变的,包括 空格、Tab键、换行字符、分号(;)、圆括号、输入重定向符(<)、输出重定向符(>)、管道符(|)和&符号。令牌可以是单词(word)、关键字,也可以是I/O重定向器和分号;

  2) 检查命令行的第一个令牌是否为不带引号或反斜杠的关键字,若此令牌是开放关键字,开放关键字指if、while、for或其他控制结构中的开始符号,Shell就认为此命令是复合命令,并为该复合命令进行内部设置,读取下一条命令,再次启动进程。若此令牌不是复合命令的开始符号,该令牌是then、else、do、fi、done等符号,说明该令牌不应该处在命令行的首位。Shell会提示语法错误;

  3) 检查命令行的第一个令牌是否为某命令的别名,需要将此令牌与别名(alias)列表逐个比较,若匹配,说明该令牌是别名,则将该令牌替换掉,返回步骤 1),否则进入步骤 4)。这种机制允许别名递归,也允许定义关键字别名。可以用如下命令定义 while 关键字的别名 when:

alias when=while

  4) 花括号展开,比如 h{a,i}t展开为hat或hit;

  5) 将单词开头处的破浪号(~)替换成用户的根目录$HOME;

  6) 将任何开头为$符号的表达式执行变量替换;

  7) 将反引号内的表达式执行命令替换;

  8) 将 $((string)) 的表达式进行算术运算;

  9) 从变量、命令和算术替换的结果中取出命令行,再次进行单词切分。与步骤 1) 不同的是,此时不再用元字符分隔单词,而是使用$IFS分隔单词;

  10) 对*、?、[...]等符号,执行通配符展开,生成文件名;

  11) 将第一个单词作为命令,可以是函数、内建命令和可执行文件;

  12) 在完成I/O重定向与其他类似事项后,执行命令;

  命令行处理步骤是由Shell自动完成的,用户不能清晰的看到Shell完成的每一个步骤。

下面看个例子:

echo ~/i* $PWD `echo Yahoo Hadop` $((21*20)) > output

Shell处理此命令行的步骤如下:

1、首先将命令行分割成令牌,分割成的令牌如下:
echo ~/i* $PWD `echo Yahoo Hadop` $((21*20)) > output
  1   2    3          4               5 

  需要注意的是,重定向 >output 虽已被识别,但它不是令牌,Shell将在后面对I/O重定向进行处理。

2、检查第一个单词echo是否为关键字,echo显然不是开放关键字,所以,命令行继续下面的判断;

3、检查exho是否为别名,echo不是别名,命令行继续往下处理;

4、扫描命令行是否需要花括号展开,该命令无花括号,命令行继续往下处理;

5、扫描命令行是否需要波浪号展开,命令行中存在波浪号,令牌2将被修改,命令行变为如下形式:

echo /home/shell/i* $PWD `echo Yahoo Hadop` $((21*20))
  1      2           3           4               5 

6、扫描命令行中是否存在变量,若存在变量,则进行变量替换,该命令行中存在环境变量PWD。因此,令牌3将被修改,命令行变为如下形式:

echo /home/shell/i* /home/shell `echo Yahoo Hadop` $((21*20))
  1         2            3              4               5 

7、扫描命令行中是否存在反引号,若存在,则进行命令替换,该命令行存在命令替换,因此,令牌4将被修改,命令行变为如下形式:

echo /home/shell/i* /home/shell Yahoo Hadop $((21*20))
  1        2             3           4           5 

8、执行命令行中的算术替换,令牌5将被修改,命令行变为如下形式:

echo /home/shell/i* /home/shell Yahoo Hadop 420
  1        2             3           4       5

9、Shell将前面所有展开所产生的结果进行再次扫描,依据$IFS变量值对结果进行单词分割,形成如下形式的新命令行:

echo /home/shell/i* /home/shell Yahoo Hadop 420
  1        2             3        4     5    6

  由于$IFS是空格,因此,命令行被分割为6个令牌,Yahoo Hadop被分为两个令牌。

10、扫描命令行中的通配符,并展开,该命令行中存在通配符*,展开后,命令行变为如下形式:

echo /home/shell/indirect.sh /home/shell/install.log /home/shell Yahoo Hadop 420
 1            2                         3                 4        5     6    7         

  i*展开为当前目录下所有以为i开头的文件,该目录以i开头的文件:indirect.sh 和 install.log,因此,令牌2又被分为令牌 2 和 3。

11、此时,Shell已经准备执行命令了,寻找echo,echo是内建命令;

12、Shell执行echo命令,此时执行>output的I/O重定向,再调用echo命令,显示最后的参数,执行结果如下:

3.2、eval命令

  eval命令的作用,可以从执行命令步骤跳转到初始步骤。eval命令将其参数作为命令行,让Shell重新执行该命令行,eval的参数再次经过Shell命令行处理的12个步骤。

  eval在处理简单命令时,与直接执行该命令无区别,如下:

0

  若将变量赋一个特殊值,eval的作用就能体现出来。

0

  第1条命令将pipe变量赋为管道符;

  第2条命令预期的目的是将当前目录下的文件列出来,再通过管道发送给 wc -l 命令以统计行数,中间的管道符引用了pipe变量,执行第2条命令时出现语法错误,提示 "|" 和 "wc"的文件或目录不存在,原因在于:Shell在处理ls $pipe wc -l命令时,第1步扫描没有发现有管道符,直到第6步变量替换之后,命令行才变成 ls | wc -l,第9步根据$IFS变量将命令行重新分割4个令牌,第11步将ls当做命令,后面的3个令牌|、wc 和 -l 被解析为ls命令的参数,由于该目录下没有 | 和 wc 等文件或目录,因此,Shell报语法错误。

  第3条命令,得到了预期结果。在变量替换后得到 ls | wc -l命令行,因eval命令的作用, ls | wc -l 命令行被重新提交到Shell,Shell在第1步以元字符对 ls | wc -l 命令进行令牌分割,管道符属于元字符,Shell成功的将此命令行解释为两个命令,并且中间用管道符连接,从而用wc -l命令统计出ls命令结果的行数。

  若变量中包含任何需要Shell直接在命令中看到的字符,就需要使用eval命令。命令结束符 (;,|,&)、I/O重定向符(<,>)和引号这些对Shell有特殊意义的符号,必须直接出现在命令行中。

 
posted @ 2023-10-18 16:42  无虑的小猪  阅读(175)  评论(0编辑  收藏  举报