shell 基础知识


注:文章内容来源于网络,如有侵权请联系我.

编写脚本注意事项

1.开头指定使用什么shell,例如:bash,ksh,csh等
2.脚本功能描述,使用方法,作者,版本,日期等
3.变量名,函数名要有实际意义,函数名以动名词形式,第二个单词首字母要大写。例如:updateConfig()
4.缩进统一用4个空格,不用TAB
5.取变量值使用大括号,如${varname}
6.删除文件时,如果路径有变量的,要判断变量有值,如rm -f ${abc}/* 如果变量abc没有值,则会把根目录下的文件删除
7.脚本中尽量不要使用cd变换目录
8.函数中也要有功能描述,使用依法,版本,日期等
9.函数的功能要单一,不要太复杂
10.$()` `更好
11.尽量不要使用多层if语句,而应该以case语句替代
12.如果需要执行确定次数的循环,应该用for语句替代while语句
13.输入的参数要有正确性判断
14.多加注释,方便自己或他人阅读。

问题汇总

#如果要在shell中使用alias定义快捷指令,需要在alias指令前先执行 `shopt -s expand_aliases` 开启alias扩展功能的交互模式

1.基础命令

#写入字符串到指定文件中(自动从新的一行写入,不用考虑换行问题)
echo '#!/bin/sh' >> ~/.bash_profile
#发送邮件
mail  -s "fail  backup from test" serenitysir@outlook.com < error.log
#获取格式化时间
today=`date  -d yesterday +%y%m%d`

###定时任务cron
crontab #查看已经配置的定时任务
#f1 f2 f3 f4 f5 program
#其中 f1 是表示分钟,f2 表示小时,f3 表示一个月份中的第几日,f4 表示月份,f5 表示一个星期中的第几天。program 表示要执行的程序。
#当 f1 为 * 时表示每分钟都要执行 program,f2 为 * 时表示每小时都要执行程序,其馀类推
#当 f1 为 a-b 时表示从第 a 分钟到第 b 分钟这段时间内要执行,f2 为 a-b 时表示从第 a 到第 b 小时都要执行,其馀类推
#当 f1 为 */n 时表示每 n 分钟个时间间隔执行一次,f2 为 */n 表示每 n 小时个时间间隔执行一次,其馀类推
#当 f1 为 a, b, c,... 时表示第 a, b, c,... 分钟要执行,f2 为 a, b, c,... 时表示第 a, b, c...个小时要执行,其馀类推
#例如:每一分钟执行一次 /bin/ls:
* * * * * /bin/ls
#在 12 月内, 每天的早上 6 点到 12 点,每隔 3 个小时 0 分钟执行一次 /usr/bin/backup:
0 6-12/3 * 12 * /usr/bin/backup
#周一到周五每天下午 5:00 寄一封信给 alex@domain.name:
0 17 * * 1-5 mail -s "hi" alex@domain.name < /tmp/maildata
#每月每天的午夜 0 点 20 分, 2 点 20 分, 4 点 20 分....执行 echo "haha":
20 0-23/2 * * * echo "haha"
0 */2 * * * /sbin/service httpd restart  #意思是每两个小时重启一次apache
50 7 * * * /sbin/service sshd start  #意思是每天7:50开启ssh服务
50 22 * * * /sbin/service sshd stop  #意思是每天22:50关闭ssh服务
0 0 1,15 * * fsck /home  #每月1号和15号检查/home 磁盘
1 * * * * /home/bruce/backup  #每小时的第一分执行 /home/bruce/backup这个文件
00 03 * * 1-5 find /home "*.xxx" -mtime +4 -exec rm {} \;  #每周一至周五3点钟,在目录/home中,查找文件名为*.xxx的文件,并删除4天前的文件。
30 6 */10 * * ls  #意思是每月的1、11、21、31日是的6:30执行一次ls命令

2.内置变量

