shell脚本编程

        一个shell脚本永远是以 "#!" 开头的,是一个脚本开始的标记,后面告诉系统执行该脚本需要使用什么解释器。shell脚本文件的后缀是 ".sh" ,脚本中以 "#" 开头的行都是注释。

        shell脚本的执行有几种方式:一种是直接 bash hello.sh,该种方式指定了解释器,脚本中的第一行就可以不要了;一种是赋予脚本可执行权限,然后在当前目录下 ./hello.sh执行;还有一种方式是将脚本复制到系统$PATH变量所包含的目录中,赋予其可执行权限,使其成为系统命令。

        点号 "." 可用于执行某个脚本,甚至脚本没有执行权限也可执行: . ./hello.sh 。与点号类似,source命令也可读取并在当前环境中执行脚本,同时还可返回脚本中最后一个命令的返回状态;如果没有返回值则返回0,代表执行成功;如果未找到指定的脚本则返回false。

        注意source命令或者点号,不会为脚本新建shell,而只是将脚本包含的命令在当前shell执行。

1 shell的内建命令

        shell的内建命令就是shell本身提供的命令,而不是某个可执行文件。判断一个命令是不是内建命令只需要借助于命令 type 即可。

1.1 变量声明 declare typeset

        declare typeset 都是用来声明变量的,作用完全相同。shell弱类型编程语言,声明变量时不需要指定变量类型。不过使用 declare 声明变量时,可以使用 -i 声明整型变量。

#!/bin/bash

#使用 -i 声明整型变量,变量名 等号 和 值 之间没有空格
declare -i num=5
echo $num

#使用 -r 声明变量为只读
declare -r ro="hello"
echo $ro

#使用 -a 声明数组变量,元素间不需要逗号分隔
declare -a arr=("a" "b" "c")
echo ${arr[2]}

声明局部变量 local

        local 命令用于在脚本中声明局部变量。典型用法是在函数中声明局部变量,其作用域限制于函数体内。如果试图在函数外使用 local 声明变量,则会提示错误。

1.2 别名 alias unalias

        alias用于创建命令的别名,若该命令不带任何参数,则显示系统当前使用的所有别名命令。但命令行中命名的别名只在当前shell环境中有效,若想使别名一直生效,则应写入用户家目录下的 .ashrc 文件中。

        unalias用于删除当前shell环境中的别名命令。一种方法是后接要删除的别名命令;另一种方法是接 -a 参数,删除当前shell环境中所有的别名命令。

1.3 打印 echo

        echo用于打印字符,典型用法是使用echo命令并跟上使用双引号括起的内容(即需要打印的内容),该命令会打印出引号中的内容,并在最后默认加上换行符。

#!/bin/bash

#打印 Hello World 并换行
echo "Hello World"

#打印 Hello World 且不换行
echo -n "Hello World"

#打印字符 \n 
echo "\n"

#打印一个空行
echo -e "\n"

1.4 执行命令来取代当前的shell exec 

        内建命令 exec 并不启动新的shell,而是用要被执行的命令替换当前的shell进程,并将老进程的环境清理掉,且进程PID保持不变。当被执行的命令退出时,这个进程也随之推出了,所以exec命令后的其他命令将不再执行

        一般将 exec 命令放到一个shell脚本里面,由主脚本调用这个脚本,主脚本在调用子脚本执行时,当执行到exec后,该子脚本进程就被替换成相应的exec的命令。exec典型的用法是与find联合使用,用find找出符合匹配的文件,然后交给exec处理。

1.5 退出shell exit

        使用 exit 退出当前脚本,后可接一个参数n表示退出时的状态,默认退出状态为0。

1.6 声明shell环境变量 export

        在父shell中创建变量时,这些变量并不会被其子shell进程所知,也就是说变量默认情况下是“私有”的,或称“局部变量”。使用 export 命令可以将变量导出,使得该shell的子shell都可以使用该变量,这个过程称为变量输出

        注意这种传递是值传递,若在子shell中尝试改变变量的值,也只会在子shell中生效,父shell中的变量值并不受影响。

1.7 发送信号给指定的PID或进程 kill

        Linux包含三种不同类型的进程:第一种是交互进程,这是由一个shell启动的进程,既可以在前台运行,也可以在后台运行;第二种是批处理进程,与终端没有联系,是一个进程序列;第三种是监控进程,也称系统守护进程,它们往往在系统启动时启动,并保持在后台运行。

