shell编程

什么是shell编程

shell编程就是对一堆Linux命令的逻辑化处理。

为什么要会shell编程

举个简单的例子,我们做javaweb开发的,在以前,如果要在本地将程序打包,然后部署到远程服务器(抛开现在的ci, 原始的方法), 我们以前的做法通常会经历如下几个步骤:

  • 拉取最新代码(git pull)
  • 编译打包
  • 上传并部署到远程服务器

每次打包都要经历这一个阶段,效率低又烦躁。而此时,我们可以编写一个shell脚本,然后每次只需要运行一下这个shell脚本,即可实现打包部署这一系列动作,彻底解放双手,多好

第一个shell程序

#!/bin/bash
#第一个shell小程序
echo hello world!

第一行表示我们选择使用bash shell。

shell中#符号表示注释。shell的第一行比较特殊,一般都会以#!开始来指定使用的shell类型。在linux中,除了bash shell以外,还有很多版本的shell, 例如zsh、dash等等…不过bash shell还是我们使用最多的。

第二行以#符号开始,表示本行是注释,运行的时候是不会运行本行的。

第三行中的echo是linux中的输出命令,该行的意思很明显的就是输出hello world!

touch hello.sh
//代码逻辑
chmod 700 hello.sh
./hello.sh

变量

shell编程中分为两种变量,第一种是我们自己定义的变量(自定义变量),第二种是Linux已定义的环境变量(环境变量, 例如:$PATH, $HOME 等…, 这类变量我们可以直接使用)。

#!/bin/bash
#使用环境变量
echo $PATH
#自定义变量hello
hello="hello world"
echo $hello

以上演示了自定义变量和系统环境变量的用法,使用很简单,就是使用 符 号 加 上 变 量 名 就 行 了 。 记 住 : 定 义 变 量 不 用 符号加上变量名就行了。记住:定义变量不用 符号,使用变量要加$就行了。

在第5行中,我们在自定义变量时,使用了双引号,在shell编程中, 如果变量出现空格或者引号,那么也必须加引号, 否则就可以省略。
还有一点需要注意,定义变量的时候,“=”左右千万不要有空格啊。

将linux命令执行结果赋值给变量

#!/bin/bash
path=$(pwd)
files=`ls -al`
echo current path: $path
echo files: $files

以上2行和第3行分别演示了两种方式来将Linux命令执行结果保存到变量。

第2行将pwd执行结果(当前所在目录)赋值给path变量。

第3行将ls -al命令执行结果(列出当前目录下所有的文件及文件夹)赋值给变量

注意:第三行的符号不是单引号,是键盘上“~”这个按键

基本数据类型运算

符号语义描述
+10+10,结果为20
-10-3, 结果为7
*10*2,结果为20
/10/3, 结果为3(取整数)
%求余10%3, 结果为1 (取余数)
==判断是否相等两数相等返回1,否则0
!=判断是否不等两数不等返回1,否则0
>大于前者大于后者返回1,否则0
>=大于或等于前者大于或等于后者返回1,否则0
<小于前者小于后者返回1,否则0
<=小于或等于前者小于或等于后者返回1,否则0

整数运算

在shell中,有两种方式能实现整数运算,一种是使用expr命令, 另外一种是通过方括号($[])来实现。下面分别来看看:

expr

#!/bin/bash
#输出13
expr 10 + 3

#输出10+3
expr 10+3

#输出7
expr 10 - 3

#输出30
expr 10 \* 3

#输出3
expr 10 / 3

#输出1
expr 10 % 3

#将计算结果赋值给变量
num1=$(expr 10 % 3)

#将计算结果赋值给变量
num2=`expr 10 % 3`
  1. 在以上的乘法(*)中,我们用了反斜线(\)来转义,不然会报错。
  2. 运算符前后必须还有空格,否则会被直接当作字符串返回。
  3. 如果要将计算结果保存到变量,就需要用到我们上篇文章讲到的那两种方式($() 或者 )来替换命令了。

方括号($[])