$$ #Shell本身的PID(ProcessID)
$! #Shell最后运行的后台Process的PID
$? #最后运行的命令的退出状态(如 0:执行成功,1:执行失败...,具体根据程序定义为准) 或 函数返回值
$- #使用Set命令设定的Flag一览
#$-记录着当前设置的shell选项,himBH是默认值,5个字母分别有各自含义。解释如下:
#h:hashall,打开这个选项后,Shell 会将命令所在的路径记录下来,避免每次都要查询。举例:当h选项开启时,如果将某个自定义命令从/usr/bin/目录下移动到/usr/local/bin/,再运行,会提示无此命令。而当通过set +h将h选项关闭后,上述情况就不会出现。
#i:interactive-comments,包含这个选项说明当前的 shell 是一个交互式的 shell。所谓的交互式shell,就是输入命令后,shell解释执行,然后返回一个结果。在脚本中,i选项是关闭的。
#m:monitor,打开监控模式,就可以通过Job control来控制进程的停止、继续,后台或者前台执行等。
#B:braceexpand,大括号扩展。如果关闭B选项,那么shell就不会将大括号扩展。
#H:history,Shell 会把我们执行的命令记录下来,可以通过 history 命令查看,每一行是序号 + 执行的命令,在 shell 退出时,会将这些信息保存到~/.bash_history 文件中。如果H选项打开,就可以展开历史列表中的命令,可以通过!感叹号来完成,例如"!!"返回上最近的一个历史命令,"!n"返回第 n 个历史命令,等等。
$* #所有参数列表。如"$*"用「"」括起来的情况、以"$1 $2 … $n"的形式输出所有参数。
$@ #所有参数列表。如"$@"用「"」括起来的情况、以"$1" "$2" … "$n" 的形式输出所有参数。
$# 添加到Shell的参数个数
$0 Shell本身的文件名
$1$n #添加到Shell的各参数值。$1是第1参数、$2是第2参数…。

#例:
##1.参数传递到子shell
$ vi ./shell1.sh
./child_shell.sh $@
$ ./shell1.sh aa bb cc #aa bb cc 将会传递到子shell中

##2.变量赋值
# 从参数给变量赋值
for i in "$@";do
    if [[ $i = *"="* ]];then
        eval "$i"
    fi
done

3.条件判断

#if的基本语法:
if [ command ];then
   符合该条件执行的语句
elif [ command ];then
   符合该条件执行的语句
else
   符合该条件执行的语句
fi

###注意:
#1、[ ]表示条件测试。注意这里的空格很重要。要注意在'['后面和']'前面都必须要有空格
#2、在shell中,then和fi是分开的语句。如果要在同一行里面输入,则需要用分号将他们隔开。
#3、注意if判断中对于变量的处理,需要加引号,以免一些不必要的错误。没有加双引号会在一些含空格等的字符串变量判断的时候产生错误。比如[ -n "$var" ]如果var为空会出错
#4、判断是不支持浮点值的
#5、如果只单独使用>或者<号,系统会认为是输出或者输入重定向,虽然结果显示正确,但是其实是错误的,因此要对这些符号进行转意
#6、在默认中,运行if语句中的命令所产生的错误信息仍然出现在脚本的输出结果中
#7、使用-z或者-n来检查长度的时候,没有定义的变量也为0
#8、空变量和没有初始化的变量可能会对shell脚本测试产生灾难性的影响,因此在不确定变量的内容的时候,在测试号前使用-n或者-z测试一下
#9、? 变量包含了之前执行命令的退出状态(最近完成的前台进程)(可以用于检测退出状态)