1.8 从标准输入读取一行到变量:read

        read 是按行读取的,用回车符区分一行。如果不指定变量,read 命令会将读取到的值放入环境变量 REPLY 中。

#!/bin/bash

declare name

echo -n "What's our name?"
read name
echo "Hi, $name"

#使用参数 -p 指定提示信息
read -p "What's our name?" name
echo "Hi, $name"

1.9 向左移动位置参数 shift

        假设一个脚本在运行时可以接受参数,那么从左到右第一个参数被记作 $1 ,第二个参数为 $2 ,以此类推,第n个参数为 $N。所有参数记作 $@ 或 $* ,参数的总个数记作 $# ,而脚本本身记作 $0 。

        shift命令可以对脚本的参数做“偏移”操作。假设脚本有A、B、C这3个参数,那么$1为A,$2为B,$3为C;shift一次后,$1为B,$2为C;再次shift后$1为C。

1.10 显示并设置进程资源限度 ulimit

        ulimit 可以控制进程对可用资源的访问。默认情况下Linux系统的各个资源都做了软硬限制,其中硬限制的作用是控制软限制(软限制不能高于硬限制)。使用 ulimit -a 可以查看当前系统的软限制,使用命令ulimit -a -H 可查看系统的硬限制。

        使用ulimit直接调整参数,只会在当前运行时生效,一旦系统重启,所有调整过的参数就会变回系统默认值。所以建议将所有的改动放在ulimit的系统配置文件(/etc/security/limits.conf)中。

1.11 任务前后台切换 bg fg jobs

        该命令用于将任务放置后台运行,一般会与Ctrl+z、fg、&符号联合使用。典型的使用场景是运行比较耗时的任务。

1.12 测试表达式 test

        test expression

1.13 其他内建命令

cd        pwd        break        continue        return        set        shopt         readonly 等

eval        将所跟的参数作为Shell的输入,并执行产生的命令

let        整数运算

2 shell编程基础

2.1 变量

2.1.1 变量的声明

        shell中的变量必须以字母或者下划线开头,后面可以跟数字、字母和下划线,变量长度没有限制。但是,变量是区分大小写的

        shell变量的作用域是在本shell内,属于本shell的全局变量,也就是从定义该变量的地方开始到shell结束,或到主动使用unset删除了该变量的地方为止。

        变量的定义非常简单:变量名=变量值。注意,变量名和变量值之间使用等号紧密相连,没有任何空格。当变量值中有空格(字符串)时,必须使用引号引起来,单引号、双引号都可以。

        变量的取值也很简单,只需要在变量名前加上 $ 符号既可,严谨一点的写法是 ${}

        由于shell弱变量的特性,即使没有预先声明的变量也是可以引用的,并且没有任何提示和报错。为避免不必要的麻烦,可以在设置脚本在运行时必须遵循“先声明再使用”的原则,这样一旦出现使用为声明的变量时就立即报错。

shopt -s -o nounset

        unset 命令可以取消变量,即将变量从内存中释放。用法为 unset 后面跟变量名。unset后面也可以接函数名,用于取消函数。

        内建命令 readonly 可以创建只读变量,效果和 declare -r 是一样的。

2.1.2 bash的环境变量

BASH       bash shell的全路径

BASH_VERSION        bash shell的版本

HOSTNAME        主机名

HOSTTYPE        主机架构

MATCHTYPE        主机类型的GNU标识

LANG        设置系统的语言环境

PWD        记录当前目录

OLDPWD        记录之前的目录

PATH        命令的搜索路径

FUNCNAME        当前函数的函数名,只能在函数体中使用

HISTCMD        记录下一条命令在history命令中的编号

HISTFILE        记录history命令记录文件的位置

HISTFILESIZE        设置HISTFILE文件记录命令的行数

HISTSIZE        设置命令缓冲区的大小

PS1        命令提示符,默认值是[\u@\h \W]\$,其中\u是用户名、\h是主机名、\W是当前工作目录的basename、\$是用户UID的替换字符:如果UID是0则替换成“#”,否则替换成“$”,所以此处具体显示出来就是“[root@localhost ~]#”。

2.1.3 特殊变量

         shell中有一些预先定义的特殊只读变量,它们的值只有在脚本运行时才能确定。

