shell脚本

摘录整理自:http://billie66.github.io/TLCL/book/

为了成功地创建和运行一个 shell 脚本,我们需要做三件事情:

  1. 编写一个脚本。 Shell 脚本就是普通的文本文件。所以我们需要一个文本编辑器来书写它们。最好的文本 编辑器都会支持语法高亮,这样我们就能够看到一个脚本关键字的彩色编码视图。语法高亮会帮助我们查看某种常见 错误。为了编写脚本文件,vim,gedit,kate,和许多其它编辑器都是不错的候选者。

  2. 使脚本文件可执行。 系统会相当挑剔不允许任何旧的文本文件被看作是一个程序,并且有充分的理由! 所以我们需要设置脚本文件的权限来允许其可执行。

  3. 把脚本放置到 shell 能够找到的地方 当没有指定可执行文件明确的路径名时,shell 会自动地搜索某些目录, 来查找此可执行文件。为了最大程度的方便,我们会把脚本放到这些目录当中。

脚本文件格式
#!/bin/bash
# This is our first script.
echo 'Hello World!'
#!字符序列是一种特殊的结构叫做 shebang。 这个 shebang 被用来告诉操作系统将执行此脚本所用的解释器的名字。每个 shell 脚本都应该把这一文本行作为它的第一行。
文本行中,# 符号之后的所有字符都会被忽略。
保存文件并使该文件具有可执行权限后,就可以执行该脚本文件了。如果将该文件存放在PATH环境变量某一路径,则可以直接执行该文件,而不必指明路径,即把脚本放置在shell能够找到的地方(例:指明路径执行./hello_world  直接执行hello_world)PATH路径可通过指令echo $PATH查看

文本输出方法除了使用echo外,还可以使用here document,或者叫做 here script。一个 here document 是另外一种 I/O 重定向形式,我们在脚本文件中嵌入正文文本,然后把它发送给一个命令的标准输入。它这样工作:
command << token
text
token
这里的 command 是一个可以接受标准输入的命令名,token 是一个用来指示嵌入文本结束的字符串。如:
#!/bin/bash
# Program to output a HTML file
cat << _EOF_
<HTML>
         <HEAD>
                <TITLE>web page name</TITLE>
         </HEAD>
         <BODY>
                page body
         </BODY>
</HTML>
_EOF_
该脚本使用 cat 命令和一个 here document。这个字符串_EOF_(意思是“文件结尾”, 一个常见用法)被选作为 token,并标志着嵌入文本的结尾。注意这个 token 必须在一行中单独出现,并且文本行中 没有末尾的空格。 那么使用一个 here document 的优点是什么呢?它很大程度上和 echo 一样,除了默认情况下,here documents 中的单引号和双引号会失去它们在 shell 中的特殊含义。
运行该脚本时,只需将结果重定向到一个html文件,即可通过浏览器查看内容
如果我们把重定向操作符从 “<<” 改为 “<<-”,shell 会忽略在此 here document 中开头的 tab 字符。 这就能缩进一个 here document,从而提高脚本的可读性,即以下两个脚本效果一样
#!/bin/bash
cat << _EOF_
hello world
this is my program
_EOF_
#!/bin/bash
cat <<- _EOF_
    hello world
    this is my program
_EOF_

变量
#!/bin/bash
# Program to output a system information page
title="System Information Report"
echo "<HTML>
        <HEAD>
                <TITLE>$title</TITLE>
        </HEAD>
        <BODY>
                <H1>$title</H1>
        </BODY>
</HTML>"
许多编程语言中的变量在使用前必须声明或者定义,shell脚本在这一点上非常宽松。当shell遇到一个变量时它会自动地创建该变量,这方便我们编写脚本,同时也要求我们不能拼写错误,否则使用时会得到一个空变量。
给变量赋值:
variable=value
variable是变量的名字,value是一个字符串。不同于一些其它的编程语言,shell 不会在乎变量值的类型;它把它们都看作是字符串。注意在赋值过程中,变量名,等号和变量值之间必须没有空格。
和其它编程语言一样,shell脚本的变量也分为全局变量和局部变量。在shell函数中通过在变量前加上单词local来定义局部变量,作用域局限于定义它的函数,函数执行完,该局部变量也就不存在了。(在shell函数中定义的变量如果前面没有local单词,则该变量依然是全局变量)

