bash基础引导

这篇文章是从之前的博客里面分离出来的,并做了一些整理。

最初学习shell脚本是在2015年,工作的这几年主要还是用php处理相关的需求,shell更多是运维小伙伴来处理些东西。这就导致我的shell基本忘没了。这不,这周想写写shell,碰到好多的语法错误,记录一下。

变量,字符串

var="aaabbbccc"

# 字符串替换
echo ${var/aaa/ddd}

# 字符串截取
echo ${var:0:8}
echo ${var:0:-1} # zsh支持,测试用bash3.2.57不支持

# 获取字符串长度
echo ${#var}

# 字符串拼接
echo 'my name '$var'!'
echo "my name $test!"
echo "my name ${test}!"

注意点:

  1. 等号两边不能有空格,否则会被识别为命令
  2. 单引号不解析变量,双引号解析

内置变量

  • $0: 脚本名称
  • $n: 第n个参数
  • $#: 参数个数
  • $?: 上个语句的返回值
  • $@: 脚本参数字符串,与$*略有区别
  • $*: 脚本参数字符串
  • $$: pid

注意点:

  1. $@,$*都可以代表参数字符串,略有区别,$*加上引号时候"$*",那么它表示"$1 $2…$n"的一个字符串,而不是由每个参数组成的列表,$@行为不变。所以下面的代码会输出一行空格分开的参数列表:
for i in "$*"
do
    echo $i
done

命令

在shell脚本中执行命令、重定向之类的和在终端上执行没啥区别,直接写命令就行。例如:

echo "aaa" > a.log
echo "who am i `whoami`"
echo "who am i $(whoami)"

字符串拼接命令并运行:

c="ls"
c="$c /tmp"
$c

# or
echo "$c /tmp" | sh

流程控制

流程控制这块花费的时间比较多,主要是bash的判断写法很多,初学者非常容易混淆,下面做一些简单的解释:

总览

首先bash中的判断只有如下几种:

  1. 单中括号
if [ expression ]; then
  1. test
if test expression; then
  1. 双中括号
if [[ expression ]]; then
  1. 双小括号
if (( expression )); then

单中括号 & test

这两种方式是最古老的,也是兼容性最好的。

这两个放在一起说明是一样的,也即test[是完全一样的两个东西,man页面都是一样的。所以两者完全可以互换,在括号比较多的时候通常会用test来替换,这样看起来更简洁一些。

这两种判断主要用在两个地方:

  1. 数字判断,例如:
if [ 10 -gt 5 ]; then
  1. 字符串判断,例如:
if [ "aa" = "bb" ]; then

到这里可能又混淆了,为什么字符串和数字还不一样呢?其实本质上判断条件是test[命令的参数,而参数规定就是这样写

这里面如果用大于号或者小于号之类的会出发重定向,所以比较字符串大小不推荐用这种方法

除了这两种用法,还有就是文件相关的判断,有很多的选项,具体可以查看文档。例如:

比较文件新旧

# 如果file1的修改时间比file2新
if [ file1 -nt file2 ]; then

# 反之,旧
if [ file1 -ot file2 ]; then

判断是否为空文件

# 文件非空返回true
if [ -s /tmp/b.sh ]; then

判断多个文件的简便写法

# -a 等同于 &&,同理,-o为 ||
if [ -f $file -a -f $file2 ]; then

不过文档上更建议这种写法,理由是-a或-o不好理解:

if test -f $file && test -f $file2; then

判断字符串是否为空:

# -z 字符串为空返回true
if [ -z "$*" ]; then

# -n 字符串不为空返回true
if [ -n "$*" ]; then

判断文件是否为符号连接

if [ -h $file ]; then

# 注意这里不能用-f,-f会找到符号链接所指的文件,可能一直为true

双中括号

双中括号与单中括号功能类似,上面所有的代码都可以换成双中括号的写法。双中括号可以看作是单中括号的加强版,毕竟是新的产物,通常会解决一部分痛点。相比之下双中括号有以下几个特点:

  1. 扩展glob,也就是支持一些通配符之类的,例如:
if [[ "strings" == string* ]]; then

注意,后面的glob不能加双引号

  1. 扩展文件名,例如:
if [ -e *.sh ]; then

这种写法会扩展glob,如果当前目录下有一个.sh文件,返回true。如果有多个,会有语法错误。

if [[ -e *.sh ]]; then

这种写法不会扩展glob,也就是只有当前目录下有*.sh文件,才会返回true。

  1. 逻辑运算 两者逻辑运算写法不同,分别是:
if [ $a -gt 10 -a $a -lt 20 ]; then

# 或者
if [ $a -gt 10 ] && [ $a -lt 20 ]; then

# 后者的写法
if [[ $a > 10 && $a < 20 ]]; then
  1. 正则表达式 双中括号的写法,如果用了=~,那么右侧参数会被识别为正则表达式,例如:
# 同样不能使用双引号
if [[ "$1" =~ ^[0-9]+$ ]]; then
  1. 变量不需要添加引号 双中括号对变量做了特殊处理,防止分裂,所以变量内有空格不影响。但是保持变量两侧有双引号是一个好的习惯。

双小括号

这种方式也是用来数学运算,相对单中括号来说更像其他编程语言,例如:

if (( 12 > 10 )); then

if (( 12 > 10 && 5 < 6 )); then

# 还能直接进行算数计算,单中括号是不支持的
if (( 12%6 == 0 )); then

简单总结

简单粗暴一点就是:数字相关的比较,用双小括号;字符串相关的用双中括号。当然如果精益求精酌情使用即可。

分支语句、循环

a="wu"
case "$a" in
    "as") echo 'as';;
    "zz"|"xx") echo "zz and xx";;
    *) echo "default";;