1.位置参数

       位置参数的命名简单直接:脚本本身为$0,第一个参数为$1,第二个参数为$2,第三个为$3,以此类推。当位置参数的个数大于9时,需要用 ${} 括起来标识,比如说第10个位置参数应该记为${10}。另外,$# 表示脚本参数的个数总和,$@ 或$* 表示脚本的所有参数。

2.脚本或命令返回值 $?

        Linux中规定正常退出的命令和脚本应该以0作为其返回值,任何非0的返回值都表示命令未正确退出或未正常执行。

        $? 永远是上一个命令的返回值,所以要查看某个命令的返回值必须在运行该命令后立即查看$?。在自动化脚本中,也可以通过 $? 变量的值判断之前命令的执行状态,从而采取不同的动作。

2.2 数组

        shell中的数组对元素个数没有限制,但只支持一维数组。因为shell是弱类型的,所以没有要求数组元素类型必须一致。

#!/bin/bash

#使用declare关键字声明数组
declare -a arr
arr=(2 3 4)
arr[3]="hello, world!"

echo ${arr[0]}
echo ${arr[3]}

#取出所有元素
echo ${arr[@]}
echo ${arr[*]}

#不使用关键字声明数组
name=("Chris" "Bin")
echo ${name[1]}

#对特定元素赋值
declare -a arr2=([1]=2 [3]=9)
echo ${arr2[3]}

        数组长度:即数组元素个数。利用“@”或“*”字符,可以将数组扩展成列表,然后使用“#”来获取数组元素的个数。

#!/bin/bash

name=('Chris' 'Bin')

