Shell腳本字符串匹配及日常命令工具 - 用法總結(技巧指南)


 

Shell提供了很多字符串和文件處理的命令,如awk、expr、grep、sed等命令,還有文件的排序、合並和分割等一系列的操作命令。下面重點總結下Shell字符串處理、文本處理以及各類命令及函數用法。

先從expr命令開始梳理,expr 引出通用求值表達式,可以實現算術操作、比較操作、字符串操作和邏輯操作等功能。

1) 計算字符串長度
字符串名為string,可以使用命令 ${#string} 或 expr length ${string} 兩種方法來計算字符串的長度。
若string中包括空格,則expr計算命令中需用雙引號引起來,即expr length "${string}"。(${#string}方法對於有無空格的字符串均可使用)
需要注意:expr length后面只能跟一個參數,string有空格會當作多個參數處理。

[root@kevin ~]# string="kevinisgood"   
[root@kevin ~]# expr length ${string}  
11
[root@kevin ~]# expr length "${string}"  
11
[root@kevin ~]# echo ${#string}                  # 使用自帶shell函數讀取字符串長度
11

如果string字符串中有空格
[root@kevin ~]# string="kevin is good" 
[root@kevin ~]# expr length ${string} 
expr: syntax error
[root@kevin ~]# expr length "${string}"
13
[root@kevin ~]# echo ${#string}        
13

注意:這里說到如果string字符串中有空格,則需要雙引號引起來。但如果是單引號引起來呢?
如果string字符串用單引號引起來,則統計出來的是"字符串去重和去掉空格"后的長度

[root@kevin ~]# echo ${string}         
kevin is good
[root@kevin ~]# expr length '${string}'         #使用單引號的話,統計的是"字符去重以及去掉空格"之后的長度
9

[root@kevin ~]# string="kevinisgood"
[root@kevin ~]# echo ${string}         
kevinisgood
[root@kevin ~]# expr length '${string}'
9

2)匹配字符串長度
即匹配字符串開頭子串的長度!!使用命令 expr match ${string} $substring,表示在string字符串的開頭匹配substring字符串,返回匹配到的substring字符串的長度。若string開頭匹配不到則返回0,其中substring可以是字符串也可以是正則表達式。

expr匹配字符串長度的兩種格式:
# expr match "$string" '$substring'
# expr "$string" : '$substring'

[root@kevin ~]# string="zhongguo hao"
[root@kevin ~]# expr length "${string}"
12
[root@kevin ~]# expr match "${string}" "z.*"
12
[root@kevin ~]# expr match "${string}" "zho"
3
[root@kevin ~]# expr match "${string}" "hao"
0

注意:
"hao"盡管在string字符串中出現,但是未出現在string的開頭處,因此返回0!
所以說,expr match匹配的一定是字符串開頭子串的長度!!

后面的$substring可以是正則, $substring兩邊是單引號或雙引號無所謂。
[root@kevin ~]# string="KEvin IS good"
[root@kevin ~]# expr match "${string}" "[A-Z]*"
2
[root@kevin ~]# expr match "${string}" '[A-Z]*'    
2

[root@kevin ~]# expr "${string}" : "[A-Z]*"      #注意這種方法中就沒有了"match"參數 
2
[root@kevin ~]# expr "${string}" : '[A-Z]*[a-z]*'
5

3)匹配字符串索引
expr的索引命令格式為:expr index ${string} $substring。
在字符串$string上匹配$substring中字符最早第一次出現的位置(從左到右,從1開始),匹配不到,expr index返回0。
簡單來說,就是找出子串在字符串中最早第一次出現的單個字符的位置!!

[root@kevin ~]# string="love beijing"
[root@kevin ~]# echo ${string}       
love beijing
[root@kevin ~]# expr index ${string} "bei"
expr: syntax error
[root@kevin ~]# expr index "${string}" "bei"        #注意string字符串中有空格,需要加雙引號
4
[root@kevin ~]# expr index "${string}" "jing"
8
[root@kevin ~]# expr index "${string}" "haa" 
0

注意:
expr index命令中返回的是后面$substring中"子串字符中"最早第一次出現的位置。
"bei"子串中在${string}字符串中最早第一次出現的是"e",第4位置
"jing"子串中在${string}字符串中最早第一次出現的是"i",第4位置
"haa"匹配不到,所以返回0!

===  Bash Shell命令  ===
4)抽取字符串的子串
Shell提供兩種命令和expr實現抽取子串功能。

正着抽取(即從左到右)有兩種格式。(左邊默認從0開始標號)
格式一:{string:position}  從名稱為${string}的字符串的第$position個位置開始抽取子串,從0開始標號。
格式二:{string:position:length}  增加$length變量,表示從${string}字符串的第$position個位置開始抽取長度為$length的子串。

需要注意:都是從string的左邊開始計數抽取子串。

示例:
[root@kevin ~]# string="hello world wang"
[root@kevin ~]# echo ${string}
hello world wang
[root@kevin ~]# echo ${string:0}    #從標號為0(即從第一個字母)開始截取,截取到結尾。
hello world wang
[root@kevin ~]# echo ${string:6}    #從標號為6的字符開始截取,截取到結尾。
world wang
[root@kevin ~]# echo ${string:6:4}  #從標號為6的字符開始截取,截取長度為4。
worl
[root@kevin ~]# echo ${string:6:0}  #從標號為6的字符開始截取,截取長度為0。

[root@kevin ~]# 

反着抽取(即從右到左)有兩種格式。(右邊默認從-1開始標號)
格式一:{string: -position}。需要謹記:冒號與橫杠間有一個空格!!!!
格式二:{string:(position)}。如果加了括號,則冒號后面加不加空格是一樣的效果!!!

[root@kevin ~]# echo ${string:-2}    #冒號與"-"之間必要要有空格,否則截取無效!
hello world wang
[root@kevin ~]# echo ${string: -2}
ng

[root@kevin ~]# echo ${string:(-2)}
ng
[root@kevin ~]# echo ${string: (-2)}
ng

如果要想實現從右邊第幾個字符開始截取,截取長度為多少,則方法為:
{string:m-n:x} 表示從右邊第"m-n"個字符開始截取,截取長度為x
[root@kevin ~]# echo ${string:1-7:3}
d w
[root@kevin ~]# echo ${string:3-9:3}
d w

expr substr也能夠實現抽取子串功能,命令格式:expr substr ${string} $position $length,
這個與上面最大不同是expr substr命令從1開始進行標號!!!!
[root@kevin ~]# echo ${string}
hello world wang
[root@kevin ~]# echo ${string:3:5}            #從0開始進行標號    
lo wo
[root@kevin ~]# expr substr "${string}" 3 5   #從1開始標號
llo w

還可以使用正則表達式抽取子串的命令,但只能抽取string開頭處或結尾處的子串。

抽取字符串開頭處的子串:
格式一:expr match $string ''
格式二:expr $string : ''。  注意:其中冒號前后都有一個空格。

抽取字符串結尾處的子串:
格式一:expr match $string '.*'
格式二:expr $string : '.*'。   注意:.*表示任意字符的任意重復。一個.表示一個字符。

[root@kevin ~]# string="20181112hello WORld Good"
[root@kevin ~]# echo ${string}
20181112hello WORld Good
[root@kevin ~]# expr match "$string" '\([0-9]*\)'     #這里的${string}最好使用雙引號引起來,因為字符串可能中有空格!如果沒有空格,就可以不用使用雙引號。
20181112
[root@kevin ~]# expr match "$string" "\([0-9]*\)"
20181112
[root@kevin ~]# expr "$string" : '\([0-9]*\)'
20181112

[root@kevin ~]# expr match "$string" '.*\(.\)'  
d
[root@kevin ~]# expr match "$string" '.*\(..\)'  
od
[root@kevin ~]# expr match "$string" '.*\(...\)'  
ood
[root@kevin ~]# expr match "$string" '.*\(.....\)'  
 Good
[root@kevin ~]# expr "$string" : '.*\(.....\)'
 Good
[root@kevin ~]# expr "$string" : '.*\(.........\)'
ORld Good

[root@kevin ~]# string="heLLO2018 world"         
[root@kevin ~]# expr match "$string" '\([a-z]*\)'
he
[root@kevin ~]# expr match "$string" '\([a-Z]*\)'
heLLO
[root@kevin ~]# expr match "$string" '\([a-Z]*[0-9]*\)'
heLLO2018
[root@kevin ~]# expr match "$string" '\(.[a-Z]*[0-9]*\)'
heLLO2018

[root@kevin ~]# expr "$string" : '.*\(.........\)'
018 world

5)刪除字符串的子串
刪除字串是指將原字符串中符合條件的子串刪除。

