参考:《Shell从入门到精通》
参考:Shell 编程快速入门
参考:Shell 教程
一、Shell
Shell脚本(英语:Shell script)是一种电脑程序与文本文件,内容由一连串的shell命令组成,由Unix shell扮演命令行解释器的角色,经由Unix Shell直译其内容后运作。
Shell的用处:
- shell简单、灵活、高效,特别适合处理一些系统管理方面的小问题
- shell可以实现自动化管理,让系统管理员的工作变得容易、简单、高效
- shell脚本可移植性好,在unix/linux系统中可灵活移植,几乎不用任何设置就能正常运行
- shell脚本可轻松方便读取和修改源代码,不需要编译
- 资源密集型的任务,尤其在需要考虑效率时(比如排序,hash 等)
- 需要处理大任务的数学操作,尤其是浮点运算,精确运算,或者复杂的算术运算(这种情况一般使用C++或FORTRAN 来处理)
- 有跨平台移植需求(一般使用C 或Java)
- 复杂的应用,在必须使用结构化编程的时候(需要变量的类型检查,函数原型,等等)
- 对于影响系统全局性的关键任务应用。
- 对于安全有很高要求的任务,比如你需要一个健壮的系统来防止入侵,破解,恶意破坏等等.
- 需要直接操作系统硬件、需要产生或操作图形化界面 GUI
- 私人的,闭源的应用(shell 脚本把代码就放在文本文件中,全世界都能看到)
二、Shell环境
Linux
Linux默认安装就带了shell解释器。
Mac OS
Mac OS不仅带了sh、bash这两个最基础的解释器,还内置了ksh、csh、zsh等不常用的解释器。
Windows上的模拟器
windows出厂时没有内置shell解释器,需要自行安装,为了同时能用grep, awk, curl等工具,最好装一个cygwin或者mingw来模拟linux环境。
即Bourne shell,POSIX(Portable Operating System Interface)标准的shell解释器,它的二进制文件路径通常是/bin/sh,由Bell Labs开发。
bash
Bash是Bourne shell的替代品,属GNU Project,二进制文件路径通常是/bin/bash。在CentOS里,/bin/sh是一个指向/bin/bash的符号链接,但在Mac OS上不是,/bin/sh和/bin/bash是两个不同的文件,尽管它们的大小只相差100字节左右,业界通常混用bash、sh、和shell。
三、Shell的解释和执行
- #!/bin/sh
- #!/bin/bash
- #!/bin/csh
- #!/bin/tcsh
- #!/bin/ksh
//为shell脚本直接加上可执行权限并执行
chmod 755 run.sh
./run.sh
//通过sh命令执行shell脚本
sh run.sh
四、Shell中的特殊字符
1. 美元符号
美元符号表示变量替换,即用其后面指定的变量的值来代替变量。反斜线“\”为转义字符,转义字符告诉shell不要对其后面的那个字符进行特殊处理,只是当做普通字符。而shell下的引号情况比较复杂,分为三种:双引号("),单引号(')和倒引号(`)。他们的作用都不尽相同,以下一一说明。
2. 双引号(")
由双引号括起来的字符,除$,倒引号(`)和反斜线(\)仍保留其特殊功能外,其余字符均作为普通字符对待。
3. 单引号(')
由单引号括起来的字符都作为普通字符出现。
4. 倒引号(`)
由倒引号括起来的字符串被shell解释为命令行,在执行时,shell会先执行该命令,并以它的标准输出结果取代整个引号部分。
示例1的代码及输出如下:
#echo "My current directory is `pwd` and logname is $LOGNAME"
My current directory is /root and logname is root
示例2的代码及输出如下:
#echo "My current directory is `pwd` and logname is \$LOGNAME"
My current directory is /root and logname is $LOGNAME
示例3的代码及输出如下:
#echo 'My current directory is `pwd` and logname is $LOGNAME'
【单引号里面的内容不变】
My current directory is `pwd`and logname is $LOGNAME
五、Shell中的变量
variable=value(注:等号左右不能有空格)
2. 可以在同一行中对多个变量赋值
a=5 b="a string"
3. 使用变量
$variable 或 ${variable}
4. 只读变量
myUrl="http://www.w3cschool.cc"
readonly myUrl
myUrl="http://www.baidu.com"
运行脚本,结果如下:
/bin/sh: NAME: This variable is read only.
5. 删除变量
unset variable_name
变量被删除后不能再次使用。unset 命令不能删除只读变量。
1) 局部变量 局部变量在脚本或命令中定义,仅在当前shell实例中有效,其他shell启动的程序不能访问局部变量。
2) 环境变量 所有的程序,包括shell启动的程序,都能访问环境变量,有些程序需要环境变量来保证其正常运行。必要的时候shell脚本也可以定义环境变量。
3) shell变量 shell变量是由shell程序设置的特殊变量。shell变量中有一部分是环境变量,有一部分是局部变量,这些变量保证了shell的正常运行
7. 变量的通配符
假设有变量 var=http://www.aaa.com/123.htm
1. # 号截取,从左边开始删除第一个匹配到的字符及左边的字符,保留右边字符。
echo ${var#*//}
# 号是运算符,*// 表示 // 号及左边的所有字符,即删除 http://
结果是 :www.aaa.com/123.htm
2. ## 号截取,从左边开始删除最后一个匹配到的字符及左边的字符,保留右边字符。
echo ${var##*/}
##*/ 表示从左边开始删除最后(最右边)一个 / 号及左边的所有字符,即删除 http://www.aaa.com/
结果是 123.htm
3. %号截取,删除从右开始第一个匹配到的字符及右边字符,保留左边字符
echo ${var%/*}
%/* 表示从右边开始,删除第一个 / 号及右边的字符
结果是:http://www.aaa.com
4. %% 号截取,删除从右开始最后一个匹配到的字符及右边字符,保留左边字符
echo ${var%%/*}
%%/* 表示从右边开始,删除最后(最左边)一个 / 号及右边的字符
结果是:http:
5. :从左边第几个字符开始,及字符的个数
echo ${var:0:5}
其中的 0 表示左边第一个字符开始,5 表示字符的总个数。
结果是:http:
6. 从左边第几个字符开始,一直到结束。
echo ${var:7}
其中的 7 表示左边第8个字符开始,一直到结束。
结果是 :www.aaa.com/123.htm
7. 从右边第几个字符开始,及字符的个数
echo ${var:0-7:3}
其中的 0-7 表示右边算起第七个字符开始,3 表示字符的个数。
结果是:123
8. 从右边第几个字符开始,一直到结束。
echo ${var:0-7}
表示从右边第七个字符开始,一直到结束。
结果是:123.htm
注:(左边的第一个字符是用 0 表示,右边的第一个字符用 0-1 表示)
六、Shell中的数组
array_name=(value0 value1 value2 value3)
用括号来表示数组,数组元素用"空格"符号分割开。还可以单独定义数组的各个分量:
array_name[0]=value0
array_name[1]=value1
array_name[n]=valuen
2. 读取数组
valuen=${array_name[n]}
使用@符号可以获取数组中的所有元素
3. 获取数组的长度
# 取得数组元素的个数
length=${#array_name[@]}
# 或者
length=${#array_name[*]}
# 取得数组单个元素的长度
lengthn=${#array_name[n]}
七、Shell中的函数和运算符
7.1 函数语法形式
function name {
commands
return
}
或
name () {
commands
return
}
参数处理 | 说明 |
---|---|
$# | 传递到脚本或函数的参数个数 |
$* | 以一个单字符串显示所有向脚本传递的参数。 如"$*"用「"」括起来的情况、以"$1 $2 … $n"的形式输出所有参数。 |
$$ | 脚本运行的当前进程ID号 |
$! | 后台运行的最后一个进程的ID号 |
$@ | 与$*相同,但是使用时加引号,并在引号中返回每个参数。 如"$@"用「"」括起来的情况、以"$1" "$2" … "$n" 的形式输出所有参数。 |
$- | 显示Shell使用的当前选项,与set命令功能相同。 |
$? | 显示最后命令的退出状态。0表示没有错误,其他任何值表明有错误。 |
7.2 表达式计算工具
#!/bin/bash
val=`expr 2 + 2`
echo "两数之和为 : $val"
表达式和运算符之间要
有空格,例如 2+2 是不对的,必须写成 2 + 2,这与我们熟悉的大多数编程语言不一样。完整的表达式要被 ` ` 包含,注意这个字符不是常用的单引号,在 Esc 键下边。
7.3 算数运算符
运算符 | 说明 | 举例 |
---|---|---|
+ | 加法 | `expr $a + $b` 结果为 30。 |
- | 减法 | `expr $a - $b` 结果为 -10。 |
* | 乘法 | `expr $a \* $b` 结果为 200。 |
/ | 除法 | `expr $b / $a` 结果为 2。 |
% | 取余 | `expr $b % $a` 结果为 0。 |
= | 赋值 | a=$b 将把变量 b 的值赋给 a。 |
== | 相等。用于比较两个数字,相同则返回 true。 | [ $a == $b ] 返回 false。 |
!= | 不相等。用于比较两个数字,不相同则返回 true。 | [ $a != $b ] 返回 true。 |
7.4 关系运算符
运算符 | 说明 | 举例 |
---|---|---|
-eq | 检测两个数是否相等,相等返回 true。 | [ $a -eq $b ] 返回 false。 |
-ne | 检测两个数是否相等,不相等返回 true。 | [ $a -ne $b ] 返回 true。 |
-gt | 检测左边的数是否大于右边的,如果是,则返回 true。 | [ $a -gt $b ] 返回 false。 |
-lt | 检测左边的数是否小于右边的,如果是,则返回 true。 | [ $a -lt $b ] 返回 true。 |
-ge | 检测左边的数是否大于等于右边的,如果是,则返回 true。 | [ $a -ge $b ] 返回 false。 |
-le | 检测左边的数是否小于等于右边的,如果是,则返回 true。 | [ $a -le $b ] 返回 true。 |
7.5 布尔运算符
运算符 | 说明 | 举例 |
---|---|---|
! | 非运算,表达式为 true 则返回 false,否则返回 true。 | [ ! false ] 返回 true。 |
-o | 或运算,有一个表达式为 true 则返回 true。 | [ $a -lt 20 -o $b -gt 100 ] 返回 true。 |
-a | 与运算,两个表达式都为 true 才返回 true。 | [ $a -lt 20 -a $b -gt 100 ] 返回 false。 |
7.6 逻辑运算符
运算符 | 说明 | 举例 |
---|---|---|
&& | 逻辑的 AND | [[ $a -lt 100 && $b -gt 100 ]] 返回 false |
|| | 逻辑的 OR | [[ $a -lt 100 || $b -gt 100 ]] 返回 true |
7.7 字符串运算符
运算符 | 说明 | 举例 |
---|---|---|
= | 检测两个字符串是否相等,相等返回 true。 | [ $a = $b ] 返回 false。 |
!= | 检测两个字符串是否相等,不相等返回 true。 | [ $a != $b ] 返回 true。 |
-z | 检测字符串长度是否为0,为0返回 true。 | [ -z $a ] 返回 false。 |
-n | 检测字符串长度是否为0,不为0返回 true。 | [ -n $a ] 返回 true。 |
str | 检测字符串是否为空,不为空返回 true。 | [ $a ] 返回 true。 |
7.8 文件测试运算符
操作符 | 说明 | 举例 |
---|---|---|
-b file | 检测文件是否是块设备文件,如果是,则返回 true。 | [ -b $file ] 返回 false。 |
-c file | 检测文件是否是字符设备文件,如果是,则返回 true。 | [ -c $file ] 返回 false。 |
-d file | 检测文件是否是目录,如果是,则返回 true。 | [ -d $file ] 返回 false。 |
-f file | 检测文件是否是普通文件(既不是目录,也不是设备文件),如果是,则返回 true。 | [ -f $file ] 返回 true。 |
-g file | 检测文件是否设置了 SGID 位,如果是,则返回 true。 | [ -g $file ] 返回 false。 |
-k file | 检测文件是否设置了粘着位(Sticky Bit),如果是,则返回 true。 | [ -k $file ] 返回 false。 |
-p file | 检测文件是否是有名管道,如果是,则返回 true。 | [ -p $file ] 返回 false。 |
-u file | 检测文件是否设置了 SUID 位,如果是,则返回 true。 | [ -u $file ] 返回 false。 |
-r file | 检测文件是否可读,如果是,则返回 true。 | [ -r $file ] 返回 true。 |
-w file | 检测文件是否可写,如果是,则返回 true。 | [ -w $file ] 返回 true。 |
-x file | 检测文件是否可执行,如果是,则返回 true。 | [ -x $file ] 返回 true。 |
-s file | 检测文件是否为空(文件大小是否大于0),不为空返回 true。 | [ -s $file ] 返回 true。 |
-e file | 检测文件(包括目录)是否存在,如果是,则返回 true。 | [ -e $file ] 返回 true。 |
八、Shell中的逻辑控制语句
1. if 语句
if commands; then
commands
elif commands; then
commands
else
commands
fi
for var in item1 item2 ... itemN
do
command1
command2
...
commandN
done
当变量值在列表里,for循环即
执行一次所有命令,使用变量名获取列表中的当前取值。命令可为任何有效的shell命令和语句。in列表可以包含替换、字符串和文件名。in列表是可选的,如果不用它,for循环使用命令行的位置参数。
while condition
do
command
done
until condition
do
command
done
条件可为任意测试条件,测试发生在
循环末尾,因此循环
至少执行一次—请注意这一点。
case 值 in
模式1)
command1
command2
...
commandN
;;
模式2)
command1
command2
...
commandN
;;
esac
case工作方式如上所示。取值后面必须为单词
in,每一模式必须以
右括号结束。取值可以为变量或常数。匹配发现取值符合某一模式后,其间所有命令开始执行直至
;;。
九、Shell中的测试语句
或
[ expression ]
正则表达式和[[ expression ]]
#!/bin/bash
# test-integer2: evaluate the value of an integer.
INT=-5
if [[ "$INT" =~ ^-?[0-9]+$ ]]; then
if [ $INT -eq 0 ]; then
echo "INT is zero."
else
if [ $INT -lt 0 ]; then
echo "INT is negative."
else
echo "INT is positive."
fi
if [ $((INT % 2)) -eq 0 ]; then
echo "INT is even."
else
echo "INT is odd."
fi
fi
else
echo "INT is not an integer." >&2
exit 1
fi
整数复合命令(( ))
#!/bin/bash
# test-integer2a: evaluate the value of an integer.
INT=-5
if [[ "$INT" =~ ^-?[0-9]+$ ]]; then
if ((INT == 0)); then
echo "INT is zero."
else
if ((INT < 0)); then
echo "INT is negative."
else
echo "INT is positive."
fi
if (( ((INT % 2)) == 0)); then
echo "INT is even."
else
echo "INT is odd."
fi
fi
else
echo "INT is not an integer." >&2
exit 1
fi
十、Shell中的输入和交互
echo -n "Please enter an integer -> "
read var
echo "var = '$var'"
给多个变量赋值:
echo -n "Enter one or more values > "
read var1 var2
echo "var1 = '$var1'"
echo "var2 = '$var2'"
若没有提供变量名,shell 变量 REPLY 会包含数据行
echo -n "Enter one or more values > "
read
echo "REPLY = '$REPLY'"
read选项:read
[-options] [variable...]
- -a array:把输入赋值到数组 array 中,从索引号零开始
- -d delimiter:用字符串 delimiter 中的第一个字符指示输入结束,而不是一个换行符
- -e:使用 Readline 来处理输入。这使得与命令行相同的方式编辑输入
- -n num:读取 num 个输入字符,而不是整行
- -p prompt:为输入显示提示信息,使用字符串 prompt
- -r:Raw mode. 不把反斜杠字符解释为转义字符
- -s:Silent mode. 不会在屏幕上显示输入的字符。当输入密码和其它确认信息的时候,这会很有帮助
- -t seconds:超时. 几秒钟后终止输入。read 会返回一个非零退出状态,若输入超时
- -u fd:使用文件描述符 fd 中的输入,而不是标准输入
#!/bin/bash
if read -t 10 -sp "Enter secret pass phrase > " secret_pass; then
echo -e "\nSecret pass phrase = '$secret_pass'"
else
echo -e "\nInput timed out" >&2
exit 1
fi
输入校正
#!/bin/bash
invalid_input () {
echo "Invalid input '$REPLY'" >&2
exit 1
}
read -p "Enter a single item > "
# input is empty (invalid)
[[ -z $REPLY ]] && invalid_input
# input is multiple items (invalid)
(( $(echo $REPLY | wc -w) > 1 )) && invalid_input
# is input a valid filename?
if [[ $REPLY =~ ^[-[:alnum:]\._]+$ ]]; then
echo "'$REPLY' is a valid filename."
if [[ -e $REPLY ]]; then
echo "And file '$REPLY' exists."
else
echo "However, file '$REPLY' does not exist."
fi
# is input a floating point number?
if [[ $REPLY =~ ^-?[[:digit:]]*\.[[:digit:]]+$ ]]; then
echo "'$REPLY' is a floating point number."
else
echo "'$REPLY' is not a floating point number."
fi
# is input an integer?
if [[ $REPLY =~ ^-?[[:digit:]]+$ ]]; then
echo "'$REPLY' is an integer."
else
echo "'$REPLY' is not an integer."
fi
else
echo "The string '$REPLY' is not a valid filename."
fi
菜单驱动
#!/bin/bash
# read-menu: a menu driven system information program
clear
echo "
Please Select:
1. Display System Information
2. Display Disk Space
3. Display Home Space Utilization
0. Quit
"
read -p "Enter selection [0-3] > "
if [[ $REPLY =~ ^[0-3]$ ]]; then
if [[ $REPLY == 0 ]]; then
echo "Program terminated."
exit
fi
if [[ $REPLY == 1 ]]; then
echo "Hostname: $HOSTNAME"
uptime
exit
fi
if [[ $REPLY == 2 ]]; then
df -h
exit
fi
if [[ $REPLY == 3 ]]; then
if [[ $(id -u) -eq 0 ]]; then
echo "Home Space Utilization (All Users)"
du -sh /home/*
else
echo "Home Space Utilization ($USER)"
du -sh $HOME
fi
exit
fi
else
echo "Invalid entry." >&2
exit 1
fi
十一、文件重定向
1. linux文件描述符
- 标准输入 standard input 0
- 正确输出 standard output 1
- 错误输出 error output 2
2. 输出重定向
command-line1 [1-n] > file或文件操作符或设备
将一条命令执行结果(标准输出,或者错误输出,本来都要打印到屏幕上面的) 重定向其它输出设备(文件,打开文件操作符,或打印机等等)1,2分别是标准输出,错误输出。
#把错误输出,不输出到屏幕,输出到err.txt
[zenhobby@zencode shell]$ ls test.sh test1.sh 1>suc.txt 2>err.txt
[zenhhobby@zencode shell]$ cat suc.txt err.txt
test.sh
ls: test1.sh: 没有这个文件和目录
#继续追加把输出写入suc.txt err.txt “>>”追加操作符
[zenhobby@zencode shell]$ ls test.sh test1.sh 1>>suc.txt 2>>err.txt
命令 | 说明 |
---|---|
command > file | 将输出重定向到 file。 |
command < file | 将输入重定向到 file。 |
command >> file | 将输出以追加的方式重定向到 file。 |
n > file | 将文件描述符为 n 的文件重定向到 file。 |
n >> file | 将文件描述符为 n 的文件以追加的方式重定向到 file。 |
n >& m | 将输出文件 m 和 n 合并。 |
n <& m | 将输入文件 m 和 n 合并。 |
<< tag | 将开始标记 tag 和结束标记 tag 之间的内容作为输入。 |
1、shell遇到”>”操作符,会判断右边文件是否存在,如果存在就先删除,并且创建新文件。不存在直接创建。 无论左边命令执行是否成功。右边文件都会变为空。
2、“>>”操作符,判断右边文件,如果不存在,先创建。以添加方式打开文件,会分配一个文件描述符[不特别指定,默认为1,2]然后,与左边的标准输出(1)或错误输出(2) 绑定。
3、当命令:执行完,绑定文件的描述符也自动失效。0,1,2又会空闲。
4、一条命令启动,命令的输入,正确输出,错误输出,默认分别绑定0,1,2文件描述符。
5、一条命令在执行前,先会检查输出是否正确,如果输出设备错误,将不会进行命令执行
3. 输入重定向
command-line [n] <file或文件描述符&设备
命令将默认从键盘获得的输入,改成从文件,或者其它打开文件以及设备输入。执行这个命令,将标准输入0,与文件或设备绑定。将由它进行输入。
[zenhobby@zencode shell]# cat > catfile
testing
cat file test
#这里按下 [ctrl]+d 离开
#从标准输入【键盘】获得数据,然后输出给catfile文件
[zenhobby@zencode shell]$ cat>catfile <test.sh
#cat 从test.sh 获得输入数据,然后输出给文件catfile
[zenhobby@zencode shell]$ cat>catfile <<eof
test a file
test!
eof
#<< 这个连续两个小符号, 他代表的是『结束的输入字符』的意思。这样当空行输入eof字符,输入自动结束,不用ctrl+D
4. 重定向到空设备文件
1>/dev/null 2>&1
1. 标准输入stdin文件描述符为0,标准输出stdout文件描述符为1,标准错误stderr文件描述符为2
2. /dev/null 空设备文件,相当于垃圾桶
- 0代表标准输入
- 1代表stdout标准输出,默认值为1,所以”1>/dev/null”可以简写为”>/dev/null”
- 2代表stderr标准错误输出
所以,1>/dev/null 2>&1的解释就是:
将stdout标准输出重定向到空设备文件/dev/null ,同时将stderr标准错误输出的重定向跟stdout标准输出重定向一致,也输出到空设备文件/dev/null。