#获取元素个数
echo ${#name[@]}

#获取第二个元素字符串的长度
echo ${#name[1]}

数组的截取、连接和替换:

#!/bin/bash

name=('Jonh' 'Chris' 'Bob' 'Bin' 'Andy' 'Dave')

#数组截取:取出数组的第2 3个元素 Chris Bob
echo ${name[*]:1:2}

#数组截取:取出数组第2个元素的从第3个字符开始的连续2个字符 ri
echo ${name[1]:2:2}

#连接数组
arr=(1 2 3)
con=(${arr[@]} ${name[*]})
echo ${con[@]}

#元素替换
name=(${name[*]/Bob/Bin})
echo ${name[@]}

2.3 转义和引用

2.3.1 转义

        shell中的转义符是反斜线 “\” 。

2.3.2 引用

        引用是指将字符串用某种符号括起来,以防止特殊字符被解析为其他意思。shell中一共有4种引用符:双引号、单引号、反引号(在键盘上和波浪号位于同一个键)和转义符。其中,双引号又叫“部分引用”或“弱引用”,可以引用除$符、反引号、转义符之外的所有字符;单引号又叫“全引用”或“强引用”,可以引用所有字符,仅表示字面意思;反引号则会将反引号括起的内容解释为系统命令。

        注意,单引号中不可再使用单引号引用。

2.3.3 命令替换

        命令替换是指将命令的标准输出作为值赋给某个变量。shell中有两种方式可以完成命令替换,一种是反引号,一种是 $() 。且 $() 支持嵌套,而反引号不行。$()仅在Bash Shell中有效,而反引号可在多种UNIX Shell中使用。

2.4 运算符

        注意,shell只支持整数运算,且运算符和操作数之间紧密相连,不能有空格。常见的算术运算大多需要结合shell的内建命令let来使用。

2.4.1 使用 $[ ] 做运算 

        $[] 和 $(()) 一样,可用于简单的算术运算

#!/bin/bash
echo $[2+4]
echo $[2**3]
echo $((2%5))
echo $((8/4))

2.4.2 使用 expr 运算

        expr命令也可用于整数运算。和其他算术运算方式不同,expr要求操作数和操作符之间使用空格隔开(否则只会打印出字符串),所以特殊的操作符要使用转义符转义(比如*)

        expr支持的算术运算符有加、减、乘、除、余等。

2.4.3 使用 declare 命令运算

        虽然shell在创建变量时可以不使用 declare ,但使用不使用的区别蛮大,如下:

#!/bin/bash

a=2+3
echo $a		# "2+3"

declare -i b
b=3+5
echo $b		# 8

2.4.4 算数扩展

        算数扩展是shell提供的整数运算机制,是shell的内建命令,语法: $((算术表达式)) 。

        其中的算术表达式由变量(此时变量前不需要 $)和运算符组成,常见的用法是显示输出和变量赋值。若表达式中的变量没有定义,则在计算时,其值会被假设为0(但是并不会真的因此赋0值给该变量)。

#!/bin/bash

a=2
echo $((a+3))	# 5

echo $((2*(j+3))) # 6
echo $j		# 空

2.4.5 使用 bc 做运算

        bc 是Linux下一款专门用于高精度计算的工具,可用于浮点数的计算。bc 还支持逻辑运算、比较运算。最简单的使用方式是直接键入 bc 命令进入交互界面。

        默认情况下 bc 并不显示小数部分,可以通过设置 scale 决定要显示的小数位数。

2.5 特殊字符

2.5.1 通配符

        通配符用于模式匹配,常见的通配符有 *、? 和 用[]括起来的字符序列:

* 代表任意长度的字符串,但是不包括点号和斜线号;

? 可用于匹配任一单个字符;

[ ] 代表匹配其中的任意一个字符。

2.5.2 位置参数

        详见 2.1.3节。

3 测试和判断

3.1 测试

        测试的第一种方式是直接使用test命令: test expression

其中expression是一个表达式,可以是算术比较、字符串比较、文本和文件属性比较等。

        测试的第二种方式是使用 [ ] : [ expression ]

注意,在[ ] 和 expression之间必须有空格这种方式方便和 if case while 等关键字连用。

3.1.1 文件测试符

文件测试说明
-b file当文件存在且是个块文件时返回真,否则为假
-c file当文件存在且是个字符设备文件时返回真,否则为假
-d file当文件存在且是个目录时返回真,否则为假
-e file当文件或者目录存在时返回真,否则为假
-f file当文件存在且为普通文件时返回真,否则为假
-x file当文件存在且为可执行文件时返回真,否则为假
-w file当文件存在且为可写文件时返回真,否则为假
-r file当文件存在且为可读文件时返回真,否则为假
-l file当文件存在且为连接文件时返回真,否则为假
-p file当文件存在且为管道文件时返回真,否则为假
-s file当文件存在且大小不为0时返回真,否则为假
-S file当文件存在且为Socket文件时返回真,否则为假
-g file当文件存在且设置了SGID时返回真,否则为假
-u file当文件存在且设置了SUID时返回真,否则为假
-k file当文件存在且设置了sticky属性时返回真,否则为假
-G file当文件存在且属于有效用户组时返回真,否则为假
-O file当文件存在且属于有效用户时返回真,否则为假
file1 -nt file2当 file1 比 file2 新时返回真,否则为假(newer than)
file1 -ot file2当 file1 比 file2 旧时返回真,否则为假(older than)

3.1.2 字符串测试符

字符串测试说明
-z "string"字符串string为空时返回真,否则为假
-n "string"字符串string为非空时返回真,否则为假
"string1"="string2"两字符串相同时返回真,否则为假
"string1"!="string2"两字符串不相同时返回真,否则为假
"string1">"string2"按照字典排序,字符串1排在字符串2之前时返回真,否则为假
"string1"<"string2"按照字典排序,字符串1排在字符串2之后时返回真,否则为假

3.1.3 整数比较符

整数比较说明
num1 -eq num2如果num1等于num2则返回真,否则为假。equal
num1 -gt num2如果num1大于num2则返回真,否则为假。great than
num1 -lt num2如果num1小于num2则返回真,否则为假。less than
num1 -ge num2如果num1大于等于num2则返回真,否则为假。great equal
num1 -le num2如果num1小于等于num2则返回真,否则为假。less equal
num1 -ne num2如果num1不等于num2则返回真,否则为假。not equal

3.1.4 逻辑测试与逻辑运算

逻辑测试符
逻辑运算说明
!expression若expression为真,则测试结果为假 !
expression1 -a expression2两个表达式都为真,则测试结果为真 &&
expression1 -o expression2两个表达式任意一个为真,则测试结果为真 ||

        shell中的逻辑运算符同C语言一致: 与&&、或||、非!。

3.2 判断

3.2.1 if 判断

#!/bin/bash

if expression; then
    ...
fi

if expression; then
    ...
else
    ...
fi

if expression; then
    ...
elif expression2; then
    ...
elif expression3; then
    ...
fi

3.2.2 case 判断

        注意,case判断结构中的var1、var2、var3等这些值只能是常量或正则表达式。

#!/bin/bash

case VAR in
var1) command1 ;;
var2) command2 ;;
var3) command3 ;;
...
*) command ;;
esac

4 循环

        for循环在读取文件时,任何空白字符都可以作为其读取的分隔符。

        while循环在读取文件时,是按行读取的,因为while使用的是换行符作为行标记。