esac

# 循环
for var in {1..7}; do
    tmp=$(( $var%2 ))
    # echo $tmp
    if [ $tmp -eq 0 ]; then
       echo $var
    fi
done

# 传统for循环
for ((i=0; i < 5; i++)); do
    echo $i
done

# 输出文件信息
for file in $(ls); do
    if [ -e $file ] && [ ! -d $file ] && [ -r $file ]; then
        wc $file
    fi
done

# while循环
tmp=0
while [ true ]; do
    if [ $tmp -gt 5 ]; then
        break
    fi
    echo $tmp
    ((tmp=$tmp+1))
done

注意点:

  1. 中括号内部两侧需要有空格,否则会被识别为命令
  2. 变量名最好加上双引号,否则当一个变量中包含空格的时候,会引发问题(双中括号的形式没有这个问题)

函数

函数和其他语言类似:

function test()
{
    echo "参数个数:$#,参数列表:$@,第一个参数:$1"
    return 0
}

# 调用
test 'a' 'b' 'c'
# 输出:参数个数:3,参数列表:a b c,第一个参数:a

echo $?
# 输出:0

需要注意的是,我们可以通过var=$(fun)语法来获取函数的标准输出,返回值我们要通过$?来获取。

判断函数是否存在

下面这些如果返回true代表命令存在:

if command -v foo >/dev/null 2>&1; then

if type foo >/dev/null 2>&1; then

if hash foo 2>/dev/null; then

bash中变量的作用域都是全局的,为避免函数中的变量被污染,可以在函数内部使用local关键字修饰变量。

bash中引入文件可以直接用source命令。

数学计算

# 方法1
((var=1024/1024))
((c=$a+$b))
c=$((a+b))

# 方法2
let "var=1024/1024"
let c=a+b

# 方法3
var=$[1024+1024]
var=$[a+b]
var=$[$a+$b]

# 方法4(运算符之间必须有空格)
var=`expr $var + 1`
# 注意乘法这里的转义字符
var=$(expr $var \* 2)

# 借助外部程序
# bc
var=`echo "20*10"|bc`

# awk
var=`echo "20 2"|awk '{printf("%i",$1*$2)}'`

# awk的printf与c语言的类似,换为%f可以支持浮点运算
echo `echo 2014 2098 | awk '{printf("%f", $1/$2)}'`

对于shell的理解并不深刻,如有错误,欢迎指正。

参考链接

learn x in y minutes Conditions in bash scripting (if statements) Bash Guide for Beginners

(完)