shell编程入门

  最近由于工作原因,写了几个脚本,然后再linux中用定时任务去跑,记录一下shell脚本的学习过程吧!

  首先知道shell第一行#!/bin/bash表示这是一个shell脚本,例如下面这个简单的脚本:

#!/bin/bash

#author:Gary

#date:20200313

#description:用于清理14天之前日志文件夹

#这里就是要进行进行清理的nas中的目录
baseDir="/usr/local/java/shellscript/store"

#服务器中不存在该目录就退出
if [ ! -d ${baseDir} ];then
  echo "不存在目录:${baseDir}"
  exit 1
fi

#这里指定前段时间第n天日志需要进行清理
num=14

#需要清理的日期转换成时间戳
barrierDate=`date -d "$num days ago" +%F`
barrierDateStamp=`date -d "${barrierDate}" +%s`

#遍历目录下的所有日志文件进行正则匹配
for file in `ls ${baseDir}`;do
  #获取目录中文件名中的日期
  dirDate=`expr "${file}" : '\([0-9]\{4\}\-[0-9]\{2\}\-[0-9]\{2\}\)'`
  #目录中的日志文件名没有日期在其中,说明不需要删除,就跳过,进行下一次循环
  if [ -z "${dirDate}" ];then
    continue
  fi
  #获取该文件文件名的时间戳,当该时间符合需要清理的时间的时候,再判断一次该目录是否存在,保险一点!存在的话就删除就行了
  dirDateTimeStamp=`date -d "${dirDate}" +%s`

  if [ ${dirDateTimeStamp} -lt ${barrierDateStamp} ];then
    echo "当前遍历的目录是:${file},目录时间是:${dirDate}"
    #当前日志目录所在的绝对路径
    fileAbsPath="${baseDir}/${file}"
      if [ -d ${basePath} ];then
        echo "对${file}文件进行删除"
        rm -rf ${fileAbsPath}
        echo "日志目录删除成功:${fileAbsPath}"
      else
        echo "删除目录发生错误:${fileAbsPath}"
      fi
  fi
done

  

  上面的目录中存放的就是每天生成的日志文件,粗略看一下上面这个简单的脚本,由于本人写脚本的水平抠脚,只能用这种很直白的写法,没有任何花里胡哨.....

  就是按照java代码那样的逻辑写的,应该都能看得懂,基本上是一些for循环和if判断;还有一个echo语句表示在命令行中输出相关信息,如果使用echo "xxx" >> /usr/local/file_name.log表示将echo输出的信息追加到/usr/local/file_name.log文件中;

  注意>和>>的区别,>是覆盖的意思,比如用命令清空一个文件,最简单的就是echo “” > a.txt;而>>表示在文件后面追加,不会覆盖

1.先说说for循环

  下面这个就是遍历一个目录下的子目录的,不能遍历文件的哦!那个反引号,表示让系统去执行ls /etc命令,什么叫做让系统去执行呢?就跟我们手动敲这条命令去执行一样;还有一点,就是shell中要使用一个声明好的变量,需要用${xxx}表示,可以不加大括号,习惯加上好看点;

  最后就是要以done表示这个for循环结束;

for file in `ls /etc`;do
    echo ${file}
done

  

 2.if语句

  if和for写法差不多,下面这个作用是:当根目录下没有Top这个目录,就创建Top目录;注意,if后面的中括号那么多空格,不能少!!!if语句要以fi结尾;

  那么问题来了,那个-d是干嘛的呀?下面列出来了很多-xx的,写脚本的时候拿出来看一看就行;发现判断文件或者是字符串是不是空都可以用这种-xx的方式判断;

if [ ! -d "/Top" ]; then
  mkdir -p /Top
fi
[ -a FILE ]  如果 FILE 存在则为真。  

[ -b FILE ]  如果 FILE 存在且是一个块特殊文件则为真。  

[ -c FILE ]  如果 FILE 存在且是一个字特殊文件则为真。  

[ -d FILE ]  如果 FILE 存在且是一个目录则为真。  

[ -e FILE ]  如果 FILE 存在则为真。  

[ -f FILE ]  如果 FILE 存在且是一个普通文件则为真。  

[ -g FILE ]  如果 FILE 存在且已经设置了SGID则为真。  

[ -h FILE ]  如果 FILE 存在且是一个符号连接则为真。  

[ -k FILE ]  如果 FILE 存在且已经设置了粘制位则为真。  

[ -p FILE ]  如果 FILE 存在且是一个名字管道(F如果O)则为真。  

[ -r FILE ]  如果 FILE 存在且是可读的则为真。  

[ -s FILE ]  如果 FILE 存在且大小不为0则为真。  

[ -t FD ]  如果文件描述符 FD 打开且指向一个终端则为真。  

[ -u FILE ]  如果 FILE 存在且设置了SUID (set user ID)则为真。  

[ -w FILE ]  如果 FILE 如果 FILE 存在且是可写的则为真。  

[ -x FILE ]  如果 FILE 存在且是可执行的则为真。  

