shell脚本知识记录

先来简单的看一下变量定义的规则

在Shell中,使用变量之前不需要事先声明,只是通过使用它们来创建它们;
在默认情况下,所有变量都被看做是字符串,并以字符串来存储;
Shell变量是区分大小写的;
在赋值变量的时候等号两端不能有空格-_-
定义了变量之后,一定要加上$符号才能使用

#! /bin/bash

VAR1=HELLO
VAR2=MY NAME
VAR3="MY AGE"
VAR4 = IS

echo VAR1 #error 能输出 但不是输出该变量
echo $VAR1 #ok 正常读取变量并打印
echo $VAR2 #error 定义变量的值 用空格隔开了
echo $VAR3 #ok 作为一整个字符串
echo $VAR4 #error 变量定义的时候等号两端有空格
输出的结果为

./test.sh: line 2: NAME: command not found
./test.sh: line 4: VAR4: command not found
VAR1
HELLO

MY AGE
关于shell脚本的执行:shell基本一般是以.sh为后缀,然后在*unix系统下一般都是直接使用./[当前shell文件名] 的方式来执行,也可以使用全部经/[shell文件名]的方式来执行,并且需要注意的是 被执行的shell文件一定是有含有可执行权限了的,可以使用chmod命令来修改
还有另一个点就是在调用变量的时候 ,如果在双引号中直接使用$name任然可以识别,但是如果在单引号是就无法适用$name的方式来调用变量

read读取输入值
这个功能就像java中的readline来读取,使用方法为

#! /bin/bash

echo "whats your name?"
read NAME  #在这里读取输入值到NAME变量中 ,这里如果不输入会停留在屏幕上
echo "webcome back" $NAME

exit 0
可以看到熟悉的结果为

whats your name?
tom
webcome back tom
环境变量
Shell脚本还提供能一些实用的环境变量
1. $HOME:为当前用户所在的目录
3. $PATH:当前用户所能方法的PATH变量
4. $#:传递参数额个数 类似java中的args.length
5. $$:Shell脚本的进程号,脚本程序通常会用它来生成一个唯一的临时文件。

#! /bin/bash

echo "当前用户所在的目录为" $HOME
echo "当前的执行目录为" $(pwd)  #这个是访问当前的脚本的目录很实用
echo "当前用户所能访问的PATH为" $PATH
echo "当前参数的参数个数为" $#  #这儿参数的格式是使用空格隔开的哦
echo "当前Shell脚本的进程号为" $$

exit 0
可以到看的结果是

yans-MacBook-Pro:Downloads yanyl$ ./hi.sh  hello world
当前用户所在的目录为 /Users/yanyl
当前的执行目录为 /Users/yanyl/Downloads
当前用户所能访问的PATH为 /usr/local/bin:/usr/bin:/bin:/usr/sbin:/sbin:/Users/yanyl/Program/apache-maven-3.2.5/bin:/Users/yanyl/Program/scala-2.10.4//bin
当前参数的参数个数为 2
当前Shell脚本的进程号为 43746
参数变量
刚刚看到可以使用read关键字可以来读取输入变量,但是我们可能更加常用的是参数变量,也就是$#的个数,它的规则如下
1. $#表示参数变量的个数
2. $0表示当前的脚本名称
3. $1,$2...$n表示依次能读取到的变量 但是如果参数变量不够,$i会被赋值为空

#! /bin/bash

echo "当前输入的参数变量的长度为" $#
echo "当前执行的Shell脚本为" $0
echo "当前输入的第一个参数为" $1
echo "当前输入的第二个参数为" $2
echo "当前的输入的第三个参数为" $3 #现在如果只传2个参数 这里将不会报错

exit 0
可以看到的结果为

yans-MacBook-Pro:Downloads yanyl$ ./hi.sh  hello world
当前输入的参数变量的长度为 2
当前执行的Shell脚本为 ./hi.sh
当前输入的第一个参数为 hello
当前输入的第二个参数为 world
当前的输入的第三个参数为
可以看到在Shell脚本中去读取参数变量还是很方便的,这样配合下面的条件判断以及循环就可以做很多事情了

读取返回码
一般的程序/命令在执行结束时都会返回一个 返回码,比如

java的system.exit(-1)
python的sys.exit(-1)
还有上面Shell脚本中的最后一行exit 0
如果你不显式指定返回码,一般默认为0,表示正常退出,但是有时候显式的指定返回码是一个好习惯哦
这些程序在Shell中执行的,可以使用$?来读取上一个程序执行下来的脚本码
#! /bin/bash

du -s #执行的返回码一般为0
echo du -s的返回码为 $?

duu -s #这个命令故意输错
echo duu -s的返回码为 $?

exit 0
可以看到正确的结果为

