bash

来源:互联网 发布:学计算机编程需要多久 编辑:程序博客网 时间:2024/06/03 20:29

bash 是一个为GNU计划编写的Unix shell。它的名字是一系列缩写:Bourne-Again SHell — 这是关于Bourne shell(sh)的一个双关语(Bourne again / born again)。

参数说明编辑
-c字符串
若用-c参数,则bash从字符串中读入命令,如果字符串后还有变量就被设定为从$0开始的位置参数。
-i
若用-i参数,则bash是交互的。
-s
若用-s参数,则bash从标准输入中读入命令(在执行完-c带的命令之后。)直到输入exit。
-
单一的号表明参数执行完毕,并且屏蔽此后所跟参数,后面的所有变量都被看作是文件名。
-norc
如果bash是交互的,则不执行个人初始化文件:-/.bashrc,如果bash作为sh来运行,这个参数缺省是关闭的。
-noprofile
不执行系统范围的启动文件/etc/profile也不执行个人的启动文件-/.bash_profile,-/.bash_login或-/.profile,缺省情况下,bash作为登录的shell时以这些文件作为启动文件。
–refile文件名
如果bash是交互的,则以此文件作为bash的启动文件。替代-/.bashrc。
-version
在bash开始时显示此bash的版本号。
-quiet
不显示版本号和其他信息,这是缺省值。
-login
激活bash,伪装为登录shell。
-nobraceexpansion
不执行大括号扩展。
-nolineediting
在交互状态下不使用GNU的readline库去读取命令。即取消了命令行编辑功能。
-posix
改变bash的行为,使其符合Posix 1003.2规定的标准。

简单shell脚本

!/bin/bash

这一行表明,不管用户选择的是那种交互式shell,该脚本需要使用bash shell来运行。由于每种shell的语法大不相同,所以这句非常重要。

简单实例

下面是一个非常简单的shell脚本。它只是运行了几条简单的命令

#!/bin/bashecho "hello, $USER. I wish to list some files of yours"echo "listing files in the current directory, $PWD"ls  # 列出当前目录所有文件

首先,请注意第四行。在bash脚本中,跟在#符号之后的内容都被认为是注释(除了第一行)。Shell会忽略注释。这样有助于用户阅读理解脚本。 ?USERPWD都是变量。它们是bash脚本自定义的标准变量,无需在脚本中定义即可使用。请注意,在双引号中引用的变量会被展开(expanded)。“expanded”是一个非常合适的形容词:基本上,当shell执行命令并遇到$USER变量时,会将其替换为该变量对应的值。

变量

任何编程语言都会用到变量。你可以使用下面的语句来定义一个变量:

X="hello"

并按下面的格式来引用这个变量:

$X

更具体的说,$X表示变量X的值。关于语义方面有如下几点需要注意:

等于号两边不可以有空格!例如,下面的变量声明是错误的 :

X = hello

在我所展示的例子中,引号并不都是必须的。只有当变量值包含空格时才需要加上引号。例如:

X = hello world # 错误X = "hello world" # 正确

这是由于shell将每一行命令视为命令及其参数的集合,以空格分隔。 foo=bar就被视为一条命令。foo = bar 的问题就在于shell将空格分开的foo视为命令。同样,X=hello world的问题就在于shell将X=hello视为一条完整的命令,而”world”则被彻底无视(因为赋值命令不需其他参数)。

单引号 VS 双引号

基本上来说,变量名会在双引号中展开,单引号中则不会。如果你不需要引用变量值,那么使用单引号可以很直观的输出你期望的结果。 An example 示例

#!/bin/bashecho -n '$USER=' # -n选项表示阻止echo换行echo "$USER"echo "\$USER=$USER"  # 该命令等价于上面的两行命令

输出如下(假设你的用户名为elflord)) USER=elflordUSER=elflord

$USER=elflord $USER=elflord

从例子中可以看出,在双引号中使用转义字符也是一种解决方案。虽然双引号的使用更灵活,但是其结果不可预见。如果要在单引号和双引号之间做出选择,最好选择单引号。

使用引号封装变量

有时候,使用双引号来保护变量名是个很好的点子。如果你的变量值存在空格或者变量值为空字符串,这点就显得尤其重要。看下面这个例子:

