Shell脚本编程(-)

来源:互联网 发布:windows 微软雅黑 编辑:程序博客网 时间:2024/04/27 13:31

我曾经有一个T恤衫,上面印着“Go away or I will replace you with a very small shell script”。这句话很形象地说明了在Linux系统上完成Shell脚本编程的特点:简单、快速以及轻松。很少有语言能达到这种境界,程序猿只知道命令行的使用就可以开始编程做很复杂的事情。例如想知道计算机的主机名,小case,使用hostname命令然后进行赋值host=$(hostname)。

1.1 第一个Shell脚本程序

为了能成功编写一个Shell脚本,你需要做三件事情:首先当然先写个脚本,然后赋给脚本可执行权限,最后把脚本放在Shell可以找到的某个地方。

Shell脚本包含的是ASCII文本,编写Shell脚本只要文本编辑器就行。在Linux系统上可供选择的文本编辑器不少,有运行在命令行环境的,有运行在GUI环境的,拉个候选名单供你参考,它们是vi(vim)、emacs、nano、gedit和kwrite等。我喜欢用vi,就连在eclipse开发环境中也可以装个vi插件,一招鲜吃遍天。现在打开编辑器输入以下内容:

#!/bin/sh

# My first script

echo "Hello World!"

把这些内容存成一个文件,文件名叫hello_world如何?接下来我们看看脚本内容。

脚本的第一行很重要。它给Shell一个很重要的线索,告诉它用什么程序解释这个脚本。在这个例子中用的是/bin/sh。其它脚本语言例如perl、awk、tcl和python等也都采用这种机制。

第二行是注释。在“#”符号后面的东西,bash都视而不见给忽略掉。如果你的脚本很复杂的话,注释还是蛮重要的。这样其它看到你脚本的人会理解你在做什么。

最后一行是echo命令。这个命令简单在显示终端上打印输出。

一个让你十分困惑的问题立马就摆在了你的面前。因为我说第一行很重要,又说第二行是注释会被忽略。都是“#”开头的东西,难道第一行和第二行有甚区别?有区别,而且就是跟它出现的位置有关。第一行的“#”号后面接着一个“!”号,这个就是脚本解释程序的声明指令,由调用这个脚本的Shell来检测。它仅在脚本程序的第一行有效。

若想要这个脚本能够执行,必须要做的一个事情就是使用chmod命令给它赋予可执行权限。例如:chmod 755 hello_world。“755”是啥意思咱就不在这里不解释了,不知道的就回头第二章吧!

1.2 编程的基本要素

我一直在思考着如何讲Shell编程才能让初学者更容易接受。干巴巴的理论和晦涩的语法总是令人生畏。但我还是不甘心地要试上一试,就当是挑战一下自我吧。毕竟“与天斗其乐无穷,与人斗其乐无穷,与己斗其乐无穷”啊-_*!

代码1是一个“二进制转十进制”脚本的源代码。你可以先跳过这个脚本,在认识各知识点的时候再回过头一点一点地参照。

代码1:

#!/bin/sh

help()

{

#用一对HELP之间的字符串表示命令帮助信息并显示,然后退出命令

cat<<HELP

       b2d - convert binary to decimal

       USAGE: b2d [-h] binarynum

       OPTIONS: -h help text

       EXAMPLE: b2d 111010

       will return 58

HELP #这个HELP要顶头写,不能有空格

       exit 0

}

 

error()

{

       #打印错误并退出

       echo "$1"

       exit1

}

 

lastchar()

{

       #返回在字符串中的最后一个字符

       if [ -z "$1" ]; then

       #是空字符串,返回值为空

              rval=""

              return

       fi

      

       #获取字符串中字符的个数

       #wc会在输出添加一些空格所以我们用sed把它们过滤掉

       numofchar=$(echo -n "$1" | wc-c | sed 's/ //g')

      

       #获取最后一个字符

       rval=$(echo -n "$1" | cut -b "$numofchar")

}

 

chop()

{

       #移去字符串中的最后一个字符,然后返回剩下的字符串

       if [ -z "$1" ]; then

       #是空字符串,返回值为空

              rval=""

              return

       fi

      

       #获取字符串中字符的个数

       #wc会在输出添加一些空格所以我们用sed把它们过滤掉

       numofchar=$(echo -n "$1" | wc-c | sed 's/ //g')

       if [ "$numofchar" ="1" ]; then

       #在字符串中只有一个字符

              rval=""

              return

       fi

       numofcharminus1=$(expr"$numofchar" - 1)

      

       #获取移除最后一个字符后的字符串

       rval=$(echo -n "$1" | cut -b1-${numofcharminus1})

}

 

