Bash 15分鐘

Bash 15 分鐘

背景

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

在線練習

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

內容

#!/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時,執行清理任務
相關文章
相關標籤/搜索