Bash 基本语法

2018-03-01 bash language

在 Linux 中通过 Bash 与系统进行简单的交互,但是,通常可能会有一些负责的逻辑进行处理,这就涉及到了 Bash 的编程,实际上就是通过命令行以及一些常见的处理逻辑。

这里简单介绍常见的语法。

判断

Shell 中使用条件判断时,有几种方式:A) if ... fi;B) if ... else ... fi;C) if ... elif ... else ... fi

在 Shell 编程中,如果要判断条件是否满足,可以使用 [ ] test [[ ]] let (( )) 这几种方式,其中 [ ] test 是 Shell 的内部命令,其功能基本相同,而 [[ ]] 是 Shell 的关键字。

[ ] 的三个基本功能是判断文件、字符串、整数,需要注意:

  • 比较运算符只支持 == !=,两者用于字符串比较,不可用于整数比较,整数只能使用类似 -eq -gt 这种形式。注意,如果要使用 < > 需要加转义,否则会被作为重定向。
  • let (( )) 功能基本相同,用于算术运算以及整数比较,可以直接使用 < > 等比较运算符。而且可以直接使用变量名不需要 $var 这种形式。

判断方式

经常会通过 [] 或者 [[]] 进行判断条件的处理,两者略有区别,如下简单介绍。

1. 关键字 VS. 内部命令

$ [ 2 < 1 ] && echo True || echo False
True
$ [[ 2 < 1 ]] && echo True || echo False
False

因为是命令,那么它就会和参数组合为一体被 Shell 解析,例如上述的 > < 就被 Shell 解释为重定向符号了,导致第一个认为是 True ;而关键字会认为这就是一个条件判断,所以其返回的结果符合预期。

2. 逻辑判断