while [-n "$1" ]; do

#命令行参数不为空

       case $1 in

              -h) help; shift1;; #调用帮助函数

              --) shift; break;; #结束选项

              -*) error "error: no suchoption $1. -h for help";;#错误提示

              *) break;;#退出判断

       esac

done

 

#主程序开始,初始化总和和权重变量

sum=0

weight=1

 

#至少要给出一个命令行参数

[ -z"$1" ] && help

 

#从命令行参数中获取二进制数的字符串

binnum="$1"

binnumorig="$1"

 

while [-n "$binnum" ]; do

#二进制字符串不为空,获取字符串中的最后一位字符

       lastchar "$binnum"

       if [ "$rval" = "1" ];then

       # 最后一位字符为1,总和增1

              sum=$(expr "$weight" +"$sum")

       fi

      

       #移除字符串的最后一位赋给循环判断变量

       chop $binnum

       binnum=$rval

       echo $binnum

      

       #权重翻倍

       weight=$(expr "$weight" "*"2)

       echo $weight

done

 

#显示用户名及其转换结果

echo -e"Hello, $USER. \n\nBinary $binnumorig is decimal $sum"

1.2.1             变量

变量是通过名字能访问到的存储信息的内存。不知道这种说法是不是有些绕口,反正绕口你也得将就将就,毕竟绕口令也是我们的国粹。来看在本例中,我们首先做的事情是对总和以及权重进行初始化。

……

#主程序开始,初始化总和和权重变量

sum=0

weight=1

#至少要给出一个命令行参数

[ -z "$1" ] &&help

binnum="$1"

……

与大多数解释型语言相同,Shell编程中变量无需事先声明。Shell对变量是一视同仁的,只要看到以$开始的单词,它就去寻找变量的值然后替代它。变量名的命名必须遵循以下规则:

l  首个字符必须为字母(a-z,A-Z)

l  中间不能有空格,可以使用下划线(_)

l  不能使用标点符号

l  不能使用bash里的关键字(可用help命令查看保留关键字)

显然在本例中“$1”就不满足上述定义,因为这是特殊的变量。具体怎么特殊的我们后面在做详细介绍。

Shell对那些有代码洁癖的程序猿可能不太友好,因为在给变量赋值的时候“=”号两边不能有空格,这样你就不能将代码整理的整整齐齐了。但是取用一个变量的值却相当的方便,只需在变量名前面加一个“$”就行,而且即便在引号“" "”内的字符串中也管用。这种特性的使用还是需要小心谨慎的,比如下面的情况:

sum=20

echo "We have$sumapples"

并不会输出“Wehave 20apples”而是“We have”。原因是shell会去搜索变量$sumapples的值,而实际上这个变量此时并没有值。如果我们这样改就解决问题了:

       echo "We have ${sum}apples"

用花括号告诉shell要打印的是sum变量。

除了只在脚本内有效的普通Shell变量外,还有环境变量。它们在你每次登陆的时候已经在脚本中定义好了。如果你想知道在你的运行环境中的所有变量,使用printenv 命令。因为其数值很少变化,环境变量经常被认为是常量。像其它常量一样,环境变量全部是由大写字母组成。我的习惯是常量用大写字母,变量用小写字母。

echo "Hello, $USER. \nBinary$binnumorig is decimal $sum"

我们通过环境变量USER获取用户名。

1.2.2             运算

我们使用计算机免不了做一些简单的数学运算。脚本明白我们这点需求,所以提供了且仅提供了整数的数学运算。那么什么是整数呢?整数就是1、2、666、-89这样的数,而不能是0.333 或 3.1415这样的数,这些是小数。如果你必须使用小数的话,那就只能请bc帮忙。bc 这个独立的程序提供了精确的计算器语言,可以在Shell 脚本中使用,使用方法将在以后的章节讲解。Shell求值时使用固定宽度的整数,并且不检查溢出。和其它语言一样,对除法运算的要求是除数不能为0,也可以捕获到除以零的情况并报错。

在本例中,需要循环执行找到最后一位(weight)移出后乘以2这样的操作。例如:

weight=1

weight=$weight+1

echo $weight

但是事与愿违打印出来的不是2而是1 + 1。为了达到我们预期的效果,有以下几种表达方式供参考:

