顯示具有 shell script 標籤的文章。 顯示所有文章
顯示具有 shell script 標籤的文章。 顯示所有文章

2010年12月13日

[Shell Script] 批次修改使用者的密碼有效期限 - V2

之前寫過一篇[Shell Script] 批次修改使用者的密碼有效期限 - V1,當時是自己寫了一個有點囉唆的 shell script 來處理這樣的需求,最麻煩的問題是「如何判斷哪些帳號是使用者帳號」,過濾 /etc/passwd 檔案有以下困難:

  1. 在 RHEL 系統中,通常使用者帳號的 uid 是從 500 開始,但是在 Solaris 系統中是從 100 開始。
  2. 上次寫的:加強過濾 /etc/passwd 檔案時使用的 regular expression,雖然目前實務上接觸到的主機還不至於帳號多到 uid > 600 或者更高,但可以當作一個練習 regular expression 的機會。<--相當麻煩
最近又要去修改使用者密碼的到期日(每三個月就要做一次),因為不想再跑這個很麻煩的 script,因此研究出一個更方便的指令,主要是以 echo -e 搭配 xargs 來完成。

要設定「密碼有效期為90天」以及「密碼到期前30天警告」可使用以下指令:

echo -e "account1\naccount2" | xargs -i chage -M 90 -W 30 {}

echo 加上 -e 參數後會讓兩個帳號間的「\n」換行字元生效(不加 -e 的話 xargs 會收到「account1\naccount2」這個完整的字串),再搭配 xargs -i 的置換效果({}會被置換成透過 pipeline 接收的字元),上面一行指令相當於下面兩行指令:

chage -M 90 -W 30 account1
chage -M 90 -W 30 account2


這樣的寫法的彈性在於,要 echo 哪些帳號出來完全可以自由控制,不用考慮不同系統中 uid 的起始數字不同,也不用考慮例外狀況。


在我管的系統中每個 OS 中的帳號都類似(大概是5~8個),透過 xargs 可以把 5~8 行指令濃縮成一行執行,非常省事。

接下來的幾個指令就依樣畫葫蘆:

  • 檢查上述指令是否生效:echo -e "account1\naccount2" | xargs -i chage -l {}
  • 設定最後一次變更密碼的日期:echo -e "account1\naccount2" | xargs -i chage -d YYYY-MM-DD {}
  • 在 Unix 系統(e.g., Solaris)上設定密碼90天到期、到期30天前提醒:echo -e "account1\naccount2" | xargs -i passwd -x 90 -w 30 {}(Solaris預設沒有 chage 指令)

xargs 真的是非常好用,下一篇再來介紹怎樣用 find、sed 搭配 xargs 來批次修改 awstats 統計資料檔(純文字檔)的內容。

參考:[2010-03-17] [Shell Script] 批次修改使用者的密碼有效期限 - V1



2010年8月12日

[Linux/Unix] 讓 script 在登出後仍可繼續在背景執行 - at 排程

之前的文章中曾經提到,因為改了 cron job 的執行時間,導致 awstats.pl 執行失敗,必須重跑 cron job。由於是在週末發現這個狀況,因此我透過 VPN 連回公司下指令 (sh /path/to/awstats.pl -update ...),但由於 VPN 有限制幾分鐘內沒有向 server 要求資料就會自動斷線,但 awstats.pl 分析一個網站往往要耗時 15~20 分鐘,且 VPN 連線過程中若登出 server (手動 or 被踢出),則執行中的 script 就會被中斷了,最後發現利用 at 排程 + 背景執行就可以解決這個問題。

只要輸入 at HH:mm YYYY-MM-DD,按 Enter 後,再輸入「perl /path/to/awstats.pl -update ... &」,按 Enter,最後按 ctrl+d 結束 at 排程的編輯即可。

其中關於 at 排程的設定可參考:鳥哥-僅執行一次的工作排程,至於如何讓 script 於背景執行,請參考:鳥哥-job control的管理

利用 at 排程設定的工作,即使在執行此排程的帳號登出系統的狀況下仍然可以正確執行,就和利用 cron job 執行排定的工作一樣。

當然要對每個網站都手動輸入一次指令也太麻煩了,最好是把所有的 awstats.pl -update 指令包成一個 .sh 檔案,之後就只要執行那個 .sh 檔就可以啦!

參考資料:

2010年8月9日

[shell script] 驗證 tar 解壓縮的執行結果

前陣子在用 tar 解壓縮更新程式的時候,發現某個檔案沒有被正確被覆蓋,經過確認,tar 指令的確有用到 -m 參數 (用法參考:這篇),難道是 tar 這個全世界用了 N 年的工具出了問題?

雖然最後證實那個檔案是因為其他的原因被還原到舊版,但是在那段真相水落石出之前的空窗期我寫了一個短短的 script 來驗證 tar 的執行結果,完整程式碼如下:

#!/bin/bash
#created by Tim, verify the tar unzip operation is successful
#this file should be put in the /tmp directory

