Bash Cookbook 学习笔记 【工具】
Read Me
约定格式# 注释:前导的$表示命令提示符 # 注释:无前导的第二+行表示输出 # 例如: $ 命令 参数1 参数2 参数3 # 行内注释 输出_行一 输出_行二 $ cmd par1 par1 par2 # in-line comments output_line1 output_line2 四、工具
grep 搜索字符串在当前路径的所有c后缀文件中,查找printf字符串 $ grep printf *.c both.c: printf("Std Out message.n",argv[0],argc-1); both.c: fprintf(stderr,"Std Error message.n",argc-1); good.c: printf("%s: %d args.n",argc-1); somio.c: // we'll use printf to tell us what we somio.c: printf("open: fd=%dn",iod[i]); $ 当然,也可以像这样,指定不同的搜索路径 $ grep printf ../lib/*.c ../server/*.c ../cmd/*.c */*.c 搜索结果的默认输出格式为“文件名 冒号 匹配行” 可以通过 $ grep -h printf *.c printf("Std Out message.n",argc-1); fprintf(stderr,argc-1); printf("%s: %d args.n",argc-1); // we'll use printf to tell us what we printf("open: fd=%dn",iod[i]); $ 或者,不显示匹配行,而只是用 $ grep -c printf *.c both.c:2 good.c:1 somio.c:2 $ 或者,只是简单地列出(list)含搜索项的文件清单,可以用 $ grep -l printf *.c both.c good.c somio.c 文件清单可视为一个不包含重复项的集合,便于后续处理,比如 $ rm -i $(grep -l 'This file is obsolete' * ) 有时候,只需要知道是否满足匹配,而不关心具体的内容,可以使用 $ grep -q findme bigdata.file $ if [ $? -eq 0 ] ; then echo yes ; else echo nope ; fi nope $ 也可以把输出重定向进/dev/null位桶,一样实现静默的效果。位桶(bit bucket)就相当于“位的垃圾桶”,一个有去无回的比特黑洞 $ grep findme bigdata.file >/dev/null $ if [ $? -eq 0 ] ; then echo yes ; else echo nope ; fi nope $ 经常,你更希望搜索时忽略(ignore)大小写,这时可以用 $ grep -i error logfile.msgs # 匹配ERROR,error,eRrOr.. 很多时候,搜索范围并不是来自文件,而是管道 $ 命令1 | 命令2 | grep 举个例。将gcc编译的报错信息从标准错误(STDERR,2)重定向到标准输出(STDOUT,1),再通过管道传给grep进行筛选 $ gcc bigbadcode.c 2>&1 | grep -i error 多个grep命令可以串联,以不断地缩小搜索范围 grep 关键字1 | grep 关键字2 | grep 关键字3 比如,与 $ grep -i 李 专辑/* ... 世界上有太多人姓李 ... $ !! | grep -i 四 grep -i 李 专辑/* | grep -i 四 ... 叫李四的也不少 ... $ !! | grep -i "饶舌歌手" grep -i 李 专辑/* | grep -i 四 | grep -i "饶舌歌手" 李四,饶舌歌手 <lsi@noplace.org>
$ grep -i dec logfile | grep -vi decimal | grep -vi decimate 按关键字'dec'匹配,但不要匹配'decimal',也不要匹配'decimate'。因为这里的dec意思是december ... error on Jan 01: not a decimal number error on Feb 13: base converted to Decimal warning on Mar 22: using only decimal numbers error on Dec 16 : 匹配这一行就对了 error on Jan 01: not a decimal number ... 像上边这样“要匹配这个,但不要包含那个”...是非常笨重的,就像在纯手工地对密码进行暴力破解。 仔细观察规律,匹配关键字的模式,才是正解。 $ grep 'Dec [0-9][0-9]' logfile 0-9匹配dec后边的一位或两位数日期。如果日期是一位数,syslog会在数字后加个空格补齐格式。所以为了考虑进这种情况,改写如下, $ grep 'Dec [0-9 ][0-9]' logfile 对于包含空格等敏感字符的表达式,总是用单引号'...'对表达式进行包裹是个良好的习惯。这样可以避免很多不必要的语法歧义。 当然,用反斜杠 $ grep Dec [0-9 ][0-9] logfile 结合正则表达式(Re),可以实现更复杂的匹配。 常用的正则表达式【简表】. # 任意一个字符 .... # 任意四个字符 A. # 大写A,跟一个任意字符 * # 零个或任意一个字符 A* # 零个或任意多个大写A .* # 零个或任意个任意字符,甚至可以是空行 ..* # 至少包含一个空行以外的任意字符 ^ # 行首 $ # 行尾 ^$ # 空行 # 保留各符号的本义 [字符集合] # 匹配方括号内的字符集合 [^字符集合] # 不匹配方括号内的字符集合 [AaEeIiOoUu] # 匹配大小写元音字母 [^AaEeIiOoUu] # 匹配不包括大小写元音的任意字母 {n,m} # 重复,最少n次,最多m次 {n} # 重复,正好n次 {n,} # 重复,至少n次 A{5} # AAAAA A{5,} # 至少5个大写A 举个实用的例子:匹配社保编号 SSN $ grep '[0-9]{3}-{0,1}[0-9]{2}-{0,1}[0-9]{4}' datafile 这么长的正则,写的人很爽,读的人崩溃。所以也被戏称为Write Only. 为了写给人看,一定要加个注释的。 为了讲解清楚,来做个断句 [0-9]{3} # 先匹配任意三位数 -{0,1} # 零或一个横杠 [0-9]{2} # 再跟任意两位数 -{0,1} # 零或一个横杠 [0-9]{4} # 最后是任意四位数 还有一些z字头工具,可以直接对压缩文件进行字符串的查找和查看处理。比如zgrep,zcat,gzcat等。一般系统会预装有 $ zgrep 'search term' /var/log/messages* 特别是zcat,会尽可能地去还原破损的压缩文件,而不像其他工具,对“文件损坏”只会一味的报错。 $ zcat /var/log/messages.1.gz awk 变色龙
作为(最)强大的文本处理引擎,awk博大精深,一本书都讲不完。这里只能挑些最常用和基础的内容来讲。 首先,以下三种传文件给awk的方式等效: $ awk '{print $1}' 输入文件 # 作为参数 $ awk '{print $1}' < 输入文件 # 重定向 $ cat 输入文件 | awk '{print $1}' # 管道 对于格式化的文本,比如
$ ls -l total 4816 drwxr-xr-x 4 jimhs jimhs 4096 Nov 26 02:10 backup drwxr-xr-x 3 jimhs jimhs 4096 Nov 24 08:20 bash ... $ $ ls -l| awk '{print $1,$NF}' # 打印第一行和最后一行 total 4816 drwxr-xr-x backup drwxr-xr-x bash $ 注意到,第五列是文件大小,可以对其大小求和,并作为结果输出 $ ls -l | awk '{sum += $5} END {print sum}'
但实际上,严格来讲,应该对这样的特例做预处理,即,删掉该行。 首先想到的:可以用之前介绍grep时的 $ ls -l | grep -v '^total' | awk '{sum += $5} END {print sum}' 另一种方法是:在awk脚本内,先用正则定位到total行(第一行),找到后立即执行紧跟的{getline}句块,因为getline用来接收新的输入行,这样就顺利跳过了total行,而进入了{sum += $5}句块。 $ ls -l | awk '/^total/{getline} {sum += $5} END {print sum}' 也就是说,作为awk脚本,各结构块摆放的顺序是相当重要的。 一个完整的awk脚本可以允许多个大括号{}包裹的结构。END前缀的结构体,表示待其他所有语句执行完后,执行一次。与之相对的,是BEGIN前缀,会在任何输入被读取之前执行,通常用来进行各种初始化。 作为可编程的语言,awk部分借用了c语言的语法。 可以像这样,将结构写成多行 $ awk '{ > for (i=NF; i>0; i--) { > printf "%s ",$i; > } > printf "n" > }' 也可以把整个结构体塞进一行内 $ awk '{for (i=NF; i>0; i--) {printf "%s ",$i;} printf "n" }' 以上脚本,将各列逆序输出: drwxr-xr-x 4 jimhs jimhs 4096 Nov 26 02:10 backup 变成了 backup 02:10 26 Nov 4096 jimhs jimhs 4 drwxr-xr-x 对于复杂的脚本,可以单独写成一个.awk后缀的文件 # # 文件名: asar.awk # NF > 7 { # 触发计数语句块的逻辑,即该行的项数要大于7 user[$3]++ # ls -l的第3个变量是用户名 } END { for (i in user) { printf "%s owns %d filesn",i,user[i] } } 然后通过 $ ls -lR /usr/local | awk -f asar.awk bin owns 68 files albing owns 1801 files root owns 13755 files man owns 11491 files $ 这个脚本asar.awk,递归地遍历/usr/local路径,并统计各用户名下的文件数量。 注意:其中用于自增时计数的user[]数组,它的索引是$3,即用户名,而不是整数。这样的数组也叫作关联数组(associative arrays) ,或称为映射(map),或者是哈希表(hashes)。 至于怎么做的关联、映射、哈希,这些技术细节,awk都在幕后自行处理了。 这样的数组,肯定是无法用整数作为索引去遍历了。 所以,awk为此专门定制了一条优雅的for...in...的语法 for (i in user) 这里,i会去遍历整个关联数组user,本例是[bin,albing,man,root]。再强调一下,重点是会遍历“整个”。至于遍历“顺序”,你没法事先指定,也没必要关心。 下边的hist.awk脚本,在asar.awk的基础上,加了格式化输出和直方图的功能。也借这个稍复杂的例子,说明awk脚本中函数的定义和调用: # # 文件名: hist.awk # function max(arr,big) { big = 0; for (i in user) { if (user[i] > big) { big=user[i];} } return big } NF > 7 { user[$3]++ } END { # for scaling maxm = max(user); for (i in user) { #printf "%s owns %d filesn",user[i] scaled = 60 * user[i] / maxm ; printf "%-10.10s [%8d]:",user[i] for (i=0; i<scaled; i++) { printf "#"; } printf "n"; } } 本例中还用到了printf的格式化输出,这里不展开说明。 awk内的算术运算默认都是浮点型的,除非通过调用內建函数int(),显示指定为整型。 本例中做的是浮点运算,所以,只要变量scaled不为零,for循环体就至少会执行一次,类似下边的"bin"一行,虽然寥寥68个文件,也还是会显示一格# $ ls -lR /usr/local | awk -f hist.awk bin [ 68]:# albing [ 1801]:####### root [ 13755]:################################################## man [ 11491]:########################################## $ 至于各用户名输出时的排列顺序,如前所述,是由建立哈希时的内在机制决定的,你无法干预。 如果非要干预(比如希望按字典序,或文件数量)排列的话,可以这样实现:将脚本结构一分为二,将第一部分的输出先送给sort做排序,然后再通过管道送给打印直方图的第二部分。 最后,再通过一个小例子,结束awk的介绍。 这个简短的脚本,打印出包含关键字的段落: $ cat para.awk /关键字/ { flag=1 } { if (flag == 1) { print $0 } } /^$/ { flag=0 } $ $ awk -f para.awk < 待搜索的文件 段落(paragraph),是指两个空行之间所有的文本。空行表示段落的结束
/^[:blank:]*$/ (编辑:李大同) 【声明】本站内容均来自网络,其相关言论仅代表作者个人观点,不代表本站立场。若无意侵犯到您的权利,请及时与联系站长删除相关内容! |