###二、文件/文件夹(目录)判断
#常用的:
[ -a FILE ] #如果 FILE 存在则为真。
[ -d FILE ] #如果 FILE 存在且是一个目录则返回为真。
[ -e FILE ] #如果 指定的文件或目录存在时返回为真。
[ -f FILE ] #如果 FILE 存在且是一个普通文件则返回为真。
[ -r FILE ] #如果 FILE 存在且是可读的则返回为真。
[ -w FILE ] #如果 FILE 存在且是可写的则返回为真。(一个目录为了它的内容被访问必然是可执行的)
[ -x FILE ] #如果 FILE 存在且是可执行的则返回为真。
#不常用的:
[ -b FILE ] #如果 FILE 存在且是一个块文件则返回为真。
[ -c FILE ] #如果 FILE 存在且是一个字符文件则返回为真。
[ -g FILE ] #如果 FILE 存在且设置了SGID则返回为真。
[ -h FILE ] #如果 FILE 存在且是一个符号符号链接文件则返回为真。(该选项在一些老系统上无效)
[ -k FILE ] #如果 FILE 存在且已经设置了冒险位则返回为真。
[ -p FILE ] #如果 FILE 存并且是命令管道时返回为真。
[ -s FILE ] #如果 FILE 存在且大小非0时为真则返回为真。
[ -u FILE ] #如果 FILE 存在且设置了SUID位时返回为真。
[ -O FILE ] #如果 FILE 存在且属有效用户ID则返回为真。
[ -G FILE ] #如果 FILE 存在且默认组为当前组则返回为真。(只检查系统默认组)
[ -L FILE ] #如果 FILE 存在且是一个符号连接则返回为真。
[ -N FILE ] #如果 FILE 存在且自上次读取以来已被修改则返回为真。
[ -S FILE ] #如果 FILE 存在且是一个套接字则返回为真。
[ FILE1 -nt FILE2 ] #如果 FILE1 比 FILE2 新, 或者 FILE1 存在但是 FILE2 不存在则返回为真。
[ FILE1 -ot FILE2 ] #如果 FILE1 比 FILE2 老, 或者 FILE2 存在但是 FILE1 不存在则返回为真。
[ FILE1 -ef FILE2 ] #如果 FILE1 和 FILE2 指向相同的设备和节点号则返回为真。

###字符串判断
[ -z STRING ] #如果STRING的长度为零则返回为真,即空是真
[ -n STRING ] #如果STRING的长度非零则返回为真,即非空是真
[ STRING1 ]  #如果字符串不为空则返回为真,与-n类似
[ STRING1 == STRING2 ] #如果两个字符串相同则返回为真
[ STRING1 != STRING2 ] #如果字符串不相同则返回为真
[ STRING1 < STRING2 ] #如果 “STRING1”字典排序在“STRING2”前面则返回为真。
[ STRING1 > STRING2 ] #如果 “STRING1”字典排序在“STRING2”后面则返回为真。
###数值判断
[ INT1 -eq INT2 ] #INT1和INT2两数相等返回为真 =
[ INT1 -ne INT2 ] #INT1和INT2两数不等返回为真 <>
[ INT1 -gt INT2 ] #INT1大于INT2返回为真 >
[ INT1 -ge INT2 ] #INT1大于等于INT2返回为真 >=
[ INT1 -lt INT2 ] #INT1小于INT2返回为真 <
[ INT1 -le INT2 ] #INT1小于等于INT2返回为真 <=
###逻辑判断
[ ! EXPR ] #逻辑非,如果 EXPR 是false则返回为真。
[ EXPR1 -a EXPR2 ] #逻辑与,如果 EXPR1 and EXPR2 全真则返回为真。
[ EXPR1 -o EXPR2 ] #逻辑或,如果 EXPR1 或者 EXPR2 为真则返回为真。
[ ] || [ ] #用OR来合并两个条件
[ ] && [ ] #用AND来合并两个条件
###其他判断
[ -t FD ] #如果文件描述符 FD (默认值为1)打开且指向一个终端则返回为真
[ -o optionname ] #如果shell选项optionname开启则返回为真
###高级特性:
##1.双圆括号(( )):表示数学表达式
#在判断命令中只允许在比较中进行简单的算术操作,而双圆括号提供更多的数学符号,而且在双圆括号里面的'>','<'号不需要转意。
##2.双方括号[[ ]]:表示高级字符串处理函数
#双方括号中判断命令使用标准的字符串比较,还可以使用匹配模式,从而定义与字符串相匹配的正则表达式。
#双括号的作用:
#在shell中,[ $a != 1 || $b = 2 ]是不允许出,要用[ $a != 1 ] || [ $b = 2 ],而双括号就可以解决这个问题的,
#[[ $a != 1 || $b = 2 ]]。又比如这个[ "$a" -lt "$b" ],也可以改成双括号的形式(("$a" < "$b"))


###例:
##1.判断文件中是否包含指定字符
#grep -c "dfimage" ~/.bash_profile 得到的是包含的个数
if [[ `grep -c "test" ~/.bash_profile` -qe '0' ]]; then
    echo '不包含'