#!/bin/bashX=""if [ -n $X ]; then     # -n 用来检查变量是否非空    echo "the variable X is not the empty string"fi

运行这个脚本,输出如下:

the variable X is not the empty string
为何?这是因为shell将$X展开为空字符串,表达式[-n]返回真值(因为改表达式没有提供参数)。再看这个脚本:

#!/bin/bashX=""if [ -n "$X" ]; then   # -n 用来检查变量是否非空         echo "the variable X is not the empty string"fi

在这个例子中,表达式展开为[ -n “”],由于引号中内容为空,因此该表达式返回false值。

在执行时展开变量

为了证实shell就像我上面说的那样直接展开变量,请看下面的例子:

#!/bin/bashLS="ls"LS_FLAGS="-al"$LS $LS_FLAGS $HOME

乍一看可能有点不好理解。其实最后一行就是执行这样一条命令:

Ls -al /home/elflord

(假设当前用户home目录为/home/elflord)。这就说明了shell仅仅只是将变量替换为对应的值再执行命令而已。

使用大括号保护变量

这里有一个潜在的问题。假设你想打印变量X的值,并在值后面紧跟着打印”abc”。那么问题来了:你该怎么做呢? 先试一试:

#!/bin/bashX=ABCecho "$Xabc"

这个脚本没有任何输出。究竟哪里出了问题?这是由于shell以为我们想要打印变量Xabc的值,实际上却没有这个变量。为了解决这种问题可以用大括号将变量名包围起来,从而避免其他字符的影响。下面这个脚本可以正常工作:

!/bin/bashX=ABCecho${X}abc”
#!/bin/bashX=ABCecho "${X}abc"

条件语句, if/then/elif

在某些情况下,我们需要做条件判断。比如判断字符串长度是否为0?判断文件foo是否存在?它是一个链接文件还是实际文件?首先,我们需要if命令来执行检查。语法如下:

if conditionthen    statement1    statement2    ..........fi

当指定条件不满足时,可以通过else来指定其他执行动作。

if conditionthen    statement1    statement2    ..........else    statement3fi

当if条件不满足时,可以添加多个elif来检查其他条件是否满足。

if condition1then    statement1    statement2    ..........elif condition2then    statement3    statement4    ........    elif condition3then    statement5    statement6    ........   

当相关条件满足时,shell会执行在相应的if/elif与下个elif或fi之间的语句。事实上,判断条件可以是任意命令,当且只当命令返回并且退出状态为0时,才会执行该条件块中的语句(换句话说,就是当命令成功返回时)。不过在本文的学习中,我们只会关注“test”或“[]”形式的条件判断。

Test命令与操作符

条件判断中的命令几乎都是test命令。test根据测试条件通过或失败来返回true或false(更准确的说是返回0或非0值)。如下所示:

test operand1 operator operand2

对某些测试来说,只需要一个操作数(operand2)通常是下面这种情况的简写:

[ operand1 operator operand2 ]

为了让我们的讨论更接地气一点,给出下面一些例子:

#!/bin/bashX=3Y=4empty_string=""if [ $X -lt $Y ]   # is $X less than $Y ? then    echo "$X=${X}, which is smaller than $Y=${Y}"fiif [ -n "$empty_string" ]; then    echo "empty string is non_empty"fiif [ -e "${HOME}/.fvwmrc" ]; then          # test to see if ~/.fvwmrc exists    echo "you have a .fvwmrc file"    if [ -L "${HOME}/.fvwmrc" ]; then      # is it a symlink ?          echo "it's a symbolic link    elif [ -f "${HOME}/.fvwmrc" ]; then    # is it a regular file ?        echo "it's a regular file"    fielse    echo "you have no .fvwmrc file"fi

需要注意的细节

Test命令的格式为“操作数< 空格 >操作符< 空格 >操作数”或者“操作符< 空格 >操作数”,这里特别说明必须要有这些空格,因为shell将没有空格的第一串字符视为一个操作符(以-开头)或者操作数。比如下面这个:

if [ 1=2 ]; then echo “hello”fi

它会打印出hello,这明显与预期结果是不一致的(因为shell只看到操作数1=2,没看到操作符)。

还有一种隐藏陷阱是未加引号的变量。像我们之前例子说的-n测试时变量须加引号的情形。其实,不管在什么情况下,加上引号总是没有坏处的,还有可能规避一些很奇葩的错误。因为有时候不加引号的变量扩展开的测试结果会让人非常困惑。例如:

