bash script取亂數

在 bash script 裡,可以用 ${RANDOM} 取亂數,例如

echo "random number: ${RANDOM}"

就會出現一個隨機的數字。

那如果想限制在 20~40 這個範圍內的話,可以怎麼做呢?這時候可以用 bash 的 % ,取餘數的運算子來做,例如

echo "random number(0~10): $((20 + RANDOM % 20))"

參考資料

Shell Script 最佳實踐

來源:Shell Script Best Practice

這篇寫的很好,整理並摘錄裡面的內容

  1. 用 bash,不要用 zsh / fish
  2. 一開頭的 Sha-Bang 統一都寫 #!/usr/bin/env bash,這樣可以確保都是用相同的 shell 來執行,我自己是都寫 #!/bin/bash
  3. 副檔名用 .sh
  4. 第二行寫 set -o errexit,只要裏面有一行錯誤,就離開,不要繼續。
  5. 第三行寫 set -o nounset,只要有未設定的變數,就離開,不要繼續。
  6. 第四行寫 set -o pipefail,只要 pipe 過程中的指令有錯誤,就離開,不要繼續。
  7. 加上判斷 TRACE 環境參數是否存在的腳本,當有設定這個環境變數時,就加上 set -o xtrace,這可以幫助除錯。
  8. 儘量使用 [[ ]]取代 [ ][[ ]]是 bash內建的關鍵字,基本上作用跟 []或 test 一樣,可以減少額外消耗,而且有更多功能。
  9. 存取變數時,前後都加上雙引號。
  10. 函式裏面用 local 宣告變數。
  11. 加上 -h--help來說明自己怎麼使用。
  12. 印出錯誤訊息時,要導向到 stderr :echo 'Something unexpected happened' >&2
  13. 呼叫指令若有帶參數,最好帶長的,例如用 --silent代替 -s,這樣可讀性比較高。
  14. 一開始就切換到腳本的目錄,例如: cd "$(dirname "$0")" 我想這個應該是要確定腳本所要作用的目錄,所以加上註解,或是印出當前所在目錄,都對於後續維護的人會很有幫助。
  15. shellcheck來檢查腳本

作者有提供一個範本,可以直接複製來修改:

#!/usr/bin/env bash

set -o errexit
set -o nounset
set -o pipefail
if [[ "${TRACE-0}" == "1" ]]; then
    set -o xtrace
fi

if [[ "${1-}" =~ ^-*h(elp)?$ ]]; then
    echo 'Usage: ./script.sh arg-one arg-two

This is an awesome bash script to make your life better.

'
    exit
fi

cd "$(dirname "$0")"

main() {
    echo do awesome stuff
}

main "$@"

基本上就包含了前面所提到的重點,這裡額外一提的是有 main,這對於理解腳本來說,也是很重要的事情。

bash 檢查檔案是否為空的

我忘記為什麼會要找這個,總之,要檢查檔案不是空的,可以用 -s 。

這用 man test 可以查到, -s 的說明是這樣的:True if file exists and has a size greater than zero.

當檔案存在而且長度大於 0 時,回傳 True。換言之,False 時,就代表檔案的長度是 0 (空的)。

我是在 StackOverflow 找到的:linux – How to check if a file is empty in Bash? – Stack Overflow

if [ -s diff.txt ]; then
        # The file is not-empty.
        rm -f empty.txt
        touch full.txt
else
        # The file is empty.
        rm -f full.txt
        touch empty.txt
fi

更新sudoers檔案內容

一般更新 /etc/sudoers 檔案內容都是使用 visudo 指令進行;若是要管理 sudoer ,Ansible 可以用 community.general.sudoers 模組來管理。

那如果要在腳本裡更新,該怎麼做呢?這時候可以參考 How do I edit /etc/sudoers from a script? 裡的作法來進行。

這作法蠻 tricky 的,主要是利用 EDITOR 環境變數把 visudo 預設的編輯器換為 tee -a ,這樣就不會開啟 vi 編輯器,而會改用 tee 來處理。來看看這行指令

echo 'foobar ALL=(ALL:ALL) ALL' | sudo EDITOR='tee -a' visudo
  1. | 的左邊是很單純的 echo,這裡可以放置要放進去的設定
  2. | 的右邊就是 sudo 跟 visudo ,這是用 sudo 執行 visudo 指令,並且把編輯器換為 tee -a

這樣組合以後,就可以把設定放到 /etc/sudoers 裏面去了。

另外一種比較單純的作法就是在 /etc/sudoers.d 目錄下去新增檔案,新增以後,因為 /etc/sudoers 裏面有寫 #includedir /etc/sudoers.d ,所以會引用到 /etc/sudoers.d 目錄下的檔案。

bash arguments cheatsheet

記錄關於 bash argument 的幾則常用語法。

argument 一般翻譯為引數或參數,帶有 — 的,會被稱為選項。

prog arg1 arg2 arg3
prog --output option1 arg1

arg1, arg2, arg3 就是引數;–output option1 就是選項。

寫 shell script 時,是不管 — 的,在 prog 之後的,都叫做引數。以下用法均以 prog arg1 arg2 arg3 作為範例