shell函数
在shell脚本中用一行代码实现一个功能的方法有两种。我们可以分别编写一个脚本,并把它们放置到 环境变量 PATH 所列出的目录下(如我们编写了一个shell脚本hello_world,并放在PATH路径,则在其它脚本里可以直接调用hello_world来实现其功能),或者我们也可以把这些脚本作为 shell 函数嵌入到我们的程序中。 Shell 函数有两种语法形式:
function name {
    commands
    return
}
name () {
    commands
    return
}
name 是函数名,commands 是一系列包含在函数中的命令。上面两种形式等价,可以交替使用。
#!/bin/bash
function funct
{
    echo "Step 2"
    return
}
    echo "Step 1"
    funct
    echo "Step 3"
注意为了使函数调用被识别出是 shell 函数,而不是被解释为外部程序的名字,所以在脚本中 shell 函数定义必须出现在函数调用之前。
一个函数必须至少包含一条命令。这条 return 命令(是可选的)满足要求。

if分支语句
if分支语句结构为:
if [ expression ]; then
     commands
else
     commands
fi
a=5
if [ $a = 5 ]; then
     echo "a=5"
else
     echo "a!=5"
fi
注意if判断条件用到了多个空格,其中方括号[]左半部分[左右必须各有一个空格,右半部分]的左边必须有一个空格;等号=左右各有一个空格
if语句可以多重嵌套,即commands语句可以包含有另外的if语句。
if语句判断expression是否为真涉及到退出状态这个概念。
当命令执行完毕后,命令(包括我们编写的脚本和 shell 函数)会给系统发送一个值,叫做退出状态。 这个值是一个 0 到 255 之间的整数,说明命令执行成功或是失败。按照惯例,一个零值说明成功,其它所有值说明失败。If 语句真正做的事情是计算命令执行成功或失败,如果 if 之后跟随一系列命令,则将计算列表中的最后一个命令
[me@linuxbox ~]$ if false; true; then echo "It's true."; fi
It's true.
if语句判断[ expression ]可以使用test expression代替,两者等价,但[ expression ]较为常用
if语句的文件判断expression有:
表达式 如果为真
file1 -ef file2 file1 和 file2 拥有相同的索引号(通过硬链接两个文件名指向相同的文件)。
file1 -nt file2 file1新于 file2。
file1 -ot file2 file1早于 file2。
-b file file 存在并且是一个块(设备)文件。
-c file file 存在并且是一个字符(设备)文件。
-d file file 存在并且是一个目录。
-e file file 存在。
-f file file 存在并且是一个普通文件。
-g file file 存在并且设置了组 ID。
-G file file 存在并且由有效组 ID 拥有。
-k file file 存在并且设置了它的“sticky bit”。
-L file file 存在并且是一个符号链接。
-O file file 存在并且由有效用户 ID 拥有。
-p file file 存在并且是一个命名管道。
-r file file 存在并且可读(有效用户有可读权限)。
-s file file 存在且其长度大于零。
-S file file 存在且是一个网络 socket。
-t fd fd 是一个定向到终端/从终端定向的文件描述符 。 这可以被用来决定是否重定向了标准输入/输出错误。
-u file file 存在并且设置了 setuid 位。
-w file file 存在并且可写(有效用户拥有可写权限)。
-x file file 存在并且可执行(有效用户有执行/搜索权限)。
字符串判断expression有:
表达式 如果为真...
string string 不为 null。
-n string 字符串 string 的长度大于零。
-z string 字符串 string 的长度为零。

string1 = string2

string1 == string2

