UNIX 新手指南: 一些很好的 Shell 诀窍

http://www.ibm.com/developerworks/cn/education/aix/au-unixtips4/index.html

UNIX 新手指南: 一些很好的 Shell 诀窍

 

简介:  当编写 Shell 程序时,您通常会遇到一些特殊的情况,希望采用自动方式处理。本教程包括一些关于此类情况的 Bourne Shell 脚本示例。这些情况包括字符串的进制转换(十进制到十六进制、十六进制到十进制、十进制到八进制,等等)、在管道循环中读取键盘、Subshell 执行、内联输入、为目录中的每个文件执行一次命令,以及使用多种方法构造连续循环。本系列文章的第 4 部分总结了一批执行有用功能的 Shell 单命令行程序。

 

开始之前

 

了解本教程中包含的内容以及如何最好地利用本教程。

 

关于本系列

 

本系列教程主要针对新用户撰写,简要介绍 UNIX® 基本概念。本系列教程的前三篇文章站在拥有 Microsoft® Windows® 背景的新用户的角度重温了一遍 UNIX 系统,讲述了文件系统和常用命令,介绍了 vi(最常见的 UNIX 编辑器),并且通过使用 grep、sed 和awk 工具简要介绍了筛选器和正则表达式。

 

关于本教程

 

本教程介绍了一套新用户易于掌握的诀窍和技巧。说明在特定情况下,如何使用在 Bourne Shell 中编写的小脚本自动执行操作,包括自动执行进制转换、读取键盘输入、在 Subshell 中执行命令、为目录中的所有文件执行相同命令,以及多种形式的循环。本教程最后以一套实用的 Shell 单命令行程序作为结束。

 

目标

 

本教程的目标是向新用户介绍如何使用和实现许多在各种级别上提供自动化操作的 Shell 方法。本教程通过提供针对特定情况的诀窍和技巧来说明这些方法,并且提供适用于常见任务的 Shell 单命令行程序的概要性介绍。

 

先决条件

 

本教程面向相对不熟悉 UNIX 的用户。唯一的先决条件是了解 UNIX 文件系统的基本知识和操作命令、命令行本身,以及能够使用类似 vi 的编辑器编写文本文件。本系列教程的前面部分对这些概念作了全面说明。

 

系统要求

 

您需要在带有 Bourne 兼容 Shell 环境(例如 bash)的 UNIX 系统上拥有用户级访问权限。这是本教程唯一的系统要求。

 

Shell 运算和进制转换

 

Shell 提供大量的基本运算操作,在脚本中非常有用。Shell 对您提供的算术表达式求值,执行运算展开式,此时使用得出的结果替换表达式。以下面的格式提供运算表达式:

 

$(( expression ))

 

您可以使用 echo 在命令行显示运算展开式的结果,了解其工作情况。现在尝试清单 5 所显示的结果。

 

 

清单 5. Bourne Shell 中的运算展开式

                    

$ echo $((10+40))

50

$ echo $((5*(3+3)))

30

 

您还可以将展开式分配给变量。尝试清单 6 所显示的结果。

 

 

清单 6. 将运算展开式分配给 Shell 变量

                    

$ myvar = 10

$ echo $myvar

10

$ echo $(($myvar-2))

8

$ myvar = $(($myvar+5))

$ echo $myvar

15

$ result = $(($myvar-10))

$ echo $result

5

$

 

表 2 列出了在大多数 Bourne 以及与 Bourne 兼容的 Shell中可以使用的运算符。正如上面第二个示例,使用圆括号括起来的语句有更高的优先级。实际上,Shell 算术优先级通常根据 C 语言的规则来确定。

 

 

表 2. Shell 条件表达式

运算符描述

+加

-减

*乘

/除

%求余

<小于(1 代表真,0 代表假)

<=小于等于(1 代表真,0 代表假)

>大于(1 代表真,0 代表假)

>=大于等于(1 代表真,0 代表假)

<<按位向左移位:将给定的整数或第一个表达式向左移动第二个表达式表示的位数

>>按位向右移位:将给定的整数或第一个表达式向右移动第二个表达式表示的位数

回页首

 

使用 Shell 运算进行进制转换

 

