在循环中的变量中使用 grep

在循环中的变量中使用 grep

我在将 grep 结果存储为循环中的变量时遇到问题。

while read file;do
  Server=$(echo $file | awk '{ print $1 }')
  FDate=$(echo $file | awk '{ print $2 }')
  ST=$(cat foobar | grep $Server | awk '{ print $3 }')
  #ST=$(grep $Server foobar | awk '{ print $3 }')

  echo "Server = $Server"
  echo "FDate = $FDate"
  echo "ST = $ST"

done < inputfile

第一个 ST var 为每次迭代提供输出“Usage: grep [Option]... Pattern [File]”,这意味着它没有正确读取命令。

被注释掉的第二个 ST var 实际上破坏了整个脚本,导致当它尝试回显时所有其他变量都为空。

现在,当我尝试在命令行上执行相同的操作时,它会起作用:

$ testme=$(cat foobar | grep Big | awk '{ print $3}'
$ echo "$testme"
tada

所以我的问题是如何将该 grep 命令存储在变量中?模式匹配只有一种可能的结果,因此我不必担心多个匹配。但循环中的每个服务器在第 3 列中可能有不同的字符串 (tada,tada1,tada2)

编辑:

输入文件有一个包含多列的服务器列表。我正在获取当前行的第 1 列中列出的服务器,并在 foobar 文件中搜索匹配项并从第 3 列中获取字符串。

我发现该脚本实际上确实有效,即使它给出了“使用情况”消息。可能是因为输入文件中的某些服务器条目尚未出现在 foobar 文件中,因此 grep 没有匹配项,但仍尝试将其通过管道传输到 awk。我不确定。

不过,我仍然想消除“使用”消息。我认为“set -o pipelinefail”可能会起作用,但我宁愿不这样做。

答案1

while read file;do                                       # 1
  Server=$(echo $file | awk '{ print $1 }')              # 2
  FDate=$(echo $file | awk '{ print $2 }')               # 3
  ST=$(cat foobar | grep $Server | awk '{ print $3 }')   # 4
  #ST=$(grep $Server foobar | awk '{ print $3 }')        # 5

grep至少需要搜索模式(或提供等效项的-eor选项),因此如果最终为空,则第 4 行和第 5 行中未加引号的内容将在-f$Server$Server分词(也可以看看 什么时候需要双引号?), 和

  • 第 4行grep没有参数。如果没有强制参数,它将打印用法描述。
  • 第 5行grep获取单个参数foobar,并将其作为模式。默认情况下,它从标准输入读取,并且在循环内部,它具有与循环相同的标准输入,因此从那里读取所有内容。

现在,整个循环让我想起了这个问题:为什么使用 shell 循环处理文本被认为是不好的做法?至少可以在一定程度上简化它。read可以将输入拆分为字段本身,因此我们可以删除命令替换。

然后,我们可能应该处理其中一个或两个值碰巧为空的情况。而且,由于 awk 也可以完成 的工作grep,所以让我们这样做:

while read server fdate; do
    if [ -z "$server" ] || [ -z "$fdate" ]; do
        continue
    fi
    ST=$(awk < foobar -v server="$server" '$0 ~ server { print $3 }')

    echo "server $server fdate $fdate ST $ST"
done < inputfile

(或者,根据您最终打算做什么,用 awk 程序替换整个内容。)

答案2

不要在 shell while 循环中使用grep和/或在其中执行此操作(或awk任何就此而言,类似于 shell 循环中的命令)。看为什么使用 shell 循环处理文本被认为是不好的做法?出于原因。简而言之:shell 非常擅长让其他程序完成工作、设置重定向和管道以及将文件名和数据提供给实际完成工作的其他程序,但它本身在完成这项工作方面却很糟糕。 Shell 速度很慢并且容易出现用户错误,例如没有对变量进行双引号(例如,您未能引用 $Server ,这"$Server"是导致问题的直接原因grep......无论如何,其中一个原因。另一个原因是您未能检查$Server 实际上是否在 awk 之后包含任何值)

无论如何,您需要的一切都可以通过一个简短的脚本完成awk。例如:

awk 'NR==FNR { fdates[$1] = $2 ; next}; # read first file into fdates array

     $1 in fdates {  # process second file
       printf "Server = %s\nFDate = %s\nST = %s\n", $1, fdates[$1], $3;
     }' inputfile foobar

用英语讲:

  • 阅读第一的文件,将其存储在名为 的关联数组中fdates,键为字段 1 (Server),值为字段 2 (FDate)

  • 当我们读取第二个文件(以及任何后续文件)时,如果服务器名称是 fdates 中的键,则打印出您想要的详细信息。

笔记:您尚未指定希望在 file 中的哪个字段中找到服务器名称foobar。上面的脚本假设它位于 的字段 1 中foobar。如果它在不同的字段中,请更改$1两行($1 in fdates线printf)以适应。

如果服务器名称可以是任何地方在一行中foobar(即没有固定的字段编号可以匹配),那么您可以像这样编写脚本:

awk 'NR==FNR { fdates[$1] = $2 ; next};
     { for (server in fdates) {
       if ($0 ~ server) {
         printf "Server = %s\nFDate = %s\nST = %s\n", server, fdates[server], $3;
       }
     }' inputfile foobar

用英语讲:

  • 阅读第一的文件,将其存储在名为 的关联数组中fdates,键为字段 1(服务器),值为字段 2(FDate)——即与第一个版本相同。

  • 然后,对于后续文件的每一行,迭代fdates数组的每个元素。如果该行的任何位置都有与键(服务器名称)匹配的正则表达式,则打印所需的详细信息。

第二个版本会比第一个版本慢一点,因为它必须对包含在其中的每个服务器名称进行正则表达式匹配fdates匹配每个线foobar.

这两个版本都会比 while read 循环快几个数量级,大约 3 次调用awk和 1 次调用catand -每次通过循环时grep读取整个文件。foobar上面的任何一个 awk 脚本都只被调用一次,并且它们只需要读取inputfile一次foobar

相关内容