SHELL脚本编程及字符操作

自动化运维第一步, shell编程以及熟练的字符串操作是十分重要的技能点! 开始修炼!

shell编程认知

首先要明确的是, shell编程语言是一个解释型语言. 也就是说, 我们所写的脚本其实就是文本文件而已. 这些文本文件CPU无法运行, 因此需要一个二进制的程序来解释他们. 这个程序就是解释器.

与其他的解释型语言(例如: Python)不同, shell近乎没有函数, 他不需要依赖于函数库. 这是为什么? 因为我们的shell很特殊. 他并不需要这些函数来支持, 因为已经有很多别人实现好的程序可以实现各种各样的功能了. 比如Python打开一个文件需要调用open函数, 而shell想要打开一个文件就直接调用例如cat这样的工具就可以了. 在解释器解释执行的时候, 会从环境变量中扫描到cat这个程序的所在, 接着执行它.

除了这些, 在脚本的开头我们经常会写上这样一句:

1
#!/bin/bash

这里的#! 是一种叫做魔数的机制, 通过这个机制了可以来判断文件该有什么解释器来进行解释, 如果一旦出现了空格或者没有写在第一行, 这个机制就会失效. 同样的, 还有每个图片文件的开头的一串特殊的数据会用来表示其格式很多很多. 都是通过这一种机制来表示自己的数据组织结构.

接下来简单说说SHELL的变量吧.

有一种变量叫做全局变量. 在shell中的表现就是环境变量. 声明一个环境变量的方法很简单:

1
2
declare -x name=justin
export name=justin

以bash为例, bash内置了很多环境变量. 例如HOSTNAME USER SHELL LANG PATH 等等.

除了环境变量, 在进行shell编程中还有一种重要的变量, 就是位置变量

常用到的位置变量有:

  • ${0,1,2,3,….} 其中0表示命令本身, 后面的数字就表示参数
  • $# 传递给脚本的参数个数
  • $* 传递给脚本的所有参数, 视为一个字符串
  • $@ 传递给脚本的所有参数, 单个形式

数组与流程控制

首先来看看数组, 在Shell中声明一个数组使用:

1
2
declare -a ARRAY_NAME -- 数组
declare -A ARRAY_NAME -- 关联数组 字典

在赋值的时候也有多种方法, 可以单个赋值也可以统一赋值:

1
2
3
[root@WWW mage_shell_edu]# weekday=('Sun' 'Mon' 'Tus' 'Wed')
[root@WWW mage_shell_edu]# weekday[0]='Sun'
[root@WWW mage_shell_edu]# weekday[1]='Mon'

在集合赋值的时候使用空格分开每一个值.

在引用时, 我们是用${ARRAY}[INDEX] 当省略[INDEX]的时候, 不是整个数组, 而是第一个元素, 如果想要获得全部数组的话, 使用@或者*就可以了, 例如:

1
2
3
4
[root@WWW mage_shell_edu]# echo ${weekday[*]}
Sun Mon Tus Wed
[root@WWW mage_shell_edu]# echo ${weekday[@]}
Sun Mon Tus Wed

while的特殊用法

读取文件的每一行:

1
2
3
4
5
6
7
8
#!/bin/bash
#
while read line; do
if [ $[`echo $line | cut -d: -f3` % 2] -eq 0 ]; then
echo -e -n "username: `echo $line | cut -d: -f1`\t"
echo "uid: `echo $line | cut -d: -f3`"
fi
done < /etc/passwd

这段脚本就是读取passwd文件的每一行. 接着输出uid为偶数的行. 每一行都会被存在line这个变量中.

if循环和elif和else

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
#!/bin/bash
cat << EOF
What information would you like to see:
(cpu) show cpu information;
(disk) show disk information;
(mem) show memory information;
(quit) quit
===================================
EOF

read -p "Enter a option: " option

while [ $option != 'cpu' -a $option != 'disk' -a $option != 'mem' -a $option != 'quit' ]; do
read -p "Wrong option. Enter again: " option
done

while [ -n $option ]; do
if [ $option == "cpu" ]; then
lscpu
elif [ $option == "mem" ]; then
cat /proc/meminfo
elif [ $option == 'disk' ]; then
fdisk -l
elif [ $option == 'quit' ]; then
echo "Quit"
exit 0
fi
read -p "See anything? [cpu,disk,mem,quit] " option
done

这里面还有一个有趣的地方就是cat的hereDOC输出.

case和函数

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
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
#!/bin/bash
#

LOCK=/var/lock/subsys/service

usage() {
echo "USAGE: service {start|stop|restart|status}"
}

start() {
if [ -e $LOCK ]; then
echo "service has already run...exit"
exit 1
fi
echo "Attempting to start the service..."
touch $LOCK
if [ $? -eq 0 ]; then
echo "Service started."
exit 0
else
echo "Something goes wrong.."
rm -f $LOCK
exit 2
fi
}

stop() {
if [ -e $LOCK ]; then
echo "Stopping service..."
rm -f $LOCK
if [ $? -eq 0 ]; then
echo "Service stopped."
exit 0;
fi
else
echo "Service is not running..."
fi
}