string1 和 string2 相同. 单或双等号都可以,不过双等号更受欢迎。
string1 != string2 string1 和 string2 不相同。
string1 > string2 sting1 排列在 string2 之后。
string1 < string2 string1 排列在 string2 之前。
警告:这个 > 和 <表达式操作符必须用引号引起来(或者是用反斜杠转义), 当与 test 一块使用的时候。如果不这样,它们会被 shell 解释为重定向操作符,造成潜在地破坏结果。
整形判断expression有:
表达式 如果为真...
integer1 -eq integer2 integer1 等于 integer2.
integer1 -ne integer2 integer1 不等于 integer2.
integer1 -le integer2 integer1 小于或等于 integer2.
integer1 -lt integer2 integer1 小于 integer2.
integer1 -ge integer2 integer1 大于或等于 integer2.
integer1 -gt integer2 integer1 大于 integer2.
目前的 bash 版本包括一个复合命令,作为加强的 test 命令替代物。它使用以下语法:
[[ expression ]]
它的功能类似于原来的[ expression ],只是增加了一些功能。
功能1:增加了一个重要的新的字符串表达式
string1 =~ regex
其返回值为真,如果 string1匹配扩展的正则表达式 regex。这就为执行比如数据验证等任务提供了许多可能性。 
功能2:增加了==操作符来支持类型匹配
除了 [[ ]] 复合命令之外,bash 也提供了 (( )) 复合命令,其有利于操作整数。它支持一套完整的算术计算。
(( ))被用来执行算术真测试。如果算术计算的结果是非零值,则一个算术真测试值为真。
[me@linuxbox ~]$ if ((1)); then echo "It is true."; fi
It is true.
[me@linuxbox ~]$ if ((0)); then echo "It is true."; fi
[me@linuxbox ~]$
我们可以使用罗技操作符来表达更加复杂的表达式
操作符 测试 [[ ]] and (( ))
AND -a &&
OR -o ||
NOT ! !
语法:
command1 && command2
command1 || command2
对于 && 操作符,先执行 command1,如果并且只有如果 command1 执行成功后, 才会执行 command2。对于 || 操作符,先执行 command1,如果并且只有如果 command1 执行失败后, 才会执行 command2。

读取键盘输入
read命令被用来从标准输入读取单行数据,语法格式如下:
read [-options] [variable...]
这里的 options 是下面列出的可用选项中的一个或多个,也可不用; variable 是用来存储输入数值的一个或多个变量名。 如果没有提供变量名,shell 变量 REPLY 会包含数据行。
选项 说明
-a array 把输入赋值到数组 array 中,从索引号零开始。
-d delimiter 用字符串 delimiter 中的第一个字符指示输入结束,而不是一个换行符。
-e 使用 Readline 来处理输入。这使得与命令行相同的方式编辑输入。
-n num 读取 num 个输入字符,而不是整行。
-p prompt 为输入显示提示信息,使用字符串 prompt。
-r Raw mode. 不把反斜杠字符解释为转义字符。
-s Silent mode. 不会在屏幕上显示输入的字符。当输入密码和其它确认信息的时候,这会很有帮助。
-t seconds 超时. 几秒钟后终止输入。read 会返回一个非零退出状态,若输入超时。
-u fd 使用文件描述符 fd 中的输入,而不是标准输入。
使用例子:
#!/bin/bash
echo -n "Please enter an integer -> "
read int
echo "value that enter is $int"
使用带有 -n 选项的 echo 命令,其输出结果会删除末尾的换行符,即不换行。 然后使用 read 来读入变量 int 的数值。
read 可以给多个变量赋值
#!/bin/bash
echo -n "Please enter three integers -> "
read int1 int2 int3
echo "$int1 $int2 $int3"
如果 read 命令接收到变量值数目少于期望的数字,那么额外的变量值为空;如果接收到过多输入,则多余的输入数据会被包含到最后一个变量中。如果 read 命令之后没有列出变量名,则一个 shell 变量,REPLY,将会包含 所有的输入:
#!/bin/bash
echo -n "Please enter an integers -> "
read
echo "$REPLY"
使用各种各样的选项,我们能用 read 完成有趣的事情。
#!/bin/bash
# read-single: read multiple values into default variable
read -p "Enter one or more values > "
echo "REPLY = $REPLY"
我们使用read获取标准输入时,通常使用一个或多个空格来分开输入的单词,并被read赋值给单独的变量。这种行为是由一个shell变量IFS(内部字符分隔符)配置的,IFS 的默认值包含一个空格,一个 tab,和一个换行符,每一个都会把字段分割开。我们可以修改IFS的值,改变分隔符。
#!/bin/bash
IFS=","
read -p "please enter three integers->" int1 int2 int3 
echo "$int1"
echo "$int2"
echo "$int3"
此时输入的数值应该以逗号分隔,否则输入的所有数值将赋给int1.
常用的做法是先存储 IFS 的值,然后赋一个新值给IFS,再执行 read 命令,最后把 IFS 恢复原值。