[ -O FILE ]  如果 FILE 存在且属有效用户ID则为真。  

[ -G FILE ]  如果 FILE 存在且属有效用户组则为真。  

[ -L FILE ]  如果 FILE 存在且是一个符号连接则为真。  

[ -N FILE ]  如果 FILE 存在 and has been mod如果ied since it was last read则为真。  

[ -S FILE ]  如果 FILE 存在且是一个套接字则为真。  

[ FILE1 -nt FILE2 ]  如果 FILE1 has been changed more recently than FILE2, or 如果 FILE1 exists and FILE2 does not则为真。  

[ FILE1 -ot FILE2 ]  如果 FILE1 比 FILE2 要老, 或者 FILE2 存在且 FILE1 不存在则为真。  

[ FILE1 -ef FILE2 ]  如果 FILE1 和 FILE2 指向相同的设备和节点号则为真。  

[ -o OPTIONNAME ]  如果 shell选项 “OPTIONNAME” 开启则为真。  

[ -z STRING ]  字符串“STRING” 的长度为零则为真。 或者字符串为NULL时也为真。 

[ -n STRING ]  和-z相反,默认不加-n也行,也就是说这个写法和[STRING]是一样的

[ STRING1 == STRING2 ]  如果2个字符串相同。 “=” may be used instead of “==” for strict POSIX compliance则为真。  