4.1 for

4.1.1 带列表的for循环

#!/bin/bash

fruits="apple orange pear banana"
for fruit in ${fruits}
do
	echo "${fruit} is Chris favorite."
done
echo "Hello World!"


for var in {1..20}
do
    echo "loop: ${var}"
done


sum=0
for var in $(seq 1 2 100)
do
    let "sum+=var"
done
echo "Total: ${sum}"

4.1.2 类C的for循环

#!/bin/bash

for ((i=1; i<=10; ++i))
do
	echo -n "${i} "
done

4.2 while

#!/bin/bash

loop=5
i=0
while [ $loop -gt 0 ]
do
	let "loop--"
	let "i++"
	echo " loop ${i} "
done

4.3 until

        until采用的是测试假值的方式,当测试结果为假时执行循环体,直到测试为真时才停止循环。

4.4 select

        select是一种菜单扩展循环方式,当程序运行到select语句时,会自动将列表中的所有元素生成为可用1、2、3等数选择的列表,并等待用户输入。用户输入并回车后,select可判断输入并执行后续命令。如果用户在等待输入的光标后直接按回车键,select将不会退出而是再次生成列表等待输入。

        select有判断用户输入的功能,所以select经常和case语句合并使用。

#!/bin/bash
echo "today is ?"
select today in Mon Tue Wed Thu Fri Sat Sun
do
        case $today in
        Mon) echo "Today is Monday";;
        Tue) echo "Today is Tuesday";;
        Wed) echo "Today is Wednesday";;
        Thu) echo "Today is Thursday";;
        Fri) echo "Today is Friday";;
        Sat|Sun) echo "You can have a rest today";;
        *) echo "Unknown input,exit now" && break;;
        esac
done

5 函数

5.1 函数定义

        shell中函数定义使用关键字 function,不过关键字 function 也可省略。

#!/bin/bash

function sayHi(){
    echo "Hello World!"
}

sayHi

5.2 函数返回值

#!/bin/bash

file = "./test.txt"

checkFileExist(){
    if [ -f $file ];then
        return 0
    else
        return 1
    fi
}

checkFileExist
result=$?
if [ result -eq 0 ];then
    echo "$file exist"
else
    echo "$file not exist"
fi

5.3 函数的参数

        shell中函数的参数传递,一种是通过位置参数传递,在执行脚本时后接参数,在脚本中通过 $1 $2 等位置参数获取;另一种是使用内置命令 set 来设置参数,一旦使用了 set 设置参数后,将忽略执行脚本时传入的参数,实际上是 set 重置了位置参数的值;当然还可以配合 shift 命令使位置参数左移。

#!/bin/bash

set hello world
for i in $@
do
	echo -n  "$i "
done

5.4 函数库

        当要引用别的脚本中的函数时,可以直接加载那个脚本。有两种加载方法:点(.) 和 source 命令。

        很多Linux发行版中都有 /etc/init.d 目录,这是系统中放置所有开机启动脚本的目录,这些开机脚本在脚本开始运行时都会加载 /etc/init.d/functions 或 /etc/rc.d/init.d/functions 函数库(实际上这两个函数库的内容是完全一样的),functions函数库中定义了27个函数。

6 重定向

        在Linux中,文件标识符也是一种“设备”,这个标识符会出现在 /dev/fd 目录中。

        Linux使用0到9的整数指明了与特定进程相关的数据流,系统在启动一个进程的同时会为该进程打开三个文件:标准输入(stdin)、标准输出(stdout)、标准错误输出(stderr),分别用文件标识符0、1、2来标识。如果要为进程打开其他的输入输出,则需要从整数3开始标识。默认情况下,标准输入为键盘,标准输出和错误输出为显示器。

        I/O重定向可以将任何文件、命令、脚本、程序或脚本的输出重定向到另外一个文件、命令、程序或脚本。

常见的I/O重定向符号
符号含义
>标准输出覆盖:将命令的输出重定向输出到其他文件中,同时覆盖文件中已有的内容
>>标准输出追加:将命令的输出重定向输出到其他文件中,以追加而不是覆盖的方式
>&标识输出重定向:将一个标识的输出重定向到另一个标识的输入
<标准输入重定向:命令将从指定文件中读取输入,而不是从键盘
|管道:从一个命中读取输出并作为另一个命令的输入

  • 20
    点赞
  • 11
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值