#read the log file from the output of "tar -zxvfm XXX.tar.gz > /tmp/tar.log" command

BASEDIR=`pwd`  cd /tmp
files=`cat tar.log`
count=0

for file in $files
do
   #check the last modified date of each file, it should be the same
   ls -l "$BASEDIR/$file" 
   count=`expr $count + 1`
done
echo "count: $count"

#verify files count
#precondition: there should be only 1 .tar.gz file in the /tmp directory
#the number of files in the .tar.gz file
tarCount=`tar -tv -f *.tar.gz | wc -l`   
echo "tarCount: $tarCount"

if [ $count -eq $tarCount ]; then

   echo "file count check OK!"
else
   echo "file count check FAILED!!"
fi 

程式很單純,只是先利用 tar 的 -v (verbose) 參數把執行過程中解壓縮的
檔案相對路徑紀錄到 /tmp 下的 tar.log 檔,而後逐一比對 log 檔中的
路徑與目前系統中實際的檔案修改時間是否相同,最後會比較 tar 檔中
所含的檔案數量與 tar.log 檔中紀錄到的檔案數量是否一致。 
 
雖然這程式應該沒啥用 (tar 出問題的機率超低),但還是擺著備忘一下,
當作 shell script 的範本也好 :p 

2010年3月17日

[Shell Script] 批次修改使用者的密碼有效期限 - V1

最近公司在做資訊稽核,其中有一塊牽涉到主機上的帳號管理。根據公司的資安規範,每個使用者帳號 (系統內建帳號除外) 必須符合以下限制:
  1. 密碼長度不得小於6個字元
  2. 密碼有效期為90天 (也就是每90天就要變更一次密碼的意思)
  3. 不得使用三代以內的密碼
  4. 密碼不得與帳號相同
除了第四點是系統內建,不需要調整設定以外,其餘幾項都需要調整系統設定 (Solaris 只要修改系統內建的設定檔即可;針對第1和第3項要求,Linux 則必須要使用 PAM 模組的設定來達成,下次再撰文說明),本文先針對第二點進行說明。
要設定「密碼有效期為90天」並不困難,不管在 Solaris / Linux 上都只要透過 passwd 指令或者 chage 指令即可完成,但由於我覺得 chage 指令很容易使用,查詢所得的結果也很容易閱讀,所以這次就以 chage 來實作。
有關 chage 指令的用法,除了參考鳥哥的網站,也可以直接下 man chage 指令來查詢內建的 man page,我用的指令如下:
chage -I 1 M 90 -W 30 [username] chage -d 2010-03-17 [username]
(第二個指令是為了避免設定好第一個指令以後,所有系統上 90 天內不曾修改密碼的使用者下次登入系統時都要重新設定密碼,因此把「最後一次修改密碼」的日期改為今天)
設定完畢之後,再下 chage –l [username] 指令 (英文的 l) 即可查詢該使用者的帳號狀態。
OK, 既然 chage 指令這麼簡單,那本文有甚麼好講的呢?如果只需要在單一主機上對少數幾個帳號作如此的設定,當然是直接下幾個指令就搞定,問題是這次需要修改的主機超過 50 台,每台主機上面又有一大堆帳號,真要這樣下指令的話會瘋掉!(而且還有第1和第3項要求要作到,那又是另外兩行 Perl 程式) 所以我就把這些機械化的動作寫成一個 script 啦!
第一步:詢問使用者 chage -d 要使用的日期參數:
echo “set Last password change day (YYYY-MM-DD): “ read lDays #把使用者輸入的參數存到 $lDays 變數中
接下來判斷使用者輸入的字串是否符合 YYYY-MM-DD 的格式:
syntaxOK=`echo $lDays | perl –ne ‘print “OK” if /^\d{4}-\d{2}-\d{2}/’` (`` 是 backtick,不是單引號哦~)
if [ "$syntaxOK" != "OK" ]; then    echo "Wrong syntax! Usage: YYYY-MM-DD"    exit fi
這裡用 perl 來寫 regular expression 會比用 shell script 簡單很多,shell script 要這樣寫 (因為沒有支援 \d 這樣的表示法,而且也不支援 {4} 的寫法):
syntaxOK=`echo $lDays | grep "^[0-9][0-9][0-9][0-9]-[0-9][0-9]-[0-9][0-9]$"`
神奇的是用 perl 的話不用加上「$」來限制結尾要是 2 個數字,就自然可以判斷出「1111-11-111」是不符合 pattern 的,但是用 shell script 中的 grep 的話就要 (用 egrep 還是要加上「$」)。
第二步:列出系統中所有的使用者,除了系統內建帳號以外:
我參考的是 Listing all users on the system 一文,但是在 awk 的主程式中加上 regular expression 的判斷:
for name in $(awk 'BEGIN{FS=":"} { if($3 ~ /^5[0-9][0-9]/) {print $1}}' < "$PASSWORD_FILE" )
利用這個 pattern 可以過濾出第三欄 ($3) 的值是「5xx」(xx 為數字) 的資料,且印出第一欄 ($1)。
第三步:過濾 uid > 500 的帳號中,屬於系統帳號 or 給程式使用的帳號:
一般來說 uid < 500 的是保留給系統使用的帳號,但 uid > 500 的帳號中仍有可能存在單純給程式使用的帳號 (e.g., 給 FTP / scp 使用),因此需要進一步過濾。我的作法世新增一個 AccountExcluded.txt 檔案,以一行一個帳號的方式,將欲排除的帳號寫入這個檔案,接下來用一個迴圈就可以比對:
accountExcluded=`cat ./AccountExcluded.txt` excluded="0" for exclude in $accountExcluded do      if [ $name = $exclude ]; then             excluded="1"      fi done
第四步:執行 chage 指令
到了這個步驟才是整個 script 的核心,對於每個不在 AccountExcluded.txt 檔案中的帳號,只要在以上的迴圈中執行以下兩行即可:
chage -M 90 -W 30 $name chage -d $lDays $name
整個 script 其實就只有這樣而已,為了避免更動到系統帳號才額外加了一堆有的沒的程式碼 (為了寫 log 又另外加了一堆),在這次練習的過程中,發現還是寫 RegExp 最好玩 (awk 也很有趣),其他的程式碼都挺無聊的。
這個 script 未來還有很多可以 enhance 的地方,依照重要性由高到低排列:
  1. 在本機上登入所有需要修改的主機,將本機的 shell script 檔&設定檔複製到所有的主機上執行 (不然一台一台的登入也是很累人的)。將這個架構建立起來以後,未來有遇到類似要再每台主機上調整設定的需求,就可按照同樣的模式寫成一個 script 來執行。
  2. 加強 YYYY-MM-DD 的判斷,找找看有沒有轉換成 Date 之類型態的方式可用,順便避免 2/29 的問題。
  3. 把讀取兩次 /etc/passwd 的寫法改成只讀取一次,第一次讀出資料以後存到一個 array 裡面就好。
  4. 加強過濾 /etc/passwd 檔案時使用的 regular expression,雖然目前實務上接觸到的主機還不至於帳號多到 uid > 600 或者更高,但可以當作一個練習 regular expression 的機會。