while循环
while 命令的语法是:
while [ expression ]; do
    commands
done
和 if 一样, while 计算一系列命令的退出状态。只要退出状态为零,它就执行循环内的命令。因此,前面的if语句判断表达式也可用于while语句。
bash 提供了两个内部命令来控制while循环。 break 命令立即终止一个循环, 且程序继续执行循环之后的语句。 continue 命令导致程序跳过循环中剩余的语句,且程序继续执行 下一次循环。 
shell脚本编程中还有一个命令与while功能相似,until命令,语法格式如下:
until [ expression ]; do
    commands
done
当遇到一个非零退出状态时,while语句退出循环;当遇到一个零退出状态时,until语句退出循环。也就是说,当表达式为真时,while语句执行循环;当表达式为假时,until语句执行循环。

case分支语句
case语句的语法格式为:
case word in
    pattern commands ;; ...
esac
如:
#!/bin/bash
read -p "please enter an integer->" int
case $int in
0)    echo "int=0";;
1)    echo "int=1";;
*)    echo "not equal to 0 or 1";;
esac
当与之相匹配的模式找到之后,就会执行与该模式相关联的命令。若找到一个模式之后,就不会再继续寻找。
这里 case 语句使用的模式和路径展开中使用的那些是一样的。模式以一个 “)” 为终止符。这里是一些常用的模式类型:
模式 描述
a) 若单词为 “a”,则匹配
[[:alpha:]]) 若单词是一个字母字符,则匹配
???) 若单词只有3个字符,则匹配
*.txt) 若单词以 “.txt” 字符结尾,则匹配
*) 匹配任意单词。把这个模式做为 case 命令的最后一个模式,是一个很好的做法, 可以捕捉到任意一个与先前模式不匹配的数值;也就是说,捕捉到任何可能的无效值。
可以使用竖线字符作为分隔符,把多个模式结合起来,这就创建了一个 “或” 条件模式。这对于处理诸如大小写字符很有用处。
上面展示的case语句找到一个匹配的模式后就执行该模式相关的命令,执行完就退出case语句。若想case语句能够匹配多个选项,可以使用;;&来作为行动的终止符
#!/bin/bash
# test a character
read -n 1 -p "Type a character > "
case $REPLY in
    [[:upper:]])    echo "'$REPLY' is upper case." ;;&
    [[:lower:]])    echo "'$REPLY' is lower case." ;;&
    [[:alpha:]])    echo "'$REPLY' is alphabetic." ;;&
    [[:digit:]])    echo "'$REPLY' is a digit." ;;&
    [[:graph:]])    echo "'$REPLY' is a visible character." ;;&
    [[:punct:]])    echo "'$REPLY' is a punctuation symbol." ;;&
    [[:space:]])    echo "'$REPLY' is a whitespace character." ;;&
    [[:xdigit:]])   echo "'$REPLY' is a hexadecimal digit." ;;&
esac
添加的 “;;&” 允许 case 语句继续执行下一条测试,而不是简单地终止运行。

for循环
传统的for命令语法格式为:
for variable [in words]; do
    commands
