自学shell的笔记

时间:2023-02-11 17:00:03

shell 理论:

第一章 了解shell用法、变量、字符、特殊字符、自增的用法

shell 变量

程序运行时候,内存空间的某些值是变化的,这个内存空间就理解为变量。

变量的类型

自定义变量(局部或者普通的变量)
#!/bin/bash
ip=1.1.1.1
ping -c1 $ip &>/dev/null

环境变量(export使用内部命令)
在执行shell进程时,建立一个子进程shell
#!/bin/bash
ip=1.1.1.1
ping -c1 $ip &>/dev/null
if [ $? -eq 0 ];then
echo "this is ok"
else
echo "this is down"
fi

位置变量(用于在命令行、函数或脚本中传递参数,其变量名不用自己定义,起作用也是固定的)
$0 自己的本身
$1~$9 里面的数值
$10 需要用{}括起来

vi test.sh
echo $1 $2

./test.sh aa bb

预定义变量
$0 脚本名
$*\$@ 所有的参数
$# 参数的个数
$$ 当前进程的PID
$! 上一个后台进程的PID
$? 上一个命令的返回值,0表示成功

NR==FNR

[root@tyjs09 ~]# cat a
张三|000001
李四|000002
[root@tyjs09 ~]# cat b
000001|10
000001|20
000002|30
000002|15
[root@tyjs09 ~]# awk -F'|' 'NR==FNR{a[$2]=$0;next}{print a[$1] FS $2}' a b
张三|000001|10
张三|000001|20
李四|000002|30
李四|000002|15

解释:
NR会将2个文件是为一个整体进行行计数,FNR会将每个文件是为一个整体进行计数,例如:
NR===>>123,456
FNR===>>123,123
本例中,当NR==FNR是为真,反之为假;为真时运行第一个动作{},不为真时就跳过第一个{},执行第二个{}。
$0表示整行,$1表示第一列,$2表示第二列
代码解说:
当为真时,执行第一个文件a,a的文件内容如下你:
张三|000001
李四|000002

转义符

\\   反斜杠
\a 警报,响铃
\b 退格(删除键)
\f 换页(FF),将当前位置移到下页开头
\n 换行
\r 回车
\t 水平制表符(tab键)
\v 垂直制表符

从键盘中读入赋值

#!/bin/bash
#ip=www.baidu.com
read -p "input ip:" ip 标准的输入读取单行数据
ping -c2 $ip &> /dev/null
if [ $? == 0 ];
then
echo "this is $ip ok "
else
echo "this is ip down"
fi

使用命令行参数

使用命令行参数 直接在脚本文件加上对应的数值即可
cat test.sh
echo $1 $2
sh test.sh 1 2
1 2

利用命令的输出结果来赋值

这种脚本开发时很常见,如每天打包网站的站点目录程序,生成不同的文件名
cmd=`date +%F`
[root@192 ~]# echo $cmd
2021-07-23

从文件中读入数据赋值

#!/bin/bash
ls *.sh > 1.txt
while read line
do
echo $line
done<1.txt

单引号 把数值原样输出

双引号 把定义的数据解析出来进行输出

反撇号 以命令的方式进行输出

shell变量的运算

变量值得默认类型为 字符串 不能直接参与运算 如果需要对shell变量进行运算,需要使用特殊方法。shell中用于整数运算的方法有expr、(())和$[] shell可以对小数进行运算。

exper数值运算

unm1=10
unm2=20
运算
expr $unm1 + $unm2
30

expr 2 \* 2 (必须加反撇号转义,不然shell会认为*号)
4
“(())”或“[]”数值运算
[root@192 ~]# s1um=$[$num1 + $num2]
[root@192 ~]# sum=$(($num1+$num2))
[root@192 ~]# echo $s1um
30
[root@192 ~]# echo $sum
30

let 数值运算命令

let数值符号可以直接进行计算,并不带显示功能。
let语法
let 赋值表达式,其功能等同于“((赋值表达式))”

案例
a=10
let sum=a+10
echo $sum

小数运算

bc是linux下的计算机,它可以用于命令进行小数计算(但是不显示小数点后面的数值),用于交互和非交互,用的少、效率低

echo "2*4" | bc

shell变量的删除、替代、替换

shell提供了一下直接对变量进行操作的符号。这些符号可以把变量中的部分内容进行删除、替换、替代,这些简单的修改变量,可以减少代码的行数并提高可读性。

1、shell变量的删除

