1

别催了,别催了,这篇文章我一次性把Shell的内容说完

 1 year ago
source link: https://blog.51cto.com/boxuegu/6004169
Go to the source link to view the article. You can view the picture content, updated content and better typesetting reading experience. If the link is broken, please click the button below to view the snapshot at that time.

Shell 搜索与匹配

1、在文件中查找字符串

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

$ grep myvar *.c

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

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

$ grep myvar ../lib/*.c ../server/*.c ../cmd/*.c */*.c

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

grep 的第一个(非选项)参数可以是一个简单的字符串,也可以是更复杂的正则表达式(regexp)。正则表达式不同于 shell 的模式匹配,尽管两者有时看起来差不多。

常见错误

忘记指定 grep 的输入,例如 grep myvar。这种情况下,grep 会认为你要从 STDIN 提供输入,而你以为它会读取文件,于是 grep 就干等着,无所事事。

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

你需要找出包含特定字符串的文件,但是不想看到其所在的文本行,只用输出文件名即可,经常在线上为了搜索配置文件。

用 grep 的 -l 选项仅显示文件名即可,如下:

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

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

由于这些文件包含了你要查找的字符串,如果想据此构建一个待处理

文件的列表,选项 -l 就能派上用场了。将 grep 命令放进 $(),然后就可以在命令行上使用这些文件名了,如下:

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

删除包含字符串“This file is obsolete”的文件,我们给 rm 加上了 -i 选项,以便在删除每个文件前都先询问你。

3、不区分大小写搜索

你想要在日志文件中不区分大小写地搜索字符串(如“error”),以匹配该字符串的所有出现。用 grep 的 -i 选项忽略大小写,如下所示:

grep -i error logfile.log

不区分大小写的搜索能够找出包含“ERROR”、“error”、“Error”的日志消息,“ErrOR”和“eRrOr”这样的也不例外。该选项在查找大小写混合的单词时尤其管用,或者对于查找的内容无法确定大小时。

4、缩减搜索结果

如果搜索返回的结果不符合预期,其中包括许多并不需要的内容。将结果通过管道传给 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 串联在一起(因为前所未见、出乎意料的匹配会不断出现),逐步过滤搜索结果,直至满意,这种做法并不鲜见。

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

5、搜索更复杂的模式

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

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

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

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

如下所示,我们知道一行的某些单词,我们想模糊匹配,如下操作:

grep -E "1.*22" 2.text  // 匹配1开头,任意个字符后是22

结果如下:

1898090808098822:  

Shell 文件查找

1、查找所有的txt文件

文件系统中到处都是 txt 文件。你想将它们集中到一个位置。那么我们该如何做呢?

find 命令可以找出符合要求的所有文件并执行命令,将其移动到指定位置。例如:

find . -name '*.txt' -print -exec mv '{}'  /txts  \;

find 命令的语法和其他 Unix 命令不同,其选项并不是那种典型的连字符加上单字母,后面再跟上若干参数。find 命令的选项看起来像是简短的单词 1,依照逻辑顺序出现,并描述要查找哪些文件以及如何处理找到的文件(如果存在的话)。这种像单词一样的选项通常称为谓词(predicate)。

find 命令的第一个参数是待搜索的目录。典型用法是用点号(.)代表当前目录,不过你也可以提供一个目录列表,甚至通过指定根目录(/)来搜索整个文件系统(只要权限允许)。

示例中的第一个选项(谓词 -name)指定了要搜索的文件模式。其语法和 bash 的模式匹配语法差不多,因此 *.txt 能够匹配所有以“.txt”结尾的文件名。匹配该模式的文件被认为返回的是真(true),接着将其交给下一个谓词进行处理。

find 会遍历文件系统,将找到的文件名交给谓词测试。如果谓词返回真,就通过。如果返回假,则不再继续往下进行,会接着处理下一个文件名。

谓词 -print 很简单。它总是返回真,同时会将文件名打印到标准输出,因此,能在谓词序列中通过测试而到达这一步的文件都会输出其名称。如果不写,默认会带有这个谓词。

-exec 就有点怪异了。到达这一步的文件名都会变成接下来要执行的命令的一部分。剩下一直到 ; 的这部分就是命令,其中的 {} 会被替换成已查找到的文件名。因此,在上面的例子中,如果 find 在./txt/jazz 子目录中找到名为 1.txt 的文件,那么要执行的命令就会是:

mv ./txt/jazz/1.txt  /txts

所有匹配指定模式的文件都会执行命令。如果找到的文件数量众多,那么命令的执行次数自然也不会少。

2、提升已找到文件的处理速度

按照上面的例子,find命令会为每个名字符合要求的文件执行命令,但是当文件过多时命令自然会很慢,那么我们如何提高速度呢?

xargs 命令从标准输入中接收以空白字符分隔(指定 -0 时除外)的文件名,然后对尽可能多的文件(略微少于系统的 ARG_MAX 值,参见 15.13 节)执行指定命令。由于调用其他命令会带来不小的开销,因此使用 xargs 可以显著提升操作速度,因为它能够尽量减少命令的调用次数,而不是每个文件都调用。如下所示:

find . -name '*.txt' -print  | xargs  mv '{}'  /txts;

3、查找文件时不区分大小写

有些 TXT 文件的扩展名是 .TXT,而不是 .txt。查找时该如何兼顾两者?

用 -iname 谓词(如果使用的 find 版本支持)执行不区分大小写的搜索。例如:

find . -iname '*.txt' -print  | xargs  mv '{}'  /txts;

4、按日期查找文件

几个月前,有人给你发了一张 JPEG 图片,你接收后就保存了起来,但现在记不清放哪了。怎样才能找到这张图片呢?

使用 find 命令的 -mtime 谓词来检查文件的最后修改日期。例如:

find . -name '*.jpg' -mtime +90 -print

-mtime 谓词接受一个参数,用于指定要搜索的时间段。90 代表 90天。在数字前使用加号(+90)表明要搜索的文件是在 90 天前修改的。使用减号(-90)表明文件是在 90 天以内修改的。如果既没减号,也没加号,则表明正好就是 90 天。

find 还可以使用逻辑运算符 AND、OR、NOT,如果知道文件修改时间至少在一周(7 天)前,但不超过 14 天,那么就可以像下面这样将两个谓词结合起来。如下所示:

find . -mtime +7 -a -mtime -14 -print

5、按类型查找文件

你正在查找名称中带有单词“java”的目录。先尝试了以下命令。

find . -name '*java*' -print

找到的文件太多了,其中还包括文件系统中所有的 Java 源代码文件。使用 -type 谓词只选择目录。如下:

find . -type d -name '*java*' -print

同样,我们可以使用 -type f指定指查找文件。

我们将 -type d 放在前面,然后是 -name ‘java’。两者的顺序并不影响最终结果,但将 -type d 放在谓词列表的最前面能略微提高搜索效率:对于碰到的每个文件,先测试其是否为目录,如果是,才测试名称是否符合模式。目录的数量比文件要少一些。因此,这种测试顺序使得大部分文件不用再进一步比较名称了。

6、按内容查找文件

你之前写了一份重要的信件,并将其保存为以 .txt 为扩展名的文本文件,但现在想不起文件名的其余部分了。除此之外,唯一记得的就是信件内容中用到过单词“portend”。那么该如何查找已知部分内容的文件呢?

如果文件就在当前目录下,可以使用简单的 grep 命令。

grep -i portend *.txt

如果还没找到,我们换用一个更完备的解决方案:find 命令。使用其 -exec 选项对满足谓词的文件执行命令。你可以按下列方式使用grep 或其他实用工具:

find . -name '*.txt' -exec grep -Hi portend '{}' \;

或者也可以使用xargs

find . -name '*.txt' | xargs grep -Hi portend

Shell 文本解析awk

1、保留部分输出

你需要用某种方法保留部分输出,丢弃其余输出。比如我们日常线上日志,我们可能会输出很多属性,但是真正能用来解决实际问题的,大多是我们输出的文字信息。以下代码会打印出所有输入行的第一个字段:

awk '{print $1}' myinput.file

字段之间以空白字符分隔。实用工具 awk 从命令行上指定的文件中读取数据,如果没有指定文件,则从标准输入读取。$1代表每行以空格分割后的第一列。

除了上面的写法,我们还可以通过管道传入:

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

awk 的用法多变。最简单的用法就是从输入中打印出所选的一个或多个字段。字段之间以空白字符分隔(也可以用 -F 选项指定分隔字符),编号从 1 开始。字段 $0 代表整个输入行

2、保留部分输入行

你只想保留部分输入行,例如第一个和最后一个字段。举例来说,你希望 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 2026-10-28 20:09 bin

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

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

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

3、颠倒每行的单词

如果想按照逆序输出输入行中的单词。通过下列脚本:

$ 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 语言中的非常相似。我们用 for 循环从最后一个字段开始倒着处理到第一个字段,同时输出每个字段的内容。

4、汇总数字列表

如果你需要汇总数字列表,其中有些数字并未出现在行中。用 awk 先过滤出待汇总的字段,然后再做汇总。这里我们要对 ls -l 命令输出的文件大小进行汇总。如下:

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

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

-rw-r--r--. 1 root root  37 12月  23 21:44 2.text                                           
-rwxr--r--. 1 root root 110 12月  10 02:20 ifTest.sh

各个字段分别为:权限、链接、所有者、所属组、大小(以字节为单位)、最后一次修改日期、最后一次修改时间,以及文件名。我们只对文件大小感兴趣,因此在 awk 程序中用 $5 来引用该字段。我们在花括号({})里放置了两段 awk 代码,注意,awk 程序中可以有多个代码段(或代码块)。前有关键词 END 的代码块仅在程序其他部分完成后运行一次。

本文由传智教育博学谷教研团队发布。

如果本文对您有帮助,欢迎关注点赞;如果您有任何建议也可留言评论私信,您的支持是我坚持创作的动力。

转载请注明出处!


About Joyk


Aggregate valuable and interesting links.
Joyk means Joy of geeK