Bash 15分鐘

Bash 15 分鐘

背景

偶爾翻了之前的bash 編程的書,一些語法和細節發現可能還需要再review 下,然後就翻在線的ABS(Advanced Bash Scripting) ,發現這篇文檔還是太長了,而且英文的東西,當時看了明白了,後面忘記了再去查,就有點需要花點時間找找在哪裏看過。而且記得10年前好像看過一個word版本的bash 指南,寫的相當簡練,現在也忘記名字了,所以想想還是弄個簡表,第一個是自己總結,第二個方便以後查找。我本來想直接用這個Learn X in Y ,因爲他的格式比較符合我想記錄的方式,但是內容上感覺有些地方沒有說全,而且有些地方有錯誤的寫法。我想我就以這篇爲基礎,然後擴充修改下這個bash教程吧,我同時也參考了其他的Bash 15分鐘教程之類的內容,以免遺漏。

在線練習

如果你需要一個在線的比較好的練習環境來學習bash,這裏有個好地方 還有之前老版本,通過老版本里的評論和回答你可以診斷下自己所學是否足夠真材實料。後面或者會根據類似的問題列表,整理出來一個FAQ,相信這樣對BASH編程理解會更深入

內容


#!/bin/bash
# 腳本的第一行叫 shebang,用來告知系統如何執行該腳本:
# 參見: http://en.wikipedia.org/wiki/Shebang_(Unix)
# 如你所見,註釋以 # 開頭,shebang 也是註釋。

# 單行註釋
echo "A comment will follow"  # 語句後的註釋
   # 註釋前面有空格也OK
echo "# this is string start with shebang" # 在引號中不起作用
ehco '# this is anothing string start with shebang' # '#'在引號中不起作用
echo the \# string      # 不在引號中的'#'要轉義

# 每一句指令以換行或分號隔開:
echo 'This is the first line'; echo 'This is the second line'

# 聲明一個變量:
Variable="Some string"

# 下面是錯誤的做法:
Variable = "Some string"
# Bash 會把 Variable 當做一個指令,由於找不到該指令,因此這裏會報錯。

# 也不可以這樣:
Variable= 'Some string'
# Bash 會認爲 'Some string' 是一條指令,由於找不到該指令,這裏再次報錯。
# (這個例子中 'Variable=' 這部分會被當作僅對 'Some string' 起作用的賦值。)

# 使用變量:
echo $Variable
echo "$Variable"
echo '$Variable'
# 當你賦值 (assign) 、導出 (export),或者以其他方式使用變量時,變量名前不加 $。
# 如果要使用變量的值, 則要加 $。
# 注意: ' (單引號) 不會展開變量(即會屏蔽掉變量)。

# 變量加雙引號和不加雙引號的區別
para="a b c d"
ls $para                    # 使用a,b,c,d 四個參數執行ls 相當於ls a b c d
ls "$para"                  # 使用一個參數執行ls ,相當於 ls "a b c d"

## 雙括號結構中可以使用類似C語言的 cond?result-if-true:result-if-false
(( var = 1>2?3:4))

: # 冒號爲空指令文件,返回爲0

# 單花括號中的?可以用來檢測變量是否被設置
# ${var?error_info},可以用來做必要參數檢查

: ${var?}
: ${var?test message}

# 空指令版本的註釋
: <<MULTILINECOMMENT
coomandd
\sds
~2@*
MULTILINECOMMENT

# 擴展單引號中被轉義的字符串爲ACSII
echo $'\n\n\n'              

# 變量賦值,使用命令執行結果,等同於echo `cat /etc/hostname`
echo $(cat /etc/hostname)

# 變量擴展或者置換