done
这里的 variable 是一个变量的名字,这个变量在循环执行期间会增加,words 是一个可选的条目列表, 其值会按顺序赋值给 variable,commands 是在每次循环迭代中要执行的命令。
如:
#!/bin/bash
for i in {A..D};do
echo "$i"
done
如果省略掉 for 命令的可选项 words 部分,for 命令会默认处理位置参数,从$1开始(后面有介绍)。
最新版本的 bash 已经添加了第二种格式的 for 命令语法,该语法相似于 C 语言中的 for 语法格式。
for (( expression1; expression2; expression3 )); do
    commands
done
这里的 expression1,expression2,和 expression3 都是算术表达式,commands 是每次循环迭代时要执行的命令。expression1 用来初始化循环条件,expression2 用来决定循环结束的时机,还有在每次循环迭代的末尾会执行 expression3。
#!/bin/bash
# simple_counter : demo of C style for command
for (( i=0; i<5; i=i+1 )); do
    echo $i
done

位置参数
位置参数用于接收和处理命令行选项和参数,它是一个变量集合,这些变量按照从0到9命名,举例说明:
#!/bin/bash
echo "$0"
echo "$1"
echo "$2"
假设该脚本名为posit_param,位于/root/bin目录下,在终端执行命令:posit_param param1 param2,则结果如下:
[root@localhost ~]# posit_param param1 param2
/root/bin/posit_param
param1
param2
实际上通过参数展开方式你可以访问的参数个数多于9个。只要指定一个大于9的数字,用花括号把该数字括起来就可以。 例如 ${10}, ${55}, ${211},等等。
另外 shell 还提供了一个名为 $#,可以得到命令行参数个数的变量。
shell提供了一个shift命令来处理大量的位置参数。执行一次 shift 命令, 就会导致所有的位置参数 “向下移动一个位置”。事实上,用 shift 命令也可以 处理只有一个参数的情况(除了其值永远不会改变的变量 $0),即每次 shift 命令执行的时候,变量 $2 的值会移动到变量 $1 中,变量 $3 的值会移动到变量 $2 中,依次类推。 变量 $# 的值也会相应的减1。
 shell 提供了两种特殊的参数。他们二者都能扩展成完整的位置参数列表,但略有不同。
参数 描述
$* 展开成一个从1开始的位置参数列表。当它被用双引号引起来的时候,展开成一个由双引号引起来 的字符串,包含了所有的位置参数,每个位置参数由 shell 变量 IFS 的第一个字符(默认为一个空格)分隔开。
$@ 展开成一个从1开始的位置参数列表。当它被用双引号引起来的时候, 它把每一个位置参数展开成一个由双引号引起来的分开的字符串。
下面这个脚本在程序中展示了这些特殊参数:
#!/bin/bash
# posit-params3 : script to demonstrate $* and $@
print_params () {
    echo "\$1 = $1"
    echo "\$2 = $2"
    echo "\$3 = $3"
    echo "\$4 = $4"
}
pass_params () {
    echo -e "\n" '$* :';      print_params   $*
    echo -e "\n" '"$*" :';    print_params   "$*"
    echo -e "\n" '$@ :';      print_params   $@
    echo -e "\n" '"$@" :';    print_params   "$@"
}
pass_params "word" "words with spaces"
[me@linuxbox ~]$ posit-param3
 $* :
$1 = word
$2 = words
$3 = with
$4 = spaces
 "$*" :
$1 = word words with spaces
$2 =
$3 =
$4 =
 $@ :
$1 = word
$2 = words
$3 = with
$4 = spaces
 "$@" :
$1 = word
$2 = words with spaces
$3 =
$4 =
在Linux中,单引号和双引号的一个重要区别是双引号能保有变量的内容,而单引号只能是字符,而不会有特殊符号。

