shell脚本是什么?
(1)命令的堆积;
(2)程序逻辑;
如何写shell脚本:
脚本文件的第一行,顶格:给出shebang,解释其路径,用于指明解释执行当前脚本的解释器程序文件。
常见的解释器:
#!/bin/bash
#!/usr/bin/python
#!/usr/bin/perl
运行脚本:
(1)赋予执行权限,并直接运行此程序文件;
(2)直接运行解释器,将脚本以命令行参数传递给解释器程序:
bash /PATH/TO/SCRIPT_FILE
注:脚本中的空白行会被解释器忽略。
脚本中,除了shebang,余下所有以#开头的行,都会被视作注释行而被忽略;此即为解释行。
shell脚本的运行是通过一个子shell进程实现的。
bash的算术运算及条件测试
(1)实现算术运算
let var=算术表达式
var=$[算术表达式]
var=$((算术表达式))
var=$(expr arg1 arg2 arg3)
例:mul=$(expr $num1 \* $num2)
注:称号在某些场景中需要转义
(2)增强型赋值符号
+=, -=, *=, /=, %=
let varOPERvalue
例如:let count+=1
自增,自减:
let var+=1
let var++
let var-=1
let var--
(3)条件测试:
判断某需求是否满足,需要由测试机制来实现;
如何编写测试表达式以实现所需的测试:
(1)执行命令;并利用命令状态返回值来判断。
0:成功 1-255:失败
(2)测试表达式:[ EXPRESSION ]
[[ EXPRESSION ]]
注:EXPRESSION前后必须有空白字符;
①数值测试:
-eq:是否等于;
-ne:是否不等于;
-gt:是否大于;
-lt:是否小于;
-le:是否小于等于;
②字符串测试:
==:是否等于;
>:是否大于;
<:是否小于;
!=:是否不等于;
=~:左侧字符串是否能够被右侧的PATTERN所匹配;
注:此表达式一般用于[[ ]]中;
-z "STRING":测试字符串是否为空,空则为真,不空则为假;
-n "STRING":测试字符串是否不空,不空则为真,空则为假;
注:用于字符串比较时用到的操作数东应该使用引号;
③自定义退出状态码:
exit [n]:自定义推出状态码;
注:脚本中一旦遇到exit命令,脚本会立即终止;终止退出状态取决于exit命令后面的数字;如果未给脚本指定退出状态码,整个脚本的退出状态码取决于脚本中执行的最后一条命令的状态码;
④文件测试:
存在性测试:
-a FILE
-e FILE:文件存在性测试,存在为真,否则为假;
存在性及类别测试:
-b FILE:是否存在且为块设备文件;
-c FILE:是否存在且为字符设备文件;
-d FILE:是否存在且为目录文件;
-f FILE:是否存在且为普通文件;
-h FILE 或 -L FILE:存在且为符号链接文件;
-p FILE:是否存在且为管道文件;
-S FILE:是否存在且为套接字文件;
文件权限测试:
-r FILE:是否存在且为可读;
-w FILE:是否存在且为可写;
-x FILE:是否存在且为可执行
文件特殊权限测试:
-g FILE:测试是否存在且拥有sgid权限;
-u FILE:是否存在且拥有suid权限;
-k FILE:是否存在且拥有sticky权限;
文件大小测试:
-s FILE:是否存在且非空
文件是否打开:
-t fd:fd表示文件描述符是否已经打开且与某终端相关
-N FILE:文件上一次读取之后是否被修改过
-O FILE: 当前有效用户是否为文件属主
-G FILE: 当前有效用户是否为文件属组
双目测试:
FILE1 -ef FILE2:两个文件是否指向同一个设备上相同的inode
FILE1 -nt FILE2:FILE1是否新于FILE2
FILE1 -ot FILE2:FILE1是否旧于FILE2
组合测试条件:
逻辑运算
第一种方式
COMMAND1 && COMMAND2
COMMAND1 || COMMAND2
!COMMAND
第二种方式
EXPRESSION1 -a EXPRESSION2
EXPRESSION1 -o EXPRESSION2
!EXPRESSION
(4)shell脚本变量:
1)变量赋值:name='value'
其中的value可以为
a.可以使直接字串:name="username"
b.变量引用:name="$username"
c.命令引用:name=`COMMAND`(单反引号)
name=$(COMMAND)
2)变量引用:${name},$name
" ": 弱引用,其中的变量引用会被替代为变量值
' ': 强引用,其中的变量引用不会被替换为变量值,而保持原字符串
3)位置变量
在脚本代码中调用通过命令行传递给脚本的参数
$1,$2,...:对应调用第1,第2等参数
$#:它可抓出positional parameter的数量,即在脚本后面的参数有几个
$0:脚本本身的名字
$@:传给脚本的所有参数
$*:是以一个单字符串显示里所有向脚本传递的参数,与位置参数不同,参数可超过9个
$$:是脚本运行的当前进程的ID号
$?:显示最后命令的退出状态
bash脚本编程:
(1)用户交互
read [option]... [name]...
-p 'PROMPT'
-t TIMEOUT
bash -n /PATH/TO/SOME_SCRIPT
检查脚本中有无语法错误;
bash -x /PATH/TO/SOME_SCRIPT
分步执行脚本内容;
(2)脚本编程结构:
①条件测试
运行命令或[[ EXPRESSION ]]
执行状态返回值
if
case
②循环执行
将某代码段重复运行多次
必须有进入条件和退出条件
for ,while, until
③函数:结构化编程及代码复用
function
(3)if语句
单分支:
if CONDITION; then
...
fi
双分支:
if CONDITION; then
...
else
...
fi
多分支:
if CONDITION1;then
...
elif CONDITION2; then
...
elif CONDITION3; then
...
....
else
...
fi
逐条件进行判断,第一次判断为“真”时,执行其分支,而后结束;
if语句可嵌套;
示例1:提示用户键入文件路径,用脚本判断文件类型:
#!/bin/bash #judge type about a file what user give read -p "Please Enter a filename:" filename if [ -z "$filename" ]; then echo "Usage: Enter a file path" exit 2 fi if [ ! -e $filename ]; then echo "Usage: No such file, check it and enter again" exit 3 fi if [ -f $filename ]; then echo "It's a common file" elif [ -d $filename ]; then echo "It's a directory" elif [ -L $filename ]; then echo "It's a symbolic file" else echo "Other type" fi [root@localhost ~]# bash file.sh Please Enter a filename:file.sh It's a common file
示例2:传递给脚本文件名,判断两个文件的行数多少:
#!/bin/bash #compare two files lines if ! [ -e $1 ] || ! [ -e $2 ]; then echo "Please enter a exist filename" exit 2 fi A=` wc $1 &> /dev/null ` B=` wc $2 &> /dev/null ` if [ $A -gt $B ]; then echo "$1 has more lines" else echo "$2 has more lines" fi [root@localhost ~]# bash first.sh /etc/fstab /etc/passwd /etc/fstab has more lines
(4)for循环语句:
①for VARAIBLE in LIST; do
循环体
done
进入条件:只要列表有元素,即可进入循环;
退出条件:列表中的元素遍历完成;
②列表生成方式:
直接给出
整数列表
{start..end}
${seq [start [incremtal]] last}
返回列表的命令
$(COMMAND)
glob
/etc/rc.d/rc1.d/k*(遍历该路径下的以k开头的所有文件)
变量引用
$@,$*
示例1:添加10个用户,user1-user10;密码同用户名;
#!/bin/bash # add 10 user, username:user1~user10,password:user1~user10; for i in {1..10}; do if id user$i &> /dev/null; then echo "user$i is exist" else useradd user$i && echo "user$i" | passwd --stdin user$i &> /dev/null echo " add user$i is successed" fi done
示例2:判断某路径下所有文件的类型
#!/bin/bash #judge type of file in some path for file in $(ls /etc); do if [ -f /etc/$file ]; then echo "$file is common file" elif [ -d /etc/$file ]; then echo "$file is directory file" elif [ -L /etc/$file ]; then echo "$file is symbolic file" else echo "$file is oth type" fi done
示例3:打印九九乘法表:
#!/bin/bash #9X9 multiplication for i in {1..9}; do for j in $(seq 1 $i); do echo -e -n "${i}x${j}=$[$i*$j]\t" done echo done
(4)for循环的特殊用法:
for ((控制变量初始化;条件判断表达式;控制变量的修正表达式));do
循环体
done
控制变量初始化:尽在运行到循环代码段时执行一次
控制变量的修正表达式:每轮循环结束后会先对控制变量进行修正运算
示例:九九乘法表
for ((i=1; i<=9; i++)); do for ((j=1; j<=$i; j++)); do echo -e -n "${i}x${j}=$[$i*$j]\t" done echo done
(5)while循环:
①while CONDITION; do
循环体
done
CONDITION:循环控制条件,进入循环前,先做一次判断;每一次循环之后会再次做判断,条件为“true”,则执行一次循环;直到条件测试状态为“false”终止循环;
示例:求100以内的偶数和以及奇数和
#!/bin/bash #even number sum and odd number sum declare -i i=0 declare -i evensum=0 declare -i oddsum=0 while [ $i -le 100 ]; do if [ $[$i%2] -eq 0 ]; then evensum=$[$evensum+$i] else oddsum=$[$oddsum+$i] fi let i++ done echo "even number sum is $evensum" echo "odd number sum is $oddsum" [root@localhost ~]# bash sum.sh even number sum is 2550 odd number sum is 2500
②while循环的特殊用法(遍历文件中的每一行)
while read line; do
循环体
done < /PATH/FROM/SOMEFILE
依次读取/PATH/FROM/SOMEFILE文件中的每一行,且将行赋值给变量line;
示例:找出其ID号为偶数的所有用户,显示其用户名及ID号;
#!/bin/bash #ouput even number id 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
(6)until循环
until CONDITION; do
循环体
done
进入条件:false
退出条件:true
(7)循环控制语句(用于循环体中)
1)continue [N]
提前结束由内而外的第N层的本轮循环,并且直接进入下一轮循环判断
while CONDITION1; do
...
if CONDITION2; then
continue
fi
...
done
2)break [N]:提前结束循环
示例:只求100内的偶数和
#!/bin/bash #output even numner sum declare -i i=0 declare -i sum=0 until [ $i -gt 100 ]; do let i++ if [ $[$i%2] -eq 1 ]; then continue fi let sum+=$i done echo "even number sum is $sum"
(8)条件判断:case语句
case 变量引用 in
PAT1)
分支1
; ;
PAT2)
分支2
; ;
PAT3)
分支3
; ;
...
*)
默认分支
; ;
esac
case支持glob风格的通配符
*:任意长度任意字符
?:任意单个字符
[]:指定范围内的单个字符
a|b:a或b
示例:提示用户键入字符,并判断类型:
#!/bin/bash #input a key,judge type read -p "Please enter a key:" key case $key in [[:lower:]] ) echo "Lowercase letter" ;; [[:upper:]] ) echo "Uppercase letter" ;; [0-9] ) echo "Digit" ;; *) echo "others" esac [root@localhost ~]# bash case.sh Please enter a key:a Lowercase letter
(9)函数:function
对于过程式编程,可以实习代码重写
模块化编程
结构化编程
把一段独立功能的代码当做一个整体,并为之起个名字;命名的代码段,纪委函数。
注:定义函数的代码段不会自动执行,在调用时执行;所谓调用函数,在代码中给定函数名即可;函数名出现的任何位置,在代码执行时,都会被自动被替换为函数代码。
1)语法一
function f_name{
函数体
}
语法二
f_name{
函数体
}
2)调用:函数只有被调用才会执行
调用方法:给定函数名即可
函数名出现的地方,会被自动替换为函数代码
函数生命周期:被调用时创建
3)函数返回值
函数执行结果返回值
使用echo或print命令进行输出
函数体中调用命令的执行结果
函数的退出状态码
默认取决于函数体中执行的最后一条命令的退出状态码
自定义退出状态码:return
4)函数可以接受参数
传递参数给函数:调用函数时,在函数名后面以空白分隔给定参数列表即可
在函数体中,可使用$1,$2...调用这些参数,还可以使用$@,$*,$#等特殊变量
5)函数递归:
函数直接或间接调用自身
示例:简单使用函数:
#!/bin/bash #show function print(){ echo "your choice is $1" } read -p "Please enter one、two、three or four:" num case $num in one) print 1 ;; two) print 2 ;; three) print 3 ;; four) print 4 ;; esac [root@localhost ~]# bash function.sh Please enter one、two、three or four:one your choice is 1 [root@localhost ~]# bash function.sh Please enter one、two、three or four:two your choice is 2
(10)数组
基本概念:
变量:存储单个元素的内存空间
数组:存储多个元素的连续的内存空间
数组名
索引:编号从0开始,是数值索引
引用数组中的元素:${ARRAY_NAME[INDEX]}
1)声明数组
declare -a ARRAY_NAME
declare -A ARRAY_NAME 关联数组
注:索引也可支持使用自定义的格式,而不仅仅是数值格式
bash数组支持稀疏格式
2)数组元素的赋值
①一次只赋值一个元素
ARRAY_NAME[INDEX]=VALUE
例: weekday[0]="Sunday"
weekday[0]="Thursday"
②一次赋值全部元素
ARRAY_NAME=("VAL1" "VAL2" "VAL3"...)
③只赋值特定元素
ARRAY_NAME=([0]="VAL1" [3]="VAL2")
④read -a ARRAY
3)引用数组元素
${ARRAY_NAME [INDEX]}
注:引用时,只给数组名,表示引用下标为0的元素;
${ARRAY_NAME[*]}:引用数组中的所有元素;
${ARRAY_NAME[@]}
4)数组的长度:(数组中元素的个数)
${#ARRAY_NAME[*]},${#ARRAY_NAME[@]}
引用数组中的元素
数组切片;${ARRAY[@]:offset:number}
offset:要跳过的元素的个数
number:要去除的元素个数,取偏移量之后的所有元素
${ARRAY[@]:offset}
向数组中追加元素
ARRAY[${#ARRAY[*]}]
删除数组中的某元素
unset ARRAY[INDEX]
关联数组
declare -A ARRAY_NAME
ARRAY_NAME=([index_name1]='val1'[index_name]='val2'...)
示例:使用数组
#!/bin/bash # declare -a arry arry=(tom jery joe) for ((i=0; i<=${#arry[@]}; i++)) do echo "${arry[$i]}" done echo "array length: ${#arry[@]}" [root@localhost ~]# bash array.sh tom jery joe array length: 3
示例:生成10个随机数并从小到大排序
#!/bin/bash # declare -a num for i in {0..9}; do num[$i]=$RANDOM done echo ${num[*]} for((i=0;i<=9;i++)); do for((j=$[${i}+1];j<10;j++)); do if [ ${num[$i]} -gt ${num[$j]} ]; then a=${num[$i]} num[$i]=${num[$j]} num[$j]=$a fi done done echo ${num[*]} [root@localhost ~]# bash paixu.sh 6670 15462 25364 28280 14556 4667 24577 7020 22897 5217 4667 5217 6670 7020 14556 15462 22897 24577 25364 28280