fi
##2.判断目录$doiido是否存在,若不存在,则新建一个
if [ ! -d "$doiido"]; then
  mkdir "$doiido"
fi
##3.判断普通文件$doiido是否存(! 判断结果取反),若不存在,则新建一个
if [ ! -f "$doiido" ]; then
  touch "$doiido"
fi
##4.判断$doiido是否存在并且是否具有可执行权限
if [ ! -x "$doiido"]; then
  mkdir "$doiido"
chmod +x "$doiido"
fi
##5.是判断变量$doiido是否有值
if [ ! -n "$doiido" ]; then
  echo "$doiido is empty"
  exit 0
fi
##6.两个变量判断是否相等
if [ "$var1" = "$var2" ]; then
  echo '$var1 eq $var2'
else
  echo '$var1 not eq $var2'
fi
##7.测试退出状态:
if [ $? -eq 0 ];then
    echo 'That is ok'
fi
##8.数值的比较:
if [ "$num" -gt "150" ];then
   echo "$num is biger than 150"
fi
##9.a>b且a<c
#1). (( a > b )) && (( a < c ))
#2). [[ $a > $b ]] && [[ $a < $c ]]
#3). [ $a -gt $b -a $a -lt $c ]
##10.a>b或a<c
#1). (( a > b )) || (( a < c ))
#2). [[ $a > $b ]] || [[ $a < $c ]]
#3). [ $a -gt $b -o $a -lt $c ]
##11.检测执行脚本的用户
if [ "$(whoami)" != 'root' ]; then
   echo  "You  have no permission to run $0 as non-root user."
   exit  1;
fi
#上面的语句也可以使用以下的精简语句
[ "$(whoami)" != 'root' ] && ( echo "You have no permission to run $0 as non-root user."; exit 1 )
##12.正则表达式
doiido="hero"
if  [[ "$doiido" == h* ]];then
    echo "hello,hero"
fi
##13.查看当前操作系统类型
#!/bin/sh
SYSTEM=`uname  -s`
if [ $SYSTEM = "Linux" ] ; then
   echo "Linux"
elif
    [ $SYSTEM = "FreeBSD" ] ; then
   echo "FreeBSD"
elif
    [ $SYSTEM = "Solaris" ] ; then
    echo "Solaris"
else
    echo  "What?"
fi
##14.if利用read传参判断
#!/bin/bash
read -p "please  input a score:"  score
echo  -e "your  score [$score] is judging by sys now"
if [ "$score" -ge "0" ]&&[ "$score" -lt "60" ];then
    echo  "sorry,you  are lost!"
elif [ "$score" -ge "60" ]&&[ "$score" -lt "85" ];then
    echo "just  soso!"
elif [ "$score" -le "100" ]&&[ "$score" -ge "85" ];then
     echo "good  job!"
else
     echo "input  score is wrong , the range is [0-100]!"
fi
##15.这个脚本在每个星期天由cron来执行。如果星期的数是偶数,他就提醒你把垃圾箱清理:
#!/bin/bash
WEEKOFFSET=$[ $(date +"%V") % 2 ]
if [ $WEEKOFFSET -eq "0" ]; then
   echo "Sunday evening, put out the garbage cans." | mail -s "Garbage cans out"  your@your_domain.com
fi
##16.挂载硬盘脚本(windows下的ntfs格式硬盘)
#! /bin/sh
dir_d=/media/disk_d
dir_e=/media/disk_e
dir_f=/media/disk_f

a=`ls $dir_d | wc -l`
b=`ls $dir_e | wc -l`
c=`ls $dir_f | wc -l`
echo "checking disk_d..."
if [ $a -eq 0 ]; then
    echo "disk_d  is not exsit,now creating..."
    sudo  mount -t ntfs /dev/disk/by-label/software /media/disk_d
else
    echo "disk_d exits"
fi

echo  "checking  disk_e..."
if [ $b -eq 0 ]; then
    echo "disk_e is not exsit,now creating..."
    sudo mount -t ntfs /dev/disk/by-label/elitor /media/disk_e
else
    echo  "disk_e exits"
fi

echo  "checking  disk_f..."
if [ $c -eq 0 ]; then
    echo  "disk_f  is not exsit,now creating..."
    sudo mount -t ntfs /dev/disk/by-label/work /media/disk_f