$ x=1, y=1
$ [ $x == 1 && $y == 1 ] && echo True || echo False
-bash: [: missing `]'
False
$[ $x == 1 -a $y == 1 ] && echo True || echo False
True
$ [[ $x == 1 && $y == 1 ]] && echo True || echo False
True

如果使用与、或等判断条件时,需要注意:A) 在 [[ 中可以使用 &&||;B) 在 [ 中需要使用 -a-o

3. 字符匹配

$ [[ 'abcd' == a*d ]] && echo True || echo False
True
$ [ 'abcd' == a*d ] && echo True || echo False
False

[[ 支持字符串的模式匹配,右侧会被作为一个模式,而不仅仅是一个字符串,而且不需要使用引号。另外,果使用 =~ 操作符时还可以支持 Shell 的正则表达式。

总结

建议使用 [[ ]] ,相比来说,使用 [[ ]] 会更加简单,而且符合判断逻辑,可以防止出现逻辑错误,另外还支持字符串、命令等较高级的模式。

使用方法总结如下。

  • [[ ]] 左右都要有空格分隔;
  • 内部操作符与操作变量之间要有空格 [[ "a" == "b" ]]
  • 字符串比较可以直接使用 > < 无需转义,也可以使用 && || 逻辑判断;
  • 字符串或者 ${} 变量默认会进行模式和元字符匹配,也可以使用 "" 进行严格匹配;

数组

数组有如下的几种处理方式。

#----- 使用[]操作符
foobar[0]="Hello"
foobar[1]="World"
foobar[2]="!!!"

#----- 使用()直接赋值,如下两种方式相同
foobar=("Hello" "World" "!!!")
foobar=([0]="Hello" [1]="World" [2]="!!!")

#----- declare -a 定义一个空的数组,如果已经定义则不会清零
declare -a foobar

#----- 也可以从文件中读取,每行作为其一个元素,注意可能会失败报错
foobar=(`cat 'foobar.txt'`)

读取数组

foobar=("Hello" "World" "!!!")
#----- 可以直接通过下标读取
echo ${foobar[0]}

#----- 获取数组长度
echo ${#foobar[@]}

#----- 未定义的下标不会占用个数
foobar=([0]="Hello" [1]="World" [5]="!!!")
echo ${#foobar[@]}

#----- 获取数组的一部分,会使用上述的下标
echo ${foobar[@]:4:5}

#----- 合并两个数组
hello=("Hi" "Foobar")
all=(${foobar[@]} ${hello[@]})
echo ${#all[@]}

使用 @ 这个特殊的下标,可以将数组扩展成列表,然后就可以使用 bash 中的获取变量长度的操作符 # 来获取数组中元素的个数。注意,没有定义的数组下标,并不会占用数组中元素的个数,如上所述。

模式操作符对数组也是有效的,可以使用它来替换数组中的元素

#----- 修改数组,这里是临时替换,也可以重新保存一个
echo ${foobar[@]/World/Foobar}
nfoobar=(${foobar[@]/World/Foobar})

数组遍厉如下。

for foo in ${foobar[@]}; do
       echo ${foo}
done

len=${#adobe[@]}
for ((i = 0; i < $len; i++));do
	echo ${adobe[$i]}
done

for 循环

使用 for 循环时,基本有如下的几种方法。

#----- 普通枚举类型,列表通过空格或者回车分隔
for fruit in 'apple' 'meat' 'sleep' 'woman'; do
	echo "I like ${fruit}"
done

#----- 也可以使用`` ()调用子命令
ans=0
for i in `seq 0 2 100`; do
    let ans+=${i}
done
echo $ans

#----- 使用花括号标示枚举,例如{1..100} {Z..A}
ans=0
for i in {1..100}; do
    let ans+=${i}
done
echo $ans

如果使用 {} 也可以指定增量,例如 {0..100..2} 对应的步长是 2 ,而 seq 命令为 seq 首数 [增量] 末数 。也可以使用如下的方式,在一行内运行 for 变量 in 取值列表; do 各种操作; done

另外,还支持 C 语言风格的 for 循环,示例如下,也就是计算一下 1~100 的和。

ans=0
for ((i = 1; i <= 100; i++)); do
        let ans+=$i
done
echo $ans

注意,数值比较只支持整形,如果要使用浮点数,则需要通过 bc 进行转换。

a=7.2; b=8.3
if [[ `echo "$a < $b" | bc` -eq 1 ]] ; then
    echo  "$a < $b "
fi

示例

参数遍厉

Bash 中在遍历参数时,尽量采用 $@ 而非使用 $* 参数,其中前者可以正确处理一些带有空格的参数,例如如下的示例。

foobar() {
    for var in "$@"; do
        echo "$var"
    done
}
foobar 1 2 "3 4"

文件遍历

for file in data/*; do
	echo $file
done

函数

其中 $# 表示所有入参的数量,位置参数 $1 $2$N 代表了各个参数,而 $0 代表了第一个参数,也就是脚本的名字。

函数的返回值需要小于 255 ,可以通过 $? 获取。

function foobar() {
    if [[ $1 == "foo" ]]; then
        return 1
    fi
    return 0
}

foobar foo
echo $?
foobar
echo $?

也可以通过 echo 命令返回一个字符串。

function foobar() {
    if [[ $1 == "foo" ]]; then
        echo "bar"
        return
    fi
    echo "foo"
}

ret=$(foobar foo)
echo $ret

这里采集的是标准输出,需要特别注意标准错误输出的处理方式。

括号使用

在编写 Shell 脚本时,可以使用多种括号,其中使用括号主要有如下的几种方式:

  • ${var} 引用变量;
  • $(cmd) ```` 调用命令并获取标准输出,注意不会收集标准错误输出;
  • () {} 用于执行子命令,使用方式略有区别;
  • $((expr)) 执行数学运算,其语法类似于 C 语言;
  • ${var: } ${var% } ${var# } 用于字符串的替换、匹配。

() VS.

二者都用于执行一串命令,指令之间用 ; 分开,其区别为:

  • () 需要重新开启一个 Shell 运行命令,{} 会在当前 Shell 运行;
  • () 最后一条命令不需要 ; ,而 {} 最后一条命令需要 ;
  • {} 第一条指令和左括号之间需要有空格,而 () 不需要。
$ var=test
$ (var=notest; echo $var)   # 只影响到子Shell
$ echo $var

$ { var=notest; echo $var; }
$ echo $var                 # 同时也会被修改

$((expr))

用来执行数学计算,简单介绍其使用方法。

$ echo $((3 + 2))
5
$ echo $((3 > 2))
1
$ echo $((25 < 3 ? 2 : 3))
3
$ echo $var

$ echo $((var = 2 + 3))
5
$ echo $var
5
$ echo $((var++))
5
$ echo $var
6
$ echo $((++var))
5