28494656    .
du -s的返回码为 0
./hi.sh: line 6: duu: command not found
duu -s的返回码为 127
返回码配上if判断,就可以使用shell脚本自由得在各个语言以及命令中穿梭啦^_^
数学运算
在上一小节中说道,Shell中变量一般都是当字符串来处理,那我遇到数字运算该咋办呢??

可以先看

#! /bin/bash

a=1+2
b=$a+3
echo $a
echo $b

exit 0
结果却看到

1+2
1+2+3
那在Shell中解决这个问题大概有这么几种方法

let关键字
#! /bin/bash

let a=1+2
let b=$a+3
echo $a
echo $b

exit 0
输出的结果为

3
6
这个关键词大致需要注意以下几个点:

let只支持整数运算
当let后面的运算部分有bash关键字时,需加双引号
幂次方可以使用**符号
使用(())
#! /bin/bash

((a=1+2))
((b=$a+3))
echo $a
echo $b

exit 0
结果还是正确的

3
6
(())的用法与let完全相同
使用$[]
上面的效果需要这么写

a=$[1+2]
b=$[$a+3]
其余与上面两种限制大致相同

使用expr
关于这个方式是这么写的

a=`expr 1 + 2`
b=`expr $a * 3`  #需要转义
需要额外注意的有:

运算符两端需要加空格 一定要记住。。。很容易失误
对于|、&、<、<=、>=、>、*运算符号需要加上</code>进行转义
使用bc
这个终于是可以用于浮点数的运算了

#! /bin/bash

a=3.1415926
b=`echo "$a*2"|bc`
echo $a
echo $b

exit 0
可以看到结果

3.1415926
6.2831852
据说这里还有一个scale来设置精度,但是我设置了感觉木有效果-_-

条件判断
if 语法
在Shell脚本中有两种书写if判断的语法

使用test 关键字

#! /bin/bash

# if test expression1 operation expression2
if test 5 -gt 4;  #这个最后的结尾可以加上:或者;
then
    echo "ok,5>4"
else
    echo "oh,no"
fi #这个结束符号必须得加

exit 0
输出为

ok,5>4
使用[和]关键字

#! /bin/bash

# if [ expression1 operation expression2 ]
if [ 5 -lt 4 ];  #注意[和]两端必须留空格 同时表达式两端都需要有空格
then
    echo "ok,5>4"
else
    echo "oh,no"
fi

exit 0
输出为

oh,no
如果还更加复杂的判断你可以使用elif继续增加条件表达式,但是别忘了加then哦
判断表达式
在Shell中有三种判断表达式

字符串比较

字符串比较 结果
string1 = string2 如果两个字符串相同,也可用==结果就为真
string1 != string2  如果两个字符串不同,结果就为真
-n string 如果字符串不为空,则结果为真
-z string 如果字符串为一个空串(null),则结果为真
这里需要注意下,-n 和 -z string比较时必须用双引号("")将变量引起来
#! /bin/bash

a=5

if [ -n "$a"  ]  #注意要空括号来包住哦
then
    echo exists
else
    echo null
fi

if [ -n "$c"  ]
then
    echo exists
else
    echo null
fi

exit 0
结果为

exists
null
算术比较

算术比较  结果
expression1 -eq expression2 如果两个表达式相等,则结果为真
expression1 -ne expression2 如果两个表达式不等,则结果为真
expression1 -gt expression2 如果expression1 大于expression2 ,则为真
expression1 -ge expression2 如果expression1 大于等于expression2 ,则为真
expression1 -lt expression2 如果expression1 小于expression2 ,则为真
expression1 -le expression2 如果expression1 小于等于expression2 ,则为真
!expression 表达式为假,则结果就为真;反之亦然
关于上面比较符号的快速记法如下:eq=equal,gt=great than,lt=less than,然后组合拼凑即可,如果觉得这样还是很难记,就可以像我一样,将这些符号记录下来,需要的时候来查表-_-
文件条件测试

文件条件测试  结果
-d file 如果文件是一个目录,则为真
-f file 如果文件是一个普通文件,则为真;也可以用来测试文件是否存在
-r file 如果文件可读,则结果为真
-s file 如果文件大小不为0,则结果为真
-w file 如果文件可写,则结果为真
-x file 如果文件可执行,则结果为真
这,真的是一个利民的测试
循环结构
for 循环
先来看一种经典C语法版的for

#! /bin/bash

for ((i=0;i<5;i++))
do
    echo $i
done
exit 0
看输出,

0
1
2
3
4
还支持在外部控制步长

#! /bin/bash

for ((i=0;i<5;))
do
    echo $i
    i=$[$i+2]
done
exit 0
0
2
4
是不是感觉基本功能都有呀,就是写某些东西写起来奇怪点
是不是有一种莫名的熟悉感
另一种就是类似foreach的情况了,他的格式是这样的

