目录
一、bash
1.1 shell就是一个bash程序
- 解释器,启动器
- 解释器:
用户交互输入
文本文件输入 - 脚本本质:
-#!/bin/bash
-#!/usr/bin/python - 读取方式:
bash file - 当前shell:source file.
- 新建子shell:/bin/bash file 或者 ./file.sh (需要chmod +x file.sh)
命令行中的命令都可以放到一个文件中!
省的每次都得重新写大量的shell命令。
在当前shell执行脚本命令
type source
help source
source mysh.sh
./mysh.sh
[root@bk1 ~]# bash
[root@bk1 ~]# exit
exit
[root@bk1 ~]# echo $$ #打印当前bash的PID
1280
[root@bk1 ~]# bash
[root@bk1 ~]# pstree
init─┬─auditd───{auditd}
├─crond
├─master─┬─pickup
│ └─qmgr
├─6*[mingetty]
├─rsyslogd───3*[{rsyslogd}]
├─sshd───sshd───bash───bash───pstree
└─udevd───2*[udevd]
[root@bk1 ~]# exit
exit
[root@bk1 ~]# pstree
init─┬─auditd───{auditd}
├─crond
├─master─┬─pickup
│ └─qmgr
├─6*[mingetty]
├─rsyslogd───3*[{rsyslogd}]
├─sshd───sshd───bash───pstree
└─udevd───2*[udevd]
pstree -p #显示进程id
1.创建脚本到执行脚本
[root@bk1 ~]# vi mysh.sh
#!/bin/bash
ls -l /
echo "hello world!"
添加执行权限
[root@bk1 ~]# chmod +x mysh.sh #等价于a+x
[root@bk1 ~]# ./mysh.sh #执行
2.bash mysh.sh #bash启动一个新进程,执行文件mysh.sh
[root@bk1 ~]# vi mysh1.sh
#!/bin/bash
ls -l /
echo "hello world!"
echo $$
pstree
[root@bk1 ~]# chmod +x mysh.sh
[root@bk1 ~]# ./mysh1.sh #执行
3.让#!后跟的字符表示要启动的程序,该程序读取该文件执行。
[root@bk1 ~]# vi awk.sh
#!/bin/awk -f
{split($3, date, "-");
if (date[2] == "01") {name[$1]+=$5;
if($2=="0"){role[$1]="Manager"}
else{role[$1]="Worker"}}}
END{for(i in name){print i"\t"name[i]"\t"role[i]}}
[root@bk1 ~]# chmod +x awk.sh
[root@bk1 ~]# ./awk.sh awk.txt
定义函数:
myshellname () {
command1
command2
command3
……
}
调用:
myshellname
1.2 总结:
bash是一个程序,shell是一个bash进程
bash是一个解释器,启动器
解释执行用户的输入指令,可以通过shell启动其他的进程
将要执行的命令放到一个文件中,
在文件的开头:
#!/bin/bash
#!/usr/bin/python
#!/bin/awk -f
用于指定该脚本由哪个程序负责解释执行
当前shell执行脚本:source .
子进程执行:bash mysh.sh或者./mysh.sh(需要该文件具有可执行权限)
定义函数:
funName() {
各种命令
}
直接输入funName就可以执行了
1.3 io
重定向不是命令
- 程序自身都有I/O
0:标准输入
1:标准输出
2:错误输出 - 控制程序I/O位置
- 一切皆文件
cd /proc/$$/fd - 程序是否处理I/O
- 重定向绑定顺序:从左到右
ls / /hello 1> log.out
ls / /hello 1> log.out 2> log.err
1.3.0 数据重定向
标准输入(stdin):编号为0
标准输出(stdout):编号为1
标准错误输出(stderr):编号为2
1>:以覆盖的方法,将正确的数据输出到文件;
1>>:以累加的方法,将正确的数据输出到文件;
2>:以覆盖的方法,将错误输出的数据输出到文件;
2>>:以累加的方法,将错误输出的数据输出到文件;
ls -l >> ok1.log
ls -l > ok2.log
ls hello 2>/root/err.log
ls hello / 1>/root/log.log 2>/root/err.log
ls 1>/dev/null
ls 2>/tmp/err.log
既向控制台输出,也向文件写入
ls -l / | tee ok2.log
tee命令,将输入分成两个输出
先输出错误输出,再输出正确输出
1.3.1 输出重定向: 重定向从左到右绑定
ls / /hello 2>&1 1> mylog.log
从左向右绑定,错误输出绑定到标准输出,此时标准输出是输出到控制台,然后才是标准输出重定向到文件。两个重定向的绑定没有关系。
注意&1符号的替换,直接将输出绑定到资源,而不是绑定到文件描述符
ls / /hello 1> mylog1.log 2>&1
先让标准输出重定向到文件,然后将错误输出绑定到标准输出,也就是左边绑定的文件。
第二种写法
标准输出和错误输出都重定向到文件
ls / /hello >& mylog2.log
ls / /hello &> mylog3.log
1.3.2 read
-
将用户控制台的输入赋值给aaa变量
read在不提供参数的时候,会将用户的输入存储在REPLY变量中
[root@bk1 ~]# read
abc
[root@bk1 ~]# echo $REPLY
abc -
如果提供了参数,则赋值给指定的参数
[root@bk1 ~]# read aaa
nihao
[root@bk1 ~]# echo $aaa
nihao -
将标准输入重定向到字符串,read读取后赋值给指定的变量:
<<<将标准输入重定向到字符串
[root@bk1 ~]# read aaa 0<<<“hello”
[root@bk1 ~]# echo $aaa
hello
1.3.3 了解
用在脚本中用于向控制台打印n行
[root@bk1 ~]# cat 0<<CATEOF
aaaaaa
bbbbbb
cccccccc
CATEOF
aaaaaa
bbbbbb
cccccccc
exec:使用指定的命令替换当前shell命令。
创建文件描述符:
以读写方式打开到www.baidu.com的80端口的tcp连接
exec 8<> /dev/tcp/www.baidu.com/80
echo -e “GET / HTTP/1.0\n” >& 8
cat <& 8
……
cd /proc/$$/fd #查看文件描述符
ll
echo -e “GET / HTTP/1.0\n” >&8 #重定向到8文件描述符
cat 0<& 8 从文件描述符8读取信息
1.4 bash变量
bash中变量的类型:
-本地变量
-局部变量
-位置变量
-特殊变量
-环境变量
1.4.1 本地变量
-当前shell所有
-生命周期跟当前shell一样
[root@bk1 ~]# a=99
[root@bk1 ~]# echo $a
99
[root@bk1 ~]# myfunc() {
> myvar=99
> echo $myvar
> }
[root@bk1 ~]# echo $myvar #访问不到
[root@bk1 ~]# myfunc #调用函数
[root@bk1 ~]# echo $myvar #可以访问到
99
[root@bk1 ~]# abc=sxt
[root@bk1 ~]# echo $abc
[root@bk1 ~]# echo "$abcisnothere"
[root@bk1 ~]# echo "${abc}isnothere"
1.4.2 局部变量:
-只能用于函数
-local var=100
[root@bk1 ~]# myfunc(){
> local myvar=1001
> echo $myvar
> }
[root@bk1 ~]# myfunc
1001
[root@bk1 ~]# echo $myvar
1.4.3 位置变量:
$1,
${11}
-脚本
-函数
[root@node1 ~]# myfunc1(){
> echo $1
> }
[root@node1 ~]# myfunc1
[root@node1 ~]# myfunc1 hello
hello
[root@node1 ~]# myfunc2(){
> echo $4
> }
[root@node1 ~]# myfunc2 a b c
[root@node1 ~]# myfunc2 a b c d
d
[root@node1 ~]# myfunc3(){
> echo $13
> }
[root@node1 ~]# myfunc3 1 2 3 4 5 6 7 8 9 10 11 12 13
13
[root@node1 ~]# myfunc3 0 1 2 3 4 5 6 7 8 9 10 11 12 13
03
[root@node1 ~]# myfunc3 a 1 2 3 4 5 6 7 8 9 10 11 12 13
a3
[root@node1 ~]# myfunc3() {
> echo ${13}
> }
[root@node1 ~]# myfunc3 a 1 2 3 4 5 6 7 8 9 10 11 12 13
12
[root@node1 ~]# myfunc3 1 2 3 4 5 6 7 8 9 10 11 12 13
13
1.4.4 特殊变量
-
$#:位置参数个数
-
$*:参数列表,双引号引用为一个字符串 ./a a b c d e #“a b c d e”
所有的参数作为一个字符串 5个参数作为一个字符串 -
$@:参数列表,双引号引用为单独的字符串 “a” “b” “c” “d” “e”
所有的参数作为单个的字符串 5个参数作为五个字符串 -
$$:当前shell的PID:接收者
-
$BASHPID:真实的值
-
$?:上一个命令的退出状态
0:成功
其他:失败
[root@node1 ~]# func() {
> echo $*
> }
[root@node1 ~]# func 1 2 a b d "hell owlrld"
1 2 a b d hell owlrld
[root@node1 ~]# func() {
> echo $@
> }
[root@node1 ~]# func 1 2 a b d "hell owlrld"
1 2 a b d hell owlrld
$$和$BASHPID的区别
$$
在哪个进程中执行命令,该值就是哪个进程的PID
$BASHPID
就是当前进程的PID真实值
$$
意味着该脚本文件下运行的进程ID。对于任何给定的脚本,在运行时,它会只有一个“主”进程ID。不管你有多少子shell调用,$$总是返回与脚本关联的第一个进程ID。 BASHPID会告诉你的bash的当前实例的进程ID,因此在子shell会跟调用它的“顶级”的bash不同。
二、数组:sxt=(a b c) #数组
Bash提供了一维数组变量。任何变量都可以作为一个数组;内建命令 declare 可以显式地定义数组。数组的大小没有上限,也没有限制在连续对成员引用和赋值时有什么要求。数组以整数为下标,从0开始。
如果变量赋值时使用语法 name[subscript]=value,那么就会自动创建数组。 subscript 被当作一个算术表达式,结果必须是==大于等于 0 ==的值。
数组赋值可以使用复合赋值的方式,形式是== name=(value1 … valuen)==,这里每个 value 的形式都是[subscript]=string。如sxt[3]=d
,string 必须出现。如果出现了可选的括号和下标,将为这个下标赋值,否则被 赋值的元素的下标是语句中上一次赋值的下标加一。下标从 0 开始。这个语法也被内建命令 declare 所接受。单独的数组元素可以用上面介绍的语法 name[subscript]=value 来赋值。
数组的任何元素都可以用${name[subscript]}
来引用。,如echo ${sxt[1]} # 取某一个元素
,花括号是必须的,以避免和路径扩展冲突。如果subscript 是 @ 或是 *,它扩展为 name 的所有成员。这两种下标只有在双引号中才不同。在双引号中,${name[*]}
扩展为一个词,由所有数组成员的值组成,用特殊变量 IFS 的 第 一 个 字 符 分 隔;${name[@]}
将 name 的每个成员扩展为一个词。如果数组没有成员,${name[@]}
扩展为空串。这种不同类似于特殊参数 * 和 @ 的扩展 (参见上面的 Special Parameters 段落)。${#name[subscript]}
扩展为${name[subscript]}
的长度。如果 subscript 是 * 或者是 @,扩展结果是数组中元素的个数。引用没有下标数组变量等价于引用元素 0。
内建命令 unset 用于销毁数组。unset name[subscript] 将销毁下标是 subscript 的 元 素 。 unset name, 这里 name 是一个数组,或者 unset name[subscript], 这里 subscript 是 * 或者是 @,将销毁整个数组。
内建命令 declare, local, 和 readonly 都能接受 -a 选项,从而指定一个数组。内建命令 read 可 以接受 -a 选项,从标准输入读入一列词来为数组赋值。内建命令 set 和 declare 使用一种可以重用为输入的格式来显示数组元素。
[root@bk1 ~]# sxt=(a b c) #数组
[root@bk1 ~]# echo $sxt #默认取第一个元素
a
[root@bk1 ~]# echo ${sxt[1]} # 取某一个元素
b
[root@bk1 ~]# echo ${sxt[*]}
a b c
[root@bk1 ~]# echo ${sxt[@]}
a b c
[root@bk1 ~]# echo $sxt[1] # 错误的写法
管道
[root@bk1 ~]# a=9
[root@bk1 ~]# echo $a
9
[root@bk1 ~]# b=22 | echo ok
ok
[root@bk1 ~]# echo $b #访问不到子进程的数据
父进程修改值不会影响子进程的变化,子进程值的修改也不会影响父进程的值。
fork子进程,写时复制 copy on write
管道两边的命令在当前shell的两个子进程中执行。
[root@bk1 ~]# vi mysh.sh
#!/bin/bash
echo $a
echo "---------"
[root@bk1 ~]# chmod +x mysh.sh
[root@bk1 ~]# ./mysh.sh
[root@bk1 ~]# export a #将a导出为环境变量,子进程就可以访问了
[root@bk1 ~]# ./mysh.sh #可以打印出a的值
子进程睡20s,在此期间修改环境变量的值,查看export是导出还是共享
[root@bk1 ~]# vi mysh.sh
echo $a
echo "----------"
sleep 20
echo $a
[root@bk1 ~]# a=110 #主进程设置值
[root@bk1 ~]# export a #导出变量
[root@bk1 ~]# ./mysh.sh & #后台执行子进程
[root@bk1 ~]# a=220 #主进程修改值
[root@bk1 ~]# export a #导出变量
子进程不能打印220,而是打印110
三、引用
- 双引号:弱引用,参数扩展
- 单引号:强引用,不可嵌套
- 花括号扩展不能被引用
- 命令执行前删除引用
[root@node1 ~]# a=99
[root@node1 ~]# echo "$a" # 99双引号引用,弱引用
[root@node1 ~]# echo "\"$a\"" #"99"
[root@node1 ~]# echo '$a' #$a 单引号引用,强引用
花括号扩展,创建adir,bdir,cdir三个目录
mkdir ./{a,b,c}dir
花括号扩展,拷贝/etc/profile以及/etc/init.d/network到当前目录
cp /etc/{profile,init.d/network} ./ #把etc/profile和etc/init.d/network拷贝到当前目录
-
命令替换:
命令替换允许我们将shell命令的输出赋值给变量。它是脚本编程中的一个主要部分。
命令替换会创建子shell进程来运行相应的命令。子shell是由运行该脚本的shell所创建出来的一个独立的子进程。由该子进程执行的命令无法使用脚本中所创建的变量。 -
反引号:
ls -l
-
$(ls -l /) 可以嵌套
反引号提升扩展优先级,先执行反引号的内容,再执行其他的。
错误
[root@node1 ~]# myvar=echo "hello"
[root@node1 ~]# myvar=`echo "hello"`
命令替换的嵌套
[root@node1 ~]# myvar=$(echo $(echo "hello world"))
[root@node1 ~]# myvar=$(echo "hello world")
[root@node1 ~]# myvar="hello world"
四、逻辑判断:
退出状态:echo $?
- command1 && command2
如果command1退出状态是0,则执行command2 - command1 || command2
如果command1的退出状态不是0,则执行command2
[root@node1 ~]# [ -d /hello ]
[root@node1 ~]# test -d /hello || echo "文件夹/hello不存在"
[root@node1 ~]# test -d /bin && echo "文件夹/bin存在"
[root@node1 ~]# test -f profile && rm -f profile && touch profile
[root@node1 ~]# ls / && echo ok
[root@node1 ~]# ls / || echo ok
五、表达式
5.1 算术表达式:
-
let 算数运算表达式
let C=$A+$B !!!
-
$[算术表达式]
C=$[$A+$B]
-
$((算术表达式))!!!
C=$((A+B))
-
expr算术表达式
表达式中各操作数及运算符之间要有空格,同时要使用命令引用
C=expr $A + $B
[root@node1 ~]# a=1
[root@node1 ~]# b=2
[root@node1 ~]# let c=$a+$b
[root@node1 ~]# echo $c
[root@node1 ~]# d=$((a+b))
[root@node1 ~]# echo $d
[root@node1 ~]# ((a++))
[root@node1 ~]# echo $a
前置++表示先自身++之后再参与计算
后置++表示先计算,再自身++
[root@node1 ~]# c=$((a+b)) && echo $c
4
[root@node1 ~]# c=$((a--+b)) && echo $c
4
[root@node1 ~]# c=$((a--+b)) && echo $c
3
[root@node1 ~]# echo $a
0
[root@node1 ~]# echo $b
2
[root@node1 ~]# c=$((--a+b)) && echo $c
1
[root@node1 ~]# c=$((a--+b)) && echo $c
1
[root@node1 ~]# echo $a $b
-2 2
[root@node1 shdemo]# e=$((a*b)) ?????
5.2 条件表达式
- [ 表达式 ]
- test 表达式
- [[ 表达式 ]]
help test
[root@node1 shdemo]# test 3 -gt 2
[root@node1 shdemo]# echo $?
[root@node1 shdemo]# test 3 -gt 8
[root@node1 shdemo]# echo $?
[root@node1 shdemo]# test 3 -gt 2 && echo ok
等价于
[root@node1 shdemo]# [ 3 -gt 2 ] && echo ok
[root@node1 shdemo]# test 3 -gt 8 && echo ok
等价于
[root@node1 shdemo]# [ 3 -gt 8 ] && echo ok
练习题:
-添加用户 useradd <username>
-用户密码同用户名 passwd --stdin <username> 0<<<"username"
-静默运行脚本
-避免捕获用户接口
-程序自定义输出 echo "user added!"
[root@bk1 ~]# vi mysh.sh
#! /bin/bash
useradd $1 接收第一个参数作为用户名
使用管道将用户输入的第一个参数传递给passwd,passwd命令指定选项--stdin就不会捕获用户接口了,
同时将passwd的标准输出重定向到/dev/null(数据黑洞)
passwd --stdin $1 >& /dev/null
echo "user added!"
问题:
如果用户没有输入参数或参数个数不对怎么办?
[root@bk1 ~]# vi mysh.sh
#!/bin/bash
[ ! $# -eq 1 ] && echo "Usage: ./$0 arg1" && exit 2
useradd $1
passwd --stdin $1 >& /dev/null
echo "user added"
[root@bk1 ~]# bash mysh.sh hello 执行
[root@bk1 ~]# id hello 查看hello用户是否添加成功
[root@bk1 ~]# userdel -rf hello 如果添加成功,就删除该用户,以继续后面的测试
问题:
如果要添加的用户已经存在怎么办?
[root@bk1 ~]# vi adduser.sh
#!/bin/bash
[ ! $# -eq 1 ] && echo "Usage: ./$0 arg1" && exit 2
#使用id命令检查,看返回值是否为0,如果是表示用户存在,提示并退出
id $1 &> /dev/null && echo "user exists!" && exit 3
useradd $1
passwd --stdin $1 >& /dev/null
echo "user added"
[root@bk1 ~]# chmod 700 adduser.sh
[root@bk1 ~]# ./adduser.sh user1
六、分支与循环
6.1 if
-
单分支结构
if [ 条件判断 ]
then //命令
fi # 将if反过来写,就成为fi 结束if语句 -
双分支结构
if [ 条件1 ];then
条件1成立执行,指令集1
else
条件1不成执行指令集2;
fi -
多分支结构
if [ 条件1 ];then
条件1成立,执行指令集1
elif [ 条件2 ];then
条件2成立,执行指令集2
else
条件都不成立,执行指令集3
fi
使用[ 条件表达式]命令判断
if [ 3 -gt 2 ]; then
echo ok
fi
if [ 3 -gt 8 ];
then echo ok;
fi
[root@node1 ~]# cat sh01.sh
#!/bin/bash
a=20
if [ $a -gt $1 ];
then
echo "你输入的数字太小"
elif [ $a -eq $1 ];
then
echo "恭喜哈,数字相等"
else
echo "你输入的数字太大"
fi
6.2 case
case $变量名称 in
“值1")
程序段1
;;
“值2")
程序段2
;;
*)
exit 1
;;
esac
案例:判断用户输入的是哪个数,1-7显示输入的数字,1显示 Mon,2 :Tue,3:Wed,4:Thu,5:Fir,6-7:weekend,其它值的时候,提示:please input [1,7],该如何实现?
[root@node1 ~]# cat sh02.sh
#!/bin/bash
read -p "please input a number[1,7]:" num
case $num in
1)
echo "Mon"
;;
2)
echo "Tue"
;;
3)
echo "Wed"
;;
4)
echo "Thu"
;;
5)
echo "Fir"
;;
[6-7])
echo "weekend"
;;
*)
echo "please input [1,7]"
;;
esac