- 拆多個 Script、自訂設定檔,擔心檔案漏掉不知道
- 因為手痠不想在執行 Script 時寫輸出導向(大於符號 > log_file 2>&1);更不想在 Script 裡面每一行都加上輸出導向(手更痠。。)。只想要一兩行公式就自動檔向檔案
- 想要在 Shell Script 所吐出來的輸出內容,每一行添加時間或是檔名等裝飾品
- [Update 2022/3/8] 症狀四:不能同時運作的 shell script,運作時間又抓不準,然而卻需要設定頻繁 cronjob 排程,卻又害怕重複運行。。
這篇集以上問題之小成於一身,弄一個簡易可以抄的 Shell Script 範本,自認症狀符合的人類就夾去配ㄅ~
首先,寫 Script 常常會草草的弄,作一些相對簡易的作業,因此有時不像程式,會去留意程式碼的組織。
但就算是短短的 Script,放任 Script 恣意生長流轉,往往會回到手上就會變成認不得又難懂的面貌(就算是少少行數也可能發生),或是挖掘遺跡發現已經遺忘的差不多。
因此,這個樣本 Script 參照 C 的 main()、Python 的 __main__ 的組織方式,把 Shell Script 用相仿的方式呈現:只要把 Script 的大步驟用 function 包裝,在 main 區塊中只要呈現大步驟就可以快速一覽。這麼一來至少在未來閱讀上比較有機會清晰一點、加速重新掌握;並且便於複製成相似功能的不同 Script 而不會在不同相近 Script 之間看的頭昏。
以下 Script 樣板在最後面放了 do_main() 函數,並且在最後一行放一個呼叫,以模擬相仿功能。
症狀一:拆多個 Script、自訂設定檔,擔心檔案漏掉不知道
有的時候試圖把 Shell Script 像程式碼一樣,拆分成多個檔案,或是用幾個變數設定檔,或甚至是呼叫其他類的 Script 檔(像是 Python Script、Awk Script、SQL Script 等,避免都用 Shell Script 字串呈現:因為在文字檔編輯器裡面套色都看不懂)。
但由於 Shell Script 並不總是會有程式碼的待遇,使用例如 Subversion、Git 等程式碼版控平台列管。因此執行上總會有不小心漏掉其中一個某檔案導致執行到一半錯誤的疑慮。
因此在以下樣本中,處置相依 Script、設定檔的思路是,在主要執行的 Script 裡面「用變數具體明列出所有相依的檔案」,並且在執行前快速檢查這些列舉的檔案;而在 Script 底下的各段落中,都以變數代替具體檔名作使用與呼叫。
如此一來
- 只要確切列舉清楚相依檔案在 Script 前面,就可以快速確認執行是否有漏檔案而不會跑到一半才失敗;
- 相依檔名想要變更時,只要改一處,不用在 Script 裡面到處撈,或是考驗自己搜尋取代甚至 Regex 的技能
- 此外,「通常」Script 的等級比較不會像程式碼一樣有成千上百個程式碼檔案,因此這樣列舉的方式,還算是人眼可接受的範圍。
Script 檔名為「filevar_check.sh」,用以在下面範例操作。
#!/bin/bash ## Get script filename SCRIPTBN=$(basename $0) SCRIPTFN="${SCRIPTBN%.*}" SCRIPTDR=$(dirname $0) VARFILE0="${SCRIPTDR}/text_file.txt" VARFILE1="${SCRIPTDR}/cfg_file.conf" VARFILE2="${SCRIPTDR}/some_script.sh" file_var_check() { cat << EOF ######################### # Dependent Files Check # ######################### EOF local varprefix=$1 ## Check dependent script files (loop over variables) for ((i=0;; i++)) do local chkvar="${varprefix}$i" if [ -z ${!chkvar+x} ]; then echo "$chkvar is unset"; break; else echo "$chkvar is set to '${!chkvar}'" fi if [ ! -f "${!chkvar}" ]; then echo "[FATAL] File '${!chkvar}' not found!" exit 1; fi done } do_main() { echo "Test Script start" file_var_check VARFILE echo "Test Script end" } # Entry Point do_main
直接執行的效果如下:
bash-4.4$ cd filevar_check_script_test bash-4.4$ ls filevar_check.sh bash-4.4$ ./filevar_check.sh Test Script start ######################### # Dependent Files Check # ######################### VARFILE0 is set to './text_file.txt' [FATAL] File './text_file.txt' not found! bash-4.4$ touch text_file.txt bash-4.4$ ./filevar_check.sh Test Script start ######################### # Dependent Files Check # ######################### VARFILE0 is set to './text_file.txt' VARFILE1 is set to './cfg_file.conf' [FATAL] File './cfg_file.conf' not found! bash-4.4$ touch ./cfg_file.conf bash-4.4$ ./filevar_check.sh Test Script start ######################### # Dependent Files Check # ######################### VARFILE0 is set to './text_file.txt' VARFILE1 is set to './cfg_file.conf' VARFILE2 is set to './some_script1.sh' VARFILE3 is unset Test Script end bash-4.4$ ls cfg_file.conf filevar_check.sh text_file.txt bash-4.4$
症狀二:想要把 Bash Shell Script 執行內容全部自動檔向指定檔案,不假執行之手
雖然通常寫 Shell Script 要把輸出內容導向檔案,有不同層次的手段:
- 把 Putty、Mintty、MobaXterm 等 CLI 界面作設定,把紀錄導向檔案:通常懶的在打開檔案的機會比較多
- 簡單的用 > 符號指到任意檔案:但 log 檔名、log 檔案位置也跟著隨意起來。久了這台 Linux 在指令界面執行 ls 就會眼花撩亂,甚至久了就沒人敢動這些檔案,因為不知道有沒有人還需要
- 進階一點用丟背景執行公式 nohup some_command 2>&1 &,但這個跟上一點有相似問題,此外 nohup 預設把內容導向執行目錄底下開一個 nohup.out 檔案,久了就到處都是 nohup.out
- 在 Shell Script 裡面想要負責的把 log 檔名附上,但是執行時,每一行指令都多了一個 >> log 檔名 2>&1 的尾巴,這樣寫了手很痠
- 進階一些把上一點包成一個函數
不過這邊有一個簡易的公式:只要在 Shell Script 裡面先宣告好檔名之後,可以補上以下橘色公式,就能把所有這行之後的輸出內容都導向檔案
#!/bin/bash ## Execution Start Time EXECUTEDATE=$(date +%Y%m%d) ## Log file path LOGDIR=/some/path/to/logs/${EXECUTEDATE}/ LDRLOGFN=$LOGDIR/${SCRIPTFN}_${EXECUTEDATE}.log ## Write all output to the log file exec &> ${LDRLOGFN} ## Do other things below
如此一來,所有 Script 內容都可以正常撰寫,也能順利的把輸出指向檔案,同時可以另開視窗搭配 tail -f 指令,就能同時紀錄內容與查看了。
另外,如果想要累加內容,只要把 &> 多一個大於,改成 &>> 就行了。
症狀三:想要在 Shell Script 所吐出來的輸出內容,每一行添加時間或是檔名等裝飾品
有時候會希望把 Shell Script 的輸出內容,每一行前面都補上一些固定前綴。這在各種程式語言中都會使用各自的 logging library/module 達成一樣功能。但在 Shell Script 裡面,沒有找到方法,可能就會需要用 Function 加工 echo 才能仿效這樣的功能。但是這樣的加工還是有點卡手卡腳,因為要補前綴,就得在每行指令前面呼叫這些加工後的函數。
不過跟症狀二一樣,Shell Script 也有隱藏技能,可以用一行公式,就能達成自動前綴的功效~
這邊的範本會組合症狀二。擴充 exec &> 公式。另外,為了湊一個完整的範本,這邊也把症狀一補進來,加一點點其他我常用的功能。那一行神奇的公式用黃色套底標示。
Script 檔名為「bash_add_logging_prefix.sh」,注意如果想直接跑看看,記得指定紅字的 log 目錄位置。
#!/bin/bash ## Get script filename SCRIPTBN=$(basename $0) SCRIPTFN="${SCRIPTBN%.*}" SCRIPTDR=$(dirname $0) ## Execution Start Time EXECUTEDATE=$(date +%Y%m%d) ## Dependent scripts DEPSCRPT1="${SCRIPTDR}/some_script1.sh" DEPSCRPT2="${SCRIPTDR}/some_script2.sh" DEPSCRPT3="${SCRIPTDR}/some_sql_script1.sql" DEPSCRPT4="${SCRIPTDR}/some_sql_script2.sql" ## Log file path LOGDIR=/some/path/to/logs/${EXECUTEDATE}/ LDRLOGFN=$LOGDIR/${SCRIPTFN}_${EXECUTEDATE}.log ## 偷偷藏 PGSQL 執行 SQL Script 的方便變數組合 PSQLEXE="/usr/bin/psql -h $PGHOST -p $PGPORT -U $PGUSER -d $PGDATABASE -v ON_ERROR_STOP=1" PSQLVALUE="/usr/bin/psql -h $PGHOST -p $PGPORT -U $PGUSER -d $PGDATABASE -v ON_ERROR_STOP=1 -AqtX" file_var_check() { cat <<EOF ######################### # Dependent Files Check # ######################### EOF local varprefix=$1 ## Check dependent script files (loop over variables) for ((i = 1; ; i++)); do local chkvar="${varprefix}$i" if [ -z ${!chkvar+x} ]; then echo "$chkvar is unset" break else echo "$chkvar is set to '${!chkvar}'" fi if [ ! -f "${!chkvar}" ]; then echo "[FATAL] File '${!chkvar}' not found!" exit 1 fi done } function Usage() { cat << EOF Usage : $SCRIPTBN {-B | -S <yyyy-mm-dd> | -T <yyyy-mm>} -B : Process daily data -S : Specify date for processing -T : Specify the whole month for processing EOF exit; } cmd_arg(){ ## Command line options if [ $# -ne 0 ] ; then while getopts "BS:T:" opts ; do case "$opts" in B) echo "Process Mode: B" TYPE="B"; #TYPE="${opts^}" ## 強制轉大寫範例的怪異技巧,純粹參考用 process_date=$(date --date='1 week ago' +%Y-%m-%d) process_month=$(date --date='1 week ago' +%Y-%m) ;; S) echo "Process Mode: S" TYPE="S"; process_date=$(date --date=${OPTARG} +%Y-%m-%d) process_month=$(date --date=${OPTARG} +%Y-%m) ;; T) echo "Process Mode: T" TYPE="T"; process_date=$(date --date="${OPTARG}-01" +%Y-%m-01) process_month=$(date --date="${OPTARG}-01" +%Y-%m) echo "Process date: ${process_date}" echo "Process month: ${process_month}" ;; *) Usage ;; esac done else Usage fi } do_others1(){ echo "Some other contents" } do_others2(){ echo "Yet other contents" ## 兩個小範例 #$PSQLEXE -c 'SELECT 1' -c 'SELECT 2' #$PSQLVALUE -c 'SELECT 3' } do_main() { ################## ## Main process ## ################## ## Add log directory first mkdir -p ${LOGDIR} ## Redirect stderr/stdout to a process substitution that adds the prefix of choice ## And write all output to the log file ## 用 trap 抓 signal 並把 output STDOUT/STDERR 導向檔案,兩種要一起放括號裡面 ## 此外 >& _file_descriptor_ 跟 &> _log_file_name_ 兩者功能不相同也不相關 exec &> >(trap "" INT TERM; sed "s/^/$(date +'[%Y-%m-%d - %H:%M:%S]') /" &>${LDRLOGFN}) ## exit when any command fails set -e ## Script Start cmd_arg "$@" file_var_check DEPSCRPT do_others1 do_others2 } # Entry Point do_main "$@"
執行範例
bash-4.4$ ./bash_add_logging_prefix.sh bash-4.4$ ls bash_add_logging_prefix.sh logs bash-4.4$ cat logs/20211116/bash_add_logging_prefix_20211116.log [2021-11-16 - 23:15:44] Usage : bash_add_logging_prefix.sh {-B | -S <yyyy-mm-dd> | -T <yyyy-mm>} [2021-11-16 - 23:15:44] -B : Process daily data [2021-11-16 - 23:15:44] -S : Specify date for processing [2021-11-16 - 23:15:44] -T : Specify the whole month for processing bash-4.4$ bash-4.4$ ./bash_add_logging_prefix.sh -B bash-4.4$ cat logs/20211116/bash_add_logging_prefix_20211116.log [2021-11-16 - 23:17:24] Process Mode: B [2021-11-16 - 23:17:24] ######################### [2021-11-16 - 23:17:24] # Dependent Files Check # [2021-11-16 - 23:17:24] ######################### [2021-11-16 - 23:17:24] DEPSCRPT1 is set to './some_script1.sh' [2021-11-16 - 23:17:24] [FATAL] File './some_script1.sh' not found! bash-4.4$ touch some_script1.sh bash-4.4$ ./bash_add_logging_prefix.sh -B bash-4.4$ cat logs/20211116/bash_add_logging_prefix_20211116.log [2021-11-16 - 23:17:52] Process Mode: B [2021-11-16 - 23:17:52] ######################### [2021-11-16 - 23:17:52] # Dependent Files Check # [2021-11-16 - 23:17:52] ######################### [2021-11-16 - 23:17:52] DEPSCRPT1 is set to './some_script1.sh' [2021-11-16 - 23:17:52] DEPSCRPT2 is set to './some_script2.sh' [2021-11-16 - 23:17:52] [FATAL] File './some_script2.sh' not found! bash-4.4$ bash-4.4$ touch some_script2.sh bash-4.4$ touch some_sql_script1.sql bash-4.4$ touch some_sql_script2.sql bash-4.4$ ./bash_add_logging_prefix.sh -B bash-4.4$ cat logs/20211116/bash_add_logging_prefix_20211116.log [2021-11-16 - 23:19:48] Process Mode: B [2021-11-16 - 23:19:48] ######################### [2021-11-16 - 23:19:48] # Dependent Files Check # [2021-11-16 - 23:19:48] ######################### [2021-11-16 - 23:19:48] DEPSCRPT1 is set to './some_script1.sh' [2021-11-16 - 23:19:48] DEPSCRPT2 is set to './some_script2.sh' [2021-11-16 - 23:19:48] DEPSCRPT3 is set to './some_sql_script1.sql' [2021-11-16 - 23:19:48] DEPSCRPT4 is set to './some_sql_script2.sql' [2021-11-16 - 23:19:48] DEPSCRPT5 is unset [2021-11-16 - 23:19:48] Some other contents [2021-11-16 - 23:19:48] Yet other contents bash-4.4$
等等。。。。。。。。上面的前綴時戳好像不會動。。。。。。。
之後發現那個神奇的公式只呼叫一次 date 指令。。。。。。。。
好在另外找到一個在這功能上替代 date 指令工具叫做 moreutils 的 ts 指令:用這個指令才能有效發揮這個套件在各大主流 Linux 都有提供,例如 RHEL 系列的 moreutils 套件出沒在 EPEL Repo 裡面 (但相依套件需要啟用 RHEL Optional/Powertool Repo 才行);Debian 系列出沒在 additional Unix utilities;而 Slackware 則是在 Slackbuilds 裡面
測試一下:記得測試前要安裝 moreutils 套件
以下可以看到綠底的時戳確定有一起變化~
之後發現那個神奇的公式只呼叫一次 date 指令。。。。。。。。
好在另外找到一個在這功能上替代 date 指令工具叫做 moreutils 的 ts 指令:用這個指令才能有效發揮這個套件在各大主流 Linux 都有提供,例如 RHEL 系列的 moreutils 套件出沒在 EPEL Repo 裡面 (但相依套件需要啟用 RHEL Optional/Powertool Repo 才行);Debian 系列出沒在 additional Unix utilities;而 Slackware 則是在 Slackbuilds 裡面
#!/bin/bash ## Get script filename SCRIPTBN=$(basename $0) SCRIPTFN="${SCRIPTBN%.*}" SCRIPTDR=$(dirname $0) ## Execution Start Time EXECUTEDATE=$(date +%Y%m%d) ## Dependent scripts DEPSCRPT1="${SCRIPTDR}/some_script1.sh" DEPSCRPT2="${SCRIPTDR}/some_script2.sh" DEPSCRPT3="${SCRIPTDR}/some_sql_script1.sql" DEPSCRPT4="${SCRIPTDR}/some_sql_script2.sql" ## Log file path LOGDIR=/some/path/to/logs/${EXECUTEDATE}/ LDRLOGFN=$LOGDIR/${SCRIPTFN}_${EXECUTEDATE}.log ## 偷偷藏 PGSQL 執行 SQL Script 的方便變數組合 PSQLEXE="/usr/bin/psql -h $PGHOST -p $PGPORT -U $PGUSER -d $PGDATABASE -v ON_ERROR_STOP=1" PSQLVALUE="/usr/bin/psql -h $PGHOST -p $PGPORT -U $PGUSER -d $PGDATABASE -v ON_ERROR_STOP=1 -AqtX" file_var_check() { cat <<EOF ######################### # Dependent Files Check # ######################### EOF local varprefix=$1 ## Check dependent script files (loop over variables) for ((i = 1; ; i++)); do local chkvar="${varprefix}$i" if [ -z ${!chkvar+x} ]; then echo "$chkvar is unset" break else echo "$chkvar is set to '${!chkvar}'" fi if [ ! -f "${!chkvar}" ]; then echo "[FATAL] File '${!chkvar}' not found!" exit 1 fi done } function Usage() { cat << EOF Usage : $SCRIPTBN {-B | -S <yyyy-mm-dd> | -T <yyyy-mm>} -B : Process daily data -S : Specify date for processing -T : Specify the whole month for processing EOF exit; } cmd_arg(){ ## Command line options if [ $# -ne 0 ] ; then while getopts "BS:T:" opts ; do case "$opts" in B) echo "Process Mode: B" TYPE="B"; #TYPE="${opts^}" ## 強制轉大寫範例的怪異技巧,純粹參考用 process_date=$(date --date='1 week ago' +%Y-%m-%d) process_month=$(date --date='1 week ago' +%Y-%m) ;; S) echo "Process Mode: S" TYPE="S"; process_date=$(date --date=${OPTARG} +%Y-%m-%d) process_month=$(date --date=${OPTARG} +%Y-%m) ;; T) echo "Process Mode: T" TYPE="T"; process_date=$(date --date="${OPTARG}-01" +%Y-%m-01) process_month=$(date --date="${OPTARG}-01" +%Y-%m) echo "Process date: ${process_date}" echo "Process month: ${process_month}" ;; *) Usage ;; esac done else Usage fi } do_others1(){ echo "Some other contents" date } do_others2(){ sleep 10 echo "Yet other contents" ## 兩個小範例 #$PSQLEXE -c 'SELECT 1' -c 'SELECT 2' #$PSQLVALUE -c 'SELECT 3' } do_main() { ################## ## Main process ## ################## ## Add log directory first mkdir -p ${LOGDIR} ## Redirect stderr/stdout to a process substitution that adds the prefix of choice ## And write all output to the log file ## 用 trap 抓 signal 並把 output STDOUT/STDERR 導向檔案,兩種要一起放括號裡面 ## 此外 >& _file_descriptor_ 跟 &> _log_file_name_ 兩者功能不相同也不相關 ## 額外從 EPEL 裝 moreutils 使用 ts 指令 exec &> >( ts '[%Y-%m-%d.%H:%M:%S] ' > ${LDRLOGFN} ) ## exit when any command fails set -e ## Script Start cmd_arg "$@" file_var_check DEPSCRPT do_others1 do_others2 } # Entry Point do_main "$@"
測試一下:記得測試前要安裝 moreutils 套件
以下可以看到綠底的時戳確定有一起變化~
bash-4.4# yum install -y moreutils
bash-4.4$ ./bash_add_logging_prefix.sh -B
bash-4.4$ cat logs/20211117/bash_add_logging_prefix_20211117.log
[2021-11-17.12:02:30] Process Mode: B
[2021-11-17.12:02:30] #########################
[2021-11-17.12:02:30] # Dependent Files Check #
[2021-11-17.12:02:30] #########################
[2021-11-17.12:02:30] DEPSCRPT1 is set to './some_script1.sh'
[2021-11-17.12:02:30] DEPSCRPT2 is set to './some_script2.sh'
[2021-11-17.12:02:30] DEPSCRPT3 is set to './some_sql_script1.sql'
[2021-11-17.12:02:30] DEPSCRPT4 is set to './some_sql_script2.sql'
[2021-11-17.12:02:30] DEPSCRPT5 is unset
[2021-11-17.12:02:30] Some other contents
[2021-11-17.12:02:30] 2021年11月17日 下午 12:02:30
[2021-11-17.12:02:40] Yet other contents
bash-4.4$
其實症狀三跟症狀二是同一回事,不過症狀二比較簡易理解,因此先獨立列一份~
這邊用到的手段,簡單解釋,就是把不同 file descriptor 的輸出捕捉起來,使用 trap 指令抓需要的 Signal,對輸出內容的開頭加工,最後選擇性的導向指定檔案。
另外一提, Linux 的 syslog 其實也能夠以 Shell Script 來呼叫,指令叫做 logger,使用這個可以把 log 標準化,甚至導向 OS 的 log 目錄 /var/log/ 底下,達成更加標準化的撰寫。
依照看到的文章(在底下),應該是如下公式就可以了,有興趣的人可以測試一下~
exec &> >(logger -s -t $(basename $0))
以上症狀三的 Script 樣本應該足以當作樣板使用~可以直接摳去用。
參考資料
搜尋 bash check variable filenames exists
How do I tell if a regular file does not exist in Bash? - Stack Overflow
搜尋 bash loop over defined variables
Loop Through Variables - Unix & Linux Stack Exchange
搜尋 check bash variable defined
shell - How to check if a variable is set in Bash? - Stack Overflow
搜尋 bash break loop and continue
Bash break and continue | Linuxize
搜尋 bash script exit on error for commands
How to Exit When Errors Occur in Bash Scripts - intoli
Bash 程式設計教學與範例:Heredoc << 與<<< 的用法
Cuddly, Octo-Palm Tree: Bash functions are better than I thought
搜尋 bash printing fixed width banner
Bash: using printf to display fixed-width padded string – Fabian Lee : Software Engineer
搜尋 bash array
搜尋 bash loop over array
Loop through an array of strings in Bash? - Stack Overflow
搜尋 bash argument
How to Use Command Line Arguments in a Bash Script | Baeldung on Linux
搜尋 bash getopts function
Using getopts inside a Bash function - Stack Overflow
Changing to Uppercase or Lowercase - Shell Scripting Tips
5 Unix Terminal Tips To Boost Your Coding Speed | by Shalitha Suranga | Nov, 2021 | Better Programming
搜尋 bash redirect script output to file
搜尋 bash redirect script output to file exec &>
bash - How to redirect output of an entire shell script within the script itself? - Stack Overflow
Using exec and tee to redirect logs to stdout and a log file in the same time - Unix & Linux Stack Exchange
搜尋 bash add prefix time stdout
搜尋 bash trap prefix for stdout
How to prefix any output in a bash script? - Unix & Linux Stack Exchange
sed Substitution With Variables | Baeldung on Linux
搜尋 bash &>
What does &> do in bash? - Stack Overflow
搜尋 bash logger
Linux 如何將資料寫到 Syslog – Tsung's Blog
搜尋 logger bash script
搜尋 bash script pipe all output to logger
搜尋 bash script exec pipe all output to logger
Linux : how to redirect stdout & stderr to logger? - Unix & Linux Stack Exchange
Redirecting bash script output to syslog - urbanautomaton.com
[Update 2022/3/8] 症狀四:不能同時運作的 shell script,運作時間又抓不準,然而卻需要設定頻繁 cronjob 排程,卻又害怕重複運行。。
要是打算跑的 shell script 不能夠並行執行,然而又需要把 script 用比較高的頻率在 cronjob 裡面執行(例如每 10 分鐘),這種狀況下,就會擔心 shell script 上一回合還沒跑完,下一輪又啟動了。這種類型常常是從他處抓不特定大小資料或是一些批次資料運算/處理的功能。
這時學習大型軟體善用 lock file 機制就是一個好主意~
在 shell script 裡面有提供兩三種 lock file 控制手法,這邊用 flock 指令(現在大多在各主流 linux distribution 的 util-linux 套件裡面),主要是 flock 屬於 advisory lock,不會過度跟OS 牽扯太多。
參考資料中,大多都是直接當指令使用,但我偏好全部塞在 shell script 裡面:東找西找之後,只看到起 subshell 搭配 flock(用小括號);最後總算發現在 manpage 裡面還藏有一個神奇指令。
以下直接從症狀三擴充,紅字標示增加的部份。
Note:裡面用了一個冒號(colon)~這是有點罕見的 shell script 的 TRUE/FALSE 用法
#!/bin/bash ## Get script filename SCRIPTBN=$(basename $0) SCRIPTFN="${SCRIPTBN%.*}" SCRIPTDR=$(dirname $0) ## Execution Start Time EXECUTEDATE=$(date +%Y%m%d) ## Job lock file JBLCK="/tmp/${SCRIPTFN}.lck" ## Dependent scripts DEPSCRPT1="${SCRIPTDR}/some_script1.sh" DEPSCRPT2="${SCRIPTDR}/some_script2.sh" DEPSCRPT3="${SCRIPTDR}/some_sql_script1.sql" DEPSCRPT4="${SCRIPTDR}/some_sql_script2.sql" ## Log file path LOGDIR=/some/path/to/logs/${EXECUTEDATE}/ LDRLOGFN=$LOGDIR/${SCRIPTFN}_${EXECUTEDATE}.log ## 偷偷藏 PGSQL 執行 SQL Script 的方便變數組合 PSQLEXE="/usr/bin/psql -h $PGHOST -p $PGPORT -U $PGUSER -d $PGDATABASE -v ON_ERROR_STOP=1" PSQLVALUE="/usr/bin/psql -h $PGHOST -p $PGPORT -U $PGUSER -d $PGDATABASE -v ON_ERROR_STOP=1 -AqtX" file_var_check() { cat <<EOF ######################### # Dependent Files Check # ######################### EOF local varprefix=$1 ## Check dependent script files (loop over variables) for ((i = 1; ; i++)); do local chkvar="${varprefix}$i" if [ -z ${!chkvar+x} ]; then echo "$chkvar is unset" break else echo "$chkvar is set to '${!chkvar}'" fi if [ ! -f "${!chkvar}" ]; then echo "[FATAL] File '${!chkvar}' not found!" exit 1 fi done } function Usage() { cat << EOF Usage : $SCRIPTBN {-B | -S <yyyy-mm-dd> | -T <yyyy-mm>} -B : Process daily data -S : Specify date for processing -T : Specify the whole month for processing EOF exit; } cmd_arg(){ ## Command line options if [ $# -ne 0 ] ; then while getopts "BS:T:" opts ; do case "$opts" in B) echo "Process Mode: B" TYPE="B"; #TYPE="${opts^}" ## 強制轉大寫範例的怪異技巧,純粹參考用 process_date=$(date --date='1 week ago' +%Y-%m-%d) process_month=$(date --date='1 week ago' +%Y-%m) ;; S) echo "Process Mode: S" TYPE="S"; process_date=$(date --date=${OPTARG} +%Y-%m-%d) process_month=$(date --date=${OPTARG} +%Y-%m) ;; T) echo "Process Mode: T" TYPE="T"; process_date=$(date --date="${OPTARG}-01" +%Y-%m-01) process_month=$(date --date="${OPTARG}-01" +%Y-%m) echo "Process date: ${process_date}" echo "Process month: ${process_month}" ;; *) Usage ;; esac done else Usage fi } do_others1(){ echo "Some other contents" date } do_others2(){ sleep 10 echo "Yet other contents" ## 兩個小範例 #$PSQLEXE -c 'SELECT 1' -c 'SELECT 2' #$PSQLVALUE -c 'SELECT 3' } do_main() { ################## ## Main process ## ################## ## Job lock - 避免 cronjob 還沒跑完又啟動一次 [ "${FLOCKER}" != "$0" ] && exec env FLOCKER="$0" flock -n "$JBLCK" "$0" "$@" || : ## Add log directory first mkdir -p ${LOGDIR} ## Redirect stderr/stdout to a process substitution that adds the prefix of choice ## And write all output to the log file ## 用 trap 抓 signal 並把 output STDOUT/STDERR 導向檔案,兩種要一起放括號裡面 ## 此外 >& _file_descriptor_ 跟 &> _log_file_name_ 兩者功能不相同也不相關 ## 額外從 EPEL 裝 moreutils 使用 ts 指令 exec &> >( ts '[%Y-%m-%d.%H:%M:%S] ' > ${LDRLOGFN} ) ## 如果偏好同時看到畫面跟檔案,可以 > 改用 | tee #exec &> >( ts '[%Y-%m-%d.%H:%M:%S] ' | tee ${LDRLOGFN} ) ## exit when any command fails set -e ## Script Start cmd_arg "$@" file_var_check DEPSCRPT do_others1 do_others2 } # Entry Point do_main "$@"
參考資料
搜尋 bash check variable filenames exists
How do I tell if a regular file does not exist in Bash? - Stack Overflow
搜尋 bash loop over defined variables
Loop Through Variables - Unix & Linux Stack Exchange
搜尋 check bash variable defined
shell - How to check if a variable is set in Bash? - Stack Overflow
搜尋 bash break loop and continue
Bash break and continue | Linuxize
搜尋 bash script exit on error for commands
How to Exit When Errors Occur in Bash Scripts - intoli
Bash 程式設計教學與範例:Heredoc << 與<<< 的用法
Cuddly, Octo-Palm Tree: Bash functions are better than I thought
搜尋 bash printing fixed width banner
Bash: using printf to display fixed-width padded string – Fabian Lee : Software Engineer
搜尋 bash array
搜尋 bash loop over array
Loop through an array of strings in Bash? - Stack Overflow
搜尋 bash argument
How to Use Command Line Arguments in a Bash Script | Baeldung on Linux
搜尋 bash getopts function
Using getopts inside a Bash function - Stack Overflow
Changing to Uppercase or Lowercase - Shell Scripting Tips
5 Unix Terminal Tips To Boost Your Coding Speed | by Shalitha Suranga | Nov, 2021 | Better Programming
搜尋 bash redirect script output to file
搜尋 bash redirect script output to file exec &>
bash - How to redirect output of an entire shell script within the script itself? - Stack Overflow
Using exec and tee to redirect logs to stdout and a log file in the same time - Unix & Linux Stack Exchange
搜尋 bash add prefix time stdout
搜尋 bash trap prefix for stdout
How to prefix any output in a bash script? - Unix & Linux Stack Exchange
sed Substitution With Variables | Baeldung on Linux
搜尋 bash &>
What does &> do in bash? - Stack Overflow
搜尋 bash logger
Linux 如何將資料寫到 Syslog – Tsung's Blog
搜尋 logger bash script
搜尋 bash script pipe all output to logger
搜尋 bash script exec pipe all output to logger
Linux : how to redirect stdout & stderr to logger? - Unix & Linux Stack Exchange
Redirecting bash script output to syslog - urbanautomaton.com
搜尋 bash trap file descriptor
bash - How can I print to stdout from within a trap called during eval - Stack Overflow
搜尋 bash trap
搜尋 bash trap signal
trap(1p) - Linux manual page
bash - How can I print to stdout from within a trap called during eval - Stack Overflow
搜尋 bash trap
搜尋 bash trap signal
trap(1p) - Linux manual page
搜尋 bash exec trap execute time
搜尋 bash exec trap prefix time
搜尋 bash exec trap prefix timestamp
Echo each shell script command with a timestamp - Server Fault
The Bash Trap Trap. Traps are a cool way to implement error… | by Dirk Avery
搜尋 bash record date for each line in script
Prepending a timestamp to each line of output from a command - Unix & Linux Stack Exchange
搜尋 moreutils centos
搜尋 Centos yum Requires: perl(IPC::Run)
RHEL 7.4 - moreutils dependency on perl IPC::Run - Unix & Linux Stack Exchange
NCTU CSCC Mirror Site - CentOS
NCTU CSCC Mirror Site - EPEL
搜尋 cronjob skip if still running
Run cron job only if it isn't already running - Stack Overflow
Prevent duplicate cron jobs running - Server Fault
RPM resource /usr/bin/flock
flock 防止重新執行方法 | 程式狂想筆記
用 flock 防止指令同時間重複執行 – Gea-Suan Lin's BLOG
Linux 防止 Shell 指令稿重複執行教學 - G. T. Wang
Overlapping cron jobs solution using Flock - BlueGrid
Is flock automatically released on process exit? - Stack Overflow
搜尋 flock in shell script
Ensure Only One Instance of a Bash Script Is Running | Baeldung on Linux
Introduction to File Locking in Linux | Baeldung on Linux
RPubs - Linux 小撇步:利用flock來做同步和非同步應用
What is the difference between locking with `fcntl` and `flock`?
What is the functional difference between lockf() and flock() in Linux? - Quora
搜尋 flock file descriptor
bash flock: Why 200? - Stack Overflow
What is the purpose of the : (colon) GNU Bash builtin? - Stack Overflow
: colon command for bash - Super User
其他好習慣
13 Tips & Tricks for Writing Shell Scripts with Awesome UX | by Joseph Matthias Goh | codeburst
Shell Script Best Practices — The Sharat's13 Tips & Tricks for Writing Shell Scripts with Awesome UX | by Joseph Matthias Goh | codeburst
Shell script best practices, from a decade of scripting things | Hacker News
沒有留言:
張貼留言