if [ -n $1 ]; then
if [ $1 == 'start' -o $1 == 'stop' -o $1 == 'restart' -o $1 == 'status' ]; then
case "$1" in
start)
start
;;
stop)
stop
;;
status)
if [ -e $LOCK ]; then
echo "Service is running."
else
echo "Service is not running"
fi
exit 0
;;
restart)
stop
start
;;
*)
usage
exit 0
;;
esac
else
echo -n -e "Invalid option: $1\n"
usage
fi
fi

生成一个列表

Shell生成一个列表的方式有很多, 由于默认都是字符串, 所以直接给出列表就是可以的, 接下来再说几个:

  • 生成整数列表的两种方式
    • {start..end}
    • $(seq [start [step]] end)
  • 返回列表的命令(e.g: ls)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
[root@WWW ~]# for n in {1..9}; do echo $n; done
1
2
3
4
5
6
7
8
9
[root@WWW ~]# seq 1 2 10
1
3
5
7
9

接下是一个ls返回列表的示例:

1
2
3
4
5
6
7
8
9
10
#!/bin/bash
for file in $(ls /var/); do
if [ -f /var/$file ]; then
echo "Regular File"
elif [ -L /var/$file ]; then
echo "Symbolic File"
elif [ -r /var/$file ]; then
echo "Directory"
fi
done

接下来看看, Shell对字符串的操作:

首先先来看下字符串的切片:

1
2
3
4
5
[root@WWW mage_shell_edu]# name="Justin"
[root@WWW mage_shell_edu]# echo ${name:1:3}
ust
[root@WWW mage_shell_edu]# echo ${name:3}
tin

很好理解. 倒序也是支持的, 但是..诡异的是:

1
2
3
4
[root@WWW mage_shell_edu]# echo ${name: -3}
tin
[root@WWW mage_shell_edu]# echo ${name: -3: -1}
ti

一定要加空格.

除了切片, Shell还支持模式取字串和查找替换. 不过说实话, 我觉得这些功能…似乎大家都选择使用sed和awk来实现了…就简单的记录一下吧:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
[root@WWW mage_shell_edu]# name=$(head -1 /etc/passwd)
[root@WWW mage_shell_edu]# echo $name
root:x:0:0:root:/root:/bin/bash
[root@WWW mage_shell_edu]# echo ${name/root/ROOT}
ROOT:x:0:0:root:/root:/bin/bash
[root@WWW mage_shell_edu]# echo ${name//root/ROOT}
ROOT:x:0:0:ROOT:/ROOT:/bin/bash
[root@WWW mage_shell_edu]# echo ${name/#root/ROOT}
ROOT:x:0:0:root:/root:/bin/bash
[root@WWW mage_shell_edu]# name="admin:$name:root"
[root@WWW mage_shell_edu]# echo $name
admin:root:x:0:0:root:/root:/bin/bash:root
[root@WWW mage_shell_edu]# echo ${name/#root/ROOT}
admin:root:x:0:0:root:/root:/bin/bash:root
[root@WWW mage_shell_edu]# echo ${name/%root/ROOT}
admin:root:x:0:0:root:/root:/bin/bash:ROOT

上面是关于查找替换的示例, 其中//表示全文检索替换, 而% 是行尾锚定, # 行首锚定.

大小写转换:

1
2
3
4
5
6
7
8
9
[root@WWW mage_shell_edu]# echo $name
admin:root:x:0:0:root:/root:/bin/bash:root
[root@WWW mage_shell_edu]# echo ${name^^}
ADMIN:ROOT:X:0:0:ROOT:/ROOT:/BIN/BASH:ROOT
[root@WWW mage_shell_edu]# newname=${name^^}
[root@WWW mage_shell_edu]# echo $newname
ADMIN:ROOT:X:0:0:ROOT:/ROOT:/BIN/BASH:ROOT
[root@WWW mage_shell_edu]# echo ${newname,,}
admin:root:x:0:0:root:/root:/bin/bash:root

最后附赠一个有意思的小命令:

1
2
3
4
5
6
7
8
9
10
11
12
[root@WWW mage_shell_edu]# mktemp /tmp/test.XXX
/tmp/test.Vmd
[root@WWW mage_shell_edu]# mktemp /tmp/test.XXX
/tmp/test.LQW
[root@WWW mage_shell_edu]# mktemp /tmp/test.XXXXXX
/tmp/test.SuR3Bk
[root@WWW mage_shell_edu]# mktemp -d /tmp/test.XXXXXX
/tmp/test.N9FxlV
[root@WWW mage_shell_edu]# ls /tmp/test*
/tmp/test /tmp/test.LQW /tmp/test.SuR3Bk /tmp/test.Vmd

/tmp/test.N9FxlV:

这个命令是有返回值的, 也就是说你可以赋给变量从而使用它. 最少要使用三个X才可以. 另外, 使用-d参数可以创建目录.

管道和字符操作

(略)