#!/bin/bash
num1=10
num2=3
#输出num1 + num2=13
echo "num1 + num2=$[$num1 + $num2]"

#输出num1+num2=13
echo "num1+num2=$[$num1+$num2]"

#输出num1 - num2=7
echo "num1 - num2=$[$num1 - $num2]"

#输出num1 * num2=30
echo "num1 * num2=$[$num1 * $num2]"

#输出num1 > num2=1
echo "num1 > num2=$[$num1 > $num2]"

#输出num1 < num2=0
echo "num1 < num2=$[$num1 < $num2]"

#将运算结果赋值给变量,输出num3=3
num3=$[$num1 / $num2]
echo "num3=$num3"

看了这种运算,再回看expr, 是不是觉得要升天,终于正常了。expr的那几个注意事项,在这儿都不算事儿。所以,如果要图简单,还是用这种方式吧。

浮点运算

在shell中,做浮点运算一般是用bash的计算器(bc)。在shell脚本中,一般我们的使用方法是:

variable=$(echo “options; expression” | bc)

options是bc的一些选项,例如: 可以通过scale去设置保留的小数位数。具体有哪些参数,可以man bc进行查看

expression就是我们具体的表达式,例如 10 * 3

“ | “ 这个符号,对于熟悉linux系统的人来说,这个再熟悉不过了。它叫做管道, 之所以会叫做管道,其实很形象,你可以把它看作一根水管,水管一头接入前一个命令的返回结果, 一头接入下一个命令。表示将前一个命令的执行结果作为后一个命令的参数输入。以上,表示将我们的表达式作为bc的参数输入。
#!/bin/bash
#表示 10/3, 保留2位小数,将结果赋值给了num, 输出3.33
num=$(echo "scale=2; 10 / 3" | bc)
echo $num

条件选择

if-then语句

if command
then
    commands
fi

吃瓜群众表示一脸懵比:if语句后面接的是命令,我们其它编程语言中,这儿都是接返回布尔值(true,false)的表达式。

那么这到底是怎么回事呢?

在shell脚本的if其实是根据紧跟后面的那个命令的退出状态码来判断是否执行then后面的语句的。

关于退出状态码,你只需要记住:正常退出(命令执行正常)的状态码是0, 非正常退出的状态码不是0(有不少)。

以上语句的语义为: 如果if后面的命令执行正常(状态码0),那么就执行then后面的语句。否则不执行。 fi代表if语句的结束。
#!/bin/bash
#这儿由于pwd是linux内置的命令,因此执行后会正常退出(状态码0),所以会执行then中的语句
#如果此处替换为一个不存在的命令(例如: pw),那么就会非正常退出,不会执行then中的语句
if pwd
then
   echo 执行then里面的语句
fi

if-then还可以简写为

if command; then
    commands
fi

因此,以上代码还可以写成以下:

#!/bin/bash
if pwd; then
   echo 执行then里面的语句
fi

以上,如果我要判断处理异常退出(状态码非0)情况,该怎么办?

别着急: else 来帮你。

if-then-else语句

if command
then
    commands
else
    commands
fi

与if-then语句相比,这回多了个else语句,else语句用来判断if后面的命令非正常退出的情况。

#!/bin/bash
if pwd
then
    echo 正常退出
else 
    echo 非正常退出
fi        

甚至,我们还可以变形写出更多的else:

if command1 
then
    commands 
elif 
    command2 
then
    command3
fi

但是上面就只能根据退出状态码判断,不能写表达式,你还让我怎么写? 我各个编程语言直接吊打你!

不要慌,客官,请接着往下看:

test命令

test命令用于if-then或者if-then-else语句中,主要用于判断列出的条件是否成立,如果成立,就会退出并返回退出状态码0,否则返回非0。

这意味着我们可以通过test命令来写表达式命令了。不过,对于已习惯其它编程语言的程序猿们(没学过的除外),不要高兴得太早,前方有坑,至于是什么坑,待会儿就能看到。

先看看test命令的基本用法吧:

直接用:

test condition

结合if-then语句用

