快捷鍵:
ctrl+L: 清除屏幕并移到頂部
ctrl+U: 刪到行首(剪) ctrl+A: 移到行首
ctrl+K: 刪到行尾(剪) ctrl+E: 移到行尾
ctrl+b, f, (左移, 右移)
alt+b,f (前, 后移一個單詞)
ctrl+1 等同于clear
ctrl+d delete, ctrl+w 刪到單詞首
alt+t 詞換位, ctrl+t 字符換位 (與前一個)
alt+d, alt+backspace 剪切至詞首, 詞尾
ctrl+y 粘貼
$ ’Something wrong happend’ >&2
解釋:
>
代表重定向輸出
&
表示接下來的是一個文件描述符數(shù)字(file descriptor number)
2
表示stderr
所以就是把上述字符串輸出到stderr里去的意思,
如果是$ ”str” <2
(沒有&), 則會輸出到一個叫2的文件里去
echo
-n
: 可以取消結(jié)尾的換行
-e
: 解釋字符串里的轉(zhuǎn)義符
擴展(通配符)
$ ~/walker # 表示用戶目錄下的walker目錄
$ ~walker # 表示名為walker的目錄
$ ~+ # 擴展為當前目錄, 等同于 pwd
$ echo d{a, e, I, o, u}g 輸出: dag, deg, dig, dog, dug # 大括號內(nèi)不要有空格(否則會當成參數(shù))
$ echo {j{p,pe}g, png} —> 嵌套
波浪線, 方括號的括號都是基于”路徑”的, 如果當前路徑?jīng)]有匹配到對應(yīng)的文件名, 則會變成字符串原樣輸出, 而大括號則不然, 是基于”邏輯”的, 只管擴展, 不會去探測擴展后對應(yīng)的路徑存不存在, 因此可能報錯文件不存在.
如echo [a,b].txt
, 如果不存在a.txt, b.txt, 則會變成”[a,b].txt”
這樣一個輸出, 而{a,b}.txt
則一定會擴展成a.txt, b.txt
例外:
在用..
來擴展時, 如果系統(tǒng)無法理解, 則不會擴展, 如{1..5}
會擴展成1,2,3,4,5, 但{ab..123}
, 則會變成字符串
但是前導(dǎo)0不參與路徑匹配: {01…5}
# 01,02,03,04,05 (幾個零都可以)
步長:{0..8..2} (未測試成功
) # 要打開哪個shopt開關(guān)?
活用:
$ echo .{mp{3..4},m4{a,b,p,v}} # 匹配了: .mp3 .mp4 .m4a .m4b .m4p .m4v
$ mkdir {2007..2009}-{01..12} # 建了2007-2009每年12個目錄
for I in {1..4}
注意驚嘆號的使用(類似正則里的^
)
$ echo ${!S*} # 返回所有以S開頭的”變量名”, 如SHELL, SSH..等
另兩種轉(zhuǎn)義(string interpolation
):
$ echo date is $(date) # 即包在$(…)中
$ echo date is `date` # 包在反引號中
但是要計算2+2, 只有echo $((2+2))
這種形式, 反引號就不行了
[[:alnum:]], [[:digit:]]等預(yù)置的字符類擴展見: https://wangdoc.com/bash/expansion.html 很豐富, 建議詳讀.
(?, \*, +, @, !)
則為匹配的個數(shù), 分別是(0或1, 0或多, 一或多, 一個, 非一個), 如song@(.)mp3
等同于song.mp3,
是的, 不同于正則, 它是先規(guī)定個數(shù), 再設(shè)定匹配字串
注: 需要打開
shopt -s extglob
雙引號碰到$, 反引號和反斜杠都會自動擴展, 所以echo “$SHELL”
等同于echo echo \$SHELL
雙引號能保留”輸出”的格式, 比如 echo `cal`, 格式就沒了, 自己試試看? 而echo “$(cal)”
則可以保留格式:
$ echo "$(cal)"
六月 2020
日 一 二 三 四 五 六
1 2 3 4 5 6
7 8 9 10 11 12 13
14 15 16 17 18 19 20
21 22 23 24 25 26 27
28 29 30
大段文字輸入可以用
$command << token
your long inputs
token
等同于: echo "your long inputs" | command
即把echo的輸出作為command的輸入, 這個一般用于多行文本
texts里面可以進行使用變量, 但是如果把token用雙引號包起來就不能解釋變量了.
如果只是簡單字符串, 用下面更明確:
$ command <<< ‘text’
如: $ cat <<< “hello world”
, 一樣等同于 $ echo “hello world” | cat
<<<
還有一個作用就是把變量值用這種方式能變成標準輸入, 這樣被”計算”出來的值也能用于只接受標準輸入的命令了, 比如read
變量
printenv PATH
與 echo $PATH
等同
解釋變量中的變量, 比如$PATH
, 不是想象中的$嵌套: ${${myvar}}
, 應(yīng)該這么用
$ myvar=PATH
$ echo ${!myvar}, # 即多加一個驚嘆號
$?
: 上一個命令的退出碼(0成功, 1失敗)
$$
: 當前Shell進程的ID
$_
: 上一個命令的最后一個參數(shù)
$!
: 最后一個后臺執(zhí)行的異步命令的進程ID
$0
: 當前Shell的名稱
$-
: 當前shell的啟動參數(shù), $@
, $#
表示腳本的參數(shù)數(shù)量
$?
命令除了取出上一個命令的返回值, 也可以取出上一個函數(shù)的返回值
${varname-:value}
取值, 如果不存在則返value, 但不賦值
${varname=:value}
取值, 如果不存在則返value, 順便賦值
${varname+:value}
如果有值則返value(而不是值本身), 沒有值則為空, 所以這個時候的value一般用一個標識符號就好了
${varname?:value}
取值, 如果不存在就報錯并把value作為錯誤錯誤打印出來
比如 $ filename=${$1:?”filename missing”}
從腳本中取第一個參數(shù)作為文件名, 發(fā)現(xiàn)沒有文件名就報錯退出
變量都是字符串, 可以用declare
來進行一些限定
$ declare -I v1=13 v2=14 v3=v1+v2 # 聲明為integer
$ echo $v3
# 這樣更快:
$ let v=13+14 # 如果習(xí)慣了=兩邊加空格, 則包到引號里: $ let “v = 13 + 14”
字符串
${#”string”}
長度
${varname:offset:length}
切片(變量名不需要美元符號)
刪除: (# 和 ## 的區(qū)別就是貪婪與否的區(qū)別)
$ phone="555-456-1414"
$ echo ${phone#*-}
> 456-1414
$ echo ${phone##*-}
> 1414
替換: ${variable/#pattern/string}
注意, #
左邊多了一個 /
, 右邊多了替換字串
以上, 都是從頭匹配, 從尾部匹配把 #
換成 %
任意位置匹配則換成/, 所以就成了你們最熟悉的語法:varname/search/replace
這個時候再回頭看
/#
,/%
, 不過是/
語法的修飾符罷了(限定起始方向)
${varname^^}
, ${varname,,}
# 轉(zhuǎn)大寫, 轉(zhuǎn)小寫
數(shù)值運算
逗號是求值, 如
$ echo $((foo = 1+2, 3*4))
輸出為12, 但foo的值是3, 依次計算, 輸出是逗號后面的
expr
命令等同于雙括號:expr 3+5
與$((3+5))
同義
行操作
Bash內(nèi)置Readline庫, 默認采用Emacs快捷鍵, 切換:
$ set -o vi 或 $ set -o emacs
切換目錄/堆棧
不管你cd到哪個了哪個目錄, 想回到cd前的目錄, 用cd -
就行了
pushd
, popd
則可以把目錄推到堆棧里, 演示:
$ pushd 2
/test/1/2 /test/1
$ pushd 3
/test/1/2/3 /test/1/2 /test/1
$ pushd 4
/test/1/2/3/4 /test/1/2/3 /test/1/2 /test/1
$ dirs # 其實每一次pushd都會把當前堆棧dirs出來
/test/1/2/3/4 /test/1/2/3 /test/1/2 /test/1
$ cd /tmp
$ dirs # 觀察, cd其實只是把頂層給改了, 不會增減層級
/tmp /test/1/2/3 /test/1/2 /test/1
$ cd /usr
$ dirs # 驗算
/usr /test/1/2/3 /test/1/2 /test/1
現(xiàn)在你知道 了, cd永遠只是更改頂棧, 大多數(shù)情況下, 你可以用pushd
來替換cd
, 這樣你就有了后退權(quán)了
此時你再popd
, 目錄會順利切到/test/1/2/3
, 不管你進行過多少次cd
, 第二層都不會變并且能直接pop
出來
如果你查看堆棧, 要從第4個開始后退(0為起始), 那么可以把從3開始(不是4)的記錄提到頂層來(然后再popd
):
$ pushd +3
(加號不可省)
注意, 此時0, 1, 2都還在, 只是挪到了尾巴
而popd +3
則不是”移動”, 而是刪除了, 意思是正向刪除第3個, 如果不帶+
, 則理解為刪除3以后的所有堆棧(即從4開始)
注意: 為什么要從第4個開始退要把從3開始的移到頂層呢?
因為如下dirs: /1, /2, /3
, 你做popd
, 是會回到/2的
可見頂棧永遠表示的是"當前"目錄. 所以你自然無法跳到當前目錄.
而pushd
,popd
+數(shù)字
改動的只是堆棧表, 不是目錄, 即雖然你的目錄沒變, 但是系統(tǒng)認為你在第三層, 這個時候再后退(popd
)自然到了目錄表里的下一層/4
.
腳本
shebang行#!/usr/bin/env bash
的寫法是為了避免#!/bin/bash
這種寫法時bash不在bin目錄
source
命令可以 用一個點來表示: . ~/.bash_profile
讀用戶的輸入:
$ read firstname lastname
$ “you input: $firstname, $lastname” # 如果read后沒有給變量名, 則由默認的$REPLY來取出
讀文件:
while read myline # 每次讀一行
do
echo "$myline"
done < $filename # 注意這里特殊的傳參方式, 同時, 如果不傳入文件路徑, 就是一個無限循環(huán)了(read)
存數(shù)組:
$ read -a varname. # -a 參數(shù)把用戶的多個輸入全存到`varname`這個數(shù)組里了
其它有用的:
# -e 參數(shù)使得用戶在輸入的時候能用tab補全(包含所有readline庫快捷鍵),
# 如果沒有這個參數(shù)輸入文本的時候是不能使用快捷鍵的
$ read -e -p “please input the path to the file”
# -s 可以隱藏用戶的輸入, 通常用于密碼
$ read -s -p “input password”
# -p 顯然就是能直接顯示輸入前的提示了
條件判斷
if
里面的test
命令
test expression
, [ expression]
, [[ expression ]]
是等價的(第三種支持正則) 空格不能省
[ -? file ]
查看文件狀態(tài)有非常多的表達式(參數(shù)), 具體參閱https://wangdoc.com/bash/condition.html 推薦閱讀
for循環(huán)
for [ test ] in list; do … done
其中的in list
如果省略, 則代表所有腳本參數(shù)”$@“
:
$ for filename; do echo “$filename”; done
# 等同于
$ for filename in “$@“; do echo “$filename”; done
同理, 如果是用在函數(shù)中, 則等于所有函數(shù)參數(shù).
用雙括號, 變量也無需加$
了
for (( i=0; i<5; i+=1 )); do echo $i; done
case in
, 如果希望一個匹配后繼續(xù)做下一個匹配(passthrough
), 每一個case 的結(jié)尾用;;&
而不是;;
(多了一個&)
select
select生成的菜單, 選擇并執(zhí)行命令后, 要自行在do-done
體內(nèi)用break
退出, 否則會一直要你選擇
數(shù)組
以下方式聲明數(shù)組
$ names=(hatter [5]=duchess Alice), 指定了0, 5, 6, 其它為空字符串
$ mp3s=( *.mp3 )
$ declare -a ARRAYNAME
$ read -a ARRAYNAME
讀取的時候: $ echo ${array[1]}
大括號不可省
@
仍然是返回所有元素: $ echo ${array[@]}
但是在for…in
中, 要把整個表達式放雙引號中:
$ activities=( swimming "water skiing" canoeing "white-water rafting" surfing )
$ for act in “${activities[@]}”; do….; done
不然其中有”water”, “skiing”,”white-water", rafting”等都會被拆開(bug吧? 字符串也拆)
把@
換成*
, 加上雙引號, 則會一個個字符返回
拷貝數(shù)組最方便的方法:
$ hobbies=( “${activities[@]}” diving ) # 順便演示了為數(shù)組添加成員
直接賦值給一個數(shù)組(即沒有指定索引), 則是賦給第0個組員, 同理, 使用數(shù)組名也是使用的0號組員
# 以下@可以換成*
$ echo ${#array[@]} # #仍然用以計數(shù), 但是如果傳的是具體索引, 則返回的是對應(yīng)項的字符串長度
$ echo ${!array[@]} 用以返回有值的索引 (為空的不返回) # 活用的話遍歷數(shù)組更高效
$ echo ${array[@]:2:3} # 切片
$ arr+=(3 4 5) # 追加
$ unset arr[2] # 刪除 , 或:
$ arr[2]= # 或
$ arr[2]=‘’
# 以上三者等效,
根據(jù)上面知識$ arr=
表示刪除第一個成員, 但是unset arr
則是清空整個數(shù)組了
也可以用字符串做索引, 就成了字典了:
$ declare -A colors # 變成大寫即可
$ colors[“red”]=“#ff0000”
set命令
單獨一個set會顯示所有環(huán)境變量和Shell函數(shù)
以下都可以以set -xxx
的方式寫在腳本頭或任何位置, 就當一個即時開關(guān)使用吧
也可以在調(diào)用bash腳本前傳入比如: bash -eux script.sh
-u
: 遇到不存在的變量就報錯, 而不是忽略 與 -o nounset
等價
-x
: 每一個命令執(zhí)行前會先打印出來 等同于 -o xtrace
, 關(guān)閉用set +x
(組合起來用就是一個小環(huán)境)
-e
: 有錯誤就中止 等同于 -o errexit
-o pipefail
: 即使在管道中, 有錯也中止(-e 在管道中會失效)
-n
: -o noexec
不執(zhí)行命令只檢查語法
-f
: -o noglob
不對通配符進行文件名(路徑)擴展 可用+f 關(guān)閉
-v
: -o verbose
打印shell接收到的每一行輸入 可用+v 關(guān)閉
$ set -euxo pipefail 一般這么四個連用
shopt
即: shell option
同set, 直接shopt也可以列出所有參數(shù), -s
, -u
分別是是打開, 關(guān)閉某個參數(shù)
shopt 參數(shù)名
, 可直接查詢該參數(shù)是否打開關(guān)閉, 但是如果是用于編程, 因為返回是字符串不好判斷, 所以提供了-q
參數(shù)(返回0/1, 分別表示打開/關(guān)閉)
$ if shopt -q globstar; then …; fi
除錯
# 先看目錄存不存在, 然后再進入, 然后再打印出來將要刪除的文件,
# 這是最安全的刪除方法
# 否則一旦目錄不存在, 不同的寫法會有不同的問題
[[ -d $dir_name ]] && cd $dir_name && echo rm *
如果在執(zhí)行bash腳本前加入-x
參數(shù), 則每一條命令執(zhí)行前都會打印出來 # 等同于set -x
或者寫在腳本的shebang行里也行
每一條命令會同上一個標識符作前綴, 默認是+
, 可以用export PS4=‘$LINENO +’
這種方式自定義(比如現(xiàn)在就加上了行號)
$LINENO
: 這個變量在哪, 打印的就是這一行的行號
$FUNCNAME
: 返回一個數(shù)組, 函數(shù)調(diào)用的名稱堆棧, 最里層(即本函數(shù))的是0
$BASH_SOURCE
: 返回一個數(shù)組, 函數(shù)調(diào)用的腳本堆棧, 即每層調(diào)用的腳本是哪一個, 最里層(即本文件)的是0
$BASH_LINENO
: 返回一個數(shù)組, 函數(shù)每一次被調(diào)用時在該腳本的行號, 同樣也是從最里層開始
例:
${BASH_SOURCE[1] = main.sh # [0] 是文件本身, 所以要[1]
${BASH_LINENO[0] = 17 # 調(diào)用來源的行號 —> 所以調(diào)用來源的行號的索引永遠比調(diào)用來源(文件)的索引要小1
${FUNCNAME[0]} = hello # 本方法(或者說”被調(diào)用的方法”)
上例代表在 main.sh的17行調(diào)用了hello()方法
#!/bin/bash
source lv2.sh # 引入外部腳本
function lv1method()
{
echo ---------lv1------------
i=0
for v in "${BASH_LINENO[@]}"; do
echo "bash_line_no[$((i++))]: $v"
done
i=0
for v in "${FUNCNAME[@]}"; do
echo "func_name[$((i++))]: $v"
done
i=0
for v in "${BASH_SOURCE[@]}"; do
echo "bash_source[$((i++))]: $v"
done
lv2method # 調(diào)用外部腳本的方法
}
以上腳本, 多做幾次嵌套, 打印出來看看索引之間的關(guān)系
輸出:
---------lv1------------
bash_line_no[0]: 5
bash_line_no[1]: 0
func_name[0]: lv1method
func_name[1]: main
bash_source[0]: lv1.sh
bash_source[1]: entry.sh
---------lv2------------
bash_line_no[0]: 21
bash_line_no[1]: 5
bash_line_no[2]: 0
func_name[0]: lv2method
func_name[1]: lv1method
func_name[2]: main
bash_source[0]: lv2.sh
bash_source[1]: lv1.sh
bash_source[2]: entry.sh
---------lv3------------
bash_line_no[0]: 19
bash_line_no[1]: 21
bash_line_no[2]: 5
bash_line_no[3]: 0
func_name[0]: lv3method
func_name[1]: lv2method
func_name[2]: lv1method
func_name[3]: main
bash_source[0]: lv3.sh
bash_source[1]: lv2.sh
bash_source[2]: lv1.sh
bash_source[3]: entry.sh
臨時文件
安全的用法:
trap 'rm -f "$TMPFILE"’ EXIT # 退出時刪除臨時文件)
TMPFILE=$(mktemp) || exit 1 # 用mktemp命令建立臨時文件可以只有本人能讀, 如果失敗就退出
echo "Our temp file is $TMPFILE”
參數(shù):
-d
: 創(chuàng)建的是目錄
-p
: 指定目錄
-t
: 指定模板
如 mktemp -t aaa.XXXXXXX
能生成/tmp/aaa.yZ1HgZV
(與X個數(shù)相同)
trap
是用來響應(yīng)系統(tǒng)信號的, 如ctrl+c
產(chǎn)生中斷信號SIGINT
$ trap -l
列出所有信號(自己打印出來看看)
trap
的格式: $ trap [動作] [信號1] [信號2] ...
trap
命令接的信號有如下
- HUP:編號1,腳本與所在的終端脫離聯(lián)系昵观。
- INT:編號2,用戶按下 Ctrl + C,意圖讓腳本中止運行。
- QUIT:編號3征冷,用戶按下 Ctrl + 斜杠抛丽,意圖退出腳本样漆。
- KILL:編號9,該信號用于殺死進程蛹稍。
- TERM:編號15吧黄,這是kill命令發(fā)出的默認信號。
- EXIT:編號0唆姐,這不是系統(tǒng)信號拗慨,而是 Bash 腳本特有的信號,不管什么情況,只要退出腳本就會產(chǎn)生赵抢。
如果trap要執(zhí)行多條命令, 可以封裝到函數(shù)里, 命令的位置寫函數(shù):$ trap func_name EXIT
啟動環(huán)境
登錄session依次啟動如下腳本:
- /etc/profile
- /etc/profile.d # 目錄下的所有.sh文件
- ~/.bash_profile # 如果有, 則中止
- ~/.bash_login # 如果有, 則中止 此為C shell 初始化腳本
- ~/.profile # Bourne shell 和 Korn shell 初始化腳本
通過$ bash - -login
參數(shù), 可以強制執(zhí)行以上腳本
非登錄session
- /etc/bash.bashrc # 所有用戶都執(zhí)行
- ~/.bashrc # 當前用戶的
啟動參數(shù):
-n
: 不執(zhí)行腳本, 只檢查語法
-v
: 執(zhí)行語句前先輸出
-x
: 執(zhí)行語句后輸出該語句
~/.bash_logout
退出時要執(zhí)行的命令
$ include /etc/inputrc
在~/.inputrc
里加這一行, 可以在里面自定義快捷鍵
命令提示符
上面提到過$PS4
能修改set -x
時打印的每句語句前面的+
號
命令提示符默認的$
符號(根用戶是#
號)則可以用$PS1
來修改, 怎么改參考https://wangdoc.com/bash/prompt.html
$PS2
表示的是輸入時折行的提示符, 默認為>
$PS3
表示使用select命令時系統(tǒng)輸入菜單的提示符