字符串展开
${#parameter}
展开成由 parameter 所包含的字符串的长度。通常,parameter 是一个字符串;然而,如果 parameter 是 @ 或者是 * 的话, 则展开结果是位置参数的个数。
[me@linuxbox ~]$ foo="This string is long."
[me@linuxbox ~]$ echo "${#foo}"
20
${parameter:offset}
${parameter:offset:length}
这些展开用来从 parameter 所包含的字符串中提取一部分字符。提取的字符始于 第 offset 个字符(从字符串开头算起)直到字符串的末尾,除非指定提取的长度。
[me@linuxbox ~]$ foo="This string is long."
[me@linuxbox ~]$ echo ${foo:5}
string is long.
[me@linuxbox ~]$ echo ${foo:5:6}
string
最新的 bash 版本已经支持字符串的大小写转换了。bash 有四个参数展开和 declare 命令的两个选项来支持大小写转换。
declare 命令可以用来把字符串规范成大写或小写字符。使用 declare 命令,我们能强制一个 变量总是包含所需的格式,无论如何赋值给它。
declare -u upper
declare -l lower
其中upper总为大写字符,lower总为小写字符
执行大小写转换的四个参数展开为:
格式 结果
${parameter,,} 把 parameter 的值全部展开成小写字母。
${parameter,} 仅仅把 parameter 的第一个字符展开成小写字母。
${parameter^^} 把 parameter 的值全部转换成大写字母。
${parameter^} 仅仅把 parameter 的第一个字符转换成大写字母(首字母大写)。
数组
数组变量就像其它 bash 变量一样,当被访问的时候,它们会被自动地创建。因此,不需要特意定义一个数组变量,只需直接对数组变量赋值并使用。当然,也可以通过declare来定义一个数组
[me@linuxbox ~]$ declare -a a
使用 -a 选项,declare 命令的这个例子创建了数组 a
有两种方式可以给数组赋值。单个值赋值使用以下语法:
name[subscript]=value
这里的 name 是数组的名字,subscript 是一个大于或等于零的整数(或算术表达式)。注意数组第一个元素的下标是0, 而不是1。数组元素的值可以是一个字符串或整数。
多个值赋值使用下面的语法:
name=(value1 value2 ...)
如:
days=(Sun Mon Tue Wed Thu Fri Sat)
还可以通过指定下标,把值赋给数组中的特定元素:
days=([0]=Sun [1]=Mon [2]=Tue [3]=Wed [4]=Thu [5]=Fri [6]=Sat)
注意变量展开时应使用大括号:
echo ${days[0]}
有许多常见的数组操作。比方说删除数组,确定数组大小,排序,等等。
下标 * 和 @ 可以被用来访问数组中的每一个元素。与位置参数一样,@ 表示法在两者之中更有用处。
[me@linuxbox ~]$ animals=("a dog" "a cat" "a fish")
[me@linuxbox ~]$ for i in ${animals[*]}; do echo $i; done
a
dog
a
cat
a
fish
[me@linuxbox ~]$ for i in ${animals[@]}; do echo $i; done
a
dog
a
cat
a
fish
[me@linuxbox ~]$ for i in "${animals[*]}"; do echo $i; done
a dog a cat a fish
[me@linuxbox ~]$ for i in "${animals[@]}"; do echo $i; done
a dog
a cat
a fish
使用参数展开,我们能够确定数组元素的个数
[me@linuxbox ~]$ a[100]=foo
[me@linuxbox ~]$ echo ${#a[@]} # number of array elements
1
[me@linuxbox ~]$ echo ${#a[100]} # length of element 100
3
通过使用 += 赋值运算符, 我们能够自动地把值附加到数组末尾。
[me@linuxbox~]$ foo=(a b c)
[me@linuxbox~]$ echo ${foo[@]}
a b c
[me@linuxbox~]$ foo+=(d e f)
[me@linuxbox~]$ echo ${foo[@]}
a b c d e f
删除一个数组,使用 unset 命令:
[me@linuxbox ~]$ foo=(a b c d e f)
[me@linuxbox ~]$ unset foo
也可以使用 unset 命令删除单个的数组元素:
[me@linuxbox~]$ foo=(a b c d e f)
[me@linuxbox~]$ unset 'foo[2]'
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值