假定在您的脚本中有一些数字,您需要以另外的进制处理这些数字。使用 Shell 运算可以很容易地自动实现这类转换。一种情况是使用 Shell 运算把一个数字从给定的进制转换位十进制。如果数字以运算展开式的形式提供,那么假定它带有十进制符号,除非 它前面带有 0(这种情况假定是八进制)或 0x(这种情况假定是十六进制)。键入以下内容以得到一些八进制和十六进制值的十进制输出:

 

$ echo $((013))

$ echo $((0xA4))

 

您还可以使用以下格式指定 2 到 64 之间的任意进制:

 

$((BASE#NUMBER))

 

通过在 Shell 提示符后键入清单 7 中所示的行,尝试将二进制、八进制、十六进制以及其他进制的数转换为十进制。

 

 

清单 7. 在 Shell 中将任意进制的数以十进制输出

                    

echo $((2#1101010))

echo $((8#377))

echo $((16#D8))

echo $((12#10))

echo $((36#ZZYY))

 

使用 bc 进行进制转换

 

在 Shell 中进行进制转换的另一个诀窍是使用 bc,它是一种任意精度运算语言,大多数 UNIX 安装程序都提供。因为它允许您指定输出进制,所以当您需要以十进制以外的进制输出时,这是一种很好的技术。

 

bc 的特殊变量 ibase 和 obase 分别包含用于输入和输出的进制的值。缺省情况下,都被设置为 10。要执行进制转换,需要改变其中的一个或两个值,然后提供一个数字。立即尝试,如清单 8 中所示。

 

 

清单 8. 使用 bc 执行进制转换

                    

$ bc -ql10

10

obase=1610

A

ibase=210

2

Control-D

$

 

要快速执行进制转换,可以联合使用 bc 和 echo形成快捷的单命令行程序,将给定的值通过管道传输给 bc。键入清单 9 中显示的内容。

 

 

清单 9. Shell 单命令行 bc 程序

                    

$ echo ‘obase=16; 47′ | bc

2F

$ echo ‘obase=10; ibase=16; A03′ | bc

2563

$

 

警告:当您设置 bc 的输入进制以后,输入 bc 的所有数字都使用该进制,包括您提供用于设置输出进制的数字。因此最好先设置输出进制,否则可能会产生意想不到的结果,如清单 10 中所示。

 

 

清单 10. 设置输入和输出进制的先后顺序的重要性

                    

$ echo ‘ibase=16; obase=10; A’ | bc

A

$ echo ‘ibase=16; obase=A; A’ | bc

10

$

 

内联输入

 

尽管 echo 通过管道将内容传递给交互式命令(比如 bc)可以生成快捷的单命令行程序,但是它对于多行输入并不适用,比如可能用到实际文件中的内容。但是另外一种有用的方法可以完成这个任务。Shell 有一种工具称为 here documents 或内联输入,这是一种动态构建文件的非常好的方法,比如用于脚本内部,并且将该文件的内容重定向到一个命令。

 

使用 Shell << 操作符来指定一个 here document,然后在同一行的后面跟上一个限定字符串,该字符串标记输入的结束,并且您可以选择任何文本,只要是不包含空格字符的单个词都可以。其后跟随构成您的输入文件的行,然后以独占一行的限定字符串结束输入,在它的前面或后面不能有任何文本,否则该行将被视为输入的一部分。使用 cat 进行尝试,如清单 11 中所示。

 

 

清单 11. 编写 here document

                    

$ cat << END

>  END of input text

> ENDspace

> This is still not the END

> ENDING SOON

> THE END

> END

 END of input text

END 

This is still not the END

ENDING SOON

THE END

$

 

限定字符串(本例中是 END)可以出现在输入的任何地方,只有当它以独占一行并且不含空格或其他字符的形式出现时,才表示输入的结束。

 

脚本中的内联输入

 

在脚本中经常使用内联输入将使用信息输出到标准输出。这通常通过将 here document 发送给 cat 来完成,如清单 12 中的脚本所示。使用 vi 输入该脚本并保存到名为 baseconv 的文件中,并且将该文件设置为可执行文件(请参见创建 Shell 脚本部分)。

 

 

清单 12. 使用 here document 提供 Shell 脚本使用信息

                    

#!/bin/sh

cat << EOF

baseconv is a program to convert a number from one base to another.

 

Usage: baseconv [options]

 

Options:

 

-iBASE input base

-oBASE output base

-hdisplay this message

 

For more information, consult the baseconv man page.

EOF

 

当执行该脚本时,here document 的内容被发送到(使用 cat)标准输出。立即尝试,如清单 13 中所示。

 

 

清单 13. 从 here document 输出 Shell 脚本使用信息

                    

$ baseconv

baseconv is a program to convert a number from one base to another.

 

Usage: baseconv [options]

 

Options:

 

-iBASE input base

-oBASE output base

-hdisplay this message

 

For more information, consult the baseconv man page.

$

 

此外,Bourne Shell 的大多数实现允许出现使用可选的连字符重定向的内联输入。可选的连字符将所有的前导 Tab 字符从所有输入行的前面去掉,也包括包含限定字符串的行。这对于您希望让编写的脚本保持当前缩进时会有帮助。由于内联输入通常逐字读取,并且限定字符串必须在行的开始处给出,因此输入将打乱您的当前缩进并使脚本看起来不雅观。因此,您可以重写清单 12 中的脚本,使其与清单 14 一致,而输出不会改变。

 

 

清单 14. 带前导缩进的 Shell 脚本 here document

                    

#!/bin/sh

 

cat <<- EOF

baseconv is a program to convert a number from one base to another.

 

Usage: baseconv [options]

 

Options:

 

-iBASE input base

-oBASE output base

-hdisplay this message

 

For more information, consult the baseconv man page.

EOF

 

回页首

 

在命令行使用内联输入

 

在命令行中,使用调用交互式程序的单命令行程序进行内联输入,比如在使用 bc 进制转换部分讨论的 bc 计算程序。在任意交互式命令中,您可以使用 here document 代替实际文件,或代替任意行的实际输入。

 

尝试使用 here document 将多行输入发送到 bc。键入清单 15 中显示的内容。

 

 

清单 15. 将内联输入发送到交互式程序

                    

$ bc << EOF

> ibase=16

> A

> EOF

10

$

 

通常使用内联输入来扩展变量。尝试清单 16 中显示的内容。

 

 

清单 16. 内联输入如何扩展变量

                    

$ BASECON=16

$ bc << EOF

> ibase=16

> $BASECON

> EOF

22

$

 

Subshell 执行

 

可以在一个名为 subshell 的新 Shell 中执行一个或一组命令,当前 Shell 是 SubShell 的父 Shell。Subshell 继承父亲的环境。I/O 重定向可以出现在子 Shell 和父 Shell 之间,但是 Subshell 永远不能修改父环境。当您为了执行这些命令(比如设置变量)要更改 Shell 的环境,并且不想更改脚本自身运行所在的环境时,这就是您所期望的技术。当您想要同时在后台启动多个长时间运行的进程时也最好使用 Subshell。一个 Shell 可以生成多个 Subshell,而 Subshell 又可以循环生成属于它们自身的任意数量的 Subshell。图 1 说明了这个过程。

 

 

图 1. Subshell 如何与它的父 Shell 交互

 

Shell 有时自动生成自身的 Subshell,比如在管道中使用内置命令时。在 Subshell 中,Shell $ 参数扩展到父 Shell 而不是 Subshell 的进程 ID (PID)。

 

在 Subshell 中运行命令

 

要在 Subshell 中运行一组命令,可以使用括号将其括起来。您可以使用重定向将输入发送到 Subshell 的标准输入,或将 Subshell 的集合输出发送到文件或管道。

 

尝试在您的 home 目录键入清单 17 中显示的内容。该示例创建一个 example 目录和一些测试文件,前提是原来不存在 example 目录。

 

 

清单 17. 在 Subshell 中创建一组文件

                    

$ pwd

/home/user

$ (mkdir example; cd example; touch A B C)

$ pwd

/home/user

$ cd example; ls

A B C

$ pwd

/home/user/example

$

 

在本例中,Shell 生成一个在后台运行的 Subshell,建立 example 目录,然后使用 touch 在该目录中生成三个虚拟文件。同时,Shell 返回 home 目录的命令行。

 

当您有一组执行时间长的命令时,在命令行和脚本中使用 Subshell 都很方便。为了让 Shell 保持空闲,您可以在后台运行 Subshell,或者在后台运行许多个 Subshell。

 

( group-of-long-running-commands ) &

( another-group-of-long-running-commands ) &

( yet-another-group-of-long-running-commands ) &

 

回页首

 

Subshell 和变量

 

理解变量与 Subshell 的交互方式非常重要。因为 Subshell 环境是其父亲的副本,所以它继承了父亲的所有变量。但是父 Shell 从不会看到 Subshell 环境发生的任何变化,同样,Subshell 生成以后,再也不会看到父亲发生的任何变化。

 

作为示例,使用 vi 编辑器将清单 18 中的脚本保存到 home 目录的 vartest 文件中,然后将其设置为可执行(请参见编写 shell 脚本部分)。

 

 

清单 18. 演示 Subshell 中变量行为的 Shell 脚本

                    

#!/bin/sh

# Demonstrates variable behavior in a subshell environment

 

VAR=10

 

echo "VAR is" $VAR

 

(

 

echo "In the subshell, VAR is still" $VAR

 

VAR=$(($VAR+5))

 

echo "The new value of VAR in the subshell is" $VAR

 

)

 

echo "Outside of the subshell, VAR is" $VAR

 

现在尝试通过键入脚本的名称来执行它,如清单 19 中所示。

 

 

清单 19. vartest 脚本的输出

                    

$ vartest

VAR is 10

In the subshell, VAR is still 10

The new value of VAR in the subshell is 15

Outside of the subshell, VAR is 10

$

连续循环

 

现在来看循环,它允许您执行重复任务,比如对一组文件执行一些操作或命令。Shell 有几种构造循环的方法。

 

构造 for 循环

 

最常见的循环结构是 for 循环。首先定义一个变量作为循环的名称,提供一组成员,可以是包括整数和文件名在内的任何单词,然后提供每次重复执行的命令。每个命令都以分号结束 (;),整个命令组以位于单词 do 和 done 之间。清单 20 描述了它的结构。

 

 

清单 20. Shell 中循环的结构

                    

for loopname in members

 

do

 

command;

command;

command;

 

done

 

在循环的第一次重复中,loopname 变量获取第一个成员的值。然后 loopname 的值被清单中下一个成员的值替代,接下来它继续重复直到遍历所有成员。

 

在大多数 Shell 中,do 和 done 都可以被大括号所替代,如清单 21 中所示。

 

 

清单 21. Shell 循环的替代结构

                    

for loopname in members

 

{

 

command;

command;

command;

 

}

 

键入清单 22 中的文本来运行包含三个成员的简单循环:

 

 

清单 22. 使用循环来改变变量的值

                    

$ for i in 1 2 3

> {

> VAR = $(($VAR+$i))

> echo $i:$VAR

> }

1:1

2:3

3:6

$

 

回页首

 

针对目录中的每个文件执行命令

 

您可以使用循环针对给定的一组文件执行一个或一组命令。如果您提供文件的名称作为 for 循环的成员,那么循环按您提供名称的顺序在每个文件上执行操作。您可以两次提供同一个文件,循环将依次对该文件执行操作。在您的 example 目录中尝试使用清单 23 中的文本执行上述操作。

 

 

清单 23. 利用一组文件构造循环

                    

$ cd ~/example

$ ls

A B C

$ for file in C B B C

> {

> echo $file

> }

C

B

B

C

$

 

要对同一目录下的所有文件执行操作,可以使用星号 (*) 作为循环的唯一成员,如清单 24 中所示。Shell 将星号扩展为目录中的所有文件。然后,对于循环中您要对所有文件执行的命令,使用 loopname 变量作为合适的参数或选项。

 

 

清单 24. 针对目录中的所有文件执行同一命令

                    

$ ls

A B C

$ for file in *

> {

> mv $file $((0x$file))

> }

$

 

如果您正在运行本教程中的所有示例,那么您的 example 目录中的内容应该已改变:

 

$ ls

10 11 12

$

 

发生的情况是循环中的 mv 命令将文件的名称从十六进制值(通过在名称的前面插入 0x 构成)更改为与它相等的十进制值。

 

构造 while 循环

 

您可以构造一种当满足某些条件就一直运行的循环。使用 while 条件语句来实现这一目标,其格式如清单 25 所示。

 

 

清单 25. Shell while 循环的结构

                    

while [ condition ]; do

 

command;

command;

command;

done

 

在循环中,condition 可以是使用操作符(请参见表 3)构建的语句,或者可以像一个变量名那样简单。只要值是非 0 的,就代表真。

 

 

表 3. 常用 Shell 操作符

操作符描述

-eq等于

-ne不等于

-lt小于

-le小于等于

-gt大于

-ge大于等于

构造 while 循环时,有一些注意事项需要牢记在心。首先,在条件与将它括起来的括号之间必须留有空白字符。其次,如果在条件中将变量用于数字比较,那么在 while 语句之前必须首先定义该变量。

 

键入清单 26 中的文本以执行一个简短的 while 循环:

 

 

清单 26. 使用 while 循环更改变量

                    

$ VAR=0

$ while [ $VAR -lt 10 ]; do

>   echo $VAR;

>   VAR=$(($VAR+1));

> done

0

1

2

3

4

5

6

7

8

9

$

 

构造 until 循环

 

until 条件语句与 while 相似并使用相同的操作符,但是它们的行为相反。它只有当条件为假时才执行循环,并且循环持续重复直到给定的条件为真。它的格式在清单 27 中说明。

 

 

清单 27. Shell until 循环的结构

                    

until [ condition ] ; do

 

command;

command;

command;

done

 

通过键入清单 28 中所示的内容尝试运行一个简短的 until 循环:

 

 

清单 28. 使用 until 循环更改变量

                    

$ VAR=10

$ until [ $VAR -eq 0 ]; do

>   echo $VAR;

>   VAR=$(($VAR-1));

> done

10

9

8

7

6

5

4

3

2

1

$

 

嵌套多重循环

 

您可以嵌套循环和组合多种类型的循环来执行各种类型的复杂操作。由于 for 循环的成员不必是数字或以任意类型的顺序排列,因此您可以使用稍后在某个内部循环中作为命令执行的命令名称作为其成员,比如 printf、echo、stop、resume,等等。

 

尝试运行清单 29 中的示例。这是一个执行算术替换的 until 循环,同时嵌套在循环词未按数字顺序排列的 for 循环内部。

 

 

清单 29. 使用嵌套循环进行算术替换

                    

$ for i in 250 100 2136 875

>  {

>    VAR=10;

>    until [ $VAR -eq 0 ]; do

>      echo "$i / $VAR = $(($i/$VAR))  $i * $VAR = $(($i*$VAR))

             $i + $VAR = $(($i+$VAR))  $i – $VAR = $(($i-$VAR))";

>      VAR=$(($VAR-1);

>    done;

>  }

250 / 10 = 25  250 * 10 = 2500  250 + 10 = 260  250 – 10 = 240

250 / 9 = 27  250 * 9 = 2250  250 + 9 = 259  250 – 9 = 241

250 / 8 = 31  250 * 8 = 2000  250 + 8 = 258  250 – 8 = 242

250 / 7 = 35  250 * 7 = 1750  250 + 7 = 257  250 – 7 = 243

250 / 6 = 41  250 * 6 = 1500  250 + 6 = 256  250 – 6 = 244

250 / 5 = 50  250 * 5 = 1250  250 + 5 = 255  250 – 5 = 245

250 / 4 = 62  250 * 4 = 1000  250 + 4 = 254  250 – 4 = 246

250 / 3 = 83  250 * 3 = 750  250 + 3 = 253  250 – 3 = 247

250 / 2 = 125  250 * 2 = 500  250 + 2 = 252  250 – 2 = 248

250 / 1 = 250  250 * 1 = 250  250 + 1 = 251  250 – 1 = 249

100 / 10 = 10  100 * 10 = 1000  100 + 10 = 110  100 – 10 = 90

100 / 9 = 11  100 * 9 = 900  100 + 9 = 109  100 – 9 = 91

100 / 8 = 12  100 * 8 = 800  100 + 8 = 108  100 – 8 = 92

100 / 7 = 14  100 * 7 = 700  100 + 7 = 107  100 – 7 = 93

100 / 6 = 16  100 * 6 = 600  100 + 6 = 106  100 – 6 = 94

100 / 5 = 20  100 * 5 = 500  100 + 5 = 105  100 – 5 = 95

100 / 4 = 25  100 * 4 = 400  100 + 4 = 104  100 – 4 = 96

100 / 3 = 33  100 * 3 = 300  100 + 3 = 103  100 – 3 = 97

100 / 2 = 50  100 * 2 = 200  100 + 2 = 102  100 – 2 = 98

100 / 1 = 100  100 * 1 = 100  100 + 1 = 101  100 – 1 = 99

2136 / 10 = 213  2136 * 10 = 21360  2136 + 10 = 2146  2136 – 10 = 2126

2136 / 9 = 237  2136 * 9 = 19224  2136 + 9 = 2145  2136 – 9 = 2127

2136 / 8 = 267  2136 * 8 = 17088  2136 + 8 = 2144  2136 – 8 = 2128

2136 / 7 = 305  2136 * 7 = 14952  2136 + 7 = 2143  2136 – 7 = 2129

2136 / 6 = 356  2136 * 6 = 12816  2136 + 6 = 2142  2136 – 6 = 2130

2136 / 5 = 427  2136 * 5 = 10680  2136 + 5 = 2141  2136 – 5 = 2131

2136 / 4 = 534  2136 * 4 = 8544  2136 + 4 = 2140  2136 – 4 = 2132

2136 / 3 = 712  2136 * 3 = 6408  2136 + 3 = 2139  2136 – 3 = 2133

2136 / 2 = 1068  2136 * 2 = 4272  2136 + 2 = 2138  2136 – 2 = 2134

2136 / 1 = 2136  2136 * 1 = 2136  2136 + 1 = 2137  2136 – 1 = 2135

875 / 10 = 87  875 * 10 = 8750  875 + 10 = 885  875 – 10 = 865

875 / 9 = 97  875 * 9 = 7875  875 + 9 = 884  875 – 9 = 866

875 / 8 = 109  875 * 8 = 7000  875 + 8 = 883  875 – 8 = 867

875 / 7 = 125  875 * 7 = 6125  875 + 7 = 882  875 – 7 = 868

875 / 6 = 145  875 * 6 = 5250  875 + 6 = 881  875 – 6 = 869

875 / 5 = 175  875 * 5 = 4375  875 + 5 = 880  875 – 5 = 870

875 / 4 = 218  875 * 4 = 3500  875 + 4 = 879  875 – 4 = 871

875 / 3 = 291  875 * 3 = 2625  875 + 3 = 878  875 – 3 = 872

875 / 2 = 437  875 * 2 = 1750  875 + 2 = 877  875 – 2 = 873

875 / 1 = 875  875 * 1 = 875  875 + 1 = 876  875 – 1 = 874

$

 

读取键盘输入

 

您还可以在脚本中或从命令行本身读取键盘输入。使用 read 命令可以实现这一功能,这是一个内置函数,将任意数量的变量名作为参数。它从标准输入读取变量的值,读入单行输入并将各个输入词分配给各个变量。

 

尝试读取一个变量,如清单 30 中所示:

 

 

清单 30. 使用 read 读取一个变量

                    

$ read VAR23

$ echo $VAR

23

$

 

使用 -p 选项为每次 read 提供提示。使用以引号括起来的字符串提供提示,如清单 31 中所示。发生变量扩展。

 

 

清单 31. 在变量读取时使用提示

                    

$ read -p "Instead of $VAR, what number would you like? " VAR

Instead of 23, what number would you like? 17

$ echo $VAR

17

$

 

如果键盘输入的词比变量个数多,那么依次为变量分配输入的词,到最后一个变量时,为其分配输入行余下的部分。(如果输入的词比变量个数少,那么为变量分配值直到所有的输入都已分配,然后为所有剩余的变量分配空值。)

 

在循环中读取

 

您可以在循环中使用 read 作为条件表达式。现在使用清单 32 中的内容尝试这一操作:

 

 

清单 32. 在循环中读取一组文件名

                    

$ while read -p "File? " file; do ls $file; done

File? 10

10

File? 12

12

File? 42

42: no such file or directory

File? Carriage return

10 11 12

File? Control-C

$

 

此技术通常在对循环的输入使用管道时使用。尝试键入清单 33 中的文本,该文本使用循环替代 ls 命令的输出:

 

 

清单 33. 从管道读取

                    

$ ls | while read file; do ls $file; done

10

11

12

$

 

您还可以跨多行操作变量,比如将一条消息发送到标准输出,然后对 loopname 变量执行 Shell 运算(请参见 Shell 运算和进制转换部分)。尝试清单 34 中提供的示例:

 

 

清单 34. 使用管道读取的较长循环

                    

$ ls | while read file; do echo "The file is " `ls -i $file`;

echo "If the number were in hex, the value would be $((16#$file))"; done

The file is  100267120 10

If the number were in hex, the value would be 16

The file is  100267121 11

If the number were in hex, the value would be 17

The file is  100267122 12

If the number were in hex, the value would be 18

$

 

您可以在一个管道输入的 read 中读取多个值,如清单 35 中所示。

 

 

清单 35. 从一个管道读取多个变量

                    

$ ls -i | while read inode file; do echo "File $file has inode $inode"; done

File 10 has inode 100267120

File 11 has inode 100267121

File 12 has inode 100267122

$

 

实际运用

 

此结束部分将您在前面学到的诀窍和技术加以组合来实现在实际中有用的单命令行程序。它还包括一个简单的 Shell 脚本――执行任意进制的转换。

 

有用的单命令行程序

 

以下示例是执行有用功能的 Shell 单命令行程序样本。它们全部由本教程中描述的各种结构组成。

 

从当前目录中获取一组文件名恰好为两个字符长的文件,并使用 .ppm 扩展名为其重新命名:

 

for i in ??; { mv $i $i.ppm; }

 

使用 tar 和 Subshell 复制整个目录树,同时保持相同的文件权限:

 

( cd source ; tar pcf – * ) | ( cd target ; tar pxvf – )

 

读取二进制数并以十进制输出值:

 

read BINLOC;echo $((2#$BINLOC))

 

在 /usr/local 目录树中找到所有带 .mp3 扩展名的文件(这些文件的名称中可能包含空格字符),然后使用 bzip2 实用程序压缩这些文件:

 

find /usr/local -name "*.mp3" | while read name ; do bzip2 $name; done

 

将给定文件中所有十进制数的值以十六进制输出:

 

cat file | while read number ; do echo $((0x$number)); done

 

将给定文件中所有十进制数转换为十六进制的值,并将值输出到带有 .hex 扩展名的新文件中:

 

cat file | while read number ; do echo $((0x$number)) >> file.hex; done

 

构造重复十次的循环,以数字(从 0 到 90 以 10 递增)作为传递的参数运行 command:

 

i=0; while [ $i -ne 100 ]; do command $i; i=$(($i+10)); done

 

回页首

 

示例脚本:将数字转换为其他进制

 

本教程中讨论的一些诀窍在清单 36 中被组合在一起。它是一个示例脚本――baseconv,将数字从给定的输入进制转换为输出进制。为它提供输入进制和输出进制的值作为参数,然后它从键盘输入读取数字,直到读取了数字 0。

 

 

清单 36. 转换进制的简单脚本

                    

#!/bin/sh

# baseconv, convert numbers from one base to another.

#

 

NUMBER=1

while [ $NUMBER ]; do

    read -p "Input base: " IN

    read -p "Output base: " OUT

    read -p "Number: " NUMBER

    bc -ql <<- EOF

obase=$OUT

ibase=$IN

$NUMBER

EOF

done

 

当您把它保存到可执行文件后(请参见创建 Shell 脚本部分),尝试运行该文件,如清单 37 中所示:

 

 

清单 37. baseconv 脚本的输出

                    

$ ./baseconv

Input base: 10

Output base: 16

Number: 33

21

Input base: 2

Output base: 1100

Number: 101

5

Input base: 16

Output base: A

Number: ACA

2762

Input base: 10

Output base: 10

Number: Carriage return

$

 

结束语

 

总结

 

噢!本教程确实涵盖了许多内容,带您快速浏览了基本的 Shell 编程概念。在学习本教程的过程中,您了解了有关 Shell 编程的许多核心概念:连续循环、内联输入、读取键盘输入、进制转换及 Subshell 执行。您还了解到 Shell 代码片段如何能够作为单命令行程序直接从 Shell 提示符上运行,以及如何将它们放在同一个文件中作为可执行脚本。您可以从中学习一些最重要的脚本编程概念。您如果能综合运用在本教程以及本系列教程的前面部分学到的知识,那么您已成功地迈上 UNIX 专家之路。

 

 

发表评论

电子邮件地址不会被公开。 必填项已用 * 标注

您可以使用这些 HTML 标签和属性: <a href="" title=""> <abbr title=""> <acronym title=""> <b> <blockquote cite=""> <cite> <code> <del datetime=""> <em> <i> <q cite=""> <strike> <strong>