shell脚本基础

本文

主要对shell脚本的基础语法知识进行记录。

版本 说明
0.1 初版发布

背景

  • 主机: Thinkpad S2
  • 系统: Deepin GNU/Linux 15.11
  • 内核: Debian 6.3.0-18+deb9u1
  • shell:bash

学习网站

字符串截取

计数截取

  • 左边开始计数:${string: start :length}
    • start 是起始位置(从左边开始,从 0 开始计数)
    • length 是要截取的长度(省略的话表示直到字符串的末尾)
1
2
3
url="c.biancheng.net"
echo ${url: 2: 9}
# 结果:biancheng
  • 右边开始计数:${string: 0-start :length}
    • 右边开始计数时,起始数字是 1
    • 不管从哪边开始计数,截取方向都是从左到右。
1
2
3
url="c.biancheng.net"
echo ${url: 0-13: 9}
# 结果:biancheng

指定子字符串截取

  • 截取右边字符:${string#*chars}
    • #*chars 表示忽略左边的所有字符,直到遇见 chars(chars 不会被截取)
    • 当有多个匹配时,只从左到右第一个有效
1
2
3
url="http://c.biancheng.net/index.html"
echo ${url#*/}
#结果:/c.biancheng.net/index.html
  • 截取右边字符:${string##*chars}
    • ##*chars 表示忽略左边的所有字符,直到遇见 chars(chars 不会被截取)
    • 当有多个匹配时,只从左到右最后一个有效
1
2
3
url="http://c.biancheng.net/index.html"
echo ${url##*/}
#结果:index.html
  • 截取左边字符:${string%chars*}
    • %chars* 表示忽略右边的所有字符,直到遇见 chars(chars 不会被截取)
    • 当有多个匹配时,只从右到左第一个有效
1
2
3
url="http://c.biancheng.net/index.html"
echo ${url%/*}
#结果:http://c.biancheng.net
  • 截取左边字符:${string%%chars*}
    • %%chars* 表示忽略右边的所有字符,直到遇见 chars(chars 不会被截取)
    • 当有多个匹配时,只从右到左最后一个有效
1
2
3
url="http://c.biancheng.net/index.html"
echo ${url%%/*}
#结果:http:

总结

格式 说明
${string: start :length} 从 string 字符串的左边第 start 个字符开始,向右截取 length 个字符。
${string: start} 从 string 字符串的左边第 start 个字符开始截取,直到最后。
${string: 0-start :length} 从 string 字符串的右边第 start 个字符开始,向右截取 length 个字符。
${string: 0-start} 从 string 字符串的右边第 start 个字符开始截取,直到最后。
${string#*chars} 从 string 字符串第一次出现 *chars 的位置开始,截取 *chars 右边的所有字符。
${string##*chars} 从 string 字符串最后一次出现 *chars 的位置开始,截取 *chars 右边的所有字符。
${string%*chars} 从 string 字符串第一次出现 *chars 的位置开始,截取 *chars 左边的所有字符。
${string%%*chars} 从 string 字符串最后一次出现 *chars 的位置开始,截取 *chars 左边的所有字符。

字符串替换

简单的替换

  • b=\({a/123/321};将\){a}里的第一个123替换为321
  • b=\({a//123/321};将\){a}里的所有123替换为321

删除前后空格

1
2
3
4
5
6
a=" 123 456  "
# 方法一
b=`echo $a`
# 方法二
b=`echo $a | sed -r 's/^[ \t]*//g'` # 删除行首空格
b=`echo $b | sed -r 's/[ \t]*$//g'` # 删除行尾空格

sed与正则表达式

基础

  • 命令格式:
    • 面对文件:sed [options] ‘command’ file(s)
    • 面对变量:echo $b | sed [options] ‘command’
  • 常用选项:
    -n :使用安静(silent)模式,只显示处理的行。
    -r :支持延伸型正则表达式
    -i :直接修改读取的文件内容,而不是输出到终端。
  • 常用功能:
    • s:替换字符串
    • d:删除行
    • a:新增行
    • i:插入行
    • c:替换行
    • p:输出到终端

举例

  • 替换字符串:
1
2
3
4
sed 's/aa/AA/' test.txt  # 替换aa为AA,注意每行只替换从左到右第一个匹配处
sed 's/aa/AA/g' test.txt # 替换aa为AA,注意每行替换所有匹配处
sed '5,$s/aa/AA/g' test.txt # 指定第5行至文件末尾为操作域
sed '/^[0-9]/s/aa/AA/g' test.txt # 指定数字开头的行为操作域
  • 删除:
1
2
sed '1,4d' test.xx     # 删除1至4行
sed '/^2/d' test.txt   # 删除以数字2开头的行(//里内容是正则表达式,并且必要时可以添加-r选项)
  • 新增与插入
1
2
sed '1a hello world' test.txt # 在第1行后面新增hello world(也就是处理后的第2行)
sed '1i hello world' test.txt # 在第1行前面插入hello world(也就是处理后的第1行)
  • 替换行
1
2
sed '1c hello world' test.txt      # 第1行替换为hello world
sed '/^2/c hello world' test.txt   # 以数字2开头的行替换为hello world
  • 输出到终端
1
2
sed -n '2p' test.txt # 把第二行输出到终端
sed -n -r '/^2/p' test.txt # 把以数字2开头的行输出到终端
  • 文件修改的方法:
    • sed ‘command’ file(s) > tmp_file (重定向到临时文件)
    • sed -i ‘command’ file(s) (直接修改文件,最好处理前备份一下)

条件判断 if

if-elif-else-fi

1
2
3
4
5
6
7
if (( $age <= 18 )); then
    echo "儿童"
elif (( $age > 18 && $age <= 40 )); then
    echo "青年"
else
    echo "老年"
fi

数值比较

  • 数值的运算使用(()):支持 +、-、*、/、%
1
2
3
4
5
6
a=100
b=50
c=$(($a + $b))
d=$((100 + 50))
echo $c
echo $d
  • 数值的比较使用(()):支持 >、>=、<、<=、==、!=
1
2
3
4
5
6
7
a=100
b=50
if (($a > $b));then
    echo "max is a"
else
    echo "max is b"
fi

字符串比较

  • 字符表达式的比较使用 [[]]:支持 =、!=、-z 和 &&、|| 、! 运算符 (注意,中括号与表达式间要保留空格)
1
2
3
4
5
# str1 或 str2 非空
if [[ -z $str1 || -z $str2 ]]
# 支持通配符
if [[ hest = h??t ]] # ? 表示任意字符
if [ hest = h*t ]] # * 表示任意个任意字符
  • 支持正则表达式:=~
    • ^ 匹配字符串的开头(一个位置);
    • [0-9]{10} 匹配连续的十个数字;
    • $匹配字符串的末尾(一个位置)
1
2
3
4
5
6
if [[ $tel =~ ^1[0-9]{10}$ ]]
then
    echo "你输入的是手机号码"
else
    echo "你输入的不是手机号码"
fi

文件表达式

文件表达式的测试使用 [[]] ,其运算符如下:

表达式 含义
-e filename 如果 filename 存在,则为真
-d filename 如果 filename 为目录,则为真
-f filename 如果 filename 为常规文件,则为真
-L filename 如果 filename 为符号链接,则为真
-r filename 如果 filename 可读,则为真
-w filename 如果 filename 可写,则为真
-x filename 如果 filename 可执行,则为真
filename1 -nt filename2 如果 filename1 比 filename2 新,则为真
filename1 -ot filename2 如果 filename1 比 filename2 旧,则为真

循环for和while

for

1
2
3
4
for line in `cat filename` # 或 for line in $(cat filename)
do
    echo $line
done

这里有时候会出现问题,bash没有将换行符作为一行的分隔符,而是将空格作为了分隔符。这时候需要更改shell的系统变量IFS。

1
2
3
IFS='\n'   # 无效
IFS=$"\n"  # 无效
IFS=$'\n'  # 有效

以上这三个赋值看起来都比较像”将换行符赋值给IFS“,但实际上只有最后一种写法才是我想要的结果,一定要注意。

  • IFS=‘\n’ //将字符n作为IFS的换行符。
  • IFS=$”\n” //这里\n确实通过$转化为了换行符,但仅当被解释时(或被执行时)才被转化为换行符。
  • IFS=$’\n’ //这才是真正的换行符。

while

1
2
3
4
while read -r line
do
 echo $line
done < filename

break与continue

  • break:表示跳出当前的整个循环。break 关键字通常和 if 语句一起使用,即满足条件时便跳出循环。
1
2
3
4
5
6
7
while read n; do
    if((n>0)); then
        ((sum+=n))
    else
        break
    fi
done
  • continue:表示过本次循环,忽略本次循环的剩余代码,直接进入下一次循环。
1
2
3
4
5
6
while read n; do
    if((n<1 || n>100)); then
        continue
    fi
    ((sum+=n))
done

重定向

输出重定向

1
echo $str >>demo.txt  #将输入结果以追加的方式重定向到文件
类 型 符 号 作 用
标准输出重定向 command >file 以覆盖的方式,把 command 的正确输出结果输出到 file 文件中。
标准输出重定向 command »file 以追加的方式,把 command 的正确输出结果输出到 file 文件中。
标准错误输出重定向 command 2>file 以覆盖的方式,把 command 的错误信息输出到 file 文件中。
标准错误输出重定向 command 2»file 以追加的方式,把 command 的错误信息输出到 file 文件中。
正确输出和错误信息同时保存 command >file 2>&1 以覆盖的方式,把正确输出和错误信息同时保存到同一个文件(file)中。
正确输出和错误信息同时保存 command »file 2>&1 以追加的方式,把正确输出和错误信息同时保存到同一个文件(file)中。
正确输出和错误信息同时保存 command >file1 2>file2 以覆盖的方式,把正确输出保存到 file1 文件,把错误信息保存到 file2 文件中。
正确输出和错误信息同时保存 command »file1 2»file2 以追加的方式,把正确的输出结果输出到 file1 文件中,把错误信息输出到 file2 文件中

输入重定向

1
2
3
while read str; do
    echo $str
done <readme.txt
符号 说明
command <file 将 file 文件中的内容作为 command 的输入。
command «END 从标准输入(键盘)中读取数据,直到遇见分界符 END 才停止(分界符可以是任意的字符串,用户自己定义)。
command file2 将 file1 作为 command 的输入,并将 command 的处理结果输出到 file2。

case-esac

以读取命令行参数为例:

 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
usage()
{
    echo "usage: $0 [--testname testname] [--basetest btname] [--dump] [--debug] [--simpath simlogpath] [--nocmp] [--seed seednum] [--cov] [--funcov] [--xrun]"
}

while [[ x$1 != x]]; do
    case $1 in
        --testname )
            shift
            testname=$1
            ;;
        --basetest )
            shift
            btname=$1
            ;;
        --dump     )
            dump=1
            ;;
        --debug    )
            debug=1
            ;;
        --simpath  )
            shift
            simlogpath=$1
            ;;
        --nocmp    )
            nocmp=1
            ;;
        --seed     )
            shift
            seednum=$1
            ;;
        --cov      )
            cov=1
            ;;
        --funcov   )
            funcov=1
            ;;
        --xrun     )
            xrun=1
            ;;
        -h         )
            usage
            exit 0
            ;;
        *          )
            usage
            exit 0
            ;;
    esac
    shift
done

特殊变量

变量 含义
$0 当前脚本的文件名。
$n(n≥1) 传递给脚本或函数的参数。n 是一个数字,表示第几个参数。例如,第一个参数是 $1,第二个参数是 $2。
$# 传递给脚本或函数的参数个数。
$* 传递给脚本或函数的所有参数。
$@ 传递给脚本或函数的所有参数。当被双引号” “包含时,$@ 与 $* 稍有不同,我们将在《Shell $*和$@的区别》一节中详细讲解。
$? 上个命令的退出状态,或函数的返回值,我们将在《Shell $?》一节中详细讲解。
$$ 当前 Shell 进程 ID。对于 Shell 脚本,就是这些脚本所在的进程 ID。

文章原创,可能存在部分错误,欢迎指正,联系邮箱 cao_arvin@163.com。