Shell脚本字符串匹配及日常命令工具 - 用法总结(技巧指南)
Shell提供了很多字符串和文件处理的命令,如awk、expr、grep、sed等命令,还有文件的排序、合并和分割等一系列的操作命令。下面重点总结下Shell字符串处理、文本处理以及各类命令及函数用法。
先从expr命令开始梳理,expr 引出通用求值表达式,可以实现算术操作、比较操作、字符串操作和逻辑操作等功能。
1) 计算字符串长度
字符串名为string,可以使用命令 ${#string} 或 expr length ${string} 两种方法来计算字符串的长度。
若string中包括空格,则expr计算命令中需用双引号引起来,即expr length "${string}"。(${#string}方法对于有无空格的字符串均可使用)
需要注意:expr length后面只能跟一个参数,string有空格会当作多个参数处理。
[root@kevin ~]# string="kevinisgood" [root@kevin ~]# expr length ${string} 11 [root@kevin ~]# expr length "${string}" 11 [root@kevin ~]# echo ${#string} # 使用自带shell函数读取字符串长度 11 如果string字符串中有空格 [root@kevin ~]# string="kevin is good" [root@kevin ~]# expr length ${string} expr: syntax error [root@kevin ~]# expr length "${string}" 13 [root@kevin ~]# echo ${#string} 13 注意:这里说到如果string字符串中有空格,则需要双引号引起来。但如果是单引号引起来呢? 如果string字符串用单引号引起来,则统计出来的是"字符串去重和去掉空格"后的长度 [root@kevin ~]# echo ${string} kevin is good [root@kevin ~]# expr length '${string}' #使用单引号的话,统计的是"字符去重以及去掉空格"之后的长度 9 [root@kevin ~]# string="kevinisgood" [root@kevin ~]# echo ${string} kevinisgood [root@kevin ~]# expr length '${string}' 9
2)匹配字符串长度
即匹配字符串开头子串的长度!!使用命令 expr match ${string} $substring,表示在string字符串的开头匹配substring字符串,返回匹配到的substring字符串的长度。若string开头匹配不到则返回0,其中substring可以是字符串也可以是正则表达式。
expr匹配字符串长度的两种格式: # expr match "$string" '$substring' # expr "$string" : '$substring' [root@kevin ~]# string="zhongguo hao" [root@kevin ~]# expr length "${string}" 12 [root@kevin ~]# expr match "${string}" "z.*" 12 [root@kevin ~]# expr match "${string}" "zho" 3 [root@kevin ~]# expr match "${string}" "hao" 0 注意: "hao"尽管在string字符串中出现,但是未出现在string的开头处,因此返回0! 所以说,expr match匹配的一定是字符串开头子串的长度!! 后面的$substring可以是正则, $substring两边是单引号或双引号无所谓。 [root@kevin ~]# string="KEvin IS good" [root@kevin ~]# expr match "${string}" "[A-Z]*" 2 [root@kevin ~]# expr match "${string}" '[A-Z]*' 2 [root@kevin ~]# expr "${string}" : "[A-Z]*" #注意这种方法中就没有了"match"参数 2 [root@kevin ~]# expr "${string}" : '[A-Z]*[a-z]*' 5
3)匹配字符串索引
expr的索引命令格式为:expr index ${string} $substring。
在字符串$string上匹配$substring中字符最早第一次出现的位置(从左到右,从1开始),匹配不到,expr index返回0。
简单来说,就是找出子串在字符串中最早第一次出现的单个字符的位置!!
[root@kevin ~]# string="love beijing" [root@kevin ~]# echo ${string} love beijing [root@kevin ~]# expr index ${string} "bei" expr: syntax error [root@kevin ~]# expr index "${string}" "bei" #注意string字符串中有空格,需要加双引号 4 [root@kevin ~]# expr index "${string}" "jing" 8 [root@kevin ~]# expr index "${string}" "haa" 0 注意: expr index命令中返回的是后面$substring中"子串字符中"最早第一次出现的位置。 "bei"子串中在${string}字符串中最早第一次出现的是"e",第4位置 "jing"子串中在${string}字符串中最早第一次出现的是"i",第4位置 "haa"匹配不到,所以返回0!
=== Bash Shell命令 ===
4)抽取字符串的子串
Shell提供两种命令和expr实现抽取子串功能。
正着抽取(即从左到右)有两种格式。(左边默认从0开始标号) 格式一:{string:position} 从名称为${string}的字符串的第$position个位置开始抽取子串,从0开始标号。 格式二:{string:position:length} 增加$length变量,表示从${string}字符串的第$position个位置开始抽取长度为$length的子串。 需要注意:都是从string的左边开始计数抽取子串。 示例: [root@kevin ~]# string="hello world wang" [root@kevin ~]# echo ${string} hello world wang [root@kevin ~]# echo ${string:0} #从标号为0(即从第一个字母)开始截取,截取到结尾。 hello world wang [root@kevin ~]# echo ${string:6} #从标号为6的字符开始截取,截取到结尾。 world wang [root@kevin ~]# echo ${string:6:4} #从标号为6的字符开始截取,截取长度为4。 worl [root@kevin ~]# echo ${string:6:0} #从标号为6的字符开始截取,截取长度为0。 [root@kevin ~]# 反着抽取(即从右到左)有两种格式。(右边默认从-1开始标号) 格式一:{string: -position}。需要谨记:冒号与横杠间有一个空格!!!! 格式二:{string:(position)}。如果加了括号,则冒号后面加不加空格是一样的效果!!! [root@kevin ~]# echo ${string:-2} #冒号与"-"之间必要要有空格,否则截取无效! hello world wang [root@kevin ~]# echo ${string: -2} ng [root@kevin ~]# echo ${string:(-2)} ng [root@kevin ~]# echo ${string: (-2)} ng 如果要想实现从右边第几个字符开始截取,截取长度为多少,则方法为: {string:m-n:x} 表示从右边第"m-n"个字符开始截取,截取长度为x [root@kevin ~]# echo ${string:1-7:3} d w [root@kevin ~]# echo ${string:3-9:3} d w expr substr也能够实现抽取子串功能,命令格式:expr substr ${string} $position $length, 这个与上面最大不同是expr substr命令从1开始进行标号!!!! [root@kevin ~]# echo ${string} hello world wang [root@kevin ~]# echo ${string:3:5} #从0开始进行标号 lo wo [root@kevin ~]# expr substr "${string}" 3 5 #从1开始标号 llo w 还可以使用正则表达式抽取子串的命令,但只能抽取string开头处或结尾处的子串。 抽取字符串开头处的子串: 格式一:expr match $string '' 格式二:expr $string : ''。 注意:其中冒号前后都有一个空格。 抽取字符串结尾处的子串: 格式一:expr match $string '.*' 格式二:expr $string : '.*'。 注意:.*表示任意字符的任意重复。一个.表示一个字符。 [root@kevin ~]# string="20181112hello WORld Good" [root@kevin ~]# echo ${string} 20181112hello WORld Good [root@kevin ~]# expr match "$string" '\([0-9]*\)' #这里的${string}最好使用双引号引起来,因为字符串可能中有空格!如果没有空格,就可以不用使用双引号。 20181112 [root@kevin ~]# expr match "$string" "\([0-9]*\)" 20181112 [root@kevin ~]# expr "$string" : '\([0-9]*\)' 20181112 [root@kevin ~]# expr match "$string" '.*\(.\)' d [root@kevin ~]# expr match "$string" '.*\(..\)' od [root@kevin ~]# expr match "$string" '.*\(...\)' ood [root@kevin ~]# expr match "$string" '.*\(.....\)' Good [root@kevin ~]# expr "$string" : '.*\(.....\)' Good [root@kevin ~]# expr "$string" : '.*\(.........\)' ORld Good [root@kevin ~]# string="heLLO2018 world" [root@kevin ~]# expr match "$string" '\([a-z]*\)' he [root@kevin ~]# expr match "$string" '\([a-Z]*\)' heLLO [root@kevin ~]# expr match "$string" '\([a-Z]*[0-9]*\)' heLLO2018 [root@kevin ~]# expr match "$string" '\(.[a-Z]*[0-9]*\)' heLLO2018 [root@kevin ~]# expr "$string" : '.*\(.........\)' 018 world
5)删除字符串的子串
删除字串是指将原字符串中符合条件的子串删除。
从string开头处删除子串: 格式一:${string#substring} 删除开头处与substring匹配的最短子串。 格式二:${string##substring} 删除开头处与substring匹配的最长子串。其中substring并非是正则表达式而是通配符。 [root@kevin ~]# string="china IS niuBIlity2018" [root@kevin ~]# echo ${string#c*i} #删除c开头到a的最短子串 na IS niuBIlity2018 [root@kevin ~]# echo ${string##c*i} #删除c开头到a的最长子串 ty2018 [root@kevin ~]# echo ${string#c*n} a IS niuBIlity2018 [root@kevin ~]# echo ${string##c*n} iuBIlity2018 [root@kevin ~]# echo ${string#ch*n} #删除ch开头到a的最短子串 a IS niuBIlity2018 [root@kevin ~]# echo ${string##ch*n} #删除ch开头到a的最长子串 iuBIlity2018 上面#或##后面的字符必须是${string}字符串的开头子串!否则删除子串就无效了! [root@kevin ~]# echo ${string#i*n} #i不是开头字符,所以删除无效 china IS niuBIlity2018 [root@kevin ~]# echo ${string##i*n} #i不是开头字符,所以删除无效 china IS niuBIlity2018 另外:可以使用下面方式进行删除: ${string#*substring} 删除${string}字符串中第一个$substring及其之前的字符 ${string##*substring} 删除${string}字符串中最后一个$substring及其之前的字符 [root@kevin ~]# string="china IS niuBIlity2018" [root@kevin ~]# echo ${string#*i} #删除第一个i及其之前的字符 na IS niuBIlity2018 [root@kevin ~]# echo ${string##*i} #删除最后一个i及其之前的字符 ty2018 也可以使用下面方法进行删除 格式一:${string%substring*} 删除${string}字符串中最后一个$substring及其之后的字符 格式二:${string%%substring*} 删除${string}字符串中第一个$substring及其之后的字符 [root@kevin ~]# echo ${string} china IS niuBIlity2018 [root@kevin ~]# echo ${string%i*} china IS niuBIl [root@kevin ~]# echo ${string%%i*} ch [root@kevin ~]# echo ${string%c*} [root@kevin ~]# echo ${string%%c*} [root@kevin ~]#
6)字符串替换
替换子串命令可以在任意处、开头处、结尾处替换满足条件的子串,其中的substring都不是正则表达式而是通配符。
在任意处替换子串命令: 格式一:${string/substring/replacement},仅替换第一次与substring相匹配的子串。 格式二:${string//substring/replacement},替换所有与substring相匹配的子串。 [root@kevin ~]# string="china IS niuBIlity2018" [root@kevin ~]# echo ${string/i/#} ch#na IS niuBIlity2018 [root@kevin ~]# echo ${string//i/#} ch#na IS n#uBIl#ty2018 [root@kevin ~]# echo ${string} china IS niuBIlity2018 [root@kevin ~]# echo ${string/ /--} #替换空格 china--IS niuBIlity2018 [root@kevin ~]# echo ${string// /--} china--IS--niuBIlity2018 在开头处替换与substring相匹配的子串,格式为:${string/#substring/replacement}。 在结尾除替换与substring相匹配的子串,格式为:${string/%substring/replacement}。 [root@kevin ~]# echo ${string} china IS niuBIlity2018 [root@kevin ~]# echo ${string/#ch/he} heina IS niuBIlity2018 [root@kevin ~]# echo ${string/#china/anhui} anhui IS niuBIlity2018 [root@kevin ~]# echo ${string/#niu/he} #注意这里#后面的字符必须是${string}字符串中开头的字符 china IS niuBIlity2018 [root@kevin ~]# echo ${string/%2018/2020} china IS niuBIlity2020 [root@kevin ~]# echo ${string/%lity2018/hehehe} china IS niuBIhehehe
7)${!varprefix*} 和 ${!varprefix@}
[root@kevin ~]# test="bobo" [root@kevin ~]# test1="bobo1" [root@kevin ~]# test2="bobo2" [root@kevin ~]# test4="bobo4" [root@kevin ~]# echo ${!test*} test test1 test2 test4 [root@kevin ~]# echo ${!test@} test test1 test2 test4
8)参数替换
从string开头处删除子串: 格式一:${string#substring} 删除开头处与substring匹配的最短子串。 格式二:${string##substring} 删除开头处与substring匹配的最长子串。其中substring并非是正则表达式而是通配符。 [root@kevin ~]# string="china IS niuBIlity2018" [root@kevin ~]# echo ${string#c*i} #删除c开头到a的最短子串 na IS niuBIlity2018 [root@kevin ~]# echo ${string##c*i} #删除c开头到a的最长子串 ty2018 [root@kevin ~]# echo ${string#c*n} a IS niuBIlity2018 [root@kevin ~]# echo ${string##c*n} iuBIlity2018 [root@kevin ~]# echo ${string#ch*n} #删除ch开头到a的最短子串 a IS niuBIlity2018 [root@kevin ~]# echo ${string##ch*n} #删除ch开头到a的最长子串 iuBIlity2018 上面#或##后面的字符必须是${string}字符串的开头子串!否则删除子串就无效了! [root@kevin ~]# echo ${string#i*n} #i不是开头字符,所以删除无效 china IS niuBIlity2018 [root@kevin ~]# echo ${string##i*n} #i不是开头字符,所以删除无效 china IS niuBIlity2018 另外:可以使用下面方式进行删除: ${string#*substring} 删除${string}字符串中第一个$substring及其之前的字符 ${string##*substring} 删除${string}字符串中最后一个$substring及其之前的字符 [root@kevin ~]# string="china IS niuBIlity2018" [root@kevin ~]# echo ${string#*i} #删除第一个i及其之前的字符 na IS niuBIlity2018 [root@kevin ~]# echo ${string##*i} #删除最后一个i及其之前的字符 ty2018 也可以使用下面方法进行删除 格式一:${string%substring*} 删除${string}字符串中最后一个$substring及其之后的字符 格式二:${string%%substring*} 删除${string}字符串中第一个$substring及其之后的字符 [root@kevin ~]# echo ${string} china IS niuBIlity2018 [root@kevin ~]# echo ${string%i*} china IS niuBIl [root@kevin ~]# echo ${string%%i*} ch [root@kevin ~]# echo ${string%c*} [root@kevin ~]# echo ${string%%c*} [root@kevin ~]# ------------------------------------------------------------------- 再看下面一例: [root@kevin ~]# str=bo/www/kevin/data/test/ccd.log [root@kevin ~]# echo ${str#*/*/} kevin/data/test/ccd.log [root@kevin ~]# echo ${str##*/*/} ccd.log [root@kevin ~]# echo ${str%/*/*} bo/www/kevin/data [root@kevin ~]# echo ${str%%/*/*} bo 以上是以/*/作为匹配的字符串,即正则匹配的字符串。
9)如何判断一个字符串是否由字母数字开头 (grep)
1)判断一个字符串是否由大小写字母或数字开头(或结尾) [root@kevin ~]# cat test.sh #!/bin/bash #str="This IS a root USER, 20171aaabb" read -p "请输入内容:" str if echo "${str}" | grep -q '^[A-Za-z0-9].*\+$'; then echo -e "${str}\nok" else echo "invaliad" fi 需要注意: 脚本中的echo后面只需要添加-e参数,是为了让打印中的\n换行符生效!如果不加-e参数,则\n就被做当普通字符打印出来了! read -p 表示"指定要显示的提示"。如果添加-s参数,即"read -sp",则表示"静默输入,即隐藏输入的数据,一般用于密码输入" 执行脚本: [root@kevin ~]# sh test.sh 请输入内容:TOOk213gg TOOk213gg ok [root@kevin ~]# sh test.sh 请输入内容:@#sadf123 invaliad [root@VM_16_9_centos ~]# sh test.sh 请输入内容:arTR213#$1 arTR213#$1 ok ============================================================== [root@kevin ~]# read -p "输入你想要输入的内容:" 输入你想要输入的内容:asdfsafsaf [root@kevin ~]# read -sp "输入你想要输入的内容:" #加了-s参数后,即为静默输入,隐藏输入的内容 输入你想要输入的内容: [root@kevin ~]# ============================================================== 为了简化,还可以将上面脚本中的: grep -q '^[A-Za-z0-9].*\+$' 改为 grep -q '^[A-Za-z0-9].*' -------------------------------------- 如果判断一个字符串是否由大小写字母或数字结尾!!!!! 则只需要将上面脚本中的: grep -q '^[A-Za-z0-9].*\+$' 改为 grep -q '.*[A-Za-z0-9]$' ============================================================== 还要注意: '^[A-Za-z0-9].*\+$' 表示以大写字母或小写字母或数字为开头。没有顺序要求!! '^[A-Za-z0-9].*' 可以直接去掉后面的"\+$"部分 '^[A-Z].*' 表示以大写字母开头 '^[a-z].*' 表示以小写字母开头 '^[0-9].*' 表示以数字字母开头 下面都是最常用的 grep ^[0-9] grep "^[0-9]" grep ^[a-z] grep "^[a-z]" grep ^[A-Z] grep "^[A-Z]" grep ^[a-Z] grep "^[a-Z]" grep .*[0-9]$ grep ".*[0-9]$" grep ".*[a-z]$" grep ".*[a-z]$" grep ".*[A-Z]$" grep ".*[A-Z]$" grep .*[a-Z]$ grep ".*[a-Z]$" grep [0-9]G grep [a-z]2018_data [root@kevin ~]# cat a.txt Good study 2018! hahahah~ good Study 2018hehehehe 2018 Good study 1wqe 2018stuDY is heht6ttt !@#asdf TOOk213gg asdfasdf anhui 2018asdfjlsadfdsaff #$$$$$ [root@kevin ~]# cat a.txt|grep '^[A-Z].*\+$' Good study 2018! hahahah~ TOOk213gg asdfasdf [root@kevin ~]# cat a.txt|grep '^[a-z].*\+$' good Study 2018hehehehe anhui 2018asdfjlsadfdsaff [root@kevin ~]# cat a.txt|grep '^[0-9].*\+$' 2018 Good study 1wqe 2018stuDY is heht6ttt [root@kevin ~]# cat a.txt|grep '^[A-Za-z0-9].*\+$' Good study 2018! hahahah~ good Study 2018hehehehe 2018 Good study 1wqe 2018stuDY is heht6ttt TOOk213gg asdfasdf anhui 2018asdfjlsadfdsaff [root@kevin ~]# cat a.txt|grep -v '^[A-Za-z0-9].*\+$' !@#asdf #$$$$$ [root@kevin ~]# cat a.txt|grep -v '^[A-Z].*\+$'|grep -v '^[a-z].*\+$'|grep -v '^[0-9].*\+$' !@#asdf #$$$$$ [root@kevin ~]# cat a.txt |grep -v ^[a-Z] 2018 Good study 1wqe 2018stuDY is heht6ttt !@#asdf #$$$$$ [root@kevin ~]# cat a.txt |grep -v ^[a-Z]|grep -v [0-9] !@#asdf #$$$$$ grep获取多个条件(grep -E "条件1|条件2|条件3") [root@kevin ~]# cat a.txt |grep -E "^[a-z]|^[0-9]" good Study 2018hehehehe 2018 Good study 1wqe 2018stuDY is heht6ttt anhui 2018asdfjlsadfdsaff grep过滤多个条件(grep -v "条件1\|条件2\|条件3"),注意""里面有转义符"\" [root@kevin ~]# cat a.txt |grep -v "^[a-z]\|^[0-9]" Good study 2018! hahahah~ !@#asdf TOOk213gg asdfasdf #$$$$$ [root@kevin ~]# cat a.txt |grep -v "[a-z]\|^[0-9]" #$$$$$
10)删除字符串中指定字符(tr命令、sed命令)
一、使用sed将字符串中指定的字符删除 [root@kevin ~]# echo "2018-10-08 15:19:05"|sed 's/-//g'|sed 's#:##g' 20181008 151905 删除字符串中的特殊字符 [root@kevin ~]# cat test.sh #!/bin/bash str="root!@#QWE123" echo ${str}| sed 's/\!//g' | sed 's/\@//g' | sed 's/\#//g' [root@kevin ~]# sh test.sh rootQWE123 还可以使用tr -d命令进行删除: [root@kevin ~]# cat test.sh #!/bin/bash str="root!@#QWE123" echo ${str}| tr -d "!" | tr -d "@" | tr -d "#" [root@kevin ~]# sh test.sh rootQWE123 ------------------------------------------------------------------------------- 另外:sed也支持正则 sed -n '/[0-9]/p' filename 将文件中匹配数字的行打印出来 sed -n '/[a-z]/p' filename 将文件中匹配小写字母的行打印出来 sed -n '/[A-Z]/p' filename 将文件中匹配大写字母的行打印出来 sed -i '/[0-9]/d' filename 将文件中匹配数字的行删除 sed -n '/[a-z]/d' filename 将文件中匹配小写字母的行删除 sed -n '/[A-Z]/d' filename 将文件中匹配大写字母的行删除 sed -i '/[0-9]/s/root/huoqiu/g' filename 将文件中匹配数字的行里的root替换为huoqiu sed -i '/[a-z]/s/root/huoqiu/g' filename 将文件中匹配小写字母的行里的root替换为huoqiu sed -i '/[A-Z]/s/root/huoqiu/g' filename 将文件中匹配大写字母的行里的root替换为huoqiu =============================================================================================== 二、使用tr命令删除字符串中指定字符。tr使用-d参数可以起到删除字符的效果。支持正则表达式 [root@kevin ~]# echo "huoqiu123GOOD"|tr -d "a-z" #删除字符串中的小写字母 123GOOD [root@kevin ~]# echo "huoqiu123GOOD"|tr -d "a-zA-Z" #删除字符串中的大写字母 123 [root@kevin ~]# echo "huoqiu123GOOD"|tr -d "0-9" #删除字符串中的数字 huoqiuGOOD 删除字符串中指定的部分字符 [root@kevin ~]# echo "huoqiu123GOOD"|tr -d "k"|tr -d "G" evin123OOD [root@kevin ~]# echo "huoqiu123GOOD"|tr -d "123" huoqiuGOOD 截取字符串中的特殊字符 [root@kevin ~]# cat test.sh #!/bin/bash str="root!@#QWE123" echo ${str}| tr -d "a-z"| tr -d "A-Z"| tr -d "0-9" [root@kevin ~]# sh test.sh !@# 3)这里简单介绍下tr命令的日常用法 tr命令可以对来自标准输入的字符进行替换、压缩和删除。它可以将一组字符变成另一组字符,经常用来编写优美的单行命令,作用很强大。 语法 # tr (选项) (参数) 选项 -c或——complerment: 用字符集1中的字符串替换,要求字符集为ASCII。 -d或——delete: 删除所有属于字符集1的字符; -s或--squeeze-repeats: 把连续重复的字符以单独一个字符表示。即压缩字符,但是必须是连续重复的单个字符! -t或--truncate-set1: 先删除字符集1较字符集2多出的字符。 参数 字符集1:指定要转换或删除的原字符集。当执行转换操作时,必须使用参数“字符集2”指定转换的目标字符集。但执行删除操作时,不需要参数“字符集2”; 字符集2:指定要转换成的目标字符集。 tr用来从标准输入中通过替换或删除操作进行字符转换。 tr主要用于删除文件中控制字符或进行字符转换。 使用tr时要转换两个字符串:字符串1用于查询,字符串2用于处理各种转换。 tr刚执行时,字符串1中的字符被映射到字符串2中的字符,然后转换操作开始。 通过使用tr可以非常容易地实现sed的许多最基本功能,可以将tr看作为sed的(极其)简化的变体。 它可以用一个字符来替换另一个字符,或者可以完全除去一些字符。您也可以用它来除去重复字符。这就是所有 tr 所能够做的。 看看下面示例: 1)tr的替换命令 将输入字符由小写转换为大写: [root@kevin ~]# echo "anhui@root"|tr "a-z" "A-Z" ANHUI@ROOT 将输入字符由大写转换为小写: [root@kevin ~]# echo "ANhui@ROOT"|tr "A-Z" "a-z" anhui@root [root@kevin ~]# echo "172.16.60.34 data-node01"|tr "." "_" 172_16_60_34 data-node01 [root@kevin ~]# echo "root \n 213"| tr " " "#" root#\n#213 [root@kevin ~]# echo "123456@abc"|tr "0-9" "#" ######@abc [root@kevin ~]# echo "123456@abc"|tr "123" "pas" # 替换两个字符集的时候,分别是一个字符对应一个字符的关系 pas456@abc [root@kevin ~]# echo "123456@abc"|tr "123" "password" # 当字符集1的字符数少于字符集2的字符数时,就取字符集2的前面对应数目的字符进行替换 pas456@abc [root@kevin ~]# echo "123456@abc"|tr "1234" "password" pass56@abc [root@kevin ~]# echo "123456@abc"|tr "12345" "password" passw6@abc [root@kevin ~]# echo "123456@abc"|tr "123456" "password" passwo@abc [root@kevin ~]# echo "123456@abc"|tr "123456" "T" #当字符集1的字符数多于字符集2的字符数时,先进行两个字符集对应数目字符的替换,剩下多余的字符集1字符就使用字符集2的最后一个字符进行重复替换 TTTTTT@abc [root@kevin ~]# echo "123456@abc"|tr "123456" "TM" TMMMMM@abc [root@kevin ~]# echo "123456@abc"|tr "123456" "TMC" TMCCCC@abc [root@kevin ~]# echo "123456@abc"|tr "123456" "TMCH" TMCHHH@abc [root@kevin ~]# echo "123456@abc"|tr "123456" "TMCHR" TMCHRR@abc [root@kevin ~]# echo "123456@abc"|tr "123456" "TMCHRY" TMCHRY@abc 需要注意: 'A-Z' 和 'a-z'都是集合,集合是可以自己制定的,例如:'ABD-}'、'bB.,'、'a-de-h'、'a-c0-9'都属于集合,集合里可以使用'\n'、'\t',可以可以使用其他ASCII字符。 替换两个字符集的时候,分别是一个字符对应一个字符的关系 当字符集1的字符数少于字符集2的字符数时,就取字符集2的前面对应数目的字符进行替换 当字符集1的字符数多于字符集2的字符数时,先进行两个字符集对应数目字符的替换,剩下多余的字符集1字符就使用字符集2的最后一个字符进行重复替换 另外:"O*n" 表示字符O重复出现指定次数n。因此"O*2"匹配OO的字符串!!!! [root@kevin ~]# echo "aaa123"|tr "a*3" "w" #将a和3都替换为了w www12w [root@kevin ~]# echo "aaa123"|tr "a*3" "xy" #将a替换为了x,3替换为了y xxx12y [root@kevin ~]# echo "aaa123"|tr "a3" "xy" xxx12y [root@kevin ~]# echo "aaaaaaaa123"|tr "a*3" "xyz" xxxxxxxx12z [root@kevin ~]# echo "aaa123"|tr "aaa" "w" #匹配字符集1中最后一个字符对应字符集2中的那个字符 www123 [root@kevin ~]# echo "aaa123"|tr "aaa" "wx" xxx123 [root@kevin ~]# echo "aaa123"|tr "aaa" "wxy" yyy123 [root@kevin ~]# echo "aaa123"|tr "aaa" "wxym" yyy123 [root@kevin ~]# echo "aaa123"|tr "aaa" "wxymn" yyy123 2)tr的删除命令 (上面案例已经说明) [root@kevin ~]# echo "hello 123 world 456" | tr -d '0-9' hello world [root@kevin ~]# cat test beijing shanghai abcde [root@kevin ~]# cat test|tr -d "abcji" #即凡是test文件中出现的a,b,c,j,i都会被删除 eng shngh de 3)用tr压缩字符,可以压缩输入中重复的字符。但是注意:必须是连续重复的单个字符!! [root@kevin ~]# echo "thissss is a text linnnnnnne." | tr -s ' sn' this is a text line. [root@kevin ~]# echo "123123123"|tr -s "123" 123123123 [root@kevin ~]# echo "123333344444"|tr -s "3"|tr -s "4" 1234 4)巧妙使用tr命令进行数字相加操作: [root@kevin ~]# echo 1 2 3 4 5 6 7 8 9 | xargs -n1 | echo $[ $(tr '\n' '+') 0 ] 45 [root@kevin ~]# echo "10 11 12 13 14"|xargs -n1|echo $[ $(tr '\n' '+') 0 ] 60 -------------------------------------------------------------------------------------- <<<<< "xargs -n[数字]"用法 >>>>> [root@kevin ~]# echo "aa bb cc 1 2 3"|xargs aa bb cc 1 2 3 [root@kevin ~]# echo "aa bb cc 1 2 3"|xargs -n1 aa bb cc 1 2 3 [root@kevin ~]# echo "aa bb cc 1 2 3"|xargs -n2 aa bb cc 1 2 3 [root@kevin ~]# echo "aa bb cc 1 2 3"|xargs -n3 aa bb cc 1 2 3 [root@kevin ~]# echo "aa bb cc 1 2 3"|xargs -n4 aa bb cc 1 2 3 -------------------------------------------------------------------------------------- 5)把文件中的数字0-9替换为a-j (都是10个字符,一一对应) [root@kevin ~]# cat filename |tr [0-9] [a-j] 6)删除文件file中出现的换行'\n'、制表'\t'字符 [root@kevin ~]# cat filename | tr -d "\n\t" 删除换行符等 [root@kevin ~]# echo -e "asdf\n123"|tr -d "\n" asdf123 [root@kevin ~]# echo -e "asdf\n\r\t123"|tr -d "\n\r\t" asdf123 7)删除空行 [root@kevin ~]# cat file | tr -s "\n" > new_file [root@kevin ~]# cat test beijing shanghai abcde 123123 [root@kevin ~]# cat test| tr -s "\n" beijing shanghai abcde 123123 8)把路径变量中的冒号":",替换成换行符"\n" [root@kevin ~]# echo "a:b:c:d"|tr ":" "\n" a b c d [root@kevin ~]# echo "/www/data/haha/html"|tr "/" ":" :www:data:haha:html 9)字符集补集!!! (tr -d -c) set1的补集意味着从这个集合中包含set1中没有的所有字符。 最典型的用法就是从输入文本中将不在补集中的所有字符全部删除!!!! [root@kevin ~]# echo "hello 123 world " | tr -d -c '0-9 \n' 123 [root@kevin ~]# echo "wang beijinganhui"|tr -d -c 'a-m\n' agbeijigahi [root@kevin ~]# echo "123WRsdf"|tr -d -c "a-z\n" sdf [root@kevin ~]# echo "123@#QWEanhui"|tr -d -c "anhui\n" anhui 10) 删除Windows文件"造成"的'^M'字符 [root@kevin ~]# cat file | tr -s "\r" "\n" > new_file 或 [root@kevin ~]# cat file | tr -d "\r" > new_file 11) 使用tr命令生成固定长度的随机密码!!!! [root@kevin ~]# head /dev/urandom | tr -dc A-Za-z0-9 | head -c 20 koR3ZjLekd6Xujfeslu1 [root@kevin ~]# head /dev/urandom | tr -dc A-Za-z0-9 | head -c 20 vgOKX39zeQSWP6KD6rjd [root@kevin ~]#
11)shell的 read 输入用法
read命令用于接收键盘或其它文件描述符的输入。
read命令接收标准输入(键盘)的输入,或其他文件描述符的输入(后面在说)。得到输入后,read命令将数据放入一个标准变量中。
read 命令格式如下: #read [选项] [变量名] 选项: -p: 指定要显示的提示 -s: 静默输入,输入的数据不显示出来。实际上输入数据是显示的,只是read命令将文本颜色设置成与背景相同的颜色!!!一般用于密码输入。 -n: 指定输入的字符长度最大值。如果超出了,就默认使用前面最大长度值的字符! -d '字符': 输入结束符,当你输入的内容出现这个字符时,立即结束输入 -t N: 超出N秒没有进行输入,则自动退出。 需要注意: 变量名可以自定义。如果不指定变量名,则会把输入保存到默认变量REPLY中; 如果只提供了一个变量名,则将整个输入行赋予该变量; 如果提供了一个以上的变量名,则输入行分为若干字,一个接一个地赋予各个变量,而命令行上的最后一个变量取得剩余的所有字; [root@kevin ~]# read -p "请输入你的内容: " haha;echo "${haha}" 请输入你的内容: 123456 123456 加了-s参数,即静默输入。输入数据的时候看不到。默认不换行! [root@kevin ~]# read -sp "请输入你的内容: " haha;echo "${haha}" 请输入你的内容: 123456 [root@kevin ~]# read -sp "请输入你的内容: " haha;echo -e "\n${haha}" 请输入你的内容: 123456 下面来看看下面的一些示例: 1)基本读取 [root@kevin ~]# cat test.sh #!/bin/bash echo -n "Enter your name:" #参数-n的作用是不换行,echo默认是换行! read name #从键盘输入 echo "hello $name,welcome to my program" #显示信息 exit 0 #退出shell程序。 [root@kevin ~]# sh test.sh Enter your name:beijing hello beijing,welcome to my program 2)使用read输入 (read -p) 由于read命令提供了-p参数,允许在read命令行中直接指定一个提示。 上面脚本可以改进为: [注意:read命令后必须加它自己的参数,如果不加参数,则不执行!] [root@kevin ~]# cat test.sh #!/bin/bash read -p "Enter your name:" name echo "hello $name, welcome to my program" exit 0 [root@kevin ~]# sh test.sh Enter your name:beijing hello beijing, welcome to my program 需要注意: 在上面read后面的变量只有name一个,也可以有多个,多个变量使用空格隔开,这时如果输入多个数据,则第一个数据给第一个变量,第二个数据给第二个变量; 如果输入数据个数过多,则最后所有的值都给最后那个变量!! 如果输入数据太少,则对应不到数据的变量为空! 在read命令行中也可以不指定变量,如果不指定变量,那么read命令会将接收到的数据放置在环境变量REPLY中。 3)read后面可以跟多个变量 [root@kevin ~]# cat test.sh #!/bin/bash read -p "Enter your name:" name age city echo "hello ${name},${age},${city}, welcome to my program" exit 0 [root@kevin ~]# sh test.sh Enter your name:bobo 26 beijing hello bobo,26,beijing, welcome to my program 如果输入数据多余变量,则多余的数据都给最后那个变量!! [root@kevin ~]# sh test.sh Enter your name:yang 26 shanghai huoqiu haha hello yang,26,shanghai huoqiu haha, welcome to my program 如果输入数据少于变量,则对应不到数据的变量为空! [root@kevin ~]# sh test.sh Enter your name:yang 26 hello yang,26,, welcome to my program [root@kevin ~]# sh test.sh Enter your name:yui hello yui,,, welcome to my program 4)不跟变量,默认使用${REPLY} 如果read后面不指定变量,则read命令会将接收到的数据放置在环境变量REPLY中!!!! 环境变量REPLY中包含输入的所有数据,可以像使用其他变量一样在shell脚本中使用环境变量REPLY,即${REPLY} [root@kevin ~]# cat test.sh #!/bin/bash read -p "Enter your name:" echo "${REPLY}是输入的内容" exit 0 [root@kevin ~]# sh test.sh Enter your name:zhangyang is 27,very nice!! zhangyang is 27,very nice!!是输入的内容 5)read的计时输入 (read -t) 使用read命令存在着潜在危险,脚本很可能会停下来一直等待用户的输入。 如果无论是否输入数据脚本都必须继续执行,那么可以使用-t选项指定一个计时器。 -t选项指定read命令等待输入的秒数。当计时满时,read命令返回一个非零退出状态; [root@kevin ~]# cat test.sh #!/bin/bash if read -t 5 -p "please enter your name:" name then echo "hello $name ,welcome to my script" else echo "sorry,too slow" fi exit 0 [root@kevin ~]# sh test.sh please enter your name:zhangtianba hello zhangtianba ,welcome to my script [root@kevin ~]# sh test.sh please enter your name:sorry,too slow 即超出5s没有输入内容,脚本就自动结束! 6)read的计算字符长度输入 (read -n) 除了输入时间计时,还可以设置read命令计数输入的字符。当输入的字符数目达到预定数目时,自动退出,并将输入的数据赋值给变量。 [root@kevin ~]# cat test.sh #!/bin/bash read -n1 -p "Do you want to continue [Y/N]?" answer case $answer in Y | y) echo -e "\nfine ,continue";; N | n) echo -e "\nok,good bye";; *) echo -e "\nerror choice";; esac exit 0 [root@kevin ~]# sh test.sh Do you want to continue [Y/N]?y fine ,continue [root@kevin ~]# sh test.sh Do you want to continue [Y/N]?N ok,good bye [root@kevin ~]# sh test.sh Do you want to continue [Y/N]?A error choice 注意: 该例子使用了-n选项,后接数值1,指示read命令只要接受到一个字符就退出。 只要按下一个字符进行回答,read命令立即接受输入并将其传给变量。无需按回车键。 上面脚本中要在echo语句中添加换行符"\n",否则执行脚本,在输入内容后会将echo的内容和read提示信息放在一行! echo需要加上参数"-e"才能将后面引号内的特殊字符"\n"生效!否则就当普通字符处理了! 如果输入的字符超过了设定的最大字符长度,则就默认使用前面最大长度值的字符! [root@kevin ~]# cat test.sh #!/bin/bash read -n5 -p "please input name:" name echo "需要输入的名字是:${name}" 没有超过设定的最大字符长度,这时候会自动换行! [root@kevin ~]# sh test.sh please input name:bao 需要输入的名字是:bao 超过了设定的最大字符长度。 本来输入的是zhangzihua,但是默认使用了前面5个字符:zhang 超过后,不会自动换行! [root@kevin ~]# sh test.sh please input name:zhang需要输入的名字是:zhang 改进下,解决不换行问题: [root@kevin ~]# cat test.sh #!/bin/bash read -n5 -p "please input name:" name echo -e "\n需要输入的名字是:${name}" 这样,输入字符超过设定的最大字符长度时,就会自动换行了! [root@kevin ~]# sh test.sh please input name:zhang 需要输入的名字是:zhang 再来改进下脚本: [root@kevin ~]# cat test.sh #!/bin/bash read -n5 -p "please input name:" name len=$(expr length "${name}") #或者使用len=`echo ${#name}` if [ ${len} -lt 5 ];then #注意第一个逻辑判断中的len变量不能等于设置的最大长度值,否则下面就不会执行。 echo "输入的名字为:${name}" #当不超过设定的字符长度时,会自动换行 else echo -e "\n输入的名字长度超过5,当前名字为${name}" exit 0 fi [root@kevin ~]# sh test.sh please input name:bao 输入的名字为:bao [root@kevin ~]# sh test.sh please input name:hangu 输入的名字长度超过5,当前名字为hangu 7)read的静默输入。(read -s) 有时会需要脚本用户输入,但不希望输入的数据显示在监视器上。典型的例子就是输入密码,当然还有很多其他需要隐藏的数据。 -s选项能够使read命令中输入的数据不显示在监视器上(实际上,数据是显示的,只是read命令将文本颜色设置成与背景相同的颜色!!!)。 [root@kevin ~]# cat test.sh #!/bin/bash read -s -p "Enter your password: " pass echo -e "\nyour password is $pass" exit 0 静默输入,即输入的数据是看不到的。 [root@kevin ~]# sh test.sh Enter your password: your password is 123456 8)read读取文件!!!!(cat filename | while read line) 可以使用read命令读取Linux系统上的文件。 每次调用read命令都会读取文件中的"一行"文本!! 当文件没有可读的行时,read命令将以非零状态退出。 读取文件的关键是如何将文本中的数据传送给read命令?? 最常用的方法:对文件使用cat命令并通过管道将结果直接传送给包含read命令的while命令 [root@kevin ~]# cat test.txt wangbo is a boy! beijing is good! abc 123 sahdfksfah asf#$$!QA [root@kevin ~]# cat test.sh #!/bin/bash count=1 cat test.txt | while read line #cat命令的输出作为read命令的输入,read读到的值放在line中。line为读取文件行内容的变量 do echo ${line}|grep -w "beijing" >/dev/null 2>&1 if [ $? -eq 0 ];then echo "想要的是:${line}" else echo "Line ${count}:${line}" #读取的内容默认为变量${line} count=$[ ${count} + 1 ] #注意中括号中的空格。 fi done echo "finish" exit 0 [root@kevin ~]# sh test.sh Line 1:wangbo is a boy! 想要的是:beijing is good! Line 2:abc 123 sahdfksfah Line 3:asf#$$!QA finish
12)shell的 case 用法
case语句还是很好理解的,在shell编程中,if语句有它的语法,函数也有它的语法,那么在shell编程中的case语句也是有它的语法的,语法格式如下: case ${变量名} in 赋值1) 执行指令1 ;; # 每一个选择都以双;;结束。(需要注意:;;相当于break语句) 赋值2) 执行指令2 ;; 赋值3) 执行指令3 ;; *) # *未匹配到相符的其他值 执行其他指令 ;; esac 示例一 当命令行参数是 1 时,输出 "周一", 是 2 时,就输出"周二", 其它情况输出 "其他" --------------------------------------------------------------------- [root@localhost ~]# cat test.sh #!/bin/bash case $1 in 1) echo "周一" ;; 2) echo "周二" ;; *) echo "其他" ;; esac [root@localhost ~]# sh test.sh 1 周一 [root@localhost ~]# sh test.sh 2 周二 [root@localhost ~]# sh test.sh 3 其他 [root@localhost ~]# sh test.sh 4 其他 [root@localhost ~]# sh test.sh 其他 示例二 shell脚本中case选择语句可以结合read指令实现比较好的交互应答操作,case接收到read指令传入的一个或多个参数,然后case根据参数做选择操作。 --------------------------------------------------------------------- 1) 案例1 [root@localhost ~]# cat test.sh #!/bin/bash echo "Please enter A,B,C" read letter #上面两句可以改进为: #read -p "Please enter A,B,C: " letter case $letter in A|a) echo "you entered A" ;; B|b) echo "you entered B" ;; C|c) echo "you entered C" ;; *) echo "Not in A,B,C" ;; esac [root@localhost ~]# sh test.sh Please enter A,B,C A you entered A [root@localhost ~]# sh test.sh Please enter A,B,C b you entered B [root@localhost ~]# sh test.sh Please enter A,B,C C you entered C [root@localhost ~]# sh test.sh Please enter A,B,C w3 Not in A,B,C [root@localhost ~]# sh test.sh 2) 案例二:查看系统资源使用情况 [root@localhost ~]# cat test.sh #!/bin/bash echo "check system run status" echo "show CPUinfo: C/c " echo "show Memery used: M/m " echo "show Disk use status: D/n " echo "show System user login: U/n " echo "show System load average:L/l" echo "show System Ip address: I/i" read_input () { read -t 10 -p "please Input C/M/D/U/L/I : " char } show_status () { case $char in C | c ) cat /proc/cpuinfo | grep -o -i 'model name.*' ;; M | m ) free -m ;; D | d ) df -h ;; U | u ) w ;; L | l ) top | head -1 | cut -d " " -f 11-15 ;; I | i ) ifconfig | grep -o "[0-9.]\{7,\}" | head -1 ;; * ) echo "The characters you have entered are wrong. Please look at the hints" ;; esac } for i in $( seq 1 10) #呼应前面"read -t 10"中的10秒钟要输入内容的限制 do read_input show_status if [ $i -eq 10 ]; then echo "已经到达查询的最大次数,脚本退出;" fi done [root@localhost ~]# sh test.sh check system run status show CPUinfo: C/c show Memery used: M/m show Disk use status: D/n show System user login: U/n show System load average:L/l show System Ip address: I/i please Input C/M/D/U/L/I : c model name : Intel(R) Xeon(R) CPU E5-2640 v4 @ 2.40GHz model name : Intel(R) Xeon(R) CPU E5-2640 v4 @ 2.40GHz model name : Intel(R) Xeon(R) CPU E5-2640 v4 @ 2.40GHz model name : Intel(R) Xeon(R) CPU E5-2640 v4 @ 2.40GHz please Input C/M/D/U/L/I : m total used free shared buff/cache available Mem: 7815 250 5826 56 1738 5102 Swap: 2047 0 2047 please Input C/M/D/U/L/I : d Filesystem Size Used Avail Use% Mounted on devtmpfs 3.9G 0 3.9G 0% /dev tmpfs 3.9G 0 3.9G 0% /dev/shm tmpfs 3.9G 57M 3.8G 2% /run tmpfs 3.9G 0 3.9G 0% /sys/fs/cgroup /dev/mapper/centos-root 20G 3.2G 17G 16% / /dev/mapper/centos-data 78G 33M 78G 1% /data /dev/xvda1 197M 166M 32M 85% /boot tmpfs 782M 0 782M 0% /run/user/0 please Input C/M/D/U/L/I : u 11:07:27 up 31 days, 17:24, 2 users, load average: 0.00, 0.01, 0.05 USER TTY FROM LOGIN@ IDLE JCPU PCPU WHAT root tty1 30Oct19 31days 0.03s 0.03s -bash root pts/0 172.16.198.22 09:57 7.00s 0.18s 0.00s w please Input C/M/D/U/L/I : l load average: 0.00, 0.01, please Input C/M/D/U/L/I : i 172.16.60.236 please Input C/M/D/U/L/I : a The characters you have entered are wrong. Please look at the hints please Input C/M/D/U/L/I : The characters you have entered are wrong. Please look at the hints please Input C/M/D/U/L/I : The characters you have entered are wrong. Please look at the hints please Input C/M/D/U/L/I : The characters you have entered are wrong. Please look at the hints 已经到达查询的最大次数,脚本退出; 案例3: 有时经常会使用比较长的if,then,else来尝试计算一个变量的值,在一组可能的值中寻找特定值。其实这种情况如果使用case遇见就会变得简单的多了! 使用多个if判断来一一核对,代码量比较多,还容易乱。用case的话,能减少代码量,不需要再写出所有的elif语句来不停地检查同一个变量的值了。case命 令会采用列表格式来检查单个变量的多个值。如下两种方法效果一样: if判断语句写法: [root@localhost ~]# cat test.sh #!/bin/bash USER=$1 if [ $USER = "kevin" ];then echo "Welcome $USER,Please enjoy your visit" elif [ $USER = "grace" ];then echo "Welcome $USER,Please enjoy your visit" elif [ $USER = "xiaoru" ];then echo "Special testing account" elif [ $USER = "shibo" ];then echo "Do not forget to logout when you're done" else echo "Sorry, you are not allowed here" fi case语句写法: [root@localhost ~]# cat test.sh #!/bin/bash USER=$1 case ${USER} in kevin|grace) echo "Welcome $USER,Please enjoy your visit";; xiaoru) echo "Special testing account";; shibo) echo "Do not forget to logout when you're done";; *) echo "Sorry, you are not allowed here";; esac 示例三: case语句经常会使用在应用服务的一键部署脚本中。 比如:https://www.cnblogs.com/kevingrace/p/6086426.html --------------------------------------------------------------------- 示例四: 结合shell的"function函数+if逻辑判断+case选择语句 --------------------------------------------------------------------- [root@localhost ~]# cat test.sh #!/bin/bash function OPS(){ #定义一个OPS的函数 cat << kevin_test 1.北京 2.上海 3.深圳 kevin_test } OPS #调用CDAN函数 read -p "请输入您想要去的地方: " WHERE #输入一条提示,然后把用户输入的字符串赋值给变量WHERE expr ${WHERE} + 1 >/dev/null 2>&1 #使用数值运算命令expr来确定用户输入的是否是数值 if [ "$?" -ne 0 ];then #如果用户输入的不是数值 echo "请您输入{1|2|3}" exit 1 fi case ${WHERE} in 1) echo "明天去北京!" ;; 2) echo "明天去上海!" ;; 3) echo "明天去深圳!" ;; esac [root@localhost ~]# sh test.sh 1.北京 2.上海 3.深圳 请输入您想要去的地方: 1 明天去北京! 示例五: 再来分享一例shell的case循环用法 --------------------------------------------------------------------- 要求: 输入a|A显示出红色的本机IP 输入b|B显示出绿色的本机磁盘的剩余内存 输入c|C显示出黄色的系统运行时间 输入 q|Q显示出蓝色的直接退出 [root@localhost ~]# cat test.sh #!/bin/bash while true do echo -e " \033[31m A 显示主机ip \033[0m \033[32m B 显示磁盘剩余空间 \033[0m \033[33m C 显示系统运行时间 \033[0m \033[34m Q 退出系统 \033[0m " read -p "请输入你的选择:" char case ${char} in a|A) echo -e "\033[31m `ifconfig eth0 | grep "netmask" | awk '{print $2}'` \033[0m" ;; b|B) echo -e "\033[32m `df -h | awk 'NR==2{print "剩余空间大小为:"$4}'` \033[0m" ;; c|C) echo -e "\033[33m `uptime | awk '{print "系统已经运行了"$3""$4""}'` \033[0m" ;; q|Q) exit 0 ;; *) echo "请输入A/B/C/Q" ;; esac done [root@localhost ~]# sh test.sh A 显示主机ip B 显示磁盘剩余空间 C 显示系统运行时间 Q 退出系统 请输入你的选择:a 172.16.60.238 A 显示主机ip B 显示磁盘剩余空间 C 显示系统运行时间 Q 退出系统 请输入你的选择:b 剩余空间大小为:3.9G A 显示主机ip B 显示磁盘剩余空间 C 显示系统运行时间 Q 退出系统 请输入你的选择:c 系统已经运行了32days, A 显示主机ip B 显示磁盘剩余空间 C 显示系统运行时间 Q 退出系统 请输入你的选择:d 请输入A/B/C/Q A 显示主机ip B 显示磁盘剩余空间 C 显示系统运行时间 Q 退出系统 请输入你的选择:q [root@localhost ~]#
12)Shell的 for、case、while 循环流程控制语句用法
shell作为一种脚本编程语言,同样包含循环、分支等其他程序控制结构,从而轻松完成更加复杂、强大的功能。
编写脚本的思路 1. 明确脚本的功能 2. 编写脚本时会使用到那些命令 3. 把变化的数据使用变量表示 4. 选择适合的流程控制 (选择 、 循环 、分支) 一、使用for循环语句 ======================================================================================================== 在工作中,经常遇到某项任务需要多次执行,而每次执行仅仅是处理对象不一样,其他命令都相同。使用简单的if语句已经难以满足要求, 编写全部代码将困难重重,而for循环语句将很好的解决类似的问题。 for语句的结构 使用for循环语句时,需要指定一个变量及可能的取值列表,针对每一个不同的取值重复执行相同的命令,直到变量值用完退出循环。 for的语法结构: for;do;done 语法格式: for 变量名 in 列表内容 do commands done 或者 for 变量名 in 列表内容 ;do commands done 示例1):使用嵌套循环输出99乘法表 [root@localhost ~]# cat test.sh #!/bin/bash for i in `seq 9` do for j in `seq 9` do [ $j -le $i ] && echo -n "$j x $i = `echo $(($j*$i))` " #如果j 小与等于i才会打印式子。注意后面双引号后面要有一个空格,表示下面执行时各列间的空格。 done echo "" done 注意:外层循环循环行,内层循环循环列。$(())返回的是里面运算结果;echo -n表示不换行 规律: 内层循环的变量<=外层循环的变量 [root@localhost ~]# sh test.sh 1 x 1 = 1 2 x 1 = 2 2 x 2 = 4 3 x 1 = 3 3 x 2 = 6 3 x 3 = 9 4 x 1 = 4 4 x 2 = 8 4 x 3 = 12 4 x 4 = 16 5 x 1 = 5 5 x 2 = 10 5 x 3 = 15 5 x 4 = 20 5 x 5 = 25 6 x 1 = 6 6 x 2 = 12 6 x 3 = 18 6 x 4 = 24 6 x 5 = 30 6 x 6 = 36 7 x 1 = 7 7 x 2 = 14 7 x 3 = 21 7 x 4 = 28 7 x 5 = 35 7 x 6 = 42 7 x 7 = 49 8 x 1 = 8 8 x 2 = 16 8 x 3 = 24 8 x 4 = 32 8 x 5 = 40 8 x 6 = 48 8 x 7 = 56 8 x 8 = 64 9 x 1 = 9 9 x 2 = 18 9 x 3 = 27 9 x 4 = 36 9 x 5 = 45 9 x 6 = 54 9 x 7 = 63 9 x 8 = 72 9 x 9 = 81 示例2):根据IP地址检查主机状态 [root@localhost ~]# vim test.sh #!/bin/bash for NUM in $(seq 1 254) #或者直接"seq 254" 或者 "seq 10 30"用于一段ip do IP=172.16.60.${NUM} # -c表示ping的次数,-i表示时间间隔(秒), ping -c 3 -i 0.2 $IP &> /dev/null if [ $? -eq 0 ];then echo "$IP is up" else echo "$IP id down" fi done 示例3):文件列表循环 [root@localhost ~]# vim test.sh #!/bin/bash cd /etc/ for a in `ls /etc/` do if [ -d $a ] then ls -d $a fi done 二、使用while循环语句 ======================================================================================================== for语句适用于列表对象无规律,且列表来源以固定的场合。而对于要求控制循环次数、操作对象按数字顺序编号、按特定的条件重复操作等情况,则更适合于while循环语句。 while循环:重复测试某个条件,只要条件成立,就重复执行命令,条件不成立,立即退出,自带判断; while语句的结构 使用while循环语句时,可以根据特定的条件反复执行一个命令序列,直到该条件不在满足为止。 需要注意:要避免出现while ture 的死循环!!! 语法格式如下: while 测试命令 do 命令 done 退出while循环体的三种方式: 1. 条件为假退出循环体,继续执行循环体以外的命令; 2. exit退出脚本,循环体外的命令不会执行; 3. break退出脚本中的循环体,继续执行循环体外的命令; 特殊条件表达式: 1. true :当条件表达式为true时,那么代表条件表达式永远成立,为真; 2. false:当条件表达式为false时,那么条件表达式永远为假; 示例1):降序输出10到1 [root@localhost ~]# cat test.sh #!/bin/bash num=10 while [ ${num} -gt 0 ] do echo "${num}" num=$[${num}-1] #或者使用下面的表达式也可以,$(())或$[]都表示返回运算结果 num=$((${num}-1)) done [root@localhost ~]# sh test.sh 10 9 8 7 6 5 4 3 2 1 示例2):批量添加用户 用户名称以kevin_开头,按照数字顺序进行编号 添加10个用户,即kevin_1、kevin_2、...、kevin_10 初始密码均设为123456 [root@VM_16_9_centos ~]# cat test.sh #!/bin/bash UR="kevin_" NUM=1 while [ ${NUM} -le 10 ] do USER=${UR}${NUM} useradd ${USER} echo "123456"|passwd --stdin ${USER} NUM=$((${NUM}+1)) #或者使用let NUM++,效果等同于NUM=$((${NUM}+1))或者NUM=$[${NUM}+1] done 示例3)判断输入的数要是数字 [root@ss-server ~]# cat test.sh #!/bin/bash while : do read -p "Please input a number: " n if [ -z "$n" ];then #空串为真 echo "you need input sth." continue fi n1=`echo $n|sed 's/[0-9]//g'` if [ -n "$n1" ];then #非空串为真 echo "you just only input numbers." continue fi break done echo $n [root@ss-server ~]# sh test.sh Please input a number: 2 2 [root@ss-server ~]# sh test.sh Please input a number: 2a you just only input numbers. Please input a number: you need input sth. Please input a number: 5 5 三、使用case分支语句 ======================================================================================================== case语句主要适用于以下情况: 某个变量存在多种取值,需要对其中的每一种取值分别执行不同的命令序列。与多分支if语句相识,只是if语句需要判断多个不同的条件,而case只是判断一个变量的不同取值 1) case语句的结构如下: case 变量或表达式 in 变量或表达式1) 命令序列1 ;; 变量或表达式2) 命令序列2 ;; ...... *) 默认命令序列 ;; esac 2) case执行流程 1. 首先使用"变量或表达式"的值与值1进行比较,若取值相同则执行值1后的命令序列,直到遇见双分号";;"后跳转至esac,表示分支结束; 2. 若与值1不相匹配,则继续与值2进行比较,若取值相同则执行值2后的命令序列,直到遇见双分号";;"后跳转至esac,表示结束分支; 3. 依次类推,若找不到任何匹配的值,则执行默认模式"*)"后的命令序列,直到遇见esac后结束分支。 3) case执行流程注意事项 1. "变量或表达式"后面必须为单词in,每一个"变量或表达式"的值必须以右括号结束。取值可以为变量或常数。匹配发现取值符合某一模式后,其间所有命令开始执行直至;; 2. 匹配中的值可以是多个值,通过"|"来分隔。 3. 匹配中的值可以是正则。 示例1):编写一个备份,拷贝的交互式脚本 [root@localhost ~]# cat test.sh #!/bin/bash cat <<eof ***************** **1. backup **2. copy **3. quit ***************** eof read -p "input your choose: " OP case $OP in 1|backup) echo "Backup..." ;; 2|copy) echo "Copy..." ;; 3|quit) exit ;; *) echo input error esac [root@localhost ~]# sh test.sh ***************** **1. backup **2. copy **3. quit ***************** input your choose: 1 Backup... [root@localhost ~]# sh test.sh ***************** **1. backup **2. copy **3. quit ***************** input your choose: 2 Copy... 示例2):提示用户输入一个字符,判断出该字符是字母、数字 [root@localhost ~]# cat test.sh #!/bin/bash #read后面跟的变量必须要空格隔开,否则变量无效! read -p "请输入一个字符: " star case ${star} in [a-z]|[A-Z]) echo "输入的是一个字母" ;; [0-9]) echo "输入的是一个数字" ;; *) echo "请输入字母或数字" ;; esac [root@localhost ~]# sh test.sh 请输入一个字符: a 输入的是一个字母 [root@localhost ~]# sh test.sh 请输入一个字符: 3 输入的是一个数字
13)Shell的 function 函数用法
简单的说,Shell函数的作用就是将程序里面多次被调用的代码组合起来,称为函数体,并取一个名字称为(函数名),当需要用到这段代码的时候,就可以直接来调用函数名。 Shell函数是一个脚本代码块,可以对它进行自定义命名,并且可以在脚本中任意位置使用这个函数。 如果想要这个函数,只要调用这个函数的名称就可以了。使用函数的好处在于模块化以及代码可读性强。 一、Shell函数的创建语法 ================================================================================================ 在shell中 if语句有它的语法,for循环也有它的语法,那么shell中的函数,那肯定也有它的语法有以下三种: 函数的创建方法一: function 函数名 () { 指令... return -n } 函数的创建方法二: function 函数名 { 指令... return -n } 函数的创建方法三: 函数名 () { 指令... return -n } 需要注意: 1. 在以上三种函数语法中,前面的funcation 表示声明一个函数! 可以不写 return -n 是指退出函数! 2. 上面最后两种函数创建方法的声明方式效果等价(即函数的创建方法二和函数的创建方法三的效果是一样的)!!!!!!! 3. 如果函数名后面没有跟(),则函数名和"{"之间必须有空格!shell对空格变态的敏感。如果函数名后面有(),则两者之间可以有空格,也可没有空格。 4. 不得声明形式参数。 5. 必须在调用前声明。 6. 无法重载。 7. 后来的声明会覆盖之前的声明。即如果存在相同名称的函数,以最后一个为准! 另外注意:函数名称在当前脚本必须唯一。 二、Shell函数调用的方法 ================================================================================================ 调用方法1:直接指定函数名即可。但一定要注意在声明之后才可以调用函数!!格式如下: 函数名称 调用方法2:调用函数时可以传递参数,函数内部中使用$1、$2......来引用传递的参数。格式如下: 函数名称 参数1 参数2 ...... 需要注意: 1. 其实函数被调用时会被当作一个小脚本来看待,调用时也可以在函数名后跟参数。 2. Shell函数在调用时都不可以加() $1 #调用第一个参数 $2 #调用第二个参数 ... $n #调用第n个参数 $# #返回参数个数n $0 #当前脚本文件名 三、Shell函数的返回值 ================================================================================================ Shell函数运行结束后会有一个退出状态码,可以用$?变量来显示上一条命令/函数执行结束的退出状态码。 当然,shell也为我们提供了return,像其他语言函数中return 一样,不过(整形)返回值必须在0~255之间。 四、Shell函数创建库 ================================================================================================ 与c的头文件类似,在Shell中,也可以定义"库文件",然后再另一个文件中导入。库文件没有特殊声明或者定义,也是脚本文件.sh。 使用库函数的关键在于导入库文件。用source来导入,source实际上会在当前shell上下文中执行命令,从而达到导入效果。 注意:使用"source"或点符号"."都可以导入库文件!(如下示例7) 五、在"~/.bashrc"文件中定义Shell函数 ================================================================================================ 在使用函数的库文件时,如果每次都需要自己去导入定义的库文件会显得很麻烦!那么,我们可不导入直接使用呢?答案是肯定可以的!!! 方法就是在Shell的配置文件的.bashrc中声明该函数,因为每次启动shell都会载入.bashrc文件,所以就实现了"自动导入库文件"!!!(如下示例8) 六、Shell函数使用实例 ================================================================================================ 示例1:直接在调用函数时进行参数传递!可以直接传递具体的变量值! [root@ss-server ~]# cat test.sh #!/bin/bash function kevin(){ echo "welcome to anhui!" } function shibo(){ echo "$1+$2" } grace(){ echo $(($1+$2+$3)) } kevin shibo hello world grace 2 4 5 [root@ss-server ~]# sh test.sh welcome to anhui! hello+world 11 再看一例:调用函数时,也可以直接传递变量。在脚本执行的时候再赋予变量具体的值! [root@ss-server ~]# cat test.sh #!/bin/bash function kevin () { echo "老子想要的是:$1 $2" } kevin $1 $2 [root@ss-server ~]# sh test.sh 房子 车子 老子想要的是:房子 车子 示例2:如果存在相同名称的函数,以最后一个为准!即后来的函数声明会覆盖之前的声明! [root@ss-server ~]# cat test.sh #!/bin/bash function kevin(){ echo "welcome to anhui!" } kevin(){ echo "hello world" } kevin [root@ss-server ~]# sh test.sh hello world 示例3:return返回值 使用return命令来退出函数并返回特定的退出码($?) [root@ss-server ~]# cat test.sh #!/bin/bash function kevin(){ echo "welcome to anhui!" return 2 echo "why reruen" } kevin [root@ss-server ~]# sh test.sh welcome to anhui! 需要注意: return一般是在函数的最后一行,因为一旦执行return命令,该函数后面的命令就不执行了。 return与exit的区别:return和exit都可以返回退出码,但是不同的是,return是退出函数,而exit是退出整个脚本。 示例4:函数值赋给变量 如下方实例中显示,此时的函数就相当于一个命令,需要使用$()或``调用。 [root@ss-server ~]# cat test.sh #!/bin/bash function kevin(){ read -p "请输入内容: " str echo ${str} } bo=$(kevin) #也可以是bo=`kevin` echo "测试结果为${bo}" [root@ss-server ~]# sh test.sh 请输入内容: xinzhongguo 测试结果为xinzhongguo 示例5:外部参数传入函数 前面已经提到过,调用函数可以在后面跟随参数,函数内部可以使用$n的形式调用。 [root@ss-server ~]# cat test.sh #!/bin/bash function kevin_1(){ echo "this is $1" } function kevin_2 { echo "this is $1+$2" echo $# } kevin_3() { echo "this is $1!!" } kevin_1 安徽 kevin_2 上海 北京 kevin_3 /root/pass.list [root@ss-server ~]# sh test.sh this is 安徽 this is 上海+北京 2 this is /root/pass.list!! 示例6:函数的参数 在一个Shell脚本当中: 函数外的参数,函数可以直接调用; 函数内的参数,只要运行过函数,外部也可以直接调用。 [root@ss-server ~]# cat test.sh #!/bin/bash str="hello world" function kevin(){ bo="${str} is very nice!!" } kevin echo "${bo}, 666~" [root@ss-server ~]# sh test.sh hello world is very nice!!, 666~ 示例7:导入库文件 说白了,就是在一个shell脚本中导入另一个脚本,导入脚本中的变量在新脚本中同样有效可用! [root@ss-server ~]# cat /root/haha.sh #!/bin/bash USER=$1 ADDRESS=$2 AGE=$3 function PER(){ if [ ${AGE} -gt 30 ];then echo "来自${ADDRESS}的${USER}是一个大叔!" fi } [root@ss-server ~]# cat test.sh #!/bin/bash #可以使用soirce或.导入库文件 #source /root/haha.sh . /root/haha.sh function TES { YOU=`PER` echo "告诉你!${YOU}" } TES [root@ss-server ~]# sh test.sh 李楠 霍城 38 告诉你!来自霍城的李楠是一个大叔! 示例8:在"~/.bashrc"文件中定义Shell函数 [root@ss-server ~]# cat ~/.bashrc ........ #定义PER函数 USER=$1 ADDRESS=$2 AGE=$3 function PER(){ if [ ${AGE} -gt 30 ];then echo "来自${ADDRESS}的${USER}是一个大叔!" fi } [root@ss-server ~]# cat test.sh #!/bin/bash source ~/.bashrc function TES { YOU=`PER` echo "告诉你!${YOU}" } TES [root@ss-server ~]# sh test.sh 李楠 霍城 38 告诉你!来自霍城的李楠是一个大叔! -------------------------------- 或者: [root@ss-server ~]# cat ~/.bashrc ...... source /root/haha.sh [root@ss-server ~]# cat /root/haha.sh #!/bin/bash USER=$1 ADDRESS=$2 AGE=$3 function PER(){ if [ ${AGE} -gt 30 ];then echo "来自${ADDRESS}的${USER}是一个大叔!" fi } [root@ss-server ~]# cat test.sh #!/bin/bash source ~/.bashrc function TES { YOU=`PER` echo "告诉你!${YOU}" } TES [root@ss-server ~]# sh test.sh 李楠 霍城 38 告诉你!来自霍城的李楠是一个大叔!
14)Shell获取随机数 的 random 用法
1)使用$RANDOM 需要系统支持,通过echo来检测, 打印出一个随机数字,证明当前环境支持$RANDOM,反之为空不支持: [root@bz3aomsmsap1002 ~]# echo $RANDOM 1525 [root@bz3aomsmsap1002 ~]# echo $RANDOM 16218 2)使用/dev/urandom + tr [root@bz3aomsmsap1002 ~]# tr -cd 0-9 </dev/urandom | head -c 8 #取8位随机种子 67128612 [root@bz3aomsmsap1002 ~]# tr -cd 0-9 </dev/urandom | head -c 16 #取12位随机种子 8398375834495017 随机生成密码(如下两种方式都可以,生成16位长度的随机密码) [root@bz3aomsmsap1002 ~]# head /dev/urandom | tr -dc A-Za-z0-9 | head -c 16 gXeLJ5XLAaV26tKJ [root@bz3aomsmsap1002 ~]# tr -dc A-Za-z0-9 </dev/urandom | head -c 16 0PUw4BHWQw8oQ3ai
15)快速去除文件或字符串中的空格(使用sed或tr)
1. 删除字符串中的空格(使用sed或tr) [root@ss-server ~]# echo "aa dd"|sed 's/ //g' aadd [root@ss-server ~]# echo "aa dd"|tr -d " " aadd [root@ss-server ~]# echo " aa b t "|tr -d " " aabt [root@ss-server ~]# echo " root, bin, hook"|tr -d " " root,bin,hook 2. 删除文件中行首空格的命令 # sed 's/^[ \t]*//g' filename #不加-i参数,表示仅仅在当前终端显示命令执行效果。 # sed -i 's/^[ \t]*//g' filename #加-i参数,表示命令执行效果直接为文件生效,即直接在文件中删除行首空格。 命令解释: 第一个/的左边是s表示替换,即将空格替换为空。 第一个/的右边是中括号表示"或",空格或tab中的任意一种。这是正则表达式的规范。 中括号右边是*,表示一个或多个。 第二个和第三个\中间没有东西,表示空 g表示替换原来buffer(缓冲区)中的,sed在处理字符串的时候并不对源文件进行直接处理,先创建一个buffer,但是加g表示对原buffer进行替换 整体的意思是:用空字符去替换一个或多个用空格或tab开头的本体字符串 示例: [root@ss-server ~]# cat test aa bb 123 aa sadf 12313 yy 45 [root@ss-server ~]# cat test|sed 's/^[ \t]*//g' test aa bb 123 aa sadf 12313 yy 45 [root@ss-server ~]# cat test|sed -i 's/^[ \t]*//g' test [root@ss-server ~]# cat test aa bb 123 aa sadf 12313 yy 45 3. 删除文件中行尾空格的命令 # sed 's/[ \t]*$//g' filename #仅仅在当前终端显示执行效果。 # sed -i 's/[ \t]*$//g' filename #直接删除文件中行尾空格。 命令解释: 和上面稍微有些不同是前面删除了^符,在后面加上了美元符。表示删除行尾空格。 4. 删除文件中所有的空格的命令 # sed s/[[:space:]]//g filename # sed 's/[[:space:]]//g' haha # sed -i 's/[[:space:]]//g' haha # cat filename|tr -d " " #使用tr删除所有空格,只是在终端显示里生效,文件里默认并不会生效 示例: [root@ss-server ~]# cat haha 12 3 4 ads ss 12 3123 asdf ok jk 1 [root@ss-server ~]# sed 's/[[:space:]]//g' haha 1234 adsss 123123asdf okjk1 [root@ss-server ~]# cat haha 12 3 4 ads ss 12 3123 asdf ok jk 1 [root@ss-server ~]# sed -i 's/[[:space:]]//g' haha [root@ss-server ~]# cat haha 1234 adsss 123123asdf okjk1 [root@ss-server ~]# cat haha 12 3 4 ads ss 12 3123 asdf ok jk 1 [root@ss-server ~]# cat haha|tr -d " " 1234 adsss 123123asdf okjk1 [root@ss-server ~]# cat haha 12 3 4 ads ss 12 3123 asdf ok jk 1 [root@ss-server ~]# cat haha|tr -d " " > haha.txt && \cp -f haha.txt haha && rm -f haha.txt [root@ss-server ~]# cat haha 1234 adsss 123123asdf okjk1
16)如何根据截取某个字段多少位进行去重?以及连续重复字符去重
一、tr命令加-s参数,表示把连续重复的字符以单独一个字符表示。即压缩字符,但是必须是连续重复的单个字符!注意是连续出现的"单个"字符!! [root@ss-server ~]# echo "111222342133"|tr -s "1" 1222342133 [root@ss-server ~]# echo "111222342133"|tr -s "12" 12342133 [root@ss-server ~]# echo "111222342133"|tr -s "123" 1234213 [root@ss-server ~]# echo "aa966ha34jj9"|tr -s "a" a966ha34jj9 [root@ss-server ~]# echo "aa966ha34jj9"|tr -s "a96" a96ha34jj9 还可以使用正则 [root@ss-server ~]# echo "aaAAF7889HHjkk09"|tr -s "a-zA-Z0-9" aAF789Hjk09 二、根据下面test文件的第一个字段、第二个字段截取前8位进行排序去重 [root@ss-server ~]# cat /root/test 25ds51dd225d86af,20180725115911,22570,20443,120.17138,30.119047 002a51dd225d86af,20180725120017,22570,184680195,120.176506,30.11527 002a51dd225d86af,20180725120058,22290,80489347,120.1810786,30.10003 002a51dd225d86af,20180725120149,22290,80489345,120.1810786,30.10003 002a51dd225d86af,20180725120209,22290,189880577,120.18859,30.093405 102a51dd225d86af,20180725120239,22290,155606668,120.1990471,30.0961 002a51dd225d86af,20180725120303,22290,155606666,120.1990468,30.0961 002a51dd225d86af,20180725120434,22290,193501442,120.220661,30.09723 002a79ded185cb04,20180725125428,22570,185263107,120.1576002,30.1293 002a79ded185cb04,20180726125649,22290,80489347,120.1810786,30.10003 运用的技巧: echo ${var:0:8} 表示从${var}变量的第1个字符(左边的下标从0开始)开始截取,截取的总个数为8! xargs -n2 表示将前面命令的结果按照每行2列显示(从第一行开始算,往下每行2列,直至分配完为止,不够的就是一列) sort|uniq 表示去重,仅仅去掉连续出现的相同记录 1)脚本1:打印test文件的第一个和第二个字段以及自个截取的前面八位字符。 [root@ss-server ~]# cat test_0.sh #!/bin/bash file=$(cat /root/test) while read line do #输出每一行 echo line=${line} #截取一行的第一列 column1=`echo ${line}| cut -d "," -f1` echo ${column1} #输出前8个字符 echo ${column1:0:8} #截取一行的第二列 column2=`echo ${line}| cut -d "," -f2` echo ${column2} #输出前8个字符 echo ${column2:0:8} done <<EOF ${file} EOF [root@ss-server ~]# sh test_0.sh line=25ds51dd225d86af,20180725115911,22570,20443,120.17138,30.119047 25ds51dd225d86af 25ds51dd 20180725115911 20180725 line=002a51dd225d86af,20180725120017,22570,184680195,120.176506,30.11527 002a51dd225d86af 002a51dd 20180725120017 20180725 line=002a51dd225d86af,20180725120058,22290,80489347,120.1810786,30.10003 002a51dd225d86af 002a51dd 20180725120058 20180725 line=002a51dd225d86af,20180725120149,22290,80489345,120.1810786,30.10003 002a51dd225d86af 002a51dd 20180725120149 20180725 line=002a51dd225d86af,20180725120209,22290,189880577,120.18859,30.093405 002a51dd225d86af 002a51dd 20180725120209 20180725 line=102a51dd225d86af,20180725120239,22290,155606668,120.1990471,30.0961 102a51dd225d86af 102a51dd 20180725120239 20180725 line=002a51dd225d86af,20180725120303,22290,155606666,120.1990468,30.0961 002a51dd225d86af 002a51dd 20180725120303 20180725 line=002a51dd225d86af,20180725120434,22290,193501442,120.220661,30.09723 002a51dd225d86af 002a51dd 20180725120434 20180725 line=002a79ded185cb04,20180725125428,22570,185263107,120.1576002,30.1293 002a79ded185cb04 002a79de 20180725125428 20180725 line=002a79ded185cb04,20180726125649,22290,80489347,120.1810786,30.10003 002a79ded185cb04 002a79de 20180726125649 20180726 2)脚本2: 改进下,仅仅获取第一个和第二个字段的前面8位字符 [root@ss-server ~]# cat test_1.sh #!/bin/bash file=$(cat /root/test) while read line do #输出每一行 #echo line=${line} #截取一行的第一列 column1=`echo ${line}| cut -d "," -f1` #echo ${column1} #输出前8个字符 echo ${column1:0:8} #截取一行的第二列 column2=`echo ${line}| cut -d "," -f2` #echo ${column2} #输出前8个字符 echo ${column2:0:8} done <<EOF ${file} EOF [root@ss-server ~]# sh test_1.sh 25ds51dd 20180725 002a51dd 20180725 002a51dd 20180725 002a51dd 20180725 002a51dd 20180725 102a51dd 20180725 002a51dd 20180725 002a51dd 20180725 002a79de 20180725 002a79de 20180726 3)接着继续改进,获取第一个和第二个字段的前面8位字符,并排序去重 [root@ss-server ~]# sh test_1.sh |xargs -n2 25ds51dd 20180725 002a51dd 20180725 002a51dd 20180725 002a51dd 20180725 002a51dd 20180725 102a51dd 20180725 002a51dd 20180725 002a51dd 20180725 002a79de 20180725 002a79de 20180726 [root@ss-server ~]# sh test_1.sh |xargs -n2|sort|uniq 002a51dd 20180725 002a79de 20180725 002a79de 20180726 102a51dd 20180725 25ds51dd 20180725 [root@ss-server ~]# sh test_1.sh |xargs -n2|sort|uniq -u|sed 's/ /,/g' 002a51dd,20180725 002a79de,20180725 002a79de,20180726 102a51dd,20180725 25ds51dd,20180725
17)shell特殊符号:sort排序,wc统计,uniq去重,tee,split
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)zcat 和 zgrep命令用法
服务器端常有很多压缩过的日志文件,当需要查找日志中某些特定信息的时候,为了避免解压文件,可以使用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