#!/bin/bashX="-n"Y=""if [ $X = $Y ] ; then    echo "X=Y"fi

这个脚本打印出来的结果是错误的,因为shell将判断展开为 [ -n = ],但是”=”的长度不为0,所以条件判断通过从而导致输出结果为“X=Y”。

Test操作符简介

下图是test操作符的快速查询列表。当然这个列表并不全面,但记下这些就足够平常使用了(如果还需要了解其他操作符,可以查看man手册)。

operator produces true if… number of operands
-n operand non zero length 1
-z operand has zero length 1
-d there exists a directory whose name is operand 1
-f there exists a file whose name is operand 1
-eq the operands are integers and they are equal 2
-neq the opposite of -eq 2
= the operands are equal (as strings) 2
!= opposite of = 2
-lt operand1 is strictly less than operand2 (both operands should be integers) 2
-gt operand1 is strictly greater than operand2 (both operands should be integers) 2
-ge operand1 is greater than or equal to operand2 (both operands should be integers) 2
-le operand1 is less than or equal to operand2 (both operands should be integers) 2

循环

循环结构允许我们执行重复的步骤或者在若干个不同条目上执行相同的程序。Bash中有下面两种循环

for 循环
while 循环
For 循环

直接来个例子,来直观地感受for循环的语法。

#!/bin/bashfor X in red green bluedo    echo $Xdone

For循环会遍历空格分开的条目。注意,如果某一项含有空格,必须要用引号引起来,例子如下:

#!/bin/bashcolour1="red"colour2="light blue"colour3="dark green"for X in "$colour1" $colour2" $colour3"do    echo $Xdone

如果我们漏掉for循环中的引号,你能猜想出会发生什么吗?这个例子说明,除非你确认变量中不会包含空格,否则最好都用引号将变量保护起来。

在for循环中使用通配符

如果shell解析字符串时遇到号,会将它展开为所有匹配的文件名。当且仅当目标文件与号展开后的字符串一致才会匹配成功。例如,单独的号展开为当前目录的所有文件,中间以空格分开(包含隐藏文件)。

所以:

echo *

列出当前目录下的所有文件和目录。

echo *.jpg

列出所有的jpeg图片格式的文件。

echo ${HOME}/public_html/*.jpg

列出home目录中public_html目录下的所有jpeg文件。

正是由于这种特性,使得我们可以很方便的来操作目录和文件,尤其是和for循环结合使用时,更是便利。例子如下:

#!/bin/bashfor X in *.htmldo        grep -L '<UL>' "$X"done

打印出当前目录下所有不包含

    字段的html文件。

While 循环

当给定条件为真值时,while循环会重复执行。例如:

#!/bin/bashX=0while [ $X -le 20 ]do    echo $X    X=$((X+1))done

这样导致这样的疑问: 为什么bash不能使用C风格的for循环呢?

for (X=1,X<10; X++)

这也跟bash自身的特性有关,之所以不允许这种for循环是由于:bash是一种解释性语言,因此其运行效率比较低。也正是由于这个原因,高负荷迭代是不允许的。

命令替换

Bash shell有个非常好用的特性叫做命令替换。允许我们将一个命令的输出当做另一个命令的输入。比如你想要将命令的输出赋值给变量X,你可以通过变量替换来实现。

有两种命令替换的方式:大括号扩展和反撇号扩展。

大括号扩展: $(commands) 会展开为命令commands的输出结果。并且允许嵌套使用,所以commands中允许包含子大括号扩展。

反撇好扩展:将commands扩展为命令commands的输出结果。不允许嵌套。

这里有一个例子:

#!/bin/bashfiles="$(ls)"web_files=`ls public_html`echo "$files"      # we need the quotes to preserve embedded newlines in $filesecho "$web_files"  # we need the quotes to preserve newlines X=`expr 3 * 2 + 4` # expr evaluate arithmatic expressions. man expr for details.echo "$X"

$()替换方式的优点不言自明:非常易于嵌套。并且大多数bourne shell的衍生版本都支持(POSIX shell 或者更好的都支持)。不过,反撇号替换更简单明了,即使是最基本的shell它也提供了支持(任意版本的#!/bin/sh都可以)。