從string開頭處刪除子串:
格式一:${string#substring}    刪除開頭處與substring匹配的最短子串。
格式二:${string##substring}   刪除開頭處與substring匹配的最長子串。其中substring並非是正則表達式而是通配符。
[root@kevin ~]# string="china IS niuBIlity2018"
[root@kevin ~]# echo ${string#c*i}     #刪除c開頭到a的最短子串         
na IS niuBIlity2018
[root@kevin ~]# echo ${string##c*i}    #刪除c開頭到a的最長子串
ty2018
[root@kevin ~]# echo ${string#c*n} 
a IS niuBIlity2018
[root@kevin ~]# echo ${string##c*n}
iuBIlity2018
[root@kevin ~]# echo ${string#ch*n}    #刪除ch開頭到a的最短子串          
a IS niuBIlity2018
[root@kevin ~]# echo ${string##ch*n}   #刪除ch開頭到a的最長子串
iuBIlity2018

上面#或##后面的字符必須是${string}字符串的開頭子串!否則刪除子串就無效了!
[root@kevin ~]# echo ${string#i*n}    #i不是開頭字符,所以刪除無效
china IS niuBIlity2018
[root@kevin ~]# echo ${string##i*n}   #i不是開頭字符,所以刪除無效
china IS niuBIlity2018

另外:可以使用下面方式進行刪除:
${string#*substring}     刪除${string}字符串中第一個$substring及其之前的字符
${string##*substring}    刪除${string}字符串中最后一個$substring及其之前的字符
[root@kevin ~]# string="china IS niuBIlity2018"
[root@kevin ~]# echo ${string#*i}    #刪除第一個i及其之前的字符          
na IS niuBIlity2018
[root@kevin ~]# echo ${string##*i}   #刪除最后一個i及其之前的字符
ty2018

也可以使用下面方法進行刪除
格式一:${string%substring*}    刪除${string}字符串中最后一個$substring及其之后的字符
格式二:${string%%substring*}   刪除${string}字符串中第一個$substring及其之后的字符
[root@kevin ~]# echo ${string}    
china IS niuBIlity2018
[root@kevin ~]# echo ${string%i*}
china IS niuBIl
[root@kevin ~]# echo ${string%%i*}
ch
[root@kevin ~]# echo ${string%c*} 

[root@kevin ~]# echo ${string%%c*}

[root@kevin ~]#

6)字符串替換
替換子串命令可以在任意處、開頭處、結尾處替換滿足條件的子串,其中的substring都不是正則表達式而是通配符。

在任意處替換子串命令:
格式一:${string/substring/replacement},僅替換第一次與substring相匹配的子串。
格式二:${string//substring/replacement},替換所有與substring相匹配的子串。

[root@kevin ~]# string="china IS niuBIlity2018"
[root@kevin ~]# echo ${string/i/#}
ch#na IS niuBIlity2018
[root@kevin ~]# echo ${string//i/#}
ch#na IS n#uBIl#ty2018

[root@kevin ~]# echo ${string}
china IS niuBIlity2018
[root@kevin ~]# echo ${string/ /--}   #替換空格
china--IS niuBIlity2018
[root@kevin ~]# echo ${string// /--}
china--IS--niuBIlity2018

在開頭處替換與substring相匹配的子串,格式為:${string/#substring/replacement}。
在結尾除替換與substring相匹配的子串,格式為:${string/%substring/replacement}。
[root@kevin ~]# echo ${string}
china IS niuBIlity2018
[root@kevin ~]# echo ${string/#ch/he} 
heina IS niuBIlity2018
[root@kevin ~]# echo ${string/#china/anhui}
anhui IS niuBIlity2018
[root@kevin ~]# echo ${string/#niu/he}     #注意這里#后面的字符必須是${string}字符串中開頭的字符  
china IS niuBIlity2018

[root@kevin ~]# echo ${string/%2018/2020}
china IS niuBIlity2020
[root@kevin ~]# echo ${string/%lity2018/hehehe}
china IS niuBIhehehe

7)${!varprefix*} 和 ${!varprefix@}

[root@kevin ~]# test="bobo"
[root@kevin ~]# test1="bobo1"    
[root@kevin ~]# test2="bobo2"
[root@kevin ~]# test4="bobo4"
[root@kevin ~]# echo ${!test*}
test test1 test2 test4
[root@kevin ~]# echo ${!test@}
test test1 test2 test4

8)參數替換

從string開頭處刪除子串:
格式一:${string#substring}    刪除開頭處與substring匹配的最短子串。
格式二:${string##substring}   刪除開頭處與substring匹配的最長子串。其中substring並非是正則表達式而是通配符。
[root@kevin ~]# string="china IS niuBIlity2018"
[root@kevin ~]# echo ${string#c*i}     #刪除c開頭到a的最短子串        
na IS niuBIlity2018
[root@kevin ~]# echo ${string##c*i}    #刪除c開頭到a的最長子串
ty2018
[root@kevin ~]# echo ${string#c*n}
a IS niuBIlity2018
[root@kevin ~]# echo ${string##c*n}
iuBIlity2018
[root@kevin ~]# echo ${string#ch*n}    #刪除ch開頭到a的最短子串         
a IS niuBIlity2018
[root@kevin ~]# echo ${string##ch*n}   #刪除ch開頭到a的最長子串
iuBIlity2018
 
上面#或##后面的字符必須是${string}字符串的開頭子串!否則刪除子串就無效了!
[root@kevin ~]# echo ${string#i*n}    #i不是開頭字符,所以刪除無效
china IS niuBIlity2018
[root@kevin ~]# echo ${string##i*n}   #i不是開頭字符,所以刪除無效
china IS niuBIlity2018
 
另外:可以使用下面方式進行刪除:
${string#*substring}     刪除${string}字符串中第一個$substring及其之前的字符
${string##*substring}    刪除${string}字符串中最后一個$substring及其之前的字符
[root@kevin ~]# string="china IS niuBIlity2018"
[root@kevin ~]# echo ${string#*i}    #刪除第一個i及其之前的字符         
na IS niuBIlity2018
[root@kevin ~]# echo ${string##*i}   #刪除最后一個i及其之前的字符
ty2018
 
也可以使用下面方法進行刪除
格式一:${string%substring*}    刪除${string}字符串中最后一個$substring及其之后的字符
格式二:${string%%substring*}   刪除${string}字符串中第一個$substring及其之后的字符
[root@kevin ~]# echo ${string}   
china IS niuBIlity2018
[root@kevin ~]# echo ${string%i*}
china IS niuBIl
[root@kevin ~]# echo ${string%%i*}
ch
[root@kevin ~]# echo ${string%c*}
 
[root@kevin ~]# echo ${string%%c*}
 
[root@kevin ~]#

-------------------------------------------------------------------
再看下面一例:
[root@kevin ~]# str=bo/www/kevin/data/test/ccd.log
[root@kevin ~]# echo ${str#*/*/}                  
kevin/data/test/ccd.log
[root@kevin ~]# echo ${str##*/*/}
ccd.log

[root@kevin ~]# echo ${str%/*/*} 
bo/www/kevin/data
[root@kevin ~]# echo ${str%%/*/*}
bo

以上是以/*/作為匹配的字符串,即正則匹配的字符串。

9)如何判斷一個字符串是否由字母數字開頭 (grep)

1)判斷一個字符串是否由大小寫字母或數字開頭(或結尾)
[root@kevin ~]# cat test.sh
#!/bin/bash
#str="This IS a root USER, 20171aaabb"
 
read -p "請輸入內容:" str
  
if echo "${str}" | grep -q '^[A-Za-z0-9].*\+$'; then      
echo -e "${str}\nok"
else
echo "invaliad"
fi
 
需要注意:
腳本中的echo后面只需要添加-e參數,是為了讓打印中的\n換行符生效!如果不加-e參數,則\n就被做當普通字符打印出來了!
read -p 表示"指定要顯示的提示"。如果添加-s參數,即"read -sp",則表示"靜默輸入,即隱藏輸入的數據,一般用於密碼輸入"
 
執行腳本:
[root@kevin ~]# sh test.sh
請輸入內容:TOOk213gg
TOOk213gg
ok
 
[root@kevin ~]# sh test.sh
請輸入內容:@#sadf123
invaliad
 
[root@VM_16_9_centos ~]# sh test.sh
請輸入內容:arTR213#$1      
arTR213#$1
ok
 
==============================================================
[root@kevin ~]# read -p "輸入你想要輸入的內容:"
輸入你想要輸入的內容:asdfsafsaf
 
[root@kevin ~]# read -sp "輸入你想要輸入的內容:"   #加了-s參數后,即為靜默輸入,隱藏輸入的內容
輸入你想要輸入的內容:
[root@kevin ~]#
==============================================================
 
為了簡化,還可以將上面腳本中的:
grep -q '^[A-Za-z0-9].*\+$'
改為
grep -q '^[A-Za-z0-9].*'
 
--------------------------------------
如果判斷一個字符串是否由大小寫字母或數字結尾!!!!!  則只需要將上面腳本中的:
grep -q '^[A-Za-z0-9].*\+$'
改為
grep -q '.*[A-Za-z0-9]$'
 
==============================================================
還要注意:
'^[A-Za-z0-9].*\+$'  表示以大寫字母或小寫字母或數字為開頭。沒有順序要求!!
'^[A-Za-z0-9].*'     可以直接去掉后面的"\+$"部分
'^[A-Z].*'           表示以大寫字母開頭 
'^[a-z].*'           表示以小寫字母開頭
'^[0-9].*'           表示以數字字母開頭
 
下面都是最常用的
grep ^[0-9]
grep "^[0-9]"
grep ^[a-z]
grep "^[a-z]"
grep ^[A-Z]
grep "^[A-Z]"
 
grep ^[a-Z]
grep "^[a-Z]"

grep .*[0-9]$
grep ".*[0-9]$"
grep ".*[a-z]$"
grep ".*[a-z]$"
grep ".*[A-Z]$"
grep ".*[A-Z]$"

grep .*[a-Z]$
grep ".*[a-Z]$"

grep [0-9]G
grep [a-z]2018_data

[root@kevin ~]# cat a.txt
Good study 2018! hahahah~
good Study 2018hehehehe
2018 Good study 1wqe
2018stuDY is heht6ttt
!@#asdf
TOOk213gg asdfasdf
anhui 2018asdfjlsadfdsaff
#$$$$$
 
[root@kevin ~]# cat a.txt|grep '^[A-Z].*\+$'       
Good study 2018! hahahah~
TOOk213gg asdfasdf
 
[root@kevin ~]# cat a.txt|grep '^[a-z].*\+$'   
good Study 2018hehehehe
anhui 2018asdfjlsadfdsaff
 
[root@kevin ~]# cat a.txt|grep '^[0-9].*\+$'   
2018 Good study 1wqe
2018stuDY is heht6ttt
 
[root@kevin ~]# cat a.txt|grep '^[A-Za-z0-9].*\+$'   
Good study 2018! hahahah~
good Study 2018hehehehe
2018 Good study 1wqe
2018stuDY is heht6ttt
TOOk213gg asdfasdf
anhui 2018asdfjlsadfdsaff
 
[root@kevin ~]# cat a.txt|grep -v '^[A-Za-z0-9].*\+$'
!@#asdf
#$$$$$
 
[root@kevin ~]# cat a.txt|grep -v '^[A-Z].*\+$'|grep -v '^[a-z].*\+$'|grep -v '^[0-9].*\+$'   
!@#asdf
#$$$$$
 
[root@kevin ~]# cat a.txt |grep -v ^[a-Z]
2018 Good study 1wqe
2018stuDY is heht6ttt
!@#asdf
#$$$$$
 
[root@kevin ~]# cat a.txt |grep -v ^[a-Z]|grep -v [0-9]
!@#asdf
#$$$$$
 
grep獲取多個條件(grep -E "條件1|條件2|條件3")
[root@kevin ~]# cat a.txt |grep -E "^[a-z]|^[0-9]"
good Study 2018hehehehe
2018 Good study 1wqe
2018stuDY is heht6ttt
anhui 2018asdfjlsadfdsaff
 
grep過濾多個條件(grep -v "條件1\|條件2\|條件3"),注意""里面有轉義符"\"
[root@kevin ~]# cat a.txt |grep -v "^[a-z]\|^[0-9]"
Good study 2018! hahahah~
!@#asdf
TOOk213gg asdfasdf
#$$$$$
 
[root@kevin ~]# cat a.txt |grep -v "[a-z]\|^[0-9]"
#$$$$$

10)刪除字符串中指定字符(tr命令、sed命令)

一、使用sed將字符串中指定的字符刪除
[root@kevin ~]# echo "2018-10-08 15:19:05"|sed 's/-//g'|sed 's#:##g'
20181008 151905

刪除字符串中的特殊字符
[root@kevin ~]# cat test.sh 
#!/bin/bash
str="root!@#QWE123"
echo ${str}| sed 's/\!//g' | sed 's/\@//g' | sed 's/\#//g'

[root@kevin ~]# sh test.sh
rootQWE123

還可以使用tr -d命令進行刪除:
[root@kevin ~]# cat test.sh
#!/bin/bash
str="root!@#QWE123"
echo ${str}| tr -d "!" | tr -d "@" | tr -d "#"

[root@kevin ~]# sh test.sh
rootQWE123

-------------------------------------------------------------------------------
另外:sed也支持正則
sed -n '/[0-9]/p' filename   將文件中匹配數字的行打印出來
sed -n '/[a-z]/p' filename   將文件中匹配小寫字母的行打印出來
sed -n '/[A-Z]/p' filename   將文件中匹配大寫字母的行打印出來

sed -i '/[0-9]/d' filename   將文件中匹配數字的行刪除
sed -n '/[a-z]/d' filename   將文件中匹配小寫字母的行刪除
sed -n '/[A-Z]/d' filename   將文件中匹配大寫字母的行刪除

sed -i '/[0-9]/s/root/huoqiu/g' filename   將文件中匹配數字的行里的root替換為huoqiu
sed -i '/[a-z]/s/root/huoqiu/g' filename   將文件中匹配小寫字母的行里的root替換為huoqiu
sed -i '/[A-Z]/s/root/huoqiu/g' filename   將文件中匹配大寫字母的行里的root替換為huoqiu

===============================================================================================
二、使用tr命令刪除字符串中指定字符。tr使用-d參數可以起到刪除字符的效果。支持正則表達式
[root@kevin ~]# echo "huoqiu123GOOD"|tr -d "a-z"        #刪除字符串中的小寫字母
123GOOD
[root@kevin ~]# echo "huoqiu123GOOD"|tr -d "a-zA-Z"     #刪除字符串中的大寫字母
123
[root@kevin ~]# echo "huoqiu123GOOD"|tr -d "0-9"        #刪除字符串中的數字
huoqiuGOOD

刪除字符串中指定的部分字符
[root@kevin ~]# echo "huoqiu123GOOD"|tr -d "k"|tr -d "G"
evin123OOD
[root@kevin ~]# echo "huoqiu123GOOD"|tr -d "123"
huoqiuGOOD

截取字符串中的特殊字符
[root@kevin ~]# cat test.sh
#!/bin/bash
str="root!@#QWE123"
echo ${str}| tr -d "a-z"| tr -d "A-Z"| tr -d "0-9"

[root@kevin ~]# sh test.sh
!@#

3)這里簡單介紹下tr命令的日常用法
tr命令可以對來自標准輸入的字符進行替換、壓縮和刪除。它可以將一組字符變成另一組字符,經常用來編寫優美的單行命令,作用很強大。

語法
# tr (選項) (參數)

選項
-c或——complerment:      用字符集1中的字符串替換,要求字符集為ASCII。
-d或——delete:           刪除所有屬於字符集1的字符;
-s或--squeeze-repeats:  把連續重復的字符以單獨一個字符表示。即壓縮字符,但是必須是連續重復的單個字符!
-t或--truncate-set1:    先刪除字符集1較字符集2多出的字符。

參數
字符集1:指定要轉換或刪除的原字符集。當執行轉換操作時,必須使用參數“字符集2”指定轉換的目標字符集。但執行刪除操作時,不需要參數“字符集2”;
字符集2:指定要轉換成的目標字符集。

tr用來從標准輸入中通過替換或刪除操作進行字符轉換。
tr主要用於刪除文件中控制字符或進行字符轉換。
使用tr時要轉換兩個字符串:字符串1用於查詢,字符串2用於處理各種轉換。
tr剛執行時,字符串1中的字符被映射到字符串2中的字符,然后轉換操作開始。

通過使用tr可以非常容易地實現sed的許多最基本功能,可以將tr看作為sed的(極其)簡化的變體。
它可以用一個字符來替換另一個字符,或者可以完全除去一些字符。您也可以用它來除去重復字符。這就是所有 tr 所能夠做的。

看看下面示例:
1)tr的替換命令
將輸入字符由小寫轉換為大寫:
[root@kevin ~]# echo "anhui@root"|tr "a-z" "A-Z"
ANHUI@ROOT

將輸入字符由大寫轉換為小寫:
[root@kevin ~]# echo "ANhui@ROOT"|tr "A-Z" "a-z"
anhui@root

[root@kevin ~]# echo "172.16.60.34 data-node01"|tr "." "_"
172_16_60_34 data-node01
[root@kevin ~]# echo "root \n 213"| tr " " "#"
root#\n#213

[root@kevin ~]# echo "123456@abc"|tr "0-9" "#"
######@abc
[root@kevin ~]# echo "123456@abc"|tr "123" "pas"       # 替換兩個字符集的時候,分別是一個字符對應一個字符的關系
pas456@abc

[root@kevin ~]# echo "123456@abc"|tr "123" "password"  # 當字符集1的字符數少於字符集2的字符數時,就取字符集2的前面對應數目的字符進行替換
pas456@abc
[root@kevin ~]# echo "123456@abc"|tr "1234" "password"
pass56@abc
[root@kevin ~]# echo "123456@abc"|tr "12345" "password"
passw6@abc
[root@kevin ~]# echo "123456@abc"|tr "123456" "password"
passwo@abc

[root@kevin ~]# echo "123456@abc"|tr "123456" "T"     #當字符集1的字符數多於字符集2的字符數時,先進行兩個字符集對應數目字符的替換,剩下多余的字符集1字符就使用字符集2的最后一個字符進行重復替換
TTTTTT@abc
[root@kevin ~]# echo "123456@abc"|tr "123456" "TM"
TMMMMM@abc
[root@kevin ~]# echo "123456@abc"|tr "123456" "TMC"
TMCCCC@abc
[root@kevin ~]# echo "123456@abc"|tr "123456" "TMCH"
TMCHHH@abc
[root@kevin ~]# echo "123456@abc"|tr "123456" "TMCHR"
TMCHRR@abc
[root@kevin ~]# echo "123456@abc"|tr "123456" "TMCHRY"
TMCHRY@abc

需要注意:
'A-Z' 和 'a-z'都是集合,集合是可以自己制定的,例如:'ABD-}'、'bB.,'、'a-de-h'、'a-c0-9'都屬於集合,集合里可以使用'\n'、'\t',可以可以使用其他ASCII字符。
替換兩個字符集的時候,分別是一個字符對應一個字符的關系
當字符集1的字符數少於字符集2的字符數時,就取字符集2的前面對應數目的字符進行替換
當字符集1的字符數多於字符集2的字符數時,先進行兩個字符集對應數目字符的替換,剩下多余的字符集1字符就使用字符集2的最后一個字符進行重復替換

另外:"O*n" 表示字符O重復出現指定次數n。因此"O*2"匹配OO的字符串!!!!
[root@kevin ~]# echo "aaa123"|tr "a*3" "w"      #將a和3都替換為了w
www12w
[root@kevin ~]# echo "aaa123"|tr "a*3" "xy"     #將a替換為了x,3替換為了y
xxx12y
[root@kevin ~]# echo "aaa123"|tr "a3" "xy" 
xxx12y
[root@kevin ~]# echo "aaaaaaaa123"|tr "a*3" "xyz"
xxxxxxxx12z

[root@kevin ~]# echo "aaa123"|tr "aaa" "w"     #匹配字符集1中最后一個字符對應字符集2中的那個字符
www123
[root@kevin ~]# echo "aaa123"|tr "aaa" "wx"  
xxx123
[root@kevin ~]# echo "aaa123"|tr "aaa" "wxy" 
yyy123
[root@kevin ~]# echo "aaa123"|tr "aaa" "wxym"      
yyy123
[root@kevin ~]# echo "aaa123"|tr "aaa" "wxymn" 
yyy123

2)tr的刪除命令 (上面案例已經說明)
[root@kevin ~]# echo "hello 123 world 456" | tr -d '0-9'
hello  world 

[root@kevin ~]# cat test
beijing
shanghai
abcde
[root@kevin ~]# cat test|tr -d "abcji"    #即凡是test文件中出現的a,b,c,j,i都會被刪除
eng
shngh
de

3)用tr壓縮字符,可以壓縮輸入中重復的字符。但是注意:必須是連續重復的單個字符!!
[root@kevin ~]# echo "thissss is      a text linnnnnnne." | tr -s ' sn'
this is a text line.
[root@kevin ~]# echo "123123123"|tr -s "123"          
123123123
[root@kevin ~]# echo "123333344444"|tr -s "3"|tr -s "4"
1234

4)巧妙使用tr命令進行數字相加操作:
[root@kevin ~]# echo 1 2 3 4 5 6 7 8 9 | xargs -n1 | echo $[ $(tr '\n' '+') 0 ]
45
[root@kevin ~]# echo "10 11 12 13 14"|xargs -n1|echo $[ $(tr '\n' '+') 0 ]     
60

--------------------------------------------------------------------------------------
<<<<<   "xargs -n[數字]"用法  >>>>>
[root@kevin ~]# echo "aa bb cc 1 2 3"|xargs
aa bb cc 1 2 3
[root@kevin ~]# echo "aa bb cc 1 2 3"|xargs -n1
aa
bb
cc
1
2
3
[root@kevin ~]# echo "aa bb cc 1 2 3"|xargs -n2
aa bb
cc 1
2 3
[root@kevin ~]# echo "aa bb cc 1 2 3"|xargs -n3
aa bb cc
1 2 3
[root@kevin ~]# echo "aa bb cc 1 2 3"|xargs -n4
aa bb cc 1
2 3
--------------------------------------------------------------------------------------

5)把文件中的數字0-9替換為a-j (都是10個字符,一一對應)
[root@kevin ~]# cat filename |tr [0-9] [a-j]

6)刪除文件file中出現的換行'\n'、制表'\t'字符 
[root@kevin ~]# cat filename | tr -d "\n\t"

刪除換行符等
[root@kevin ~]# echo -e "asdf\n123"|tr -d "\n"
asdf123
[root@kevin ~]# echo -e "asdf\n\r\t123"|tr -d "\n\r\t"
asdf123

7)刪除空行
[root@kevin ~]# cat file | tr -s "\n" > new_file

[root@kevin ~]# cat test
beijing
shanghai

abcde


123123
[root@kevin ~]# cat test| tr -s "\n"    
beijing
shanghai
abcde
123123

8)把路徑變量中的冒號":",替換成換行符"\n"
[root@kevin ~]# echo "a:b:c:d"|tr ":" "\n"           
a
b
c
d

[root@kevin ~]# echo "/www/data/haha/html"|tr "/" ":"
:www:data:haha:html

9)字符集補集!!!  (tr -d -c)
set1的補集意味着從這個集合中包含set1中沒有的所有字符。
最典型的用法就是從輸入文本中將不在補集中的所有字符全部刪除!!!!
[root@kevin ~]# echo "hello 123 world " | tr -d -c '0-9 \n'
 123  
[root@kevin ~]# echo "wang beijinganhui"|tr -d -c 'a-m\n'
agbeijigahi
[root@kevin ~]# echo "123WRsdf"|tr -d -c "a-z\n"
sdf
[root@kevin ~]# echo "123@#QWEanhui"|tr -d -c "anhui\n" 
anhui

10) 刪除Windows文件"造成"的'^M'字符
[root@kevin ~]# cat file | tr -s "\r" "\n" > new_file
或
[root@kevin ~]# cat file | tr -d "\r" > new_file

11) 使用tr命令生成固定長度的隨機密碼!!!!
[root@kevin ~]# head /dev/urandom | tr -dc A-Za-z0-9 | head -c 20
koR3ZjLekd6Xujfeslu1
[root@kevin ~]# head /dev/urandom | tr -dc A-Za-z0-9 | head -c 20
vgOKX39zeQSWP6KD6rjd
[root@kevin ~]# 

11)shell的 read 輸入用法
read命令用於接收鍵盤或其它文件描述符的輸入。
read命令接收標准輸入(鍵盤)的輸入,或其他文件描述符的輸入(后面在說)。得到輸入后,read命令將數據放入一個標准變量中。

read 命令格式如下:
#read [選項] [變量名]

選項:
-p:    指定要顯示的提示
-s:    靜默輸入,輸入的數據不顯示出來。實際上輸入數據是顯示的,只是read命令將文本顏色設置成與背景相同的顏色!!!一般用於密碼輸入。
-n:    指定輸入的字符長度最大值。如果超出了,就默認使用前面最大長度值的字符!
-d '字符':  輸入結束符,當你輸入的內容出現這個字符時,立即結束輸入
-t N:   超出N秒沒有進行輸入,則自動退出。

需要注意:
變量名可以自定義。如果不指定變量名,則會把輸入保存到默認變量REPLY中;
如果只提供了一個變量名,則將整個輸入行賦予該變量;
如果提供了一個以上的變量名,則輸入行分為若干字,一個接一個地賦予各個變量,而命令行上的最后一個變量取得剩余的所有字;

[root@kevin ~]# read -p "請輸入你的內容: " haha;echo "${haha}"
請輸入你的內容: 123456
123456

加了-s參數,即靜默輸入。輸入數據的時候看不到。默認不換行!
[root@kevin ~]# read -sp "請輸入你的內容: " haha;echo "${haha}"
請輸入你的內容: 123456

[root@kevin ~]# read -sp "請輸入你的內容: " haha;echo -e "\n${haha}"
請輸入你的內容: 
123456

下面來看看下面的一些示例:
1)基本讀取 
[root@kevin ~]# cat test.sh
#!/bin/bash
echo -n "Enter your name:"   #參數-n的作用是不換行,echo默認是換行!
read  name                   #從鍵盤輸入
echo "hello $name,welcome to my program"     #顯示信息
exit 0                       #退出shell程序。

[root@kevin ~]# sh test.sh
Enter your name:beijing
hello beijing,welcome to my program

2)使用read輸入 (read -p)
由於read命令提供了-p參數,允許在read命令行中直接指定一個提示。
上面腳本可以改進為:
[注意:read命令后必須加它自己的參數,如果不加參數,則不執行!]
[root@kevin ~]# cat test.sh
#!/bin/bash
read -p "Enter your name:" name
echo "hello $name, welcome to my program"
exit 0

[root@kevin ~]# sh test.sh
Enter your name:beijing
hello beijing, welcome to my program

需要注意:
在上面read后面的變量只有name一個,也可以有多個,多個變量使用空格隔開,這時如果輸入多個數據,則第一個數據給第一個變量,第二個數據給第二個變量;
如果輸入數據個數過多,則最后所有的值都給最后那個變量!!
如果輸入數據太少,則對應不到數據的變量為空!
在read命令行中也可以不指定變量,如果不指定變量,那么read命令會將接收到的數據放置在環境變量REPLY中。

3)read后面可以跟多個變量
[root@kevin ~]# cat test.sh
#!/bin/bash
read -p "Enter your name:" name age city
echo "hello ${name},${age},${city}, welcome to my program"
exit 0

[root@kevin ~]# sh test.sh
Enter your name:bobo 26 beijing
hello bobo,26,beijing, welcome to my program

如果輸入數據多余變量,則多余的數據都給最后那個變量!!
[root@kevin ~]# sh test.sh
Enter your name:yang 26 shanghai huoqiu haha
hello yang,26,shanghai huoqiu haha, welcome to my program

如果輸入數據少於變量,則對應不到數據的變量為空!
[root@kevin ~]# sh test.sh
Enter your name:yang 26
hello yang,26,, welcome to my program

[root@kevin ~]# sh test.sh
Enter your name:yui
hello yui,,, welcome to my program

4)不跟變量,默認使用${REPLY}
如果read后面不指定變量,則read命令會將接收到的數據放置在環境變量REPLY中!!!!
環境變量REPLY中包含輸入的所有數據,可以像使用其他變量一樣在shell腳本中使用環境變量REPLY,即${REPLY}
[root@kevin ~]# cat test.sh 
#!/bin/bash
read -p "Enter your name:"
echo "${REPLY}是輸入的內容"
exit 0

[root@kevin ~]# sh test.sh
Enter your name:zhangyang is 27,very nice!!
zhangyang is 27,very nice!!是輸入的內容

5)read的計時輸入 (read -t)
使用read命令存在着潛在危險,腳本很可能會停下來一直等待用戶的輸入。
如果無論是否輸入數據腳本都必須繼續執行,那么可以使用-t選項指定一個計時器。
-t選項指定read命令等待輸入的秒數。當計時滿時,read命令返回一個非零退出狀態;
[root@kevin ~]# cat test.sh
#!/bin/bash
if read -t 5 -p "please enter your name:" name
then
    echo "hello $name ,welcome to my script"
else
    echo "sorry,too slow"
fi
exit 0

[root@kevin ~]# sh test.sh 
please enter your name:zhangtianba
hello zhangtianba ,welcome to my script

[root@kevin ~]# sh test.sh
please enter your name:sorry,too slow

即超出5s沒有輸入內容,腳本就自動結束!

6)read的計算字符長度輸入 (read -n)
除了輸入時間計時,還可以設置read命令計數輸入的字符。當輸入的字符數目達到預定數目時,自動退出,並將輸入的數據賦值給變量。
[root@kevin ~]# cat test.sh
#!/bin/bash
read -n1 -p "Do you want to continue [Y/N]?" answer
case $answer in
Y | y)
      echo -e "\nfine ,continue";;
N | n)
      echo -e "\nok,good bye";;
*)
     echo -e "\nerror choice";;
esac
exit 0

[root@kevin ~]# sh test.sh
Do you want to continue [Y/N]?y
fine ,continue

[root@kevin ~]# sh test.sh
Do you want to continue [Y/N]?N
ok,good bye

[root@kevin ~]# sh test.sh
Do you want to continue [Y/N]?A
error choice

注意:
該例子使用了-n選項,后接數值1,指示read命令只要接受到一個字符就退出。
只要按下一個字符進行回答,read命令立即接受輸入並將其傳給變量。無需按回車鍵。
上面腳本中要在echo語句中添加換行符"\n",否則執行腳本,在輸入內容后會將echo的內容和read提示信息放在一行!
echo需要加上參數"-e"才能將后面引號內的特殊字符"\n"生效!否則就當普通字符處理了!

如果輸入的字符超過了設定的最大字符長度,則就默認使用前面最大長度值的字符!
[root@kevin ~]# cat test.sh
#!/bin/bash
read -n5 -p "please input name:" name
echo "需要輸入的名字是:${name}" 

沒有超過設定的最大字符長度,這時候會自動換行!
[root@kevin ~]# sh test.sh 
please input name:bao
需要輸入的名字是:bao

超過了設定的最大字符長度。
本來輸入的是zhangzihua,但是默認使用了前面5個字符:zhang
超過后,不會自動換行!
[root@kevin ~]# sh test.sh
please input name:zhang需要輸入的名字是:zhang

改進下,解決不換行問題:
[root@kevin ~]# cat test.sh
#!/bin/bash
read -n5 -p "please input name:" name
echo -e "\n需要輸入的名字是:${name}" 

這樣,輸入字符超過設定的最大字符長度時,就會自動換行了!
[root@kevin ~]# sh test.sh
please input name:zhang
需要輸入的名字是:zhang

再來改進下腳本:
[root@kevin ~]# cat test.sh
#!/bin/bash
read -n5 -p "please input name:" name

len=$(expr length "${name}")
#或者使用len=`echo ${#name}`
if [ ${len} -lt 5 ];then         #注意第一個邏輯判斷中的len變量不能等於設置的最大長度值,否則下面就不會執行。
   echo "輸入的名字為:${name}"   #當不超過設定的字符長度時,會自動換行
else
   echo -e "\n輸入的名字長度超過5,當前名字為${name}"
   exit 0
fi

[root@kevin ~]# sh test.sh 
please input name:bao
輸入的名字為:bao

[root@kevin ~]# sh test.sh 
please input name:hangu
輸入的名字長度超過5,當前名字為hangu

7)read的靜默輸入。(read -s)
有時會需要腳本用戶輸入,但不希望輸入的數據顯示在監視器上。典型的例子就是輸入密碼,當然還有很多其他需要隱藏的數據。
-s選項能夠使read命令中輸入的數據不顯示在監視器上(實際上,數據是顯示的,只是read命令將文本顏色設置成與背景相同的顏色!!!)。
[root@kevin ~]# cat test.sh
#!/bin/bash
read  -s -p "Enter your password: " pass
echo -e "\nyour password is $pass"
exit 0

靜默輸入,即輸入的數據是看不到的。
[root@kevin ~]# sh test.sh
Enter your password: 
your password is 123456

8)read讀取文件!!!!(cat filename | while read line)
可以使用read命令讀取Linux系統上的文件。
每次調用read命令都會讀取文件中的"一行"文本!!
當文件沒有可讀的行時,read命令將以非零狀態退出。
讀取文件的關鍵是如何將文本中的數據傳送給read命令??

最常用的方法:對文件使用cat命令並通過管道將結果直接傳送給包含read命令的while命令

[root@kevin ~]# cat test.txt 
wangbo is a boy!
beijing is good!
abc 123 sahdfksfah
asf#$$!QA

[root@kevin ~]# cat test.sh 
#!/bin/bash
count=1   
cat test.txt | while read line        #cat命令的輸出作為read命令的輸入,read讀到的值放在line中。line為讀取文件行內容的變量
do
   echo ${line}|grep -w "beijing" >/dev/null 2>&1
   if [ $? -eq 0 ];then
      echo "想要的是:${line}"
   else
      echo "Line ${count}:${line}"       #讀取的內容默認為變量${line}
      count=$[ ${count} + 1 ]            #注意中括號中的空格。
   fi
done
echo "finish"
exit 0

[root@kevin ~]# sh test.sh
Line 1:wangbo is a boy!
想要的是:beijing is good!
Line 2:abc 123 sahdfksfah
Line 3:asf#$$!QA
finish

12)shell的 case 用法

case語句還是很好理解的,在shell編程中,if語句有它的語法,函數也有它的語法,那么在shell編程中的case語句也是有它的語法的,語法格式如下:

case ${變量名} in
賦值1) 
  執行指令1 
;;                # 每一個選擇都以雙;;結束。(需要注意:;;相當於break語句)
賦值2) 
  執行指令2 
;; 
賦值3) 
  執行指令3 
;; 
*)                # *未匹配到相符的其他值   
  執行其他指令
;; 
esac

示例一
當命令行參數是 1 時,輸出 "周一", 是 2 時,就輸出"周二", 其它情況輸出 "其他"
---------------------------------------------------------------------
[root@localhost ~]# cat test.sh
#!/bin/bash
case $1 in
  1)
    echo "周一"
  ;;
  2) 
    echo "周二"
  ;;
  *)
    echo "其他"
  ;;
esac

[root@localhost ~]# sh test.sh 1
周一
[root@localhost ~]# sh test.sh 2
周二
[root@localhost ~]# sh test.sh 3
其他
[root@localhost ~]# sh test.sh 4
其他
[root@localhost ~]# sh test.sh 
其他

示例二
shell腳本中case選擇語句可以結合read指令實現比較好的交互應答操作,case接收到read指令傳入的一個或多個參數,然后case根據參數做選擇操作。
---------------------------------------------------------------------
1) 案例1
[root@localhost ~]# cat test.sh 
#!/bin/bash
echo "Please enter A,B,C"
read letter
#上面兩句可以改進為:
#read -p "Please enter A,B,C:  " letter
case $letter in
  A|a) 
    echo "you entered A"
    ;; 
  B|b) 
    echo "you entered B"
    ;;
  C|c) 
    echo "you entered C"
    ;;
  *)
    echo "Not in A,B,C"
   ;;
esac
[root@localhost ~]# sh test.sh
Please enter A,B,C
A
you entered A
[root@localhost ~]# sh test.sh
Please enter A,B,C
b
you entered B
[root@localhost ~]# sh test.sh
Please enter A,B,C
C
you entered C
[root@localhost ~]# sh test.sh
Please enter A,B,C
w3
Not in A,B,C
[root@localhost ~]# sh test.sh

2) 案例二:查看系統資源使用情況
[root@localhost ~]# cat test.sh
#!/bin/bash
echo "check system run status"
echo "show CPUinfo: C/c "
echo "show Memery used: M/m "
echo "show Disk use status: D/n "
echo "show System user login: U/n "
echo "show System load average:L/l"
echo "show System Ip address: I/i"

read_input () {
    read  -t 10 -p "please Input C/M/D/U/L/I : " char
}
show_status () {
case $char in 
    C | c )
        cat /proc/cpuinfo | grep -o -i 'model name.*'
        ;;
    M | m )
        free -m
        ;;
    D | d )
        df -h
        ;;
    U | u )
        w
        ;;
    L | l )
        top | head -1 | cut -d " " -f 11-15
        ;;
    I | i )
        ifconfig | grep -o "[0-9.]\{7,\}" | head -1
        ;;
    * )
        echo "The characters you have entered are wrong. Please look at the hints"
    ;;
esac
}

for i in $( seq 1 10)       #呼應前面"read -t 10"中的10秒鍾要輸入內容的限制
do
  read_input
  show_status
if [ $i -eq 10 ]; then
   echo "已經到達查詢的最大次數,腳本退出;"
fi
done

[root@localhost ~]# sh test.sh
check system run status
show CPUinfo: C/c 
show Memery used: M/m 
show Disk use status: D/n 
show System user login: U/n 
show System load average:L/l
show System Ip address: I/i
please Input C/M/D/U/L/I : c
model name      : Intel(R) Xeon(R) CPU E5-2640 v4 @ 2.40GHz
model name      : Intel(R) Xeon(R) CPU E5-2640 v4 @ 2.40GHz
model name      : Intel(R) Xeon(R) CPU E5-2640 v4 @ 2.40GHz
model name      : Intel(R) Xeon(R) CPU E5-2640 v4 @ 2.40GHz
please Input C/M/D/U/L/I : m   
              total        used        free      shared  buff/cache   available
Mem:           7815         250        5826          56        1738        5102
Swap:          2047           0        2047
please Input C/M/D/U/L/I : d
Filesystem               Size  Used Avail Use% Mounted on
devtmpfs                 3.9G     0  3.9G   0% /dev
tmpfs                    3.9G     0  3.9G   0% /dev/shm
tmpfs                    3.9G   57M  3.8G   2% /run
tmpfs                    3.9G     0  3.9G   0% /sys/fs/cgroup
/dev/mapper/centos-root   20G  3.2G   17G  16% /
/dev/mapper/centos-data   78G   33M   78G   1% /data
/dev/xvda1               197M  166M   32M  85% /boot
tmpfs                    782M     0  782M   0% /run/user/0
please Input C/M/D/U/L/I : u   
 11:07:27 up 31 days, 17:24,  2 users,  load average: 0.00, 0.01, 0.05
USER     TTY      FROM             LOGIN@   IDLE   JCPU   PCPU WHAT
root     tty1                      30Oct19 31days  0.03s  0.03s -bash
root     pts/0    172.16.198.22    09:57    7.00s  0.18s  0.00s w
please Input C/M/D/U/L/I : l
 load average: 0.00, 0.01,
please Input C/M/D/U/L/I : i
172.16.60.236
please Input C/M/D/U/L/I : a
The characters you have entered are wrong. Please look at the hints
please Input C/M/D/U/L/I : The characters you have entered are wrong. Please look at the hints
please Input C/M/D/U/L/I : The characters you have entered are wrong. Please look at the hints
please Input C/M/D/U/L/I : The characters you have entered are wrong. Please look at the hints
已經到達查詢的最大次數,腳本退出;

案例3: 
有時經常會使用比較長的if,then,else來嘗試計算一個變量的值,在一組可能的值中尋找特定值。其實這種情況如果使用case遇見就會變得簡單的多了!
使用多個if判斷來一一核對,代碼量比較多,還容易亂。用case的話,能減少代碼量,不需要再寫出所有的elif語句來不停地檢查同一個變量的值了。case命
令會采用列表格式來檢查單個變量的多個值。如下兩種方法效果一樣:

if判斷語句寫法:
[root@localhost ~]# cat test.sh
#!/bin/bash

USER=$1
if [ $USER = "kevin" ];then
  echo "Welcome $USER,Please enjoy your visit"
elif [ $USER = "grace" ];then
  echo "Welcome $USER,Please enjoy your visit"
elif [ $USER = "xiaoru" ];then
  echo "Special testing account"
elif [ $USER = "shibo" ];then
  echo "Do not forget to logout when you're done"
else
  echo "Sorry, you are not allowed here"
fi

case語句寫法:
[root@localhost ~]# cat test.sh 
#!/bin/bash

USER=$1
case ${USER} in
kevin|grace)
   echo "Welcome $USER,Please enjoy your visit";;
xiaoru)
   echo "Special testing account";;
shibo)
   echo "Do not forget to logout when you're done";;
*)
   echo "Sorry, you are not allowed here";;
esac

示例三: 
case語句經常會使用在應用服務的一鍵部署腳本中。
比如:https://www.cnblogs.com/kevingrace/p/6086426.html
---------------------------------------------------------------------

示例四:
結合shell的"function函數+if邏輯判斷+case選擇語句
---------------------------------------------------------------------
[root@localhost ~]# cat test.sh 
#!/bin/bash 
 
function OPS(){   #定義一個OPS的函數
cat << kevin_test
1.北京
2.上海 
3.深圳 
kevin_test
} 
OPS       #調用CDAN函數 
read -p "請輸入您想要去的地方: " WHERE    #輸入一條提示,然后把用戶輸入的字符串賦值給變量WHERE
expr ${WHERE} + 1 >/dev/null 2>&1         #使用數值運算命令expr來確定用戶輸入的是否是數值
if [ "$?" -ne 0 ];then                    #如果用戶輸入的不是數值        
  echo "請您輸入{1|2|3}"      
  exit 1       
fi        
  
case ${WHERE} in     
1)       
  echo "明天去北京!"     
;; 
2)        
  echo "明天去上海!"   
;; 
3)               
  echo "明天去深圳!"
;; 
esac   

[root@localhost ~]# sh test.sh
1.北京
2.上海 
3.深圳 
請輸入您想要去的地方: 1
明天去北京!

示例五:
再來分享一例shell的case循環用法
---------------------------------------------------------------------
要求:
輸入a|A顯示出紅色的本機IP
輸入b|B顯示出綠色的本機磁盤的剩余內存
輸入c|C顯示出黃色的系統運行時間
輸入 q|Q顯示出藍色的直接退出

[root@localhost ~]# cat test.sh 
#!/bin/bash
while true
do
    echo -e "
    \033[31m A 顯示主機ip \033[0m
    \033[32m B 顯示磁盤剩余空間 \033[0m
    \033[33m C 顯示系統運行時間 \033[0m
    \033[34m Q 退出系統 \033[0m
            "
read -p "請輸入你的選擇:" char
case ${char} in
a|A)
    echo -e "\033[31m `ifconfig eth0 | grep "netmask" | awk '{print $2}'` \033[0m"
    ;;
b|B)
    echo -e "\033[32m `df -h | awk 'NR==2{print "剩余空間大小為:"$4}'` \033[0m"
    ;;
c|C)
    echo -e "\033[33m `uptime | awk '{print "系統已經運行了"$3""$4""}'` \033[0m"
    ;;
q|Q)
    exit 0
    ;;
*)
    echo "請輸入A/B/C/Q"
    ;;
esac
done


[root@localhost ~]# sh test.sh

     A 顯示主機ip 
     B 顯示磁盤剩余空間 
     C 顯示系統運行時間 
     Q 退出系統 
            
請輸入你的選擇:a
 172.16.60.238 

     A 顯示主機ip 
     B 顯示磁盤剩余空間 
     C 顯示系統運行時間 
     Q 退出系統 
            
請輸入你的選擇:b
 剩余空間大小為:3.9G 

     A 顯示主機ip 
     B 顯示磁盤剩余空間 
     C 顯示系統運行時間 
     Q 退出系統 
            
請輸入你的選擇:c
 系統已經運行了32days, 

     A 顯示主機ip 
     B 顯示磁盤剩余空間 
     C 顯示系統運行時間 
     Q 退出系統 
            
請輸入你的選擇:d
請輸入A/B/C/Q

     A 顯示主機ip 
     B 顯示磁盤剩余空間 
     C 顯示系統運行時間 
     Q 退出系統 
            
請輸入你的選擇:q
[root@localhost ~]# 

12)Shell的 for、case、while 循環流程控制語句用法
shell作為一種腳本編程語言,同樣包含循環、分支等其他程序控制結構,從而輕松完成更加復雜、強大的功能。

編寫腳本的思路
1. 明確腳本的功能
2. 編寫腳本時會使用到那些命令
3. 把變化的數據使用變量表示
4. 選擇適合的流程控制  (選擇 、 循環 、分支)
 
一、使用for循環語句
========================================================================================================
在工作中,經常遇到某項任務需要多次執行,而每次執行僅僅是處理對象不一樣,其他命令都相同。使用簡單的if語句已經難以滿足要求,
編寫全部代碼將困難重重,而for循環語句將很好的解決類似的問題。
 
for語句的結構   
使用for循環語句時,需要指定一個變量及可能的取值列表,針對每一個不同的取值重復執行相同的命令,直到變量值用完退出循環。
 
for的語法結構:
for;do;done
 
語法格式:
for 變量名 in 列表內容
do
  commands
done
 
或者
for 變量名 in 列表內容 ;do
  commands
done
 
示例1):使用嵌套循環輸出99乘法表
[root@localhost ~]# cat test.sh
#!/bin/bash
for i in `seq 9`
do
    for j in `seq 9`
    do
      [ $j -le $i ] && echo -n "$j x $i = `echo $(($j*$i))`  "   #如果j 小與等於i才會打印式子。注意后面雙引號后面要有一個空格,表示下面執行時各列間的空格。
    done
echo ""
done
 
注意:外層循環循環行,內層循環循環列。$(())返回的是里面運算結果;echo -n表示不換行
規律:  內層循環的變量<=外層循環的變量
 
[root@localhost ~]# sh test.sh
1 x 1 = 1 
2 x 1 = 2  2 x 2 = 4 
3 x 1 = 3  3 x 2 = 6  3 x 3 = 9 
4 x 1 = 4  4 x 2 = 8  4 x 3 = 12  4 x 4 = 16 
5 x 1 = 5  5 x 2 = 10  5 x 3 = 15  5 x 4 = 20  5 x 5 = 25 
6 x 1 = 6  6 x 2 = 12  6 x 3 = 18  6 x 4 = 24  6 x 5 = 30  6 x 6 = 36 
7 x 1 = 7  7 x 2 = 14  7 x 3 = 21  7 x 4 = 28  7 x 5 = 35  7 x 6 = 42  7 x 7 = 49 
8 x 1 = 8  8 x 2 = 16  8 x 3 = 24  8 x 4 = 32  8 x 5 = 40  8 x 6 = 48  8 x 7 = 56  8 x 8 = 64 
9 x 1 = 9  9 x 2 = 18  9 x 3 = 27  9 x 4 = 36  9 x 5 = 45  9 x 6 = 54  9 x 7 = 63  9 x 8 = 72  9 x 9 = 81

示例2):根據IP地址檢查主機狀態
[root@localhost ~]# vim test.sh
#!/bin/bash
for NUM in $(seq 1 254)    #或者直接"seq 254" 或者 "seq 10 30"用於一段ip
do 
   IP=172.16.60.${NUM}
   # -c表示ping的次數,-i表示時間間隔(秒),
   ping -c 3 -i 0.2  $IP &> /dev/null
   if [ $? -eq 0 ];then
     echo "$IP is up"
   else
      echo "$IP id down"
   fi
done

示例3):文件列表循環
[root@localhost ~]# vim test.sh
#!/bin/bash
cd /etc/
for a in `ls /etc/`
do
    if [ -d $a ]
    then
  ls -d $a
    fi
done

二、使用while循環語句
========================================================================================================
for語句適用於列表對象無規律,且列表來源以固定的場合。而對於要求控制循環次數、操作對象按數字順序編號、按特定的條件重復操作等情況,則更適合於while循環語句。
while循環:重復測試某個條件,只要條件成立,就重復執行命令,條件不成立,立即退出,自帶判斷;
 
while語句的結構
使用while循環語句時,可以根據特定的條件反復執行一個命令序列,直到該條件不在滿足為止。
需要注意:要避免出現while ture 的死循環!!!
 
語法格式如下:
while 測試命令
do
命令
done

退出while循環體的三種方式:
1. 條件為假退出循環體,繼續執行循環體以外的命令;
2. exit退出腳本,循環體外的命令不會執行;
3. break退出腳本中的循環體,繼續執行循環體外的命令;

特殊條件表達式:
1. true :當條件表達式為true時,那么代表條件表達式永遠成立,為真;
2. false:當條件表達式為false時,那么條件表達式永遠為假;

示例1):降序輸出10到1
[root@localhost ~]# cat test.sh
#!/bin/bash
num=10
while [ ${num} -gt 0 ]
do
    echo "${num}"
    num=$[${num}-1]
    #或者使用下面的表達式也可以,$(())或$[]都表示返回運算結果
    num=$((${num}-1))
done
[root@localhost ~]# sh test.sh
10
9
8
7
6
5
4
3
2
1

示例2):批量添加用戶
用戶名稱以kevin_開頭,按照數字順序進行編號
添加10個用戶,即kevin_1、kevin_2、...、kevin_10
初始密碼均設為123456
[root@VM_16_9_centos ~]# cat test.sh 
#!/bin/bash
UR="kevin_"
NUM=1
while [ ${NUM} -le 10 ]
do
    USER=${UR}${NUM}
    useradd ${USER}
    echo "123456"|passwd --stdin ${USER}
    NUM=$((${NUM}+1))
    #或者使用let NUM++,效果等同於NUM=$((${NUM}+1))或者NUM=$[${NUM}+1]
done

示例3)判斷輸入的數要是數字
[root@ss-server ~]# cat test.sh
#!/bin/bash
while :
do
    read -p "Please input a number: " n
    if [ -z "$n" ];then               #空串為真
        echo "you need input sth."
        continue
    fi
    n1=`echo $n|sed 's/[0-9]//g'`
    if [ -n "$n1" ];then              #非空串為真
        echo "you just only input numbers."
        continue
    fi
    break
done
echo $n

[root@ss-server ~]# sh test.sh 
Please input a number: 2
2
[root@ss-server ~]# sh test.sh 
Please input a number: 2a
you just only input numbers.
Please input a number: 
you need input sth.
Please input a number: 5
5
 
三、使用case分支語句
========================================================================================================
case語句主要適用於以下情況:
某個變量存在多種取值,需要對其中的每一種取值分別執行不同的命令序列。與多分支if語句相識,只是if語句需要判斷多個不同的條件,而case只是判斷一個變量的不同取值
 
1) case語句的結構如下:
case  變量或表達式  in
變量或表達式1)
   命令序列1
;;
變量或表達式2)
  命令序列2
;;
......
*) 
  默認命令序列
;;
esac
 
2) case執行流程
1. 首先使用"變量或表達式"的值與值1進行比較,若取值相同則執行值1后的命令序列,直到遇見雙分號";;"后跳轉至esac,表示分支結束;
2. 若與值1不相匹配,則繼續與值2進行比較,若取值相同則執行值2后的命令序列,直到遇見雙分號";;"后跳轉至esac,表示結束分支;
3. 依次類推,若找不到任何匹配的值,則執行默認模式"*)"后的命令序列,直到遇見esac后結束分支。
 
3) case執行流程注意事項
1. "變量或表達式"后面必須為單詞in,每一個"變量或表達式"的值必須以右括號結束。取值可以為變量或常數。匹配發現取值符合某一模式后,其間所有命令開始執行直至;;
2. 匹配中的值可以是多個值,通過"|"來分隔。
3. 匹配中的值可以是正則。
 
示例1):編寫一個備份,拷貝的交互式腳本
[root@localhost ~]# cat test.sh
#!/bin/bash
cat <<eof
*****************
**1. backup
**2. copy
**3. quit
*****************
eof
 
read -p "input your choose: " OP
case $OP in
1|backup)
   echo "Backup..."
;;
2|copy)
   echo "Copy..."
;;
3|quit)
   exit
;;
*)
   echo input error
esac
[root@localhost ~]# sh test.sh
*****************
**1. backup
**2. copy
**3. quit
*****************
input your choose: 1
Backup...
[root@localhost ~]# sh test.sh
*****************
**1. backup
**2. copy
**3. quit
*****************
input your choose: 2
Copy...
 
示例2):提示用戶輸入一個字符,判斷出該字符是字母、數字
[root@localhost ~]# cat test.sh 
#!/bin/bash
#read后面跟的變量必須要空格隔開,否則變量無效!
read -p "請輸入一個字符: " star

case ${star} in
[a-z]|[A-Z])
    echo "輸入的是一個字母"
;;
[0-9])
    echo "輸入的是一個數字"
;;
*)  
    echo "請輸入字母或數字"
;;
esac

[root@localhost ~]# sh test.sh
請輸入一個字符: a
輸入的是一個字母
[root@localhost ~]# sh test.sh
請輸入一個字符: 3
輸入的是一個數字

13)Shell的 function 函數用法

簡單的說,Shell函數的作用就是將程序里面多次被調用的代碼組合起來,稱為函數體,並取一個名字稱為(函數名),當需要用到這段代碼的時候,就可以直接來調用函數名。

Shell函數是一個腳本代碼塊,可以對它進行自定義命名,並且可以在腳本中任意位置使用這個函數。
如果想要這個函數,只要調用這個函數的名稱就可以了。使用函數的好處在於模塊化以及代碼可讀性強。

一、Shell函數的創建語法
================================================================================================
在shell中 if語句有它的語法,for循環也有它的語法,那么shell中的函數,那肯定也有它的語法有以下三種:

函數的創建方法一:
function 函數名 () {  
        指令...  
        return -n  
}  

函數的創建方法二:
function 函數名 {  
        指令...  
        return -n  
}  

函數的創建方法三:
函數名 () {  
    指令...  
    return -n  
}  

需要注意:
1. 在以上三種函數語法中,前面的funcation 表示聲明一個函數! 可以不寫 return -n 是指退出函數!
2. 上面最后兩種函數創建方法的聲明方式效果等價(即函數的創建方法二和函數的創建方法三的效果是一樣的)!!!!!!!
3. 如果函數名后面沒有跟(),則函數名和"{"之間必須有空格!shell對空格變態的敏感。如果函數名后面有(),則兩者之間可以有空格,也可沒有空格。
4. 不得聲明形式參數。
5. 必須在調用前聲明。
6. 無法重載。
7. 后來的聲明會覆蓋之前的聲明。即如果存在相同名稱的函數,以最后一個為准!

另外注意:函數名稱在當前腳本必須唯一。

二、Shell函數調用的方法
================================================================================================
調用方法1:直接指定函數名即可。但一定要注意在聲明之后才可以調用函數!!格式如下:
函數名稱

調用方法2:調用函數時可以傳遞參數,函數內部中使用$1、$2......來引用傳遞的參數。格式如下:
函數名稱 參數1 參數2 ......

需要注意:
1. 其實函數被調用時會被當作一個小腳本來看待,調用時也可以在函數名后跟參數。
2. Shell函數在調用時都不可以加() 

$1 #調用第一個參數
$2 #調用第二個參數
...
$n #調用第n個參數
$# #返回參數個數n
$0 #當前腳本文件名

三、Shell函數的返回值
================================================================================================
Shell函數運行結束后會有一個退出狀態碼,可以用$?變量來顯示上一條命令/函數執行結束的退出狀態碼。
當然,shell也為我們提供了return,像其他語言函數中return 一樣,不過(整形)返回值必須在0~255之間。

四、Shell函數創建庫
================================================================================================
與c的頭文件類似,在Shell中,也可以定義"庫文件",然后再另一個文件中導入。庫文件沒有特殊聲明或者定義,也是腳本文件.sh。
使用庫函數的關鍵在於導入庫文件。用source來導入,source實際上會在當前shell上下文中執行命令,從而達到導入效果。
注意:使用"source"或點符號"."都可以導入庫文件!(如下示例7)

五、在"~/.bashrc"文件中定義Shell函數
================================================================================================
在使用函數的庫文件時,如果每次都需要自己去導入定義的庫文件會顯得很麻煩!那么,我們可不導入直接使用呢?答案是肯定可以的!!!
方法就是在Shell的配置文件的.bashrc中聲明該函數,因為每次啟動shell都會載入.bashrc文件,所以就實現了"自動導入庫文件"!!!(如下示例8)

六、Shell函數使用實例
================================================================================================

示例1:直接在調用函數時進行參數傳遞!可以直接傳遞具體的變量值!
[root@ss-server ~]# cat test.sh
#!/bin/bash

function kevin(){
      echo "welcome to anhui!"
}

function shibo(){
     echo "$1+$2"
}

grace(){
     echo $(($1+$2+$3))
}

kevin
shibo hello world
grace 2 4 5
[root@ss-server ~]# sh test.sh
welcome to anhui!
hello+world
11

再看一例:調用函數時,也可以直接傳遞變量。在腳本執行的時候再賦予變量具體的值!
[root@ss-server ~]# cat test.sh
#!/bin/bash

function kevin () {
    echo "老子想要的是:$1 $2"
}
kevin $1 $2

[root@ss-server ~]# sh test.sh 房子 車子
老子想要的是:房子 車子

示例2:如果存在相同名稱的函數,以最后一個為准!即后來的函數聲明會覆蓋之前的聲明!
[root@ss-server ~]# cat test.sh
#!/bin/bash

function kevin(){
      echo "welcome to anhui!"
}

kevin(){
     echo "hello world"
}

kevin
[root@ss-server ~]# sh test.sh
hello world

示例3:return返回值
使用return命令來退出函數並返回特定的退出碼($?)
[root@ss-server ~]# cat test.sh
#!/bin/bash

function kevin(){
      echo "welcome to anhui!"
      return 2
      echo "why reruen"
}

kevin

[root@ss-server ~]# sh test.sh
welcome to anhui!

需要注意:
return一般是在函數的最后一行,因為一旦執行return命令,該函數后面的命令就不執行了。
return與exit的區別:return和exit都可以返回退出碼,但是不同的是,return是退出函數,而exit是退出整個腳本。

示例4:函數值賦給變量
如下方實例中顯示,此時的函數就相當於一個命令,需要使用$()或``調用。
[root@ss-server ~]# cat test.sh
#!/bin/bash

function kevin(){
       read -p "請輸入內容: " str
       echo ${str}
}

bo=$(kevin)   #也可以是bo=`kevin`
echo "測試結果為${bo}"
[root@ss-server ~]# sh test.sh
請輸入內容: xinzhongguo
測試結果為xinzhongguo

示例5:外部參數傳入函數
前面已經提到過,調用函數可以在后面跟隨參數,函數內部可以使用$n的形式調用。
[root@ss-server ~]# cat test.sh
#!/bin/bash

function kevin_1(){
     echo "this is $1"
}

function kevin_2 {
     echo "this is $1+$2"
     echo $#
}

kevin_3() {
     echo "this is $1!!"
}

kevin_1 安徽
kevin_2 上海 北京
kevin_3 /root/pass.list

[root@ss-server ~]# sh test.sh 
this is 安徽
this is 上海+北京
2
this is /root/pass.list!!

示例6:函數的參數
在一個Shell腳本當中:
函數外的參數,函數可以直接調用;
函數內的參數,只要運行過函數,外部也可以直接調用。
[root@ss-server ~]# cat test.sh
#!/bin/bash

str="hello world"
function kevin(){
       bo="${str} is very nice!!"
}

kevin
echo "${bo}, 666~"

[root@ss-server ~]# sh test.sh
hello world is very nice!!, 666~

示例7:導入庫文件
說白了,就是在一個shell腳本中導入另一個腳本,導入腳本中的變量在新腳本中同樣有效可用!
[root@ss-server ~]# cat /root/haha.sh               
#!/bin/bash

USER=$1
ADDRESS=$2
AGE=$3

function PER(){
    if [ ${AGE} -gt 30 ];then
       echo "來自${ADDRESS}的${USER}是一個大叔!"
    fi
}

[root@ss-server ~]# cat test.sh               
#!/bin/bash

#可以使用soirce或.導入庫文件
#source /root/haha.sh
. /root/haha.sh

function TES {
     YOU=`PER`
     echo "告訴你!${YOU}"
}

TES

[root@ss-server ~]# sh test.sh 李楠 霍城 38
告訴你!來自霍城的李楠是一個大叔!

示例8:在"~/.bashrc"文件中定義Shell函數
[root@ss-server ~]# cat ~/.bashrc
........
#定義PER函數
USER=$1
ADDRESS=$2
AGE=$3
function PER(){
    if [ ${AGE} -gt 30 ];then
       echo "來自${ADDRESS}的${USER}是一個大叔!"
    fi
}

[root@ss-server ~]# cat test.sh 
#!/bin/bash

source ~/.bashrc

function TES {
     YOU=`PER`
     echo "告訴你!${YOU}"
}

TES

[root@ss-server ~]# sh test.sh 李楠 霍城 38
告訴你!來自霍城的李楠是一個大叔!

--------------------------------
或者:
[root@ss-server ~]# cat ~/.bashrc 
......
source /root/haha.sh

[root@ss-server ~]# cat /root/haha.sh 
#!/bin/bash

USER=$1
ADDRESS=$2
AGE=$3

function PER(){
    if [ ${AGE} -gt 30 ];then
       echo "來自${ADDRESS}的${USER}是一個大叔!"
    fi
}

[root@ss-server ~]# cat test.sh 
#!/bin/bash

source ~/.bashrc

function TES {
     YOU=`PER`
     echo "告訴你!${YOU}"
}

TES

[root@ss-server ~]# sh test.sh 李楠 霍城 38
告訴你!來自霍城的李楠是一個大叔!

14)Shell獲取隨機數 的 random 用法

1)使用$RANDOM
需要系統支持,通過echo來檢測, 打印出一個隨機數字,證明當前環境支持$RANDOM,反之為空不支持:
[root@bz3aomsmsap1002 ~]# echo $RANDOM
1525
[root@bz3aomsmsap1002 ~]# echo $RANDOM
16218

2)使用/dev/urandom + tr
[root@bz3aomsmsap1002 ~]# tr -cd 0-9 </dev/urandom | head -c 8   #取8位隨機種子
67128612
[root@bz3aomsmsap1002 ~]# tr -cd 0-9 </dev/urandom | head -c 16  #取12位隨機種子
8398375834495017

隨機生成密碼(如下兩種方式都可以,生成16位長度的隨機密碼)
[root@bz3aomsmsap1002 ~]# head /dev/urandom | tr -dc A-Za-z0-9 | head -c 16
gXeLJ5XLAaV26tKJ
[root@bz3aomsmsap1002 ~]# tr -dc A-Za-z0-9 </dev/urandom | head -c 16
0PUw4BHWQw8oQ3ai

15)快速去除文件或字符串中的空格(使用sed或tr)

1. 刪除字符串中的空格(使用sed或tr)
[root@ss-server ~]# echo "aa dd"|sed 's/ //g'
aadd
[root@ss-server ~]# echo "aa dd"|tr -d " " 
aadd
[root@ss-server ~]# echo " aa b t "|tr -d " "   
aabt
[root@ss-server ~]# echo " root, bin, hook"|tr -d " "            
root,bin,hook

2. 刪除文件中行首空格的命令
# sed 's/^[ \t]*//g' filename        #不加-i參數,表示僅僅在當前終端顯示命令執行效果。
# sed -i 's/^[ \t]*//g' filename     #加-i參數,表示命令執行效果直接為文件生效,即直接在文件中刪除行首空格。

命令解釋:
第一個/的左邊是s表示替換,即將空格替換為空。
第一個/的右邊是中括號表示"或",空格或tab中的任意一種。這是正則表達式的規范。
中括號右邊是*,表示一個或多個。
第二個和第三個\中間沒有東西,表示空
g表示替換原來buffer(緩沖區)中的,sed在處理字符串的時候並不對源文件進行直接處理,先創建一個buffer,但是加g表示對原buffer進行替換
整體的意思是:用空字符去替換一個或多個用空格或tab開頭的本體字符串

示例:
[root@ss-server ~]# cat test
   aa 
bb
123  aa
   sadf
12313 yy
  45
[root@ss-server ~]# cat test|sed 's/^[ \t]*//g' test   
aa 
bb
123  aa
sadf
12313 yy
45
[root@ss-server ~]# cat test|sed -i 's/^[ \t]*//g' test
[root@ss-server ~]# cat test
aa 
bb
123  aa
sadf
12313 yy
45

3. 刪除文件中行尾空格的命令
# sed 's/[ \t]*$//g' filename              #僅僅在當前終端顯示執行效果。
# sed -i 's/[ \t]*$//g' filename           #直接刪除文件中行尾空格。

命令解釋:
和上面稍微有些不同是前面刪除了^符,在后面加上了美元符。表示刪除行尾空格。

4. 刪除文件中所有的空格的命令
# sed s/[[:space:]]//g filename
# sed 's/[[:space:]]//g' haha
# sed -i 's/[[:space:]]//g' haha
# cat filename|tr -d " "           #使用tr刪除所有空格,只是在終端顯示里生效,文件里默認並不會生效

示例:
[root@ss-server ~]# cat haha
  12 3 4
ads ss
12 3123  asdf
ok jk    1   
[root@ss-server ~]# sed 's/[[:space:]]//g' haha
1234
adsss
123123asdf
okjk1
[root@ss-server ~]# cat haha     
  12 3 4
ads ss
12 3123  asdf
ok jk    1   
[root@ss-server ~]# sed -i 's/[[:space:]]//g' haha
[root@ss-server ~]# cat haha
1234
adsss
123123asdf
okjk1

[root@ss-server ~]# cat haha
  12 3 4
ads ss
12 3123  asdf
ok jk    1
[root@ss-server ~]# cat haha|tr -d " "
1234
adsss
123123asdf
okjk1
[root@ss-server ~]# cat haha
  12 3 4
ads ss
12 3123  asdf
ok jk    1
[root@ss-server ~]# cat haha|tr -d " " > haha.txt && \cp -f haha.txt haha && rm -f haha.txt 
[root@ss-server ~]# cat haha
1234
adsss
123123asdf
okjk1

16)如何根據截取某個字段多少位進行去重?以及連續重復字符去重

一、tr命令加-s參數,表示把連續重復的字符以單獨一個字符表示。即壓縮字符,但是必須是連續重復的單個字符!注意是連續出現的"單個"字符!!
[root@ss-server ~]# echo "111222342133"|tr -s "1" 
1222342133
[root@ss-server ~]# echo "111222342133"|tr -s "12"
12342133
[root@ss-server ~]# echo "111222342133"|tr -s "123"
1234213
[root@ss-server ~]# echo "aa966ha34jj9"|tr -s "a"
a966ha34jj9
[root@ss-server ~]# echo "aa966ha34jj9"|tr -s "a96"
a96ha34jj9
 
還可以使用正則
[root@ss-server ~]# echo "aaAAF7889HHjkk09"|tr -s "a-zA-Z0-9"
aAF789Hjk09
 
二、根據下面test文件的第一個字段、第二個字段截取前8位進行排序去重
[root@ss-server ~]# cat /root/test
25ds51dd225d86af,20180725115911,22570,20443,120.17138,30.119047
002a51dd225d86af,20180725120017,22570,184680195,120.176506,30.11527
002a51dd225d86af,20180725120058,22290,80489347,120.1810786,30.10003
002a51dd225d86af,20180725120149,22290,80489345,120.1810786,30.10003
002a51dd225d86af,20180725120209,22290,189880577,120.18859,30.093405
102a51dd225d86af,20180725120239,22290,155606668,120.1990471,30.0961
002a51dd225d86af,20180725120303,22290,155606666,120.1990468,30.0961
002a51dd225d86af,20180725120434,22290,193501442,120.220661,30.09723
002a79ded185cb04,20180725125428,22570,185263107,120.1576002,30.1293
002a79ded185cb04,20180726125649,22290,80489347,120.1810786,30.10003
 
運用的技巧:
echo ${var:0:8}  表示從${var}變量的第1個字符(左邊的下標從0開始)開始截取,截取的總個數為8!
xargs -n2   表示將前面命令的結果按照每行2列顯示(從第一行開始算,往下每行2列,直至分配完為止,不夠的就是一列)
sort|uniq        表示去重,僅僅去掉連續出現的相同記錄

1)腳本1:打印test文件的第一個和第二個字段以及自個截取的前面八位字符。
[root@ss-server ~]# cat test_0.sh
#!/bin/bash
 
file=$(cat /root/test)
while read line
do
    #輸出每一行
    echo line=${line}
 
    #截取一行的第一列
    column1=`echo ${line}| cut -d "," -f1`
    echo ${column1}
    #輸出前8個字符
    echo ${column1:0:8}
 
    #截取一行的第二列
    column2=`echo ${line}| cut -d "," -f2`
    echo ${column2}
    #輸出前8個字符
    echo ${column2:0:8}
done <<EOF
${file}
EOF
 
[root@ss-server ~]# sh test_0.sh 
line=25ds51dd225d86af,20180725115911,22570,20443,120.17138,30.119047
25ds51dd225d86af
25ds51dd
20180725115911
20180725
line=002a51dd225d86af,20180725120017,22570,184680195,120.176506,30.11527
002a51dd225d86af
002a51dd
20180725120017
20180725
line=002a51dd225d86af,20180725120058,22290,80489347,120.1810786,30.10003
002a51dd225d86af
002a51dd
20180725120058
20180725
line=002a51dd225d86af,20180725120149,22290,80489345,120.1810786,30.10003
002a51dd225d86af
002a51dd
20180725120149
20180725
line=002a51dd225d86af,20180725120209,22290,189880577,120.18859,30.093405
002a51dd225d86af
002a51dd
20180725120209
20180725
line=102a51dd225d86af,20180725120239,22290,155606668,120.1990471,30.0961
102a51dd225d86af
102a51dd
20180725120239
20180725
line=002a51dd225d86af,20180725120303,22290,155606666,120.1990468,30.0961
002a51dd225d86af
002a51dd
20180725120303
20180725
line=002a51dd225d86af,20180725120434,22290,193501442,120.220661,30.09723
002a51dd225d86af
002a51dd
20180725120434
20180725
line=002a79ded185cb04,20180725125428,22570,185263107,120.1576002,30.1293
002a79ded185cb04
002a79de
20180725125428
20180725
line=002a79ded185cb04,20180726125649,22290,80489347,120.1810786,30.10003
002a79ded185cb04
002a79de
20180726125649
20180726
 
2)腳本2: 改進下,僅僅獲取第一個和第二個字段的前面8位字符
[root@ss-server ~]# cat test_1.sh
#!/bin/bash
 
file=$(cat /root/test)
while read line
do
    #輸出每一行
    #echo line=${line}
 
    #截取一行的第一列
    column1=`echo ${line}| cut -d "," -f1`
    #echo ${column1}
    #輸出前8個字符
    echo ${column1:0:8}
 
    #截取一行的第二列
    column2=`echo ${line}| cut -d "," -f2`
    #echo ${column2}
    #輸出前8個字符
    echo ${column2:0:8}
done <<EOF
${file}
EOF
 
[root@ss-server ~]# sh test_1.sh
25ds51dd
20180725
002a51dd
20180725
002a51dd
20180725
002a51dd
20180725
002a51dd
20180725
102a51dd
20180725
002a51dd
20180725
002a51dd
20180725
002a79de
20180725
002a79de
20180726
 
3)接着繼續改進,獲取第一個和第二個字段的前面8位字符,並排序去重
[root@ss-server ~]# sh test_1.sh |xargs -n2
25ds51dd 20180725
002a51dd 20180725
002a51dd 20180725
002a51dd 20180725
002a51dd 20180725
102a51dd 20180725
002a51dd 20180725
002a51dd 20180725
002a79de 20180725
002a79de 20180726
[root@ss-server ~]# sh test_1.sh |xargs -n2|sort|uniq
002a51dd 20180725
002a79de 20180725
002a79de 20180726
102a51dd 20180725
25ds51dd 20180725
[root@ss-server ~]# sh test_1.sh |xargs -n2|sort|uniq -u|sed 's/ /,/g'
002a51dd,20180725
002a79de,20180725
002a79de,20180726
102a51dd,20180725
25ds51dd,20180725

17)shell特殊符號:sort排序,wc統計,uniq去重,teesplit

1)shell特殊符號
*   任意個任意字符。可以是單個字符,也可以是多個字符。
?  任意一個字符。只能是單個字符
#   注釋字符
\   脫義字符,轉義符號
  
$   變量的前綴
$   正則里面表示行尾
^   正則里面表示行首
;   多條命令寫到一行,用;分割
~   用戶的家目錄。正則表達式里表示匹配符
&   把命令放到后台
>   正確重定向
>>  正確追加重定向
2>   錯誤重定向
2>>  錯誤追加重定向
&>   正確錯誤重定向。  通過在一條命令后面加上">/dev/null 2>&1"表示這條命令執行后不打印任何信息,執行正確或錯誤信息都不打印!
&&   當前面的命令執行成功時,才執行后面的命令
||   用在shell中表示或者的意思,如果第一條命令執行成功,則不執行第二條命令。如果第一條命令不成功,則執行第二條命令
  
[root@ss-server ~]# xx=aa
[root@ss-server ~]# yy=bb
[root@ss-server ~]# echo ${xx}${yy}
aabb
[root@ss-server ~]# echo ${xx}\${yy}
aa${yy}
[root@ss-server ~]# echo \${xx}\${yy}
${xx}${yy}
  
2)管道符和cut
cut 截取
-d  指定分隔符
-f  指定截取那一段
  
