Shell编程实例——中级shell工具(一)

Shell 编程-Expect 使用一看就会 今天给伙伴们分享一下Shell 编程-Expect 使用,希望看了有所收获。 阅读详情

1、在文件中查找字符串

你需要在一个或多个文件中查找所有出现过的某个字符串。

grep 命令可以搜索文件,查找指定的字符串。

$ grep printf *.c
both.c:    printf("Std Out message.\n", argv[0], argc-1);
both.c:    fprintf(stderr, "Std Error message.\n", argv[0], argc-1);
good.c:    printf("%s: %d args.\n", argv[0], argc-1);
somio.c:        // we'll use printf to tell us what we
somio.c:        printf("open: fd=%d\n", iod[i]);
$

在这个例子中,我们搜索的文件全都位于当前目录下。因此,我们只使用了简单的 shell 模式 *.c 来匹配以 .c 结束的文件,并没有在文件名前再添加路径。

但并非所有待搜索的文件都老老实实地待在当前目录下。但因为 shell 并不在意你输入多少路径名,所以我们也可以这么写:

grep printf ../lib/*.c ../server/*.c ../cmd/*.c */*.c

如果待搜索的文件不止一个,grep 会在输出前加上文件名以及冒号,然后是该文件中包含 grep 搜索内容的文本。

搜索会匹配指定的字符串,所以含有“fprintf”的行会被返回,因为“fprintf”包含“printf”​。

grep 的第一个(非选项)参数可以是一个简单的字符串,正如本例中所示,也可以是更复杂的正则表达式(regexp)​。正则表达式不同于 shell 的模式匹配,尽管两者有时看起来差不多。模式匹配的功能无比强大,你可能会发现自己对其的依赖已经到了开始将“grep”作为动词的程度,只想随时随地都能用到它,比如“希望能在我的书桌上 grep 到你想要的论文”​。

你可以用命令行选项控制 grep 的输出形式。如果不想看到特定文件名,指定 -h 选项即可。

$ grep -h printf *.c
  printf("Std Out message.\n", argv[0], argc-1);
  fprintf(stderr, "Std Error message.\n", argv[0], argc-1);
  printf("%s: %d args.\n", argv[0], argc-1);
      // we'll use printf to tell us what we
      printf("open: fd=%d\n", iod[i]);
$

如果不想看到文件中包含指定字符串的那些行,只想知道匹配到了几次,可以使用 -c 选项。

$ grep -c printf *.c
both.c:2
good.c:1
somio.c:2
$

常见的错误是忘记指定 grep 的输入,例如grep myvar。这种情况下,grep 会认为你要从STDIN 提供输入,而你以为它会读取文件,于是grep 就干等着,无所事事。​(其实它是在等待着你从键盘输入。​)当你要搜索大量数据,预计会花费很长时间时,很难分辨出这种尴尬的情况。

2、只显示包含搜索结果的文件名

你需要找出包含特定字符串的文件,但是不想看到其所在的文本行,只用输出文件名即可。

用 grep 的 -l 选项仅显示文件名即可。

$ grep -l printf *.c
both.c
good.c
somio.c
$

如果在一个文件中找到了多次匹配,grep 仍然只输出该文件名一次。如果没有找到匹配,则什么都不输出。

由于这些文件包含了你要查找的字符串,如果想据此构建一个待处理文件的列表,选项 -h 就能派上用场了。将grep 命令放进 $(),然后就可以在命令行上使用这些文件名了。

例如,要想删除包含字符串“This file is obsolete”的文件,可以使用以下这样的 shell 命令组合。

rm -i $(grep -l 'This file is obsolete' * )

我们给 rm 加上了 -i 选项,以便在删除每个文件前都先询问你。考虑到该命令组合的威力,这显然是一种更安全的处理方式。

bash 对 * 进行扩展,以匹配当前目录(但并不会深入子目录)下的所有文件并将其作为 grep 的参数。grep 生成包含指定字符串的文件列表。最后,rm 会删除该列表中的所有文件。

3、了解搜索是否成功

你想要知道某个文件是否包含指定的字符串,而且只关心“有”还是“没有”​。

使用 grep 的 -q(quiet)选项。或者,考虑到最大的可移植性,简单将输出重定向到 /dev/null 丢弃。不管用哪种方法,你想知道的答案就在 bash 的返回状态变量 $? 中,因此可以像下面这样将其用于 if 语句:

$ if grep -q findme bigdata.file ; then echo yes ; else echo nope ; fi
nope
$

在 shell 脚本中,通常并不需要输出搜索结果;你只是想知道有没有找到匹配,以便脚本选择相应的分支。

和大多数 Unix/Linux 命令一样,返回值 0 表示命令成功执行完毕。在这个例子中,成功的意思就是在指定文件(这里只搜索了一个文件)至少找到了一处匹配。返回值保存在 shell 变量 $? 中,随后可以在 if 语句中用到。

如果我们在 grep -q 之后列出多个文件名,那么 grep 会在找到第一处匹配后就停止搜索。它并不会搜索所有文件,因为你只是想知道有没有找到匹配。要是真想搜遍所有文件,​(为什么这么做?​)那就别用 -q 选项了。

$ if grep findme bigdata.file > /dev/null ; then echo yes ; else echo nope ; fi
nope
$

/dev/null 是一个特殊设备(位桶)​,重定向到此处的输出全都会被丢弃。

如果你希望自己编写的脚本能够适用于 Unix/Linux 系统上的各种 grep 版本,即便某些版本不支持 -q 选项,/dev/null 同样能够发挥作用。

4、不区分大小写搜索

你想要在日志文件中不区分大小写地搜索字符串(如“error”​)​,以匹配该字符串的所有出现。

用 grep 的 -i 选项忽略大小写。

grep -i error logfile.msgs

