Linux Shell Scripts
Linux Shell Scripts
概述
类似于 Windows 下的 Bat 程序,在 Linux 下可以编写和使用 Shell Scripts 来管理系统。
简单来说,Shell Scripts 就是包含 Linux 命令以及流程控制语句的可执行文件。Shell 读取这个文件,依次执行里面的命令。
开始
创建第一个 Shell Script:
[root@stone ~]# vi hello.sh
#!/bin/bash
# author: stone
echo "Hello Linux"
其中:
hello.sh
:脚本文件名,一般使用.sh
后缀名#!
:称为Shebang
,指定执行脚本的解释器,Bash 脚本的解释器一般是/bin/sh
或/bin/bash
#
:表示注释
为脚本加上可执行权限:
[root@stone ~]# chmod a+x hello.sh
[root@stone ~]# ll hello.sh
-rwxr-xr-x. 1 root root 31 Sep 4 14:22 hello.sh
使用相对路径执行脚本:
[root@stone ~]# ./hello.sh
Hello Linux
使用绝对路径执行脚本:
[root@stone ~]# /root/hello.sh
Hello Linux
手动将脚本传给解释器来执行:
[root@stone ~]# sh hello.sh
Hello Linux
也可以将脚本放到 PATH
环境变量指定的路径下,然后直接执行:
[root@stone ~]# env | grep PATH
PATH=/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/root/bin
[root@stone ~]# mkdir /root/bin
[root@stone ~]# mv hello.sh /root/bin
[root@stone ~]# hello.sh
Hello Linux
返回值
使用 $?
获取上一条命令或者脚本执行后的返回值:
[root@stone ~]# hello.sh
Hello Linux
[root@stone ~]# echo $?
0
如果返回 0,表示执行成功;返回非 0,表示执行失败。
可以使用 exit
命令终止脚本执行,并指定一个返回值:
[root@stone ~]# vi bin/hello.sh
#!/bin/bash
# author: stone
echo "Hello Linux"
exit 1
[root@stone ~]# hello.sh
Hello Linux
[root@stone ~]# echo $?
1
参数
可以为脚本指定参数,例如:
[root@stone ~]# hello.sh a b c
指定参数后,脚本内部使用以下特殊变量来引用参数:
$0
:脚本文件名,这里为hello.sh
$1
:第一个参数,这里为a
$2
:第二个参数,这里为b
$3
:第三个参数,这里为c
$#
:参数个数,这里为 3$@
:全部参数,参数之间使用空格分隔
[root@stone ~]# vi bin/hello.sh
#!/bin/bash
# author: stone
echo "Hello Linux"
echo '$0 = ' $0
echo '$1 = ' $1
echo '$2 = ' $2
echo '$3 = ' $3
echo '$# = ' $#
echo '$@ = ' $@
[root@stone ~]# hello.sh a b c
Hello Linux
$0 = /root/bin/hello.sh
$1 = a
$2 = b
$3 = c
$# = 3
$@ = a b c
可以使用 shift
命令移除参数,例如:
[root@stone ~]# vi bin/hello.sh
#!/bin/bash
# author: stone
echo "Hello Linux"
echo '$1 = ' $1
echo '$@ = ' $@
echo -e '\n'
echo "shift 1 arg"
shift
echo '$1 = ' $1
echo '$@ = ' $@
echo -e '\n'
echo "shift 2 args"
shift 2
echo '$1 = ' $1
echo '$@ = ' $@
[root@stone ~]# hello.sh a b c d
Hello Linux
$1 = a
$@ = a b c d
shift 1 arg
$1 = b
$@ = b c d
shift 2 arg
$1 = d
$@ = d
输入
在脚本中使用 read
命令等待用户输入。
语法:
read [options] [name ...]
常用选项有:
-p prompt
:指定提示信息-t timeout
:指定等待用户输入的超时秒数-s
:用户的输入不显示在屏幕上,常用于输入密码
[root@stone ~]# vi bin/hello.sh
#!/bin/bash
# author: stone
read -t 30 -p "Enter Name: " NAME
echo "Hello $NAME"
read -s -t 30 -p "Enter Password: " PASSWORD
echo -e "\nPassword: $PASSWORD"
[root@stone ~]# hello.sh
Enter Name: stone
Hello stone
Enter Password:
Password: 123456
条件
test
使用 test
命令判断条件。
语法:
test EXPRESSION
如果 EXPRESSION
表达式为真,则返回 0,否则返回 1。EXPRESSION
可以是以下形式:
- 字符串判断:
-n STRING
:如果STRING
的长度大于零,则为真-z STRING
:如果STRING
的长度等于零,则为真STRING1 = STRING2
:如果STRING1
和STRING2
相同,则为真STRING1 != STRING2
:如果STRING1
和STRING2
不同,则为真
- 整数判断:
INTEGER1 -eq INTEGER2
:如果INTEGER1
等于INTEGER2
,则为真INTEGER1 -gt INTEGER2
:如果INTEGER1
大于INTEGER2
,则为真INTEGER1 -lt INTEGER2
:如果INTEGER1
小于INTEGER2
,则为真INTEGER1 -ge INTEGER2
:如果INTEGER1
大于等于INTEGER2
,则为真INTEGER1 -le INTEGER2
:如果INTEGER1
小于等于INTEGER2
,则为真INTEGER1 -ne INTEGER2
:如果INTEGER1
不等于INTEGER2
,则为真
- 文件判断:
-e FILE
:如果FILE
存在,则为真-f FILE
:如果FILE
存在且为文件,则为真-d FILE
:如果FILE
存在且为目录,则为真-L FILE
:如果FILE
存在且为符号链接,则为真-b FILE
:如果FILE
存在且为块设备文件,则为真-c FILE
:如果FILE
存在且为字符设备文件,则为真-S FILE
:如果FILE
存在且为套接字文件,则为真-r FILE
:如果FILE
存在且有读权限,则为真-w FILE
:如果FILE
存在且有写权限,则为真-x FILE
:如果FILE
存在且有执行权限,则为真-s FILE
:如果FILE
存在且大小大于 0,则为真FILE1 -nt FILE2
:如果FILE1
比FILE2
新,则为真FILE1 -ot FILE2
:如果FILE1
比FILE2
旧,则为真
- 逻辑判断:
EXPRESSION1 -a EXPRESSION2
:与运算EXPRESSION1 -o EXPRESSION2
:或运算! EXPRESSION
:非运算
[root@stone ~]# test -e .bash_profile
[root@stone ~]# echo $?
0
[]
使用 []
替代 test
来判断表达式,需要注意:
- 中括号两端及内部各项都需要使用空格分隔
- 中括号内的变量和常量建议使用双引号包裹
[root@stone ~]# [ -e /etc/hosts ]
[root@stone ~]# echo $?
0
(())
使用 (())
进行算术运行。
[root@stone ~]# ((3 > 2))
[root@stone ~]# echo $?
0
if
使用 if
语句根据条件进行判断是否执行某些命令。
语法如下:
if commands; then
commands
[elif commands; then
commands...]
[else
commands]
fi
[root@stone ~]# vi bin/getversion.sh
#!/bin/bash
# author: stone
VERSION=`cut -d ' ' -f 4 /etc/redhat-release | cut -d '.' -f 1`
if [ "$VERSION" == 7 ]; then
echo "CentOS 7"
elif [ "$VERSION" == 6 ]; then
echo "CentOS 6"
else
echo "ERROR"
fi
[root@stone ~]# chmod a+x bin/getversion.sh
[root@stone ~]# getversion.sh
CentOS 7
case
使用 case
语句根据表达式的值执行某些命令。
语法:
case expression in
pattern )
commands ;;
pattern )
commands ;;
...
esac
其中 pattern
可以是:
a)
:匹配a
a|b)
:匹配a
或b
[[:alpha:]])
:匹配单个字母???)
:匹配 3 个字符的单词*.txt)
:匹配.txt
结尾*)
:匹配任意输入,通过作为case
结构的最后一个模式
[root@stone ~]# vi bin/getversion1.sh
#!/bin/bash
# author: stone
VERSION=`cut -d ' ' -f 4 /etc/redhat-release | cut -d '.' -f 1`
case "$VERSION" in
7 )
echo "CentOS 7" ;;
6 )
echo "CentOS 6" ;;
* )
echo "ERROR" ;;
esac
[root@stone ~]# chmod a+x bin/getversion1.sh
[root@stone ~]# getversion1.sh
CentOS 7
循环
常用的循环语句有 for
和 while
。
for
使用 for
语句进行循环。
有 2 种形式 :
for name [ [ in [ word ... ] ] ; ] do list ; done
for (( expr1 ; expr2 ; expr3 )) ; do list ; done
对于第一种 for...in
循环,还可以写为:
for variable in list
do
commands
done
其中:
list
可以是多个值,或者使用通配符匹配多个文件,或者使用命令产生。 如果省略in list
部分,则默认为脚本的所有参数,即$@
。
判断指定网段的 IP 地址是否被使用:
[root@stone ~]# vi bin/testip.sh
#!/bin/bash
# author: stone
if [ "$1" == '' ]; then
echo "Usage: testip xxx.xxx.xxx"
exit 0
fi
for i in $(seq 1 254)
do
ping -c 1 -w 1 $1.$i > /dev/null
if [ $? == 0 ]; then
echo "IP $1.$i is Used"
else
echo "IP $1.$i is not Used"
fi
done
[root@stone ~]# chmod a+x bin/testip.sh
[root@stone ~]# testip.sh 192.168.92
IP 192.168.92.1 is Used
IP 192.168.92.2 is Used
IP 192.168.92.3 is not Used
对于第二种 for (( expr1 ; expr2 ; expr3 ))
循环,还可以写为:
for (( expression1; expression2; expression3 ))
do
commands
done
其中:
expr1
:初始化循环条件expr2
:循环结束的条件expr3
:在每次循环迭代的末尾执行,用于更新值
在圆括号之中使用变量时,不必加上美元符号 $
。
[root@stone ~]# cp bin/testip.sh bin/testip1.sh
[root@stone ~]# vi bin/testip1.sh
#!/bin/bash
# author: stone
if [ "$1" == '' ]; then
echo "Usage: testip xxx.xxx.xxx"
exit 0
fi
for (( i=1; i<5; i=i+1 ))
do
ping -c 1 -w 1 $1.$i > /dev/null
if [ $? == 0 ]; then
echo "IP $1.$i is Used"
else
echo "IP $1.$i is not Used"
fi
done
[root@stone ~]# testip1.sh 192.168.92
IP 192.168.92.1 is Used
IP 192.168.92.2 is Used
IP 192.168.92.3 is not Used
IP 192.168.92.4 is not Used
while
使用 while
语句进行循环。
语法:
while condition; do commands; done
或者:
while condition
do
commands
done
[root@stone ~]# cp bin/testip.sh bin/testip2.sh
[root@stone ~]# vi bin/testip2.sh
#!/bin/bash
# author: stone
if [ "$1" == '' ]; then
echo "Usage: testip xxx.xxx.xxx"
exit 0
fi
i=1
while [ $i -lt 5 ]
do
ping -c 1 -w 1 $1.$i > /dev/null
if [ $? == 0 ]; then
echo "IP $1.$i is Used"
else
echo "IP $1.$i is not Used"
fi
i=$((i + 1))
done
[root@stone ~]# testip2.sh 192.168.92
IP 192.168.92.1 is Used
IP 192.168.92.2 is Used
IP 192.168.92.3 is not Used
IP 192.168.92.4 is not Used
break
使用 break
终止循环,不再执行剩下的循环。
[root@stone ~]# vi bin/testip.sh
#!/bin/bash
# author: stone
if [ "$1" == '' ]; then
echo "Usage: testip xxx.xxx.xxx"
exit 0
fi
for i in $(seq 1 255)
do
ping -c 1 -w 1 $1.$i > /dev/null
if [ $? == 0 ]; then
echo "IP $1.$i is Used"
else
echo "IP $1.$i is not Used"
fi
if [ $i == 5 ]; then
break
fi
done
[root@stone ~]# testip.sh 192.168.92
IP 192.168.92.1 is Used
IP 192.168.92.2 is Used
IP 192.168.92.3 is not Used
IP 192.168.92.4 is not Used
IP 192.168.92.5 is not Used
continue
使用 continue
终止本轮循环,开始执行下一轮循环。
[root@stone ~]# vi bin/testip.sh
#!/bin/bash
# author: stone
if [ "$1" == '' ]; then
echo "Usage: testip xxx.xxx.xxx"
exit 0
fi
for i in $(seq 1 255)
do
if [ $i == 2 ]; then
continue
fi
ping -c 1 -w 1 $1.$i > /dev/null
if [ $? == 0 ]; then
echo "IP $1.$i is Used"
else
echo "IP $1.$i is not Used"
fi
if [ $i == 5 ]; then
break
fi
done
[root@stone ~]# testip.sh 192.168.92
IP 192.168.92.1 is Used
IP 192.168.92.3 is not Used
IP 192.168.92.4 is not Used
IP 192.168.92.5 is not Used
select
使用 select
生成简单的菜单,语法与 for...in
循环基本一致。
语法:
select name [ in list ] ; do commands ; done
或者:
select name [in list]
do
commands
done
处理逻辑如下:
- 生成一个菜单,内容是列表
list
的每一项,并且每一项前面还有一个数字编号。 - 提示用户选择一项,输入它的编号。
- 用户输入以后,会将该项的内容存在变量
name
,该项的编号存入环境变量REPLY
。如果用户没有输入,就按回车键,则会重新输出菜单,让用户选择。 - 执行命令体
commands
。 - 执行结束后,回到第一步,重复这个过程。
[root@stone ~]# vi bin/selectversion.sh
#!/bin/bash
# author: stone
select VERSION in CentOS5 CentOS6 CentOS7
do
echo "the OS Version is $VERSION"
done
[root@stone ~]# selectversion.sh
1) CentOS5
2) CentOS6
3) CentOS7
#? 1
the OS Version is CentOS5
#?
函数
可以将脚本中的代码片段定义为函数,重复使用。
语法:
[ function ] fun_name [()]
{
action;
[return int;]
}
其中:
函数体内可以使用参数变量以获取函数参数,函数参数变量与脚本参数变量一致,包括:
$1
~$N
:函数的第 1 个到第 N 个参数$0
:函数所在的脚本名$#
:函数的参数个数$@
:函数的全部参数,参数之间使用空格分隔
使用
return
命令从函数返回一个值。函数体内直接声明的变量为全局变量,也可在函数体内修改全局变量。
使用
local
命令在函数体内声明局部变量,只在函数体内有效。
[root@stone ~]# vi bin/testfun.sh
#!/bin/bash
# author: stone
hello() {
echo "Hello $1"
}
hello $1
[root@stone ~]# chmod a+x bin/testfun.sh
[root@stone ~]# /root/bin/testfun.sh World
Hello World
命令
mktemp
使用 mktemp
命令创建一个随机文件名的临时文件。
常用选项有:
-d
:创建目录-p
:指定临时文件所在的目录,默认为$TMPDIR
,否则为/tmp
[root@stone ~]# mktemp
/tmp/tmp.lYSxy66tNv
[root@stone ~]# ll /tmp/tmp.lYSxy66tNv
-rw-------. 1 root root 0 Sep 6 16:37 /tmp/tmp.lYSxy66tNv
trap
使用 trap
命令在脚本中响应系统信号。
语法:
trap command signal
可用信号使用 -l
选项查看:
[root@stone ~]# trap -l
1) SIGHUP 2) SIGINT 3) SIGQUIT 4) SIGILL 5) SIGTRAP
6) SIGABRT 7) SIGBUS 8) SIGFPE 9) SIGKILL 10) SIGUSR1
11) SIGSEGV 12) SIGUSR2 13) SIGPIPE 14) SIGALRM 15) SIGTERM
16) SIGSTKFLT 17) SIGCHLD 18) SIGCONT 19) SIGSTOP 20) SIGTSTP
21) SIGTTIN 22) SIGTTOU 23) SIGURG 24) SIGXCPU 25) SIGXFSZ
26) SIGVTALRM 27) SIGPROF 28) SIGWINCH 29) SIGIO 30) SIGPWR
31) SIGSYS 34) SIGRTMIN 35) SIGRTMIN+1 36) SIGRTMIN+2 37) SIGRTMIN+3
38) SIGRTMIN+4 39) SIGRTMIN+5 40) SIGRTMIN+6 41) SIGRTMIN+7 42) SIGRTMIN+8
43) SIGRTMIN+9 44) SIGRTMIN+10 45) SIGRTMIN+11 46) SIGRTMIN+12 47) SIGRTMIN+13
48) SIGRTMIN+14 49) SIGRTMIN+15 50) SIGRTMAX-14 51) SIGRTMAX-13 52) SIGRTMAX-12
53) SIGRTMAX-11 54) SIGRTMAX-10 55) SIGRTMAX-9 56) SIGRTMAX-8 57) SIGRTMAX-7
58) SIGRTMAX-6 59) SIGRTMAX-5 60) SIGRTMAX-4 61) SIGRTMAX-3 62) SIGRTMAX-2
63) SIGRTMAX-1 64) SIGRTMAX
常用信号有:
1) SIGHUP
:通知进程重新加载配置文件2) SIGINT
:按下Ctrl + C
,让脚本终止运行9) SIGKILL
:强制终止进程15) SIGTERM
:结束进程,默认值。会等待正在进行的工作完成后才结束,如果卡死了,则无法结束EXIT
:编号 0, 不是系统信号,而是 Bash 脚本特有的信号,不管什么情况,只要退出脚本就会产生
在脚本中使用 trap
命令,指定退出时执行的命令:
[root@stone ~]# vi bin/testtrap.sh
#!/bin/bash
# author: stone
trap 'rm -f "$TMPFILE"' EXIT
TMPFILE=$(mktemp) || exit 1
ls /etc > $TMPFILE
if grep -qi "kernel" $TMPFILE; then
echo 'find'
fi
[root@stone ~]# chmod a+x bin/testtrap.sh
[root@stone ~]# /root/bin/testtrap.sh
find
注意:
trap
命令必须放在脚本的开头。否则,它上方的任何命令导致脚本退出,都不会被它捕获。
set
使用 set
命令修改子 Shell 环境的运行参数,提高脚本的安全性和可维护性。
常用选项有:
-e
:等价于-o errexit
,遇到错误,停止执行脚本,使用+e
关闭该选项-E
:解决使用-e
选项导致trap
失效问题-u
:等价于-o nounset
,遇到未定义的变量,停止执行脚本并报错,使用+u
关闭该选项-x
:等价于-o xtrace
,输出脚本中执行的命令,使用+x
关闭该选项-o pipefail
:只要管道中某个命令失败,则整个管道命令就失败,脚本终止执行-n
:等价于-o noexec
,不运行命令,只检查语法是否正确
建议在脚本头部加入以下选项:
set -Eeuxo pipefail
也可以使用 bash
命令从命令行传入这些选项:
[root@stone ~]# bash -Eeuxo pipefail bin/getversion.sh
+ bash -Eeuxo pipefail bin/getversion.sh
++ cut -d ' ' -f 4 /etc/redhat-release
++ cut -d . -f 1
+ VERSION=7
+ '[' 7 == 7 ']'
+ echo 'CentOS 7'
CentOS 7