# cat filename|cut -d":" -f2           打印filename文件中以:分割的第2列
# cat filename|cut -d ":" -f 1-3       打印filename文件中以:分割的第1到3列,但是會保留分隔符!!!!!
  
[root@ss-server ~]# cat test|cut -d"/" -f2
b
2
[root@ss-server ~]# cat test|cut -d"/" -f1-3
a/b/c
1/2/3
  
[root@ss-server ~]# cat test|awk -F"/" '{print $2}' 
b
2
[root@ss-server ~]# cat test|awk -F"/" '{print $1$2$3}'
abc
123
[root@ss-server ~]# cat test|awk -F"/" '{print $1"/"$2"/"$3}'
a/b/c
1/2/3
  
3)sort、uniq、wc、split、tee命令
===========================================
sort 排序命令
uniq 去重命令
  
sort -n   默認以數字去排序(默認字母和特殊符號為0,所以會排在最前面)
sort -r   反序排序,即升序。默認是降序排序
sort -kn  以第n列排序(默認降序)
sort -kn -r 以第n列降序排序
  
sort|uniq      排序去重
sort|uniq -c   排序去重,並打印每個出現的次數。即重復次數
sort|uniq -d   打印出交集部分,即打印出那些重復、相同的字符
sort|uniq -u   打印出除了交集之外的部分。即打印出那些去掉重復字符之后的字符
  
