Linux-Shell脚本基础 背景 此前讲述的都是基于命令行的Linux命令,其实那些都是非常常用的,也是基础中的基础。本文基于之前的内容,讲述关于shell编程的一些语言基础和相关的结构化命令,如:条件判断、循环。
内容 脚本基础 之前我们讲,通过;
可以在一行之中同时编辑多条命令运行,并且通过()
可以将多条命令整合成一条命令运行起来,那么在shell脚本中如何运行多条命令呢?如下:
在shell中,#
起注释作用,除了首行以外,首行的#!/bin/bash
表示运行该脚本所使用的程序。编写完成以后,如果你直接运行该文件(run.sh),则会提示command not found
,这个是因为我们没有将shell脚本所在的路径纳入到PATH
搜索环境中。解决方案如下:
或者,如果不想修改PATH的搜索路径,则通过指定脚本具体的路径来运行
但是这么运行的话,会提示Permission denied
,这是因为该文件没有可执行权限,所以需要追加执行权限
在shell中,有一个输出内容的命令echo
1 2 3 4 5 # 如果输出的内容可以用单引号/双引号包裹即可 echo "内容" # -n:取消默认输出内容末尾的换行符 echo -n "内容"
同样的在shell脚本中,是允许用户直接访问环境变量的,访问的方式依然是$变量名
,其中$
在shell中被认为是直接调用变量的特殊符号,因此即便是被双引号包裹,依然不会影响。
1 2 3 4 5 # 比如直接访问登录的用户USER echo $USER # 如果需要打印出$符号,则需要转义 echo "\$12"
在shell脚本中允许用户自己定义用户变量,但是这些变量的作用域仅限于当前shell脚本运行的环境下,脚本运行结束,则脚本中定义的用户变量就会失效。
1 2 3 4 5 6 7 # 定义变量的格式依然遵循:修改的时候不要$,引用的时候需要$ 变量名="变量值" # shell脚本会自己判断变量的类型 var1="shuai" var2=12 echo $var1
通过上述方式给用户变量赋值,是比较死的,有时候我们需要将命令的执行结果赋值给新的变量,这个在shell中也是支持的:
1 2 3 4 5 # 通过``包裹命令 var1=`date` # 通过$()包裹命令 var2=$(date)
此处有一点需要说明的是,通过上面方式执行的命令,本质是在shell脚本运行的终端下创建了一个子shell来运行。与此相类似的还有通过指定具体路径来运行shell脚本,本质上也是在当前的shell下创建子shell来运行脚本的。
有时候,我们想要将程序运行的内容保存到文件中,就说到了输出重定向:>、>>
1 2 3 4 5 # >:将命令的内容输出到文件中,并且覆盖文件中原本的内容 date > log.txt # >>:将命令的内容追加到文件的末尾 date >> log.txt
如果我们希望将文件的内容输入到命令中,则需要用到输入重定向:<、<<
1 2 # wc:统计文本中的行数、字符数、字节数 wc < run.sh
比较特殊的是<<
,被称为内联输入重定向,直接将命令行的内容作为数据输入给命令
1 2 3 4 # EOF只是起文本标示作用,可以用任意字符替代,但是首尾必须一样 wc << EOF who is the smart boy EOF
有时候,我们需要将一个命令的运行结果作为输入发送给另一个命令处理,这个时候就会用到管道命令:|
1 2 3 4 5 # 格式: 命令1 | 命令2 | 命令3 ... # 比如 cat /etc/passwd | sort | more
在shell中如果需要进行算数运算的话,有三种方式:expr、[]、bc
,其中expr
在shell中存在算数符号不兼容的情况,因此在bash中,可以使用[]
,[]
本身也只在bash
程序中支持,但是它和expr
一样,只支持整数类的运算,不支持浮点数的运算:
1 2 var1=$[ 1 + 2 ] var2=$[ $var1 * 2 ]
如果有浮点数运算需要的时候,则需要调用外部命令:bc
,它是bash内置的一个计算器,通过管道命令,我们可以直接将相关运算直接提交给bc
,然后将结果返回:
1 2 3 4 5 # 格式: variable=$(echo "options; expressions" | bc) # scale表示保留小数点后4位,bc程序默认保留0位 var1=$(echo "scale=4;4+5" | bc)
同样的,也可以利用重定向的命令将要计算的输入发送给bc程序
进行计算:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 # !/bin/bash var1=1 var2=2 var3=$(bc << EOF scale=4 # $var1 是直接调用了上面的用户变量的值,在shell中,它只会将变量的值传递到运算的位置,然后bc去执行得出结果 var4=$var1*$var2 var5=$var1+$var2 # 它会依次打印出所有非赋值表达式计算的结果 var6=var4+var5 var4*var5 EOF ) echo "the var3 is $var3"
在shell中,shell运行的每一个命令都会记录一个退出状态码,并且用一个环境变量记录最近一次命令执行的退出状态码:$?
:
1 2 3 4 5 6 7 # 非0表示命令执行错误,0表示命令执行成功 echo $? # 126:命令不可执行 # 127:没找到命令 # 128:无效的退出参数 # 130:Ctrl+c退出了shell
同样,在shell脚本中,我们可以自己指定退出参数:exit 数值
,但是退出的参数数值必须在0~255之间,过大的话,则系统自动截断(求余)
结构化命令 说到结构化命令,其实对应于程序语言中的判断语句
和循环语句
,首先说到判断语句,在shell中判断语句有两类形式,四种状态,先说if的三种状态:
单一状态的判断:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 # 格式 if condition then statement fi # then 可以和if 写在一行,只要中间以;分割 if condition; then statement fi # 比如 if ls $HOME/.ssh then echo "you are a good boy" fi
两种状态的判断:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 # 格式 if condition then statement else statement fi # 比如 if ls $HOME/.ssh then echo "you are a good boy" else echo "lazy boy" fi
多种状态的判断:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 # 格式 if condition; then statement elif condition; then statement # 也可以省略else else statement fi # 比如 if ls $HOME/.ssh; then echo "you are a good boy" elif ls $HOME/.profile_history; then echo "lazy boy" else echo "pretty boy" fi
同样,你也可以直接在if命令中进行if的嵌套
1 2 3 4 5 6 7 8 9 10 if ls $HOME/.ssh; then echo "you are a good boy" elif ls $HOME/.profile_history; then if ls $HOME/.profile_history/wudashuai; then echo "lazy boy" else echo "没文件" else echo "pretty boy" fi
仔细观察的话,你会发现,在shell中的判断条件非常的奇怪,它们是一些shell命令。这个是因为在shell中,if判断条件成立的情况是指这些shell命令的返回状态码为0,如果非0则不会执行if后面的语句
。但是这样的判断方式并不能满足复杂的使用环境,因此在bash shell中还有另外一个命令:test
,用于辅助判断语句。它已两种状态存在:test condition、[ condition ]
:
1 2 3 4 5 6 7 8 9 # test condition的形式, 如果不写condition,则test 会返回非0值 if test condition; then statement fi # [ condition ]的形式, 这种方式是最常用的 if [ condition ]; then statement fi
介于test condition
的形式不常用,所以下文主要记录[ condition ]
的形式,首先[ condition ]中可以进行数值、文本、文件比较
,进行数值比较的时候,有一点需要注意:它不是使用>、<
等的传统比较符号,而是使用django中gt、lt
等方式表示比较符号,并且bash shell中的test只能进行整数比较,不支持浮点数的比较
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 # n1 -qe n2: n1等于n2 if [ $n1 -eq $n2 ]; then echo "n1=n2" fi # n1 -ne n2: n1不等于n2 if [ $n1 -ne $n2 ]; then echo "n1 >= n2" fi # n1 -ge n2: n1大于等于n2 if [ $n1 -ge $n2 ]; then echo "n1 >= n2" fi # n1 -gt n2: n1大于n2 if [ $n1 -gt $n2 ]; then echo "n1 >= n2" fi # n1 -le n2: n1小于等于n2 if [ $n1 -le $n2 ]; then echo "n1 >= n2" fi # n1 -lt n2: n1小于n2 if [ $n1 -lt $n2 ]; then echo "n1 >= n2" fi
进行文本比较的时候,则是使用>、<
等符号了,然而你会发现>、<
与shell中的重定向命令重复导致冲突,所以在进行比较的时候就需要对>、<
进行转义:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 # str1 = str2: str1和str2相等 if [ $str1 = $str2 ]; then echo "str1=str2" fi # str1 != str2: str1和str2不相等 if [ $str1 != $str2 ]; then echo "str1!=str2" fi # str1 > str2: str1大于str2 if [ $str1 \> $str2 ]; then echo "str1=str2" fi # str1 < str2: str1小于str2 if [ $str1 \< $str2 ]; then echo "str1=str2" fi # -n str1: str1长度是否非0 if [ -n $str1 ]; then echo "str1 长度不为0" fi # -z str1: str1长度是否为0 if [ -z $str1 ]; then echo "str1 长度为0" fi
说到此处,则有一处需要指明,对于if判断条件中,如果存在空变量、未定义变量,则会产生灾难性的影响,为了避免这种情况,应该在不确定变量是否有值的情况下,需要用-n、-z
进行一下判断。
然后是shell编程中常用的文件比较:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 # -e file:检查file是否存在 if [ -e $file ]; then echo "file is exist" fi # -d file:检查file是否存在,并且是一个目录 if [ -d $file ]; then echo "file is a dirtory" fi # -f file:检查file是否存在,并且是一个文件 if [ -f $file ]; then echo "file is a txt" fi # -s file:检查file是否存在,并且非空 if [ -s $file ]; then echo "file not null" fi # -r file:检查file是否存在,并且可读 if [ -r $file ]; then echo "file could be read" fi # -w file:检查file是否存在,并且可写 if [ -w $file ]; then echo "file could be write" fi # -x file:检查file是否存在,并且可执行 if [ -x $file ]; then echo "file could be excute" fi # -O file:检查file是否存在,并且属于当前用户 if [ -O $file ]; then echo "file belongs to $USER" fi # -G file:检查file是否存在,并且默认组与当前用户属组相同 if [ -G $file ]; then echo "file belongs to $HOME" fi # file1 -nt file2:检查file1是否比file2新 if [ $file1 -nt $file2 ]; then echo "file is newer than file2" fi # file1 -ot file2:检查file1是否比file2旧 if [ $file1 -ot $file2 ]; then echo "file is older than file2" fi
同样的,对于if也可以使用多条件判断:
1 2 3 4 5 6 7 8 9 # &&:与逻辑 if [ condition ] && [condition]; then statement fi # ||:或逻辑 if [ condition ] || [condition]; then statement fi
在bash shell针对if -- then
中的数值运算,还提供了另外一种模式:(( condition ))
,在(( condition ))
中,>、<
等都和python中的比较一样,不需要进行转义;
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 # var++num:后加 if (( $var1++2 > $var2 )); then (( var1 = $var1++2 )) fi # var--num:后减 if (( $var1--2 > $var2 )); then (( var1 = $var1++2 )) fi # ++var:先增 if (( ++$var1 > $var2 )); then (( var1 = $var1++2 )) fi # --var:先减 if (( --$var1 > $var2 )); then (( var1 = $var1++2 )) fi # !: 逻辑求反 if (( !( $var1 > $var2 ) )); then (( var1 = $var1++2 )) fi # &&:逻辑与 if (( ( $var1--2 > $var2 ) && ( $var1 == 2 ) )); then (( var1 = $var1++2 )) fi # ||:逻辑或 if (( ( $var1--2 > $var2 ) || ( $var1 == 2 ) )); then (( var1 = $var1++2 )) fi # ~:按位求反 if (( $var1--2 > $var2 )); then (( var1 = ~$var1 )) fi # &:按位与 if (( $var1--2 > $var2 )); then (( var1 = $var1 & $var2 )) fi # |:按位或 if (( $var1--2 > $var2 )); then (( var1 = $var1 | $var2 )) fi
同样也对字符串比较提供了额外的方式:[[ condition ]]
,它除了支持原本[ condition ]
的运算符外,同时还提供了模式匹配的功能(正则)。
1 2 3 4 # ==:判断是否符合正则 if [[ $var1 == r* ]]; then (( var1 = $var1++2 )) fi
然后就是判断语句的另一种格式:case
1 2 3 4 5 6 7 8 9 10 11 12 13 # 格式 case variable in pattern1 | pattern2 ) command;; parttern3 ) command;; *) default command;; esac # 比如 case $var1 in 2 | 3 ) echo "\$var1 is 2 or 3";; 4 ) echo "\$var1 is 4";; * ) echo "\$var1 not in 2,3,4";; esac