for variable in values
do
    statements
done
其中values 可能有的情况为:

使用linux命令输出的行作为迭代的输入:ls,seq,cat之类均可,其实就可以完成很强大的文件读取功能

#! /bin/bash

for i in `head -n 5 words.dit`;do  #words.dit 这是一个通用词表 每行一个词
    echo $i
done

exit 0
可以看到通用词典中前5个词

阿巴丹
阿巴岛
阿巴鸟
阿巴伊达
使用$*可以来表示遍历传入的参数列表

#! /bin/bash

for i in $*;do
    echo $i
done

exit 0
来看个结果

yans-MacBook-Pro:Downloads yanyl$ ./hi.sh  my name is tom
my
name
is
tom
还可以使用带空格的字符串 来进行按空格分隔输出

#! /bin/bash

a="yello red green"
for i in $a;do
    echo $i
done

exit 0
这样在一定程度上可以看成一个简易的数组

这里需要注意的是包含条件以及循环逻辑是双重括号,以及开始结果的do和Done

while 循环
另一个常用的就是while循环了
他的结构是

while condition
do
    statements
done
这个也是蛮好理解的,可以来看一个demo

#! /bin/bash

echo "please ent your password:"
read pwd

while [ "$pwd"x != "root"x  ] #这里加x是为了防止啥也不输入直接回车产生的报错
do
    echo "error,please try again:"
    read pwd
done
echo "welcome here"

exit 0
看一下结果

please ent your password:
sha
error,please try again:

error,please try again:
root
welcome here
很有意思的一个哈~

until语句
这个语句与while的结构完全一样,只是使用了until关键字来代替了while,然后在条件为true的时候停止,正好与while相反

函数
Shell这么叼,能没有函数吗

[function] functon_name()
{
    statements
}
上面是定义函数的结构,大致有以下几个要点
1. 前面的function关键字可有可无,不过感觉还是加上去比较好,这样在代码里面比较好辨识
2. 函数名后面的括号中不能带参数 取的参数是用过$1,$2...$n这样的方式来取的 
3. 调用的时候直接写函数名 不需要加括号
4. 如果想传递参数的话 直接在调用后来加上参数列表 用空格隔开 (就是Shell的传参一样)
5. 使用local关键字来定义函数体里面的局部变量
7. 所以在函数调用必须在函数定义之后

先看一个小的demo

#! /bin/bash

function sayhi()
{
    echo hi $1
}

sayhi tom #前面的sayhi是函数的调用 后面的tom是传参

exit 0
可以看到输出

hi tom
函数的返回值
关于Shell的返回值方式有两种

输出给主程序,他的结构为:

function function_name()
{
    echo $something  #通过输出的方式来返回值
}

a=`function_name`  这种方式接收返回值
看到的demo可以是这样的

Press ENTER or type command to continue
#! /bin/bash

function sum()
{
    echo $[$1+$2]
}

a=`sum 1 2`

echo the sum is $a
exit 0
最终输出结果为

the sum is 3
使用return作为返回码来返回值

function function_name()
{
    return $ret #这里进行返回码的返回
}

function_name
$? #在这里接收返回值
一样再来一个demo

#! /bin/bash

function sum()
{
    return $[$1+$2]
}

sum 1 2
echo the sum is $?

exit 0
可以看到输出为

the sum is 3
case语句
这里的case的与传统的switch有点像,但是又像scala中的match模式匹配的强大,
他的结构是这样的

case variable in
    pattern [ | pattern] ...) statements;;
    pattern [ | pattern] ...) statements;;
    ...
esac
来看这个强大的demo

#! /bin/bash

function match()
{
    case $1 in
        root ) echo this is password ;;
        h* ) echo hi $1 ;; #使用通配符
        yes | YES ) echo agree with me ;; #可以进行或操作
        * ) echo everything is here;;  #你可以理解为switch中的default
    esac
}

match root
match hello
match YES
match Yes
exit 0
来看一下结果

this is password
hi hello
agree with me
everything is here
注意,这里一旦匹配中了一个之后就马上会停止匹配
外部命令/文件/语言的调用
Shell的另一个强大之处就是可以无缝的和外部的命令,文件,语言结合,去调用组织他们
1. 外部命令:一般情况下可以直接写外部命令,如果要赋值的话得使用``括起来
2. 外部文件:比如资源配置文件,profile文件之类的,可以直接使用source关键字的来执行
3. 外部语言:比如java,python可以直接使用他们的java调用jar,java文件,也可以直接使用关键字来执行python文件

总结
Shell很好很强大,得学习!!!
注意变量的字符串格式以及需要数学运算时的语法
注意变量赋值时等号两端一定不能有空格以及再取值时一定要加$
平常的控制结束符号别忘了,比如fi,doen,esac等
忘了的时候来查查这个文件