不区分大小写的搜索能够找出包含“ERROR”​、​“error”​、​“Error”的日志消息,​“ErrOR”和“eRrOr”这样的也不例外。该选项在查找大小写混合的单词时尤其管用,包括可能在句首或电子邮件地址中大写的那些单词。

5、在管道中进行搜索

你要搜索的文本并不在文件中,而是在某个命令或命令管道的输出中。

将输出导入 grep 即可。

some pipeline | of commands | grep

如果没有提供文件名,grep 会从标准输入读取。大多数经过良好设计可用于 shell 脚本编程的实用工具会这么做。这也是它们能够作为 shell 脚本的构建块(buildingblock)发挥作用的原因之一。

如果还想用 grep 搜索上一个命令中的错误信息,那么要确保在管道之前将该命令的错误输出重定向到标准输出。

gcc bigbadcode.c 2>&1 | grep -i error

此命令尝试编译一些假想的代码。将输出通过管道(|)传给 grep 之前,我们先重定向命令的标准错误到标准输出(2>&1)​,然后由 grep 搜索字符串 error,不区分其大小写(-i)​。

对 grep 的结果再执行 grep 也不是不可能的事。为什么要这么做?这是为了进一步缩小搜索范围。假设你想要找出Bob Johnson 的电子邮件地址。