if    test condition
then
    commands
fi

结合if-then-else语句用

if    test condition
then
    commands
else 
    commands    
fi

条件成立就执行then语句,否则else语句。

test命令只能判断一下三类条件:

  • 数值比较
  • 字符串比较
  • 文件比较

数值比较

比较描述
n1 -eq n2判断n1是否等于n2
n1 -ge n2判断n1是否大于或等于n2
n1 -gt n2判断n1是否大于n2
n1 -le n2判断n1是否小于或等于n2
n1 -lt n2判断n1是否小于n2
n1 -ne n2判断n1是否不等于n2
#!/bin/bash
num1=100
num2=200
if test $num1 -eq $num2
then
    echo num1等于num2
else
    echo num2不等于num2
fi

好好的标准的数学比较符号不能使用,难道非得写这种文本形式?是不是觉得很别扭?
不着急,还有替代方案:

使用双括号
双括号命令允许你在比较过程中使用高级数学表达式。关键是使用双括号,咱就可以用数学比较符号啦(等于==, 大于>, 小于< 等等都能使用啦)。
使用方法:

(( expression ))

注意:括号里面两边都需要有空格

#!/bin/bash
num1=100
num2=200
if (( num1 > num2 )) 
then
    echo "num1 > num2"
else 
    echo "num2 <= num2"     

字符串比较

比较描述
str1 = str2判断str1是否与str2相同
str1 != str2判断str1是否与str2不相同
str1 < str2判断str1是否比str2小(根据ASCII)
str1 > str2判断str1是否比str2大(根据ASCII)
-n str1判断str1的长度是否非0
-z str1判断str1的长度是否为0

程序猿们,要骂的就尽情释放吧。我反正是骂了。

test命令和测试表达式使用标准的数学比较符号来表示字符串比较,而用文本代码来表 示数值比较。这与其它语言相比都不一样。

#!/bin/bash
var1=test
var2=Test
if test $var1 = $str2
then
    echo 相等
else 
    echo 不相等
fi

注意,在使用大于(>)或小于(<)符号时,需要转义(>)(<),不然会把这两种符号时别为重定向(后面文章才会讲到)。

吐槽模式开启:我要用个比较符号,还要转义,很蛋疼的设计!

使用双方括号

双方括号命令提供了针对字符串比较的高级特性。它不仅解决了使用test所带来的一系列毛病,还提供了一些test命令所没有的高级用法。双方括号命令的格式如下:

[[ expression ]]    

注意,可能有些shell不支持此种写法。不过bash完美支持。以上写法注意括号内两边都有空格。

#!/bin/bash
var1=test
var2=Test
if [[ $test < $test2 ]]
then
    echo "test1 < test2"
else
    echo "test1 >= test2"
fi      

这下终于不用转义了。

文件比较

对于文件的比较,其实跟上面差不多,都是用test命令。由于篇幅有限,我这儿就不多写了。通过man test命令可以看到具体的用法。

case语句

在使用if-then-else语句中,如果碰到条件很多的情况,如下:

#!/bin/bash
num=3
if (( $num == 1 ))
then
    echo "num=1"
elif (( $num == 2 ))
then
    echo "num=2"
elif (( $num == 3 ))
then
    echo "num=3"    
elif (( $num == 4 ))
then
    echo "num=4"
fi    

如果再多点条件,看起来是不是很多?
此时,其实还有一种替代方案,那就是使用case.

case variable in
pattern1 | pattern2) commands1;; pattern3) commands2;;
*) default commands;;
esac

将以上代码替换为case:

#!/bin/bash
case $num in
1)
    echo "num=1";;
2)
    echo "num=2";;
3)
    echo "num=3";;
4)
    echo "num=4";;
*)
    echo "defaul";;
esac    

shell中的条件语句与其他编程语言相比有不小的区别,最大的区别就在于条件语句后接的是命令,而不是布尔值, 是根据命令执行退出的状态码来决定是否进入then语句的。这点需要牢记。

for-in语句

for var in list 
do
    commands