sort|uniq|sort -k3      #排序去重,並按第3列排序(降序)
sort|uniq|sort -k3 -rn  #排序去重,並按第3列排序(降序)
sort|uniq|sort -k3 -r   #排序去重,並按第3列排序(升序)
  
[root@ss-server ~]# cat test
11
ad
0
34
3
21
4
[root@ss-server ~]# cat test|sort -n
0
ad
3
4
11
21
34
[root@ss-server ~]# cat test|sort -rn
34
21
11
4
3
ad
0
  
[root@ss-server ~]# cat haha
aa 11 hj
2b 7 ok
23 100 jo
op 32 fg
[root@ss-server ~]# cat haha|sort -k2
23 100 jo
aa 11 hj
op 32 fg
2b 7 ok
[root@ss-server ~]# cat haha|sort -k2 -r
2b 7 ok
op 32 fg
aa 11 hj
23 100 jo
[root@ss-server ~]# cat haha|sort -k2 -rn
23 100 jo
op 32 fg
aa 11 hj
2b 7 ok
 
[root@ss-server ~]# cat test
wang bo
wang bo
kevin ai
han hu
han hu
wang bo
xiao ru
han hu
[root@ss-server ~]# cat test|sort|uniq
han hu
kevin ai
wang bo
xiao ru
[root@ss-server ~]# cat test|sort|uniq -d
han hu
wang bo
[root@ss-server ~]# cat test|sort|uniq -u
kevin ai
xiao ru
 