$ grep -i johnson mail/*
... too much output to think about; there are lots of Johnsons in the world ...
$ !! | grep -i robert
grep -i johnson mail/* | grep -i robert
... more manageable output ...
$ !! | grep -i "the bluesman"
grep -i johnson mail/* | grep -i robert | grep -i "the bluesman"
Robert M. Johnson, The Bluesman <rmj@noplace.org>

你可以将第一个 grep 命令再输入一遍,但这个示例还展示了历史运算符 !! 的威力(参见 18.2 节)​。!! 可以重复上一个命令,无须重新输入。然后就像示例那样在 !! 之后添加新的命令。shell 会显示所执行的命令,因此你会看到 !! 替换后的结果。

你可以按照这种方法简单快速地构建一个比较长的 grep管道,查看中间步骤的结果,并决定如何用其他 grep 表达式改善搜索过程。也可以用单个 grep 配合精巧的正则表达式来完成相同任务,不过我们发现以增量方式构建管道要来得更加容易。

6、缩减搜索结果

搜索返回的结果不符合预期,其中包括许多并不需要的内容。

将结果通过管道传给 grep -v 并用表达式描述出你不想看到的内容。

假设你想在日志文件中找出整个 12 月的日志消息。你知道日志文件用字母缩写 Dec 代表 12 月,但不敢肯定总是如此,为了确保找出所有的日志消息,输入下列命令:

grep -i dec logfile

得到的结果却如下所示:

...
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 : the actual message you wanted
error on Jan 01: not a decimal number
...

一种快而糙的解决方案是,将第一次得到的结果通过管道传给另一个 grep,由后者过滤掉所有的“decimal”​。

grep -i dec logfile | grep -vi decimal

将多个 grep 串联在一起(因为前所未见、出乎意料的匹配会不断出现)​,逐步过滤搜索结果,直至满意,这种做法并不鲜见。

grep -i dec logfile | grep -vi decimal | grep -vi decimate

在这种解决方案中,​“糙”是指它可能会漏掉一些你本想留下的 12 月的日志消息:如果消息中含有单词“decimal”​,那么就会被 grep -v 过滤。

如果谨慎使用,-v 选项非常方便,你只需要记住该排除什么就行了。

对于这个例子,更好的解决方案是使用威力更为强大的正则表达式来匹配月份和日期,查找“Dec”以及紧随其后的一个空格和两个数字。

grep 'Dec [0-9][0-9]' logfile

不过,这种写法往往行不通,因为 syslog 会用一个空格填充单数字日期。要想解决这个问题,可以在第一个字符集中加上一个空格。

grep 'Dec [0-9 ][0-9]' logfile

由于存在内嵌空格,同时为了避免 shell 解释方括号,我们在方括号周围加上了单引号。在任何可能会对 shell 造成困惑的地方使用单引号是一个好习惯。我们可以使用反斜线转义空格,如下所示:

grep Dec\ [0-9\ ][0-9] logfile

但是这种写法不太容易看出待搜索的字符串在哪里结束,文件名从哪里开始。

7、搜索更复杂的模式

grep 中的正则表达式提供了更为强大的模式匹配功能,能够满足大部分需求。

正则表达式描述了待匹配字符串的模式。字母字符(或者对于 shell 没有特殊含义的其他字符)只匹配自身。​“A”匹配 A,​“B”匹配 B,这没什么好说的。另一个重要的规则是按位置组合字母,如 AB匹配“AB”​。这看起来也是显而易见的。但是,正则表达式还定义了其他一些特殊字符,它们既可以单独使用,也可以与其他字符结合,从而形成更为复杂的模式。

第一个特殊字符是点号(.)​,它可以匹配任意单个字符。因此,… 可以匹配任意 4 个字符;A. 匹配“A”以及紧随其后的任意单个字符;.A. 匹配任意单个字符,然后是“A”​,接着是任意单个字符(未必和匹配到的第一个字符相同)​。

星号()匹配上一个字符的 0 次或多次出现,因此,A匹配 0 个或多个“A”字符,.* 匹配 0 个或多个任意字符(如“abcdefg”​、​“aaaabc”​、​“sdfgf ;lkjhj”​,甚至是空行)​。

那么 …* 是什么意思?它匹配任意单个字符以及紧随其后的0 个或多个任意字符(也就是一个或多个字符,但不能是空行)​。

说到文本行,脱字符 ^ 匹配文本行的行首位置,而美元符号 $ 匹配文本行的行尾位置,因此 ^$ 匹配空行(行首紧接行尾,两者之间什么都没有)​。

如果想要匹配字面上的点号、脱字符、美元符号或者其他特殊字符,该怎么办呢?在特殊字符前加上反斜线(\)即可。ion. 匹配字母“ion”以及紧随其后的任意单个字符,但是 ion. 则匹配“ion”以及点号(例如,在句末或者其他以尾部点号出现的地方)​。

方括号中的一组字符(如 [abc])匹配其中任意某个字符(如“a”​、​“b”或“c”​)​。如果方括号内的第一个字符是脱字符,则匹配的是不在该字符组中的任意字符。

举例来说,[AaEeIiOoUu] 匹配任意的元音字母,[^AaEeIiOoUu] 则匹配元音字母以外的任意字符。后一种情况不同于匹配辅音字母,因为 [^AaEeIiOoUu] 也能匹配标点符号和其他既非元音也非辅音的特殊字符。

另一个要介绍的是被称为“区间表达式”的重复机制,写作 {n,m},其中,n 是重复的最小次数,m 是重复的最大次数。如果写作 {n},则表示“只重复 n 次”​;写作 {n,},则表示“至少重复 n 次”​。

例如,正则表达式 A{5} 匹配连续 5 个“A”​,而 A{5,}至少匹配连续 5 个“A”​。

8、搜索SSN

你需要一个能够匹配社会保险号(social securitynumber,SSN)的正则表达式。

美国的社会保险号由 9 位数字组成,通常划分为 3 部分:先是 3 位数字,然后是 2 位数字,最后是 4 位数字(如123-45-6789)​。有时候也可以不使用连字符,因此要在正则表达式中令连字符成为可选字符。

grep '[0-9]\{3\}-\{0,1\}[0-9]\{2\}-\{0,1\}[0-9]\{4\}' datafile

这种正则表达式经常被戏称为只写表达式,意思是很难或压根无法阅读。这里我们将其一一分解来帮助你理解。一般而言,只要是涉及正则表达式的 bash 脚本,一定要在正则表达式附近加上注释,解释清楚要匹配的内容。

在正则表达式中加入一些额外的空格能够改善可读性,让视觉效果更加轻松,但这同时也会改变正则表达式的含义,变成了需要在这些位置上匹配空格字符。暂时忽略这一点,先在其中插入一些空格,方便阅读。

[0-9]\{3\} -\{0,1\} [0-9]\{2\} -\{0,1\} [0-9]\{4\}

第一部分匹配 3 个任意数字。第二部分匹配 0 次或 1 次连字符。第三部分匹配 2 个任意数字。第四部分匹配 0 次或1 次连字符。最后一部分匹配 4 个任意数字。

9、搜索压缩文件

你需要搜索压缩文件。是不是非得先解压缩?

如果系统中提供了 zgrep、zcat 或 gzcat,那就不必了。

zgrep 就是能够理解各类压缩文件和未压缩文件(具体类型视系统而定)的 grep。通常来说,在 Linux 系统中搜索syslog 消息时就得用上它,因为日志轮替机制不会压缩当前日志文件(这样才不会影响正常使用)​,但会用 gzip压缩归档日志。

zgrep 'search term' /var/log/messages*

zcat 就是能够理解各类压缩文件和未压缩文件(具体类型视系统而定)的 cat。它比 zgrep 理解的格式还要多,而且很多系统已经默认安装了。zcat 还可用于恢复受损的压缩文件,因为它会尽可能输出所有内容,而不像 gunzip或其他工具那样出错。

zcat /var/log/messages.1.gz

gzcat 类似于 zcat,二者的区别在于商业软件与自由软件之分,以及向后兼容性。

10、保留部分输出

你需要用某种方法保留部分输出,丢弃其余输出。

以下代码会打印出所有输入行的第一个字段。

awk '{print $1}' myinput.file

字段之间以空白字符分隔。实用工具 awk 从命令行上指定的文件中读取数据,如果没有指定文件,则从标准输入读取。因此,你也可以将输入重定向到文件,如下所示:

awk '{print $1}' < myinput.file

或者通过管道传入:

cat myinput.file | awk '{print $1}'

awk 的用法多变。最简单的用法就是从输入中打印出所选的一个或多个字段。

字段之间以空白字符分隔(也可以用 -F 选项指定分隔字符)​,编号从 1 开始。字段 $0 代表整个输入行。

awk 是一门完备的编程语言。awk 脚本可以变得极为复杂。这个实例仅仅是个开始而已。

11、仅保留部分输入行

你只想保留部分输入行,例如第一个和最后一个字段。举例来说,你希望 ls 只列出文件名和权限,不需要 ls -l 所提供的其他信息。可惜的是,ls 并没有相应的选项能够按照这种方式限制输出。

可以通过管道将 ls 的输出传给 awk,并从中挑选出你需要的字段。

$ ls -l | awk '{print $1, $NF}'
total 151130
-rw-r--r-- add.1
drwxr-xr-x art
drwxr-xr-x bin
-rw-r--r-- BuddyIcon.png
drwxr-xr-x CDs
drwxr-xr-x downloads
drwxr-sr-x eclipse
...
$

思考 ls -l 命令的输出。其形式如下所示:

drwxr-xr-x 2 username group        176 2006-10-28 20:09 bin

对于 awk 而言,解析这种输出易如反掌(在 awk 中,默认的字段分隔符为空白字符)​。

在输出文件名时,我们用了点小技巧。在 awk 中,各种字段是用美元符号和字段编号来引用的(如$1、$2、$3)​,而且 awk 还有一个内建变量 NF,其中保存着当前行中的字段总数,$NF 总是引用最后一个字段。​(例如,ls 的输出行共有 8 个字段,因此变量 NF 的值就是 8,$NF 指向的就是输入行中的第 8 个字段,在这个例子中就是文件名)​。

记住,读取 awk 变量时不需要使用 $(这一点和 bash 变量不同)​。NF 本身就是一个有效的变量引用。在其之前加上 $ 就将其含义从“当前行的字段总数”改成了“当前行的最后一个字段”​。

12、颠倒每行的单词

你想按照逆序输出输入行中的单词。

$ awk '{
>     for (i=NF; i>=0; i--) {
>         printf "%s ", $i;
>     }
>     printf "\n"
> }' <filename>

字符 > 不用你输入,shell 会输出该字符来提醒你还没有敲完命令(shell 在查找能配对的单引号)​。由于 awk 程序位于单引号中,因此 bash shell 允许我们输入多行代码,同时使用 > 作为辅助提示符,直到我们给出与先前匹配的结束单引号。考虑到可读性,我们在程序中加入了空白字符,不过也完全可以写成一行。

$ awk '{for (i=NF; i>=0; i--) {printf "%s ", $i;} printf "\n" }'<filename>

awk 语言的 for 循环语法和 C 语言中的非常相似。前者甚至还支持用 printf 进行格式化输出,这自然也是从 C 语言(以及 bash)中学来的。我们用 for 循环从最后一个字段开始倒着处理到第一个字段,同时输出每个字段的内容。

我们特意没有在第一个 printf 处加上 \n,这是因为想将多个字段输出在同一行中。for 循环结束后,输出换行符来结束输出行。

对于 $i 的引用,awk 与 bash 的含义大相径庭。在 bash中,$i 表示要获取保存在变量 i 中的值。但在 awk 中,和大多数编程语言一样,要想引用变量 i 的值,写作 i 即可。那么 $i 在 awk 中是什么意思呢?变量 i 的值被解析成一个数字,然后“美元符号–数字”的表达式会被理解为引用某个输入字段(或单词)​,即第 i 个字段。因此,随着 i 从最后一个字段倒计到第一个字段,for 循环也就以逆序的方式输出了各个字段的内容。

13、汇总数字列表

你需要汇总数字列表,其中有些数字并未出现在行中。

用 awk 先过滤出待汇总的字段,然后再做汇总。这里我们要对 ls -l 命令输出的文件大小进行汇总。

ls -l | awk '{sum += $5}; END {print sum}'

我们要汇总 ls -l 输出的第 5 个字段。ls -l 的输出如下所示:

-rw-r--r-- 1 albing users 267 2005-09-26 21:26 lilmax

各个字段分别为:权限、链接、所有者、所属组、大小(以字节为单位)​、最后一次修改日期、最后一次修改时间,以及文件名。我们只对文件大小感兴趣,因此在 awk程序中用 $5 来引用该字段。

我们在花括号({})里放置了两段 awk 代码,注意,awk程序中可以有多个代码段(或代码块)​。前有关键词 END的代码块仅在程序其他部分完成后运行一次。同样,你可以在代码块前添加 BEGIN 关键词并提供读取输入前运行的代码。BEGIN 代码块可用于初始化变量,这里我们用它来初始化 sum,不过 awk 能够确保变量内容一开始为空。

如果查看 ls -l 命令的输出,你会注意到第一行是文件总量信息,与其他行的预期格式不一致。

我们有两种处理方法。第一种方法是,假装该行不存在,之前的解决方案用到的就是这个方法。因为用不着的这行并没有第 5 个字段,所以 $5 为空,汇总结果不会受到影响。

更负责的做法,即第二种方法是跳过这一行。将 ls -l 命令的输出传给 awk 之前,先使用 grep。

ls -l | grep -v '^total' | awk '{sum += $5}; END {print sum}'

或者在 awk 中完成类似操作。

ls -l | awk '/^total/{next} {sum += $5}; END {print sum}'

^total 是一个正则表达式,意思是“位于行首的字母 t-o-t-a-l”​(开头的 ^ 将搜索锚定在行首)​。对于匹配该正则表达式的输入行,执行与之关联的代码块。第二个代码块(sum)前面什么都没有,这意味着 awk 会对所有输入行执行该代码块(不管是否匹配正则表达式)​。

针对“total”进行特殊处理的目的是想要将其从汇总过程中排除。因此,在 ^total 对应的代码块中,我们使用了next 命令,它会停止处理该输入行,并从下一行输入重新开始。因为下一行并不是以“total”起始,所以 awk 将对其执行第二个代码块。我们也可以用 getline 代替 next命令。getline 并不会重新匹配开头的模式,而是继续往后匹配。注意,在 awk 编程中,代码块的顺序很重要。

14、用awk统计字符串出现次数

你需要统计多个不同字符串出现的次数,其中包括一些事先不知道具体是什么的字符串。也就是说,你要统计的并不是已经确定好的一组字符串的出现次数。相反,你会在数据中碰到一些未知字符串并对其进行统计。

可以用 awk 的关联数组(其他语言中也称为散列或字典)来搞定。

在这个例子中,我们要统计系统中各个用户都拥有多少文件。ls -l 输出的第三个字段就是用户名,因此使用该字段($3)作为数组索引,并增加数组元素​。

#!/usr/bin/awk -f
# 实例文件:asar.awk
# Awk关联数组
# 用法:ls -lR /usr/local | asar.awk

NF > 7 {
    user[$3]++
}

END {
    for (i in user) {
        printf "%s owns %d files\n", i, user[i]
    }
}

这里我们调用 awk 的方式有点不一样。由于这个 awk 脚本有些复杂,我们将它放在了单独的文件中。-f 选项告诉awk 从文件中读取脚本,不过也可以在脚本中使用shebang 行 3#!/usr/bin/awk。

$ ls -lR /usr/local | awk -f asar.awk
bin owns 68 files
albing owns 1801 files
root owns 13755 files
man owns 11491 files
$

我们用条件 NF > 7 来限制执行部分 awk 脚本,以此过滤不包含文件名的那些行,此类行出现在 ls -lR 的输出中是为了提高可读性,其中包括用于分隔不同目录的空行以及每个子目录中的文件总量信息。这种行包含的字段(单词)不多。左花括号前的 NF > 7 并没有出现在斜线中,这表明它并非正则表达式。它其实就是一个条件表达式,和if 语句中使用的差不多,用于判断条件的真假。NF 是一个特殊的内建变量,其中保存着当前输入行的字段总数。因此,如果某个输入行的字段(单词)数超过了 7 个,则由花括号中的语句进行处理。

最重要的是下面这行。

user[$3]++

这里用户名(如 bin)被用作数组索引。之所以称为关联数组,是因为使用散列表(或类似机制)将每个独一的字符串与数值关联了起来。这些事情已经由 awk 在幕后帮你做好了;字符串比较或查找之类的都不用你操心。

建立好关联数组之后,似乎不那么容易获得数组元素的值了。为此,awk 提供了一种特殊形式的 for 循环。与数字形式的 for(i=0; i<max; i++) 不同,它有专用于关联数组的语法。

for (i in user)

在该语句中,变量 i 连续从数组 user 的索引中获得值(没有什么特定顺序)​。在示例中,这意味着 i 在每次循环中都会得到一个值(bin、albing、root、man)​。如果以前还没见识过关联数组,希望这次能让你大开眼界,印象深刻。关联数组是 awk(以及 Perl)的一个强大特性。

15、用bash统计字符串出现次数

你需要统计多个字符串出现的次数,其中包括一些事先不知道具体是什么的字符串。也就是说,你要统计的并不是已经确定好的一组字符串的出现次数。相反,你会在数据中碰到一些未知字符串并需要对其进行统计。

如果使用的是 bash 4.0 或更高版本,那么可以用关联数组(其他语言中也称为散列或字典)来搞定。

在这个例子中,我们要统计系统中的各个用户都拥有多少文件。ls -l 输出的第 3 个字段就是用户名,因此我们用该字段($3)作为数组索引,并增加数组元素​。

# 实例文件:cnt_owner
# 使用bash统计文件所有者
# 将“ls -l”的输出通过管道传入该脚本

declare -A AACOUNT
while read  -a LSL
do
    # 只考虑包含7个及以上字段的行
    if (( ${#LSL[*]} > 7 ))         # 数组大小
    then
        NDX=${LSL[3]}               # 字符串赋值
        (( AACOUNT[${NDX}] += 1 ))  # 算术递增
    fi
done

for VALS in "${!AACOUNT[@]}"        # 各个元素的索引
do
    echo $VALS "owns" ${AACOUNT[$VALS]} "files"
done

我们可以像下面这样调用该脚本并获得结果。

$ ls -lR /usr/local | bash cnt_owner.sh
bin owns 68 files
root owns 13755 files
man owns 11491 files
albing owns 1801 files
$

read -a LSL 每次读取一行,将其中的各个单词(由空白字符分隔)分别赋给数组 LSL 的各元素。通过检查数组大小,我们可以确定读入了多少个单词,以此过滤不包含文件名的那些行。它们属于 ls -lR 输出的一部分,通常是为了提高可读性,因为其中包含分隔不同目录的空行和各个子目录文件总量的统计。对于我们的脚本而言,这都是些无用信息,好在这种行并不像其他行那样包含太多的字段(单词)​,不难分辨。

只有那些至少有 7 个单词的行,我们才从中提取第 3 个单词(文件所有者)并将其作为关联数组的索引。像 LSL 这样的标准数组,数组元素通过整数索引来引用。对于关联数组,索引可以是字符串。

为了输出结果,我们需要遍历数组索引。${AACOUNT[@]} 会生成数组所有元素值的列表,如果加上惊叹号变成 ${!AACOUNT[@]},则得到的是该数组所有索引的列表。

注意,输出并不遵循特定的顺序(这与散列算法的实现有关)​。要想按照用户名或文件数量排序,可以通过管道将结果传给 sort 命令。

16、用便捷的直方图展示数据

你需要为一些数据生成快捷的屏幕直方图。

使用 14 节讨论过的 awk 关联数组(参见下例)​。

#!/usr/bin/awk -f
# 实例文件:hist.awk
# 用Awk生成直方图
# 用法:ls -lR /usr/local | 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 {
    # 进行缩放
    maxm = max(user);
    for (i in user) {
        #printf "%s owns %d files\n", i, user[i]
        scaled = 60 * user[i] / maxm ;
        printf "%-10.10s [%8d]:", i, user[i]
        for (i=0; i<scaled; i++) {
            printf "#";
        }
        printf "\n";
    }
}

用 14 节中的输入来运行该脚本,可以得到:

$ ls -lR /usr/local | awk -f hist.awk
bin       [      68]:#
albing    [    1801]:#######
root      [   13755]:##################################################
man       [   11491]:##########################################
$

我们可以将 max 对应的那部分代码放进 END 代码块的开头,但这里是想告诉你也可以在 awk 中定义函数。这里用到了一个比较复杂的 printf 语句。字符串格式%-10.10s 会将字符串左对齐并填充至 10 个字符宽,同时限制长度为 10 个字符,多出的字符会被全部截断。整数格式 %8d 确保输出的整数宽度为 8 个字符。通过使用相同的屏幕空间(无论用户名的长度或整数大小是多少)​,可以确保每个直方图的起始位置相同

与 awk 中的所有算术运算一样,缩放计算使用浮点数完成,除非调用内建的 int() 函数来明确截断结果。我们没有这么做,这意味着 for 循环至少会执行一次,这样一来,最少量的数据也会显示一个 # 标记。

for(i in user) 循环返回的数据并不遵循特定顺序,可能是基于底层散列表的某些便利排序(convenientordering)​。要想按照文件数量或用户名排序显示直方 图,那就得加入一些排序处理。一种方法是将脚本拆成两部分,将第一部分的输出发送给 sort 命令,然后通过管道将排序后的结果传给第二部分,以输出直方图。

17、用bash轻松实现直方图

你想在不借助外部程序的情况下用 bash 计算并绘制直方图。有没有可能?

当然没问题,这多亏了关联数组。bash 4.0 及更高版本都可使用。可以将15 节中的代码作为基础,唯一的不同在于输出部分。首先,遍历所有的数组元素值来找出其中的最大值,然后以此对直方图输出进行缩放以适应页面大小。

BIG=0
for VALS in "${!UCOUNT[@]}"
do
    if (( UCOUNT[$VALS] > BIG )) ; then BIG=${UCOUNT[$VALS]} ; fi
done

有了最大值(保存在 BIG 中)后,为每个数组元素输出一行。

#
# 输出直方图
#
for VALS in "${!UCOUNT[@]}"
do
    printf "%-9.9s [%7d]:" $VALS ${UCOUNT[$VALS]}
    # 缩放至最大值(BIG);注:整数
    SCALED=$(( ( (59 * UCOUNT[$VALS]) / BIG) +1 ))
    for ((i=0; i<SCALED; i++)) {
        printf "#"
    }
    printf "\n"
done

与 5 节中一样,${!UCOUNT[@]} 至关重要。可以通过它得到数组的索引值列表(这个示例为数组UCOUNT)​。for 循环每次获得一个值,并用它作为数组索引来获得该用户的文件数量。

我们将直方图缩放至 59 个字符宽度,然后再多加 1 个字符宽度,这是为了令任何非 0 值 4 都能在直方图上至少生成一个标记。这在 awk 版本(参见 16 节)中不是什么问题,因为 awk 使用的是浮点运算,但 bash 版本使用的是整数运算,执行除法后,过小的值会变成 0。

18、显示匹配短语之后的文本段落

你要在文档中搜索某个短语,并希望找到该短语后还能显示之后的文本段落。

假设在一个简单的文本文件中,段落就是空行之间的所有文本,因此空行的出现就意味着段落的结束。有鉴于此,我们来看看下面这个短小精悍的 awk 脚本。

$ cat para.awk
/keyphrase/ { flag=1 }
flag == 1 { print }
/^$/ { flag=0 }

$ awk -f para.awk < searchthis.txt

这里只有 3 个简单的代码块。当输入行匹配正则表达式(这里是单词“keyphrase”​)时,调用第一个代码块。只要“keyphrase”出现在输入行内的任何位置,就算匹配,然后执行该代码块。第一个代码块只干一件事:设置标志变量。

对于所有的输入行,第二个代码块都会被调用,因为其左花括号之前没有正则表达式。即便是匹配“keyphrase”的输入行,也会调用该代码块(如果不想这样,可以在第一个代码块中使用 next 语句)​。第二个代码块所做的是:仅当标志变量设置时,打印整个输入行。

第三个代码块有对应的正则表达式,如果匹配,则重置(关闭)标志变量。这里的正则表达式使用了两个带有特殊含义的字符:脱字符(^)​,如果用作正则表达式的第一个字符,就匹配行首位置;美元符号(KaTeX parse error: Expected group after '^' at position 38: …配行尾位置。因此,正则表达式 ^̲ 匹配的是一个空行,行首和行尾之间没有任何字符。

我们可以对空行使用一个稍微复杂的正则表达式,使其也能够处理只包含空白字符的行,而不只是彻头彻尾的空行。修改后的第 3 行代码如下所示:

/^[[:blank:]]*$/ { flag=0 }

Perl 程序员会喜欢本节中讨论的这种问题和解决方案,但我们是用 awk 搞定的,因为 Perl(差不多)超出了本书的讨论范围。如果了解 Perl,那么不用犹豫,你可以直接使用。如果不了解,那就全靠 awk 了。

一文学会shell编程 前些天找到了一篇之前保存在有道云的shell笔记,感觉覆盖面很广,很适合学习或者复习,所以转移到csdn上来。内容比较长,有兴趣的可以边练习边看,希望大家一起学习交流。 目录 0、基础介绍 1、相关Linux命令 2、 概念: 管道, 重定向和 backtick 3、流程控制 快捷操作符 引号 Here document.  函数 命令行参数 4、实例 文件循环程序 调试... 阅读详情

相关推荐

Linux Shell编程实例精讲

Linux Shell 是操作系统与用户之间的重要桥梁,作为命令行解释器,它不仅负责解析用户输入的命令,还具备强大的脚本编程能力,能够实现自动化任务处理。Shell 有多种实现类型,如 Bash(Bourne-Again Shell)、Zsh(Z Shell)等,它们在功能和语法上略有差异,但核心理念一致。Shell 通常通过终端启动,用户可自定义其行为,如修改提示符、设置别名和定义环境变量。Shell 的命令执行机制遵循“读取-解析-执行”的流程,理解这一机制有助于编写更高效的脚本和掌握系统行为逻辑。

weixin_26854475的博客 820

【Linux】Shell 编程规范及检查工具推荐

总结 Shell 编程规范,推荐 Shell 脚本检查工具

运维、实施交付领域知识交流 3280

解锁RK3588视觉潜能:RGA硬件加速库实战指南

本文深入探讨了如何利用RK3588芯片内置的RGA硬件加速库,解决视觉应用中图像预处理的性能瓶颈。通过实战指南,详细介绍了RGA库在YUV转RGB、图像缩放等核心操作上的应用,相比CPU软处理可大幅降低延迟与CPU占用,从而充分释放RK3588的NPU算力,构建高效的AI视觉预处理流水线。

zeta9的博客 669

Shell编程小工具

本文主要接上一篇的shell基础篇,记录和介绍一些常见的shell工具.文中包含具体用法示例和运维场景应用,如分析登录失败IP、日志合并等,并特别强调sort+uniq组合实现全局去重的技巧。所有工具操作均不影响原文件,需配合重定向保存结果。

a1464498695的博客 2021

fNIRS 公开数据集整理

关注“心仪脑”查看更多脑科学知识的分享。 关键词:数据整理、fNIRS 之前我们向大家推送了 Public Neuroscience Dataset 系列主题的第一期:《EEG公开数据集》,我们同时意识到大家对于神经科学公开数据集的需求。本期是该系列主题的第二期,我们在延续第一期初衷的基础上,整理了功能性近红外光谱(fNIRS)技术的公开数据集。 fNIRS 是一种非侵入性、便携且价格低廉的光学神经成像技术,它具有可观的空间和时间分辨率。因fNIRS技术问世较EEG晚了许多,所以应用此技术开展研究发.

psybrain的博客 4271

Dify如何实现自主学习

摘要:Dify通过构建感知-决策-执行-反馈的闭环实现"自主学习",本质是打造能自动获取信息、更新知识库并调整策略的Agent工作流。核心包括:1)建立知识库作为记忆体;2)搭建感知与执行工具获取实时信息;3)通过工作流编排决策"大脑",包含LLM决策节点、行动执行节点和反馈反思节点。典型应用如根据用户反馈优化回答风格,通过存储反思日志到知识库实现持续优化。这种机制使智能体能基于新数据和经验不断自我改进。

专注于技术研发行业技术主要有AI人工智能视觉识别、物联网技术、电商平台、app原生技术、微信小程序、鸿蒙语言开发、办公企业oa、ERP等,致力于做东半球最优秀的程序员 725

shell实例100例《一》

2019独角兽企业重金招聘Python工程师标准>>> ...

weixin_34410662的博客 2803

智能座舱音频系统集成与调试:从芯片选型到音质优化实战

在智能座舱域控制器架构中,音频系统已从简单的功放加扬声器演进为SoC、数字音频总线、外部DSP与多通道功放协同的复杂子系统。TDM总线的时隙分配、PDM麦克风阵列的信号完整性,以及AEC回声消除算法如何与硬件协同,直接决定多音区体验和语音交互的可靠性。工程实践中,音频调试的关键在于先理顺供电、接地、时钟与数据路由,再针对功放削波、采样率失配、TDM slot错位等典型问题进行系统性排查。从芯片选型到系统级指标验收,只有将音频链路视为整车电子电气架构的一个协同整体,才能真正实现从“响”到“好听”的跨越。本文结

weixin_30162121的博客 364

shell实例100例《一》~shell实例100例《十二》

shell实例100例《一》~shell实例100例《十二》

更阑一霎的博客 1682

信捷XDH PLC多轴运动控制实战:Ethercat总线下的C语言封装技巧

本文深入探讨了在信捷XDH PLC平台上,如何利用C语言对EtherCAT总线多轴运动控制进行高效封装。通过设计全局数据池、模块化状态机及用户友好接口,将底层复杂的运动控制函数库抽象为清晰、可复用的高级模块,显著提升程序稳定性与可维护性,并分享了关键的实战避坑经验。

weixin_29188043的博客 159

Cadence Sigrity

目录 一、PowerSI 1、PowerSI仿真前设置 2、PowerSI提取S参数 (1)检查叠层 (2)选择需要提取的走线和GND (3)设置端口 (4)设置提取频率和提取方式 (5)运行仿真 一、PowerSI PowerSI是一个交流分析工具,适用于频域仿真。 S参数提取 电源阻抗提取 全波参数提取 阻抗、耦合检查 谐振分析 1、PowerSI仿真前设置 打开PCB文件: 打开后如图: 设置为单色后: 仿真前的设置:

fang_yang_wa的博客 3万+

Shell编程

大数据学习基础:Shell编程

学习记录和总结 1万+

电阻基础知识及应用电路仿真(Multisim)

本文围绕硬件工程师必备的电阻知识,从基础知识、实操、应用仿真三方面做系统讲解:基础上明确电阻的核心参数、分类、串并联计算及核心特性;实操中详解贴片/色环电阻的阻值读取、贴片封装换算,重点区分0Ω电阻(贴装跳线,实现功能选通/调试等)与磁珠(高频吸噪)的差异及选型;应用上介绍限流、分压、上拉/下拉、阻抗匹配、过压/过流保护五大典型电路的设计公式、选型原则和实操要点,同时给出电阻通用的选型、测量和布线设计规范,形成完整的电阻应用知识体系。

XU157303764的博客 2774

SHELL脚本编程 源程序 (一)

#!/bin/sh echo &amp;amp;amp;quot;Mr.$USER,Today is:&amp;amp;amp;quot; echo &amp;amp;amp;amp;amp;date &amp;amp;amp;quot;+%B%d%A&amp;amp;amp;quot; echo &amp;amp;amp;quot;Wish you a lucky day!&amp;amp;amp;quot;

qdu_huihui的博客 1888

AI应用质量工程实践:从评估到监控的完整体系构建

在软件工程领域,质量保障体系是确保产品稳定可靠的核心机制,涉及度量指标、保障流程和工具链的完整闭环。随着生成式AI应用的快速发展,如何将传统软件工程中的质量门禁思想引入AI领域,成为工程实践的关键挑战。其核心原理在于构建可量化、可监控、可改进的质量评估框架,通过自动化测试、持续监控和反馈优化来管理AI输出的不确定性。这一技术价值在于将AI应用从原型阶段推向产品化,确保输出的一致性、安全性和可靠性。在实际应用场景中,开发者需要从功能性、非功能性、一致性和合规性四个维度构建评估体系,并借助LLM-as-a-Ju

weixin_28728279的博客 294

精雕软件(JDpaint) 5.5 绿色版

精雕软件(JDpaint) 5.5 绿色版精雕软件(JDpaint) 5.5 绿色版精雕软件(JDpaint) 5.5 绿色版

Shell 编程规范及检查工具(Shell Programming Standards and Inspection Tools)

Shell 编程是指使用 Shell 命令作为程序语言,设计、编写、测试、维护脚本型程序的过程,Shell 命令是 Shell 编程的实现语句和实现方法,Shell 脚本则是 Shell 编程的实现产物。本章详细介绍Shell 编程规范及检查工具

Linux运维老纪的博客 1011

LinePhone3.2.7(android端最新版本) sip客户端

SIP客户端For android:LinePhone3.2.7(最新版本),官网地址:http://www.linphone.org/

(一)shell编程之小工具

shell编程之小工具 # 本节目标 了解shell中的通配符 熟悉grep、cut、sort等小工具和shell中的通配符的使用 #一、文本处理工具 ##1. grep工具 grep是**行**过滤工具;用于根据关键字进行行过滤 语法和选项 语法: # grep [选项] '关键字' 文件名 常见选项: OPTIONS: -i: 不区分大小写 -v: 查找不包含指定内容的行,反向选择 -w: 按单词搜索 -o: 打印匹配关键字 -c: 统计匹配到的行数

weixin_45787652的博客 592

TMS_WebGMaps_1.9.8.0_Full_Source

TMS_WebGMaps_1.9.8.0_Full_Source 全源码 delphi cbuilder

Camera2Demo

在Camera2 的基础上进行封装,使得Camera2一堆初始化逻辑与Activity分离,降低耦合度,完成预览拍照并保存的操作

Shell脚本编程(一)

简单介绍Shell编程相关内容

欢迎各位大佬光临“寒舍”!请多多指点,共同进步! 1060

LINUX Shell脚本编程实例详解(一)上

LINUX Shell脚本编程实例详解(一)上 ping命令测试网段内主机的2例详解 使用linux已经有10来年了但是一直止于各种服务器的搭建和环境的调优,能力不能得到提升,今天突然下定决心学习Shell脚本编程,因此以网上广为流传的《Shell脚本100例》为基础学习、实验和笔记。 废话不说上栗子。 使用vim shell100-scanip-1.sh命令创建脚本文件 #!/bin...

小菜籽的博客 1万+

初学shell编程(一)

什么是 Shell? 简单来说:Shell编程就是对一堆Linux命令的逻辑化处理。 首先让我们从下图看看 Shell 在整个操作系统中所处的位置吧,该图的外圆描述了整个操作系统(比如 Debian/Ubuntu/Slackware 等),内圆描述了操作系统的核心(比如 Linux Kernel),而 Shell 和 GUI 一样作为用户和操作系统之间的接口。 GUI 提供了一种图形化的用户接...

大白 1858

Shell编程实例——中级shell工具(二)

使用 sort 实用工具。通过在命令行上指定文件名,你可以对一个或多个文件进行排序。

吴声子夜歌的博客 151

Shell编程(一)

Shell脚本编程是Linux系统管理和自动化运维的必备技能。作为连接用户与操作系统内核的桥梁,Shell不仅是一种强大的命令语言,更是一门功能完备的脚本编程语言。本文将全面系统地介绍Shell编程的各个方面,从基础概念到高级应用,帮助您掌握这一实用技能。无论您是系统管理员、开发人员还是运维工程师,熟练使用Shell脚本都能显著提高工作效率。让我们开始这段Shell编程的学习之旅吧!Shell是一个用C语言编写的程序,它为用户提供了访问操作系统内核服务的接口。

m0_46460826的博客 1096

linux shell命令行及脚本编程实例详解_linux之shell编程(一)

大家,周末晚上好,今天来分享linux里面的shell编程(一种脚本编程),哦不,正确的说,你或许应该早就了解过脚本编程,比如说很火的python。嗯,不扯别了,趁着周末多给大家更文,之前有过半个月没更文,真对不住各位了,还有以前的文章排版也是一塌糊涂,不过庆幸的是,现在比较稍微好多了,也离不开各位网友给的意见。下面就开始详细介绍shell了:一、shell介绍:1、shell 它英文...

weixin_39927408的博客 1184

shell编程所用的命令

shell所用的文本小工具:1、 grep工具grep是行过滤工具;用于根据关键字进行 行过滤语法: #grep [选项] '关键字' 文件名选项描述-i不区分大小写-v查找不包含指定内容的行,反向选择-w按单词搜索-o打印匹配关键字-c统计匹配到的行数-n显示行号-r逐层遍历目录查找-A显示匹配行及后面多少行-B显示匹配行及前面多少行-C显示匹配行前后多少行-l只列出匹配的文件名-L列出不匹配的文件名-e使用正则匹配-E。

2401_88474019的博客 876

SHELL编程(一)

SHELL编程1. 文件处理工具1.1 grep工具1.2 cut工具1.3 sort工具1.4 uniq工具1.5 tee工具1.6 paste工具1.7 tr工具2. 编程语言分类3. shell介绍4. shell脚本5. bash基本特性6 变量的定义1. 变量的分类3. 变量的定义规则简单的四则运算四则运算总结条件判断条件判断总结: 1. 文件处理工具 1.1 grep工具 行过滤 grep用于根据关键字进行行过滤 grep 选项 '关键字' 文件名 选项 功能 -i 不区分大

qq_52179734的博客 686
上一篇: Shell编程实例——逻辑与算术(二)
下一篇: Shell编程实例——中级shell工具(二)
吴声子夜歌
博客等级 码龄9年 2687粉丝 2262原创
评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

个

红包个数最小为10个

元

红包金额最低5元

当前余额3.43元 前往充值 >
需支付:10.00元
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付元
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值