${变量名#关键字符} 从头开始匹配关键字符,符合的数据 最短删除
${变量名##关键字符} 从头开始匹配关键字符,符合的数据 最长删除
${变量名%关键字符} 从尾部开始匹配关键字符,符合的数据 最短删除
${变量名%%关键字符} 从尾部开始匹配关键字符,符合的数据 最长删除

案例:
[root@192 ~]# echo ${file##*/}
my.file.txt
[root@192 ~]# echo ${file#*/}
dir1/dir2/dir3/my.file.txt
[root@192 ~]# echo ${file%%/*}

[root@192 ~]# echo ${file%/*}
/dir1/dir2/dir3

2、shell变量的替换
判断某个变量存不存在,存在继续使用,不存在;定义一个常用的配置

${变量名/旧字符串/新字符串} 如何含有旧字符串,则第一个旧字符串会被新字符串替换掉
${变量名//旧字符串/新字符串} 如何含有旧字符串,则全部旧字符串会被新字符串替换掉

案例:

[root@192 ~]# var=www.baidu.com
[root@192 ~]# echo ${var/baidu/zjp1}
www.zjp1.com
[root@192 ~]# var1=www.beijing.com
[root@192 ~]# echo ${var1//n/N}
www.beijiNg.com

shell变量的替代

给变量设置默认值,如果用户没有输入变量,则shell内核会调用变量的默认值(预先设置的变量)

案例:
unset port 取消变量值
echo ${port-3306} 此变量没有被赋值
port=3307
echo ${port-3306} 此变量已经被赋值不会使用3306

shell变量的自增

自增运算符
i++ 先赋值再自加
++i 先增加在赋值

案例:
对变量值得影响
[root@192 ~]# i=1
[root@192 ~]# let i++
[root@192 ~]# echo $i
2
[root@192 ~]# j=1
[root@192 ~]# let ++j
[root@192 ~]# echo $j
2

查看查看到 自增运算符 对变量值得影响不管是i++还是++i 其结果都是一样的

案例:
对表达式得影响
[root@192 ~]# i=1
[root@192 ~]# j=1
[root@192 ~]# let x=i++
[root@192 ~]# let y=++j
[root@192 ~]# echo $i
2
[root@192 ~]# echo $j
2
[root@192 ~]# echo $x
1
[root@192 ~]# echo $y
2

以上结果看出 i++ 和 ++i 表达的结果不一样
i++ 先赋值在增加
++i 先增加在赋值

shell变量中的特殊符号

灵活运用特殊符号  非常有用。  
例如 :
# 代表程序开头注释
; 分隔符,分割同上一行上两个或者两个以上的命令
\ 转义符 把特殊符号转为普通符号
案例
# 表示行首为#表示注释 注意!!(#!是个例外)
# network information
注释本行也可以#放在后面,但是#左右两边都需要有空格
network information #

注意!! 命令是不能放在同一行注释后面的,他注释无法结束,同一行中后边的代码就无法生效,只能执行下一行命令

当然 再echo中转义的#是不能当作注释符号的,同样,#也可以出现在特定的参数替换结构中,或出现在数字常量表达式中。

案例
[root@192 ~]# echo "The # here does not begin comment. "
The # here does not begin comment.
[root@192 ~]# echo 'The # here does not begin comment. '
The # here does not begin comment.
[root@192 ~]# echo The \# here does not begin comment.
The # here does not begin comment.
[root@192 ~]# echo ${PATH#*:}
/usr/local/sbin:/usr/bin:/usr/sbin:/bin:/sbin:/root/bin
[root@192 ~]# echo ${PATH#:}
/usr/local/bin:/usr/local/sbin:/usr/bin:/usr/sbin:/bin:/sbin:/root/bin


; 再同一行中分隔两个或两个以上的命令
echo hello;echo there

当然“;”也适用于循环语句,代码如下:
#!/bin/bash
filename=xx.txt
if [ -x "$filename" ];then
echo "file $filename exists.";cp $filename $filename.bak
else
echo "file $filename not found.";touch $filename
fi

;;用于终止case
#!/bin/bash
case "$variable" in
abc)
echo "\$variable = abc"
;;
zjp)
echo "\$variable = zjp"
;;
esac

。 等价于source 是bash内建命令
。也可以作为文件名字一部分 。放在文件名的开头作为隐藏文件

空命令“:”和true命令相同
:
echo $?
0

在while死循环和if/then中也可以使用这个

案例:
while:
do
operation1
operation2
done
同等于
while true
do
operation1
operation2
done

printf格式化输出文本颜色
printf 和 echo 是相同的的 都是输出文本信息

案例
printf "\e[40;37m %s\n \e[0m" "hello world!";
echo -e "\e[40;37m hello world! \e[0m";
具体连接

​(https://blog.csdn.net/abcnull/article/details/106393431)::​

反引号命令被调用的时候可以使用
: $[username=`whoami`]
: 等同于true
特殊符号
()     在子shell执行
(()) 数值比较、运算、支持正则 如((i=1;i<3;i++)),((command1 && command2))
$() 命令替换,如=>`command`
$(()) 支持运算,如$((1+2))
{} 集合,可以将命令与字符串隔开,如${num}%
${} 变量的引用
[] 文件测试、数值比较、文件比较、字符串比较,如[ -a ]且,[ -o ] 或
[[]] 增加了对正则的支持,如[[=~]] 包含,[||]或,[[&&]]且
$[] 支持变量运算,如[2**2]=>2^2=>$[var1**var2]
#./1.sh 需要执行权限,在子shell中执行
#bash.sh 不需要执行权限,在shell中执行
#.1.sh 不需要执行权限,在当前shell中执行
#source 1.sh 不需要执行权限,在当前shell中执行
#sh -n 1.sh 仅调试syntax error
sh -vx 1.sh 已调试的方式执行,查询整个执行过程

第二章 掌握shell条件(文件、整数、字符串)测试 if 、case、逻辑运算符用法

文件测试
通常使用test命令进行条件测试,语法形式为“test <测试表达式>”。 利用test <测试表达式> 之间至少有一个空格。

案例:test -f xx.txt && echo true ||echo false
test -f 参数判断xx.txt 是否为普通文件
如果xx.txt存在且是普通文件显示true,
相反xx.txt不存在或者不是普通文件为false

相同的用法

[ -f vari.sh ] && echo true ||echo false


文件测试操作符:
-d 测试是否为目录 (directory)
-a 测试目录或者文件名是否存在 (exist)
-f 测试是否为普通文件 (file)
-r 测试当前用户是否可读 (read)
-w 测试当前用户是否可写 (write)
-x 测试当前用户是否可执行 (excute)
整数测试
整数测试通常用于数值之间的运算,
语法格式为[ 整数1 操作符 整数2 ] 或者
test 整数1 操作符 整数2

整数测试操作符
-eq 等于 (equal)
-ne 不等于 (not equal)
-gt 大于 (greater than)
-lt 小于 (lesser than)
-le 小于或等于 (lesser or equal)
-ge 大于或等于 (greater or equal)

案例:
#!/bin/bash
ip=www.baidu.com
i=1
while [ $i -le 5 ]
do
ping -c1 $ip & >/dev/null
if [ $? -eq 0 ];then
echo "this is $ip up..."
fi
let i++
done

定义变量 while 循环 i 小于五 进行ping百度 再次判断 (结果按照成功为0不成功为1的显示)如果ping百度的结果等于0 显示 ping通 在把i赋值 加1 知道不小于5之后边不在执行下面操作

关系运算符
== 等于
!= 不等于
> 小于
< 大于
<= 小于等于
>= 大于等于

案例
[root@192 ~]# ((1>2));echo $?
1 判断1大于2吗
[root@192 ~]# ((1<2));echo $?
0 判断1小于2吗
[root@192 ~]# ((1==2));echo $?
1 判断1等于2吗
[root@192 ~]# ((2==2));echo $?
0 判断2等于2吗
字符串测试
字符串测试操作符的作用 比较字符串是否相同、测试字符串的长度是否为0

语法为
[ 字符串1 = 字符串2 ]、[ 字符串1 != 字符串2 ]或[ -z 字符串 ]

字符串运算符
-z 判断字符串长度是否为0
-n 判断字符串尝试是否为非0
!= 判断两个字符串是否不相等
= 判断两个字符串是否相等

案例

bash
if [ $user != root ]
then
echo "your not install"
exit
fi
yum -y install httpd
判断变量是否为root 如果是root则安装http 如果不是root 则显示你没有权限
逻辑运算符
使用逻辑运算符实现复杂的条件测试,逻辑运算符用于操作两个变量

语法:
[ 表达式1 ] 操作符 [ 表达式2 ] 或 命令1 操作符 命令2

逻辑运算符
-a 或 && 判断操作符两边均为真,结果为真,否则为‘假’,逻辑与
-o 或 || 判断操作符两边一边为真,结果为真,否则为‘假’,逻辑或
! 判断操作符两边均为假,结果为真,否则为‘假’,逻辑否

注意!!-a和-o 放在[]里面用 &&和||放在[]外面用

案例:
[ -f /etc/hosts -a -f /etc/services ] && echo 1 || echo 0
判断/etc/hosts和/etc/services 如果都为真反馈1 其中一个为假 则显示0

if判断

流程控制语句有三大类,分别为:顺序语句、分支语句(条件语句)、循环语句对于if条件语句可以简单理解为“如果……那么……”。if条件语句在实际生产工作中用的最频繁,也是最重要的语句。

if单分支
语法
if [ 条件表达式 ]
then
代码块
fi
或者
if [ 条件表达式 ];then
代码块
fi

每个if条件语句都已if开头,并带有then,最后以fi结尾
如果条件表达式为真 那么就执行代码块 如果条件表达式为假,那么就不执行代码块

案例
#!/bin/bash
if [ -f /etc/hosts ];then
echo "1"
fi

判断/etc/hosts是否为文件 如果是 那么返回1

if双分支
if单分支的大致结构:如果……那么…… 而if双分支的判断为:如果……那么……否则

语法
if [ 条件表达式 ]
then
代码块
else
代码块
fi

案例
#!/bin/bash
name=This is zhang
if [ -f "$name" ];then
echo "YES"
else
echo "NO"
fi

判断name是否为空 空则显示YES 非空空为显示NO

if多分支
if多分支的主体:如果……就……否则……就……否则
语法:
if [条件表达式];then
代码块
elif [条件表达式2];then
代码块
elif [条件表达式3];then
代码块
else
代码块
fi

实战案例
实例1:
安装apache
#!/bin/bash
#install apache
gateway=192.168.10.1
aip=www.1.com

ping -c1 $aip &>/dev/null
if [ $? -eq 0 ];then
yum -y install httpd
systemctl start httpd
systemctl enable httpd
firewall-cmd --add-service=http --permanent
firewall-cmd --add-service=https --permanent
firewall-cmd --reload
sed -ri '/^SELINUX=/cSELINUX=disabled' /etc/selinux/config
setenforce 0
elif ping c1 $gateway &>/dev/null
then
echo "ok dns"
else
echo "no dns"
if判断 识别ping能否ping通 也就是返回的值为0不 不为0则ping dns解析是否可以联网 如果返回的值的等于0则安装httpd

实例2:
配置不同版本的yum

case条件语句
常用于系统服务启动脚本等企业应用场景中
语法
case 变量值 in
条件表达式1)
代码块 1
;;
条件表达式2)
代码块2
;;
条件表达式3)
代码块3
;;
*)
无匹配后代码块