done

list代表要循环的值,在每次循环的时候,会把当前的值赋值给var(变量名而已,随意定), 这样在循环体中就可以直接通过$var获取当前值了。

先来一个例子吧:

#!/bin/bash
for str in a b c d e
do
    echo $str
done    

以上会根据空格将abcde分割,然后依次输出出来。

如果以上例子不是以空格分割,而是以逗号(,)分割呢?

#!/bin/bash
list="a,b,c,d,e"
for str in $list
do 
    echo $str
done

结果输出a,b,c,d,e

造成这个结果的原因是:for…in循环默认是循环一组通过空格或制表符(tab键)或换行符(Enter键)分割的值。这个其实是由内部字段分隔符配置的,它是由系统环境变量IFS定义的。当然,既然是由环境变量定义的,那当然也就能修改啊。

修改IFS值

#!/bin/bash
#定义一个变量oldIFS保存未修改前的IFS的值
oldIFS=$IFS
#修改IFS值,以逗号为分隔符
IFS=$','
list=a,b,c,d,e
list2="a b c d e"
for var in $list
do
    echo $var
done
for var2 in $list2
do
    echo $var2
done
#还原IFS的值
IFS=$oldIFS

以上第一个循环会分别输出abcde几个值。而第二个循环会输出a b c d e(即未处理)。因为我们把IFS的值设置为逗号了, 当然,不一定要是逗号,想设置什么,你说了算!

C语言风格的for循环

bash中c语言风格的for循环遵循如下格式:

for (( variable assignment ; condition ; iteration process ))

一个例子足以说明:

#!/bin/bash
for (( i = 0; i <= 10; i++ ))
do
    echo $i
done    

上面例子循环11次,从0到10依次输出。稍微有过编程基础的都对此应该很熟悉。就不做详细阐述了。

while循环

如果你习惯了其它语言的while循环,那么到这儿你又会发现这个while循环有点变态了。与其它编程语言while的不同在于:在bash中的while语句,看起来似乎是结合了if-then语句(参考上一篇)和for循环语句。其基本格式如下:

while test command 
do
    other commands
done

与if-then语句一样,后面接test命令,如果test后面的命令的退出状态码为0. 那么就进入循环,执行do后面的逻辑。要注意在do后面的逻辑中写条件,避免死循环。

既然是接test命令,那么一切都可以参考if-then的test

示例一:

#!/bin/bash
flag=0
while test $flag -le 10
do
    echo $flag
    # 如果没有这句,那么flag的值一直为0,就会无限循环执行
    flag=$[$flag + 1]
done

以上判断flag是否大于或者等于10, 如果满足条件,那么输出当前flag的值,然后再将flag的值加1。最终输出的结果为0到10的结果。

结合上一篇文章test的写法,我们还可以将以上示例变形为如下:

示例二:

#!/bin/bash
flag=0
while [ $flag -le 10 ]
do
    echo $flag
    flag=$[$flag + 1]
done

示例三:

flag=0
while (( $flag <= 10 ))
do
    echo $flag
    flag=$[$flag + 1]
done

until循环语句

until语句基本格式如下:

until test commands
do
    other commands
done

在掌握while循环语句之后, until语句就很简单了。until语句就是与while语句恰好相反, while语句是在test命令退出状态码为0的时候执行循环, 而until语句是在test命令退出状态码不为0的时候执行。

示例:

#!/bin/bash
flag=0
until (( $flag > 10 ))
do
    echo $flag
    flag=$[ $flag + 1 ]
done

以上输出0到10的值。until后面的条件与上面while例子完全相反。

好啦,到此,我们学完了shell的循环语句啦。不过上面我们写的循环语句都是根据条件执行完毕,如果我们在执行的过程中想退出,该怎么办?接下来就继续看看怎么控制循环语句。

break

break用于跳出当前循环。

示例一:

#!/bin/bash
for (( flag=0; flag <= 10; flag++ ))
do
    if (( $flag == 5 ))
    then
        break
    fi
    echo $flag
done