else
    echo "disk_f  exits"
fi


4.循环/迭代

##for i in 的各种用法 :
for i in “file1” “file2” “file3”
for i in /boot/*
for i in /etc/*.conf
for i in $(seq -w 10) -->等宽的01-10
for i in {1…10}
for i in $( ls )
for I in $(< file)
for i in$@” -->取所有位置参数,可简写为for i
#注意:bash shell支持C式for循环

##产生十个随机数:
#方法1:
for i in {0..9};do echo $RANDOM;done
方法2:
for i in $(seq 10);do echo $RANDOM;done

##倒数五秒:
#方法1
#!/bin/bash
echo "准备倒数5秒:"
for i in $(seq 5 -1 1)
do
    echo -en "$i";sleep 1
done
echo -e "开始"
#方法2:
#!/bin/bash
echo "准备倒数5秒:"
for i in $(seq 5 -1 1)
do
    echo -en "\b$i";sleep 1
done
echo -e "\b开始"

##批量添加用户:
#!/bin/bash
for i in $(cat /root/users.txt)        -->从列表文件读取文件名
do
    useradd $i
    echo "123456" | passwd --stdin $i -->通过管道指定密码字串
done

##查找出uid大于10000的用户,然后删除,必须使用for循环。
#方法1
#!/bin/bash
u_uid=(`cat /etc/passwd | awk -F: '{print $3}'`)
u_name=(`cat /etc/passwd | awk -F: '{print $1}'`)
for i in  `seq ${#u_uid[@] }`
do
    if ((  ${u_uid[i-1]} > 10000  ))
    then
           userdel -r  ${u_name[i-1]}&&echo "${u_name[i-1]} delete ok"
    fi
done
#方法2用正则找出大于10000的用户:
cat /etc/passwd | egrep “1[0-9]{4} | [2-9]{5,}##输出
#$1 is aa,
#$2 is bb,
#$3 is cc,
#$4 is dd,
#$5 is ee
#答案:
#!/bin/bash
echo "there are $# arguments in this scripts"
N=1  -->变量N用来计数
for i in $@
do
    echo "\$$N is $i"
    ((N++))
done

##ping 命令
-c 1>只ping一次。
-i 0.2>第一个包和第二个包之间间隔0.2s
-w 2 -->只等待2s
#例:
ping 172.30.132.123 &>/dev/null #重定向对于ping命令无用,执行成功$?就返回0,不成功则返回1
#根据IP地址检查网络中存活的主机IP(大范围的扫描)
#!/bin/bash
for r in 192.168.1.{1..254}
do
    ping -c1 -w1 "${ip}" &>/dev/null
done
arp -n|grep ether|tr -s ' '|cut -d' ' -f1

##关于ping命令的一个最经典的脚本:
for i in {1..193}
do
    ( ping -c1 -i0.2 -w1 172.16.30.$i &>/dev/null
    if ((  $?==0  ))
    then
            echo "172.16.30.$i up"    >>2.txt
    else
            echo "172.16.30.$i down"    >>3.txt
    fi )&    -->这样就把这一段放到后台去执行了,大大加快了速度。
done
sleep 2
live_pc_num=`cat 2.txt|wc -l`
down_pc_num=`cat 3.txt|wc -l`
echo "there are $down_pc_num is down"
echo "there are $live_pc_num is up"
echo "list:"
cat 2.txt
rm -rf 2.txt 3.txt

##典型的while循环:
#!/bin/bash
i=1
while :    -->:等价为true
do
  echo "$i"
  ((i++))
  sleep 0.3
done
#注:这是个死循环,会一直执行下去
#加上break,可以跳出循环:
#!/bin/bash
i=1
while :
do
  echo "$i"
  ((  i++  ))
  if ((   i==20000  ))  -->输出的只有1-19999
  then
  	break
  fi
done

##小结:
#break:跳出整个循环
#exit:跳出脚本
#continue:跳出本次循环,接着执行下一次循环

文章作者: 慕书
版权声明: 本博客所有文章除特別声明外,均采用 CC BY 4.0 许可协议。转载请注明来源 慕书 !
评论
  目录