首页 > 技术文章 > (033) Linux之字符串和数字

jplatformx 2015-03-23 09:40 原文

十年运维系列之基础篇 - Linux

作者:曾林 

联系:1494445739@qq.com

网站:www.jplatformx.com

版权:文章未经同意请勿转载


一、引言

  计算机程序其实就是处理数据。前面的章节主要从文件层面讲解了数据的处理。然而,很多编程问题需要用到更小的数据单元,例如字符串和数字来解决。

  本章将学习几个用于操作字符串和数字的shell脚本特性。shell还提供了多种字符串操作的参数扩展。

 

二、参数扩展

  虽然以前的章节中谈论过参数扩展,但是因为绝大部分的参数扩展使用在脚本文件而不是在命令行,所以我们未加详细解释,在这之前已经使用了某些形式的参数扩展,例如shell变量。shell提供了多种参数的扩展形式。

 

1. 基本参数

  参数扩展的最简单形式体现在平常对变量的使用中。举例来说,$a扩展后成为变量a所包含的内容,无论a包含什么。简单参数也可以被花括号包围,例如${a}。这对扩展本身毫无影响。但是,当变量相邻于其他文本时,则必须使用括号,否则可能让shell混淆。看下面的例子,我们试图以附加字符串_file到变量a内容后的方式新建一个文件名。

  同样可见,大于9的位置参数可以通过给相应数字加上括号来访问。例如访问到第11个位置参数,可以这样做——${11}。

 

2. 空变量扩展的管理

  有的参数扩展用于处理不存在的变量和空变量。这些参数扩展在处理缺失的位置参数和给参数赋默认值时很有用处。这种参数扩展形式如下。

${parameter:-word}

  如果parameter未被设定(比如不存在)或者是空参数,则其扩展为word的值;如果parameter非空,则扩展为parameter的值。执行如下图所示:

  以下是另外一种扩展形式,在里面使用等号,而非连字符号。

${parameter:=word}

  如果parameter未被设定或者为空,则其扩展为word的值;此外,word的值也将赋给parameter。如果parameter非空,则扩展为parameter的值。

  需要注意一点的是,使用“=”而不是连字符“-”的话,这样的方式不能应用于位置参数和其他特殊参数。如下图所示:

  我们还可以使用问号,如下表示:

${parameter:?word}

  如果parameter未设置或者为空的话,这样扩展会致使脚本出错而退出,并且word内容输出到标准错误。如果parameter非空,则扩展结果为parameter的值。具体如下图:

  如果我们使用一个加号,如下所示:

${parameter:+word}

  若parameter未设定或者为空,将不产生任何的扩展。若parameter非空,word的值将取代parameter的值;然而,parameter的值并不发生变化。

 

3. 返回变量名的扩展 

  shell具有返回变量名的功能。这种功能在相当特殊的情况下才会被用到。返回变量名的语法如下:

${!prefix*} 或 ${!prefix@}

  该扩展返回当前以prefix开头的变量名。根据bash文档,这两种扩展形式执行效果一模一样。下面的例子中,我们列出了环境变量中所有以BASH开头的变量。

 

4. 字符串操作 

  对字符串的操作,存在着大量的扩展集合。例如下面的扩展式:

${#parameter}

  扩展为parameter内包含的字符串的长度。一般来说,参数parameter是个字符串。然而,如果参数parameter是“@”或者“*”,那么扩展结果就是位置参数的个数。样例如下:

${parameter:offset}
${parameter:offset:length}

  这个扩展用来提取一部分包含在参数parameter中的字符串。扩展以offset字符开始,直到字符串的末尾,除非length特别指定。样例如下:

  如果offset的值为负,默认表示它从字符串末尾开始,而不是字符串开头。注意,负值前必须要有一个空格,以防和“${parameter:-word}”扩展混淆。如果有length(长度)的话,length不能小于0。样例如下:

  

${parameter:#pattern}
${parameter:##pattern}

  根据pattern定义,这些扩展去除了包含在parameter中的字符串的主要部分。pattern是一个通配符模式,类似那些用于路径名的扩展。两种形式的区别在于"#"用于去除最短匹配,而"##"形式去除最长匹配。样例如下:

  

${parameter%pattern}
${parameter%%pattern}

  这些扩展与上述的“#”和“##”扩展相同,除了一点——它们从参数包含的字符串末尾去除文本,而非字符串开头。样例如下:

${parameter/pattern/string}
${parameter//pattern/string}
${parameter/#pattern/string}
${parameter/%pattern/string}

  这个扩展在parameter的内容上执行搜索和替换非常有效。如果文本被发现和通配符pattern一致,就被替换为string的内容。通常形式如下,只有第一个出现的pattern被替换。在"//"形式下,所有的pattern都被替换。“/#”形式要求匹配出现在字符串的开头,"/%"形式则要求匹配出现在字符串的末尾。"/string"可以省略,不过和pattern匹配的文本都会被删除。样例如下:

 

三、算术计算和扩展

  前面我们学习了算术扩展,用来对整数进行算术计算。它的基本形式如下所示:

$((expression))

  其中expression是一个有效的算术表达式。

  这和用于算术运算的复合命令“(())”有关。

 

1. 数字进制

  我们已经知道了八进制和十六进制的数字。在算术表达式中,shell支持任何进制表示的整数。下表列出了基本数字进制的描述。

符号 描述
Number 默认情况下,number没有任何符号,将作为10进制数字
0number 在数字表达式中,以0开始的数字被认为是八进制数字
0xnumber 十六进制符号
base#number base进制的number

  结合上面的介绍,展示如下的例子:

  

2. 一元运算符

  有两种一元运算符:+和-。它们分别被用来指示一个数字是正或是负。

 

3. 简单算术

  下表展示了普通算术运算符。

操作符 描述
+ 加法
- 减法
* 乘法
/ 除法
** 求幂
% 取模(余数)

  这里大多数操作符具有自描述性,但是整数除法和取模需要更深入的讨论。

  由于shell的算术运算符仅适用于整数,除法的结果永远是完整的数字。如下所示:

  

4. 赋值

  有关算术运算符的赋值操作,可以这样来使用。

  上例中,先给变量赋空值,并确认它确实为空。接着,执行一个以复杂命令((foo=5))为条件的if语句。整个过程有两件有趣的事情。(1)它给变量foo赋值为5。(2)因为赋值语句是成功的,所以条件为true。

  注:记住上式中“=”的确切含义很重要。单个的“=”执行赋值,即foo=5意味着“让foo等于5”。两个“==”用来判断是否相等,即“foo==5”意味着“foo是否等于5?”这可能十分令人费解,因为test命令认为单个的“=”判断字符串是否相当。但这也正是另一个使用新式的“[[]]”和“(())”混合命令代替test的理由。

  此外,除了“=”之外,shell还提供了一些相当有用的赋值语句,如下表所示:

运算符 描述
parameter = value 简单赋值运算。赋予parameter值为value
parameter += value 加法运算。等价于parameter = parameter + value
parameter -= value 减法运算。等价于parameter = parameter = value
parameter *= value 乘法运算。等价于parameter = parameter * value
parameter /= value 除法运算。等价于parameter = parameter / value
parameter %= value 取模运算。等价于parameter = parameter % value
parameter++ 变量后增量运算。等价于parameter=parameter+1
parameter-- 变量后减量运算。等价于parameter=parameter-1
++parameter 变量前增量运算。等价于parameter=parameter+1
--parameter 变量前减量运算。等价于parameter=parameter-1

  这些赋值操作为很多常见算术任务提供了一种快捷方式。增量(++)和减量(--)运算特别有意义,它们以1为间隔增加或减少参数的值。这种风格的表示法是从C编程语言衍生而来,并且已经被其他几种编程语言所采用,其中就包括bash。

  对于大部分shell应用,前置运算操作符则是最常用的。

 

5. 位操作 

  有一种操作符以一种非同寻常的方式巧妙地进行数字运算,这些操作符在位层面执行运算。它们被用于特定的低层任务中,常用来位标志的设置和读取。下表列出了位操作符。

操作符 描述
~ 按位取反。将数字里的每一位取反
<< 逐位左移。将数字里面的每一位向左移动
>> 逐位右移。将数字里面的每一位向右移动
& 按位与。对数字里的每一位执行与操作
| 按位或。对数字里的每一位执行或操作
^ 按位异或。对两个数字的每一位执行异或操作

  注意,除了按位取反之外,也存在相应的赋值操作(例如“<<=”)。

  这里将示范使用逐位左移操作,产生2的次方的一串数字。具体如下图所示:

 

6. 逻辑操作

  复合命令“(())”支持多种比较操作。有另外几种可以被用于判断逻辑是否成立的操作。具体如下表所示:

操作符 描述
<= 小于或等于
>= 大于或等于
< 小于
> 大于
== 等于
!= 不等于
&& 逻辑与
|| 逻辑或
expr1?expr2:expr3 比较(三元组)操作。如果表达式expr1非零(算术为true),那么执行expr2,否则执行expr3

  当使用逻辑操作的时候,表达式遵循算术逻辑的规则。这就是说,值为零的表达式为false,而非零表达式为true。如下所示:"(())"复合命令将结果映射到shell的正常退出代码。如下图所示:

  最奇怪的逻辑操作是三元操作。这个操作执行一个独立的逻辑测试。它可以被用作某种意义上的if/then/else语句。它作用于三个算术表达式上(不可以是字符串),并且如果第一个表达式为真(或非0),就执行第二个表达式,否则执行第三个表达式。我们可以在命令行中尝试一下。如下图所示:

   请注意在表达式内的赋值操作并不能简单使用。当试图这样做时,bash将输出一个错误。如下图:

  这个问题可以通过使用括号包围赋值表达式来解决。如下图所示:

 

四、bc:一种任意精度计算语言

  我们已经了解了shell可以处理所有种类的整数运算,但是如果需要执行更高级的数学运算,或者甚至使用浮点数怎么办呢?答案是无法实现,至少无法用shell直接实现。为了达到这个目的,我们需要使用一个外部程序。这个外部程序就是bc。它是一个专门的计算器程序,大多数Linux系统都支持这个程序。

  bc程序读取一个使用类c语言编写的程序文件,并执行它。bc脚本可以是一个单独的文件,也可以从标准输入中读取。bc语言支持很多功能,包括变量、循环以及由程序员自己定义的函数。下面可以从一个非常浅显的例子开始。如下图所示:

  脚本的第一行是一个注释。bc使用和C编程语言同样的注释语法。注释可以跨域多行,以/*开始,以*/结束。

 

1. bc的使用

  如果将上述bc脚本保存为foo.bc,那么就可以像上图中展示的那样来运行它。如下代码所示:

bc foo.bc

  如果仔细看看,可以在最底部版权信息的后面看到运行结果。这些信息可以通过-q(quiet)选项禁止显示。

  bc也可以交互地使用,如下所示:

  当交互式使用bc时,只需简单地输入运算值,计算结果就会立刻被显示出来。使用bc命令中的quit结束交互会话。

  通过标准输入传递一个脚本到bc亦是可行的,如下所示:

  既然支持标准输入,那么意味着可以使用嵌入文档、嵌入字符串和管道传递脚本。下面是一个嵌入字符串的例子。

bc <<< "8*8"

 

推荐阅读