有興趣參考完整原始碼的朋友請到這裡下載 (V1)。

參考:[2010-12-13] [Shell Script] 批次修改使用者的密碼有效期限 - V2

2010年2月23日

[Shell Script] 如何擷取字串中的子字串

今天要分享的是,在 shell script 中擷取子字串 (俗稱 substring) 的技巧 (各程式語言都要提供的最基本功能)。

一開始在學 shell script 的時候,我看的是臥龍小三的網站 (News 裡面的 Shell 入門),按照順序看過一遍固然收穫很多,但也僅止於「快速入門」的程度,也就是「讓你不要害怕 shell script」的作用,其中對字串處理這種重要的功能並沒有整理的很詳細,因此我一開始連「取出某個字串的最後一個字元」這樣的功能都不知道如何下手,用 sedawk 都不太對。後來透過孤狗大神找到了幾種方法:

法一:Bash Shell Programming in Linux (搜尋 substring)

time=”20:06:16”
unit=${time:${#time}-1:${#time}} –> $time變數要寫三次!
這個寫法顯然相當之囉嗦,但是它是有效的。

法二:parsing a string in a shell script (透過 awk 來做)

time=”20:06:16”
unit=`echo $time | awk '{print substr($0, length($0), length($0))}'` –> 可用 $0 取代 $time,較簡潔


但整體看來還是非常囉嗦,我只是要取出一個字串的最後一個字元而已阿!

法三:臥龍小三-Linux Shell 程式設計實務 (Chapter 08: 8-4 使用外部程式 expr 做算數運算)

time=”20:06:16”
startPos=$(expr length $time)
unit=`expr substr $time $startPos 1` -> 注意 $time 要被包在“ “裡面


這章介紹的 expr 功能還蠻多的,有找子字串 index 和做 substring 等等功能,值得練習一下。這個作法雖然少了很多小括號,但感覺還是挺麻煩的,最後,終於讓我找到一個最簡單直覺的作法:

法四:一樣出自 parsing a string in a shell script (看這個留言)

time=”20:06:16”
unit=`echo $time | tail -c 2
–> 重點在 -c 這個參數,以及用 tail 的話 byte 數要多 +1 (取最後1個字元:2)

tail-c

一行搞定!而且非常的直覺!除了要取出特定字串以外,如果是要正取 (用 head)或倒取 (用 tail) 固定幾個字元的話,應該都會用 head/tail 來做吧!

以上就是今天的分享,如有更好的作法還請不吝賜教 ^^~

PS. 取出字串的最後一個字元,在 Perl 中也非常簡單:substr $time, -1,負號代表由字尾開始往前面取

Google Spreadsheet 裡用規則運算式

最近因為工作關係,遇到要用 Google Form 及 Google Sheet 所以研究了 Google Sheet 裡的一些 function 怎麼用 首先,分享一下如何在 Google Sheet 裡用規則運算 :D