有人可以澄清这个 Bash 脚本吗

idj*_*adj 3 grep bash sed shell-script

我正在学习有关 Bash 脚本的知识,并且遇到了这个示例:

任务是编写一个 Bash 脚本,该脚本读取第一个参数给出的文件,提取与第二个参数给出的 ID 相同的那些记录,并保存在第三个参数给出的位置。

因此,该文件是一个用户列表,每行包含一个用户的信息,它看起来像这样(ID、姓名、父亲的姓名、位置、电话)

43  John  Mike  Smith  Boston  +3 685 123456
Run Code Online (Sandbox Code Playgroud)

在“过滤”之后,我必须将找到的数据写入标准输出,并进行以下修改:位置的第一个字母应与 ID 连接,然后名称应仅包含父亲姓名的第一个字母和一个没有任何前缀的电话。

给定示例的输出如下所示:

B43 John M Smith 123456
Run Code Online (Sandbox Code Playgroud)

解决办法是:

#!/bin/bash
cat $1|grep "^$2[0-9]*.*$3  +[0-9]*\ [0-9]*\ [0-9]*"|
sed "s/\([0-9]*\)\t\(.*\)\t\(.\).*\t\(.*\)\t\(.\).*\t+[0-9]*\ [0-9]*\
\([0-9]*\)/\5\1 \2 \3 \4 \6/"
Run Code Online (Sandbox Code Playgroud)

我不明白|垂直线的意义是什么——我知道它们是管道,一个“查询”的输出数据用作另一个“查询”的输入数据。通过查询,我的意思是一个 shell 命令。

我得到了grep命令的部分。

我没有得到sed命令。这是如何工作的?它如何“知道”将位置的第一个字母放在行首?

小智 6

逐行和逐管解释:

#!/bin/bash
Run Code Online (Sandbox Code Playgroud)

这就是所谓的shebang - 基本上是告诉它用程序运行这个脚本/bin/bash

cat $1
Run Code Online (Sandbox Code Playgroud)

$1是第一个脚本参数。cat $1将把作为第一个脚本参数提供的文件内容输出到标准输出。但是,由于在此之后有一个管道,grep在这种情况下,stdout 将通过管道传输到管道中下一个命令的 stdin 。

grep "^$2[0-9]*.*$3  +[0-9]*\ [0-9]*\ [0-9]*"
Run Code Online (Sandbox Code Playgroud)

这将读取标准输入(cat $1上面的输出)。您可以在这里阅读更多关于 grep 的信息:

以上将过滤正则表达式提供给它的行。正则表达式:

^$2[0-9]*.*$3  +[0-9]*\ [0-9]*\ [0-9]*
Run Code Online (Sandbox Code Playgroud)

基本上说我们想要这样的行:

  • 从第二个脚本参数($2上面)开始,
  • 然后有零个或多个数字 ( [0-9]*)
  • 后跟(几乎)任何字符的零次或多次出现
  • 后跟第三个脚本参数 ( $3)
  • 后跟两个或多个空格(+-注意这里有两个空格)
  • 后跟零个或多个数字 ( [0-9]*)
  • 后跟一个空格 ( \)
  • 后跟零个或多个数字 ( [0-9]*)
  • 后跟一个空格 ( \)
  • 后跟零个或多个数字 ( [0-9]*)

与上述匹配的所有行都将输出到 stdout。同样,sed在这种情况下,stdout 被传送到下一个命令的 stdin 。

sed "s/\([0-9]*\)\t\(.*\)\t\(.\).*\t\(.*\)\t\(.\).*\t+[0-9]*\ [0-9]*\
\([0-9]*\)/\5\1 \2 \3 \4 \6/"
Run Code Online (Sandbox Code Playgroud)

您可以阅读sed更多信息,例如在这里:

上面基本上说,对于每一行:

  • 替换 ( s/)
  • (在他的: \([0-9]*\)\t\(.*\)\t\(.\).*\t\(.*\)\t\(.\).*\t+[0-9]*\ [0-9]*\([0-9]*\)
  • (B) 有了这个: \5\1 \2 \3 \4 \6

上面标有(A)的部分又是regexp,和grep用的差不多。它说的有点规律。请注意,它具有沿线的结构X\tY\tZ\t...。这本质上说的sed是 - 匹配具有制表符(这就是什么\t意思)和中间的一些东西(X, Y, Z)的行。上面的那些东西可以分为两种方式:

  • 诸如\([0-9]*\)所谓的正则表达式捕获组之类的表达式。它们基本上由括号分隔,除了这sed比今天在正则表达式中通常的做法要老一些。例如,如果您使用了诸如http://regexpal.com/ 之类的正则表达式工具,您会使用它([0-9]*)来代替。sed需要将这些转义以表示组 - 否则它会认为它需要匹配实际的括号。可以通过提供-r命令行选项来指示它执行相反的操作
  • 转义括号外的表达式(例如 part \t+[0-9]*

捕获组是允许sed做你所要求的。注意sed命令的 (B) 部分。它是这样说的:

\5\1 \2 \3 \4 \6
Run Code Online (Sandbox Code Playgroud)

这实际上是一种很好的说法 - 替换我在这一行中匹配的第 5 个捕获组,然后是第 1 组,然后是一个空格,然后是第 2 组,等等。

为了更清楚,这里有一个示例命令供您尝试:

echo abc|sed 's/\(.\)\(.\)\(.\)/\3\2\1/'
Run Code Online (Sandbox Code Playgroud)

或者如果您想要更易于阅读的扩展正则表达式格式:

echo abc|sed -r 's/(.)(.)(.)/\3\2\1/'
Run Code Online (Sandbox Code Playgroud)

运行它并查看输出的内容 - 请注意, echo 在行中输出三个字符,并且对于sed部分.匹配(几乎)任何字符,应该清楚它适用于您的情况的内容和方式。我建议你在网上玩一些 sed 替换的例子 - 这应该是解决问题的最好方法。