以上当flag的值为5的时候,退出循环。输出结果为0-4的值。

break用于跳出内层循环。

示例二:

#!/bin/bash
flag=0
while (( $flag < 10 ))
do
    for (( innerFlag=0; innerFlag < 5; innerFlag++ ))
    do
        if (( $innerFlag == 2 ))
        then
            break
        fi
        echo "innerFlag=$innerFlag"
    done
    echo "outerFlag=$flag"
done

以上代码在执行内部循环for的时候,当innerFlag值为2的时候就会跳出到外层的while循环, 由于外层循环一直flag都为0, 所以while会成为一个死循环,不停的输出:

…

innerFlag=0

innerFlag=1

outerFlag=0

…

break用于跳出外层循环

break 可后接数字,用于表示退出当前循环的外层的第几层循环。

示例三:

#!/bin/bash
flag=0
while (( $flag < 10 ))
do
    for (( innerFlag=0; innerFlag < 5; innerFlag++ ))
    do
        if (( $innerFlag == 2 ))
        then
              # 2表示外面一层循环
            break 2
        fi
        echo "innerFlag=$innerFlag"
    done
    echo "outerFlag=$flag"
done

与上面例子相比,本例就只是在break后面跟了个数字2,表示退出外面的第一层循环。最终输出:

innerFlag=0

innerFlag=1

continue

continue表示终止当前的一次循环,进入下一次循环,注意,continue后面的语句不会执行。

continue的语法与break一样,因此,就只做一个示例演示啦。

示例:

flag=0
while (( $flag <= 10 ))
do
    if (( $flag == 5 ))
    then
        flag=$[$flag+1]
        continue
    fi
    echo "outerFlag=$flag"
    for (( innerFlag=11; innerFlag < 20; innerFlag++ ))
    do
        if (( $innerFlag == 16 ))
        then
            flag=$[$flag+1]
            continue 2
        fi
        echo "innerFlag=$innerFlag"
    done
done

以上例子: 当for循环中innerFlag的值为16的时候会跳到外层while循环,当外层循环的flag的值为5的时候,会直接跳过本次循环,然后进入下一次循环,因此在输出的结果中,不会出现outerFlag=5的情况。

根据参数位置获取参数

bash shell可根据参数位置获取参数。通过 $1 到 $9 获取第1到第9个的命令行参数。 0 为 s h e l l 名 。 如 果 参 数 超 过 9 个 , 那 么 就 只 能 通 过 0为shell名。如果参数超过9个,那么就只能通过 0shell9{}来获取了, 例如获取第10个参数,那么可以写为${10}。

示例一:

#!/bin/bash
#testinput.sh
echo "file name: $0"
echo "base file name: $(basename $0)"
echo "param1: $1"
echo "param2: ${2}"

运行上面的的shell

./testinput.sh 12 34

最终得到的结果如下:

file name: ./testinput4.sh

base file name: testinput4.sh

param1: 12

param2: 34

成功的得到文件名和命令行输入的参数(命令行参数以空格分隔,如果参数包含了空格,那么久必须添加引号了)

0 默 认 会 获 取 到 当 前 s h e l l 文 件 的 名 称 , 但 是 , 它 也 包 含 ( . / ) , 如 果 你 以 完 整 路 径 运 行 , 那 么 这 还 会 包 含 目 录 名 。 因 此 , 上 面 通 过 b a s e n a m e 命 令 来 获 取 单 纯 的 文 件 名 0默认会获取到当前shell文件的名称,但是,它也包含(./),如果你以完整路径运行,那么这还会包含目录名。因此,上面通过basename命令来获取单纯的文件名 0shell(./)basename(basename $0)。

试想一下,假如我们写的shell的这个参数很多,那如果像上面那样一个一个去获取参数,那岂不是要写疯!下面就来看看如何解决这种情况。

读取所有参数

既然bash shell通过位置可获取参数,那意味着如果我们知道参数的总个数就可以通过循环依次获取参数。那么如何获取参数总个数呢?

