shell 教程
shell script 的注意事项:
- 命令的运行是从上而下、从左而右的分析与运行;
- 命令、选项与参数间的多个空白都会被忽略掉;
- 空白行也将被忽略掉,并且 [tab] 按键所推开的空白同样视为空白键;
- 如果读取到一个 Enter 符号 (CR) ,就尝试开始运行该行 (或该串) 命令;
- 如果一行的内容太多,则可以使用『 \[Enter] 』来延伸至下一行;
- 『 # 』可做为注解!任何加在 # 后面的数据将全部被视为注解文字而被忽略!
shell 运行方式:
1.直接命令执行 (增加x权限)
绝对路径 /usr/local/shell.sh
相对路径 ./shell.sh
加入path变量 shell.sh
bash shell.sh
- 利用直接运行的方式来运行 script
直接运行命令(不论是绝对路径/相对路径还是 $PATH 内),或者是利用 bash (或 sh) 时
该 script 都会使用一个新的 bash 环境来运行脚本内的命令!
也就是说,使用者种运行方式时, 其实 script 是在子程序的 bash 内运行的!
我们在前面谈到 export 的功能时,曾经就父程序/子程序谈过一些概念性的问题,
重点在於:『当子程序完成后,在子程序内的各项变量或动作将会结束而不会传回到父程序中』!
- 利用 source 来运行脚本:在父程序中运行
如果你使用 source 来运行命令那就不一样了!
shell会在父程序中运行的,因此各项动作都会在原本的 bash 内生效!
这也是为啥你不注销系统而要让某些写入 ~/.bashrc 的配置生效时,需要使用『 source ~/.bashrc 』而不能使用『 bash ~/.bashrc 』
read 接收键盘输入:
read [-pt] variable
-p:提示语
-t :等待时间
例:read -p "input:" -t 20 a
declare 定义变量类型:
declare [-aixr] variable
选项与参数:
-a :将后面名为 variable 的变量定义成为数组 (array) 类型
-i :将后面名为 variable 的变量定义成为整数数字 (integer) 类型
-x :用法与 export 一样,就是将后面的 variable 变成环境变量;
-r :将变量配置成为 readonly 类型,该变量不可被更改内容,也不能 unset
例:#declare -i a=1+2+3
#echo $a
6
设置a为环境变量
#declare -x a 等同于 export a
取消a的环境变量
#declare +x a
数值运算:
declare -i total=$a*$b 或者
var=$(($a%$b ))
shell中的数字计算:
1.使用方括号$[ ] 只支持整数运算
var1=$[1 + 5]
var2=$[$var1 * 2]
echo $[var*2+1]
echo $[$var*2-1]
2.使用圆括号 (( ))
var=1
echo ((var+=1))
3.let let几乎支持所有的运算符
var=1
let "var+=1"
let "c=$var+1"
echo $var 输出结果为2
4.使用expr
var=1
var=`expr $var + 1`
foo=$(expr $foo + 1)
#expr 10 + 10
20
注意:
a)expr后的表达式个符号间需用空格隔开
b)expr支持的操作符有: |、&、<、<=、=、!=、>=、>、+、-、*、/、%
c)expr支持的操作符中所在使用时需用\进行转义的有:|、&、<、<=、>=、>、*
e)expr同样只支持整数运算
5.浮点计算可以使用bc;另外awk中也支持计算
shell的判断:
1..利用 test 命令的测试功能
-
举例来说,我要检查 /dmtsai 是否存在时,使用:
-
[root@www ~]# test -e /dmtsai
运行结果并不会显示任何信息,但最后我们可以透过 $? 或 && 及 || 来展现整个结果呢!
例如我们在将上面的例子改写成这样:
[root@www ~]# test -e /dmtsai && echo "exist" || echo "Not exist" Not exist <==结果显示不存在啊!
最终的结果可以告知我们是『exist』还是『Not exist』
测试的标志 | 代表意义 |
1. 关於某个档名的『文件类型』判断,如 test -e filename 表示存在否 | |
-e | 该『名称』是否存在?(常用) |
-f | 该『名称』是否存在且为文件(file)?(常用) |
-d | 该『名称』是否存在且为目录(directory)?(常用) |
-b | 该『档名』是否存在且为一个 block device 装置? |
-c | 该『档名』是否存在且为一个 character device 装置? |
-S | 该『档名』是否存在且为一个 Socket 文件? |
-p | 该『档名』是否存在且为一个 FIFO (pipe) 文件? |
-L | 该『档名』是否存在且为一个连结档? |
2. 关於文件的权限侦测,如 test -r filename 表示可读否 (但 root 权限常有例外) | |
-r | 侦测该档名是否存在且具有『可读』的权限? |
-w | 侦测该档名是否存在且具有『可写』的权限? |
-x | 侦测该档名是否存在且具有『可运行』的权限? |
-u | 侦测该档名是否存在且具有『SUID』的属性? |
-g | 侦测该档名是否存在且具有『SGID』的属性? |
-k | 侦测该档名是否存在且具有『Sticky bit』的属性? |
-s | 侦测该档名是否存在且为『非空白文件』? |
3. 两个文件之间的比较,如: test file1 -nt file2 | |
-nt | (newer than)判断 file1 是否比 file2 新 |
-ot | (older than)判断 file1 是否比 file2 旧 |
-ef | 判断 file1 与 file2 是否为同一文件,可用在判断 hard link 的判定上。 主要意义在判定,两个文件是否均指向同一个 inode 哩! |
4. 关于两个整数之间的判定,例如 test n1 -eq n2 | |
-eq | 两数值相等 (equal) |
-ne | 两数值不等 (not equal) |
-gt | n1 大於 n2 (greater than) |
-lt | n1 小於 n2 (less than) |
-ge | n1 大於等於 n2 (greater than or equal) |
-le | n1 小於等於 n2 (less than or equal) |
5. 判定字串的数据 | |
test -z string | 判定字串是否为 0 ?若 string 为空字串,则为 true |
test -n string | 判定字串是否非为 0 ?若 string 为空字串,则为 false。 注: -n 亦可省略 |
test str1 = str2 | 判定 str1 是否等於 str2 ,若相等,则回传 true |
test str1 != str2 | 判定 str1 是否不等於 str2 ,若相等,则回传 false |
6. 多重条件判定,例如: test -r filename -a -x filename | |
-a | (and)两状况同时成立!例如 test -r file -a -x file,则 file 同时具有 r 与 x 权限时,才回传 true。 |
-o | (or)两状况任何一个成立!例如 test -r file -o -x file,则 file 具有 r 或 x 权限时,就可回传 true。 |
! | 反相状态,如 test ! -x file ,当 file 不具有 x 时,回传 true |
#1判断文件名是否为空,若为空则提示并退出
test -z $filename && echo "You MUST input a filename." && exit 0
# 2. 判断文件是否存在?若不存在则显示信息并结束脚本
test ! -e $filename && echo "The filename '$filename' DO NOT exist" && exit 0
# 3. 开始判断文件类型与属性
test -f $filename && filetype="regulare file"
test -d $filename && filetype="directory"
test -r $filename && perm="readable"
test -w $filename && perm="$perm writable"
test -x $filename && perm="$perm executable"
2.利用判断符号
[ ]
除了我们很喜欢使用的 test 之外,其实,我们还可以利用判断符号『 [ ] 』(就是中括号) 来进行数据的判断呢!
举例来说,如果我想要知道 $HOME 这个变量是否为空的,可以这样做:
[root@www ~]# [ -z "$HOME" ] ; echo $?
使用中括号必须要特别注意,因为中括号用在很多地方,包括万用字节与正规表示法等等,所以如果要在 bash 的语法当中使用中括号作为 shell 的判断式时,必须要注意中括号的两端需要有空白字节来分隔喔! 假设我空白键使用『□』符号来表示,那么,在这些地方你都需要有空白键:
[ "$HOME" == "$MAIL" ]
[□"$HOME"□==□"$MAIL"□]
↑ ↑ ↑ ↑
Tips: 上面的判断式当中使用了两个等号『 == 』。 其实在 bash 当中使用一个等号与两个等号的结果是一样的! 不过在一般惯用程序的写法中,一个等号代表『变量的配置』,两个等号则是代表『逻辑判断 (是否之意)』。 由於我们在中括号内重点在於『判断』而非『配置变量』,因此建议您还是使用两个等号较佳! |
上面的例子在说明,两个字串 $HOME 与 $MAIL 是否相同的意思,相当於 test $HOME = $MAIL 的意思啦!
而如果没有空白分隔,例如 [$HOME==$MAIL] 时,我们的 bash 就会显示错误信息了!
要注意:
- 在中括号 [] 内的每个组件都需要有空白键来分隔;
- 在中括号内的变量,最好都以双引号括号起来;
- 在中括号内的常数,最好都以单或双引号括号起来。
使用 -a 和 -o
read -p "Please input (Y/N): " yn
[ "$yn" == "Y" -o "$yn" == "y" ] && echo "OK, continue" && exit 0
[ "$yn" == "N" -o "$yn" == "n" ] && echo "Oh, interrupt!" && exit 0
echo "I don't know what your choice is" && exit 0
[] 和 [[ ]] 的区别
[ ]
[[ ]]
Shell script 的默认变量($0, $1...):
shell变量的标识方法
/path/to/scriptname opt1 opt2 opt3 opt4
$0 $1 $2 $3 $4
还有一些较为特殊的变量可以在 script 内使用
- $# :代表后接的参数『个数』,以上表为例这里显示为『 4 』;
- $@ :代表『 "$1" "$2" "$3" "$4" 』之意,每个变量是独立的(用双引号括起来);
- $* :代表『 "$1c$2c$3c$4" 』,其中 c 为分隔字节,默认为空白键, 本例中代表『 "$1 $2 $3 $4" 』之意。
- shift:造成参数变量号码偏移:
- shift 会移动变量,而且 shift 后面可以接数字,代表拿掉最前面的几个参数的意思
-
[root@www scripts]# vi sh08.sh #!/bin/bash echo "Total parameter number is ==> $#" echo "Your whole parameter is ==> '$@'" shift # 进行第一次『一个变量的 shift 』 echo "Total parameter number is ==> $#" echo "Your whole parameter is ==> '$@'" shift 3 # 进行第二次『三个变量的 shift 』 echo "Total parameter number is ==> $#" echo "Your whole parameter is ==> '$@'"
[root@www scripts]# sh sh08.sh one two three four five six <==给予六个参数 Total parameter number is ==> 6 <==最原始的参数变量情况 Your whole parameter is ==> 'one two three four five six' Total parameter number is ==> 5 <==第一次偏移,看底下发现第一个 one 不见了 Your whole parameter is ==> 'two three four five six' Total parameter number is ==> 2 <==第二次偏移掉三个,two three four 不见了 Your whole parameter is ==> 'five six'
条件判断式:
- 单层、简单条件判断式
if [ 条件判断式 ]; then 当条件判断式成立时,可以进行的命令工作内容; fi <==将 if 反过来写,就成为 fi 啦!结束 if 之意!
如果我有多个条件要判别时, 除了『将多个条件写入一个中括号内的情况』之外,
我还可以有多个中括号来隔开。
而括号与括号之间,则以 && 或 || 来隔开,他们的意义是:
- && 代表 AND ;
- || 代表 or ;
所以,在使用中括号的判断式中, && 及 || 就与命令下达的状态不同了。
举例来说:
[ "$yn" == "Y" -o "$yn" == "y" ]
上式可替换为
[ "$yn" == "Y" ] || [ "$yn" == "y" ]
[root@www scripts]# vi sh06-2.sh
#!/bin/bash
read -p "Please input (Y/N): " yn
if [ "$yn" == "Y" ] || [ "$yn" == "y" ]; then
echo "OK, continue"
exit 0
fi
if [ "$yn" == "N" ] || [ "$yn" == "n" ]; then
echo "Oh, interrupt!"
exit 0
fi
echo "I don't know what your choice is" && exit 0
- 多重、复杂条件判断式:
-
# 一个条件判断,分成功进行与失败进行 (else) if [ 条件判断式 ]; then 当条件判断式成立时,可以进行的命令工作内容; else 当条件判断式不成立时,可以进行的命令工作内容; fi
# 多个条件判断 (if ... elif ... elif ... else) 分多种不同情况运行 if [ 条件判断式一 ]; then 当条件判断式一成立时,可以进行的命令工作内容; elif [ 条件判断式二 ]; then 当条件判断式二成立时,可以进行的命令工作内容; else 当条件判断式一与二均不成立时,可以进行的命令工作内容; fi
利用 case ..... esac 判断:
case $变量名称 in <==关键字为 case ,还有变量前有钱字号
"第一个变量内容") <==每个变量内容建议用双引号括起来,关键字则为小括号 )
程序段
;; <==每个类别结尾使用两个连续的分号来处理!
"第二个变量内容")
程序段
;;
*) <==最后一个变量内容都会用 * 来代表所有其他值
不包含第一个变量内容与第二个变量内容的其他程序运行段
exit 1
;;
esac <==最终的 case 结尾!『反过来写』思考一下!
一般来说,使用『 case $变量 in 』这个语法中,当中的那个『 $变量 』大致有两种取得的方式:
- 直接下达式:利用『 script.sh variable 』 的方式来直接给予 $1 这个变量的内容,这也是在 /etc/init.d 目录下大多数程序的设计方式。
- 互动式:透过 read 这个命令来让使用者输入变量的内容。
利用 function 功能:
function fname() { 程序段 }那个 fname 就是我们的自订的运行命令名称~
而程序段就是我们要他运行的内容了。
要注意的是,因为 shell script 的运行方式是由上而下,由左而右,
因此在 shell script 当中的 function 的配置一定要在程序的最前面, 这样才能够在运行时被找到可用的程序段
另外, function 也是拥有内建变量的~他的内建变量与
shell script 很类似, 函数名称代表示 $0 ,而后续接的变量也是以 $1, $2... 来取代的~
这里很容易搞错喔~因为『 function fname() { 程序段 } 』内的 $0, $1... 等等与 shell script 的 $0 是不同的
[root@www scripts]# vi sh12-3.sh
function printit(){
echo "Your choice is $1" # 这个 $1 必须要参考底下命令的下达
}
echo "This program will print your selection !"
case $1 in
"one")
printit 1 # 请注意, printit 命令后面还有接参数!
;;
"two")
printit 2
;;
"three")
printit 3
;;
*)
echo "Usage $0 {one|two|three}"
;;
esac
在上面的例子当中,如果你输入『 sh sh12-3.sh one 』就会出现『 Your choice is 1 』的字样~ 为什么是 1 呢?因为在程序段落当中,我们是写了『 printit 1 』那个 1 就会成为 function 当中的 $1 喔~
循环:
- while do done, until do done (不定回圈)
一般来说,不定回圈最常见的就是底下这两种状态了:
while [ condition ] <==中括号内的状态就是判断式
do <==do 是回圈的开始!
程序段落
done <==done 是回圈的结束
while 的中文是『当....时』,所以,这种方式说的是『当 condition 条件成立时,就进行回圈,直到 condition 的条件不成立才停止』的意思。
还有另外一种不定回圈的方式:
until [ condition ]
do
程序段落
done
这种方式恰恰与 while 相反,它说的是『当 condition 条件成立时,就终止回圈, 否则就持续进行回圈的程序段。』是否刚好相反啊~
- for...do...done (固定回圈)
相对於 while, until 的回圈方式是必须要『符合某个条件』的状态,
for 这种语法,则是『 已经知道要进行几次循环』的状态!他的语法是:
for var in con1 con2 con3 ...
do
程序段
done
以上面的例子来说,这个 $var 的变量内容在回圈工作时:
- 第一次回圈时, $var 的内容为 con1 ;
- 第二次回圈时, $var 的内容为 con2 ;
- 第三次回圈时, $var 的内容为 con3 ;
- ....
for 常见用法
遍历当前目录文件 for i in `ls`
遍历一个整数序列 for i in `seq 1 254`
遍历一个数组 for i in ${Array[*]}
- for...do...done 的数值处理
除了上述的方法之外,for 回圈还有另外一种写法!语法如下:
for (( 初始值; 限制值; 运行步阶 )) do 程序段 done
这种语法适合於数值方式的运算当中,在 for 后面的括号内的三串内容意义为:
- 初始值:某个变量在回圈当中的起始值,直接以类似 i=1 配置好;
- 限制值:当变量的值在这个限制值的范围内,就继续进行回圈。例如 i<=100;
- 运行步阶:每作一次回圈时,变量的变化量。例如 i=i+1。
值得注意的是,在『运行步阶』的配置上,如果每次添加 1 ,则可以使用类似『i++』的方式,亦即是 i 每次回圈都会添加一的意思。好,我们以这种方式来进行 1 累加到使用者输入的回圈吧!
for (( i=1; i<=$nu; i=i+1 )) do s=$(($s+$i)) done
shell script 的追踪与 debug:
sh [-nvx] scripts.sh
选项与参数:
-n :不要运行 script,仅查询语法的问题;
-v :再运行 sccript 前,先将 scripts 的内容输出到萤幕上;
-x :将使用到的 script 内容显示到萤幕上,这是很有用的参数!
范例一:测试 sh16.sh 有无语法的问题?
[root@www ~]# sh -n sh16.sh
# 若语法没有问题,则不会显示任何资讯!
范例二:将 sh15.sh 的运行过程全部列出来~
[root@www ~]# sh -x sh15.sh
+ PATH=/bin:/sbin:/usr/bin:/usr/sbin:/usr/local/bin:/usr/local/sbin:/root/bin
+ export PATH
+ for animal in dog cat elephant
+ echo 'There are dogs.... '
There are dogs....
+ for animal in dog cat elephant
+ echo 'There are cats.... '
There are cats....
+ for animal in dog cat elephant
+ echo 'There are elephants.... '
There are elephants....
shell数组:
数组的定义
何为数组?学过计算机编程语言的同学都知道,数组的特性就是一组数据类型相同的集合。
shell中数组两种数据类型:
一是数值类型,
二是字符串类型;
虽然shell本身是弱类型的,但也可以这么区分。
数值类型的数组:一对括号表示数组,数组中元素之间使用“空格”来隔开。
举个列子:
arr_number=(1 2 3 4 5);
字符串类型数组:同样,使用一对括号表示数组,其中数组中的元素使用双引号或者单引号包含,同样使用“空格”来隔开。
arr_string=("abc" "edf" "sss"); 或者 arr_string=('abc' 'edf' 'sss');
数组的操作
arr_number=(1 2 3 4 5)
输出所有数组:echo ${array_name[@]}
获取数组长度:${#arr_number[*]} ${#arr_number[@]}
读取某个下标的值:${arr_number[2]} 即形式:${数组名[下标]}
对某个下标赋值:
这里需要提出两个问题:
第一个问题是如果该下标元素已经存在,会怎么样?
答:会修改该下标的值为新的指定值。
例如:arr_number[2]=100,数组被修改为(1 2 100 4 5)
第二个问题是如果指定的下标已经超过当前数组的大小,如上述的arr_number的大小为5,
指定下标为10或者11或者大于5的任意值会如何?
答:新赋的值被追加到数组的尾部。
例如:arr_number[13]=13,数组被修改为(1 2 100 4 5 13)
清除某个元素:unset arr_number[1] 这里清除下标为1的数组;
清空整个数组:unset arr_number
分片访问:
分片访问形式为:${数组名[@或*]:开始下标:结束下标}
注意,不包括结束下标元素的值。
例如:${arr_number[@]:1:4},这里分片访问从下标为1开始,元素个数为4。
模式替换:
形式为:${数组名[@或*]/模式/新值}
例如:${arr_number[@]/2/98}
数组的遍历:
数组遍历我们使用for语句来演示:
for v in ${arr_number[@]}; do
echo $v;
done
Shell逐行读取文件的4种方法
方法1:while循环中执行效率最高,最常用的方法。
While read LINE
do
echo $LINE
done < $FILENAME
}
注释:我习惯把这种方式叫做read釜底抽薪,因为这种方式在结束的时候需要执行文件,就好像是执行完的时候再把文件读进去一样。
方法2 : 重定向法;管道法: cat $FILENAME | while read LINE
cat $FILENAME | while read LINE
do
echo $LINE
done
}
注释:我只所有把这种方式叫做管道法,相比大家应该可以看出来了吧。当遇见管道的时候管道左边的命令的输出会作为管道右边命令的输入然后被输入出来。
方法3: 文件描述符法
Exec 3<&0
Exec 0<$FILENAME
While read LINE
Do
Echo $LINE
Exec 0<&<3
}
注释: 这种方法分2步骤,第一,通过将所有内容重定向到文件描述符3来关闭文件描述符0.为此我们用了语法Exec 3<&0 。第二部将输入文件放送到文件描述符0,即标准输入。
方法4 for 循环
For i in `cat $FILENAME`
do
echo $i
done
}
注释:这种方式是通过for循环的方式来读取文件的内容相比大家很熟悉了
for循环有一点需要注意 ,遇到空格 会将空格后面的内容重启一行
# cat file
aaaa
bbbb
cccc dddd
# cat file | while read line; do echo $line; done
aaaa
bbbb
cccc dddd
# for line in $(<file); do echo $line; done
aaaa
bbbb
cccc
dddd
见在各个方法中,for语句效率最高,而在while循环中读写文件时执行效率最高。
shell中生成随机数:
1.echo $RANDOM
$RANDOM 范围 0-32767
实例:生成0-100随机数
echo $(($RANDOM%101))
生成1-100随机数
echo $(($RANDOM%100+1))
生成17-67随机数
echo $(($RANDOM%51+17))
生成A 66666 到B 88888 随机数
echo $(($RANDOM% B-A+1 + A))
2. /dev/random /dev/urandom
/dev/random 存储着系统当前运行环境的实时数据,是阻塞的随机数发生器,读取有时需要等待。
/dev/urandom 非阻塞随机数发生器,读取操作不会产生阻塞。
生成32位字符串 cat /dev/random |head -n 10 | md5sum
生成10位字符串 cat /dev/random |head -n 10 | md5sum | head -c 10
生成纯数字字符串 cat /dev/urandom | head -n 10 | md5sum | tr -dc 0-9
3.使用date +%s%N
%s 自从 1970-01-01 00:00:00 起开始计时的秒数
%N 纳秒(000000000-999999999) 一秒的10亿分之一
生成一个 19位的 数字字符串
# date +%s%N
1512550383763969880