----------------------------------
uniq與sort -u 兩種"去重"的區別????
 
uniq  針對的重復是連續出現的相同記錄!
sort -u 針對的所有出現的相同記錄,包括連續出現和非連續出現的相同記錄!

sort -u  相當於 sour|uniq
 
[root@ss-server ~]# cat hehe
wang
wang
wang
bobo
wang
[root@ss-server ~]# cat hehe|uniq
wang
bobo
wang
[root@ss-server ~]# uniq hehe
wang
bobo
wang
[root@ss-server ~]# cat hehe|sort -u
bobo
wang
[root@ss-server ~]# sort -u hehe
bobo
wang
[root@ss-server ~]# cat hehe|sort|uniq
bobo
wang
  
===========================================
wc  統計
wc file   默認統計file文件的行數、單詞數,以及該文件的字節數。即默認加了-l、-w、-c
  
-l  統計行數
-c  統計字節數
-w  統計字符串,即統計單詞數 (默認以空白格或,為分隔符)
-m  統計字符數 (隱藏的換行符也算,用cat -A 查看隱藏符號)
-L  顯示最長行的長度
  
[root@ss-server ~]# cat test
wang bo
zhang heng yuan
xiao ru
zhu ge shen hou
[root@ss-server ~]# wc test
 4 11 49 test
[root@ss-server ~]# cat test|wc -l -w -c
      4      11      49
  
[root@ss-server ~]# cat test|wc -l     
4
[root@ss-server ~]# cat test|wc -w
11
[root@ss-server ~]# cat test|wc -c
49
[root@ss-server ~]# cat test|wc -m
49
  
[root@ss-server ~]# cat test|wc -L
16
  
[root@ss-server ~]# cat -A test
wang bo$                 
zhang heng yuan $        
xiao ru$                 
zhu ge shen hou$        
  
[root@ss-server ~]# cat test.sh
#!/bin/bash
  
file=$(cat /root/test)
while read line
do
    echo "${line} 這一行的字節數為: $(echo ${line}|wc -c)"
done <<EOF
${file}
EOF
[root@ss-server ~]# sh test.sh
wang bo 這一行的字節數為: 8
zhang heng yuan 這一行的字節數為: 16
xiao ru 這一行的字節數為: 8
zhu ge shen hou 這一行的字節數為: 16
  
===========================================
tee   和輸出重定>向有點像,但是把重定向的內容打印到屏幕上, 即打印到終端屏幕上
-a    追加,和>>相似
  
[root@ss-server ~]# cat /etc/passwd|head -2
root:x:0:0:root:/root:/bin/bash
bin:x:1:1:bin:/bin:/sbin/nologin
[root@ss-server ~]# cat /etc/passwd|head -2|tee
root:x:0:0:root:/root:/bin/bash
bin:x:1:1:bin:/bin:/sbin/nologin
[root@ss-server ~]# cat /etc/passwd|head -2|tee > aa.txt
[root@ss-server ~]# cat aa.txt
root:x:0:0:root:/root:/bin/bash
bin:x:1:1:bin:/bin:/sbin/nologin
[root@ss-server ~]# cat /etc/passwd|head -2|tee >> aa.txt
[root@ss-server ~]# cat aa.txt
root:x:0:0:root:/root:/bin/bash
bin:x:1:1:bin:/bin:/sbin/nologin
root:x:0:0:root:/root:/bin/bash
bin:x:1:1:bin:/bin:/sbin/nologin
  
===========================================
split  用於將一個文件分割成數個。該指令將大文件分割成較小的文件,在默認情況下將按照每1000行切割成一個小文件。
  
split -b 100M filename  以文件大小切割 (可以指定文件前綴,默認是x開頭)
split -l 1000 filename  以行數切割,相當於"split -1000" filename
  
[root@ss-server ~]# du -sh aa
710M    aa
[root@ss-server ~]# split -b 100M aa     #指定按照每個小文件100M的大小來分割aa文件
[root@ss-server ~]# du -sh *
710M    aa
100M    xaa
100M    xab
100M    xac
100M    xad
100M    xae
100M    xaf
100M    xag
9.7M    xah
  
[root@ss-server mnt]# cat bb
root:x:0:0:root:/root:/bin/bash
bin:x:1:1:bin:/bin:/sbin/nologin
daemon:x:2:2:daemon:/sbin:/sbin/nologin
adm:x:3:4:adm:/var/adm:/sbin/nologin
lp:x:4:7:lp:/var/spool/lpd:/sbin/nologin
sync:x:5:0:sync:/sbin:/bin/sync
shutdown:x:6:0:shutdown:/sbin:/sbin/shutdown
halt:x:7:0:halt:/sbin:/sbin/halt
  
[root@ss-server mnt]# cat bb|wc -l  
8
[root@ss-server mnt]# split -2 bb    #相當於"split -l 2 bb" 表示指定按照每個小文件2行來分割bb文件
[root@ss-server mnt]# ls
bb  xaa  xab  xac  xad
[root@ss-server mnt]# cat xaa
root:x:0:0:root:/root:/bin/bash
bin:x:1:1:bin:/bin:/sbin/nologin
[root@ss-server mnt]# cat xab
daemon:x:2:2:daemon:/sbin:/sbin/nologin
adm:x:3:4:adm:/var/adm:/sbin/nologin
[root@ss-server mnt]# cat xac
lp:x:4:7:lp:/var/spool/lpd:/sbin/nologin
sync:x:5:0:sync:/sbin:/bin/sync
[root@ss-server mnt]# cat xad
shutdown:x:6:0:shutdown:/sbin:/sbin/shutdown
halt:x:7:0:halt:/sbin:/sbin/halt
  
  
[root@ss-server mnt]# rm -rf x*
[root@ss-server mnt]# ls
bb
[root@ss-server mnt]# du -sh -b bb     #查看bb文件的字節數
293     bb
[root@ss-server mnt]# split -b 100 bb  #按照每個小文件100個字節來分割bb文件
[root@ss-server mnt]# ls
bb  xaa  xab  xac
[root@ss-server mnt]# du -sh -b xaa
100     xaa
[root@ss-server mnt]# du -sh -b xab
100     xab
[root@ss-server mnt]# du -sh -b xac
93      xac
  
[root@ss-server mnt]# du -sh bb
32K     bb
[root@ss-server mnt]# split -b 8K bb
[root@ss-server mnt]# ls
bb  xaa  xab  xac  xad
[root@ss-server mnt]# du -sh xaa
8.0K    xaa
[root@ss-server mnt]# du -sh xab
8.0K    xab
[root@ss-server mnt]# du -sh xac
8.0K    xac
[root@ss-server mnt]# du -sh xad
8.0K    xad
  
小結:
spilt -n 相當於 split -l n   表示按照多少行數來分割成小文件
split -b 按照大小分割成小文件,默認是K,至少是4.0K(即新建文件默認大小)
  
split -b 10K   按照每10K一個文件分割成小文件
split -b 10M   按照每10M一個文件分割成小文件
split -b 10G   按照每10G一個文件分割成小文件
split -b 100   按照每100 byte字節一個文件分割成小文件

18)按照文件中的單詞字母去重排序

使用shell腳本實現單詞及字母去重排序

需求
1. 按單詞出現頻率降序排序!
2. 按字母出現頻率降序排序!

[root@ss-server ~]# cat test
wang shi hu shi kui shi juan wang fang fang anhui of huoqiu liuan the squid project provides a number of.
resources to assist users Newsgd.com is the premier New online source of Guangdong news and information, 
fully displaying shi Guangdong through is channels including Guangdong New

[root@ss-server ~]# cat test.sh 
#!/bin/bash

file=$(cat /root/test)

echo "按單詞出現頻率降序排序!"
for i in ${file}
 do 
 echo ${i} 
done|\
 sort |uniq -c|sort -nk1 -r
echo "按字母出現頻率降序排序!"
echo ${file} |grep -o "[a-z]" |sort|uniq -c |sort -nk1 -r

[root@ss-server ~]# sh test.sh
按單詞出現頻率降序排序!
      4 shi
      3 Guangdong
      2 wang
      2 the
      2 of
      2 New
      2 is
      2 fang
      1 users
      1 to
      1 through
      1 squid
      1 source
      1 resources
      1 provides
      1 project
      1 premier
      1 online
      1 of.
      1 number
      1 Newsgd.com
      1 news
      1 liuan
      1 kui
      1 juan
      1 information,
      1 including
      1 huoqiu
      1 hu
      1 fully
      1 displaying
      1 channels
      1 assist
      1 anhui
      1 and
      1 a
按字母出現頻率降序排序!
     25 n
     21 i
     20 s
     18 u
     17 o
     17 e
     16 a
     14 g
     12 h
     11 r
      9 d
      7 t
      7 l
      7 f
      6 w
      6 c
      4 p
      4 m
      2 y
      2 q
      2 j
      1 v
      1 k
      1 b

如果在命令行直接操作,如下:
[root@ss-server ~]# echo "按單詞出現頻率降序排序!" && for i in $(cat /root/test);do echo ${i};done|sort |uniq -c|sort -nk1 -r 
按單詞出現頻率降序排序!
      4 shi
      3 Guangdong
      2 wang
      2 the
      2 of
      2 New
      2 is
      2 fang
      1 users
      1 to
      1 through
      1 squid
      1 source
      1 resources
      1 provides
      1 project
      1 premier
      1 online
      1 of.
      1 number
      1 Newsgd.com
      1 news
      1 liuan
      1 kui
      1 juan
      1 information,
      1 including
      1 huoqiu
      1 hu
      1 fully
      1 displaying
      1 channels
      1 assist
      1 anhui
      1 and
      1 a

[root@ss-server ~]# echo "按字母出現頻率降序排序!" && echo $(cat /root/test) |grep -o "[a-z]" |sort|uniq -c |sort -nk1 -r 
按字母出現頻率降序排序!
     25 n
     21 i
     20 s
     18 u
     17 o
     17 e
     16 a
     14 g
     12 h
     11 r
      9 d
      7 t
      7 l
      7 f
      6 w
      6 c
      4 p
      4 m
      2 y
      2 q
      2 j
      1 v
      1 k
      1 b

grep -o  代表的是只輸出匹配的選項

19)join 命令用法

join命令用於將兩個文件中,指定欄位內容相同的行連接起來。 
注意:僅僅只能連接兩個文件!!超過兩個文件就無效!

語法格式:
join [-i][-a<1或2>][-e<字符串>][-o<格式>] [-t<字符>][-v<1或2>][-1<欄位>][-2<欄位>][--help] [--version][文件1][文件2] 

補充說明:找出兩個文件中,指定欄位內容相同的行,並加以合並,再輸出到標准輸出設備。 

參數: 
-a<1或2>   除了顯示原來的輸出內容之外,還顯示指令文件中沒有相同欄位的行。 
-e<字符串> 若[文件1]與[文件2]中找不到指定的欄位,則在輸出中填入選項中的字符串。 
-i         比較欄位內容時,忽略大小寫的差異。 
-o<格式>   按照指定的格式來顯示結果。 
-t<字符>   使用欄位的分隔字符。 
-v<1或2>   跟-a相同,但是只顯示文件中沒有相同欄位的行。 
-1<欄位>   連接[文件1]指定的欄位。 
-2<欄位>   連接[文件2]指定的欄位。 

示例:
1)內連接(忽略不匹配的行)
=============================================================================
不指定任何參數的情況下使用join命令,就相當於數據庫中的內連接,關鍵字不匹配的行不會輸出。
不指定參數,即通過兩個文件中相同元素進行連接,如下兩個文件相同的就是第一列的1,2,3,4,5,6,其他多余的都刪除
[root@ss-server ~]# cat test1
1 wang
2 han
3 niu
4 xiao
5 ping
6 hang
[root@ss-server ~]# cat test2
1 張三
2 徐思
3 劉磊
4 楊洋
5 李玉
6 王一
7 張麻
8 赫赫
[root@ss-server ~]# join test1 test2
1 wang 張三
2 han 徐思
3 niu 劉磊
4 xiao 楊洋
5 ping 李玉
6 hang 王一

[root@ss-server ~]# cat test3
1 w2
3 r4
4 y7
5 ij
[root@ss-server ~]# join test3 test2
1 w2 張三
3 r4 劉磊
4 y7 楊洋
5 ij 李玉