${param}                    # 等同$param,特定情況下這種${param}的更嚴格的書寫模式才工作         
${param:-default}           # 建議這種,當param 被decare 但是沒有賦值的時候,該表達式工作良好
${param:=default}           # If parameter not set, set it to default
${param:+default}           # If parameter set ,use alt, else use null
${param?error_mesage}       # If param not set ,error message
${#var}                     # var string length,if var is array ,return the first
${var#pattern}              # 從var中前面移除最短的pattern匹配
${var##pattern}             # 從var中前面移除最長的pattern匹配
${var%pattern}              # 從var中後面移除最短的pattern匹配
${var%%pattern}             # 從var中前面移除最長的pattern匹配

# 變量截取
${var:pos}                  # 從var的pos 位置開始截取,直到最後
${var:pos:len}              # 從var的pos 位置開始截len長度
${var/pattern/replace}      # 從var中以replace替換第一個Pattern匹配
${var//pattern//replace}    # 從var中以replace替換所有Pattern匹配
${var/#pattern/replace}     # 如果var的開頭匹配pattern,拿replace 替換第一個匹配
${var/%pattern/replace}     # 如果var的結尾匹配pattern,拿replace 替換第一個匹配

${!varprefix*}              # 獲取所有以prefix開頭的變量 同${!varprefix@}

# 內置變量:
# 下面的內置變量很有用
echo "Last program return value: $?"
echo "Script's PID: $$"
echo "Number of arguments: $#"
echo "Scripts arguments: $@"
echo "Scripts arguments separated in different variables: $1 $2..."

: << SPECIALVARS
"$*"                        # 所有命名參數  
"$@"                        # TODO
"$?"                        # 命令、腳本的退出狀態
"$!"                        # 最後一個後臺執行的任務的PID
"$_"                        # 上一個命令的最後一個內容參數,如果命令沒給任何參數,返回命令本身
"$$"                        # 腳本PID
$#                          # 腳本傳入參數數量
$@                          # 腳本傳入的所有參數
${!#}                       # 腳本最後一個傳入參數
$1                           # 腳本第一個參數,對於1-9參數可以使用$1- $9
${10}                       # 第九個參數之後,用 ${10} ${11} 的方式
$1_                         # 位置參數變量後跟下劃線,可以防止參數沒有輸入的情況

SPECIALVARS

# 讀取輸入:
echo "What's your name?"
read Name # 這裏不需要聲明新變量
echo Hello, $Name!

# 根據上一個指令執行結果決定是否執行下一個指令
echo "Always executed" || echo "Only executed if first command fails"
echo "Always executed" && echo "Only executed if first command does NOT fail"

# 數值比較單方括號內
a=2
[ 1 -eq $a ] && echo true || echo false  # false
[ 1 -ne $a ] && echo true || echo false  # true 
[ 1 -gt $a ] && echo true || echo false  # false
[ 1 -ge $a ] && echo true || echo false  # false
[ 1 -lt $a ] && echo true || echo false  # true
[ 1 -le $a ] && echo true || echo false  # true
# 數值比較也可以用雙方括號,和上面基本一致。只不過把單方擴號換成雙方括號而已

# 數值比較雙括號內,此處的 > < 不用轉義
(( 1 < "$a" )) && echo true || echo false # true
(( 1 > "$a" )) && echo true || echo false # false
(( 1 <= "$a" )) && echo true || echo false # true
(( 1 >= "$a" )) && echo true || echo false # false

# 特別注意點,bash 中數值比較不支持浮點數,只支持整數的比較,比如 
[ 1.23 -le 3 ] && echo true #將報錯 -bash: [: 1.23: integer expression expected
(( 1.23 < 3 )) && echo true #將報錯 -bash: ((: 1.23 < 3 : syntax error: invalid arithmetic operator (error token is ".23 < 3 ")

# 字符串比較
s="A"
[ "s" == "$s" ] && echo true || echo false # false
[ "s" != "$s" ] && echo true || echo false # true

# 這裏有個地方要特別注意
#下面本意是比較ascii, 小a 應該大於大A,雖然執行結果也返回true,但是實際執行的卻和用戶意圖不一樣
# 下面這句實際是把"a"的值重定向到 A文件中了,重定向執行成功,所以方括號裏面的 結果返回true
[ "a" > "$s" ] && echo true || echo false # true 

# 正確應該對方括號中的> < 都進行轉義,否則系統會理解成重定向符號。
[ "X" \< "$s" ] && echo true || echo false # true
[ "a" \> "$s" ] && echo true || echo false # true 
[ -n "$s" ] && echo "string is not null" || echo "string is null" # string is not null
[ -z "$s" ] && echo "string is null" ||echo "string is not null"  # string is not null

# 文件test

[ -x /bin/bash ] && echo "bash is executable"
# 其他的test 可以用help test 列舉。

# 通常的 if 結構看起來像這樣:
# 'man test' 可查看更多的信息,由於$USER 爲字符串,所以我們需要用!= 這些字符串比較符號
if [ $Name != $USER ]
then
    echo "Your name isn't your username"
else
    echo "Your name is your username"
fi

# 在 if 語句中使用 && 和 || 需要多對方括號
if [ $Name == "Steve" ] && [ $Age -eq 15 ]
# 也可以寫成(注意雙方括號中的&&) if [[ $Name == "Steve" && $Age -eq 15 ]]
# 也可以寫成(注意單方括號中的-a) if [ $Name == "Steve" -a $Age  -eq 15 ]
then
    echo "This will run if $Name is Steve AND $Age is 15."
fi

if [ $Name == "Daniya" ] || [ $Name == "Zach" ]
# 也可以寫成(注意雙方括號中的||) if [[ $Name == "Daniya" || $Name == "Zach" ]]
# 也可以寫成(注意單方括號中的-o) if [ $Name == "Daniya" -o $Name == "Zach" ]
then
    echo "This will run if $Name is Daniya OR Zach."
fi

# 花括號擴展
touch a{1,2,3}              # 會在當前目錄創建a1,a2,a3
touch {a,b,c}{1,2,3}        # 會創建a1,a2,a3,b1,b2,b3,c1,c2,c3
echo {a..z}                 # 從a擴展到z .a,b,c,d,e....x,y,z
echo {1..9}                 # 從1至9

# Inline group,或者可以成爲匿名函數
{ a=123; } ; echo $a        # 花括號中的變量可以在腳本後續代碼中使用,不像function中的變量,出了function的作用域,function裏面的變量無法在外部使用

# 數學計算,有三種方式
z=1
z=`expr $z + 3` # backtricks
echo $z
z=$((z+3))      # 雙括號
echo $z
let "z += 3"    # let
echo $z

# 隨機數
MAX=7
MIN=5

# 生成 0-5隨機數
echo $(($RANDOM % $MIN)) 
# 生成5-7 隨機數
echo $(($RANDOM %($MAX-$MIN) +$MIN ))

# 與其他編程語言不同的是,bash 運行時依賴上下文。比如,使用 ls 時,列出當前目錄。
ls

# 指令可以帶有選項:
ls -l # 列出文件和目錄的詳細信息

# 前一個指令的輸出可以當作後一個指令的輸入。grep 用來匹配字符串。
# 用下面的指令列出當前目錄下所有的 txt 文件:
ls -l | grep "\.txt"

# 重定向輸入和輸出(標準輸入,標準輸出,標準錯誤)。
# 以 ^EOF$ 作爲結束標記從標準輸入讀取數據並覆蓋 hello.py :
# EOF這種方式爲HERE String 
cat > hello.py << EOF
#!/usr/bin/env python
from __future__ import print_function
import sys
print("#stdout", file=sys.stdout)
print("#stderr", file=sys.stderr)
for line in sys.stdin:
    print(line, file=sys.stdout)
EOF

# HereString 前面帶-號,可以抑制文檔內部的開頭tab,注意不是space
cat <<-ENDOFMESSAGE
    This is line 1 of the message.
    This is line 2 of the message.
    This is line 3 of the message.
    This is line 4 of the message.
    This is the last line of the message.
ENDOFMESSAGE

# 重定向可以到輸出,輸入和錯誤輸出。
python hello.py < "input.in"
python hello.py > "output.out"
python hello.py 2> "error.err"
python hello.py > "output-and-error.log" 2>&1
python hello.py > /dev/null 2>&1
# > 會覆蓋已存在的文件, >> 會以累加的方式輸出文件中。
python hello.py >> "output.out" 2>> "error.err"

# 覆蓋 output.out , 追加 error.err 並統計行數
info bash 'Basic Shell Features' 'Redirections' > output.out 2>> error.err
wc -l output.out error.err

# 以 "#helloworld" 覆蓋 output.out:
cat > output.out <(echo "#helloworld")
echo "#helloworld" > output.out
echo "#helloworld" | cat > output.out
echo "#helloworld" | tee output.out >/dev/null

# 清理臨時文件並顯示詳情(增加 '-i' 選項啓用交互模式)
rm -v output.out error.err output-and-error.log

# Bash 的 case 語句與 Java 和 C++ 中的 switch 語句類似: 注意結尾的雙;;是爲了轉義;
case "$Variable" in
    # 列出需要匹配的字符串
    [[:upper:]]) echo "The letter is upper.";;
    [[:lower:]]) echo "The letter is lower.";;
    [0-9]) echo "It 's a number";;
    *) echo "may be special letter ";;
esac

# 循環遍歷給定的參數序列:

for Variable in {1..3}
# 或 for Variable in "A" "B" "C"
# 或 for Variable in `seq 2 6`
# 或 for Variable in $(ls)
# 或 for variable in *.sh;  # *.sh 在bash中會擴展成本目錄下所有.sh結尾的文件
do
    echo "$Variable"
done

# 或傳統的 “for循環” :
for ((a=1; a <= 3; a++))
do
    echo $a
done

# while 循環:
while [ true ]
do
    echo "loop body here..."
    break # break 可以跳出整個循環 # continue ,可以跳過該次循環
done

# Util 循環

until false
do 
    echo "loop body here..."
done

# Seletc 實現菜單選擇

select vegetable in "A" "B" "C" "D"
do 
    echo "your fav is $vegetable"
    echo "Yuck!"
    echo 
    break
done

# 你也可以使用函數
# 定義函數:
function foo ()
{
    echo "Arguments work just like script arguments: $@"
    echo "And: $1 $2..."
    echo "This is a function"
    return 0
}

# 更簡單的方法
bar ()
{
    echo "Another way to declare functions!"
    return 0
}

# 調用函數
foo "My name is" $Name

# 正則表達式(在雙方括號中使用)

t="abc123"
[[ "$t" == abc* ]]         # true (globbing比較)
[[ "$t" == "abc*" ]]       # false (字面比較)
[[ "$t" =~ [abc]+[123]+ ]] # true (正則表達式比較)
[[ "$t" =~ "abc*" ]]       # false (字面比較)

# 數組操作
base64_charset=( {A..Z} {a..z} {0..9} + / = )
echo $base64_charset                # 只會輸出數組的第一個項目的值
echo ${base64_charset[*]}           # 輸出A-Z a-z 0-9 + / =
echo ${base64_charset[0]}           # 輸出A
echo ${#base64_charset[*]}          # 輸出數組長度65
echo ${base64_charset[*]:1:2}       # 數組分片,輸出B C
base64_charset[2]="O"               # 對指定項目賦值,或者修改值   
echo ${!base64_charset[*]}          # 對指定輸出所有數組索引

# for 遍歷數組
for i in ${base64_charset[*]}
do 
    echo $i
done

for i in ${!base64_charset[*]}
do 
    echo ${base64_charset[$i]}
done

# 有很多有用的指令需要學習:
# 打印 file.txt 的最後 10 行
tail -n 10 file.txt
# 打印 file.txt 的前 10 行
head -n 10 file.txt
# 將 file.txt 按行排序
sort file.txt
# 報告或忽略重複的行,用選項 -d 打印重複的行
uniq -d file.txt
# 打印每行中 ',' 之前內容
cut -d ',' -f 1 file.txt
# 將 file.txt 文件所有 'okay' 替換爲 'great', (兼容正則表達式)
sed -i 's/okay/great/g' file.txt
# 將 file.txt 中匹配正則的行打印到標準輸出
# 這裏打印以 "foo" 開頭, "bar" 結尾的行
grep "^foo.*bar$" file.txt
# 使用選項 "-c" 統計行數
grep -c "^foo.*bar$" file.txt
# 如果只是要按字面形式搜索字符串而不是按正則表達式,使用 fgrep (或 grep -F)
fgrep "^foo.*bar$" file.txt 

# 以 bash 內建的 'help' 指令閱讀 Bash 自帶文檔:
help
help help
help for
help return
help source
help .

# 用 man 指令閱讀相關的 Bash 手冊
apropos bash
man 1 bash
man bash

# 用 info 指令查閱命令的 info 文檔 (info 中按 ? 顯示幫助信息)
apropos info | grep '^info.*('
man info
info info
info 5 info

# 閱讀 Bash 的 info 文檔:
info bash
info bash 'Bash Features'
info bash 6
info --apropos bash

# Bash 調試
set -u #  Treat unset variables as an error when substituting 對未設置值的變量報告錯誤
set -e #  如果命令的執行返回不爲0則退出
set -n # 執行語法檢查而不要運行腳本,等同於bash -n script.sh
set -v # 輸出每個命令,在執行每個命令之前,等同於bash -v script.sh
set -x # 和-v 類似,但是輸出時,會在每個命令前添加+,這樣可以快速區分出命令和輸出

# trap ,在接收到指定信號後,執行特定action 
# 信號可以trap -l 列出
trap 'echo script exit' EXIT                            # 在腳本退出時,打印上面內容
trap 'rm -f $TEMPFILE; exit $USER_INTERRUPT' TERM INT   # 在按CTRL+C時,執行清理任務
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章