shell基础知识

环境变量
截断字符串

环境变量
在 bash 中定义环境变量:

myvar='This is my environment variable!'

以上命令定义了一个名为 “myvar” 的环境变量,并包含字符串 “This is my environment variable!”。

有几点注意:

1.在等号 “=” 的两边没有空格;

2.虽然在定义一个字时能够省略引号,但是当定义的环境变量值多于一个字时(包含空格或tab),引号是必须的;

3.虽然通常能够用双引号来替代单引号,但在上例中,这样做会导致错误。因为使用单引号禁用了称为扩展的 bash 特性,其中,特别字符和字符系列由值替换。例如,”!” 字符是历史扩展字符,bash 通常将其替换为前面输入的命令。尽管这个类似于宏的功能很便利,但我们现在只想在环境变量后面加上一个简单的感叹号,而不是宏。

环境变量的使用

$ echo foo$myvarbar

bash会困惑,到底扩展$m、$my、$myvar、$myvarbar…在这种情况下要用显式的花括号将他括起。

$ echo foo${myvar}bar

一定要记住:当环境变量没有用空白(空格或制表键)和周围文本分开时,要使用更明确的花括号形式。

当一个环境变量被export时,他能够自动地由以后运行的任何脚本或可执行环境使用。shell脚本能够使用shell的内置环境变量支持“到达”环境变量,而C语言能够使用getenv()函数调用。如下C代码示例:

#include <stdio.h>
#include <stdlib.h>
int main(void) {
    char *myenvvar=getenv("myvar");
    printf("The myvar environment variable is %s ",myenvvar);
}

直接执行,结果为:

The myvar environment variable is (null)

export myvar后,执行结果为:

The myvar environment variable is This is my environment variable!

使用unset myvar除去环境变量后,执行结果为:

The myvar environment variable is (null)

另外,也能够在一行定义并export环境变量,如:

export myvar=abc

截断字符串

截断字符串是将初始字符串截断成较小的单独块,他是一般 shell 脚本每天执行的任务之一。

这里有basename和dirname,basename返回路径的尾,dirname返回basename丢弃的“另”一个部分路径。

$ basename /etc/nginx/sites-available/default 
default
$ dirname /etc/nginx/sites-available/default 
/etc/nginx/sites-available

命令替换

如何创建一个包含可执行命令结果的环境变量,能够通过如下方法:

$ MYDIR=`dirname /usr/local/share/doc/foo/foo.txt`
$ echo $MYDIR
/usr/local/share/doc/foo

需要注意的是:在第一行,将要执行的命令以 反引号 括起。不是标准的单引号,而是键盘中通常位于Tab键之上的单引号。(注:单引号中的内容将会被强制显示,也就是说单引号中的命令不会被替换)
除了反引号“外,还能够使用$()来完成同样操作

$ MYDIR=$(dirname /usr/local/share/doc/foo/foo.txt)
$ echo $MYDIR
/usr/local/share/doc/foo

使用命令替换能够将任何命令或命令管道放在“或$()之间,并将其分配给环境变量。

像专业人员那样截断字符串

有时候我们需要执行更高级的字符串”截断”,如下例子:

$ MYVAR=foodforthought.jpg
$ echo ${MYVAR##*fo}
rthought.jpg
$ echo ${MYVAR#*fo}
odforthought.jpg

第一个echo,bash取得MYVAR,找到从字符串”foodforthought.jpg” 开始处开始、且匹配通配符 “*fo” 的最长子字符串,然后将其从字符串的开始处截去。
第二个echo,bash取得MYVAR,找到从字符串”foodforthought.jpg” 开始处开始、且匹配通配符 “*fo” 的最短子字符串,然后将其从字符串的开始处截去。

记忆方法:当搜索最长匹配时,使用 ##(因为 ## 比 # 长)。当搜索最短匹配时,使用 #。如何记住使用”#”字符来从字符串开始部分出去?在美国键盘上,shift-4 是 “$”,他是 bash 变量扩展字符。在键盘上,紧靠 “$” 左边的是 “#”。这样,能够看到:”#” 位于 “$” 的“开始处”,因此(根据我们的记忆法),”#” 从字符串的开始处除去字符。

同理,使用”%”来从尾部截去字符串:

$ MYFOO="chickensoup.tar.gz"
$ echo ${MYFOO%%.*}
chickensoup
$ echo ${MYFOO%.*}
chickensoup.tar

假如忘记了应该使用”#” 还是 “%”,则看一下键盘上的 3、4 和 5 键,然后猜出来。
更有另一种形式的变量扩展,来选择特定子字符串。

$ EXCLAIM=cowabunga
$ echo ${EXCLAIM:0:3}
cow
$ echo ${EXCLAIM:3:7}
abunga

请注意命令替换$()和截断字符串${}的区别

应用字符串截断

下面是个简单的shell脚本,这个脚本接受一个文档作为自变量,然后打印:该文档是否是个tar文档。

#!/bin/bash
 if [ "${1##*.}" = "tar" ]
 then
 echo This appears to be a tarball.
 else
 echo At first glance, this does not appear to be a tarball.
 fi

看一下上例使用的”if”语句。语句中使用了一个布尔表达式。在bash中,”=”比较运算符检查字符串是否相等。在bash中,任何布尔表达式都用方括号括起。
($1是传给脚本的第一个命令行自变量,$2是第二个,以此类推。)

If语句

if [ condition ]
then
action
fi

只有当condition为真时,该语句才执行操作,否则不执行操作,并继续执行”fi”之后的语句。

if [ condition ]
 then
 action
 elif [ condition2 ]
 then
 action2
 .
 .
 .
 elif [ condition3 ]
 then
 else
 actionx
 fi

以上”elif”形式将连续测试每个条件,并执行符合第一个真条件的操作。假如没有条件为真,则将执行”else”操作,假如有一个条件为真,则继续执行整个”if,elif,else”语句之后的行。

接收自变量

看一下如下的例子:

#! /bin/bash
echo name of script. is $ 0
echo first argument is $ 1
echo second argument is $ 2
echo seventeenth argument is $ 17
echo number of arguments is $#

Bash中将” $ 0 “扩展成从命令行调用的脚本名称,”$#”被扩展成传递给脚本的自变量数目。(请注意$ 和 0是连起来的)

在Bash编程中有时候需要一次引用任何命令行自变量,针对这种用途,bash实现了变量”$@”,他被扩展成任何用空格分开的命令行参数。

Bash编程结构

在各种编程语言中出现的”if”语句和”for”循环等标准编程结构,Bash有自己的版本。以下几节,将介绍几种bash结构,并演示这些结构及其和其他编程语言中结构的差异。

在C语言中,要比较特定文档是否比另一个文档新必须使用两个stat()调用和两个stat结构来进行手工比较。而在bash中,因为其内置了标准文档比 较运算符,因此,确定”/tmp/myfile 是否可读”和查看”$myvar 是否大于 4″相同容易。 看下面的例子:

if [ -z "$myvar" ]
then
echo "myvar is not defined"
fi

上例表示假如$myvar没有定义(即$myvar为0),则echo “……….”

bash中可使用的比较运算符如下:

文档比较运算符
-e filename    假如 filename存在,则为真                     [ -e /var/log/syslog ]
-d filename    假如 filename为目录,则为真                 [ -d /tmp/mydir ]
-f filename     假如 filename为常规文档,则为真          [ -f /usr/bin/grep ]
-L filename    假如 filename为符号链接,则为真          [ -L /usr/bin/grep ]
-r filename     假如 filename可读,则为真                     [ -r /var/log/syslog ]
-w filename    假如 filename可写,则为真                    [ -w /var/mytmp.txt ]
-x filename     假如 filename可执行,则为真                [ -L /usr/bin/grep ]
filename1-nt filename2 假如 filename1比 filename2新,则为真

例如 [ /tmp/install/etc/services -nt /etc/services ]
filename1-ot filename2 假如 filename1比 filename2旧,则为真

例如 [ /boot/bzImage -ot arch/i386/boot/bzImage ]

字符串比较运算符 (请注意引号的使用,这是防止空格扰乱代码的好方法)

-z string 假如 string长度为零,则为真 [ -z “$myvar” ]
-n string 假如 string长度非零,则为真 [ -n “$myvar” ]
string1= string2 假如 string1和 string2相同,则为真 [ “$myvar” = “one two three” ]
string1!= string2 假如 string1和 string2不同,则为真 [ “$myvar” != “one two three” ]

算术比较运算符

num1-eq num2 等于 [ 3 -eq $mynum ]
num1-ne num2 不等于 [ 3 -ne $mynum ]
num1-lt num2 小于 [ 3 -lt $mynum ]
num1-le num2 小于或等于 [ 3 -le $mynum ]
num1-gt num2 大于 [ 3 -gt $mynum ]
num1-ge num2 大于或等于 [ 3 -ge $mynum ]

有时,有几种不同方法来进行特定比较。如下:

 if [ "$myvar" -eq 3 ]
 then
 echo "myvar equals 3"
 fi
 if [ "$myvar" = "3" ]
 then
 echo "myvar equals 3"
 fi

上面两个比较执行相同的功能,但是第一个使用算术比较运算符,而第二个使用字符串比较运算符。

字符串比较说明

大多数时候,虽然能够不使用括起字符串和字符串变量的双引号,但这并不是好主意。因为假如环境变量中恰巧有一个空格或制表键,bash 将无法分辨,从而无法正常工作。这里有一个错误的比较示例:

if [ $myvar = "foo bar oni" ]
 then
 echo "yes"
 fi

在上例中,假如 myvar 等于 “foo”,则代码将按预想工作,不进行打印。但是,假如 myvar 等于 “foo bar oni”,则代码将因以下错误失败:
[: too many arguments

在这种情况下,”$myvar”(等于 “foo bar oni”)中的空格迷惑了 bash。bash 扩展 “$myvar” 之后,代码如下:
[ foo bar ni = “foo bar oni” ]
因为环境变量没放在双引号中,所以 bash 认为方括号中的自变量过多。能够用双引号将字符串自变量括起来消除该问题。请记住,假如养成将任何字符串自变量用双引号括起的习惯,将除去很多类似的编程错误。”foo bar oni” 比较 应该写成:

if [ "$myvar" = "foo bar oni" ]
 then
 echo "yes"
 fi

在调用环境变量的时候最好使用””将环境变量括起来。(注意:假如想引用环境变量的值,则不要使用单引号,因为单引号会禁用变量(和历史)扩展。如在上例中会只接受空格以前的字符)

循环结构:”for” 先看一个简单的例子:

#! /bin/bash
for x in one two three four
 do
 echo number $x
 done

输出:
number one
number two
number three
number four
这个例子中”for x”部分定义了一个名为”$x”的新环境变量(也称为循环控制变量),他的值被依次配置为”one”、”two”、”three”和”four”。每一次 赋值之后,执行一次循环体(“do”和”done”之间的代码)。在循环体内,象其他环境变量相同,使用标准的变量扩展语法来引用循环控制变量”$x”。 还要注意,”for”循环总是接受”in”语句之后的某种类型的字列表。在这个例子中,指定了四个英语字母,但是字列表也能够引用磁盘上的文档,甚至文档通配符。再看一个使用标准shell通配符的例子:

#! /bin/bash
 for myfile in /etc/r*
 do
 if [ -d "$myfile" ]
 then
 echo "$myfile (dir)"
 else
 echo "$myfile"
 fi
 done

输出:

/etc/rc.d (dir)
/etc/resolv.conf
/etc/resolv.conf~
/etc/rpc

以上代码列出在 /etc 中每个以 “r” 开头的文档。要做到这点,bash 在执行循环之前首先取得通配符 /etc/r*,然后扩展他,用字符串 /etc/rc.d /etc/resolv.conf /etc/resolv.conf~ /etc/rpc 替换。(也即循环语句被替换成for myfile in /etc/rc.d /etc/resolv.conf /etc/resolv.conf~ /etc/rpc )一旦进入循环,根据 myfile 是否为目录,”-d” 条件运算符用来执行两个不同操作。假如是目录,则将 “(dir)” 附加到输出行。
还能够在字列表中使用多个通配符、甚至是环境变量:

for x in /etc/r--? /var/lo* /home/drobbins/mystuff/* /tmp/${MYPATH}/*
 do
 cp $x /mnt/mydir
 done

虽然任何通配符扩展示例使用了 绝对路径,但也能够使用相对路径,如下所示:

for x in ../* mystuff/*
 do
 echo $x is a silly file
 done

再看个例子:

for x in /var/log/*
 do
 echo `basename $x` is a file living in /var/log
 done

看看如何使用”$@”的:

#!/usr/bin/env bash
for thing in "$@"
 do
 echo you typed ${thing}.
 done

输出:

$ allargs hello there you silly
 you typed hello.
 you typed there.
 you typed you.
 you typed silly.

Shell算术

在学习另一类型的循环结构之前,最好先熟悉如何执行 shell 算术。能够使用 shell 结构来执行简单的整数运算。只需将特定的算术表达式用 “$((” 和 “))” 括起,bash 就能够计算表达式。这里有一些例子:

$ echo $(( 100 / 3 ))
 33
 $ myvar="56"
 $ echo $(( $myvar + 12 ))
 68
 $ echo $(( $myvar - $myvar ))
 0 $ myvar=$(( $myvar + 1 ))
 $ echo $myvar
 57

更多的循环结构:”while” 和 “until”
只要特定条件为真,”while” 语句就会执行,其格式如下:

while [ condition ]
 do
 statements
 done

通常使用 “While” 语句来循环一定次数,比如,下例将循环 10 次:

myvar=0
 while [ $myvar -ne 10 ]
 do
 echo $myvar
 myvar=$(( $myvar + 1 ))
 done

能够看到,上例使用了算术表达式来使条件最终为假,并导致循环终止。
“Until” 语句提供了和 “while” 语句相反的功能:只要特定条件为 假 ,他们就重复。下面是个和前面的 “while” 循环具备同等功能的 “until” 循环:

myvar=0
 until [ $myvar -eq 10 ]
 do
 echo $myvar
 myvar=$(( $myvar + 1 ))
 done

Case 语句
Case 语句是另一种便利的条件结构。这里有一个示例片段:

case "${x##*.}" in
 gz)
 gzunpack ${SROOT}/${x}
 ;;
 bz2)
 bz2unpack ${SROOT}/${x}
 ;;
 *)
 echo "Archive format not recognized."
 exit
 ;;
 esac

在上例中,bash 首先扩展 “${x##*.}”。在代码中,”$x” 是文档的名称,”${x##.*}” 除去文档中最后句点后文本之外的任何文本。然后,bash 将产生的字符串和 “)” 左边列出的值做比较。在本例中,”${x##.*}” 先和 “gz” 比较,然后是 “bz2″,最后是 “*”。假如 “${x##.*}” 和这些字符串或模式中的任何一个匹配,则执行紧接 “)” 之后的行,直到 “;;” 为止,然后 bash 继续执行结束符 “esac” 之后的行。假如不匹配任何模式或字符串,则不执行任何代码行,在这个特别的代码片段中,至少要执行一个代码块,因为任何不和 “gz” 或 “bz2” 匹配的字符串都将和 “*” 模式匹配。

函数

在bash中也能够定义函数。函数甚至能够使用和脚本接受命令行自变量类似的方式来接受自变量。

tarview() {
 echo -n "Displaying contents of $1 "
 if [ ${1##*.} = tar ]
 then
 echo "(uncompressed tar)"
 tar tvf $1
 elif [ ${1##*.} = gz ]
 then
 echo "(gzip-compressed tar)"
 tar tzvf $1
 elif [ ${1##*.} = bz2 ]
 then
 echo "(bzip2-compressed tar)"
 cat $1 | bzip2 -d | tar tvf -
 fi
 }
 (echo -n 不换行)

上面定义了一个名为 “tarview” 的函数,他接收一个自变量,即某种类型的 tar 文档。在执行该函数时,他确定自变量是哪种 tar 文档类型(未压缩的、gzip 压缩的或 bzip2 压缩的),打印一行信息性消息,然后显示 tar 文档的内容。

$ tarview shorten.tar.gz
 Displaying contents of shorten.tar.gz (gzip-compressed tar)
 drwxr-xr-x ajr/abbot 0 1999-02-27 16:17 shorten-2.3a/
 -rw-r--r-- ajr/abbot 1143 1997-09-04 04:06 shorten-2.3a/Makefile
 -rw-r--r-- ajr/abbot 1199 1996-02-04 12:24 shorten-2.3a/INSTALL
 -rw-r--r-- ajr/abbot 839 1996-05-29 00:19 shorten-2.3a/LICENSE
 ....

名称空间

经常需要在函数中创建环境变量。虽然有可能,但是更有一个技术细节应该了解。在大多数编译语言(如 C)中,当在函数内部创建变量时,变量被放置在单独的局部名称空间中。因此,假如在 C 中定义一个名为 myfunction 的函数,并在该函数中定义一个名为 “x” 的自变量,则任何名为 “x” 的全局变量(函数之外的变量)将不受他的印象,从而消除了负作用。
在 C 中是这样,但在 bash 中却不是。在 bash 中,每当在函数内部创建环境变量,就将其添加到 全局名称空间。这意味着,该变量将重写函数之外的全局变量,并在函数退出之后继续存在:

#!/usr/bin/env bash
 myvar="hello"
 myfunc() {
 myvar="one two three"
 for x in $myvar
 do
 echo $x
 done
 }
 myfunc
 echo $myvar $x

运行此脚本时,他将输出 “one two three three”,这显示了在函数中定义的 “$myvar” 如何影响全局变量 “$myvar”,连同循环控制变量 “$x” 如何在函数退出之后继续存在(假如 “$x” 全局变量存在,也将受到影响)。
在这个简单的例子中,很容易找到该错误,并通过使用其他变量名来改正错误。但这不是正确的方法,解决此问题的最好方法是通过使用 “local” 命令,在一开始就预防影响全局变量的可能性。当使用 “local” 在函数内部创建变量时,将把他们放在 局部名称空间中,并且不会影响任何全局变量。这里演示了如何实现上述代码,以便不重写全局变量:

#!/usr/bin/env bash
 myvar="hello"
 myfunc() {
 local x
 local myvar="one two three"
 for x in $myvar
 do
 echo $x
 done
 }
 myfunc
 echo $myvar $x

此函数将输出 “hello” — 不重写全局变量 “$myvar”,”$x” 在 myfunc 之外不继续存在。在函数的第一行,我们创建了以后要使用的局部变量 x,而在第二个例子 (local myvar=”one two three””) 中,我们创建了局部变量 myvar, 同时 为其赋值。在将循环控制变量定义为局部变量时,使用第一种形式很方便,因为不允许说:”for local x in $myvar”。此函数不影响任何全局变量,鼓励您用这种方式设计任何的函数。只有在明确希望要修改全局变量时,才 不应该使用 “local”。创建局部环境变量时最好使用”local”。

发表评论

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