2)左連接(又稱左外連接,顯示左邊所有記錄)。 也就是以左邊件為主!!!
=============================================================================
顯示左邊文件中的所有記錄,右邊文件中沒有匹配的顯示空白。
[root@ss-server ~]# join -a1 test1 test2
1 wang 張三
2 han 徐思
3 niu 劉磊
4 xiao 楊洋
5 ping 李玉
6 hang 王一
[root@ss-server ~]# join -a1 test3 test2
1 w2 張三
3 r4 劉磊
4 y7 楊洋
5 ij 李玉

3)右連接(又稱右外連接,顯示右邊所有記錄)。也就是以右邊文件為主!!!
=============================================================================
顯示右邊文件中的所有記錄,左邊文件中沒有匹配的顯示空白。
[root@ss-server ~]# join -a2 test1 test2 
1 wang 張三
2 han 徐思
3 niu 劉磊
4 xiao 楊洋
5 ping 李玉
6 hang 王一
7 張麻
8 赫赫
[root@ss-server ~]# join -a2 test3 test2
1 w2 張三
2 徐思
3 r4 劉磊
4 y7 楊洋
5 ij 李玉
6 王一
7 張麻
8 赫赫

4)全連接(又稱全外連接,顯示左邊和右邊所有記錄)
=============================================================================
[root@ss-server ~]# join -a1 -a2 test1 test2        
1 wang 張三
2 han 徐思
3 niu 劉磊
4 xiao 楊洋
5 ping 李玉
6 hang 王一
7 張麻
8 赫赫
[root@ss-server ~]# join -a1 -a2 test3 test2
1 w2 張三
2 徐思
3 r4 劉磊
4 y7 楊洋
5 ij 李玉
6 王一
7 張麻
8 赫赫

5)指定輸出字段
=============================================================================
比如參數 -o 1.1 表示只輸出第一個文件的第一個字段。
[root@ss-server ~]# join -o 1.1 test1 test2
1
2
3
4
5
6
[root@ss-server ~]# join -o 1.1 test3 test2
1
3
4
5

-o 1.2 只輸出第一個文件的第二個字段
[root@ss-server ~]# join -o 1.2 test1 test2
wang
han
niu
xiao
ping
hang
[root@ss-server ~]# join -o 1.2 test3 test2
w2
r4
y7
ij

[root@ss-server ~]# join -o 1.2 2.2 test1 test2
wang 張三
han 徐思
niu 劉磊
xiao 楊洋
ping 李玉
hang 王一

[root@ss-server ~]# join -o 2.2 1.2 test1 test2 
張三 wang
徐思 han
劉磊 niu
楊洋 xiao
李玉 ping
王一 hang

[root@ss-server ~]# join -o 1.1 2.2 test1 test2    
1 張三
2 徐思
3 劉磊
4 楊洋
5 李玉
6 王一

6)指定分隔符
=============================================================================
join -t "分隔符"   注意:這個分隔符必須是兩個文件中都存在的!!
[root@ss-server ~]# join -t ':' test1 test2
[root@ss-server ~]# join -t ' ' test1 test2
1 wang 張三
2 han 徐思
3 niu 劉磊
4 xiao 楊洋
5 ping 李玉
6 hang 王一

[root@ss-server ~]# cat haha1
a:11:aa:111:aaa
b:22:aff:5t
c:33:hji:o0p:p8u
[root@ss-server ~]# cat haha2
a:nj:5:c
b:ok:90
c:yh8
d:p:0:9
[root@ss-server ~]# join -t ":" haha1 haha2
a:11:aa:111:aaa:nj:5:c
b:22:aff:5t:ok:90
c:33:hji:o0p:p8u:yh8

20)paste 命令用法

paste 命令用於合並文件的列,會把每個文件以列對列的方式,一列列地加以合並。
語法
paste [-s][-d <間隔字符>][--help][--version][文件...]
 
參數:
-d<間隔字符>或--delimiters=<間隔字符> : 用指定的間隔字符取代跳格字符。
-s或--serial:  串列進行而非平行處理。
--help:  在線幫助。
--version:  顯示幫助信息。
[文件…]:  指定操作的文件路徑
 
比如:
paste file testfile testfile1            #合並指定文件的內容
paste file testfile testfile1  -d ":"    #合並指定文件的內容,並使用逗號隔開。-d后面的分隔符可以自行定義!
paste -s file1 file2     #將file1和file2文件的各自多行內容合並到各自的一行里面進行展示。使用-s參數可以將一個文件中的多行數據合並為一行進行顯示。

注意:
paste 可以針對多個文件進行合並!!也可以針對一個文件進行處理!
join  只能針對兩個文件進行連接!!!
 
示例如下:
[root@ss-server ~]# cat aa.txt
11
22
33
44
55
[root@ss-server ~]# cat bb.txt
aa
ab
ac
cc
cd
 
使用paste命令將文件進行合並
[root@ss-server ~]# paste aa.txt bb.txt
11 aa
22 ab
33 ac
44 cc
55 cd
 
合並后使用":"隔開
[root@ss-server ~]# paste -d":" aa.txt bb.txt
11:aa
22:ab
33:ac
44:cc
55:cd
 
合並后使用"-"隔開
[root@ss-server ~]# paste -d"-" aa.txt bb.txt
11-aa
22-ab
33-ac
44-cc
55-cd
 
paste -s 可以將一個文件中的多行內容合並為一行。例如:
[root@ss-server ~]# cat file
111
222
333
444
555
[root@ss-server ~]# cat file |paste -s
111     222     333     444     555
[root@ss-server ~]# cat file |paste -s -d":"
111:222:333:444:555
[root@ss-server ~]# cat file |paste -s -d ":"
111:222:333:444:555
[root@ss-server ~]# cat file |paste -s -d "-" 
111-222-333-444-555
[root@ss-server ~]# cat file |paste -s -d "---"
111-222-333-444-555
[root@ss-server ~]# cat file |paste -s -d "," 
111,222,333,444,555
 
看下面一個小需求:
有一個log.txt文件,第二列是ip,現在需要將log.txt文件中的ip列取出來放在一行,並用逗號隔開。
 
第一種做法:awk + paste
[root@ss-server ~]# cat log.txt
17:05 172.16.60.34 sadfjsafjsdf
17:14 172.16.60.35 asdfasudfasjfasjfklsafsaf
17:45 172.16.60.38 dsafkjdsajflsajfadf
 
[root@ss-server ~]# cat log.txt
17:05 172.16.60.34 sadfjsafjsdf
17:14 172.16.60.35 asdfasudfasjfasjfklsafsaf
17:45 172.16.60.38 dsafkjdsajflsajfadf
 
[root@ss-server ~]# cat log.txt |awk '{print $2}'
172.16.60.34
172.16.60.35
172.16.60.38
 
[root@ss-server ~]# cat log.txt |awk '{print $2}'|paste -s
172.16.60.34    172.16.60.35    172.16.60.38
 
[root@ss-server ~]# cat log.txt |awk '{print $2}'|paste -s -d","
172.16.60.34,172.16.60.35,172.16.60.38
 
另一種做法是:awk + xargs + sed
[root@ss-server ~]# cat log.txt |awk '{print $2}'
172.16.60.34
172.16.60.35
172.16.60.38
[root@ss-server ~]# cat log.txt |awk '{print $2}'|xargs
172.16.60.34 172.16.60.35 172.16.60.38
[root@ss-server ~]# cat log.txt |awk '{print $2}'|xargs|sed 's/ /,/g'
172.16.60.34,172.16.60.35,172.16.60.38

21)su命令用法

su命令用於變更為其他使用者的身份,除 root 外,需要鍵入該使用者的密碼。(即root用戶使用su切換到其他用戶不需要輸入密碼,其他用戶之間使用su切換均需要輸入密碼)。

===================================
1)su 和 su - 的區別
"su"命令僅僅是切換了用戶身份,但用戶的shell環境變量沒有切換!即su切換用戶身份后,環境變量還是切換前的用戶的環境變量。
"su -"命令不僅切換了用戶身份,用戶的shell環境變量也一起切換!即su切換用戶身份后,環境變量是切換后的用戶的環境變量。

示例:
[root@ss-server ~]# su kevin
[kevin@ss-server root]$ whoami         #用戶身份切換了
kevin
[kevin@ss-server root]$ pwd            #用戶的環境變量沒有切換
/root

[root@ss-server ~]# su - kevin                  
Last login: Fri Dec  6 10:29:59 CST 2019 on pts/16
[kevin@ss-server ~]$ whoami            #用戶身份切換了
kevin
[kevin@ss-server ~]$ pwd               #用戶的環境變量也切換了
/home/kevin

2)-c 參數,表示切換到某用戶后執行一些命令,注意,這個命令是在切換用戶后的狀態下執行的,並且執行命令后再變回原來的用戶。
示例:
[root@ss-server ~]# su - kevin -c "pwd"     #即是在切換到kevin用戶狀態下執行的"pwd"命令。這里使用"su -",即也切換了shell環境變量
/home/kevin 
[root@ss-server ~]# su kevin -c "pwd"       #即是在切換到kevin用戶狀態下執行的"pwd"命令。這里沒有切換shell環境變量
/root

su和sudo權限使用可參考:
https://www.cnblogs.com/kevingrace/p/5823003.html
https://www.cnblogs.com/kevingrace/p/6130008.html

22)test 命令用法

test 命令最短的定義可能是評估一個表達式;如果條件為真,則返回一個 0 值。如果表達式不為真,則返回一個大於 0 的值(一般為1),也可以將其稱為假值。
  
需要注意:
1. 在shell中,test命令 和 [] 是同一個命令的不同名稱。也就是說,test xxxx 等同於 [ xxxx ] 的形式!!,注意[]里面的內容兩邊要有空格!!
2. 檢查最后所執行命令的狀態的最簡便方法是使用 $? 值。
3. test功能是檢查文件和比較值。
  
test 和 [ ] 的語法如下:
test expression
[ expression ]
  
其中,expression為test命令構造的表達式。這里expression是test命令可以理解的任何有效表達式,該簡化格式將是我們可能會踫見的最常用格式返回值:
test命令或者返回0(真) 或者返回1(假).
  
因為它們彼此互為別名,所以使用 test 或 [ ] 均需要一個表達式。表達式一般是文本、數字或文件和目錄屬性的比較,並且可以包含變量、常量和運算符。
運算符可以是字符串運算符、整數運算符、文件運算符或布爾運算符 — 我們將在以下各部分依次介紹每一種運算符。
  
test可理解的表達式類型分為四類:
1. 表達式判斷
2. 字符串比較
3. 數字比較
4. 文件比較
  
1)判斷表達式
--------------------------------------------------------
if test 表達式               #表達式為真
if test ! 表達式             #表達式為假
test 表達式1 –a 表達式 2     #兩個表達式都為真
test 表達式1 –o 表達式2      #兩個表達式有一個為真
  
2)判斷字符串
--------------------------------------------------------
test –n 字符串             #字符串的長度非零。即非空字符串。
test –z 字符串             #字符串的長度為零。即空字符串。
test 字符串1=字符串 2      #字符串相等
test 字符串1 !=字符串2     #字符串不等
  
3)判斷整數
--------------------------------------------------------
test 整數1 –eq 整數2       #整數相等
test 整數1 –ne 整數 2      #整數1不等於整數2
test 整數 1 –ge 整數2      #整數1大於等於整數2
test 整數1 –gt 整數 2      #整數1大於整數2
test 整數1 –le 整數 2      #整數1小於等於整數2
test 整數1 –lt 整數 2      #整數1小於整數2
  
4)判斷文件
--------------------------------------------------------
test File1 –nt  File2     #文件1比文件2新(new)
test File1 –ot  File2     #文件1比文件2舊(old)
test File1 –ef  File2     #判斷兩個文件是否與同一個設備相連,是否擁有相同的inode號
test –d File              #文件存在並且是目錄
test –e File              #文件存在
test –f File              #文件存在並且是正規文件
test –h File              #文件存在並且是一個符號鏈接(同-L)。即存在且是軟鏈接文件
test –L File              #文件存在並且是一個符號鏈接(同-h)。即存在且是軟鏈接文件
test –k File              #文件存在並且設置了sticky位(即設置了t權限)
test –r File              #文件存在並且可讀
test –w File              #文件存在並且可寫
test –x File              #文件存在並且可執行
  
示例如下:
1)判斷表達式。下面hang.txt文件是不存在的。
[root@ss-server ~]# cat test.sh
#!/bin/bash
if test `cat /hang.txt`;then
   echo "haha.txt is exist"
else
   echo "haha.txt is not exist"
fi
 
[root@ss-server ~]# sh test.sh
cat: /hang.txt: No such file or directory
haha.txt is not exist
 
[root@ss-server ~]# cat test.sh
#!/bin/bash
if test !`cat /hang.txt >/dev/null 2>&1`;then
   echo "haha.txt is exist"
else
   echo "haha.txt is not exist"
fi
[root@ss-server ~]# sh test.sh
haha.txt is exist

使用[]的形式操作上面的腳本
[root@ss-server ~]# cat test.sh
#!/bin/bash
if [ `cat /hang.txt` ];then
   echo "haha.txt is exist"
else
   echo "haha.txt is not exist"
fi

[root@ss-server ~]# sh test.sh
cat: /hang.txt: No such file or directory
haha.txt is not exist

[root@ss-server ~]# cat test.sh
#!/bin/bash
if [ !`cat /hang.txt >/dev/null 2>&1` ];then
   echo "haha.txt is exist"
else
   echo "haha.txt is not exist"
fi

[root@ss-server ~]# sh test.sh
haha.txt is exist
 
2)判斷第一個參數是否為空字符串,不空則打印該字符串,為空則打印"空字符串"
[root@ss-server ~]# cat test.sh
#!/bin/bash
if test -n "$1";then
   echo "$1"
else
   echo "空字符串"
fi
  
執行結果:
[root@ss-server ~]# sh test.sh beijing
beijing
[root@ss-server ~]# sh test.sh
空字符串
  
由於test xxxx 等同於 [ xxxx ] 的形式,所以上面還可以改成:
[root@ss-server ~]# cat test.sh
#!/bin/bash
if [ -n "$1" ];then
   echo "$1"
else
   echo "空字符串"
fi
  
執行結果:
[root@ss-server ~]# sh test.sh shanghai
shanghai
[root@ss-server ~]# sh test.sh
空字符串
  
3)文件判斷
[root@ss-server ~]# test -h heihei
[root@ss-server ~]# echo $?
0
[root@ss-server ~]# [ -h heihei ]
[root@ss-server ~]# echo $?    
0
  
[root@ss-server ~]# test -x haha
[root@ss-server ~]# echo $?   
1
[root@ss-server ~]# [ -x haha ]   
[root@ss-server ~]# echo $?  
1
  
[root@ss-server ~]# ll haha
-rw-r--r-- 1 root root 32 Dec 18 13:06 haha
[root@ss-server ~]# ll hehe
-rw-r--r-- 1 root root 0 Dec 19 17:19 hehe
[root@ss-server ~]# test haha -nt hehe
[root@ss-server ~]# echo $?
1
[root@ss-server ~]# [ haha -nt hehe ]
[root@ss-server ~]# echo $?         
1
  
[root@ss-server ~]# test haha -ot hehe
[root@ss-server ~]# echo $?
0
[root@ss-server ~]# [ haha -ot hehe ]
[root@ss-server ~]# echo $?        
0

23)printf 命令用法

關於printf的含義,需要注意下面四點:
1. printf 命令用於格式化輸出,它是echo命令的增強版。它是C語言printf()庫函數的一個有限的變形,並且在語法上有些不同。
2. printf 由 POSIX 標准所定義,因此使用 printf 的腳本比使用 echo 移植性好。
3. printf 使用引用文本或空格分隔的參數,外面可以在 printf 中使用格式化字符串,還可以制定字符串的寬度、左右對齊方式等。
4. 默認 printf 不會像 echo 自動添加換行符,但是可以手動添加 \n。

printf 命令的語法:
printf format-string [arguments...]

參數說明:
format-string: 為格式控制字符串
arguments: 為參數列表。

需要注意:
1. printf 不像 echo 那樣會自動換行,必須顯式添加換行符(\n)。
2. printf 命令不用加括號
3. format-string 可以沒有引號,但最好加上,單引號雙引號均可。
4. 參數多於格式控制符(%)時,format-string 可以重用,可以將所有參數都轉換。
5. arguments 使用空格分隔,不用逗號。

示例如下:
echo默認自動換行。加上-n參數,則不換行
printf默認不會自動換行
[root@ss-server ~]# echo "Hello, Shell"
Hello, Shell
[root@ss-server ~]# echo -n "Hello, Shell"
Hello, Shell[root@ss-server ~]# 

[root@ss-server ~]# printf "Hello, Shell"
Hello, Shell[root@ss-server ~]# 

[root@ss-server ~]# printf "Hello, Shell\n"
Hello, Shell
[root@ss-server ~]# 

[root@ss-server ~]# printf "%-10s %-8s %-4s\n" 姓名 性別 體重kg  
姓名     性別   體重kg
[root@ss-server ~]# printf "%-10s %-8s %-4.2f\n" 小明 男 65.1234
小明     男      65.12
[root@ss-server ~]# printf "%-10s %-8s %-4.2f\n" 小洋 男 72.6589 
小洋     男      72.66
[root@ss-server ~]# printf "%-10s %-8s %-4.2f\n" 小梅 女 48.7167 
小梅     女      48.72

需要注意:
%d指的是針對數字的格式化
%s指的是字符串的格式化
%-10s 指一個寬度為10個字符(-表示左對齊,沒有則表示右對齊),任何字符都會被顯示在10個字符寬的字符內,如果不足則自動以空格填充,超過也會將內容全部顯示出來。
%-4.2f 指格式化為小數,其中.2指保留2位小數。

示例如下:
format-string為雙引號(換行)
[root@ss-server ~]# printf "%d %s\n" 1 "abc"
1 abc

單引號與雙引號效果一樣(換行)
[root@ss-server ~]# printf '%d %s\n' 1 "abc"
1 abc

沒有引號也可以輸出(不換行)
[root@ss-server ~]# printf %s abcdef
abcdef[root@ss-server ~]# 

格式只指定了一個參數,但多出的參數仍然會按照該格式輸出,format-string 被重用
[root@ss-server ~]# printf %s abc def
abcdef[root@ss-server ~]# 

[root@ss-server ~]# printf "%s\n" abc def
abc
def

[root@ss-server ~]# printf "%s %s %s\n" a b c d e f g h i j
a b c
d e f
g h i
j 

如果沒有 arguments,那么 %s 用NULL代替,%d 用 0 代替
[root@ss-server ~]# printf "%s and %d \n"
 and 0 

如果以 %d 的格式來顯示字符串,那么會有警告,提示無效的數字,此時默認置為 0
[root@ss-server ~]# printf "The first program always prints'%s,%d\n'" Hello Shell
-bash: printf: Shell: invalid number
The first program always prints'Hello,0


printf的轉義序列
=================================================================================
\a     警告字符,通常為ASCII的BEL字符
\b     后退
\c     抑制(不顯示)輸出結果中任何結尾的換行字符(只在%b格式指示符控制下的參數字符串中有效),而且,任何留在參數里的字符、任何接下來的參數以及任何留在格式字符串中的字符,都被忽略
\f     換頁(formfeed)
\n     換行
\r     回車(Carriage return)
\t     水平制表符
\v     垂直制表符
\\     一個字面上的反斜杠字符
\ddd  表示1到3位數八進制值的字符。僅在格式字符串中有效
\0ddd 表示1到3位的八進制值字符

示例如下:
[root@ss-server ~]# printf "welcome, 哈哈,北京:<%s>\n" "A\nB"
welcome, 哈哈,北京:<A\nB>

