最近由於工作原因,寫了幾個腳本,然後再linux中用定時任務去跑,記錄一下shell腳本的學習過程吧! 首先知道shell第一行#!/bin/bash表示這是一個shell腳本,例如下麵這個簡單的腳本: #!/bin/bash #author:java小新人 #date:20200311 #desc ...
最近由於工作原因,寫了幾個腳本,然後再linux中用定時任務去跑,記錄一下shell腳本的學習過程吧!
首先知道shell第一行#!/bin/bash表示這是一個shell腳本,例如下麵這個簡單的腳本:
#!/bin/bash #author:java小新人 #date:20200311 #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:java小新人 #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