用法說明結果
$#取得引數個數3
$@所有引數arg1 arg2 arg3
myArray=(“$@”)轉為 Array 會印出 arg1,要遍訪,需用 for:
myArray=(“$@”)
echo “${myArray}”
只印出Array第一個元素arg1
for arg in “${myArray[@]}”; do echo -n “${arg},”; done遍訪Array所有元素arg1,arg2,arg3,
myArray=( “$@” )
arraylength=${#myArray[@]}
for (( i=0; i<${arraylength}; i++ ));
do
echo “${myArray[$i]}”
done
遍訪Array所有元素的另一個作法,用索引方式。arg1
arg2
arg3
if ["$#" -eq 0]; then echo "no argument"; fi判斷是有否引數若無引數,印出 “no argument”
B=(“${myArray[@]:1:2}”)
echo “${B[@]}”
做slicearg2 arg3
version=4.7.1
A=( ${version//./ } )
echo “${A[@]}”
split4 7 1
a=”HELLO WORLD”
echo "${a,,}"
echo “${a,,[AEIUO]}”
轉小寫hello world
HeLLo WoRLD
a=”hello world”
echo "${a^^}"
轉大寫HELLO WORLD

參考資料

atuin

atuin 可以把指令歷史改放到 sqlite db 裡,提供了 fancy 的畫面跟功能,還蠻酷的。它在雲端有提供 sync 伺服器,可以把指令同步上去,我本來在想這會有安全疑慮吧,畢竟有些時候指令就會帶密碼。不過,atuin 已經有做加密了,放上去是安全的。

atuin sync 伺服器也可以自行架設,除了可以同步之外, sync 伺服器還有提供一個類似 github graph 的功能,可以看到活動圖,蠻有趣的。

安裝

bash <(curl https://raw.githubusercontent.com/ellie/atuin/main/install.sh)

如果想試試預設的 sync 伺服器,就先用 atuin register 來註冊

atuin register -u <USERNAME> -e <EMAIL> -p <PASSWORD>

若不需要,就匯入目前的 history

atuin import auto

設定

如果是使用 bash ,就用以下指令,把設定放到 .bashrc

curl https://raw.githubusercontent.com/rcaloras/bash-preexec/master/bash-preexec.sh -o ~/.bash-preexec.sh
echo '[[ -f ~/.bash-preexec.sh ]] && source ~/.bash-preexec.sh' >> ~/.bashrc
echo 'eval "$(atuin init bash)"' >> ~/.bashrc

若是 zsh ,就改用以下指令,把設定放到 .zshrc 裡

echo 'eval "$(atuin init zsh)"' >> ~/.zshrc

設定好,就關掉終端機,開新的終端機,或者是登出,重新再登入,設定就生效了。

使用

使用很簡單,按下 ctrl+r 或是按上,就會看到 atuin 的畫面,輸入字母,上方就會篩選出相關的指令。

那也可以打指令去找,例如找昨天下午3點以前的 ls 指令

atuin search --exit 0 --after "yesterday 3pm" ls

相當簡單。

架設 sync 伺服器的部份有空再來試好了,目前就先都以本機使用為主。

想法

其實就現在來說,使用 ctrl+r 就已經很方便,而且也不需要安裝,直接就能使用。未來會有需要集中管理指令歷史功能的強烈需求嗎?我想可能是沒有,就先用一陣子試試看好了,一周以後再來評估是否要繼續使用。

Bash 4 的 hash table

Python 的 dict 很方便,寫 bash 時,自然會想 Bash 到底有沒有這個呢?

在 StackOverflow 上找到這篇 How to define hash tables in Bash? ,裏面就介紹了用法:

# Python 語法:animals = {'moo': 'cow', 'woof': 'dog'}
# 宣告1
declare -A animals
animals=( ["moo"]="cow" ["woof"]="dog")
# 宣告2
declare -A animals=( ["moo"]="cow" ["woof"]="dog")
# 取某個鍵對應的內容,跟 Python 的 animals['moo'] 一樣。
echo "${animals[moo]}"
# Iterate
for sound in "${!animals[@]}"; do echo "$sound - ${animals[$sound]}"; done

sort month in bash

今天用 sort 去排序月份字串,明明說可以用 -M 來排序,可是實際結果卻不行。使用 –debug 來察看如何排序,卻發現有 “no match for key” 的訊息,這就奇怪了。

找了半天,才找到這篇 bash – Shell sort by month ,才明白這跟 locale 有關係,因為我的 locale 設定為 zh_TW.utf-8 ,sort 在判讀月份時,是使用當地的月份來判讀,而不認英文的月份。解決方法就是加上 LANG 或 LC_TIME 即可:LANG=c sort -k1M your_file

git 不能加空目錄的變通方法

git 沒辦法加空目錄,沒有任何選項可以用。Stackoverflow (How do I add an empty directory to a Git repository? – Stack Overflow) 上提到的變通方法就是為這些空目錄添加一個空白的檔案,檔名看是要用 .gitignore 或是 README 之類的就可以。

那要找所有空目錄的話,該怎麼找?find 就可以做到這點 (參考 bash – finding empty directories unix – Stack Overflow) :

find . -type d -empty -print

那麼,要為每個空目錄加上空白檔案的話,再利用 -exec 參數就可以了:

find . -type d -empty -exec touch {}/README \;