[root@ss-server ~]# printf "welcome, 哈哈,北京::<%b>\n" "A\nB"
welcome, 哈哈,北京::<A
B>

[root@ss-server ~]# printf "www.kevin.com \a"      
www.kevin.com [root@ss-server ~]# 

24)zcatzgrep命令用法
服務器端常有很多壓縮過的日志文件,當需要查找日志中某些特定信息的時候,為了避免解壓文件,可以使用zgrep,zcat等命令查找、查看壓縮文件中的信息。

1)gzip打包壓縮文件
[root@localhost ~]# cat test
name:wangbo
age:29
address:beijing
school:lanzhoucaida
 
[root@localhost ~]# gzip test
[root@localhost ~]# ls test.gz
test.gz
 
------------------------------------------------------------------
注意:
gzip filename         # 將filename文件打包壓縮成filename.gz
gzip -d filename.gz   # 解壓filename.gz文件
gzip -r dirname       # 將dirname目錄下的文件打包壓縮為.gz格式的文件
 
gzip打包壓縮一個文件,打包后,原文件就變成壓縮文件,原文件不存在了!
-----------------------------------------------------------------
 
現在想在不解壓的情況下查看或者搜索test.tar.gz文件中的數據
 
使用zcat命令
[root@localhost ~]# zcat test.gz |grep "age"
age:29
 
使用gzip命令
[root@localhost ~]# zgrep "age" test.gz
age:29
[root@localhost ~]# zgrep "age" test.gz |more
age:29
 
2)zip打包壓縮文件
以上針對的是gzip壓縮的gz格式的壓縮文件,如果換成zip格式的話,效果如何?繼續看下面:
[root@localhost ~]# gzip -d test.gz
[root@localhost ~]# zip test.zip test
  adding: test (stored 0%)
[root@localhost ~]# ls test*
test  test.zip
[root@localhost ~]# zcat test.zip
name:wangbo
age:29
address:beijing
school:lanzhoucaida
[root@localhost ~]# zgrep "address" test.zip
address:beijing
[root@localhost ~]# zgrep "address" test.zip |more
address:beijing
 