在bash shell中通过 $# 可获取参数总数。

示例:(循环获取参数)

#!/bin/bash
for (( index=0; index <= $#; index++ ))
do
    echo ${!index}
done

以上示例,我们通过 $# 获取总参数个数。然后通过循环获取每个位置的参数。注意: 按照正常的理解,上面的 ${!index} 应该是 KaTeX parse error: Expected '}', got 'EOF' at end of input: {index}才对, 对吧? 但是,由于 内 不 能 再 写 {}内不能再写 符号,bash shell在这个地方是用了!符号,所以以上才写为了${!index}。

方法二
在bash shell中还可以通过 $* 和 $@ 来获取所有参数。但是这两者之间有着很大的区别:

$* 会将命令行上提供的所有参数当作一个单词保存, 我们得到的值也就相当于是个字符串整体。

$@ 会将命令行上提供的所有参数当作同一字符串中的多个独立的单词。

可能文字看起来描述的不太清楚,那么还是通过示例来看二者的区别吧:

#!/bin/bash
#testinput.sh
var1=$*
var2=$@
echo "var1: $var1"
echo "var2: $var2"
countvar1=1
countvar2=1
for param in "$*"
do
    echo "first loop param$countvar1: $param"
    countvar1=$[ $countvar1 + 1 ]
done
echo "countvar1: $countvar1"

for param in "$@"
do
    echo "second param$countvar2: $param"
    countvar2=$[ $countvar2 + 1 ]
done
echo "countvar2: $countvar2"

执行上面的示例:

./testinput.sh 12 34 56 78    

上面示例的输出结果为:

var1: 12 34 56 78

var2: 12 34 56 78

param1: 12 34 56 78

countvar1: 2

param1: 12

param2: 34

param3: 56

param4: 78

countvar2: 5

通过上面的结果可见,直接输出看起来二者结果一样,但是通过for循环就可看出二者的区别了。上一篇文章我们讲到for循环会通过IFS定义的值进行分割,因此默认情况下,如果我们上面在for循环处不加引号,那么根据IFS中所定义的空格分割,最终也会导致看不出二者区别。

单个输入

有时候,我们在shell执行过程中获取用户的输入,以此与用户进行交互。这是通过read命令来实现的。下面就来看看其用法:

示例一

#!/bin/bash
echo -n "yes or no(y/n)?"
read choice
echo "your choice: $choice"

运行以上示例,首先会输出”yes or no(y/n)?“, 然后会等待用户输入(-n参数表示不换行,因此会在本行等待用户输入),当用户输入后,会把用户输入的值赋值给choice变量, 然后最终输出 “your choice: (你输入的内容)”。

事实上,我们可以不指定read后面的变量名,如果我们不指定, read命令会将它收到的任何数据都放进特殊环境变量REPLY中。如下:

示例二:

#!/bin/bash
echo -n "yes or no(y/n)?"
read
echo "your choice: $REPLY"

以上示例与示例一是等价的。

有时候,我们需要用户输入多个参数,当然,shell是支持一次接受多个参数输入的。

多个输入

示例三:

#!/bin/bash
read -p "what's your name?" first last
echo first: $first
echo last: $last

以上示例首先输出“what’s your name?”, 然后在本行等待用户输入(此处用read -p实现以上示例的echo -n + read命令的不换行效果),输入的参数以空格分隔,shell会把输入的值依次赋值给first和last两个变量。如果输入的值过多,假如我输入了3个值,那么shell会把剩下的值都赋值给最后一个变量(即第二三两个的值都会赋值给last变量)。

细想一下,有个问题,假如用户一直不输入,怎么办?一直等待?

超时设置

我们可以通过read -t 来指定超时时间(单位为秒),如果用户在指定时间内没输入,那么read命令就会返回一个非0的状态码。

示例四:

#/bin/bash
if read -t 5 -p "Please enter your name: " name 
then
    echo "Hello $name"
else
    echo "Sorry, timeout! "
fi

运行以上示例,如果超过5秒没输入,那么就会执行else里面的。

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值