[ STRING1 != STRING2 ]  如果字符串不相等则为真。

  

 3.数组的遍历

  可以看到数组中每个元素用空格分隔就行了,如果是字符串数组也是一样的,例如:dirs=("/log1" "/log2");

  注意下面的for循环中有两个小括号啊,括号里面的${#arr(*)}表示数组里面元素的个数,注意和${arr[*]}的区别,这个表示数组中所有的实际元素

arr=(1 2 3 4)
for((i=0;${#arr[*])};i++));do
   echo ${arr[i]}
done

  上面的for循环也可以用for in循环表示,,如下所示,看到这里应该很多人微微一笑,很多变成语言应该都知道这两种循环吧!

arr=(1 2 3 4)
for a in ${arr[*]};do
   echo ${a}
done

  

 4.比较运算

  很多时候我们需要比较两个字符串是否相同,如下所示,注意,中括号两边的空格啊!==号两边空格,不要吝啬空格,不然你会发现一些奇葩的错误;

if [ ${a} == ${b} ];then
    echo "相同"
else
    echo "不相同"
fi

  

 那么又有人要问了,数字的比较呢?注意,此时是没有大于号,小于号这种东西的(这里不用转义符号。。)

左边等于右边: $a -eq $b;
左边不等于右边: $a -ne $b;
左边大于右边: $a -gt $b;
左边小于右边: $a -lt $b;
左边大于等于右边: $a -ge $b;
左边小于等于右边: $a -le $b;

  这里注意一点东西,=,==和-eq都可以用来比较是否相等,都可以用在if后面的中括号中;在[ ]中=和==效果一样,在(( ))中=表示赋值,而==表示比较;那么eq和==的区别在哪呢?==可以比较字符串和数字,而eq只能比较数字(eq可以比较这样的字符串[ "20" -eq "20" ],不能比较[ "hello" -eq "hello" ],会报错),所以尽量用==;

#!/bin/bash

#不会报错,打印false
 if [ "a" == "" ];then
   echo "true"
 else
   echo "false"
 fi

#会报错
 if [ "a" -eq "" ];then
   echo "true"
 else
   echo "false"
 fi

     另外,注意[[  ]]和[ ]的区别,简单说一下,只要你使用[[ ]]那么你想表示&&和||的关系,你可使用这两个符号,也可以使用对应的-a和-o;但是如果你使用的是[ ]那么你只能使用-a和-o,还只能在中括号里面,例如[ 5 -lt 3 ] -o [ 7 -gt 6 ]就会报错;

if [[ 5 -lt 3 || 7 -gt 6 ]];then
  echo "true"
else
  echo "false"
fi


if [[ 5 -lt 3 ]] || [[ 7 -gt 6 ]];then
  echo "true"
else
  echo "false"
fi


if [ 5 -lt 3 -o 7 -gt 6 ];then
  echo "true"
else
  echo "false"
fi


if [ 5 -lt 3 ] && [ 7 -gt 6 ];then
  echo "true"
else
  echo "false"
fi

  

 5.时间

  比较常见的就是脚本中处理时间:

获取今天的日期:todayDate=`date -d now +%Y-%m-%d`或者`date +%F`
明天日期:`date -d next-day +%Y-%m-%d`或者`date -d tomorrow +%Y-%m-%d`
昨天日期:`date -d "1 years ago" +%Y-%m-%d`
第n天前的日期:`date -d "n days ago" +%F`
转换成时间戳:stamp=`date -d "${todayDate}" +s`

  

 6.正则匹配

  例如一个日志文件是这样的sgffg.log.2020-02-05.2,那么用下面这个正则去匹配,其中${file}表示该日志文件名;

fileDate=`expr "${file}" : '.*\([0-9]\{4\}\-[0-9]\{2\}\-[0-9]\{2\}\).*'`

7.日志脚本

#!/bin/bash

#author:Gary

#date:20200218

#description:主要用于定期备份过期日志

#这里表示可以备份多个目录下的日志文件
baseDirs=(
"/home/path1" 
"/home/path2" 
"/home/path3"  
)

#想要备份的地方
storePath="/home/store/path"

#这里指定前段时间第n天日志需要进行备份
num=7

#备份成功文件的个数
storeLogFileNum=0

taskStartTime=`date "+%Y-%m-%d %H:%M:%S"`
#因为需要把日志按照时间进行分类的,这里获取需要清理日志的那天日期以及时间戳
barrierDate=`date -d "$num days ago" +%F`
barrierDateStamp=`date -d "${barrierDate}" +%s`

#路径不存在就创建该目录
if [ ! -d ${storePath}/${barrierDate} ];then
  mkdir -p ${storePath}/${barrierDate} || exit 1
fi

#将脚本文件中echo输出的信息追加到文件中,这里该文件不存在就会创建
storeRecord=${storePath}/${barrierDate}/storeRecord.log

echo "当前时间为${taskStartTime},对日志文件时间是${barrierDate}的文件开始备份..." >> ${storeRecord}

#开始遍历所有需要备份日志的目录
for ((i=0;i<${#baseDirs[*]};i++));do
  basePath=${baseDirs[i]}
  
  #对存日志的路径判断进行处理,该目录不存在的话就进行下一次循环
  if [ ! -d ${basePath} ];then
    # echo "日志文件目录不存在,basePath:${basePath}"
    continue
  fi
  echo "开始对目录${basePath}下的日志文件进行处理" >> ${storeRecord}

  #判断日志目录下有没有日志文件
  folder=`ls ${basePath}`
  if [ -z ${folder} ];then
    echo "目录${basePath}中没有任何文件" >> ${storeRecord}
    continue
  fi

  #遍历目录下的所有日志文件进行正则匹配
  for file in ${folder};do
    #获取文件名中的日期
    fileDate=`expr "${file}" : '.*\([0-9]\{4\}\-[0-9]\{2\}\-[0-9]\{2\}\).*'`
    #目录中的日志文件不符合条件筛选条件,说明该文件不需要备份,跳过,进行下一次循环
    #这个很重要!!!!!!!
    if [ -z "${fileDate}" ];then
      continue
    fi

    #不为空就获取该文件文件名的时间戳,当该时间符合需要清理的时间的时候,就移动到目标目录中存起来
    fileDateTimeStamp=`date -d "${fileDate}" +%s`

    if [ ${fileDateTimeStamp} -eq ${barrierDateStamp} ];then
      #当前日志文件所在的绝对路径
      fileAbsPath="${basePath}/${file}"
      #如果在目标目录中有重名的,这里的-b参数会对目标目录中的同名文件进行备份,不会覆盖
      echo "对${file}文件进行备份" >> ${storeRecord}
      mv -b ${fileAbsPath} ${storePath}/${barrierDate}
      #日志文件移动成功的计数器
      ((storeLogFileNum++))
      echo "该文件备份成功" >> ${storeRecord}
    fi
  done
  echo "目录${basePath}下的日志文件处理完毕" >> ${storeRecord}
done
taskEndTime=`date "+%Y-%m-%d %H:%M:%S"`
startSeconds=$(date --date="${taskStartTime}" +%s);
endSeconds=$(date --date="${taskEndTime}" +%s);

#执行该任务所花费时间,精确到秒
runTime=$((endSeconds-startSeconds))"s"
echo "日志备份整理完毕,备份完成时间为${taskEndTime},花费了${runTime},共备份了${storeLogFileNum}个日志文件" >> ${storeRecord}
echo >> ${storeRecord}
exit 0

 我这个脚本在linux定时任务中是每天执行一次,将前面第七天的日志备份,例如今天是3月8号清理3月1号的,3月9号清理3月2号的...,始终保证最新的一个星期的日志不被清理;

  备份之后生成的目录是这样的:

 8.定时任务

  我们肯定不会自己手动去执行这个shell脚本吧!这个时候就要用到linux的定时任务,最重要的时cron表达式;

  定时任务分两种,一种是系统级别的定时任务,可以通过vim /etc/crontab打开,但是不建议使用这个,这个文件必须要有root权限才能修改;另外一种是用户级别的定时任务,默认就是当前用户,使用crontab -e打开;

  那么问题来了,cron表达式怕写错了怎么办?肯定不会自己傻乎乎的等啊,这里有个在线的cron表达式测试工具https://tool.lu/crontab,可以直接看看你的任务啥时候执行;

  最后,注意一点,使用crontab -e打开之后,需要配置你的shell脚本绝对路径,例如0 1 * * * /usr/local/del_file.sh,注意哦!!!要给你的脚本添加可执行权限啊!

  chmod +x del_file.sh

原文地址:https://www.cnblogs.com/gary0409/p/12496380.html