l  expr用于整数值运算,每一项用空格隔开
weight=`expr $weight+ 1`
注意“+”号两边的空格,否则还是按照字符串的方式赋值。 “`”符号与$(命令)的语法等效,用于获取命令的stdout中的内容。expr实际上是一个命令,表达式计算结果输出到stdout中。做乘法时需要注意,因为“*”在Shell中有特殊意义,所以应该写成“\*”或这用引号“""”括起来。另外,`命令`是一种古老的表示方法,用$(命令)更好。

l  let表示数学运算
let "weight+=1" 或 let "weight=$weight+1"

l  $[]将中括号内的表达式作为数学运算先计算结果再输出。
weight=$[$weight+1]

基本的运算除了有加减乘除(+-*/),还有取余运算。代码2是从终端读取秒数,然后以小时、分钟和秒的格式输出显示。

代码2:

#!/bin/bash

seconds=0

 

echo -n"Enter number of seconds > "

readseconds

 

hours=$((seconds/ 3600))

seconds=$((seconds% 3600))

minutes=$((seconds/ 60))

seconds=$((seconds% 60))

 

echo"$hours hour(s) $minutes minute(s) $seconds second(s)"

1.2.3             流程控制

脚本程序包含了一系列的命令,一行一行地开始执行直到最后结束。但是大多数程序的结构不止如此,还有比较复杂的逻辑,会根据不同情况(条件)进行不同的动作。通过使用Shell提供的表5-1所列的这些关键字我们可以控制执行的顺序。

表5-1

if

exit

for

while

until

case

break

continue

 

1.       if语句

表面上虽然看起来不难,我们做决定全靠它了。if 命令有三种形式:

l  第一种

# 条件为真执行;条件为假不做任何事情。

if condition ; then

       commands

fi

l  第二种

# 条件为真执行command1;条件为假执行command2

if condition ; then

       command1

else

       command2

fi

l  第三种

# 条件1为真时执行command1;条件2为真时执行command2

if condition1 ; then

       command1

elif condition2 ; then

       command2

fi

2.       test语句

一个正确编写的Unix应用程序会告诉操作系统执行的成功与否。它用什么方法告诉系统呢?那就是我们经常用到exit的状态。exit的状态数值的范围从0-255。"0"表示成功,其它数值都表示失败。exit状态有两个很重要的功能。第一个作用是检测和处理错误,第二个作用是可以执行true/false测试。

这种true/false的判断经常是通过test 和if 一起努力实现的,可以有两种语法形式:

l  第一种形式

test expression

l  第二种形式

[ expression ]
注意:“[”、“]”和expression之间必须有空格。

test 命令工作很简单。如果给定的表达式为true,test 退出的状态值为0,否则退出的状态值为1。

在b2d例子中有几处使用上述方法:

……

if [ -z "$1" ]; then

#是空字符串,返回值为空

       rval=""

       return

fi

……

[ -z "$1" ] && help

……

-z 表示字符串为空时test的退出值为0(表示true),也就是没有输入任何的命令行参数,会执行help功能,否则继续往下执行。表5-3列出了test可以检测的一些条件。因为test是shell内置的工具,使用“help test”可以查看完整的列表。

表5-2

表达式

描述

-d file

如果file目录存在返回为真

-e file

如果file文件存在返回为真

-f file

如果file文件存在并且是普通文件返回为真

-L file

如果file是连接文件为返回为真

-r file

如果file文件对你来说可读为返回为真

-w file

如果file文件对你来说可写为返回为真

-x file

如果file文件对你来说可执行为为真

file1 -nt file2

如果file1文件比file2文件新(根据修改时间)返回为真

file1 -ot file2

如果file1文件比file2文件旧(根据修改时间)返回为真

-z string

如果string为空返回为真

-n string

如果string不为空返回为真

string1 = string2

如果string1与string2相等返回为真

string1 != string2

如果string1与string2不相等返回为真

integer1 -eq integer2

如果integer1与integer2相等返回为真

integer1 -ge integer2

如果integer1大于等于 integer2 返回为真

integer1 -gt integer2

如果integer1大于integer2 返回为真

integer1 -le integer2

如果integer1小于等于integer2返回为真

integer1 -lt integer2

如果integer1小于integer2返回为真

integer1 -ne integer2

如果integer1不等于integer2返回为真

if test expr

如果expr为真返回真

if test !expr

如果expr为假返回真

expr1 -a expr2

如果expr1和expr2都为真返回真

expr1 -o expr2

如果expr1或expr2为真返回真

在实际应用中可能会经常遇到类似这样的写法:

if [ [ $var1 -eq$var2 ] ]; then

       ……

fi

这种写法的怪异之处就是采用双“[]”。之所以这样写是为了保证$var1和$var2没有被赋值或为非整数的时候程序能够继续运行。因为最内层的“[]”失败相当于是表达式为假。

3.       exit语句

一个好的脚本编程者应该在脚本完成时设置exit状态。为了实现这个要求,就要使用exit命令。exit命令使脚本带着状态值立刻退出。例如:

exit 0 #退出脚本,状态值为0 (成功)

exit 1 #退出脚本,状态值为1 (失败)

4.       case 语句

case表达式可以用来匹配一个给定的字符串,而不是数字(可别和C语言里的switch … case混淆)。case语句有以下格式:

case word in

       模式) 声明 ;;

esac

在本例中:

       case $1 in

              -h) help; shift 1;; #调用help函数

              --) shift; break;; #结束选项

              -*) error "error: no suchoption $1. -h for help";; #错误提示

              *) break;; #退出判断

       esac

上面使用了一个特殊变量$1,该变量包含有传递给这个脚本的第一个参数值。“*”意味着匹配任何东西。在最后的模式匹配中加入*这项是个好习惯,可以用它来检测非法的输入。

5.       循环语句

循环是只要条件成立就反复执行。shell有两种形式的循环,一种是while(until),另一种是for。

while … ; do

……

done

只要测试表达式条件为真,则while循环将一直运行。关键字“break”用来跳出循环,而关键字“continue”则可以跳过一个循环的余下部分,直接跳到下一次循环中。

for循环会查看一个字符串行表(字符串用空格分隔),若找到匹配进入循环:

for var in … ; do

……

done

在本例中:

while [ -n "$binnum" ]; do

……

done

“-n”表示字符串不为空时 test 的退出值为0(表示true)。在每次做最后一位数字移位操作后检查剩下的二进制数,如果不为空,就继续做移位求和的工作。在反复执行的代码块中,被检测的变量会有所改变,否则每次执行的时候条件总会满足,就会陷入死循环中。你可能会问谁会这么傻写出死循环的程序。老哥,千万不要麻痹大意,智者千虑必有一失,还是仔细一点好。我给你举个例子,有的计算机连接了一个外部的设备,例如USB硬盘,但是你忘了打开它的开关了。在应用和设备接口之间会发生什么?一直会出现:

Application:Are youready?

Interface:Devicenot ready.

 

Application:Are youready?

Interface:Devicenot ready.

 

Application:Are youready?

Interface:  Device not ready.

 

Application:Are youready?

Interface:Devicenot ready.

直到永远……

为了避免出现这种情况,好的循环设计应该有timeout,把尝试次数或消耗时间考虑进去。当超过尝试的次数或允许时间的时候,退出循环,产生错误提示。

1.2.4             自定义函数

top-down设计是从上层开始分析逐步增加实现细节。利用这种方法我们把大而复杂的任务拆分成许多小而简单的任务。编写脚本前,我们要静下心来想想要完成什么任务,再把任务分为子任务。在二进制转换十进制这个脚本中,我们需要找到最后一位字符lastchar()以及在字符串中删除最后一位字符chop(),另外在脚本中写上帮助函数help()以及错误处理函数()是个好习惯。那么这个脚本列表如下:

#!/bin/sh

help()

{

}

 

error()

{

}

 

lastchar()

{

}

 

chop()

{

}

通过这些简单的写法,不难猜出Shell是如何自定义函数的,是不是跟C差不多?我说差多了,你没发现这些函数都没有声明参数吗?先不管这些参数问题,看看函数里面都是啥东西吧!

我们先来看看lastchar函数,它的功能是返回给定字符串末尾字符。lastchar首先会看一下字符串是否空,如果为空就返回空字符串,否则就计算出字符串包含的字符数N,从而提取第N位字符。

lastchar()

{

       #返回在字符串中的最后一个字符

       if [ -z "$1" ]; then

              #是空字符串,返回值为空

              rval=""

              return

       fi

      

       #获取字符串中字符的个数

       #wc会在输出添加一些空格所以我们用sed把它们过滤掉

       numofchar=$(echo -n "$1" | wc-c | sed 's/ //g')

      

       #获取最后一个字符

       rval=$(echo -n "$1" | cut -b$numofchar)

}

从lastchar的功能定义上看,它应该有一个要处理的参数。这个参数在程序中由“$1”代替,即第一个参数。如果有第二个参数就是$2,依此类推……这种写法的变量叫自变量,是一种Shell内置的变量,因此它可以不按照变量命名的规矩出现。其实Shell之所以要给变量命名立下规矩也就是为了能让用户定义的变量不会与这些内置的变量相冲突。

之所以被称之为自变量,是因为其值能够自己变动。当然也是不能乱变的,一定要有规律。这个规律就是:在函数中随着函数的参数变化,在主程序中随着命令行参数变化。

除了这些以数字命名的自变量外,还有“@”和“#”。“$#”比较简单,就是用于记录参数的个数,不会区分函数和命令行。而“$@”就比较麻烦,不同的情况下有些差别。在主程序中是以空格分割的所有命令行参数,在函数中是以逗号“,”分割的所有函数参数。此外,“$0”永远都会代表脚本的文件名。

貌似现在有点跑题了,自变量就说到这里了,还是回到正题上来,看看help函数的实现。它利用了help文档形式打印帮助。

help()

{

       cat<<HELP

       b2d- convert binary to decimal

……

       HELP #这个HELP要顶头写,不能有空格

       exit0

}

当要将几行文字传递给一个命令时,用help文档是一种不错的方法。对每个脚本写一段帮助性的文字是很有用的,此时如果使用help文档就不必用echo函数一行行输出。help文档以“<<”开头,后面接上一个标志字符串,这个字符串还必须出现在help文档的末尾。

饭是一口一口吃的,代码是一点点地做的。加一小段代码就运行一下脚本,看看是否正确。一点一点去“垒”代码,如果出现错误,很容易找到问题。还有当你还没有想清楚error函数如何去做的时候,你可以用stubbing技术来帮你完成脚本的逻辑。stubbing也叫留空技术,它可以帮你理清你写的脚本逻辑。例如我们还没有考虑清楚error函数的实现细节,与其暂停脚本的整体开发直到error函数被完成,不如,直接在error函数体里面简单的添加一个echo命令,像这样:

error()

{

# Temporary functionstub

echo "function error"

}

通过这样一种方式,我们的脚本依然可以成功的执行,尽管我们还没有完成error函数。然后,在接下来的开发中,再将error的完整实现代码添加到error函数体里面。在error函数里面添加echo命令的原因是,在执行脚本的时候,我们可以获取脚本反馈出来的信息--该函数正在执行。

其它两个(chop和error)函数所需要的实现的功能就请你自己来加吧。

1.3 输入和输出

在执行任务时,shell通常会自动打开如下所示的3个标准文档。

表5-3

文档类型

说明

文件描述符

stdin

标准输入文档,通常对应终端的键盘

0

stdout

标准输出文档,对应终端的屏幕

1

stderr

标准错误输出文档,对应终端的屏幕

2

本节我们将简单介绍一下标准输入、标准输出、文件描述符、重定向以及格式化输出等。

1.3.1             基本的输出

在b2d例子中我们使用echo命令显示文本行或变量。例如在移除变量值中最后一个字符的操作时,用echo命令输出要做处理的变量:

rval=$(echo -n "$1" |cut -b 1-$numofcharminus1)

我们使用了-n选项来禁止echo命令输出后换行。echo命令虽然有很多功能选项,但是简单易懂,这里就不赘述了。

1.3.2             命令行参数


在前面的讲述中,你见过$1变量了,也了解了$@ 和 $1,$2…… $9等这些特殊变量跟命令行输入参数的关系了。那我们就不继续说这个了,来说说如何分析它们吧。

有好多方法可以实现对输入参数的分析,前面我们使用的case表达式无疑是一个不错的选择。现在看看代码3在命令行参数输入后会返回什么结果。

代码3:

while [-n "$1" ]; do

case $1in

       -h) help;shift 1;; # function help iscalled

       -a) opt_a=1;shift 1;; # variable opt_a isset

       -b) opt_b=$2;shift 2;; # -b takes anargument -> shift by 2

       --) shift;break;; # end of options

       -*) echo "error: no such option $1.-h for help";exit 1;;

       *) break;;

       esac

       done

 

       echo "opt_a is $opt_a"

       echo "opt_b is $opt_b"

       echo "first arg is $1"

       echo "2nd arg is $2"

如果你运行了代码3,并且给它传递的命令行参数为“-b hello -a -- -z yyy”,那么它的输出结果应该是:

opt_a is 1

opt_b is hello

first arg is -z

2nd arg is yyy

代码3首先在所有输入命令行参数中进行循环,将输入参数与case表达式进行比较,如果匹配则设置一个变量并且使用shfit命令移除该参数。根据unix系统的惯例,首先输入的应该是包含减号的参数。如果你的命令行参数为“-z yyy -b hello -a -”,返回的结果就有错误提示了:

error: no such option -z. -h forhelp

1.3.3             使用 read读取文本行

read命令接收标准输入,或其它文件描述符的输入。得到输入后,read命令将数据放入一个标准变量中。利用read读取文件时,每次调用read命令都会读取文件中的“一行”文本。当文件没有可读的行时,read命令将以非零状态退出。

使用read命令读取一行数据方法可以参考代码4或代码5:

代码4

while read myline  

do

echo "LINE:"$myline

done < myfile.txt

       代码5

cat myfile.txt | while read myline  

do   

echo "LINE:"$myline  

done  

此外,使用awk或for var in file命令也能完成读取文件中的每行数据的功能。

1.3.4             文件描述符

当看到在脚本里的这条命令时:

sudo kill -9 `ps -elf |grep -vgrep|grep $1|awk '{print $4}'` 1>/dev/null 2>&1

第一感觉告诉我,应该是杀进程的。里面具体东东,看起来真有点晕,“1>”、“2>”、“/dev/null”代表什么呢?

0、1、2是文件描述符,代表标准输入、输出和错误。系统中实际上有12个文件描述符,我们可以任意使用文件描述符3到9。

“>”在第二章就讲过了,表示命令的结果可以重定向到某个地方,例如:

echo "zhangjie" >/home/student.txt

“/dev/null” 代表空设备文件,很多的幽默语句与之有关。例如“I mount my soul at /dev/null”,不知情的人看了这句话可能一头雾水,但是Unix程序员知道这是一句搞笑的话,意思是说“我找不到北了”。苹果公司在2003年利用这个概念做过一则广告,“将其它牌子的UNIX都送进/dev/null”,这表明运行 Mac OS X 的PowerBook包含了全部UNIX特性。Niubility!

“1>/dev/null” 表示标准输出重定向到空设备文件,也就是不输出任何信息到终端,说白了就是不显示任何信息。

“>&”表示等同于的意思,“2>&1”表示2的输出重定向等同于1。标准错误输出重定向等同于标准输出,因为之前标准输出已经重定向到了空设备文件,所以标准错误输出也重定向到空设备文件。

执行命令时,可以指定命令的标准输入、输出和错误,要实现这一点就需要使用文件重定向。“>”表示标准输出重定向,“>>”表示标准输出重定向做追加操作,“<”表示从标准输入中读入,“<<”表示从标准输入中读入,直至遇到分隔符。例如:

command >> filename 2>&1          把标准输出和标准错误一起重定向追加到一个文件中。

command <filename1>filename2      把command命令以filename1文件作为标准输入,以filename2文件作为标准输出。

command <<eof      把从标准输入中读入,直至遇到eof分界符。

command <&m       把文件描述符m作为标准输入

command >&m       把标准输出重定向到文件描述符m中

command <&-        关闭标准输入

除了有强大的重定向功能,还可以通过管道把一个命令的输出传递给另一个命令作为输入。管道用竖杠|表示。它的一般形式为: 
命令1 |命令2,
其中|是管道符号。b2d的例子中

numofchar=$(echo -n "$1"| wc -c | sed 's/ //g')

如果没有管道的话,变量字符串就会在终端屏幕上显示出来。当shell看到管道符号以后,就会把字符串交给管道右边的wc命令计算字符数,计算的字符数再传给sed行编辑器处理去掉多余的空格最终获得字符串中的字符位数。因此管道顾名思义就是把信息从一端送到另外一端。

1.3.5             格式化输出

提起格式化输出printf, 各位一定不会陌生吧。它是echo命令的增强版,与C语言中的printf功能和使用方法类似。利用format字符串控制输出,%与一个字母的格式指示符用来表示相对应的参数字符串的格式化。

printf命令可用来指定输出字段的宽度以及进行对齐方式。为实现此目的,接在%后面的格式表达式可采用三个可选用的修饰符以及前置的格式指示符。

%[标志][宽度][.精度]格式说明符,字段的内容默认向右对齐,向左对齐需要指定-标志。例如

printf"%5.5G\n"3.1415926

最后输出结果是3. 1415吗?不对!%G要求输入是浮点数,精度是指有效位数的最大位数,别忘了,还得四舍五入,输出结果是3.1416。

%格式指示符项目还是比较多,你man一下对照表慢慢研究吧。