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