------------------------------------------------------------------
注意:
zip filename.zip filename      #將filename文件打包壓縮成filename.zip
unzip filename.zip             #解壓filename.zip文件
unzip -r xxx.zip ./*           #當前目錄的內容打包壓縮為為xxx.zip文件
unzip -r dirname.zip dirname   #當dirname目錄打包壓縮為dirname.zip文件
 
zip打包壓縮后,原文件還存在,原文件和打包文件同時存在
------------------------------------------------------------------
 
3)tar打包壓縮文件
如果換成tar打包壓縮文件,又該如何?繼續往下看
[root@localhost ~]# ls test*
test  test.zip
[root@localhost ~]# rm -f test.zip
[root@localhost ~]# tar -zvcf test.tar.gz test
test
[root@localhost ~]# ls test*
test  test.tar.gz
 
[root@localhost ~]# zgrep "age" test.tar.gz
Binary file (standard input) matches
 
上面報錯是因為grep認為test.tar.gz是個二進制文件,無法grep查找。
解決辦法:加上-a參數即可
[root@localhost ~]# zgrep -a "age" test.tar.gz
age:29
[root@localhost ~]# zgrep -a "age" test.tar.gz |more
age:29
 
[root@localhost ~]# zcat test.tar.gz
test0000644000000000000000000000006713576332517010475 0ustar  rootrootname:wangbo
age:29
address:beijing
school:lanzhoucaida
 
[root@localhost ~]# zgrep -a "name" test.tar.gz |more
test
 
可以看出,對於tar格式的壓縮文件,zcat或zgrep查看或搜索,會在第一行多處一個字符串,
這個字符串是tar打包壓縮后出現的,並把文件正文中的第一行內容和這個字符串放在一行!
這樣當zgrep搜索內容的字段在原文第一行,則就搜索不出來了!

------------------------------------------------------------------
如果一個目錄被打成tar包了,怎么查看這個tar包里有哪些文件?
如下,使用"tar -tvf"查看即可
[root@localhost ~]# tar -tvf test1.tar.gz
drwxr-xr-x root/root         0 2020-01-13 12:59 test1/
-rw-r--r-- root/root         7 2020-01-13 12:59 test1/b.txt
-rw-r--r-- root/root         7 2020-01-13 12:59 test1/a.txt
drwxr-xr-x root/root         0 2020-01-13 13:00 test1/haha/
-rw-r--r-- root/root         9 2020-01-13 13:00 test1/haha/test.txt

25); || && 區別

三種符號均用於多用命令之間的銜接。區別如下:
1); 符號,表示不論前面的命令執行結果是true還是false,后面的命令照樣執行。
2)|| 符號,表示只有前面的命令執行結果為false時,后面的命令才會繼續執行;如果前面命令執行結果為true,則后面的命令就不會繼續執行了。
3)&& 符號,表示只有前面的命令執行結果為true時,后面的命令才會繼續執行;如果前面命令執行結果為false,則后面的命令就不會繼續執行了。

示例如下:
[root@kevin_test ~]# cat haha.txt
cat: haha.txt: No such file or directory
[root@kevin_test ~]# hostname
kevin_test

[root@kevin_test ~]# cat haha.txt ; hostname
cat: haha.txt: No such file or directory
kevin_test
[root@kevin_test ~]# hostname ; cat haha.txt
kevin_test
cat: haha.txt: No such file or directory

[root@kevin_test ~]# cat haha.txt || hostname
cat: haha.txt: No such file or directory
kevin_test
[root@kevin_test ~]# hostname || cat haha.txt      
kevin_test

[root@kevin_test ~]# cat haha.txt && hostname
cat: haha.txt: No such file or directory
[root@kevin_test ~]# hostname && cat haha.txt   
kevin_test
cat: haha.txt: No such file or directory

26)如何比較兩個目錄

在Linux系統里如何快速比較兩個目錄中的文件列表的差別,比如test1、test2兩個目錄,對兩個目錄中多出的文件、少掉的文件分別做處理。基於運維日常中常用的方法,總結如下:

首先比較下兩個目錄的結構:
[root@ss-server ~]# yum install -y tree
[root@ss-server opt]# tree test1 test2
test1
├── a.txt
├── b.txt
└── haha
    └── test.txt
test2
├── a.txt
├── b.txt
└── haha
    ├── bo.txt
    └── test.txt

2 directories, 7 files

一、命令行輸出的結果  #################################################################################################

方法1:使用diff命令  -----------------------------------------------------------------
[root@ss-server ~]# cd /opt/
[root@ss-server opt]# diff -r test1 test2
diff -r test1/a.txt test2/a.txt
1a2
>  hhhhh                             # 該行內容是test2/a.txt文件比test1/a.txt文件多出來的內容"
diff -r test1/b.txt test2/b.txt
1c1,2
< abcdfg                             # 該行是test1/b.txt文件內容
--- 
> 66666                              # 這兩行是test2/b.txt文件內容。這兩個文件內容各不相同
>  asdfsafd
Only in test2/haha: bo.txt           # test2/haha目錄相比如test1/haha目錄多處一個bo.txt文件。
                                     # 另外,test1/haha和test2/haha兩個目錄下的test.txt文件內容相同。diff命令只輸出不同的,相同的不輸出。

需要注意:diff命令會對兩個每個文件中的每一行都做比較,所以文件較多或者文件較大的時候會非常慢。請謹慎使用

方法2:使用diff結合tree  -----------------------------------------------------------------
[root@ss-server opt]# diff <(tree -Ci --noreport /opt/test1) <(tree -Ci --noreport /opt/test2)
1c1
< /opt/test1
---
> /opt/test2
4a5
> bo.txt                            #/opt/test2目錄比/opt/test1目錄多處一個bo.txt文件
                                    # 該方法比較的只是兩個目錄下多處的文件。

說明:
tree的-C選項是輸出顏色,如果只是看一下目錄的不同,可以使用該選項,但在結合其他命令使用的時候建議不要使用該選項,因為顏色也會轉換為對應的編碼而輸出;
-i是不縮進,建議不要省略-i,否則diff的結果很難看,也不好繼續后續的文件操作;
--noreport是不輸出報告結果,建議不要省略該選項。
該方法效率很高!!!

方法3:find結合diff  -----------------------------------------------------------------
[root@ss-server opt]# find /opt/test1 -printf "%P\n" | sort > file1_diff.txt
[root@ss-server opt]# find /opt/test2 -printf "%P\n" | sort | sort | diff file1_diff.txt -
4a5
> haha/bo.txt

說明:
< 代表的是第一個目錄/opt/test1中有,而第二個目錄/opt/test2中沒有的文件
> 則相反,代表的是第二個目錄/opt/test2中有而第一個目錄/opt/test1中沒有。

不要省略-printf "%P\n",此處的%P表示find的結果中去掉前綴路徑。
例如/opt/test2目錄下多出了haha/bo.txt文件,所以結果顯示的是haha/bo.txt,將前綴路徑/opt/test2去掉了!
這樣效率很高,輸出也簡潔!

如果不想使用-printf,那么先進入各目錄再find也是可以的。如下:
[root@ss-server opt]# (cd /opt/test1;find . | sort >/tmp/file1.txt)    
[root@ss-server opt]# (cd /opt/test2;find . | sort | diff /tmp/file1.txt -)
4a5
> ./haha/bo.txt

上面將命令放進括號中執行是為了在子shell中切換目錄,不用影響當前所在目錄。

方法4:使用rsync  ------------------------------------------------------------------------
[root@ss-server opt]# rsync -rvn --delete /opt/test1/ /opt/test2 | sed -n '2,/^$/{/^$/!p}'
a.txt
b.txt
deleting haha/bo.txt
haha/test.txt

其中deleting所在的行就是第二個目錄/opt/test2中多出的文件。
其他的都是兩個目錄中相同文件名的文件。

如果想區分出不同的是目錄還是文件。可以加上"-i"選項。
[root@ss-server opt]# rsync -rvn -i --delete /opt/test1/ /opt/test2 | sed -n '2,/^$/{/^$/!p}'
>f.sT...... a.txt
>f.sT...... b.txt
*deleting   haha/bo.txt
>f..T...... haha/test.txt

其中>f+++++++++中的f代表的是文件,d代表的目錄。

需要注意上面的rsync比較目錄的命令中有幾點要說明:
1)一定不能缺少-n選項,它表示:嘗試着進行rsync同步,但不會真的同步。
2)第一個目錄(/opt/test1/)后一定不能缺少斜線,否則表示將/opt/test1整個目錄同步到/opt/test2目錄下。
3)其它選項,如"-r -v --delete"也都不能缺少,它們的含義很簡單,分別表示遞歸、打印詳細信息、同步前刪除(只是模擬rsync操作,不會真的執行)
4)sed的作用是過濾掉和文件不相關的內容。
5)以上rsync命令是假定了比較兩個目錄中只有普通文件和目錄,沒有軟鏈接、塊設備等特殊文件。如果有,請考慮加上對應的選項或者使用-a替代-r,
   否則結果中將出現skipping non-regular file的提示。但請注意,如果有軟鏈接,且加了對應選項(-l或-a或其他相關選項),則可能會出現fileA-->fileB的輸出。
6)這種方式效率很高,因為rsync的原因,篩選的可定制性也非常強。

二、圖形化的比較結果  #################################################################################################

方法1:使用vimdiff  ---------------------------------------------------------------------
vimdiff命令用於快速比較和合並少量文件,詳細用法參考:https://man.linuxde.net/vimdiff

[root@ss-server opt]# vimdiff <(cd /opt/test1; find . | sort) <(cd /opt/test2; find . | sort)
或者
[root@ss-server ~]# vimdiff <(find /opt/test1 -printf "%P\n"| sort) <(find /opt/test2 -printf "%P\n"| sort)

如何從圖形中退出??
依次按:Shift + 冒號,輸入quit,回車; 再接着輸入依次Shift + 冒號,輸入quit,回車。   以上兩次操作后,就退出圖形了。

方法2:使用meld  ---------------------------------------------------------------------
meld是python寫的一個圖形化文件/目錄比較工具,所以必須先安裝Linux圖形界面或設置好圖形界面接受協議。
它的功能非常豐富,和win下的beyond compare有異曲同工之妙。
meld是一個很酷的圖形化工具(一個 GNOME 桌面下的可視化的比較和合並工具),可供那些喜歡使用鼠標的人使用,可按如下來安裝。

[root@ss-server ~]# yum install -y meld

安裝了meld之后,就可以在linux桌面上使用meld比較兩個目錄了,功能非常強大。這里就不做介紹了~

27)Shell中判斷傳入的變量是否為空? ( 使用 if [ X$1 == X ] )("== 和 = ")("()和{}"

[root@ss-server ~]# cat test.sh 
#!/bin/bash
Deploy_Env=$1
Deploy_Env=`echo ${Deploy_Env} | tr '[a-z]' '[A-Z]'`
echo ${Deploy_Env}
if [[ x${Deploy_Env} == x ]];then
   echo "變量參數為空"
   exit 0
fi
[root@ss-server ~]# sh test.sh aa
AA
[root@ss-server ~]# sh test.sh 

變量參數為空
[root@ss-server ~]#


注意: 上面test.sh腳本中的"==" 也可以寫成 "="

這里需要說明下
===========================================================
1)== 和 = 的區別
==    可用於判斷變量是否相等
=     除了可用於判斷變量是否相等外,還可以表示賦值。

在 [ ] 或 [[ ]] 中,= 與 == 都表示判斷(字符串比較),此種情況下二者是等價的!
例如:
[root@ss-server ~]# a=beijing
[root@ss-server ~]# b=shanghai
[root@ss-server ~]# [ $a=$b ] && echo "equal"     
equal
[root@ss-server ~]# [ $a==$b ] && echo "equal"
equal
[root@ss-server ~]# [[ $a=$b ]] && echo "equal" 
equal
[root@ss-server ~]# [[ $a==$b ]] && echo "equal"
equal

在 (( )) 中 = 表示賦值, == 表示判斷(整數比較),此種情況下二者是不等價的!
例如:
[root@ss-server ~]# ((n=5))
[root@ss-server ~]# echo $n
5
[root@ss-server ~]# ((n==5)) && echo "equal"
equal


===========================================================
2)()和{} 區別
()和{}都是對一串的命令進行執行,但有所區別:

相同點:
()和{}都是把一串的命令放在括號里面,並且命令之間用;號隔開

不同點:
()只是對一串命令重新開一個子shell進行執行,{}對一串命令在當前shell執行。
()最后一個命令可以不用分號,{}最后一個命令要用分號。
()里的第一個命令和左邊括號不必有空格,{}的第一個命令和左括號之間必須要有一個空格!!!
()和{}中括號里面的某個命令的重定向只影響該命令,但括號外的重定向則影響到括號里的所有命令。

示例如下:
[root@ss-server ~]# var=test
[root@ss-server ~]# echo $var
test
[root@ss-server ~]# (var=notest;echo $var)
notest
[root@ss-server ~]# echo $var             
test
[root@ss-server ~]# { var=notest;echo $var;}
notest
[root@ss-server ~]# echo $var               
notest

有上面可知:
在{}中 第一個命令和{之間必須有空格,結束必須有;分號!!
{}中的修改了$var的值,說明在當前shell執行!!

[root@ss-server ~]# { var1=test1;var2=test2;echo $var1>a;echo $var2;}
test2
[root@ss-server ~]# cat a
test1
[root@ss-server ~]# { var1=test1;var2=test2;echo $var1;echo $var2;}>a
[root@ss-server ~]# cat a
test1
test2

腳本如下:
[root@ss-server ~]# cat test.sh
#!/bin/bash
(
    echo "1"
    echo "2"
) | awk '{print NR,$0}'
[root@ss-server ~]# sh test.sh
1 1
2 2

28)eval 命令用法

eval可讀取一連串的參數,然后再依參數本身的特性來執行。eval是shell內建命令,可用shell查看其用法。參數不限數目,彼此之間用分號隔開。

eval [參數]
eval命令將會首先掃描命令行進行所有的置換,然后再執行該命令。
該命令適用於那些一次掃描無法實現其功能的變量。該命令對變量進行兩次掃描。這些需要進行兩次掃描的變量有時被稱為復雜變量。
不過這些變量本身並不復雜。eval命令也可以用於回顯簡單變量,不一定是復雜變量。


示例1: eval命令也可以用於回顯簡單變量,不一定是復雜變量  ##########################################################################
[root@localhost ~]# NAME=kevin
[root@localhost ~]# echo ${NAME}
kevin
[root@localhost ~]# eval echo ${NAME}
kevin
[root@localhost ~]# echo '${'"NAME"'}'
${NAME}
[root@localhost ~]# eval echo '${'"NAME"'}'
kevin

[root@localhost ~]# a=123           
[root@localhost ~]# echo ${a}       
123
[root@localhost ~]# eval echo ${a}  
123
[root@localhost ~]# echo '${a}'           #單引號默認不識別變量
${a}
[root@localhost ~]# eval echo '${a}'      #使用eval后,用戶回顯變量,則單引號里面的變量就會被顯示出來
123
[root@localhost ~]# echo "${a}"           #雙引號默認識別變量
123
[root@localhost ~]# eval echo "${a}"
123

示例2:執行含有字符串的命令  ##########################################################################
首先我們首先創建一個名為test的小文件,在這個小文件中含有一些文本。
接着,將cat test賦給變量myfile,現在我們e c h o該變量,看看是否能夠執行上述命令。
[root@localhost ~]# cat test
Hello World!!!
I am a chinese Boy!

將"cat test"賦給變量myfile
[root@localhost ~]# myfile="cat test"

如果直接echo該變量,那么將無法列出test文件中的內容。
[root@localhost ~]# echo $myfile
cat test

接着來試一下eval命令,記住eval命令將會對該變量進行兩次掃瞄。
[root@localhost ~]# eval $myfile
Hello World!!!
I am a chinese Boy!

從上面的結果可以看出,使用e v a l命令不但可以置換該變量,還能夠執行相應的命令。第
一次掃描進行了變量置換,第二次掃描執行了該字符串中所包含的命令cat test。

示例3: eval可以用來顯示出傳遞給腳本的最后一個參數  ##########################################################################
下面提及的命令是eval其中一個很普通的應用,它重復了1次命令行參數傳遞過程,純粹地執行命令的命令。
[root@localhost ~]# echo 'eval echo \$$#' > test
[root@localhost ~]# cat test 
eval echo \$$#
[root@localhost ~]# sh test aa bb cc
cc

[root@localhost ~]# cat test.sh
#!/bin/bash
echo "Total of the arguments passed $#"
echo "The process Id is $$"
echo "Last argument os "$(eval echo \$$#)""
[root@localhost ~]# sh test.sh beijing shanghai anhui
Total of the arguments passed 3
The process Id is 82540
Last argument os anhui

在上面的腳本中,eval命令首先把$# 解析為當前shell的參數個數,然后再進行第二次掃描時得出最后一個參數。

示例4:給每個值一個變量名  ##########################################################################
可以給一個值一個變量名。下面會對此做些解釋,假定有一個名為test2的文件:
[root@localhost ~]# cat test2
COMMANY TQ
LANGUE ENGLISH
LIKE YES

希望該文件中的第一列成為變量名,第二列成為該變量的值,這樣就可以:
[root@localhost ~]# cat test.sh
#!/bin/bash
while read NAME VALUE
do
eval "${NAME}=${VALUE}"
done <test2
echo "$COMMANY $LANGUE $LIKE"
[root@localhost ~]# sh test.sh
TQ ENGLISH YES

示例5:使用eval命令進行1次命令行參數傳遞  ##########################################################################
[root@localhost ~]# cat test.sh
#!/bin/bash
NODE_IP_LIST=$1
NODE_PORT_LIST=$2
for ((i=1;i<=3;i++)); do
    eval NODE_IP='`echo ${NODE_IP_LIST}|awk -F, "{ print $"$i" }"`'       #這里awk語句中的print后面{}兩邊之所以加雙引號而不是單引號,是因為要將$i變量識別出來!!!
    eval NODE_PORT='`echo ${NODE_PORT_LIST}|awk -F, "{ print $"$i" }"`'   #如果awk語句中的print后面{}兩邊加單引號,則$i變量就識別不出來了!!!
 
    echo "address is ${NODE_IP}:${NODE_PORT}"
done

[root@localhost ~]# sh test.sh 172.16.60.21,172.16.60.22,172.16.60.23 8080,8081,8082
address is 172.16.60.21:8080
address is 172.16.60.22:8081
address is 172.16.60.23:8082

如果上面test.sh腳本中不使用eval,將eval去掉的話,則命令行參數就傳遞失敗了
[root@localhost ~]# cat test.sh
#!/bin/bash
NODE_IP_LIST=$1
NODE_PORT_LIST=$2
for ((i=1;i<=3;i++)); do
    NODE_IP='`echo ${NODE_IP_LIST}|awk -F, "{ print $"$i" }"`'          
    NODE_PORT='`echo ${NODE_PORT_LIST}|awk -F, "{ print $"$i" }"`'      
 
    echo "address is ${NODE_IP}:${NODE_PORT}"
done

[root@localhost ~]# sh test.sh 172.16.60.21,172.16.60.22,172.16.60.23 8080,8081,8082
address is `echo ${NODE_IP_LIST}|awk -F, "{ print $"$i" }"`:`echo ${NODE_PORT_LIST}|awk -F, "{ print $"$i" }"`
address is `echo ${NODE_IP_LIST}|awk -F, "{ print $"$i" }"`:`echo ${NODE_PORT_LIST}|awk -F, "{ print $"$i" }"`
address is `echo ${NODE_IP_LIST}|awk -F, "{ print $"$i" }"`:`echo ${NODE_PORT_LIST}|awk -F, "{ print $"$i" }"`

將NODE_IP和NODE_PORT兩個參數變量后面的單引號去掉,則效果和使用了eval命令一樣
[root@localhost ~]# cat test.sh 
#!/bin/bash
NODE_IP_LIST=$1
NODE_PORT_LIST=$2
for ((i=1;i<=3;i++)); do
    NODE_IP=`echo ${NODE_IP_LIST}|awk -F, "{ print $"$i" }"`          #這里awk語句中的print后面{}兩邊之所以加雙引號而不是單引號,是因為要將$i變量識別出來!!!
    NODE_PORT=`echo ${NODE_PORT_LIST}|awk -F, "{ print $"$i" }"`      #如果awk語句中的print后面{}兩邊加單引號,則$i變量就識別不出來了!!!
 
    echo "address is ${NODE_IP}:${NODE_PORT}"
done

[root@localhost ~]# sh test.sh 172.16.60.21,172.16.60.22,172.16.60.23 8080,8081,8082
address is 172.16.60.21:8080
address is 172.16.60.22:8081
address is 172.16.60.23:8082

##########################################################################################################################
需要注意一個細節:
下面兩種方式是一樣的效果,即awk中-F后面的分隔符兩邊加不加引號,加雙引號還是單引號,都不影響分割的效果!!!
但是后面print的{}兩邊必須是單引號!!!
awk -F"分隔符" '{print $n}' filename
awk -F'分隔符' '{print $n}' filename
awk -F分隔符 '{print $n}' filename

[root@localhost ~]# echo 172.16.60.21,172.16.60.22,172.16.60.23|awk -F"," '{ print $1 }'
172.16.60.21
[root@localhost ~]# echo 172.16.60.21,172.16.60.22,172.16.60.23|awk -F, '{ print $1 }'  
172.16.60.21

下面兩種方式均是不正確的。直接將$1作為前面echo輸出的整體了,即-F后面的分隔符沒有起作用!
[root@localhost ~]# echo 172.16.60.21,172.16.60.22,172.16.60.23|awk -F, "{ print $1 }"
172.16.60.21,172.16.60.22,172.16.60.23
[root@localhost ~]# echo 172.16.60.21,172.16.60.22,172.16.60.23|awk -F"," "{ print $1 }"
172.16.60.21,172.16.60.22,172.16.60.23

下面表達方式也是不正確的。$1兩邊加雙引號,直接將$1打印出來了
[root@localhost ~]# echo 172.16.60.21,172.16.60.22,172.16.60.23|awk -F, '{ print "$1" }'  
$1
[root@localhost ~]# echo 172.16.60.21,172.16.60.22,172.16.60.23|awk -F"," '{ print "$1" }'
$1

29)shell判斷傳入的便利是否為空 if [ x$1 = x ]

if語句中使用"x$1 = x"來判斷$1傳入參數是否為空。
如果x$1等於x,則$1傳入參數為空。
如果x$1不等於x,則$1傳入參數不為空。

####################  注意  ####################
x要么兩邊都是小寫,要么兩邊都是大寫!
等號可以是"=",也可以使用"=="
###############################################

如下腳本配置,如果x${num}的值為x,則$num傳入的變量為空!
[root@localhost ~]# cat test.sh
#!/bin/bash
num=$1
if [ x${num} = x ];then
   echo "the num argu is empty!"
else
   echo "the num args is $1"
fi
[root@localhost ~]# sh test.sh
the num argu is empty!
[root@localhost ~]# sh test.sh beijing
the num args is beijing

[root@localhost ~]# cat test.sh 
#!/bin/bash
num=$1
if [ X${num} = X ];then
   echo "the num argu is empty!"
else
   echo "the num args is $1"
fi
[root@localhost ~]# sh test.sh
the num argu is empty!
[root@localhost ~]# sh test.sh shanghai
the num args is shanghai

[root@localhost ~]# cat test.sh
#!/bin/bash
num=$1
if [ X${num} == X ];then
   echo "the num argu is empty!"
else
   echo "the num args is $1"
fi
[root@localhost ~]# sh test.sh
the num argu is empty!
[root@localhost ~]# sh test.sh shenzheng
the num args is shenzheng

30)set 命令用法
set命令是 Bash 腳本的重要環節,卻常常被忽視,導致腳本的安全性和可維護性出問題。set命令用來修改 Shell 環境的運行參數,也就是可以定制環境。set一共有十幾個參數可以定制,官方手冊有完整清單,這里重點介紹其中最常用的幾個參數。

1)set -e 參數
========================================================================================
set -e:表示執行的時候如果出現了返回值為非零,整個腳本就會立即退出。
set +e:表示執行的時候如果出現了返回值為非零,將會繼續執行下面的腳本。
 
即"set +e"表示關閉-e選項,"set -e"表示重新打開-e選項。
  
"set -e"命令作用
對於編寫的每個腳本,都應該在文件開頭加上set -e,這句語句的作用是告訴bash,如果任何語句的執行結果不是true則應該退出。
這樣的好處是防止錯誤像滾雪球般變大導致一個致命的錯誤,而這些錯誤本應該在之前就被處理掉。
如果要增加可讀性,可以使用set -o errexit,它的作用與set -e相同。
   
"set -e"命令用法總結:
1. 當命令的返回值為非零狀態時,則立即退出腳本的執行。
2. 作用范圍僅僅只限於腳本執行的當前進行,不作用於其創建的子進程!比如在當前終端下設置"set -e", 但是關閉該窗口,另開一個終端窗口,之前設置的"set -e"功能就失效了!
3. 另外,當想根據命令執行的返回值,輸出對應的log時,最好不要采用set -e選項,而是通過配合exit 命令來達到輸出log並退出執行的目的。
   
示例如下:
執行"set -e"之后,在接下來執行的命令中,如果命令的返回值不為0,那么會使所在的進程或shell退出。
   
示例1:
查看haha.txt文件,由於該文件不存在,之后命令后的返回值不為0
由於沒有提前執行"set -e",則命令執行后不會退出該進程
[root@ss-server ~]# cat haha.txt
cat: haha.txt: No such file or directory
[root@ss-server ~]# echo $?
1
[root@ss-server ~]#
   
現在執行"set -e"
[root@ss-server ~]# set -e
[root@ss-server ~]# cat haha.txt
cat: haha.txt: No such file or directory       #由於在之前添加了"set -e",則命令返回值為非零時就自動退出。
   
如果將"set -e"換成"set +e",則命令執行后返回值為非零,將不會自動退出shell,會繼續執行。
[root@ss-server ~]# set +e
[root@ss-server ~]# cat haha.txt
cat: haha.txt: No such file or directory
[root@ss-server ~]# echo $?
1
[root@ss-server ~]#
   
為了增加可讀性,可以使用"set -o errexit"命令來替換"set -e",兩者效果一樣!
[root@ss-server ~]# set -o errexit
[root@ss-server ~]# cat haha.txt
cat: haha.txt: No such file or directory       #執行命令的返回值為非零,直接退出當前shell。
   
示例2:
[root@ss-server ~]# cat hehe.txt;hostname
cat: hehe.txt: No such file or directory
ss-server
   
[root@ss-server ~]# set -e
[root@ss-server ~]# cat hehe.txt;hostname
cat: hehe.txt: No such file or directory       #執行命令的返回值為非零,直接退出當前shell。
   
[root@ss-server ~]# set -o errexit
[root@ss-server ~]# cat hehe.txt;hostname
cat: hehe.txt: No such file or directory       #執行命令的返回值為非零,直接退出當前shell。
 
###############  需要注意  ###############
1. "set -e"參數 和 "set -o errexit" 兩者等同!上面已有示例說明。
2. 還有一種方法是使用command || true,使得該命令即使執行失敗,腳本也不會終止執行。這個需要特別注意下!
 
如下,雖然設置了"set -e"參數,並且"cat hehe.txt"執行命令的返回值為非零。但是由於使用了||符號,表示前面命令執行結果為false后,繼續執行后面的命令。
那么只要這個||后面的命令執行結果為true,則整個命令執行結果返回值就是0,所以即使設置了"set -e",也沒有退出當前shell!

示例如下:
[root@ss-server ~]# set -e
[root@ss-server ~]# cat hehe.txt || hostname
cat: hehe.txt: No such file or directory
ss-server
[root@ss-server ~]# echo $?
0
 
[root@ss-server ~]# set -e
[root@ss-server ~]# cat hehe.txt || cat haha.txt || hostname
cat: hehe.txt: No such file or directory
cat: haha.txt: No such file or directory
ss-server
 
如果||最后面的那條命令執行結果為false,則整個命令執行結果返回值就是非零,則就會退出當前shell
[root@ss-server ~]# set -e
[root@ss-server ~]# cat hehe.txt || cat haha.txt
cat: hehe.txt: No such file or directory
cat: haha.txt: No such file or directory

3. 還要注意一種例外情況,就是set -e不適用於管道命令。

所謂管道命令,就是多個子命令通過管道運算符(|)組合成為一個大的命令。Bash 會把最后一個子命令的返回值,作為整個命令的返回值。
也就是說,只要最后一個子命令不失敗,管道命令總是會執行成功,因此它后面命令依然會執行,set -e就失效了。

示例如下:
[root@ss-server ~]# cat test.sh
#!/bin/bash
set -e
cat haha_yu | echo "hello world"
hostname

執行結果:
[root@ss-server ~]# sh test.sh
hello world
cat: haha_yu: No such file or directory
ss-server

上面腳本中,雖然haha文件不存在,但是cat haha_yu | echo "hello world"它是一個整體命令,只有|后面的命令執行成功,則整個命令的返回值就是0
所以后面的"hostname"命令也會繼續執行。


2)set -o pipefail 參數
========================================================================================
針對上面set -e不適用於管道命令的例子,set -o pipefail 用來解決這種情況,只要一個子命令失敗,整個管道命令就失敗,腳本就會終止執行。

set -o pipefail表示在管道連接的命令序列中,只要有任何一個命令返回非0值,則"整個管道命令"返回非0值,即使最后一個命令返回0!!

示例如下:
[root@ss-server ~]# cat test.sh
#!/bin/bash
set -eo pipefail
cat haha_yu | echo "hello world"
hostname

執行結果如下:
[root@ss-server ~]# sh test.sh
hello world
cat: haha_yu: No such file or directory

示例2:
|管道命令,只要最后一個命令執行成功,則整個管道命令執行結果返回值就是0,即整個管道命令執行就算成功!
[root@ss-server ~]# cat haha_yu | echo "hello world"
hello world
cat: haha_yu: No such file or directory
[root@ss-server ~]# echo $?
0
[root@ss-server ~]# cat haha_yu | echo "hello world" >/dev/null 2>&1
cat: haha_yu: No such file or directory
[root@ss-server ~]# echo $?
0

設置了"set -o pipefail"之后,只要有任何一個命令返回非0值,則"整個管道命令"返回非0值,即使最后一個命令返回0!
[root@ss-server ~]# set -o pipefail
[root@ss-server ~]# cat haha_yu | echo "hello world" >/dev/null 2>&1
cat: haha_yu: No such file or directory
[root@ss-server ~]# echo $?
1


3)set -u 參數
========================================================================================
該參數表示在腳本執行中,如遇到不存在的變量就會報錯,並停止執行!

示例如下:
[root@ss-server ~]# cat test.sh
#!/bin/bash
echo ${a}
echo `hostname`

上面腳本中,${a}是一個不存在的變量。執行結果如下:
[root@ss-server ~]# sh test.sh

ss-server

可以看到,echo ${a}輸出了一個空行,說明Bash忽略了不存在的${a}變量,然后繼續執行echo `hostname`。
大多數情況下,這不是我們想要的行為,遇到變量不存在,腳本應該報錯,而不是一聲不響地往下執行。
那么"set -u"參數就用來改變這種行為。腳本在頭部加上它,遇到不存在的變量就會報錯,並停止執行。

[root@ss-server ~]# cat test.sh
#!/bin/bash
set -u
echo ${a}
echo `hostname`

執行結果如下:
[root@ss-server ~]# sh test.sh
test.sh: line 3: a: unbound variable

從上面可以看到,添加"set -u"參數后,腳本執行中發現不存在${a}變量就報錯了(報錯"未綁定的變量"),並且不再執行后面的語句。

需要注意:
"set -u" 還有另一種寫法"set -o nounset",兩者是等價的!!!

[root@ss-server ~]# cat test.sh
#!/bin/bash
set -o nounset
echo ${a}
echo `hostname`

[root@ss-server ~]# sh test.sh
test.sh: line 3: a: unbound variable


4)set -x 參數
========================================================================================
默認情況下,shell腳本執行后,屏幕只顯示運行結果,沒有其他內容。
如果多個命令連續執行,它們的運行結果就會連續輸出。有時會分不清,某一段內容是什么命令產生的。

"set -x"參數用來在運行結果之前,先輸出執行的那一行命令。即輸出腳本執行的詳細過程!

示例如下:
[root@ss-server ~]# cat test.sh
#!/bin/bash
DATE=$(date +%Y%m%d)
echo "$(hostname) and ${DATE}"

執行結果如下,只輸出運行結果,沒有輸出詳細的執行命令
[root@ss-server ~]# sh test.sh 
ss-server and 20191218

現在加上"set -x"參數
[root@ss-server ~]# cat test.sh
#!/bin/bash
set -x
DATE=$(date +%Y%m%d)
echo "$(hostname) and ${DATE}"

執行結果如下:
[root@ss-server ~]# sh test.sh
++ date +%Y%m%d
+ DATE=20191218
++ hostname
+ echo 'ss-server and 20191218'
ss-server and 20191218

需要注意:
"set -x"參數還有另一種寫法"set -o xtrace",兩者是等價的!!!
[root@ss-server ~]# cat test.sh
#!/bin/bash
set -o xtrace
DATE=$(date +%Y%m%d)
echo "$(hostname) and ${DATE}"

執行結果如下:
[root@ss-server ~]# sh test.sh
++ date +%Y%m%d
+ DATE=20191218
++ hostname
+ echo 'ss-server and 20191218'
ss-server and 20191218

其實,上面"set -x" 和 "set -o xtrace" 作用就是打印腳本執行的詳細過程,這和"sh -x xxx.sh"的效果也是一樣的!
[root@ss-server ~]# cat test.sh
#!/bin/bash
DATE=$(date +%Y%m%d)
echo "$(hostname) and ${DATE}"

如上,腳本中沒有添加"set -x" 或 "set -o xtrace", 在執行腳本時使用"sh -x"也可以打印腳本執行過程。
[root@ss-server ~]# sh -x test.sh
++ date +%Y%m%d
+ DATE=20191218
++ hostname
+ echo 'ss-server and 20191218'
ss-server and 20191218


5)shell腳本的錯誤處理
========================================================================================
如果腳本里面有運行失敗的命令(返回值非0),Bash 默認會繼續執行后面的命令。
[root@ss-server ~]# cat test.sh
#!/bin/bash
cat haha_yu
echo `hostname`

上面腳本中,"cat haha_yu"是一個執行會報錯的命令,因為haha_yu文件不存在。
但是,Bash 會忽略這個錯誤,繼續往下執行。
[root@ss-server ~]# sh test.sh
cat: haha_yu: No such file or directory
ss-server

可以看到,上面腳本中Bash 只是顯示有錯誤,並沒有終止執行。
這種行為很不利於腳本安全和除錯。實際開發中,如果某個命令失敗,往往需要腳本停止執行,防止錯誤累積。這時,一般采用下面的寫法。
"command || exit 1"

上面的寫法表示只要command有非零返回值,腳本就會停止執行。

如果停止執行之前需要完成多個操作,就要采用下面三種寫法。
# 寫法一
command || { echo "command failed"; exit 1; }

# 寫法二
if ! command; then echo "command failed"; exit 1; fi

# 寫法三
command
if [ "$?" -ne 0 ]; then echo "command failed"; exit 1; fi

另外,除了停止執行,還有一種情況:
如果兩個命令有繼承關系,只有第一個命令成功了,才能繼續執行第二個命令,那么就要采用下面的寫法。
command1 && command2

至於,;、||、&& 這三者的使用區別,在上面已經詳細介紹過了,這里就不贅述了。

示例1
[root@ss-server ~]# cat test.sh
#!/bin/bash
cat haha_yu || exit 1
echo `hostname`

執行結果如下:
[root@ss-server ~]# sh test.sh
cat: haha_yu: No such file or directory

示例2
[root@ss-server ~]# cat test.sh
#!/bin/bash
cat haha_yu || { echo "執行失敗";exit 1;}
echo `hostname`

執行結果如下:
[root@ss-server ~]# sh test.sh
cat: haha_yu: No such file or directory
執行失敗

示例3
[root@ss-server ~]# cat test.sh
#!/bin/bash
if ! cat haha_yu;then
    echo "執行失敗"
    exit 1
fi
echo `hostname`

執行結果如下:
[root@ss-server ~]# sh test.sh
cat: haha_yu: No such file or directory
執行失敗

示例4
[root@ss-server ~]# cat test.sh
#!/bin/bash
cat haha_yu
if [ $? -ne 0 ];then
    echo "執行失敗"
    exit 1
fi
echo `hostname`

執行結果如下:
[root@ss-server ~]# sh test.sh
cat: haha_yu: No such file or directory
執行失敗


6)set命令總結
========================================================================================
set命令的上面這四個參數,一般都放在一起在shell腳本中使用。

# 寫法一
set -euxo pipefail

# 寫法二
set -eux
set -o pipefail

以上這兩種寫法建議放在所有 Bash 腳本的頭部。

還有另一種辦法:在執行Bash腳本的時候,從命令行傳入這些參數。
# bash -euxo pipefail script.sh


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM