shell
基本来说就是一组命令,按照顺序执行。 它是解释型的,意味着它不需要编译,所以shell是需要声明类型的。
命令 + 固定格式 + 基于语法 = shell
bash 补充和回顾
命令解释器 处于内核与用户之间
man type 内部命令 大概50多个
bash的特性
命令自动补全 --多用tab键
命令的历史记录
history
!$ --代表上条命令后面的值
!netstat --运行上一个运行过以netstat开头的命令
!100 --运行histroy里记录的第100号命令
ctrl+shift+r --可以用来匹配运行过的命令
别名功能 alias unalias
作业控制
管道
重定向
shell编程
/etc/profile
/etc/bashrc --这两个是全局的,针对所有用户
~/.bashrc
~/.profile --这两个是放在每个用户的家目录里,只针对它对应的用户
变量:
变量的用途:
1,简单的用途就是为了搜索方便
2,常用于脚本里,对经常使用的值使用变量,以免每次都要写这个值,使用变量就比较灵活
环境变量 --就是可以让子bash引用的变量
env --此命令可以查看环境变量
set --除了显示环境变量之外,还会显示其他的一些自定义变量
PS1='[\u@\h \W]\$ ' --默认值
\t 24小时格式时间
\H 完整的主机名
\v bash版本信息
locale --语言有关的变量
[root@li www]# locale
LANG=zh_CN.UTF-8 --主语言的环境
LC_CTYPE="zh_CN.UTF-8"
LC_NUMERIC="zh_CN.UTF-8"
LC_TIME="zh_CN.UTF-8"
LC_COLLATE="zh_CN.UTF-8"
LC_MONETARY="zh_CN.UTF-8"
LC_MESSAGES="zh_CN.UTF-8"
LC_PAPER="zh_CN.UTF-8"
LC_NAME="zh_CN.UTF-8"
LC_ADDRESS="zh_CN.UTF-8"
LC_TELEPHONE="zh_CN.UTF-8"
LC_MEASUREMENT="zh_CN.UTF-8"
LC_IDENTIFICATION="zh_CN.UTF-8"
LC_ALL= --语言环境的整体设置
vim /etc/sysconfig/i18n
[root@li ~]# export LANG=en
[root@li ~]# export LANG=zh --用时候直接用此命令设置临时的语言支持
利用export把自定义变量转化为环境变量
[root@dns ~]# a=1
[root@dns ~]# env |grep a=1
[root@dns ~]# set |grep a=1
a=1
[root@dns ~]# export a=1
[root@dns ~]# env |grep a=1
a=1
[root@li www]# a=1
[root@li www]# bash --进入到子bash
[root@li www]# echo $a --子bash里看不到此变量值
[root@li www]# exit --退到父bash
exit
[root@li www]# export a=1 --使用export命令转化为环境变量
[root@li www]# bash
[root@li www]# echo $a
1 --再在子bash里可以看到此变量值
变量的定义
[root@li ~]# a=3
[root@li ~]# echo $a
3
declare 或 typeset 定义变量
[root@li ~]# b=3+3
[root@li ~]# echo $b
3+3
declare
[root@li ~]# declare -i c=3+3
[root@li ~]# echo $c
6
变量定义的规则:
1,区分大小写,同名称但大小写不同的变量名是不同的变量
[root@li www]# a=2
[root@li www]# A=3
[root@li www]# echo $a
2
[root@li www]# echo $A
3
2,定义时的格式要注意,等号两边不能有空格,对于有空格的字符串做为赋值时,要用引号引起来
B="hello world"
B='hello world haha' --单引号和双引号在这里都可以,后赋值的会覆盖前面的赋值
--在脚本里注意引号的相互嵌套,要成对出现
3,单引号与双引号的区别,单引号内的变量或者特殊字符仅为一般字符,但双引号内的变量或者特殊字符可以保持它的变量特性
[root@li ~]# echo '$B'
$B
[root@li ~]# echo "$B"
hello world haha
4,变量名可以是字母或数字,但是不能以数字开头
[root@li ~]# c123=aaa
[root@li ~]# echo $c123
aaa
[root@li ~]# 123c=aaa
bash: 123c=aaa: command not found
5,变量赋值可以有多个 例如 echo $PATH ,以":"分隔
6,变量的获取方式: $变量名 ${变量名}
[root@li ~]# echo $a
3
[root@li ~]# echo ${a}
3
7,取消变量的命令 unset 变量名
[root@li ~]# unset a
[root@li ~]# echo $a
8,别的变量定义方式
rpm -qf `which mount`
ls /lib/modules/`uname -r`/ --执行符号的使用,执行符号是tab键上面的那个符号
[root@li /]# a=`which mount` --这样定义
[root@li /]# echo $a
/bin/mount
[root@li /]# a=$(uname -r)
[root@li /]# echo $a
2.6.18-164.el5
$( ) 等同于 执行符号 ` `,但是如果要嵌套使用,使用` `符号就不行,要用$()
[root@dns shell01]# a=$(rpm -qf `which mount`)
[root@dns shell01]# echo $a
util-linux-2.13-0.52.el5
[root@dns shell01]# a=$(rpm -ql $(rpm -qf `which mount`) |grep /bin/mount)
[root@dns shell01]# echo $a
/bin/mount
$(( )) 等同于 $[ ] --这两个是运算符号
vim 1.sh
#!/bin/bash
#第一个程序
echo "hello world" #注释
执行时,chmod 755 1.sh 给它执行权限,就可以./1.sh去执行
直接使用sh 1.sh 来执行,这种不需要执行权限。
sh -x 1.sh 执行,并查看执行过程,可以用于脚本排错
[root@li /]# read a --用read的方式定义变量,主要用于让用户去定义变量值
lalalalalaaalal
[root@li /]# echo $a
lalalalalaaalal
练习:要求用户来输入它的用户名和密码,并打印出来(输入时密码不可见,输入用户名的时间为10秒)
#/bin/bash
read -t 10 -p "输入你的用户名:" name
read -s -p "输入你的密码:" passwd
echo "$name' password is $passwd"
练习:写一个日志切割的脚本,每天凌晨12点01分mv /var/log/aaa.log,备份到/backup/年/月/年-月-前一天日期.aaa.log,然后再touch /var/log/aaa.log,要求用变量的形式
#/bin/bash
year=`date +%Y -d '-1 days'`
month=`date +%m -d '-1 days'`
day=`date +%d -d '-1 days'`
mkdir /backup/$year/$month/ -p
mv /var/log/aaa.log /backup/$year/$month/$year-$month-$day.aaa.log
touch /var/log/aaa.log
crontab -e
01 0 * * * sh /path/xxx.sh
引申一个小的面试题:
web服务器apache使用access.log日志来记录访问信息。
如果mv access.log access.log.bak后,并没有重启服务,再touch一个access.log
请问:新的请求信息,是写到哪个文件?
答案:不重启服务,还是写到access.log.bak里,因为inode不变
重启服务后,就会写到access.log里
所以上面的脚本在实际情况,最后一步touch 要改成服务reload,或者是kill -USR1 `cat /服务的pid文件`
练习:一个普通用户在tty文本模式骗取root密码的脚本
[root@li ~]# ifconfig eth0 | head -2 |tail -1
inet addr:2.2.2.34 Bcast:2.2.2.255 Mask:255.255.255.0
[root@li ~]# ifconfig eth0 | grep Bcast
inet addr:2.2.2.34 Bcast:2.2.2.255 Mask:255.255.255.0
[root@li ~]# ifconfig eth0 | grep Bcast |cut -d":" -f2 |cut -d" " -f1
2.2.2.34
[root@li ~]# echo 1; sleep 3 ;echo 2
[root@li ~]# while true
> do
> echo 1
> sleep 1
> done
while true
do
xxxxxx
done
如果只想循环3次,则
for i in 1 2 3
do
xxxxx
done
循环三次完,想再重头执行脚本
则在最后加sh $0,表示重头执行
#!/bin/bash
hostnamehead=`hostname| cut -d "." -f1`
clear
echo
echo -n "Red Hat Enterprise Linux Server release 5.4 (Tikanga)"
echo
echo -n "Kernel `uname -r` on an `uname -m`"
echo
echo
read -p "$hostnamehead login: " username
read -s -p "Password: " password
sleep 2
echo
echo "Login incorrect"
echo "$username's password is $password" >> /home/a/rootpassword
for i in 1 2 3
do
echo
read -p "login: " username
read -s -p "Password: " password
sleep 2
echo
echo "Login incorrect"
echo "$username's password is $password" >> /home/a/rootpassword
done
sh $0
=========================================================
--------------------------------------
echo $$返回程序的PID
echo $1 #代表脚本运行时接的第一个参数
echo $2 #代表脚本运行时接的第二个参数
echo $3 #代表脚本运行时接的第三个参数
echo $$ #程序运行的PID
echo $* #代表所有参数
echo $@ #也是代表所有参数
echo $? 执行成功则返回0 --常用于判断语句
失败刚返回非0
-----------------------------------------------------------------
重定向
1,标准输入(stdin);代码为0,使用< 或者 << --默认设备就是键盘
2,标准输出(stdout);代码为1,使用> 或者 >> 或1 > 或 1 >>--默认设备是屏幕
3, 错误输出(stderr);代码为2,使用 2> 或者 2>> --默认设备也是屏幕
标准输入 --> 命令 -->标准输出---> 设备/文件
|
|
错误输出
|
|
设备/文件
# /etc/init.d/sendmail restart
# mail root < 4.sh --把4.sh做为邮件正文发给本机的root
[a@li ~]$ find /home/ -name user1 --这样查找的结果中,有标准输出和错误输出,默认都是输出到屏幕
/home/user1 --这是标准输出
find: /home/user1: 权限不够
find: /home/std5: 权限不够
find: /home/aa: 权限不够 --这些是错误输出
[a@li ~]$ find /home/ -name user1 > /home/a/right.txt 2> /home/a/wrong.txt --这样把标准输出输到/home/a/right.txt文件,把错误输出输到/home/a/wrong.txt文件,屏幕上就不显示任何信息了
把标准输出和错误输出输到同一个文件:
[a@li ~]$ find /home/ -name user1 > /home/a/union.txt 2> /home/a/union.txt
[a@li ~]$ find /home/ -name user1 > /home/a/union2.txt 2>&1
[a@li ~]$ find /home/ -name a > /dev/null 2>&1 --/dev/null是脚本里经常要用到的一个设备,类似于黑洞,或者叫垃圾桶,也就是信息没了
双向重定向:tee
如果想要又要传给设备或者文件,又要显示到屏幕可以用此命令
[root@li shell01]# last |tee /root/last.txt --这样就是又把last显示的信息传到了/root/last.txt文件里,又显示到了屏幕
[root@li shell01]# cat > /root/cat3.txt <<EOF
> sdfsafsafsa
> sfwewqrwq
EOF
#!/bin/bash
#注意下面这两种区别
cat > /tmp/abc <<EOF
你好
hello
哈哈
呵呵
EOF
cat >> /tmp/abc <<EOF
你好
hello
哈哈
呵呵
EOF
ftp服务端
# yum install vsftpd* -y
# /etc/init.d/vsftpd restart
# useradd -d /share/110620 ule
# passwd ule --密码设为123
ftp客户端
# lftp 2.2.2.35 -u ule,123
lftp ule@2.2.2.35:~> mirror ule /notes
lftp ule@2.2.2.35:~> quit
练习:自动ftp下载我的笔记到你的/notes目录
#!/bin/bash
lftp 2.2.2.35 -u ule,123 <<EOF
mirror ule /notes
mirror pre /notes
EOF
练习:使用passwd命令修改你普通用户a的密码为123,要求执行脚本时没有信息显示到屏幕
#!/bin/bash
passwd a << EOF > /dev/null 2>&1
123
123
EOF
请问,ssh是否可以用这样的形式进行远程脚本操作?(有密码,不配置等效性的情况)
答案是不可以
可以使用expect自动应答脚本来做
===========================================
命令执行的判断顺序:
;
&&
||
[root@li shell01]# ./configure ; make ;make install
[root@li shell01]# ./configure && make && make install --这两个结果一样,都是前面执行成功,再执行后面的
[root@li shell01]# ls /test/ && touch /test/abc --这是代表前面执行成功则touch /test/abc
[root@li shell01]# ls /test/ || touch /test/abcd --与&&符号相反,前面执行失败才touch /test/abcd
当&&与||混用进行条件判断时,要注意逻辑上不要搞混
要先&&再||
--正确判断方法:
[root@li shell01]# ls /test/ && echo 'existed' || echo 'not existed'
existed
[root@li shell01]# ls /testsdafdg/ && echo 'existed' || echo 'not existed'
ls: /testsdafdg/: 没有那个文件或目录
not existed
--错误判断方法:
[root@li shell01]# ls /testfsdafas/ || echo 'existed' && echo 'not existed'
ls: /testfsdafas/: 没有那个文件或目录
existed
not existed
==================================================================
条件判断:
如果 xxxx ;
就xxx
否则
就xxx
结束
if [ ] ;then
command
fi
if [ ] ;then
command
else
command
fi
if [ ] ; then
command
elif [ ] ;then
command
else
command
fi
man test去查看,很多的参数都用来进行条件判断
与文件存在与否的判断
-e 是否存在
-f 是否为文件
-d 是否为目录
-S
-p
-c
-b
-L
文件权限相关的判断
-r 是否可读
-w
-x
-u 是否有suid
-g 是否sgid
-k 是否有t位
-s 是否为空白文件
两个文件的比较判断
-nt 比较file1是否比file2新
-ot 比较file1是否比file2旧
-ef 比较是否为同一个文件,或者用于判断硬连接,是否指向同一个inode
整数之间的判断
-eq 相等
-ne 不等
-gt 大于
-lt 小于
-ge 大于等于
-le 小于等于
字符串之间的判断
-z 是否为空字符串
-n 是否为非空字符串
string1 = string2 是否相等
string1 != string2 不等
多重条件判断
-a 两个条件同时满足,才为true
-o 两者满足其一,就为true
练习:read输入一个文件,判断它的读写执行权限
#!/bin/bash
read -p "输入一个你想要判断的文件:" file
[ -e $file ] && echo "此文件存在,继续判断权限" || exit 1
if [ -r $file ];then
echo "本用户对其可读"
else
echo "本用户对其不可读"
fi
if [ -w $file ];then
echo "本用户对其可写"
else
echo "本用户对其不可写"
fi
if [ -x $file ];then
echo "本用户对其可执行"
else
echo "本用户对其不可执行"
fi
练习: 判断一个文件是否为死链接
#!/bin/bash
read -p "输入一个文件:" file
[ -h $file -a ! -e $file ] && echo "文件是死链接" || echo "文件不是死链接"
练习:使用read让用户输入它的名字,性别(对性别进行判断),年龄(判断是否有18岁成年)
如:李四 男 15 ,则最后echo出 “李四小子,你好!"
李四 男 18 ,则最后echo出 “李四先生,你好!"
王五 女 16 ,则最后echo出 "王五×××,你好!"
王五 女 20 ,则最后echo出 "王五女士,你好!"
#!/bin/bash
read -p "请输入你的姓名:" name
read -n 1 -p "请输入你的性别(M或W):" sex
echo
read -p "请输入你的年龄:" age
if [ $sex = M -a $age -ge 18 ];then
echo "$name先生,你好!"
fi
if [ $sex = M -a $age -lt 18 ];then
echo "$name小子,你好!"
fi
if [ $sex = W -a $age -ge 18 ];then
echo "$name女士,你好!"
fi
if [ $sex = W -a $age -lt 18 ];then
echo "$name×××,你好!"
fi
练习:用read输入一个年份,判断是否为闰年 (能被4整除,但不能被100整除就是闰年,能被400整除也是闰年)
echo -n "input the year:"
read year
if [ $(($year%4)) -eq 0 -a $(($year%100)) -ne 0 ];then
echo 'this is a leap year'
elif [ $(($year%400)) -eq 0 ]
then
echo 'this is a leap year'
else
echo 'this is not a leap year'
fi
---------------------------------------------------------
echo -n "input the year:"
read year
a=`echo $(($year%4==0 && $year%100!=0 || $year%400==0))`
if [ $a -eq 1 ];then
echo 'this is a leap year'
else
echo 'this is not a leap year'
fi
------------------------------------------------------
管道:
grep cut
[root@li shell01]# last |tee /root/last.txt |cut -d " " -f 1
cut命令 -d "分隔符" -f 第几列
[root@li shell01]# ifconfig eth0 |grep Bcast |cut -d " " -f2 --发现这样截取,没有值,因为这里的是多空格,而-d " "指的是单空格,所以要换一种写法
[root@li shell]# ifconfig eth0 |grep Mask |cut -d " " -f12 |cut -d ":" -f2
10.1.1.35
[root@li shell01]# ifconfig eth0 |grep Bcast |cut -d ":" -f2 |cut -d " " -f1 --可以这样截取
10.1.1.35
--cut多用于截取日志,但对于多空格的情况下就不太好用了,就要借助于其它工具(AWK)
排序统计相关的:wc sort uniq
wc
-l 显示行数
-w 显示单词数
-m 显示字符数
默认不加参数,就是相当于上面三个参数都加
[root@li shell01]# cat /etc/passwd |wc -l
79
[root@li shell01]# cat /etc/passwd |wc -w
106
[root@li shell01]# cat /etc/passwd |wc -m
3374
[root@li shell01]# cat /etc/passwd |wc
79 106 3374
sort 排序命令
[root@li shell01]# cat /etc/passwd |sort --默认以开头字母排序
-r 反向排序
-n 以数字来排
-f 大小写不敏感
-t 分隔符
-k 接数字代表第几列
[root@li shell01]# cat /etc/passwd |sort -t ":" -k 3 --以UID来排序,但是它只会以数字的第一个数字来排也就是说 2要排到14的后面
[root@li shell01]# cat /etc/passwd |sort -t ":" -k 3 -n --多加一个-n参数,才会以整个的数字大小来排序
uniq 唯一命令
默认是以连续的重复值内只取一个
[root@li shell01]# cat /etc/passwd |cut -d ":" -f7 |uniq |grep bash
/bin/bash
/bin/bash
/bin/bash
/bin/bash
[root@li shell01]# cat /etc/passwd |cut -d ":" -f7 |grep bash |uniq
/bin/bash
--在管道用得多的情况下,命令的顺序会造成很大的结果不同
[root@li ~]# cat /etc/passwd | cut -d":" -f7|sort |uniq -c |sort -t" " -k2 -n
1
1 /bin/sh
1 /bin/sync
1 /sbin/halt
1 /sbin/shutdown
23 /bin/bash
33 /sbin/nologin
# cat access_log |cut -d" " -f1 |sort |uniq -c | sort -t " " -k2 -n
===========================================================
题目:
1,用read输入一个IP,判断是否能ping通这个IP
read -p "input a ip:" ip
ping -c 2 $ip > /dev/null 2>&1
if [ $? -eq 0 ];then
echo "$ip 可以ping通"
else
echo "$ip 不可以ping通"
fi
2,使用脚本实现对/var/log/ccc.log的日志轮转功能(也就是ccc.log.4没有了,ccc.log.3成了ccc.log.4,.........ccc.log成了ccc.log.1,再创建一个新的ccc.log),要求最多保留4个副本,每个星期一的凌晨0点01分自动运行
if [ -e /var/log/ccc.log.4 ];then
rm /var/log/ccc.log.4 -rf
fi
if [ -e /var/log/ccc.log.3 ];then
cat /var/log/ccc.log.3 > /var/log/ccc.log.4
fi
if [ -e /var/log/ccc.log.2 ];then
cat /var/log/ccc.log.2 > /var/log/ccc.log.3
fi
if [ -e /var/log/ccc.log.1 ];then
cat /var/log/ccc.log.1 > /var/log/ccc.log.2
fi
if [ -e /var/log/ccc.log ];then
cat /var/log/ccc.log > /var/log/ccc.log.1
echo > /var/log/ccc.log
fi
--考虑到inode不变的问题,上面做的是追加,那么记录ccc.log的这个服务就不用去reload了
crontab -e
01 0 * * 1 sh /xxx.sh
3,假如我现在要写一个自动安装软件的脚本,默认安装到根目录下,如果安装此软件最少需要3G大小,请写一个判断根目录空间是否足够安装的脚本
=================================================
--一个整个项目脚本的思路和过程
配置yum
yum install xxxx -y > /dev/null 2>&1
[ &? -ne 0 ] && echo "安装有问题,请检查重试"&& exit 1
read -p "你希望你xxx软件,安装路径为:"
.....
然后可能就是判断安装路径是否足够空间
..............
===================================================
a=`df -k |grep /$ | cut -d" " -f18`
if [ $a -lt 3000000 ];then
echo "你的空间不足,请释放空间后,再运行此脚本"
exit 1
else
echo "空间足够,继续安装"
fi
4,写两个监控apache是否存活的脚本
一个为服务器监控:表示脚本是跑在apache服务器上
一个为客户端监控:表示脚本是跑在另一台机器上(提示:wget一个小文件,判断返回值)
负载均衡
调度节点
web 1 web 2 web 3
#!/bin/bash
lsof -i:80 > /dev/null 2>&1
if [ $? -eq 0 ];then
echo "apache 正在运行"
else
echo "apache 关闭"
fi
#!/bin/bash
wget http://10.1.1.35/ule01.txt > /dev/null 2>&1
if [ $? -eq 0 ];then
echo "35的apache正在运行"
else
echo "35的apache已经关闭"
fi
5,
shell笔记目录里有一个sortuniq.txt
要把它排成下面的形式
4 root:x:0:0:root:/root:/bin/bash
1 bin:x:1:1:bin:/bin:/sbin/nologin
2 daemon:x:2:2:daemon:/sbin:/sbin/nologin
3 shutdown:x:6:0:shutdown:/sbin:/sbin/shutdown
# cat sortuniq.txt | sort |uniq -c |sort -t":" -k 3 -n
或下面的形式
4 root:x:0:0:root:/root:/bin/bash
3 shutdown:x:6:0:shutdown:/sbin:/sbin/shutdown
2 daemon:x:2:2:daemon:/sbin:/sbin/nologin
1 bin:x:1:1:bin:/bin:/sbin/nologin
# cat sortuniq.txt | sort |uniq -c |sort -t " " -k 2 -n -r
6,比如:35上的samba服务器共享/test目录,共享名也为test;
如果此目录里是每天备份的日志文件打包,格式为类似20110329.log.tar的形式
请你们写一个脚本,每天凌晨2点01分自动使用samba登录我的/test目录,并下载当天备份的tar到自己的backup目录
提示:(可以用下面的命令自动登录我的samba服务器)
[root@li ~]# smbclient //10.1.1.35/test -U ule 123
#!/bin/bash
a=`date +%Y%m%d`.log.tar
cd /backup
smbclient //10.1.1.35/test -U ule 123 >/dev/null 2>&1 << EOF
get $a
quit
EOF