Shell脚本字符串匹配及日常命令工具 - 用法总结(技巧指南)

时间:2024-04-15 18:17:18

 

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