含义:在case中程序获取变量值,如果变量值 满足 1 则执行1 如果满足2 则执行2 如果满足3 则执行3 如果都不满足,则执行*)后面的代码块、
条件表达式匹配
* 任意字符
? 任意单个字符
[abc] abc其中之一
[a-n] 从a到n的任意字符
| 多重选择

case 实战案例
case删除用户判断
case语句结合read命令(读入用户输入的内容),与对应的变量名建立关联。如果用户输入正确的内容,返回一个结果,如果输入其他内容,返回另外一个结果

if 条件语句删除用户脚本
#!/bin/bash
read -p "your creat input username: " user
id $user &>/dev/null
if [ $? -ne 0 ];then
echo "no such user : $user"
exit 1
fi
read -p "Are you sure?[y/Y]:" action
if [ "$action" = "y" -o "$action" = "Y" -o "$action" = "yes" -o "$action" = "YES" ];then
userdel -r $user
echo " your $user del yes"
fi


解析含义:
打印用户输入的信息 把用户输入的用户名的id放到垃圾箱里 在进行if判单 如果返回的值不等于0(用户名!=系统用户名 或其他的)则告诉你没有 如果输入用户名为系统存在的用户名 则告诉你 是否要真的删除这个用户呢? 输入Y 再次进行判断 action 是不是等于 Y(哪怕满足任何一条都可以) 如果等于Y 删除用户 告诉你删除用户成功

用case判断删除用户案例
#!/bin/bash
read -p "Plase your username: " user
id $user &>/dev/null
if [ $? -ne 0 ];then
echo "not username"
exit 1
fi
read -p " Are you sure?[y/Y]: " action
case "$action" in
y|Y|yes|YES)
userdel -r $user
echo "sucess"
*)
echo "error"
esac

case实现系统工具箱的使用
系统工具箱是指:系统情况、如内存大小、磁盘负载、cpu大小

案例
case实现简单的系统工具箱
#!/bin/bash
menu() { #目前了解 menu()菜单
cat <<-EOF #<<-EOF 表示后续的语法或命令输入到子shell中,直到碰到EOF
#############################
# h.help #
# f.fdisk partition #
# d.filesystem mount #
# m.memory #
# u.system load #
# q.exit #
#############################
EOF
}
menu
while true #固定语法 等同于while
do
read -p " Plase input[h for help]:" action
clear 输入h或help 打印菜单并清屏
case ${action} in
h)
menu
;;
f)
fdisk -l
;;
d)
df -hT
;;
m) free -m
;;
u)
uptime
;;
q)
break
;;
"")
;;
*)
echo "error"
esac
done
echo "finish......."

shell手写jumpserver跳板机
案例如下:
#/bin/bash
web1=192.168.10.20

menu() {
cat <<-EOF
...............................
. 1.web1 .
. q.exit .
...............................
EOF
}
menu
read -p "Plase is jump ip: " ip
case ${ip} in
1)
ssh root@$web1
;;
q)
byb
;;
*)
echo 'Error'
;;
esac


密钥实现跳板机
登录服务器直接执行跳板机
echo "跳板机脚本" .bash_profile
语句:
#junmserver
trap " " HUP INT 这是Linux捕捉信息,意思是有这几个捕捉信号就什么都不做了。
web1=192.168.10.20
menu(){
cat <<-EOF
#########################
# 1.web1 #
# 2.exit jump #
#########################
EOF
}
menu
while true
do
read -p "Place input jump-pc ip: " num
case "$num" in
1)
ssh root@$web1
;;
2)
exit
;;
"")
true
;;
*)
echo "Error"
;;
esac
done


case实现多版本安装php 书上的脚本太罗嗦了
#!/bin/bash
#install php
install_php56() {
echo "install php 5.6...."
}
install_php70(){
echo "install php7.0...."
}
install_php71(){
echo "install php71....."

}
menu(){
clear
cat <<-EOF
############################
# 1 php5.6 #
# 2 php7.0 #
# 3 php7.1 #
# 4 help #
# q exit #
############################
EOF
}
menu
while true
do
#echo "############################"
#echo -e "\t php5.6"
#echo -e "\t php7.0"
#echo -e "\t php7.1"
#echo -e "\t help"
#echo -e "\t exit"
#echo "############################"

read -p "version[1-3]:" version
case "$version" in
1)
yum -y install php5.6
;;
2)
yum -y install php7.0
;;
3)
yum -y install php7.1
;;
q)
exit
;;
4)
menu
;;
*)
echo "Error"
;;
esac
done

第三章shell 循环 主要 for while until select

for 循环主要用于固定次数的循环,而不能用于守护进程及无限循环

语法:
for 变量名 in 取值列表
do
循环体
done

变量名会获取in后面的取值列表 每次仅取一个,然后进入循环(do和done之间的部分),遇到done时结束,然后变量名再次获取in后面的取值列表,以此类推。直到获取最后一个取值列表里面的数值

案例:
#!/bin/bash
for i in 1 2 3 4 5 6 ( 循环六次 )
do
echo "The Value is ZJP"
done

第二种语法
for 变量名 in 取值列表;do 循环体;done
for循环语句实战案例
实现测试主机状态
案例:
for x in {20,30}
do
{ ip=192.168.10.$x
ping -c1 -W1 $ip &>/dev/null
if [ $? -eq 0 ];
then
echo "$ip" | tee -a ip.txt 写入到 ip.txt里面
fi
}& 放到后台执行,可以并行计算
done
wait 等待子进程执行完毕,传回最后一个命令执行状态
echo "finish......"
for的蜜汁操纵之批量创建用户
#!/bin/bash
while :
do

#邀请用户输入用户名 密码
read -p "Place your prefix && passwd && num[zhangsan 123.com 1]:" prefix pass num

#在用户输入完密码后定义变量 打印出来
printf "user infomation
-----------------
user prefix:$prefix
user passwd:$pass
user num:$num
----------------
"
#打印完成 让用户确认是否创建这个用户
read -p "are your sure?[yes/no/quit]:" action
判断 输入yes 跳出循环 并执行下面 的for循环 输入 no 直接退出
if [ "$action" = "yes" ];then
break #跳出循环
elif
[ "$action" = 'no' ];then
exit
else
continue
fi
done
用户输入yes 跳出循环 告诉用户 启动创建用户
echo "create user start"
for i in `seq -w $num` for循环 变量 i in `seq -w 等长 $num这个数值`
do
user=$prefix$i #用户变量=$prefix(用户输入的用户名)在应用i变量 大致意思$prefix和$i组合($i判断num是否等长用户输入的id)
id $user &>/dev/null
if [ $? -eq 0 ]; #再次判断 i变量没有问题 返回的值为0并 -eq(等于)0 如果识别出有用户、有密码,告诉用户:your user $user already exists
then
echo "your user $user already exists"
else #如果没有用户、没有密码 则进行创建 用户 并添加密码 添加成功再次判断 这个是是否等于0 等于0 告诉用户 创建成功 for循环结束
useradd $user
echo "$pass" | passwd --stdin $user &>/dev/null
if [ $? -eq 0 ];then
echo "$user is ok"
fi
fi
done


sh -vx fotuser.sh 测试脚本
输入输出
Shell 输入/输出重定向
大多数 UNIX 系统命令从你的终端接受输入并将所产生的输出发送回到您的终端。一个命令通常从一个叫标准输入的地方读取输入,默认情况下,这恰好是你的终端。同样,一个命令通常将其输出写入到标准输出,默认情况下,这也是你的终端。
重定向命令列表如下:
命令
说明
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 之间的内容作为输入。
需要注意的是文件描述符 0 通常是标准输入(STDIN),1 是标准输出(STDOUT),2 是标准错误输出(STDERR)。

输出重定向
重定向一般通过在命令间插入特定的符号来实现。特别的,这些符号的语法如下所示:
command1 > file1
上面这个命令执行command1然后将输出的内容存入file1。
注意任何file1内的已经存在的内容将被新内容替代。如果要将新内容添加在文件末尾,请使用>>操作符。
实例
执行下面的 who 命令,它将命令的完整的输出重定向在用户文件中(users):
$ who > users
执行后,并没有在终端输出信息,这是因为输出已被从默认的标准输出设备(终端)重定向到指定的文件。
你可以使用 cat 命令查看文件内容:
$ cat users
_mbsetupuser console Oct 31 17:35
tianqixin console Oct 31 17:35
tianqixin ttys000 Dec 1 11:33
输出重定向会覆盖文件内容,请看下面的例子:
$ echo "菜鸟教程:www.runoob.com" > users
$ cat users
菜鸟教程:www.runoob.com
$
如果不希望文件内容被覆盖,可以使用 >> 追加到文件末尾,例如:
$ echo "菜鸟教程:www.runoob.com" >> users
$ cat users
菜鸟教程:www.runoob.com
菜鸟教程:www.runoob.com
$

输入重定向
和输出重定向一样,Unix 命令也可以从文件获取输入,语法为:
command1 < file1
这样,本来需要从键盘获取输入的命令会转移到文件读取内容。
注意:输出重定向是大于号(>),输入重定向是小于号(<)。
实例
接着以上实例,我们需要统计 users 文件的行数,执行以下命令:
$ wc -l users
2 users
也可以将输入重定向到 users 文件:
$ wc -l < users
2
注意:上面两个例子的结果不同:第一个例子,会输出文件名;第二个不会,因为它仅仅知道从标准输入读取内容。
command1 < infile > outfile
同时替换输入和输出,执行command1,从文件infile读取内容,然后将输出写入到outfile中。
重定向深入讲解
一般情况下,每个 Unix/Linux 命令运行时都会打开三个文件:
标准输入文件(stdin):stdin的文件描述符为0,Unix程序默认从stdin读取数据。
标准输出文件(stdout):stdout 的文件描述符为1,Unix程序默认向stdout输出数据。
标准错误文件(stderr):stderr的文件描述符为2,Unix程序会向stderr流中写入错误信息。
默认情况下,command > file 将 stdout 重定向到 file,command < file 将stdin 重定向到 file。
如果希望 stderr 重定向到 file,可以这样写:
$ command 2>file
如果希望 stderr 追加到 file 文件末尾,可以这样写:
$ command 2>>file
2 表示标准错误文件(stderr)。
如果希望将 stdout 和 stderr 合并后重定向到 file,可以这样写:
$ command > file 2>&1

或者

$ command >> file 2>&1
如果希望对 stdin 和 stdout 都重定向,可以这样写:
$ command < file1 >file2
command 命令将 stdin 重定向到 file1,将 stdout 重定向到 file2。

Here Document
Here Document 是 Shell 中的一种特殊的重定向方式,用来将输入重定向到一个交互式 Shell 脚本或程序。
它的基本的形式如下:
command << delimiter
document
delimiter
它的作用是将两个 delimiter 之间的内容(document) 作为输入传递给 command。
注意:
结尾的delimiter 一定要顶格写,前面不能有任何字符,后面也不能有任何字符,包括空格和 tab 缩进。
开始的delimiter前后的空格会被忽略掉。
实例
在命令行中通过 wc -l 命令计算 Here Document 的行数:
$ wc -l << EOF
欢迎来到
菜鸟教程
www.runoob.com
EOF
3 # 输出结果为 3 行
$
我们也可以将 Here Document 用在脚本中,例如:
#!/bin/bash
# author:菜鸟教程
# url:www.runoob.com

cat << EOF
欢迎来到
菜鸟教程
www.runoob.com
EOF
执行以上脚本,输出结果:
欢迎来到
菜鸟教程
www.runoob.com

/dev/null 文件
如果希望执行某个命令,但又不希望在屏幕上显示输出结果,那么可以将输出重定向到 /dev/null:
$ command > /dev/null
/dev/null 是一个特殊的文件,写入到它的内容都会被丢弃;如果尝试从该文件读取内容,那么什么也读不到。但是 /dev/null 文件非常有用,将命令的输出重定向到它,会起到"禁止输出"的效果。
如果希望屏蔽 stdout 和 stderr,可以这样写:
$ command > /dev/null 2>&1
注意:0 是标准输入(STDIN),1 是标准输出(STDOUT),2 是标准错误输出(STDERR)。
这里的 2 和 > 之间不可以有空格,2> 是一体的时候才表示错误输出。
for循环语句实现文件中批量文件创建(文件套脚本)
先创建文件夹,写入需要创建的用户
cat user1.txt
jinsir 111

编写脚本
cat create_user.sh
#!/bin/bash
if [ $# -eq 0 ]; 判断脚本是否有参数
then
echo "usage: `basename $0` file"
fi
if [ ! -f $1 ];then 判断是否为文件
echo "error file"
fi
#考虑一种特殊情况,如果变量是空行,其解决方法是重新定义分隔符
#希望for处理文件按回车分隔,而不是空格或者tab空格
#IFS 内部字段分隔符
#IFS=$'\n'
#注for循环中有空格怎么处理
#如下:
更改分隔符为换行,在for循环之前修改IFS=$'\n',这样循环就会以换行作为单词分界。以for循环语句实现文件中批量用户创建的脚本为列,考虑到用户文本中空行出现,需要自定义分隔符

#批量创建用户密码
for line in `cat $1`
do
if [ ${#line} -eq 0 ];then # #line 表示line的长度是否等于0
echo "Nothing to do"
continue
fi
user=`echo "$line" | awk '{print $1}'`
pass=$(echo "$line" | awk '{print $2}')
id $user >>/root/1.log 2>&1
if [ $? -eq 0 ];then
echo "user $user zaojiuchaungjian"
else
useradd $user
if [ $? -eq 0 ];then
echo "this is $user ok"
fi
echo "$pass" |passwd --stdin $user >> pass.log 2>&1
if [ $? -eq 0 ];then
echo "this is $pass ok"
fi
fi
done


#批量创建用户密码 思路
1、先判断脚本后面是否添加了参数 没有添加报错“usage: `basename $0` file” 如果添加了,
2、再次进行判断这个参数是否为文件 没有添加报错“error file”
3、如果是文件 则执行for循环 判断脚本后面跟的参数是否等于0 如果等于 继续执行,如果不等于则报错“Nothing to do”
4、如果等于 继续执行 筛选出 第一行 返回的值是否等于0 不等于0 进行用户创建 如果 等于0 则你的用户早就创建了
5、由于刚学习(添加密码的命令出错了 为了看到问题 我多添加了一if判断条件)
6、如果用户创建成功 那么返回的值为0 告诉我“this is $user ok” 再次执行添加用户密码 如果添加成功 那么返回的值还是0 告诉我this is $pass ok。
expect交互式公钥推送
expect的作用
他是用来实现自动交互的软件,无需人工干预 比如 ssh ftp 无需输入密码 直接自动执行

代码如下:

expect实现ssh非交互式登录
先安装expect
查看一下expect的变量位置
which expect #查看PATH环境变量的位置

vim expect.sh
#!/usr/bin/expect
spawn ssh root@192.168.10.30
expect{
"yes/no" {send "yes\r"; exp_continue}
"password:" {send "123.com\r" };
}
Interact 始终保留的终端远程界面

脚本含义:
定义expect解释器 spawn (进行交互式操作)执行 root@192.168.10.30;expect实现交互操作识别yes还是no send对交互操作的输出 \r 回车(键盘指令,确认输出;exp_continue 附加于某个 expect 判断项之后,可以使该项被匹配后,还能继续匹配该 expect 判断语句内的其他项。exp_continue 类似于控制语句中的 continue 语句
继续执行password 密码操作 然后执行完成 连接远程电脑 Intercat 保持终端页面不变。

注意:exp_continue [-continue_timer] 默认情况下 exp_continue 会重高超时时钟,-continue_timer 选项会阻止时钟重新计数(连续计数)

语法:
send 用于向进程发送字符串
expect 从进程接受字符串
spawn 启用新的进程
interact 允许用户交互
for 循环修改密码
前提需要向所有主机传输ssh密码(确保ssh直接连接过去了)
在企业需要写脚本或者expect人机交互执行过去
#!/bin/bash
read -p "please your enter passwd:" pass
for i in $(cat ip.txt)
do
{
ping -c6 $ip >>/root/ipok.txt 2>&1
if [ $? -eq 0 ];
then
ssh $ip "echo $pass | passwd --stdin root"
if [ $? -eq 0 ];
then
echo "$ip" >> ok_`date +%F +%M`.txt
else
echo "$ip" >> error_`date +%F +%M`.txt
fi
else
echo "$ip" >> /root/error.txt 2>&1
fi
}
done
wait
echo "finish...."

查询磁盘

#!/bin/bash
df=`df -hT|sed -n '7p'|awk '{print $6}'|awk -F '%' '{print $1}'`
if [ $df -ge 90 ];then
echo "宝贝,磁盘马上满了,想让我崩溃嘛,占用为: $df%"
if [ $? -eq 0 ];then
echo "`df -hT | sed -n '7p'`"
fi
else
echo "宝贝 你的磁盘没有满,放心使用,占用为: $df%"
if [ $? -eq 0 ];then
echo "`df -hT | sed -n '7p'`"
fi

fi

while循环和until循环

理论:
while循环主要适用于重复执行一组命令和语句,常用于守护进程或者持续运行的程序,其次循环的次数既可以固定,也可以不固定。

语法结构 until 和while 语法一样 只是把while 替换为until
while 条件测试
do
循环体
done

注意:
until与while用法相反

while记录正确的值并记录,遇到错误并结束
until记录错误的值并记录,遇到正确并结束。

循环实战案例

ping通主机
#!/bin/bash
ip=192.168.10.129
while ping -c1 $1 &>/dev/null
do
sleep 1
done
echo "$ip is ok "

while 和 for 和 unitl 对比

for循环写脚本
#!/bin/bash
for i in {2..254}
do
{
ip=192.168.10.$i
ping -c1 $ip &>>/root/1_ip.txt
if [ $? -eq 0 ];then
echo "this is $ip ok"
fi
}& 放到后台执行
done
wait 等待wait上面的执行完成 再去执行 wait下面的命令
echo "all fiish....."
while循环
#!/bin/bash
i=2
while [ $i -le 254 ]
do
{
ip=192.168.10.$i
ping -c1 $ip &>/dev/null
if [ $? -eq 0 ];then
echo "this is $ip ok"
fi
}&
let i++
done
wait
echo "all finish...."

shell实现并发控制

#!/bin/bash  
#start=`date + %s` #定义脚本的运行开始时间
for ((i+1;i<1000;i++))
do
{
ip=192.168.10.$i
ping -c1 $ip &>/dev/null
sleep 1 #sleep 用来模仿执行一条命令需要花费的时间
if [ $? -eq 0 ];then
echo "success $i"
else
echo "error $i"
fi
}&
# {}& 用{}把循环括起来,后面再加一个&符号,代表每次循环都把命令放入后台,一旦放入后台,就意味着{}里面的命令交给操作系统的一个线程处理,循环1000次,就有1000个&把任务放入后台,操作系统会并发1000个线程来处理这些任务
done
wait
#wait命令的意思是,等待wait上面的命令(放入后台)都执行完成,再往下执行。
#写wait是因为,一条命令一旦被放入后台后,这条任务就交给操作系统,shell脚本会继续往下运行(shell脚本里面一旦碰到&符号就只管把它前面的命令放入后台就算完成任务了,具体执行交给操作系统去做,脚本会往下执行)所以要在这个位置上加入wait,等待操作系统执行完成后台的命令。
end=`date +%s` #定义脚本运行结束时间。
echo "time: `expr $end-$start`"

管道实现shell的并发控制案例实战

#!/bin/bash
thread=5 #创建线程
tmp_fifofile=/tmp/$$.fifo #定义有名管道符
start_time=`date +%s` #定义运行时间
mkfifo $tmp_fifofile #创建有名管道
exec 8<> $tmp_fifofile #创建文件描述符,可读(<)可写(>)的方式关联管道文件
#这时候文件描述符8就有了有名管道文件的所有特性
#rm -rf $tmp_filofile #关联后的文件描述符拥有管道文件的所有特性,所以这时候管道文件可以删 除,留下文件描述符用即可

for i in `seq $thread` #seq 从某个整数计算到某个整数
do
echo >&8 #$8代表引用文件描述符8,这条命令台标网管到里面放入一个“令牌”
done

for i in {1..254}
do
read -u8 # 代表从管道中读取一个令牌
{
sleep 1 #sleep 1 用来模仿执行一条命令需要花费的时间
ip=192.168.10.$i
ping -c1 -w1 $ip &>/dev/null
if [ $? -eq 0 ];then
echo "$ip iis ok"
else
echo "$ip down"
fi
echo >&8 #代表这一次命令执行到最后,把令牌放回管道
}&
done
wait
stop_time=`date +%s`
echo "TIME:`expr $stop_time-$start_time`"
exec 8<&- 关闭文件描述符的读
exec 8>&- 关闭文件描述符的写

第四章 shell 数组

什么是数组

数组就是一种数据结构,是相同数据类型的元素按一定顺序排列的元素集合。数组实际上就是一连串类型相同的变量,这些变量用数组名命名,并用索引互相区分,使用数组时,可以通过索引来访问数组元素,如数组元素的赋值和取值。

数组的概念

数组中有限个相同类型的变量用一个名字命令,然后用编号区分它们。用于区分不同元素的编号成为数组下标,数组的元素有时也称为下标变量。

数组的类型

数组分为普通数组和关联数组。普通数组中的索引(下标)都是整数,关联数组的数组索引可以用任意的文本。关联数组使用之前需要声明,关联数组与普通数组最大的区别是,它是由特殊格式的键值对组成的。
普通数组
普通数组中:数组元素的索引(下标)从0开始编号,获取数组中的元素利用所用(下标)。索引(下标)可以是算数表达式,其结果一定是一个整数

普通数组定义。
books=(linux shell awk openstack docer) #定义普通数组,在python中成为列表
相当于
----------------------------------
|linux|shell|awk|openstack|docker|
----------------------------------
| 0 | 1 | 2 | 3 | 4 | # 索引(下标) 从0 开始编号
----------------------------------
关联数组
关联数组和普通数组有所不同的是,他的索引下标可以是任意的整数和字符串

info=([name]=tianyun [sex]=male [age]=36 [height]=170 [skill]=cloud)

----------------------------------
|tianyun|male|36 |170 |cloud|
----------------------------------
|name |sex |age|height |skill| #索引下标是任意字符串,不必为整数 编号
----------------------------------

定义数组的类型

在Linux中,数组分为普通数组和关联数组 
普通数组 用户定义的数组
关联数组 需要现声明在使用

声明数组的命令为:
普通的 declare -a array
关联的 declare -A array